Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * auth.c
4 : : * Routines to handle network authentication
5 : : *
6 : : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
7 : : * Portions Copyright (c) 1994, Regents of the University of California
8 : : *
9 : : *
10 : : * IDENTIFICATION
11 : : * src/backend/libpq/auth.c
12 : : *
13 : : *-------------------------------------------------------------------------
14 : : */
15 : :
16 : : #include "postgres.h"
17 : :
18 : : #include <sys/param.h>
19 : : #include <sys/select.h>
20 : : #include <sys/socket.h>
21 : : #include <netinet/in.h>
22 : : #include <netdb.h>
23 : : #include <pwd.h>
24 : : #include <unistd.h>
25 : :
26 : : #include "commands/user.h"
27 : : #include "common/ip.h"
28 : : #include "common/md5.h"
29 : : #include "libpq/auth.h"
30 : : #include "libpq/crypt.h"
31 : : #include "libpq/libpq.h"
32 : : #include "libpq/oauth.h"
33 : : #include "libpq/pqformat.h"
34 : : #include "libpq/sasl.h"
35 : : #include "libpq/scram.h"
36 : : #include "miscadmin.h"
37 : : #include "port/pg_bswap.h"
38 : : #include "postmaster/postmaster.h"
39 : : #include "replication/walsender.h"
40 : : #include "storage/ipc.h"
41 : : #include "tcop/backend_startup.h"
42 : : #include "utils/memutils.h"
43 : :
44 : : /*----------------------------------------------------------------
45 : : * Global authentication functions
46 : : *----------------------------------------------------------------
47 : : */
48 : : static void auth_failed(Port *port, int elevel, int status,
49 : : const char *logdetail);
50 : : static char *recv_password_packet(Port *port);
51 : :
52 : :
53 : : /*----------------------------------------------------------------
54 : : * Password-based authentication methods (password, md5, and scram-sha-256)
55 : : *----------------------------------------------------------------
56 : : */
57 : : static int CheckPasswordAuth(Port *port, const char **logdetail);
58 : : static int CheckPWChallengeAuth(Port *port, const char **logdetail);
59 : :
60 : : static int CheckMD5Auth(Port *port, char *shadow_pass,
61 : : const char **logdetail);
62 : :
63 : :
64 : : /*----------------------------------------------------------------
65 : : * Ident authentication
66 : : *----------------------------------------------------------------
67 : : */
68 : : /* Max size of username ident server can return (per RFC 1413) */
69 : : #define IDENT_USERNAME_MAX 512
70 : :
71 : : /* Standard TCP port number for Ident service. Assigned by IANA */
72 : : #define IDENT_PORT 113
73 : :
74 : : static int ident_inet(Port *port);
75 : :
76 : :
77 : : /*----------------------------------------------------------------
78 : : * Peer authentication
79 : : *----------------------------------------------------------------
80 : : */
81 : : static int auth_peer(Port *port);
82 : :
83 : :
84 : : /*----------------------------------------------------------------
85 : : * PAM authentication
86 : : *----------------------------------------------------------------
87 : : */
88 : : #ifdef USE_PAM
89 : : #ifdef HAVE_PAM_PAM_APPL_H
90 : : #include <pam/pam_appl.h>
91 : : #endif
92 : : #ifdef HAVE_SECURITY_PAM_APPL_H
93 : : #include <security/pam_appl.h>
94 : : #endif
95 : :
96 : : #define PGSQL_PAM_SERVICE "postgresql" /* Service name passed to PAM */
97 : :
98 : : /* Work around original Solaris' lack of "const" in the conv_proc signature */
99 : : #ifdef _PAM_LEGACY_NONCONST
100 : : #define PG_PAM_CONST
101 : : #else
102 : : #define PG_PAM_CONST const
103 : : #endif
104 : :
105 : : static int CheckPAMAuth(Port *port, const char *user, const char *password);
106 : : static int pam_passwd_conv_proc(int num_msg,
107 : : PG_PAM_CONST struct pam_message **msg,
108 : : struct pam_response **resp, void *appdata_ptr);
109 : :
110 : : static struct pam_conv pam_passw_conv = {
111 : : &pam_passwd_conv_proc,
112 : : NULL
113 : : };
114 : :
115 : : static const char *pam_passwd = NULL; /* Workaround for Solaris 2.6
116 : : * brokenness */
117 : : static Port *pam_port_cludge; /* Workaround for passing "Port *port" into
118 : : * pam_passwd_conv_proc */
119 : : static bool pam_no_password; /* For detecting no-password-given */
120 : : #endif /* USE_PAM */
121 : :
122 : :
123 : : /*----------------------------------------------------------------
124 : : * BSD authentication
125 : : *----------------------------------------------------------------
126 : : */
127 : : #ifdef USE_BSD_AUTH
128 : : #include <bsd_auth.h>
129 : :
130 : : static int CheckBSDAuth(Port *port, char *user);
131 : : #endif /* USE_BSD_AUTH */
132 : :
133 : :
134 : : /*----------------------------------------------------------------
135 : : * LDAP authentication
136 : : *----------------------------------------------------------------
137 : : */
138 : : #ifdef USE_LDAP
139 : : #ifndef WIN32
140 : : /* We use a deprecated function to keep the codepath the same as win32. */
141 : : #define LDAP_DEPRECATED 1
142 : : #include <ldap.h>
143 : : #else
144 : : #include <winldap.h>
145 : :
146 : : #endif
147 : :
148 : : static int CheckLDAPAuth(Port *port);
149 : :
150 : : /* LDAP_OPT_DIAGNOSTIC_MESSAGE is the newer spelling */
151 : : #ifndef LDAP_OPT_DIAGNOSTIC_MESSAGE
152 : : #define LDAP_OPT_DIAGNOSTIC_MESSAGE LDAP_OPT_ERROR_STRING
153 : : #endif
154 : :
155 : : /* Default LDAP password mutator hook, can be overridden by a shared library */
156 : : static char *dummy_ldap_password_mutator(char *input);
157 : : auth_password_hook_typ ldap_password_hook = dummy_ldap_password_mutator;
158 : :
159 : : #endif /* USE_LDAP */
160 : :
161 : : /*----------------------------------------------------------------
162 : : * Cert authentication
163 : : *----------------------------------------------------------------
164 : : */
165 : : #ifdef USE_SSL
166 : : static int CheckCertAuth(Port *port);
167 : : #endif
168 : :
169 : :
170 : : /*----------------------------------------------------------------
171 : : * Kerberos and GSSAPI GUCs
172 : : *----------------------------------------------------------------
173 : : */
174 : : char *pg_krb_server_keyfile;
175 : : bool pg_krb_caseins_users;
176 : : bool pg_gss_accept_delegation;
177 : :
178 : :
179 : : /*----------------------------------------------------------------
180 : : * GSSAPI Authentication
181 : : *----------------------------------------------------------------
182 : : */
183 : : #ifdef ENABLE_GSS
184 : : #include "libpq/be-gssapi-common.h"
185 : :
186 : : static int pg_GSS_checkauth(Port *port);
187 : : static int pg_GSS_recvauth(Port *port);
188 : : #endif /* ENABLE_GSS */
189 : :
190 : :
191 : : /*----------------------------------------------------------------
192 : : * SSPI Authentication
193 : : *----------------------------------------------------------------
194 : : */
195 : : #ifdef ENABLE_SSPI
196 : : typedef SECURITY_STATUS
197 : : (WINAPI * QUERY_SECURITY_CONTEXT_TOKEN_FN) (PCtxtHandle, void **);
198 : : static int pg_SSPI_recvauth(Port *port);
199 : : static int pg_SSPI_make_upn(char *accountname,
200 : : size_t accountnamesize,
201 : : char *domainname,
202 : : size_t domainnamesize,
203 : : bool update_accountname);
204 : : #endif
205 : :
206 : :
207 : : /*----------------------------------------------------------------
208 : : * Global authentication functions
209 : : *----------------------------------------------------------------
210 : : */
211 : :
212 : : /*
213 : : * This hook allows plugins to get control following client authentication,
214 : : * but before the user has been informed about the results. It could be used
215 : : * to record login events, insert a delay after failed authentication, etc.
216 : : */
217 : : ClientAuthentication_hook_type ClientAuthentication_hook = NULL;
218 : :
219 : : /*
220 : : * Tell the user the authentication failed, but not (much about) why.
221 : : *
222 : : * There is a tradeoff here between security concerns and making life
223 : : * unnecessarily difficult for legitimate users. We would not, for example,
224 : : * want to report the password we were expecting to receive...
225 : : * But it seems useful to report the username and authorization method
226 : : * in use, and these are items that must be presumed known to an attacker
227 : : * anyway.
228 : : * Note that many sorts of failure report additional information in the
229 : : * postmaster log, which we hope is only readable by good guys. In
230 : : * particular, if logdetail isn't NULL, we send that string to the log
231 : : * when the elevel allows.
232 : : */
233 : : static void
35 jchampion@postgresql 234 :GNC 125 : auth_failed(Port *port, int elevel, int status, const char *logdetail)
235 : : {
236 : : const char *errstr;
237 : : char *cdetail;
5782 bruce@momjian.us 238 :CBC 125 : int errcode_return = ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION;
239 : :
35 jchampion@postgresql 240 [ - + ]:GNC 125 : Assert(elevel >= FATAL); /* we must exit here */
241 : :
242 : : /*
243 : : * If we failed due to EOF from client, just quit; there's no point in
244 : : * trying to send a message to the client, and not much point in logging
245 : : * the failure in the postmaster log. (Logging the failure might be
246 : : * desirable, were it not for the fact that libpq closes the connection
247 : : * unceremoniously if challenged for a password when it hasn't got one to
248 : : * send. We'll get a useless log entry for every psql connection under
249 : : * password auth, even if it's perfectly successful, if we log STATUS_EOF
250 : : * events.)
251 : : */
6486 magnus@hagander.net 252 [ + + ]:CBC 125 : if (status == STATUS_EOF)
253 : 27 : proc_exit(0);
254 : :
6441 255 [ - + - + : 98 : switch (port->hba->auth_method)
+ + - - -
- + + -
- ]
256 : : {
6486 magnus@hagander.net 257 :UBC 0 : case uaReject:
258 : : case uaImplicitReject:
259 : 0 : errstr = gettext_noop("authentication failed for user \"%s\": host rejected");
260 : 0 : break;
6486 magnus@hagander.net 261 :CBC 1 : case uaTrust:
262 : 1 : errstr = gettext_noop("\"trust\" authentication failed for user \"%s\"");
263 : 1 : break;
6486 magnus@hagander.net 264 :UBC 0 : case uaIdent:
265 : 0 : errstr = gettext_noop("Ident authentication failed for user \"%s\"");
266 : 0 : break;
5526 magnus@hagander.net 267 :CBC 6 : case uaPeer:
268 : 6 : errstr = gettext_noop("Peer authentication failed for user \"%s\"");
269 : 6 : break;
6486 270 : 5 : case uaPassword:
271 : : case uaMD5:
272 : : case uaSCRAM:
273 : 5 : errstr = gettext_noop("password authentication failed for user \"%s\"");
274 : : /* We use it to indicate if a .pgpass password failed. */
5897 bruce@momjian.us 275 : 5 : errcode_return = ERRCODE_INVALID_PASSWORD;
6486 magnus@hagander.net 276 : 5 : break;
5823 tgl@sss.pgh.pa.us 277 : 6 : case uaGSS:
278 : 6 : errstr = gettext_noop("GSSAPI authentication failed for user \"%s\"");
279 : 6 : break;
5823 tgl@sss.pgh.pa.us 280 :UBC 0 : case uaSSPI:
281 : 0 : errstr = gettext_noop("SSPI authentication failed for user \"%s\"");
282 : 0 : break;
6486 magnus@hagander.net 283 : 0 : case uaPAM:
284 : 0 : errstr = gettext_noop("PAM authentication failed for user \"%s\"");
285 : 0 : break;
3679 tgl@sss.pgh.pa.us 286 : 0 : case uaBSD:
287 : 0 : errstr = gettext_noop("BSD authentication failed for user \"%s\"");
288 : 0 : break;
6486 magnus@hagander.net 289 : 0 : case uaLDAP:
290 : 0 : errstr = gettext_noop("LDAP authentication failed for user \"%s\"");
291 : 0 : break;
5823 tgl@sss.pgh.pa.us 292 :CBC 1 : case uaCert:
293 : 1 : errstr = gettext_noop("certificate authentication failed for user \"%s\"");
294 : 1 : break;
439 dgustafsson@postgres 295 : 79 : case uaOAuth:
296 : 79 : errstr = gettext_noop("OAuth bearer authentication failed for user \"%s\"");
297 : 79 : break;
6486 magnus@hagander.net 298 :UBC 0 : default:
299 : 0 : errstr = gettext_noop("authentication failed for user \"%s\": invalid authentication method");
300 : 0 : break;
301 : : }
302 : :
1030 peter@eisentraut.org 303 :CBC 98 : cdetail = psprintf(_("Connection matched file \"%s\" line %d: \"%s\""),
1287 michael@paquier.xyz 304 : 98 : port->hba->sourcefile, port->hba->linenumber,
305 : 98 : port->hba->rawline);
4446 sfrost@snowman.net 306 [ + + ]: 98 : if (logdetail)
307 : 7 : logdetail = psprintf("%s\n%s", logdetail, cdetail);
308 : : else
309 : 91 : logdetail = cdetail;
310 : :
35 jchampion@postgresql 311 [ + - + - ]:GNC 98 : ereport(elevel,
312 : : (errcode(errcode_return),
313 : : errmsg(errstr, port->user_name),
314 : : logdetail ? errdetail_log("%s", logdetail) : 0));
315 : :
316 : : /* doesn't return */
35 jchampion@postgresql 317 :UNC 0 : pg_unreachable();
318 : : }
319 : :
320 : :
321 : : /*
322 : : * Sets the authenticated identity for the current user. The provided string
323 : : * will be stored into MyClientConnectionInfo, alongside the current HBA
324 : : * method in use. The ID will be logged if log_connections has the
325 : : * 'authentication' option specified.
326 : : *
327 : : * Auth methods should call this routine exactly once, as soon as the user is
328 : : * successfully authenticated, even if they have reasons to know that
329 : : * authorization will fail later.
330 : : *
331 : : * The provided string will be copied into TopMemoryContext, to match the
332 : : * lifetime of MyClientConnectionInfo, so it is safe to pass a string that is
333 : : * managed by an external library.
334 : : */
335 : : void
1854 michael@paquier.xyz 336 :CBC 202 : set_authn_id(Port *port, const char *id)
337 : : {
338 [ - + ]: 202 : Assert(id);
339 : :
1350 340 [ - + ]: 202 : if (MyClientConnectionInfo.authn_id)
341 : : {
342 : : /*
343 : : * An existing authn_id should never be overwritten; that means two
344 : : * authentication providers are fighting (or one is fighting itself).
345 : : * Don't leak any authn details to the client, but don't let the
346 : : * connection continue, either.
347 : : */
1854 michael@paquier.xyz 348 [ # # ]:UBC 0 : ereport(FATAL,
349 : : (errmsg("authentication identifier set more than once"),
350 : : errdetail_log("previous identifier: \"%s\"; new identifier: \"%s\"",
351 : : MyClientConnectionInfo.authn_id, id)));
352 : : }
353 : :
1350 michael@paquier.xyz 354 :CBC 202 : MyClientConnectionInfo.authn_id = MemoryContextStrdup(TopMemoryContext, id);
355 : 202 : MyClientConnectionInfo.auth_method = port->hba->auth_method;
356 : :
419 melanieplageman@gmai 357 [ + + ]: 202 : if (log_connections & LOG_CONNECTION_AUTHENTICATION)
358 : : {
1854 michael@paquier.xyz 359 [ + - ]: 175 : ereport(LOG,
360 : : errmsg("connection authenticated: identity=\"%s\" method=%s "
361 : : "(%s:%d)",
362 : : MyClientConnectionInfo.authn_id,
363 : : hba_authname(MyClientConnectionInfo.auth_method),
364 : : port->hba->sourcefile, port->hba->linenumber));
365 : : }
366 : 202 : }
367 : :
368 : :
369 : : /*
370 : : * Client authentication starts here. If there is an error, this
371 : : * function does not return and the backend process is terminated.
372 : : */
373 : : void
6486 magnus@hagander.net 374 : 14688 : ClientAuthentication(Port *port)
375 : : {
376 : 14688 : int status = STATUS_ERROR;
1575 michael@paquier.xyz 377 : 14688 : const char *logdetail = NULL;
378 : :
379 : : /*
380 : : * "Abandoned" is a SASL-specific state similar to STATUS_EOF, in that we
381 : : * don't want to generate any server logs. But it's caused by an in-band
382 : : * client action that requires a server response, not an out-of-band
383 : : * connection closure, so we can't just proc_exit() like we do with
384 : : * STATUS_EOF.
385 : : */
35 jchampion@postgresql 386 :GNC 14688 : bool abandoned = false;
387 : :
388 : : /*
389 : : * Get the authentication method to use for this frontend/database
390 : : * combination. Note: we do not parse the file at this point; this has
391 : : * already been done elsewhere. hba.c dropped an error message into the
392 : : * server logfile if parsing the hba config file failed.
393 : : */
5433 alvherre@alvh.no-ip. 394 :CBC 14688 : hba_getauthmethod(port);
395 : :
6093 tgl@sss.pgh.pa.us 396 [ - + ]: 14688 : CHECK_FOR_INTERRUPTS();
397 : :
398 : : /*
399 : : * This is the first point where we have access to the hba record for the
400 : : * current connection, so perform any verifications based on the hba
401 : : * options field that should be done *before* the authentication here.
402 : : */
2614 magnus@hagander.net 403 [ + + ]: 14688 : if (port->hba->clientcert != clientCertOff)
404 : : {
405 : : /* If we haven't loaded a root certificate store, fail */
3410 tgl@sss.pgh.pa.us 406 [ + + ]: 38 : if (!secure_loaded_verify_locations())
3410 tgl@sss.pgh.pa.us 407 [ + - ]:GBC 2 : ereport(FATAL,
408 : : (errcode(ERRCODE_CONFIG_FILE_ERROR),
409 : : errmsg("client certificates can only be checked if a root certificate store is available")));
410 : :
411 : : /*
412 : : * If we loaded a root certificate store, and if a certificate is
413 : : * present on the client, then it has been verified against our root
414 : : * certificate store, and the connection would have been aborted
415 : : * already if it didn't verify ok.
416 : : */
4285 heikki.linnakangas@i 417 [ + + ]:CBC 36 : if (!port->peer_cert_valid)
6375 magnus@hagander.net 418 [ + - ]: 6 : ereport(FATAL,
419 : : (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
420 : : errmsg("connection requires a valid client certificate")));
421 : : }
422 : :
423 : : /*
424 : : * Now proceed to do the actual authentication check
425 : : */
6441 426 [ - + + - : 14680 : switch (port->hba->auth_method)
+ - + + -
- - + + -
- ]
427 : : {
6486 magnus@hagander.net 428 :UBC 0 : case uaReject:
429 : :
430 : : /*
431 : : * An explicit "reject" entry in pg_hba.conf. This report exposes
432 : : * the fact that there's an explicit reject entry, which is
433 : : * perhaps not so desirable from a security standpoint; but the
434 : : * message for an implicit reject could confuse the DBA a lot when
435 : : * the true situation is a match to an explicit reject. And we
436 : : * don't want to change the message for an implicit reject. As
437 : : * noted below, the additional information shown here doesn't
438 : : * expose anything not known to an attacker.
439 : : */
440 : : {
441 : : char hostinfo[NI_MAXHOST];
442 : : const char *encryption_state;
443 : :
5860 simon@2ndQuadrant.co 444 : 0 : pg_getnameinfo_all(&port->raddr.addr, port->raddr.salen,
445 : : hostinfo, sizeof(hostinfo),
446 : : NULL, 0,
447 : : NI_NUMERICHOST);
448 : :
1954 tgl@sss.pgh.pa.us 449 : 0 : encryption_state =
450 : : #ifdef ENABLE_GSS
451 [ # # # # ]: 0 : (port->gss && port->gss->enc) ? _("GSS encryption") :
452 : : #endif
453 : : #ifdef USE_SSL
454 [ # # ]: 0 : port->ssl_in_use ? _("SSL encryption") :
455 : : #endif
456 : 0 : _("no encryption");
457 : :
1897 akapila@postgresql.o 458 [ # # # # ]: 0 : if (am_walsender && !am_db_walsender)
5858 tgl@sss.pgh.pa.us 459 [ # # ]: 0 : ereport(FATAL,
460 : : (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
461 : : /* translator: last %s describes encryption state */
462 : : errmsg("pg_hba.conf rejects replication connection for host \"%s\", user \"%s\", %s",
463 : : hostinfo, port->user_name,
464 : : encryption_state)));
465 : : else
466 [ # # ]: 0 : ereport(FATAL,
467 : : (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
468 : : /* translator: last %s describes encryption state */
469 : : errmsg("pg_hba.conf rejects connection for host \"%s\", user \"%s\", database \"%s\", %s",
470 : : hostinfo, port->user_name,
471 : : port->database_name,
472 : : encryption_state)));
473 : : break;
474 : : }
475 : :
5860 simon@2ndQuadrant.co 476 :CBC 69 : case uaImplicitReject:
477 : :
478 : : /*
479 : : * No matching entry, so tell the user we fell through.
480 : : *
481 : : * NOTE: the extra info reported here is not a security breach,
482 : : * because all that info is known at the frontend and must be
483 : : * assumed known to bad guys. We're merely helping out the less
484 : : * clueful good guys.
485 : : */
486 : : {
487 : : char hostinfo[NI_MAXHOST];
488 : : const char *encryption_state;
489 : :
6486 magnus@hagander.net 490 : 69 : pg_getnameinfo_all(&port->raddr.addr, port->raddr.salen,
491 : : hostinfo, sizeof(hostinfo),
492 : : NULL, 0,
493 : : NI_NUMERICHOST);
494 : :
1954 tgl@sss.pgh.pa.us 495 : 31 : encryption_state =
496 : : #ifdef ENABLE_GSS
497 [ + + + - ]: 69 : (port->gss && port->gss->enc) ? _("GSS encryption") :
498 : : #endif
499 : : #ifdef USE_SSL
500 [ + + ]: 31 : port->ssl_in_use ? _("SSL encryption") :
501 : : #endif
502 : 21 : _("no encryption");
503 : :
504 : : #define HOSTNAME_LOOKUP_DETAIL(port) \
505 : : (port->remote_hostname ? \
506 : : (port->remote_hostname_resolv == +1 ? \
507 : : errdetail_log("Client IP address resolved to \"%s\", forward lookup matches.", \
508 : : port->remote_hostname) : \
509 : : port->remote_hostname_resolv == 0 ? \
510 : : errdetail_log("Client IP address resolved to \"%s\", forward lookup not checked.", \
511 : : port->remote_hostname) : \
512 : : port->remote_hostname_resolv == -1 ? \
513 : : errdetail_log("Client IP address resolved to \"%s\", forward lookup does not match.", \
514 : : port->remote_hostname) : \
515 : : port->remote_hostname_resolv == -2 ? \
516 : : errdetail_log("Could not translate client host name \"%s\" to IP address: %s.", \
517 : : port->remote_hostname, \
518 : : gai_strerror(port->remote_hostname_errcode)) : \
519 : : 0) \
520 : : : (port->remote_hostname_resolv == -2 ? \
521 : : errdetail_log("Could not resolve client IP address to a host name: %s.", \
522 : : gai_strerror(port->remote_hostname_errcode)) : \
523 : : 0))
524 : :
1897 akapila@postgresql.o 525 [ - + - - ]: 69 : if (am_walsender && !am_db_walsender)
5858 tgl@sss.pgh.pa.us 526 [ # # # # :UBC 0 : ereport(FATAL,
# # # # #
# # # #
# ]
527 : : (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
528 : : /* translator: last %s describes encryption state */
529 : : errmsg("no pg_hba.conf entry for replication connection from host \"%s\", user \"%s\", %s",
530 : : hostinfo, port->user_name,
531 : : encryption_state),
532 : : HOSTNAME_LOOKUP_DETAIL(port)));
533 : : else
5858 tgl@sss.pgh.pa.us 534 [ + - + + :CBC 69 : ereport(FATAL,
- + + - -
- - - -
+ ]
535 : : (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
536 : : /* translator: last %s describes encryption state */
537 : : errmsg("no pg_hba.conf entry for host \"%s\", user \"%s\", database \"%s\", %s",
538 : : hostinfo, port->user_name,
539 : : port->database_name,
540 : : encryption_state),
541 : : HOSTNAME_LOOKUP_DETAIL(port)));
542 : : break;
543 : : }
544 : :
6486 magnus@hagander.net 545 : 45 : case uaGSS:
546 : : #ifdef ENABLE_GSS
547 : : /* We might or might not have the gss workspace already */
1954 tgl@sss.pgh.pa.us 548 [ + + ]: 45 : if (port->gss == NULL)
549 : 13 : port->gss = (pg_gssinfo *)
550 : 13 : MemoryContextAllocZero(TopMemoryContext,
551 : : sizeof(pg_gssinfo));
2589 sfrost@snowman.net 552 : 45 : port->gss->auth = true;
553 : :
554 : : /*
555 : : * If GSS state was set up while enabling encryption, we can just
556 : : * check the client's principal. Otherwise, ask for it.
557 : : */
558 [ + + ]: 45 : if (port->gss->enc)
559 : 32 : status = pg_GSS_checkauth(port);
560 : : else
561 : : {
562 : 13 : sendAuthRequest(port, AUTH_REQ_GSS, NULL, 0);
563 : 13 : status = pg_GSS_recvauth(port);
564 : : }
565 : : #else
566 : : Assert(false);
567 : : #endif
6486 magnus@hagander.net 568 : 45 : break;
569 : :
6486 magnus@hagander.net 570 :UBC 0 : case uaSSPI:
571 : : #ifdef ENABLE_SSPI
572 : : if (port->gss == NULL)
573 : : port->gss = (pg_gssinfo *)
574 : : MemoryContextAllocZero(TopMemoryContext,
575 : : sizeof(pg_gssinfo));
576 : : sendAuthRequest(port, AUTH_REQ_SSPI, NULL, 0);
577 : : status = pg_SSPI_recvauth(port);
578 : : #else
6403 579 : 0 : Assert(false);
580 : : #endif
581 : : break;
582 : :
5526 magnus@hagander.net 583 :CBC 28 : case uaPeer:
584 : 28 : status = auth_peer(port);
585 : 28 : break;
586 : :
5526 magnus@hagander.net 587 :UBC 0 : case uaIdent:
588 : 0 : status = ident_inet(port);
6486 589 : 0 : break;
590 : :
6486 magnus@hagander.net 591 :CBC 74 : case uaMD5:
592 : : case uaSCRAM:
3329 heikki.linnakangas@i 593 : 74 : status = CheckPWChallengeAuth(port, &logdetail);
6486 magnus@hagander.net 594 : 74 : break;
595 : :
596 : 19 : case uaPassword:
3438 heikki.linnakangas@i 597 : 19 : status = CheckPasswordAuth(port, &logdetail);
6486 magnus@hagander.net 598 : 19 : break;
599 : :
6486 magnus@hagander.net 600 :UBC 0 : case uaPAM:
601 : : #ifdef USE_PAM
602 : 0 : status = CheckPAMAuth(port, port->user_name, "");
603 : : #else
604 : : Assert(false);
605 : : #endif /* USE_PAM */
6403 606 : 0 : break;
607 : :
3679 tgl@sss.pgh.pa.us 608 : 0 : case uaBSD:
609 : : #ifdef USE_BSD_AUTH
610 : : status = CheckBSDAuth(port, port->user_name);
611 : : #else
612 : 0 : Assert(false);
613 : : #endif /* USE_BSD_AUTH */
614 : : break;
615 : :
6486 magnus@hagander.net 616 : 0 : case uaLDAP:
617 : : #ifdef USE_LDAP
618 : 0 : status = CheckLDAPAuth(port);
619 : : #else
620 : : Assert(false);
621 : : #endif
6375 622 : 0 : break;
2614 magnus@hagander.net 623 :CBC 14326 : case uaCert:
624 : : /* uaCert will be treated as if clientcert=verify-full (uaTrust) */
625 : : case uaTrust:
6486 626 : 14326 : status = STATUS_OK;
627 : 14326 : break;
439 dgustafsson@postgres 628 : 119 : case uaOAuth:
32 jchampion@postgresql 629 :GNC 119 : status = CheckSASLAuth(&pg_be_oauth_mech, port, NULL, &logdetail,
630 : : &abandoned);
439 dgustafsson@postgres 631 :CBC 117 : break;
632 : : }
633 : :
2614 magnus@hagander.net 634 [ + + + + ]: 14609 : if ((status == STATUS_OK && port->hba->clientcert == clientCertFull)
635 [ - + ]: 14580 : || port->hba->auth_method == uaCert)
636 : : {
637 : : /*
638 : : * Make sure we only check the certificate if we use the cert method
639 : : * or verify-full option.
640 : : */
641 : : #ifdef USE_SSL
642 : 29 : status = CheckCertAuth(port);
643 : : #else
644 : : Assert(false);
645 : : #endif
646 : : }
647 : :
419 melanieplageman@gmai 648 [ + + + + ]: 14609 : if ((log_connections & LOG_CONNECTION_AUTHENTICATION) &&
649 : 418 : status == STATUS_OK &&
983 michael@paquier.xyz 650 [ + + ]: 418 : !MyClientConnectionInfo.authn_id)
651 : : {
652 : : /*
653 : : * Normally, if log_connections is set, the call to set_authn_id()
654 : : * will log the connection. However, if that function is never
655 : : * called, perhaps because the trust method is in use, then we handle
656 : : * the logging here instead.
657 : : */
658 [ + - ]: 260 : ereport(LOG,
659 : : errmsg("connection authenticated: user=\"%s\" method=%s "
660 : : "(%s:%d)",
661 : : port->user_name, hba_authname(port->hba->auth_method),
662 : : port->hba->sourcefile, port->hba->linenumber));
663 : : }
664 : :
5670 rhaas@postgresql.org 665 [ - + ]: 14609 : if (ClientAuthentication_hook)
5504 bruce@momjian.us 666 :UBC 0 : (*ClientAuthentication_hook) (port, status);
667 : :
6486 magnus@hagander.net 668 [ + + ]:CBC 14609 : if (status == STATUS_OK)
3547 heikki.linnakangas@i 669 : 14484 : sendAuthRequest(port, AUTH_REQ_OK, NULL, 0);
670 : : else
35 jchampion@postgresql 671 :GNC 125 : auth_failed(port,
672 [ + + ]: 125 : abandoned ? FATAL_CLIENT_ONLY : FATAL,
673 : : status,
674 : : logdetail);
6486 magnus@hagander.net 675 :CBC 14484 : }
676 : :
677 : :
678 : : /*
679 : : * Send an authentication request packet to the frontend.
680 : : */
681 : : void
362 heikki.linnakangas@i 682 : 14913 : sendAuthRequest(Port *port, AuthRequest areq, const void *extradata, int extralen)
683 : : {
684 : : StringInfoData buf;
685 : :
4109 andres@anarazel.de 686 [ - + ]: 14913 : CHECK_FOR_INTERRUPTS();
687 : :
987 nathan@postgresql.or 688 : 14913 : pq_beginmessage(&buf, PqMsg_AuthenticationRequest);
3128 andres@anarazel.de 689 : 14913 : pq_sendint32(&buf, (int32) areq);
3547 heikki.linnakangas@i 690 [ + + ]: 14913 : if (extralen > 0)
691 : 397 : pq_sendbytes(&buf, extradata, extralen);
692 : :
6486 magnus@hagander.net 693 : 14913 : pq_endmessage(&buf);
694 : :
695 : : /*
696 : : * Flush message so client will see it, except for AUTH_REQ_OK and
697 : : * AUTH_REQ_SASL_FIN, which need not be sent until we are ready for
698 : : * queries.
699 : : */
3309 heikki.linnakangas@i 700 [ + + + + ]: 14913 : if (areq != AUTH_REQ_OK && areq != AUTH_REQ_SASL_FIN)
6486 magnus@hagander.net 701 : 374 : pq_flush();
702 : :
4109 andres@anarazel.de 703 [ - + ]: 14913 : CHECK_FOR_INTERRUPTS();
6874 magnus@hagander.net 704 : 14913 : }
705 : :
706 : : /*
707 : : * Collect password response packet from frontend.
708 : : *
709 : : * Returns NULL if couldn't get password, else palloc'd string.
710 : : */
711 : : static char *
6486 712 : 22 : recv_password_packet(Port *port)
713 : : {
714 : : StringInfoData buf;
715 : : int mtype;
716 : :
4110 heikki.linnakangas@i 717 : 22 : pq_startmsgread();
718 : :
719 : : /* Expect 'p' message type */
1888 720 : 22 : mtype = pq_getbyte();
987 nathan@postgresql.or 721 [ + + ]: 22 : if (mtype != PqMsg_PasswordMessage)
722 : : {
723 : : /*
724 : : * If the client just disconnects without offering a password, don't
725 : : * make a log entry. This is legal per protocol spec and in fact
726 : : * commonly done by psql, so complaining just clutters the log.
727 : : */
1888 heikki.linnakangas@i 728 [ - + ]: 11 : if (mtype != EOF)
1888 heikki.linnakangas@i 729 [ # # ]:UBC 0 : ereport(ERROR,
730 : : (errcode(ERRCODE_PROTOCOL_VIOLATION),
731 : : errmsg("expected password response, got message type %d",
732 : : mtype)));
1819 tgl@sss.pgh.pa.us 733 :CBC 11 : return NULL; /* EOF or bad message type */
734 : : }
735 : :
6486 magnus@hagander.net 736 : 11 : initStringInfo(&buf);
1833 tgl@sss.pgh.pa.us 737 [ - + ]: 11 : if (pq_getmessage(&buf, PG_MAX_AUTH_TOKEN_LENGTH)) /* receive password */
738 : : {
739 : : /* EOF - pq_getmessage already logged a suitable message */
6486 magnus@hagander.net 740 :UBC 0 : pfree(buf.data);
741 : 0 : return NULL;
742 : : }
743 : :
744 : : /*
745 : : * Apply sanity check: password packet length should agree with length of
746 : : * contained string. Note it is safe to use strlen here because
747 : : * StringInfo is guaranteed to have an appended '\0'.
748 : : */
6486 magnus@hagander.net 749 [ - + ]:CBC 11 : if (strlen(buf.data) + 1 != buf.len)
3253 heikki.linnakangas@i 750 [ # # ]:UBC 0 : ereport(ERROR,
751 : : (errcode(ERRCODE_PROTOCOL_VIOLATION),
752 : : errmsg("invalid password packet size")));
753 : :
754 : : /*
755 : : * Don't allow an empty password. Libpq treats an empty password the same
756 : : * as no password at all, and won't even try to authenticate. But other
757 : : * clients might, so allowing it would be confusing.
758 : : *
759 : : * Note that this only catches an empty password sent by the client in
760 : : * plaintext. There's also a check in CREATE/ALTER USER that prevents an
761 : : * empty string from being stored as a user's password in the first place.
762 : : * We rely on that for MD5 and SCRAM authentication, but we still need
763 : : * this check here, to prevent an empty password from being used with
764 : : * authentication methods that check the password against an external
765 : : * system, like PAM and LDAP.
766 : : */
3193 heikki.linnakangas@i 767 [ - + ]:CBC 11 : if (buf.len == 1)
3193 heikki.linnakangas@i 768 [ # # ]:UBC 0 : ereport(ERROR,
769 : : (errcode(ERRCODE_INVALID_PASSWORD),
770 : : errmsg("empty password returned by client")));
771 : :
772 : : /* Do not echo password to logs, for security. */
3823 peter_e@gmx.net 773 [ - + ]:CBC 11 : elog(DEBUG5, "received password packet");
774 : :
775 : : /*
776 : : * Return the received string. Note we do not attempt to do any
777 : : * character-set conversion on it; since we don't yet know the client's
778 : : * encoding, there wouldn't be much point.
779 : : */
6486 magnus@hagander.net 780 : 11 : return buf.data;
781 : : }
782 : :
783 : :
784 : : /*----------------------------------------------------------------
785 : : * Password-based authentication mechanisms
786 : : *----------------------------------------------------------------
787 : : */
788 : :
789 : : /*
790 : : * Plaintext password authentication.
791 : : */
792 : : static int
1575 michael@paquier.xyz 793 : 19 : CheckPasswordAuth(Port *port, const char **logdetail)
794 : : {
795 : : char *passwd;
796 : : int result;
797 : : char *shadow_pass;
798 : :
3329 heikki.linnakangas@i 799 : 19 : sendAuthRequest(port, AUTH_REQ_PASSWORD, NULL, 0);
800 : :
3438 801 : 19 : passwd = recv_password_packet(port);
802 [ + + ]: 19 : if (passwd == NULL)
803 : 9 : return STATUS_EOF; /* client wouldn't send password */
804 : :
3329 805 : 10 : shadow_pass = get_role_password(port->user_name, logdetail);
806 [ + - ]: 10 : if (shadow_pass)
807 : : {
808 : 10 : result = plain_crypt_verify(port->user_name, shadow_pass, passwd,
809 : : logdetail);
810 : : }
811 : : else
3329 heikki.linnakangas@i 812 :UBC 0 : result = STATUS_ERROR;
813 : :
3431 heikki.linnakangas@i 814 [ + - ]:CBC 10 : if (shadow_pass)
815 : 10 : pfree(shadow_pass);
3438 816 : 10 : pfree(passwd);
817 : :
1854 michael@paquier.xyz 818 [ + - ]: 10 : if (result == STATUS_OK)
819 : 10 : set_authn_id(port, port->user_name);
820 : :
3438 heikki.linnakangas@i 821 : 10 : return result;
822 : : }
823 : :
824 : : /*
825 : : * MD5 and SCRAM authentication.
826 : : */
827 : : static int
1575 michael@paquier.xyz 828 : 74 : CheckPWChallengeAuth(Port *port, const char **logdetail)
829 : : {
830 : : int auth_result;
831 : : char *shadow_pass;
832 : : PasswordType pwtype;
833 : :
3329 heikki.linnakangas@i 834 [ + + - + ]: 74 : Assert(port->hba->auth_method == uaSCRAM ||
835 : : port->hba->auth_method == uaMD5);
836 : :
837 : : /* First look up the user's password. */
838 : 74 : shadow_pass = get_role_password(port->user_name, logdetail);
839 : :
840 : : /*
841 : : * If the user does not exist, or has no password or it's expired, we
842 : : * still go through the motions of authentication, to avoid revealing to
843 : : * the client that the user didn't exist. If 'md5' is allowed, we choose
844 : : * whether to use 'md5' or 'scram-sha-256' authentication based on current
845 : : * password_encryption setting. The idea is that most genuine users
846 : : * probably have a password of that type, and if we pretend that this user
847 : : * had a password of that type, too, it "blends in" best.
848 : : */
849 [ + + ]: 74 : if (!shadow_pass)
3329 heikki.linnakangas@i 850 :GBC 1 : pwtype = Password_encryption;
851 : : else
3329 heikki.linnakangas@i 852 :CBC 73 : pwtype = get_password_type(shadow_pass);
853 : :
854 : : /*
855 : : * If 'md5' authentication is allowed, decide whether to perform 'md5' or
856 : : * 'scram-sha-256' authentication based on the type of password the user
857 : : * has. If it's an MD5 hash, we must do MD5 authentication, and if it's a
858 : : * SCRAM secret, we must do SCRAM authentication.
859 : : *
860 : : * If MD5 authentication is not allowed, always use SCRAM. If the user
861 : : * had an MD5 password, CheckSASLAuth() with the SCRAM mechanism will
862 : : * fail.
863 : : */
864 [ + + + + ]: 74 : if (port->hba->auth_method == uaMD5 && pwtype == PASSWORD_TYPE_MD5)
865 : 3 : auth_result = CheckMD5Auth(port, shadow_pass, logdetail);
866 : : else
1763 michael@paquier.xyz 867 : 71 : auth_result = CheckSASLAuth(&pg_be_scram_mech, port, shadow_pass,
868 : : logdetail, NULL /* can't abandon SCRAM */ );
869 : :
3329 heikki.linnakangas@i 870 [ + + ]: 74 : if (shadow_pass)
871 : 73 : pfree(shadow_pass);
872 : : else
873 : : {
874 : : /*
875 : : * If get_role_password() returned error, authentication better not
876 : : * have succeeded.
877 : : */
3329 heikki.linnakangas@i 878 [ - + ]:GBC 1 : Assert(auth_result != STATUS_OK);
879 : : }
880 : :
1854 michael@paquier.xyz 881 [ + + ]:CBC 74 : if (auth_result == STATUS_OK)
882 : 56 : set_authn_id(port, port->user_name);
883 : :
3329 heikki.linnakangas@i 884 : 74 : return auth_result;
885 : : }
886 : :
887 : : static int
1575 michael@paquier.xyz 888 : 3 : CheckMD5Auth(Port *port, char *shadow_pass, const char **logdetail)
889 : : {
890 : : uint8 md5Salt[4]; /* Password salt */
891 : : char *passwd;
892 : : int result;
893 : :
894 : : /* include the salt to use for computing the response */
2681 895 [ - + ]: 3 : if (!pg_strong_random(md5Salt, 4))
896 : : {
3329 heikki.linnakangas@i 897 [ # # ]:UBC 0 : ereport(LOG,
898 : : (errmsg("could not generate random MD5 salt")));
899 : 0 : return STATUS_ERROR;
900 : : }
901 : :
3329 heikki.linnakangas@i 902 :CBC 3 : sendAuthRequest(port, AUTH_REQ_MD5, md5Salt, 4);
903 : :
6486 magnus@hagander.net 904 : 3 : passwd = recv_password_packet(port);
905 [ + + ]: 3 : if (passwd == NULL)
906 : 2 : return STATUS_EOF; /* client wouldn't send password */
907 : :
3431 heikki.linnakangas@i 908 [ + - ]: 1 : if (shadow_pass)
3329 909 : 1 : result = md5_crypt_verify(port->user_name, shadow_pass, passwd,
910 : : md5Salt, 4, logdetail);
911 : : else
3329 heikki.linnakangas@i 912 :UBC 0 : result = STATUS_ERROR;
913 : :
6486 magnus@hagander.net 914 :CBC 1 : pfree(passwd);
915 : :
916 : 1 : return result;
917 : : }
918 : :
919 : :
920 : : /*----------------------------------------------------------------
921 : : * GSSAPI authentication system
922 : : *----------------------------------------------------------------
923 : : */
924 : : #ifdef ENABLE_GSS
925 : : static int
926 : 13 : pg_GSS_recvauth(Port *port)
927 : : {
928 : : OM_uint32 maj_stat,
929 : : min_stat,
930 : : lmin_s,
931 : : gflags;
932 : : int mtype;
933 : : StringInfoData buf;
934 : : gss_buffer_desc gbuf;
935 : : gss_cred_id_t delegated_creds;
936 : :
937 : : /*
938 : : * Use the configured keytab, if there is one. As we now require MIT
939 : : * Kerberos, we might consider using the credential store extensions in
940 : : * the future instead of the environment variable.
941 : : */
1952 tgl@sss.pgh.pa.us 942 [ + - + - ]: 13 : if (pg_krb_server_keyfile != NULL && pg_krb_server_keyfile[0] != '\0')
943 : : {
944 [ - + ]: 13 : if (setenv("KRB5_KTNAME", pg_krb_server_keyfile, 1) != 0)
945 : : {
946 : : /* The only likely failure cause is OOM, so use that errcode */
1952 tgl@sss.pgh.pa.us 947 [ # # ]:UBC 0 : ereport(FATAL,
948 : : (errcode(ERRCODE_OUT_OF_MEMORY),
949 : : errmsg("could not set environment: %m")));
950 : : }
951 : : }
952 : :
953 : : /*
954 : : * We accept any service principal that's present in our keytab. This
955 : : * increases interoperability between kerberos implementations that see
956 : : * for example case sensitivity differently, while not really opening up
957 : : * any vector of attack.
958 : : */
6486 magnus@hagander.net 959 :CBC 13 : port->gss->cred = GSS_C_NO_CREDENTIAL;
960 : :
961 : : /*
962 : : * Initialize sequence with an empty context
963 : : */
964 : 13 : port->gss->ctx = GSS_C_NO_CONTEXT;
965 : :
1118 sfrost@snowman.net 966 : 13 : delegated_creds = GSS_C_NO_CREDENTIAL;
967 : 13 : port->gss->delegated_creds = false;
968 : :
969 : : /*
970 : : * Loop through GSSAPI message exchange. This exchange can consist of
971 : : * multiple messages sent in both directions. First message is always from
972 : : * the client. All messages from client to server are password packets
973 : : * (type 'p').
974 : : */
975 : : do
976 : : {
4110 heikki.linnakangas@i 977 : 13 : pq_startmsgread();
978 : :
4109 andres@anarazel.de 979 [ - + ]: 13 : CHECK_FOR_INTERRUPTS();
980 : :
6486 magnus@hagander.net 981 : 13 : mtype = pq_getbyte();
987 nathan@postgresql.or 982 [ + + ]: 13 : if (mtype != PqMsg_GSSResponse)
983 : : {
984 : : /* Only log error if client didn't disconnect. */
6486 magnus@hagander.net 985 [ - + ]: 2 : if (mtype != EOF)
3253 heikki.linnakangas@i 986 [ # # ]:UBC 0 : ereport(ERROR,
987 : : (errcode(ERRCODE_PROTOCOL_VIOLATION),
988 : : errmsg("expected GSS response, got message type %d",
989 : : mtype)));
6486 magnus@hagander.net 990 :CBC 2 : return STATUS_ERROR;
991 : : }
992 : :
993 : : /* Get the actual GSS token */
994 : 11 : initStringInfo(&buf);
6047 heikki.linnakangas@i 995 [ - + ]: 11 : if (pq_getmessage(&buf, PG_MAX_AUTH_TOKEN_LENGTH))
996 : : {
997 : : /* EOF - pq_getmessage already logged error */
6486 magnus@hagander.net 998 :UBC 0 : pfree(buf.data);
999 : 0 : return STATUS_ERROR;
1000 : : }
1001 : :
1002 : : /* Map to GSSAPI style buffer */
6486 magnus@hagander.net 1003 :CBC 11 : gbuf.length = buf.len;
1004 : 11 : gbuf.value = buf.data;
1005 : :
147 peter@eisentraut.org 1006 [ - + ]:GNC 11 : elog(DEBUG4, "processing received GSS token of length %zu",
1007 : : gbuf.length);
1008 : :
2287 alvherre@alvh.no-ip. 1009 :CBC 11 : maj_stat = gss_accept_sec_context(&min_stat,
6486 magnus@hagander.net 1010 : 11 : &port->gss->ctx,
1011 : 11 : port->gss->cred,
1012 : : &gbuf,
1013 : : GSS_C_NO_CHANNEL_BINDINGS,
1014 : 11 : &port->gss->name,
1015 : : NULL,
1016 : 11 : &port->gss->outbuf,
1017 : : &gflags,
1018 : : NULL,
1081 bruce@momjian.us 1019 [ + + ]: 11 : pg_gss_accept_delegation ? &delegated_creds : NULL);
1020 : :
1021 : : /* gbuf no longer used */
6486 magnus@hagander.net 1022 : 11 : pfree(buf.data);
1023 : :
1700 peter@eisentraut.org 1024 [ - + ]: 11 : elog(DEBUG5, "gss_accept_sec_context major: %u, "
1025 : : "minor: %u, outlen: %zu, outflags: %x",
1026 : : maj_stat, min_stat,
1027 : : port->gss->outbuf.length, gflags);
1028 : :
4109 andres@anarazel.de 1029 [ - + ]: 11 : CHECK_FOR_INTERRUPTS();
1030 : :
1118 sfrost@snowman.net 1031 [ + + + - ]: 11 : if (delegated_creds != GSS_C_NO_CREDENTIAL && gflags & GSS_C_DELEG_FLAG)
1032 : : {
1033 : 6 : pg_store_delegated_credential(delegated_creds);
1034 : 6 : port->gss->delegated_creds = true;
1035 : : }
1036 : :
6486 magnus@hagander.net 1037 [ + - ]: 11 : if (port->gss->outbuf.length != 0)
1038 : : {
1039 : : /*
1040 : : * Negotiation generated data to be sent to the client.
1041 : : */
147 peter@eisentraut.org 1042 [ - + ]:GNC 11 : elog(DEBUG4, "sending GSS response token of length %zu",
1043 : : port->gss->outbuf.length);
1044 : :
3547 heikki.linnakangas@i 1045 :CBC 11 : sendAuthRequest(port, AUTH_REQ_GSS_CONT,
3240 tgl@sss.pgh.pa.us 1046 : 11 : port->gss->outbuf.value, port->gss->outbuf.length);
1047 : :
6486 magnus@hagander.net 1048 : 11 : gss_release_buffer(&lmin_s, &port->gss->outbuf);
1049 : : }
1050 : :
1051 [ - + - - ]: 11 : if (maj_stat != GSS_S_COMPLETE && maj_stat != GSS_S_CONTINUE_NEEDED)
1052 : : {
6486 magnus@hagander.net 1053 :UBC 0 : gss_delete_sec_context(&lmin_s, &port->gss->ctx, GSS_C_NO_BUFFER);
1954 tgl@sss.pgh.pa.us 1054 : 0 : pg_GSS_error(_("accepting GSS security context failed"),
1055 : : maj_stat, min_stat);
1056 : 0 : return STATUS_ERROR;
1057 : : }
1058 : :
6486 magnus@hagander.net 1059 [ - + ]:CBC 11 : if (maj_stat == GSS_S_CONTINUE_NEEDED)
6486 magnus@hagander.net 1060 [ # # ]:UBC 0 : elog(DEBUG4, "GSS continue needed");
1061 : :
6486 magnus@hagander.net 1062 [ - + ]:CBC 11 : } while (maj_stat == GSS_S_CONTINUE_NEEDED);
1063 : :
1064 [ - + ]: 11 : if (port->gss->cred != GSS_C_NO_CREDENTIAL)
1065 : : {
1066 : : /*
1067 : : * Release service principal credentials
1068 : : */
6486 magnus@hagander.net 1069 :UBC 0 : gss_release_cred(&min_stat, &port->gss->cred);
1070 : : }
2589 sfrost@snowman.net 1071 :CBC 11 : return pg_GSS_checkauth(port);
1072 : : }
1073 : :
1074 : : /*
1075 : : * Check whether the GSSAPI-authenticated user is allowed to connect as the
1076 : : * claimed username.
1077 : : */
1078 : : static int
1079 : 43 : pg_GSS_checkauth(Port *port)
1080 : : {
1081 : : int ret;
1082 : : OM_uint32 maj_stat,
1083 : : min_stat,
1084 : : lmin_s;
1085 : : gss_buffer_desc gbuf;
1086 : : char *princ;
1087 : :
1088 : : /*
1089 : : * Get the name of the user that authenticated, and compare it to the pg
1090 : : * username that was specified for the connection.
1091 : : */
6486 magnus@hagander.net 1092 : 43 : maj_stat = gss_display_name(&min_stat, port->gss->name, &gbuf, NULL);
1093 [ - + ]: 43 : if (maj_stat != GSS_S_COMPLETE)
1094 : : {
1954 tgl@sss.pgh.pa.us 1095 :UBC 0 : pg_GSS_error(_("retrieving GSS user name failed"),
1096 : : maj_stat, min_stat);
1097 : 0 : return STATUS_ERROR;
1098 : : }
1099 : :
1100 : : /*
1101 : : * gbuf.value might not be null-terminated, so turn it into a regular
1102 : : * null-terminated string.
1103 : : */
1777 tgl@sss.pgh.pa.us 1104 :CBC 43 : princ = palloc(gbuf.length + 1);
1105 : 43 : memcpy(princ, gbuf.value, gbuf.length);
1106 : 43 : princ[gbuf.length] = '\0';
1107 : 43 : gss_release_buffer(&lmin_s, &gbuf);
1108 : :
1109 : : /*
1110 : : * Copy the original name of the authenticated principal into our backend
1111 : : * memory for display later.
1112 : : *
1113 : : * This is also our authenticated identity. Set it now, rather than
1114 : : * waiting for the usermap check below, because authentication has already
1115 : : * succeeded and we want the log file to reflect that.
1116 : : */
1117 : 43 : port->gss->princ = MemoryContextStrdup(TopMemoryContext, princ);
1118 : 43 : set_authn_id(port, princ);
1119 : :
1120 : : /*
1121 : : * Split the username at the realm separator
1122 : : */
1123 [ + - ]: 43 : if (strchr(princ, '@'))
1124 : : {
1125 : 43 : char *cp = strchr(princ, '@');
1126 : :
1127 : : /*
1128 : : * If we are not going to include the realm in the username that is
1129 : : * passed to the ident map, destructively modify it here to remove the
1130 : : * realm. Then advance past the separator to check the realm.
1131 : : */
6327 magnus@hagander.net 1132 [ + + ]: 43 : if (!port->hba->include_realm)
1133 : 7 : *cp = '\0';
6486 1134 : 43 : cp++;
1135 : :
6325 1136 [ + + + - ]: 43 : if (port->hba->krb_realm != NULL && strlen(port->hba->krb_realm))
1137 : : {
1138 : : /*
1139 : : * Match the realm part of the name first
1140 : : */
6486 1141 [ - + ]: 2 : if (pg_krb_caseins_users)
6325 magnus@hagander.net 1142 :UBC 0 : ret = pg_strcasecmp(port->hba->krb_realm, cp);
1143 : : else
6325 magnus@hagander.net 1144 :CBC 2 : ret = strcmp(port->hba->krb_realm, cp);
1145 : :
6486 1146 [ + - ]: 2 : if (ret)
1147 : : {
1148 : : /* GSS realm does not match */
1149 [ + - ]: 2 : elog(DEBUG2,
1150 : : "GSSAPI realm (%s) and configured realm (%s) don't match",
1151 : : cp, port->hba->krb_realm);
1777 tgl@sss.pgh.pa.us 1152 : 2 : pfree(princ);
6486 magnus@hagander.net 1153 : 2 : return STATUS_ERROR;
1154 : : }
1155 : : }
1156 : : }
6325 magnus@hagander.net 1157 [ # # # # ]:UBC 0 : else if (port->hba->krb_realm && strlen(port->hba->krb_realm))
1158 : : {
6486 1159 [ # # ]: 0 : elog(DEBUG2,
1160 : : "GSSAPI did not return realm but realm matching was requested");
1777 tgl@sss.pgh.pa.us 1161 : 0 : pfree(princ);
6486 magnus@hagander.net 1162 : 0 : return STATUS_ERROR;
1163 : : }
1164 : :
1777 tgl@sss.pgh.pa.us 1165 :CBC 41 : ret = check_usermap(port->hba->usermap, port->user_name, princ,
1166 : : pg_krb_caseins_users);
1167 : :
1168 : 41 : pfree(princ);
1169 : :
6187 magnus@hagander.net 1170 : 41 : return ret;
1171 : : }
1172 : : #endif /* ENABLE_GSS */
1173 : :
1174 : :
1175 : : /*----------------------------------------------------------------
1176 : : * SSPI authentication system
1177 : : *----------------------------------------------------------------
1178 : : */
1179 : : #ifdef ENABLE_SSPI
1180 : :
1181 : : /*
1182 : : * Generate an error for SSPI authentication. The caller should apply
1183 : : * _() to errmsg to make it translatable.
1184 : : */
1185 : : static void
1186 : : pg_SSPI_error(int severity, const char *errmsg, SECURITY_STATUS r)
1187 : : {
1188 : : char sysmsg[256];
1189 : :
1190 : : if (FormatMessage(FORMAT_MESSAGE_IGNORE_INSERTS |
1191 : : FORMAT_MESSAGE_FROM_SYSTEM,
1192 : : NULL, r, 0,
1193 : : sysmsg, sizeof(sysmsg), NULL) == 0)
1194 : : ereport(severity,
1195 : : (errmsg_internal("%s", errmsg),
1196 : : errdetail_internal("SSPI error %x", (unsigned int) r)));
1197 : : else
1198 : : ereport(severity,
1199 : : (errmsg_internal("%s", errmsg),
1200 : : errdetail_internal("%s (%x)", sysmsg, (unsigned int) r)));
1201 : : }
1202 : :
1203 : : static int
1204 : : pg_SSPI_recvauth(Port *port)
1205 : : {
1206 : : int mtype;
1207 : : StringInfoData buf;
1208 : : SECURITY_STATUS r;
1209 : : CredHandle sspicred;
1210 : : CtxtHandle *sspictx = NULL,
1211 : : newctx;
1212 : : TimeStamp expiry;
1213 : : ULONG contextattr;
1214 : : SecBufferDesc inbuf;
1215 : : SecBufferDesc outbuf;
1216 : : SecBuffer OutBuffers[1];
1217 : : SecBuffer InBuffers[1];
1218 : : HANDLE token;
1219 : : TOKEN_USER *tokenuser;
1220 : : DWORD retlen;
1221 : : char accountname[MAXPGPATH];
1222 : : char domainname[MAXPGPATH];
1223 : : DWORD accountnamesize = sizeof(accountname);
1224 : : DWORD domainnamesize = sizeof(domainname);
1225 : : SID_NAME_USE accountnameuse;
1226 : : char *authn_id;
1227 : :
1228 : : /*
1229 : : * Acquire a handle to the server credentials.
1230 : : */
1231 : : r = AcquireCredentialsHandle(NULL,
1232 : : "negotiate",
1233 : : SECPKG_CRED_INBOUND,
1234 : : NULL,
1235 : : NULL,
1236 : : NULL,
1237 : : NULL,
1238 : : &sspicred,
1239 : : &expiry);
1240 : : if (r != SEC_E_OK)
1241 : : pg_SSPI_error(ERROR, _("could not acquire SSPI credentials"), r);
1242 : :
1243 : : /*
1244 : : * Loop through SSPI message exchange. This exchange can consist of
1245 : : * multiple messages sent in both directions. First message is always from
1246 : : * the client. All messages from client to server are password packets
1247 : : * (type 'p').
1248 : : */
1249 : : do
1250 : : {
1251 : : pq_startmsgread();
1252 : : mtype = pq_getbyte();
1253 : : if (mtype != PqMsg_GSSResponse)
1254 : : {
1255 : : if (sspictx != NULL)
1256 : : {
1257 : : DeleteSecurityContext(sspictx);
1258 : : free(sspictx);
1259 : : }
1260 : : FreeCredentialsHandle(&sspicred);
1261 : :
1262 : : /* Only log error if client didn't disconnect. */
1263 : : if (mtype != EOF)
1264 : : ereport(ERROR,
1265 : : (errcode(ERRCODE_PROTOCOL_VIOLATION),
1266 : : errmsg("expected SSPI response, got message type %d",
1267 : : mtype)));
1268 : : return STATUS_ERROR;
1269 : : }
1270 : :
1271 : : /* Get the actual SSPI token */
1272 : : initStringInfo(&buf);
1273 : : if (pq_getmessage(&buf, PG_MAX_AUTH_TOKEN_LENGTH))
1274 : : {
1275 : : /* EOF - pq_getmessage already logged error */
1276 : : pfree(buf.data);
1277 : : if (sspictx != NULL)
1278 : : {
1279 : : DeleteSecurityContext(sspictx);
1280 : : free(sspictx);
1281 : : }
1282 : : FreeCredentialsHandle(&sspicred);
1283 : : return STATUS_ERROR;
1284 : : }
1285 : :
1286 : : /* Map to SSPI style buffer */
1287 : : inbuf.ulVersion = SECBUFFER_VERSION;
1288 : : inbuf.cBuffers = 1;
1289 : : inbuf.pBuffers = InBuffers;
1290 : : InBuffers[0].pvBuffer = buf.data;
1291 : : InBuffers[0].cbBuffer = buf.len;
1292 : : InBuffers[0].BufferType = SECBUFFER_TOKEN;
1293 : :
1294 : : /* Prepare output buffer */
1295 : : OutBuffers[0].pvBuffer = NULL;
1296 : : OutBuffers[0].BufferType = SECBUFFER_TOKEN;
1297 : : OutBuffers[0].cbBuffer = 0;
1298 : : outbuf.cBuffers = 1;
1299 : : outbuf.pBuffers = OutBuffers;
1300 : : outbuf.ulVersion = SECBUFFER_VERSION;
1301 : :
1302 : : elog(DEBUG4, "processing received SSPI token of length %u",
1303 : : (unsigned int) buf.len);
1304 : :
1305 : : r = AcceptSecurityContext(&sspicred,
1306 : : sspictx,
1307 : : &inbuf,
1308 : : ASC_REQ_ALLOCATE_MEMORY,
1309 : : SECURITY_NETWORK_DREP,
1310 : : &newctx,
1311 : : &outbuf,
1312 : : &contextattr,
1313 : : NULL);
1314 : :
1315 : : /* input buffer no longer used */
1316 : : pfree(buf.data);
1317 : :
1318 : : if (outbuf.cBuffers > 0 && outbuf.pBuffers[0].cbBuffer > 0)
1319 : : {
1320 : : /*
1321 : : * Negotiation generated data to be sent to the client.
1322 : : */
1323 : : elog(DEBUG4, "sending SSPI response token of length %u",
1324 : : (unsigned int) outbuf.pBuffers[0].cbBuffer);
1325 : :
1326 : : port->gss->outbuf.length = outbuf.pBuffers[0].cbBuffer;
1327 : : port->gss->outbuf.value = outbuf.pBuffers[0].pvBuffer;
1328 : :
1329 : : sendAuthRequest(port, AUTH_REQ_GSS_CONT,
1330 : : port->gss->outbuf.value, port->gss->outbuf.length);
1331 : :
1332 : : FreeContextBuffer(outbuf.pBuffers[0].pvBuffer);
1333 : : }
1334 : :
1335 : : if (r != SEC_E_OK && r != SEC_I_CONTINUE_NEEDED)
1336 : : {
1337 : : if (sspictx != NULL)
1338 : : {
1339 : : DeleteSecurityContext(sspictx);
1340 : : free(sspictx);
1341 : : }
1342 : : FreeCredentialsHandle(&sspicred);
1343 : : pg_SSPI_error(ERROR,
1344 : : _("could not accept SSPI security context"), r);
1345 : : }
1346 : :
1347 : : /*
1348 : : * Overwrite the current context with the one we just received. If
1349 : : * sspictx is NULL it was the first loop and we need to allocate a
1350 : : * buffer for it. On subsequent runs, we can just overwrite the buffer
1351 : : * contents since the size does not change.
1352 : : */
1353 : : if (sspictx == NULL)
1354 : : {
1355 : : sspictx = malloc(sizeof(CtxtHandle));
1356 : : if (sspictx == NULL)
1357 : : ereport(ERROR,
1358 : : (errmsg("out of memory")));
1359 : : }
1360 : :
1361 : : memcpy(sspictx, &newctx, sizeof(CtxtHandle));
1362 : :
1363 : : if (r == SEC_I_CONTINUE_NEEDED)
1364 : : elog(DEBUG4, "SSPI continue needed");
1365 : :
1366 : : } while (r == SEC_I_CONTINUE_NEEDED);
1367 : :
1368 : :
1369 : : /*
1370 : : * Release service principal credentials
1371 : : */
1372 : : FreeCredentialsHandle(&sspicred);
1373 : :
1374 : :
1375 : : /*
1376 : : * SEC_E_OK indicates that authentication is now complete.
1377 : : *
1378 : : * Get the name of the user that authenticated, and compare it to the pg
1379 : : * username that was specified for the connection.
1380 : : */
1381 : :
1382 : : r = QuerySecurityContextToken(sspictx, &token);
1383 : : if (r != SEC_E_OK)
1384 : : pg_SSPI_error(ERROR,
1385 : : _("could not get token from SSPI security context"), r);
1386 : :
1387 : : /*
1388 : : * No longer need the security context, everything from here on uses the
1389 : : * token instead.
1390 : : */
1391 : : DeleteSecurityContext(sspictx);
1392 : : free(sspictx);
1393 : :
1394 : : if (!GetTokenInformation(token, TokenUser, NULL, 0, &retlen) && GetLastError() != 122)
1395 : : ereport(ERROR,
1396 : : (errmsg_internal("could not get token information buffer size: error code %lu",
1397 : : GetLastError())));
1398 : :
1399 : : tokenuser = malloc(retlen);
1400 : : if (tokenuser == NULL)
1401 : : ereport(ERROR,
1402 : : (errmsg("out of memory")));
1403 : :
1404 : : if (!GetTokenInformation(token, TokenUser, tokenuser, retlen, &retlen))
1405 : : ereport(ERROR,
1406 : : (errmsg_internal("could not get token information: error code %lu",
1407 : : GetLastError())));
1408 : :
1409 : : CloseHandle(token);
1410 : :
1411 : : if (!LookupAccountSid(NULL, tokenuser->User.Sid, accountname, &accountnamesize,
1412 : : domainname, &domainnamesize, &accountnameuse))
1413 : : ereport(ERROR,
1414 : : (errmsg_internal("could not look up account SID: error code %lu",
1415 : : GetLastError())));
1416 : :
1417 : : free(tokenuser);
1418 : :
1419 : : if (!port->hba->compat_realm)
1420 : : {
1421 : : int status = pg_SSPI_make_upn(accountname, sizeof(accountname),
1422 : : domainname, sizeof(domainname),
1423 : : port->hba->upn_username);
1424 : :
1425 : : if (status != STATUS_OK)
1426 : : /* Error already reported from pg_SSPI_make_upn */
1427 : : return status;
1428 : : }
1429 : :
1430 : : /*
1431 : : * We have all of the information necessary to construct the authenticated
1432 : : * identity. Set it now, rather than waiting for check_usermap below,
1433 : : * because authentication has already succeeded and we want the log file
1434 : : * to reflect that.
1435 : : */
1436 : : if (port->hba->compat_realm)
1437 : : {
1438 : : /* SAM-compatible format. */
1439 : : authn_id = psprintf("%s\\%s", domainname, accountname);
1440 : : }
1441 : : else
1442 : : {
1443 : : /* Kerberos principal format. */
1444 : : authn_id = psprintf("%s@%s", accountname, domainname);
1445 : : }
1446 : :
1447 : : set_authn_id(port, authn_id);
1448 : : pfree(authn_id);
1449 : :
1450 : : /*
1451 : : * Compare realm/domain if requested. In SSPI, always compare case
1452 : : * insensitive.
1453 : : */
1454 : : if (port->hba->krb_realm && strlen(port->hba->krb_realm))
1455 : : {
1456 : : if (pg_strcasecmp(port->hba->krb_realm, domainname) != 0)
1457 : : {
1458 : : elog(DEBUG2,
1459 : : "SSPI domain (%s) and configured domain (%s) don't match",
1460 : : domainname, port->hba->krb_realm);
1461 : :
1462 : : return STATUS_ERROR;
1463 : : }
1464 : : }
1465 : :
1466 : : /*
1467 : : * We have the username (without domain/realm) in accountname, compare to
1468 : : * the supplied value. In SSPI, always compare case insensitive.
1469 : : *
1470 : : * If set to include realm, append it in <username>@<realm> format.
1471 : : */
1472 : : if (port->hba->include_realm)
1473 : : {
1474 : : char *namebuf;
1475 : : int retval;
1476 : :
1477 : : namebuf = psprintf("%s@%s", accountname, domainname);
1478 : : retval = check_usermap(port->hba->usermap, port->user_name, namebuf, true);
1479 : : pfree(namebuf);
1480 : : return retval;
1481 : : }
1482 : : else
1483 : : return check_usermap(port->hba->usermap, port->user_name, accountname, true);
1484 : : }
1485 : :
1486 : : /*
1487 : : * Replaces the domainname with the Kerberos realm name,
1488 : : * and optionally the accountname with the Kerberos user name.
1489 : : */
1490 : : static int
1491 : : pg_SSPI_make_upn(char *accountname,
1492 : : size_t accountnamesize,
1493 : : char *domainname,
1494 : : size_t domainnamesize,
1495 : : bool update_accountname)
1496 : : {
1497 : : char *samname;
1498 : : char *upname = NULL;
1499 : : char *p = NULL;
1500 : : ULONG upnamesize = 0;
1501 : : size_t upnamerealmsize;
1502 : : BOOLEAN res;
1503 : :
1504 : : /*
1505 : : * Build SAM name (DOMAIN\user), then translate to UPN
1506 : : * (user@kerberos.realm). The realm name is returned in lower case, but
1507 : : * that is fine because in SSPI auth, string comparisons are always
1508 : : * case-insensitive.
1509 : : */
1510 : :
1511 : : samname = psprintf("%s\\%s", domainname, accountname);
1512 : : res = TranslateName(samname, NameSamCompatible, NameUserPrincipal,
1513 : : NULL, &upnamesize);
1514 : :
1515 : : if ((!res && GetLastError() != ERROR_INSUFFICIENT_BUFFER)
1516 : : || upnamesize == 0)
1517 : : {
1518 : : pfree(samname);
1519 : : ereport(LOG,
1520 : : (errcode(ERRCODE_INVALID_ROLE_SPECIFICATION),
1521 : : errmsg("could not translate name")));
1522 : : return STATUS_ERROR;
1523 : : }
1524 : :
1525 : : /* upnamesize includes the terminating NUL. */
1526 : : upname = palloc(upnamesize);
1527 : :
1528 : : res = TranslateName(samname, NameSamCompatible, NameUserPrincipal,
1529 : : upname, &upnamesize);
1530 : :
1531 : : pfree(samname);
1532 : : if (res)
1533 : : p = strchr(upname, '@');
1534 : :
1535 : : if (!res || p == NULL)
1536 : : {
1537 : : pfree(upname);
1538 : : ereport(LOG,
1539 : : (errcode(ERRCODE_INVALID_ROLE_SPECIFICATION),
1540 : : errmsg("could not translate name")));
1541 : : return STATUS_ERROR;
1542 : : }
1543 : :
1544 : : /* Length of realm name after the '@', including the NUL. */
1545 : : upnamerealmsize = upnamesize - (p - upname + 1);
1546 : :
1547 : : /* Replace domainname with realm name. */
1548 : : if (upnamerealmsize > domainnamesize)
1549 : : {
1550 : : pfree(upname);
1551 : : ereport(LOG,
1552 : : (errcode(ERRCODE_INVALID_ROLE_SPECIFICATION),
1553 : : errmsg("realm name too long")));
1554 : : return STATUS_ERROR;
1555 : : }
1556 : :
1557 : : /* Length is now safe. */
1558 : : strcpy(domainname, p + 1);
1559 : :
1560 : : /* Replace account name as well (in case UPN != SAM)? */
1561 : : if (update_accountname)
1562 : : {
1563 : : if ((p - upname + 1) > accountnamesize)
1564 : : {
1565 : : pfree(upname);
1566 : : ereport(LOG,
1567 : : (errcode(ERRCODE_INVALID_ROLE_SPECIFICATION),
1568 : : errmsg("translated account name too long")));
1569 : : return STATUS_ERROR;
1570 : : }
1571 : :
1572 : : *p = 0;
1573 : : strcpy(accountname, upname);
1574 : : }
1575 : :
1576 : : pfree(upname);
1577 : : return STATUS_OK;
1578 : : }
1579 : : #endif /* ENABLE_SSPI */
1580 : :
1581 : :
1582 : :
1583 : : /*----------------------------------------------------------------
1584 : : * Ident authentication system
1585 : : *----------------------------------------------------------------
1586 : : */
1587 : :
1588 : : /*
1589 : : * Per RFC 1413, space and tab are whitespace in ident messages.
1590 : : */
1591 : : static bool
158 peter@eisentraut.org 1592 :UNC 0 : is_ident_whitespace(const char c)
1593 : : {
1594 [ # # # # ]: 0 : return c == ' ' || c == '\t';
1595 : : }
1596 : :
1597 : : /*
1598 : : * Parse the string "*ident_response" as a response from a query to an Ident
1599 : : * server. If it's a normal response indicating a user name, return true
1600 : : * and store the user name at *ident_user. If it's anything else,
1601 : : * return false.
1602 : : */
1603 : : static bool
6486 magnus@hagander.net 1604 :UBC 0 : interpret_ident_response(const char *ident_response,
1605 : : char *ident_user)
1606 : : {
3240 tgl@sss.pgh.pa.us 1607 : 0 : const char *cursor = ident_response; /* Cursor into *ident_response */
1608 : :
1609 : : /*
1610 : : * Ident's response, in the telnet tradition, should end in crlf (\r\n).
1611 : : */
6486 magnus@hagander.net 1612 [ # # ]: 0 : if (strlen(ident_response) < 2)
1613 : 0 : return false;
1614 [ # # ]: 0 : else if (ident_response[strlen(ident_response) - 2] != '\r')
1615 : 0 : return false;
1616 : : else
1617 : : {
1618 [ # # # # ]: 0 : while (*cursor != ':' && *cursor != '\r')
1619 : 0 : cursor++; /* skip port field */
1620 : :
1621 [ # # ]: 0 : if (*cursor != ':')
1622 : 0 : return false;
1623 : : else
1624 : : {
1625 : : /* We're positioned to colon before response type field */
1626 : : char response_type[80];
1627 : : int i; /* Index into *response_type */
1628 : :
1629 : 0 : cursor++; /* Go over colon */
158 peter@eisentraut.org 1630 [ # # ]:UNC 0 : while (is_ident_whitespace(*cursor))
6486 magnus@hagander.net 1631 :UBC 0 : cursor++; /* skip blanks */
1632 : 0 : i = 0;
158 peter@eisentraut.org 1633 [ # # # # :UNC 0 : while (*cursor != ':' && *cursor != '\r' && !is_ident_whitespace(*cursor) &&
# # # # ]
1634 : : i < (int) (sizeof(response_type) - 1))
6486 magnus@hagander.net 1635 :UBC 0 : response_type[i++] = *cursor++;
1636 : 0 : response_type[i] = '\0';
158 peter@eisentraut.org 1637 [ # # ]:UNC 0 : while (is_ident_whitespace(*cursor))
6486 magnus@hagander.net 1638 :UBC 0 : cursor++; /* skip blanks */
1639 [ # # ]: 0 : if (strcmp(response_type, "USERID") != 0)
1640 : 0 : return false;
1641 : : else
1642 : : {
1643 : : /*
1644 : : * It's a USERID response. Good. "cursor" should be pointing
1645 : : * to the colon that precedes the operating system type.
1646 : : */
1647 [ # # ]: 0 : if (*cursor != ':')
1648 : 0 : return false;
1649 : : else
1650 : : {
1651 : 0 : cursor++; /* Go over colon */
1652 : : /* Skip over operating system field. */
1653 [ # # # # ]: 0 : while (*cursor != ':' && *cursor != '\r')
1654 : 0 : cursor++;
1655 [ # # ]: 0 : if (*cursor != ':')
1656 : 0 : return false;
1657 : : else
1658 : : {
3240 tgl@sss.pgh.pa.us 1659 : 0 : cursor++; /* Go over colon */
158 peter@eisentraut.org 1660 [ # # ]:UNC 0 : while (is_ident_whitespace(*cursor))
6486 magnus@hagander.net 1661 :UBC 0 : cursor++; /* skip blanks */
1662 : : /* Rest of line is user name. Copy it over. */
1663 : 0 : i = 0;
1664 [ # # # # ]: 0 : while (*cursor != '\r' && i < IDENT_USERNAME_MAX)
1665 : 0 : ident_user[i++] = *cursor++;
1666 : 0 : ident_user[i] = '\0';
1667 : 0 : return true;
1668 : : }
1669 : : }
1670 : : }
1671 : : }
1672 : : }
1673 : : }
1674 : :
1675 : :
1676 : : /*
1677 : : * Talk to the ident server on "remote_addr" and find out who
1678 : : * owns the tcp connection to "local_addr"
1679 : : * If the username is successfully retrieved, check the usermap.
1680 : : *
1681 : : * XXX: Using WaitLatchOrSocket() and doing a CHECK_FOR_INTERRUPTS() if the
1682 : : * latch was set would improve the responsiveness to timeouts/cancellations.
1683 : : */
1684 : : static int
232 peter@eisentraut.org 1685 :UNC 0 : ident_inet(Port *port)
1686 : : {
5526 magnus@hagander.net 1687 :UBC 0 : const SockAddr remote_addr = port->raddr;
1688 : 0 : const SockAddr local_addr = port->laddr;
1689 : : char ident_user[IDENT_USERNAME_MAX + 1];
3240 tgl@sss.pgh.pa.us 1690 : 0 : pgsocket sock_fd = PGINVALID_SOCKET; /* for talking to Ident server */
1691 : : int rc; /* Return code from a locally called function */
1692 : : bool ident_return;
1693 : : char remote_addr_s[NI_MAXHOST];
1694 : : char remote_port[NI_MAXSERV];
1695 : : char local_addr_s[NI_MAXHOST];
1696 : : char local_port[NI_MAXSERV];
1697 : : char ident_port[NI_MAXSERV];
1698 : : char ident_query[80];
1699 : : char ident_response[80 + IDENT_USERNAME_MAX];
6486 magnus@hagander.net 1700 : 0 : struct addrinfo *ident_serv = NULL,
1701 : 0 : *la = NULL,
1702 : : hints;
1703 : :
1704 : : /*
1705 : : * Might look a little weird to first convert it to text and then back to
1706 : : * sockaddr, but it's protocol independent.
1707 : : */
1708 : 0 : pg_getnameinfo_all(&remote_addr.addr, remote_addr.salen,
1709 : : remote_addr_s, sizeof(remote_addr_s),
1710 : : remote_port, sizeof(remote_port),
1711 : : NI_NUMERICHOST | NI_NUMERICSERV);
1712 : 0 : pg_getnameinfo_all(&local_addr.addr, local_addr.salen,
1713 : : local_addr_s, sizeof(local_addr_s),
1714 : : local_port, sizeof(local_port),
1715 : : NI_NUMERICHOST | NI_NUMERICSERV);
1716 : :
1717 : 0 : snprintf(ident_port, sizeof(ident_port), "%d", IDENT_PORT);
1718 : 0 : hints.ai_flags = AI_NUMERICHOST;
1719 : 0 : hints.ai_family = remote_addr.addr.ss_family;
1720 : 0 : hints.ai_socktype = SOCK_STREAM;
1721 : 0 : hints.ai_protocol = 0;
1722 : 0 : hints.ai_addrlen = 0;
1723 : 0 : hints.ai_canonname = NULL;
1724 : 0 : hints.ai_addr = NULL;
1725 : 0 : hints.ai_next = NULL;
1726 : 0 : rc = pg_getaddrinfo_all(remote_addr_s, ident_port, &hints, &ident_serv);
1727 [ # # # # ]: 0 : if (rc || !ident_serv)
1728 : : {
1729 : : /* we don't expect this to happen */
4101 tgl@sss.pgh.pa.us 1730 : 0 : ident_return = false;
1731 : 0 : goto ident_inet_done;
1732 : : }
1733 : :
6486 magnus@hagander.net 1734 : 0 : hints.ai_flags = AI_NUMERICHOST;
1735 : 0 : hints.ai_family = local_addr.addr.ss_family;
1736 : 0 : hints.ai_socktype = SOCK_STREAM;
1737 : 0 : hints.ai_protocol = 0;
1738 : 0 : hints.ai_addrlen = 0;
1739 : 0 : hints.ai_canonname = NULL;
1740 : 0 : hints.ai_addr = NULL;
1741 : 0 : hints.ai_next = NULL;
1742 : 0 : rc = pg_getaddrinfo_all(local_addr_s, NULL, &hints, &la);
1743 [ # # # # ]: 0 : if (rc || !la)
1744 : : {
1745 : : /* we don't expect this to happen */
4101 tgl@sss.pgh.pa.us 1746 : 0 : ident_return = false;
1747 : 0 : goto ident_inet_done;
1748 : : }
1749 : :
6486 magnus@hagander.net 1750 : 0 : sock_fd = socket(ident_serv->ai_family, ident_serv->ai_socktype,
1751 : 0 : ident_serv->ai_protocol);
4402 bruce@momjian.us 1752 [ # # ]: 0 : if (sock_fd == PGINVALID_SOCKET)
1753 : : {
6486 magnus@hagander.net 1754 [ # # ]: 0 : ereport(LOG,
1755 : : (errcode_for_socket_access(),
1756 : : errmsg("could not create socket for Ident connection: %m")));
1757 : 0 : ident_return = false;
1758 : 0 : goto ident_inet_done;
1759 : : }
1760 : :
1761 : : /*
1762 : : * Bind to the address which the client originally contacted, otherwise
1763 : : * the ident server won't be able to match up the right connection. This
1764 : : * is necessary if the PostgreSQL server is running on an IP alias.
1765 : : */
1766 : 0 : rc = bind(sock_fd, la->ai_addr, la->ai_addrlen);
1767 [ # # ]: 0 : if (rc != 0)
1768 : : {
1769 [ # # ]: 0 : ereport(LOG,
1770 : : (errcode_for_socket_access(),
1771 : : errmsg("could not bind to local address \"%s\": %m",
1772 : : local_addr_s)));
1773 : 0 : ident_return = false;
1774 : 0 : goto ident_inet_done;
1775 : : }
1776 : :
1777 : 0 : rc = connect(sock_fd, ident_serv->ai_addr,
1778 : 0 : ident_serv->ai_addrlen);
1779 [ # # ]: 0 : if (rc != 0)
1780 : : {
1781 [ # # ]: 0 : ereport(LOG,
1782 : : (errcode_for_socket_access(),
1783 : : errmsg("could not connect to Ident server at address \"%s\", port %s: %m",
1784 : : remote_addr_s, ident_port)));
1785 : 0 : ident_return = false;
1786 : 0 : goto ident_inet_done;
1787 : : }
1788 : :
1789 : : /* The query we send to the Ident server */
1790 : 0 : snprintf(ident_query, sizeof(ident_query), "%s,%s\r\n",
1791 : : remote_port, local_port);
1792 : :
1793 : : /* loop in case send is interrupted */
1794 : : do
1795 : : {
4109 andres@anarazel.de 1796 [ # # ]: 0 : CHECK_FOR_INTERRUPTS();
1797 : :
6486 magnus@hagander.net 1798 : 0 : rc = send(sock_fd, ident_query, strlen(ident_query), 0);
1799 [ # # # # ]: 0 : } while (rc < 0 && errno == EINTR);
1800 : :
1801 [ # # ]: 0 : if (rc < 0)
1802 : : {
1803 [ # # ]: 0 : ereport(LOG,
1804 : : (errcode_for_socket_access(),
1805 : : errmsg("could not send query to Ident server at address \"%s\", port %s: %m",
1806 : : remote_addr_s, ident_port)));
1807 : 0 : ident_return = false;
1808 : 0 : goto ident_inet_done;
1809 : : }
1810 : :
1811 : : do
1812 : : {
4109 andres@anarazel.de 1813 [ # # ]: 0 : CHECK_FOR_INTERRUPTS();
1814 : :
6486 magnus@hagander.net 1815 : 0 : rc = recv(sock_fd, ident_response, sizeof(ident_response) - 1, 0);
1816 [ # # # # ]: 0 : } while (rc < 0 && errno == EINTR);
1817 : :
1818 [ # # ]: 0 : if (rc < 0)
1819 : : {
1820 [ # # ]: 0 : ereport(LOG,
1821 : : (errcode_for_socket_access(),
1822 : : errmsg("could not receive response from Ident server at address \"%s\", port %s: %m",
1823 : : remote_addr_s, ident_port)));
1824 : 0 : ident_return = false;
1825 : 0 : goto ident_inet_done;
1826 : : }
1827 : :
1828 : 0 : ident_response[rc] = '\0';
1829 : 0 : ident_return = interpret_ident_response(ident_response, ident_user);
1830 [ # # ]: 0 : if (!ident_return)
1831 [ # # ]: 0 : ereport(LOG,
1832 : : (errmsg("invalidly formatted response from Ident server: \"%s\"",
1833 : : ident_response)));
1834 : :
1835 : 0 : ident_inet_done:
4402 bruce@momjian.us 1836 [ # # ]: 0 : if (sock_fd != PGINVALID_SOCKET)
6486 magnus@hagander.net 1837 : 0 : closesocket(sock_fd);
4101 tgl@sss.pgh.pa.us 1838 [ # # ]: 0 : if (ident_serv)
1839 : 0 : pg_freeaddrinfo_all(remote_addr.addr.ss_family, ident_serv);
1840 [ # # ]: 0 : if (la)
1841 : 0 : pg_freeaddrinfo_all(local_addr.addr.ss_family, la);
1842 : :
5526 magnus@hagander.net 1843 [ # # ]: 0 : if (ident_return)
1844 : : {
1845 : : /*
1846 : : * Success! Store the identity, then check the usermap. Note that
1847 : : * setting the authenticated identity is done before checking the
1848 : : * usermap, because at this point authentication has succeeded.
1849 : : */
1854 michael@paquier.xyz 1850 : 0 : set_authn_id(port, ident_user);
5526 magnus@hagander.net 1851 : 0 : return check_usermap(port->hba->usermap, port->user_name, ident_user, false);
1852 : : }
1853 : 0 : return STATUS_ERROR;
1854 : : }
1855 : :
1856 : :
1857 : : /*----------------------------------------------------------------
1858 : : * Peer authentication system
1859 : : *----------------------------------------------------------------
1860 : : */
1861 : :
1862 : : /*
1863 : : * Ask kernel about the credentials of the connecting process,
1864 : : * determine the symbolic name of the corresponding user, and check
1865 : : * if valid per the usermap.
1866 : : *
1867 : : * Iff authorized, return STATUS_OK, otherwise return STATUS_ERROR.
1868 : : */
1869 : : static int
232 peter@eisentraut.org 1870 :GNC 28 : auth_peer(Port *port)
1871 : : {
1872 : : uid_t uid;
1873 : : gid_t gid;
1874 : : #ifndef WIN32
1875 : : struct passwd pwbuf;
1876 : : struct passwd *pw;
1877 : : char buf[1024];
1878 : : int rc;
1879 : : int ret;
1880 : : #endif
1881 : :
5525 tgl@sss.pgh.pa.us 1882 [ - + ]:CBC 28 : if (getpeereid(port->sock, &uid, &gid) != 0)
1883 : : {
1884 : : /* Provide special error message if getpeereid is a stub */
5451 tgl@sss.pgh.pa.us 1885 [ # # ]:UBC 0 : if (errno == ENOSYS)
1886 [ # # ]: 0 : ereport(LOG,
1887 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
1888 : : errmsg("peer authentication is not supported on this platform")));
1889 : : else
1890 [ # # ]: 0 : ereport(LOG,
1891 : : (errcode_for_socket_access(),
1892 : : errmsg("could not get peer credentials: %m")));
5526 magnus@hagander.net 1893 : 0 : return STATUS_ERROR;
1894 : : }
1895 : :
1896 : : #ifndef WIN32
610 peter@eisentraut.org 1897 :CBC 28 : rc = getpwuid_r(uid, &pwbuf, buf, sizeof buf, &pw);
1898 [ - + ]: 28 : if (rc != 0)
1899 : : {
610 peter@eisentraut.org 1900 :UBC 0 : errno = rc;
1901 [ # # ]: 0 : ereport(LOG,
1902 : : errmsg("could not look up local user ID %ld: %m", (long) uid));
1903 : 0 : return STATUS_ERROR;
1904 : : }
610 peter@eisentraut.org 1905 [ - + ]:CBC 28 : else if (!pw)
1906 : : {
4421 tgl@sss.pgh.pa.us 1907 [ # # ]:UBC 0 : ereport(LOG,
1908 : : errmsg("local user with ID %ld does not exist", (long) uid));
5526 magnus@hagander.net 1909 : 0 : return STATUS_ERROR;
1910 : : }
1911 : :
1912 : : /*
1913 : : * Make a copy of static getpw*() result area; this is our authenticated
1914 : : * identity. Set it before calling check_usermap, because authentication
1915 : : * has already succeeded and we want the log file to reflect that.
1916 : : */
1854 michael@paquier.xyz 1917 :CBC 28 : set_authn_id(port, pw->pw_name);
1918 : :
1350 1919 : 28 : ret = check_usermap(port->hba->usermap, port->user_name,
1920 : : MyClientConnectionInfo.authn_id, false);
1921 : :
2379 peter@eisentraut.org 1922 : 28 : return ret;
1923 : : #else
1924 : : /* should have failed with ENOSYS above */
1925 : : Assert(false);
1926 : : return STATUS_ERROR;
1927 : : #endif
1928 : : }
1929 : :
1930 : :
1931 : : /*----------------------------------------------------------------
1932 : : * PAM authentication system
1933 : : *----------------------------------------------------------------
1934 : : */
1935 : : #ifdef USE_PAM
1936 : :
1937 : : /*
1938 : : * PAM conversation function
1939 : : */
1940 : :
1941 : : static int
286 tgl@sss.pgh.pa.us 1942 :UBC 0 : pam_passwd_conv_proc(int num_msg, PG_PAM_CONST struct pam_message **msg,
1943 : : struct pam_response **resp, void *appdata_ptr)
1944 : : {
1945 : : const char *passwd;
1946 : : struct pam_response *reply;
1947 : : int i;
1948 : :
6045 1949 [ # # ]: 0 : if (appdata_ptr)
1950 : 0 : passwd = (char *) appdata_ptr;
1951 : : else
1952 : : {
1953 : : /*
1954 : : * Workaround for Solaris 2.6 where the PAM library is broken and does
1955 : : * not pass appdata_ptr to the conversation routine
1956 : : */
1957 : 0 : passwd = pam_passwd;
1958 : : }
1959 : :
1960 : 0 : *resp = NULL; /* in case of error exit */
1961 : :
1962 [ # # # # ]: 0 : if (num_msg <= 0 || num_msg > PAM_MAX_NUM_MSG)
1963 : 0 : return PAM_CONV_ERR;
1964 : :
1965 : : /*
1966 : : * Explicitly not using palloc here - PAM will free this memory in
1967 : : * pam_end()
1968 : : */
1969 [ # # ]: 0 : if ((reply = calloc(num_msg, sizeof(struct pam_response))) == NULL)
1970 : : {
8323 1971 [ # # ]: 0 : ereport(LOG,
1972 : : (errcode(ERRCODE_OUT_OF_MEMORY),
1973 : : errmsg("out of memory")));
8958 bruce@momjian.us 1974 : 0 : return PAM_CONV_ERR;
1975 : : }
1976 : :
6045 tgl@sss.pgh.pa.us 1977 [ # # ]: 0 : for (i = 0; i < num_msg; i++)
1978 : : {
1979 [ # # # # ]: 0 : switch (msg[i]->msg_style)
1980 : : {
1981 : 0 : case PAM_PROMPT_ECHO_OFF:
1982 [ # # ]: 0 : if (strlen(passwd) == 0)
1983 : : {
1984 : : /*
1985 : : * Password wasn't passed to PAM the first time around -
1986 : : * let's go ask the client to send a password, which we
1987 : : * then stuff into PAM.
1988 : : */
3547 heikki.linnakangas@i 1989 : 0 : sendAuthRequest(pam_port_cludge, AUTH_REQ_PASSWORD, NULL, 0);
6045 tgl@sss.pgh.pa.us 1990 : 0 : passwd = recv_password_packet(pam_port_cludge);
1991 [ # # ]: 0 : if (passwd == NULL)
1992 : : {
1993 : : /*
1994 : : * Client didn't want to send password. We
1995 : : * intentionally do not log anything about this,
1996 : : * either here or at higher levels.
1997 : : */
2373 1998 : 0 : pam_no_password = true;
6045 1999 : 0 : goto fail;
2000 : : }
2001 : : }
2002 [ # # ]: 0 : if ((reply[i].resp = strdup(passwd)) == NULL)
2003 : 0 : goto fail;
2004 : 0 : reply[i].resp_retcode = PAM_SUCCESS;
2005 : 0 : break;
2006 : 0 : case PAM_ERROR_MSG:
2007 [ # # ]: 0 : ereport(LOG,
2008 : : (errmsg("error from underlying PAM layer: %s",
2009 : : msg[i]->msg)));
2010 : : pg_fallthrough;
2011 : : case PAM_TEXT_INFO:
2012 : : /* we don't bother to log TEXT_INFO messages */
2013 [ # # ]: 0 : if ((reply[i].resp = strdup("")) == NULL)
2014 : 0 : goto fail;
2015 : 0 : reply[i].resp_retcode = PAM_SUCCESS;
2016 : 0 : break;
2017 : 0 : default:
1978 peter@eisentraut.org 2018 [ # # # # ]: 0 : ereport(LOG,
2019 : : (errmsg("unsupported PAM conversation %d/\"%s\"",
2020 : : msg[i]->msg_style,
2021 : : msg[i]->msg ? msg[i]->msg : "(none)")));
6045 tgl@sss.pgh.pa.us 2022 : 0 : goto fail;
2023 : : }
2024 : : }
2025 : :
2026 : 0 : *resp = reply;
2027 : 0 : return PAM_SUCCESS;
2028 : :
2029 : 0 : fail:
2030 : : /* free up whatever we allocated */
2031 [ # # ]: 0 : for (i = 0; i < num_msg; i++)
1419 peter@eisentraut.org 2032 : 0 : free(reply[i].resp);
6045 tgl@sss.pgh.pa.us 2033 : 0 : free(reply);
2034 : :
2035 : 0 : return PAM_CONV_ERR;
2036 : : }
2037 : :
2038 : :
2039 : : /*
2040 : : * Check authentication against PAM.
2041 : : */
2042 : : static int
3108 peter_e@gmx.net 2043 : 0 : CheckPAMAuth(Port *port, const char *user, const char *password)
2044 : : {
2045 : : int retval;
9007 bruce@momjian.us 2046 : 0 : pam_handle_t *pamh = NULL;
2047 : :
2048 : : /*
2049 : : * We can't entirely rely on PAM to pass through appdata --- it appears
2050 : : * not to work on at least Solaris 2.6. So use these ugly static
2051 : : * variables instead.
2052 : : */
2053 : 0 : pam_passwd = password;
6045 tgl@sss.pgh.pa.us 2054 : 0 : pam_port_cludge = port;
2373 2055 : 0 : pam_no_password = false;
2056 : :
2057 : : /*
2058 : : * Set the application data portion of the conversation struct. This is
2059 : : * later used inside the PAM conversation to pass the password to the
2060 : : * authentication module.
2061 : : */
2540 2062 : 0 : pam_passw_conv.appdata_ptr = unconstify(char *, password); /* from password above,
2063 : : * not allocated */
2064 : :
2065 : : /* Optionally, one can set the service name in pg_hba.conf */
6403 magnus@hagander.net 2066 [ # # # # ]: 0 : if (port->hba->pamservice && port->hba->pamservice[0] != '\0')
2067 : 0 : retval = pam_start(port->hba->pamservice, "pgsql@",
2068 : : &pam_passw_conv, &pamh);
2069 : : else
8419 tgl@sss.pgh.pa.us 2070 : 0 : retval = pam_start(PGSQL_PAM_SERVICE, "pgsql@",
2071 : : &pam_passw_conv, &pamh);
2072 : :
8958 bruce@momjian.us 2073 [ # # ]: 0 : if (retval != PAM_SUCCESS)
2074 : : {
8323 tgl@sss.pgh.pa.us 2075 [ # # ]: 0 : ereport(LOG,
2076 : : (errmsg("could not create PAM authenticator: %s",
2077 : : pam_strerror(pamh, retval))));
8958 bruce@momjian.us 2078 : 0 : pam_passwd = NULL; /* Unset pam_passwd */
9007 2079 : 0 : return STATUS_ERROR;
2080 : : }
2081 : :
8835 2082 : 0 : retval = pam_set_item(pamh, PAM_USER, user);
2083 : :
2084 [ # # ]: 0 : if (retval != PAM_SUCCESS)
2085 : : {
8323 tgl@sss.pgh.pa.us 2086 [ # # ]: 0 : ereport(LOG,
2087 : : (errmsg("pam_set_item(PAM_USER) failed: %s",
2088 : : pam_strerror(pamh, retval))));
8958 bruce@momjian.us 2089 : 0 : pam_passwd = NULL; /* Unset pam_passwd */
9007 2090 : 0 : return STATUS_ERROR;
2091 : : }
2092 : :
2715 tmunro@postgresql.or 2093 [ # # ]: 0 : if (port->hba->conntype != ctLocal)
2094 : : {
2095 : : char hostinfo[NI_MAXHOST];
2096 : : int flags;
2097 : :
2098 [ # # ]: 0 : if (port->hba->pam_use_hostname)
2099 : 0 : flags = 0;
2100 : : else
2101 : 0 : flags = NI_NUMERICHOST | NI_NUMERICSERV;
2102 : :
2103 : 0 : retval = pg_getnameinfo_all(&port->raddr.addr, port->raddr.salen,
2104 : : hostinfo, sizeof(hostinfo), NULL, 0,
2105 : : flags);
2106 [ # # ]: 0 : if (retval != 0)
2107 : : {
2108 [ # # ]: 0 : ereport(WARNING,
2109 : : (errmsg_internal("pg_getnameinfo_all() failed: %s",
2110 : : gai_strerror(retval))));
2111 : 0 : return STATUS_ERROR;
2112 : : }
2113 : :
2114 : 0 : retval = pam_set_item(pamh, PAM_RHOST, hostinfo);
2115 : :
2116 [ # # ]: 0 : if (retval != PAM_SUCCESS)
2117 : : {
2118 [ # # ]: 0 : ereport(LOG,
2119 : : (errmsg("pam_set_item(PAM_RHOST) failed: %s",
2120 : : pam_strerror(pamh, retval))));
2121 : 0 : pam_passwd = NULL;
2122 : 0 : return STATUS_ERROR;
2123 : : }
2124 : : }
2125 : :
8835 bruce@momjian.us 2126 : 0 : retval = pam_set_item(pamh, PAM_CONV, &pam_passw_conv);
2127 : :
2128 [ # # ]: 0 : if (retval != PAM_SUCCESS)
2129 : : {
8323 tgl@sss.pgh.pa.us 2130 [ # # ]: 0 : ereport(LOG,
2131 : : (errmsg("pam_set_item(PAM_CONV) failed: %s",
2132 : : pam_strerror(pamh, retval))));
8958 bruce@momjian.us 2133 : 0 : pam_passwd = NULL; /* Unset pam_passwd */
9007 2134 : 0 : return STATUS_ERROR;
2135 : : }
2136 : :
8835 2137 : 0 : retval = pam_authenticate(pamh, 0);
2138 : :
2139 [ # # ]: 0 : if (retval != PAM_SUCCESS)
2140 : : {
2141 : : /* If pam_passwd_conv_proc saw EOF, don't log anything */
2373 tgl@sss.pgh.pa.us 2142 [ # # ]: 0 : if (!pam_no_password)
2143 [ # # ]: 0 : ereport(LOG,
2144 : : (errmsg("pam_authenticate failed: %s",
2145 : : pam_strerror(pamh, retval))));
8958 bruce@momjian.us 2146 : 0 : pam_passwd = NULL; /* Unset pam_passwd */
2373 tgl@sss.pgh.pa.us 2147 [ # # ]: 0 : return pam_no_password ? STATUS_EOF : STATUS_ERROR;
2148 : : }
2149 : :
8835 bruce@momjian.us 2150 : 0 : retval = pam_acct_mgmt(pamh, 0);
2151 : :
2152 [ # # ]: 0 : if (retval != PAM_SUCCESS)
2153 : : {
2154 : : /* If pam_passwd_conv_proc saw EOF, don't log anything */
2373 tgl@sss.pgh.pa.us 2155 [ # # ]: 0 : if (!pam_no_password)
2156 [ # # ]: 0 : ereport(LOG,
2157 : : (errmsg("pam_acct_mgmt failed: %s",
2158 : : pam_strerror(pamh, retval))));
8958 bruce@momjian.us 2159 : 0 : pam_passwd = NULL; /* Unset pam_passwd */
2373 tgl@sss.pgh.pa.us 2160 [ # # ]: 0 : return pam_no_password ? STATUS_EOF : STATUS_ERROR;
2161 : : }
2162 : :
8835 bruce@momjian.us 2163 : 0 : retval = pam_end(pamh, retval);
2164 : :
2165 [ # # ]: 0 : if (retval != PAM_SUCCESS)
2166 : : {
8323 tgl@sss.pgh.pa.us 2167 [ # # ]: 0 : ereport(LOG,
2168 : : (errmsg("could not release PAM authenticator: %s",
2169 : : pam_strerror(pamh, retval))));
2170 : : }
2171 : :
8644 bruce@momjian.us 2172 : 0 : pam_passwd = NULL; /* Unset pam_passwd */
2173 : :
1854 michael@paquier.xyz 2174 [ # # ]: 0 : if (retval == PAM_SUCCESS)
2175 : 0 : set_authn_id(port, user);
2176 : :
8835 bruce@momjian.us 2177 [ # # ]: 0 : return (retval == PAM_SUCCESS ? STATUS_OK : STATUS_ERROR);
2178 : : }
2179 : : #endif /* USE_PAM */
2180 : :
2181 : :
2182 : : /*----------------------------------------------------------------
2183 : : * BSD authentication system
2184 : : *----------------------------------------------------------------
2185 : : */
2186 : : #ifdef USE_BSD_AUTH
2187 : : static int
2188 : : CheckBSDAuth(Port *port, char *user)
2189 : : {
2190 : : char *passwd;
2191 : : int retval;
2192 : :
2193 : : /* Send regular password request to client, and get the response */
2194 : : sendAuthRequest(port, AUTH_REQ_PASSWORD, NULL, 0);
2195 : :
2196 : : passwd = recv_password_packet(port);
2197 : : if (passwd == NULL)
2198 : : return STATUS_EOF;
2199 : :
2200 : : /*
2201 : : * Ask the BSD auth system to verify password. Note that auth_userokay
2202 : : * will overwrite the password string with zeroes, but it's just a
2203 : : * temporary string so we don't care.
2204 : : */
2205 : : retval = auth_userokay(user, NULL, "auth-postgresql", passwd);
2206 : :
2207 : : pfree(passwd);
2208 : :
2209 : : if (!retval)
2210 : : return STATUS_ERROR;
2211 : :
2212 : : set_authn_id(port, user);
2213 : : return STATUS_OK;
2214 : : }
2215 : : #endif /* USE_BSD_AUTH */
2216 : :
2217 : :
2218 : : /*----------------------------------------------------------------
2219 : : * LDAP authentication system
2220 : : *----------------------------------------------------------------
2221 : : */
2222 : : #ifdef USE_LDAP
2223 : :
2224 : : static int errdetail_for_ldap(LDAP *ldap);
2225 : :
2226 : : /*
2227 : : * Initialize a connection to the LDAP server, including setting up
2228 : : * TLS if requested.
2229 : : */
2230 : : static int
5988 magnus@hagander.net 2231 : 0 : InitializeLDAPConnection(Port *port, LDAP **ldap)
2232 : : {
2233 : : const char *scheme;
7153 bruce@momjian.us 2234 : 0 : int ldapversion = LDAP_VERSION3;
2235 : : int r;
2236 : :
3044 peter_e@gmx.net 2237 : 0 : scheme = port->hba->ldapscheme;
2238 [ # # ]: 0 : if (scheme == NULL)
2239 : 0 : scheme = "ldap";
2240 : : #ifdef WIN32
2241 : : if (strcmp(scheme, "ldaps") == 0)
2242 : : *ldap = ldap_sslinit(port->hba->ldapserver, port->hba->ldapport, 1);
2243 : : else
2244 : : *ldap = ldap_init(port->hba->ldapserver, port->hba->ldapport);
2245 : : if (!*ldap)
2246 : : {
2247 : : ereport(LOG,
2248 : : (errmsg("could not initialize LDAP: error code %lu",
2249 : : LdapGetLastError())));
2250 : :
2251 : : return STATUS_ERROR;
2252 : : }
2253 : : #else
2254 : : #ifdef HAVE_LDAP_INITIALIZE
2255 : :
2256 : : /*
2257 : : * OpenLDAP provides a non-standard extension ldap_initialize() that takes
2258 : : * a list of URIs, allowing us to request "ldaps" instead of "ldap". It
2259 : : * also provides ldap_domain2hostlist() to find LDAP servers automatically
2260 : : * using DNS SRV. They were introduced in the same version, so for now we
2261 : : * don't have an extra configure check for the latter.
2262 : : */
2263 : : {
2264 : : StringInfoData uris;
2602 tmunro@postgresql.or 2265 : 0 : char *hostlist = NULL;
2266 : : char *p;
2267 : : bool append_port;
2268 : :
2269 : : /* We'll build a space-separated scheme://hostname:port list here */
2270 : 0 : initStringInfo(&uris);
2271 : :
2272 : : /*
2273 : : * If pg_hba.conf provided no hostnames, we can ask OpenLDAP to try to
2274 : : * find some by extracting a domain name from the base DN and looking
2275 : : * up DSN SRV records for _ldap._tcp.<domain>.
2276 : : */
2277 [ # # # # ]: 0 : if (!port->hba->ldapserver || port->hba->ldapserver[0] == '\0')
2278 : 0 : {
2279 : : char *domain;
2280 : :
2281 : : /* ou=blah,dc=foo,dc=bar -> foo.bar */
2282 [ # # ]: 0 : if (ldap_dn2domain(port->hba->ldapbasedn, &domain))
2283 : : {
2284 [ # # ]: 0 : ereport(LOG,
2285 : : (errmsg("could not extract domain name from ldapbasedn")));
2286 : 0 : return STATUS_ERROR;
2287 : : }
2288 : :
2289 : : /* Look up a list of LDAP server hosts and port numbers */
2290 [ # # ]: 0 : if (ldap_domain2hostlist(domain, &hostlist))
2291 : : {
2292 [ # # ]: 0 : ereport(LOG,
2293 : : (errmsg("LDAP authentication could not find DNS SRV records for \"%s\"",
2294 : : domain),
2295 : : (errhint("Set an LDAP server name explicitly."))));
2296 : 0 : ldap_memfree(domain);
2297 : 0 : return STATUS_ERROR;
2298 : : }
2299 : 0 : ldap_memfree(domain);
2300 : :
2301 : : /* We have a space-separated list of host:port entries */
2302 : 0 : p = hostlist;
2303 : 0 : append_port = false;
2304 : : }
2305 : : else
2306 : : {
2307 : : /* We have a space-separated list of hosts from pg_hba.conf */
2308 : 0 : p = port->hba->ldapserver;
2309 : 0 : append_port = true;
2310 : : }
2311 : :
2312 : : /* Convert the list of host[:port] entries to full URIs */
2313 : : do
2314 : : {
2315 : : size_t size;
2316 : :
2317 : : /* Find the span of the next entry */
2318 : 0 : size = strcspn(p, " ");
2319 : :
2320 : : /* Append a space separator if this isn't the first URI */
2321 [ # # ]: 0 : if (uris.len > 0)
2322 : 0 : appendStringInfoChar(&uris, ' ');
2323 : :
2324 : : /* Append scheme://host:port */
2325 : 0 : appendStringInfoString(&uris, scheme);
2326 : 0 : appendStringInfoString(&uris, "://");
2327 : 0 : appendBinaryStringInfo(&uris, p, size);
2328 [ # # ]: 0 : if (append_port)
2329 : 0 : appendStringInfo(&uris, ":%d", port->hba->ldapport);
2330 : :
2331 : : /* Step over this entry and any number of trailing spaces */
2332 : 0 : p += size;
2333 [ # # ]: 0 : while (*p == ' ')
2334 : 0 : ++p;
2335 [ # # ]: 0 : } while (*p);
2336 : :
2337 : : /* Free memory from OpenLDAP if we looked up SRV records */
2338 [ # # ]: 0 : if (hostlist)
2339 : 0 : ldap_memfree(hostlist);
2340 : :
2341 : : /* Finally, try to connect using the URI list */
2342 : 0 : r = ldap_initialize(ldap, uris.data);
2343 : 0 : pfree(uris.data);
3044 peter_e@gmx.net 2344 [ # # ]: 0 : if (r != LDAP_SUCCESS)
2345 : : {
2346 [ # # ]: 0 : ereport(LOG,
2347 : : (errmsg("could not initialize LDAP: %s",
2348 : : ldap_err2string(r))));
2349 : :
2350 : 0 : return STATUS_ERROR;
2351 : : }
2352 : : }
2353 : : #else
2354 : : if (strcmp(scheme, "ldaps") == 0)
2355 : : {
2356 : : ereport(LOG,
2357 : : (errmsg("ldaps not supported with this LDAP library")));
2358 : :
2359 : : return STATUS_ERROR;
2360 : : }
2361 : : *ldap = ldap_init(port->hba->ldapserver, port->hba->ldapport);
2362 : : if (!*ldap)
2363 : : {
2364 : : ereport(LOG,
2365 : : (errmsg("could not initialize LDAP: %m")));
2366 : :
2367 : : return STATUS_ERROR;
2368 : : }
2369 : : #endif
2370 : : #endif
2371 : :
5988 magnus@hagander.net 2372 [ # # ]: 0 : if ((r = ldap_set_option(*ldap, LDAP_OPT_PROTOCOL_VERSION, &ldapversion)) != LDAP_SUCCESS)
2373 : : {
7153 bruce@momjian.us 2374 [ # # ]: 0 : ereport(LOG,
2375 : : (errmsg("could not set LDAP protocol version: %s",
2376 : : ldap_err2string(r)),
2377 : : errdetail_for_ldap(*ldap)));
3127 peter_e@gmx.net 2378 : 0 : ldap_unbind(*ldap);
7153 bruce@momjian.us 2379 : 0 : return STATUS_ERROR;
2380 : : }
2381 : :
6403 magnus@hagander.net 2382 [ # # ]: 0 : if (port->hba->ldaptls)
2383 : : {
2384 : : #ifndef WIN32
5988 2385 [ # # ]: 0 : if ((r = ldap_start_tls_s(*ldap, NULL, NULL)) != LDAP_SUCCESS)
2386 : : #else
2387 : : if ((r = ldap_start_tls_s(*ldap, NULL, NULL, NULL, NULL)) != LDAP_SUCCESS)
2388 : : #endif
2389 : : {
7153 bruce@momjian.us 2390 [ # # ]: 0 : ereport(LOG,
2391 : : (errmsg("could not start LDAP TLS session: %s",
2392 : : ldap_err2string(r)),
2393 : : errdetail_for_ldap(*ldap)));
3127 peter_e@gmx.net 2394 : 0 : ldap_unbind(*ldap);
7153 bruce@momjian.us 2395 : 0 : return STATUS_ERROR;
2396 : : }
2397 : : }
2398 : :
5988 magnus@hagander.net 2399 : 0 : return STATUS_OK;
2400 : : }
2401 : :
2402 : : /* Placeholders recognized by FormatSearchFilter. For now just one. */
2403 : : #define LPH_USERNAME "$username"
2404 : : #define LPH_USERNAME_LEN (sizeof(LPH_USERNAME) - 1)
2405 : :
2406 : : /* Not all LDAP implementations define this. */
2407 : : #ifndef LDAP_NO_ATTRS
2408 : : #define LDAP_NO_ATTRS "1.1"
2409 : : #endif
2410 : :
2411 : : /* Not all LDAP implementations define this. */
2412 : : #ifndef LDAPS_PORT
2413 : : #define LDAPS_PORT 636
2414 : : #endif
2415 : :
2416 : : static char *
1147 andrew@dunslane.net 2417 : 0 : dummy_ldap_password_mutator(char *input)
2418 : : {
2419 : 0 : return input;
2420 : : }
2421 : :
2422 : : /*
2423 : : * Return a newly allocated C string copied from "pattern" with all
2424 : : * occurrences of the placeholder "$username" replaced with "user_name".
2425 : : */
2426 : : static char *
3157 peter_e@gmx.net 2427 : 0 : FormatSearchFilter(const char *pattern, const char *user_name)
2428 : : {
2429 : : StringInfoData output;
2430 : :
2431 : 0 : initStringInfo(&output);
2432 [ # # ]: 0 : while (*pattern != '\0')
2433 : : {
2434 [ # # ]: 0 : if (strncmp(pattern, LPH_USERNAME, LPH_USERNAME_LEN) == 0)
2435 : : {
2436 : 0 : appendStringInfoString(&output, user_name);
2437 : 0 : pattern += LPH_USERNAME_LEN;
2438 : : }
2439 : : else
2440 : 0 : appendStringInfoChar(&output, *pattern++);
2441 : : }
2442 : :
2443 : 0 : return output.data;
2444 : : }
2445 : :
2446 : : /*
2447 : : * Perform LDAP authentication
2448 : : */
2449 : : static int
5988 magnus@hagander.net 2450 : 0 : CheckLDAPAuth(Port *port)
2451 : : {
2452 : : char *passwd;
2453 : : LDAP *ldap;
2454 : : int r;
2455 : : char *fulluser;
2456 : : const char *server_name;
2457 : :
2458 : : #ifdef HAVE_LDAP_INITIALIZE
2459 : :
2460 : : /*
2461 : : * For OpenLDAP, allow empty hostname if we have a basedn. We'll look for
2462 : : * servers with DNS SRV records via OpenLDAP library facilities.
2463 : : */
2602 tmunro@postgresql.or 2464 [ # # # # ]: 0 : if ((!port->hba->ldapserver || port->hba->ldapserver[0] == '\0') &&
2465 [ # # # # ]: 0 : (!port->hba->ldapbasedn || port->hba->ldapbasedn[0] == '\0'))
2466 : : {
2467 [ # # ]: 0 : ereport(LOG,
2468 : : (errmsg("LDAP server not specified, and no ldapbasedn")));
2469 : 0 : return STATUS_ERROR;
2470 : : }
2471 : : #else
2472 : : if (!port->hba->ldapserver || port->hba->ldapserver[0] == '\0')
2473 : : {
2474 : : ereport(LOG,
2475 : : (errmsg("LDAP server not specified")));
2476 : : return STATUS_ERROR;
2477 : : }
2478 : : #endif
2479 : :
2480 : : /*
2481 : : * If we're using SRV records, we don't have a server name so we'll just
2482 : : * show an empty string in error messages.
2483 : : */
2484 [ # # ]: 0 : server_name = port->hba->ldapserver ? port->hba->ldapserver : "";
2485 : :
5988 magnus@hagander.net 2486 [ # # ]: 0 : if (port->hba->ldapport == 0)
2487 : : {
3044 peter_e@gmx.net 2488 [ # # ]: 0 : if (port->hba->ldapscheme != NULL &&
2489 [ # # ]: 0 : strcmp(port->hba->ldapscheme, "ldaps") == 0)
2490 : 0 : port->hba->ldapport = LDAPS_PORT;
2491 : : else
2492 : 0 : port->hba->ldapport = LDAP_PORT;
2493 : : }
2494 : :
3547 heikki.linnakangas@i 2495 : 0 : sendAuthRequest(port, AUTH_REQ_PASSWORD, NULL, 0);
2496 : :
5988 magnus@hagander.net 2497 : 0 : passwd = recv_password_packet(port);
2498 [ # # ]: 0 : if (passwd == NULL)
2499 : 0 : return STATUS_EOF; /* client wouldn't send password */
2500 : :
2501 [ # # ]: 0 : if (InitializeLDAPConnection(port, &ldap) == STATUS_ERROR)
2502 : : {
2503 : : /* Error message already sent */
3193 heikki.linnakangas@i 2504 : 0 : pfree(passwd);
5988 magnus@hagander.net 2505 : 0 : return STATUS_ERROR;
2506 : : }
2507 : :
2508 [ # # ]: 0 : if (port->hba->ldapbasedn)
2509 : : {
2510 : : /*
2511 : : * First perform an LDAP search to find the DN for the user we are
2512 : : * trying to log in as.
2513 : : */
2514 : : char *filter;
2515 : : LDAPMessage *search_message;
2516 : : LDAPMessage *entry;
3079 rhaas@postgresql.org 2517 : 0 : char *attributes[] = {LDAP_NO_ATTRS, NULL};
2518 : : char *dn;
2519 : : char *c;
2520 : : int count;
2521 : :
2522 : : /*
2523 : : * Disallow any characters that we would otherwise need to escape,
2524 : : * since they aren't really reasonable in a username anyway. Allowing
2525 : : * them would make it possible to inject any kind of custom filters in
2526 : : * the LDAP filter.
2527 : : */
5988 magnus@hagander.net 2528 [ # # ]: 0 : for (c = port->user_name; *c; c++)
2529 : : {
2530 [ # # ]: 0 : if (*c == '*' ||
2531 [ # # ]: 0 : *c == '(' ||
2532 [ # # ]: 0 : *c == ')' ||
2533 [ # # ]: 0 : *c == '\\' ||
2534 [ # # ]: 0 : *c == '/')
2535 : : {
2536 [ # # ]: 0 : ereport(LOG,
2537 : : (errmsg("invalid character in user name for LDAP authentication")));
3127 peter_e@gmx.net 2538 : 0 : ldap_unbind(ldap);
3193 heikki.linnakangas@i 2539 : 0 : pfree(passwd);
5988 magnus@hagander.net 2540 : 0 : return STATUS_ERROR;
2541 : : }
2542 : : }
2543 : :
2544 : : /*
2545 : : * Bind with a pre-defined username/password (if available) for
2546 : : * searching. If none is specified, this turns into an anonymous bind.
2547 : : */
2548 : 0 : r = ldap_simple_bind_s(ldap,
3240 tgl@sss.pgh.pa.us 2549 [ # # ]: 0 : port->hba->ldapbinddn ? port->hba->ldapbinddn : "",
1147 andrew@dunslane.net 2550 [ # # ]: 0 : port->hba->ldapbindpasswd ? ldap_password_hook(port->hba->ldapbindpasswd) : "");
5988 magnus@hagander.net 2551 [ # # ]: 0 : if (r != LDAP_SUCCESS)
2552 : : {
2553 [ # # # # ]: 0 : ereport(LOG,
2554 : : (errmsg("could not perform initial LDAP bind for ldapbinddn \"%s\" on server \"%s\": %s",
2555 : : port->hba->ldapbinddn ? port->hba->ldapbinddn : "",
2556 : : server_name,
2557 : : ldap_err2string(r)),
2558 : : errdetail_for_ldap(ldap)));
3127 peter_e@gmx.net 2559 : 0 : ldap_unbind(ldap);
3193 heikki.linnakangas@i 2560 : 0 : pfree(passwd);
5988 magnus@hagander.net 2561 : 0 : return STATUS_ERROR;
2562 : : }
2563 : :
2564 : : /* Build a custom filter or a single attribute filter? */
3157 peter_e@gmx.net 2565 [ # # ]: 0 : if (port->hba->ldapsearchfilter)
2566 : 0 : filter = FormatSearchFilter(port->hba->ldapsearchfilter, port->user_name);
2567 [ # # ]: 0 : else if (port->hba->ldapsearchattribute)
2568 : 0 : filter = psprintf("(%s=%s)", port->hba->ldapsearchattribute, port->user_name);
2569 : : else
2570 : 0 : filter = psprintf("(uid=%s)", port->user_name);
2571 : :
1333 michael@paquier.xyz 2572 : 0 : search_message = NULL;
5988 magnus@hagander.net 2573 : 0 : r = ldap_search_s(ldap,
2574 : 0 : port->hba->ldapbasedn,
4901 peter_e@gmx.net 2575 : 0 : port->hba->ldapscope,
2576 : : filter,
2577 : : attributes,
2578 : : 0,
2579 : : &search_message);
2580 : :
5988 magnus@hagander.net 2581 [ # # ]: 0 : if (r != LDAP_SUCCESS)
2582 : : {
2583 [ # # ]: 0 : ereport(LOG,
2584 : : (errmsg("could not search LDAP for filter \"%s\" on server \"%s\": %s",
2585 : : filter, server_name, ldap_err2string(r)),
2586 : : errdetail_for_ldap(ldap)));
1333 michael@paquier.xyz 2587 [ # # ]: 0 : if (search_message != NULL)
2588 : 0 : ldap_msgfree(search_message);
3127 peter_e@gmx.net 2589 : 0 : ldap_unbind(ldap);
3193 heikki.linnakangas@i 2590 : 0 : pfree(passwd);
5988 magnus@hagander.net 2591 : 0 : pfree(filter);
2592 : 0 : return STATUS_ERROR;
2593 : : }
2594 : :
4963 peter_e@gmx.net 2595 : 0 : count = ldap_count_entries(ldap, search_message);
2596 [ # # ]: 0 : if (count != 1)
2597 : : {
2598 [ # # ]: 0 : if (count == 0)
5988 magnus@hagander.net 2599 [ # # ]: 0 : ereport(LOG,
2600 : : (errmsg("LDAP user \"%s\" does not exist", port->user_name),
2601 : : errdetail("LDAP search for filter \"%s\" on server \"%s\" returned no entries.",
2602 : : filter, server_name)));
2603 : : else
2604 [ # # ]: 0 : ereport(LOG,
2605 : : (errmsg("LDAP user \"%s\" is not unique", port->user_name),
2606 : : errdetail_plural("LDAP search for filter \"%s\" on server \"%s\" returned %d entry.",
2607 : : "LDAP search for filter \"%s\" on server \"%s\" returned %d entries.",
2608 : : count,
2609 : : filter, server_name, count)));
2610 : :
3127 peter_e@gmx.net 2611 : 0 : ldap_unbind(ldap);
3193 heikki.linnakangas@i 2612 : 0 : pfree(passwd);
5988 magnus@hagander.net 2613 : 0 : pfree(filter);
2614 : 0 : ldap_msgfree(search_message);
2615 : 0 : return STATUS_ERROR;
2616 : : }
2617 : :
2618 : 0 : entry = ldap_first_entry(ldap, search_message);
2619 : 0 : dn = ldap_get_dn(ldap, entry);
2620 [ # # ]: 0 : if (dn == NULL)
2621 : : {
2622 : : int error;
2623 : :
5912 bruce@momjian.us 2624 : 0 : (void) ldap_get_option(ldap, LDAP_OPT_ERROR_NUMBER, &error);
5988 magnus@hagander.net 2625 [ # # ]: 0 : ereport(LOG,
2626 : : (errmsg("could not get dn for the first entry matching \"%s\" on server \"%s\": %s",
2627 : : filter, server_name,
2628 : : ldap_err2string(error)),
2629 : : errdetail_for_ldap(ldap)));
3127 peter_e@gmx.net 2630 : 0 : ldap_unbind(ldap);
3193 heikki.linnakangas@i 2631 : 0 : pfree(passwd);
5988 magnus@hagander.net 2632 : 0 : pfree(filter);
2633 : 0 : ldap_msgfree(search_message);
2634 : 0 : return STATUS_ERROR;
2635 : : }
2636 : 0 : fulluser = pstrdup(dn);
2637 : :
2638 : 0 : pfree(filter);
2639 : 0 : ldap_memfree(dn);
2640 : 0 : ldap_msgfree(search_message);
2641 : : }
2642 : : else
4587 peter_e@gmx.net 2643 : 0 : fulluser = psprintf("%s%s%s",
3240 tgl@sss.pgh.pa.us 2644 [ # # ]: 0 : port->hba->ldapprefix ? port->hba->ldapprefix : "",
2645 : : port->user_name,
2646 [ # # ]: 0 : port->hba->ldapsuffix ? port->hba->ldapsuffix : "");
2647 : :
7153 bruce@momjian.us 2648 : 0 : r = ldap_simple_bind_s(ldap, fulluser, passwd);
2649 : :
2650 [ # # ]: 0 : if (r != LDAP_SUCCESS)
2651 : : {
2652 [ # # ]: 0 : ereport(LOG,
2653 : : (errmsg("LDAP login failed for user \"%s\" on server \"%s\": %s",
2654 : : fulluser, server_name, ldap_err2string(r)),
2655 : : errdetail_for_ldap(ldap)));
3127 peter_e@gmx.net 2656 : 0 : ldap_unbind(ldap);
3193 heikki.linnakangas@i 2657 : 0 : pfree(passwd);
5988 magnus@hagander.net 2658 : 0 : pfree(fulluser);
7153 bruce@momjian.us 2659 : 0 : return STATUS_ERROR;
2660 : : }
2661 : :
2662 : : /* Save the original bind DN as the authenticated identity. */
1854 michael@paquier.xyz 2663 : 0 : set_authn_id(port, fulluser);
2664 : :
3127 peter_e@gmx.net 2665 : 0 : ldap_unbind(ldap);
3193 heikki.linnakangas@i 2666 : 0 : pfree(passwd);
5988 magnus@hagander.net 2667 : 0 : pfree(fulluser);
2668 : :
7153 bruce@momjian.us 2669 : 0 : return STATUS_OK;
2670 : : }
2671 : :
2672 : : /*
2673 : : * Add a detail error message text to the current error if one can be
2674 : : * constructed from the LDAP 'diagnostic message'.
2675 : : */
2676 : : static int
3127 peter_e@gmx.net 2677 : 0 : errdetail_for_ldap(LDAP *ldap)
2678 : : {
2679 : : char *message;
2680 : : int rc;
2681 : :
2682 : 0 : rc = ldap_get_option(ldap, LDAP_OPT_DIAGNOSTIC_MESSAGE, &message);
2683 [ # # # # ]: 0 : if (rc == LDAP_SUCCESS && message != NULL)
2684 : : {
2685 : 0 : errdetail("LDAP diagnostics: %s", message);
2686 : 0 : ldap_memfree(message);
2687 : : }
2688 : :
2689 : 0 : return 0;
2690 : : }
2691 : :
2692 : : #endif /* USE_LDAP */
2693 : :
2694 : :
2695 : : /*----------------------------------------------------------------
2696 : : * SSL client certificate authentication
2697 : : *----------------------------------------------------------------
2698 : : */
2699 : : #ifdef USE_SSL
2700 : : static int
6375 magnus@hagander.net 2701 :CBC 29 : CheckCertAuth(Port *port)
2702 : : {
2614 2703 : 29 : int status_check_usermap = STATUS_ERROR;
1863 andrew@dunslane.net 2704 : 29 : char *peer_username = NULL;
2705 : :
6375 magnus@hagander.net 2706 [ - + ]: 29 : Assert(port->ssl);
2707 : :
2708 : : /* select the correct field to compare */
1863 andrew@dunslane.net 2709 [ + + - ]: 29 : switch (port->hba->clientcertname)
2710 : : {
2711 : 2 : case clientCertDN:
2712 : 2 : peer_username = port->peer_dn;
2713 : 2 : break;
2714 : 27 : case clientCertCN:
2715 : 27 : peer_username = port->peer_cn;
2716 : : }
2717 : :
2718 : : /* Make sure we have received a username in the certificate */
2719 [ + - ]: 29 : if (peer_username == NULL ||
2720 [ - + ]: 29 : strlen(peer_username) <= 0)
2721 : : {
6375 magnus@hagander.net 2722 [ # # ]:UBC 0 : ereport(LOG,
2723 : : (errmsg("certificate authentication failed for user \"%s\": client certificate contains no user name",
2724 : : port->user_name)));
2725 : 0 : return STATUS_ERROR;
2726 : : }
2727 : :
1854 michael@paquier.xyz 2728 [ + + ]:CBC 29 : if (port->hba->auth_method == uaCert)
2729 : : {
2730 : : /*
2731 : : * For cert auth, the client's Subject DN is always our authenticated
2732 : : * identity, even if we're only using its CN for authorization. Set
2733 : : * it now, rather than waiting for check_usermap() below, because
2734 : : * authentication has already succeeded and we want the log file to
2735 : : * reflect that.
2736 : : */
2737 [ - + ]: 26 : if (!port->peer_dn)
2738 : : {
2739 : : /*
2740 : : * This should not happen as both peer_dn and peer_cn should be
2741 : : * set in this context.
2742 : : */
1854 michael@paquier.xyz 2743 [ # # ]:UBC 0 : ereport(LOG,
2744 : : (errmsg("certificate authentication failed for user \"%s\": unable to retrieve subject DN",
2745 : : port->user_name)));
2746 : 0 : return STATUS_ERROR;
2747 : : }
2748 : :
1854 michael@paquier.xyz 2749 :CBC 26 : set_authn_id(port, port->peer_dn);
2750 : : }
2751 : :
2752 : : /* Just pass the certificate cn/dn to the usermap check */
1863 andrew@dunslane.net 2753 : 29 : status_check_usermap = check_usermap(port->hba->usermap, port->user_name, peer_username, false);
2614 magnus@hagander.net 2754 [ + + ]: 29 : if (status_check_usermap != STATUS_OK)
2755 : : {
2756 : : /*
2757 : : * If clientcert=verify-full was specified and the authentication
2758 : : * method is other than uaCert, log the reason for rejecting the
2759 : : * authentication.
2760 : : */
2761 [ + - + + ]: 2 : if (port->hba->clientcert == clientCertFull && port->hba->auth_method != uaCert)
2762 : : {
1863 andrew@dunslane.net 2763 [ - + - ]: 1 : switch (port->hba->clientcertname)
2764 : : {
1863 andrew@dunslane.net 2765 :UBC 0 : case clientCertDN:
2766 [ # # ]: 0 : ereport(LOG,
2767 : : (errmsg("certificate validation (clientcert=verify-full) failed for user \"%s\": DN mismatch",
2768 : : port->user_name)));
2769 : 0 : break;
1863 andrew@dunslane.net 2770 :CBC 1 : case clientCertCN:
2771 [ + - ]: 1 : ereport(LOG,
2772 : : (errmsg("certificate validation (clientcert=verify-full) failed for user \"%s\": CN mismatch",
2773 : : port->user_name)));
2774 : : }
2775 : : }
2776 : : }
2614 magnus@hagander.net 2777 : 29 : return status_check_usermap;
2778 : : }
2779 : : #endif
|