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