auth.c 21 KB
Newer Older
1 2
/*-------------------------------------------------------------------------
 *
3
 * auth.c
4
 *	  Routines to handle network authentication
5
 *
Bruce Momjian's avatar
Bruce Momjian committed
6
 * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
Bruce Momjian's avatar
Add:  
Bruce Momjian committed
7
 * Portions Copyright (c) 1994, Regents of the University of California
8 9 10
 *
 *
 * IDENTIFICATION
Tom Lane's avatar
Tom Lane committed
11
 *	  $Header: /cvsroot/pgsql/src/backend/libpq/auth.c,v 1.108 2003/07/28 06:27:06 tgl Exp $
12 13 14
 *
 *-------------------------------------------------------------------------
 */
15 16

#include "postgres.h"
17

18
#include <sys/param.h>
19 20 21
#include <sys/socket.h>
#if defined(HAVE_STRUCT_CMSGCRED) || defined(HAVE_STRUCT_FCRED) || defined(HAVE_STRUCT_SOCKCRED)
#include <sys/uio.h>
22 23 24
#include <sys/ucred.h>
#include <errno.h>
#endif
25 26
#include <netinet/in.h>
#include <arpa/inet.h>
27

28
#include "libpq/auth.h"
Bruce Momjian's avatar
Bruce Momjian committed
29
#include "libpq/crypt.h"
30
#include "libpq/hba.h"
Bruce Momjian's avatar
Bruce Momjian committed
31
#include "libpq/libpq.h"
32
#include "libpq/pqcomm.h"
33
#include "libpq/pqformat.h"
Bruce Momjian's avatar
Bruce Momjian committed
34
#include "miscadmin.h"
35 36
#include "storage/ipc.h"

37

38
static void sendAuthRequest(Port *port, AuthRequest areq);
39
static void auth_failed(Port *port, int status);
40
static char *recv_password_packet(Port *port);
41
static int	recv_and_check_password_packet(Port *port);
42

43
char	   *pg_krb_server_keyfile;
44

Bruce Momjian's avatar
Bruce Momjian committed
45
#ifdef USE_PAM
46 47 48 49
#ifdef HAVE_PAM_PAM_APPL_H
#include <pam/pam_appl.h>
#endif
#ifdef HAVE_SECURITY_PAM_APPL_H
Bruce Momjian's avatar
Bruce Momjian committed
50
#include <security/pam_appl.h>
51
#endif
Bruce Momjian's avatar
Bruce Momjian committed
52

53 54 55 56 57
#define PGSQL_PAM_SERVICE "postgresql"	/* Service name passed to PAM */

static int	CheckPAMAuth(Port *port, char *user, char *password);
static int pam_passwd_conv_proc(int num_msg, const struct pam_message ** msg,
					 struct pam_response ** resp, void *appdata_ptr);
Bruce Momjian's avatar
Bruce Momjian committed
58 59

static struct pam_conv pam_passw_conv = {
60 61
	&pam_passwd_conv_proc,
	NULL
Bruce Momjian's avatar
Bruce Momjian committed
62 63
};

64 65 66
static char *pam_passwd = NULL; /* Workaround for Solaris 2.6 brokenness */
static Port *pam_port_cludge;	/* Workaround for passing "Port *port"
								 * into pam_passwd_conv_proc */
67
#endif   /* USE_PAM */
68

69 70 71 72 73 74
#ifdef KRB4
/*----------------------------------------------------------------
 * MIT Kerberos authentication system - protocol version 4
 *----------------------------------------------------------------
 */

75
#include "krb.h"
76 77 78

/*
 * pg_krb4_recvauth -- server routine to receive authentication information
79
 *					   from the client
80 81 82 83 84 85 86
 *
 * Nothing unusual here, except that we compare the username obtained from
 * the client's setup packet to the authenticated name.  (We have to retain
 * the name in the setup packet since we have to retain the ability to handle
 * unauthenticated connections.)
 */
static int
Bruce Momjian's avatar
Bruce Momjian committed
87
pg_krb4_recvauth(Port *port)
88
{
Bruce Momjian's avatar
Bruce Momjian committed
89 90 91 92 93 94 95
	long		krbopts = 0;	/* one-way authentication */
	KTEXT_ST	clttkt;
	char		instance[INST_SZ + 1],
				version[KRB_SENDAUTH_VLEN + 1];
	AUTH_DAT	auth_data;
	Key_schedule key_sched;
	int			status;
96 97 98 99

	strcpy(instance, "*");		/* don't care, but arg gets expanded
								 * anyway */
	status = krb_recvauth(krbopts,
100
						  port->sock,
101 102 103
						  &clttkt,
						  PG_KRB_SRVNAM,
						  instance,
104 105
						  &port->raddr.in,
						  &port->laddr.in,
106
						  &auth_data,
107
						  pg_krb_server_keyfile,
108 109 110 111
						  key_sched,
						  version);
	if (status != KSUCCESS)
	{
112 113
		ereport(LOG,
				(errmsg("kerberos error: %s", krb_err_txt[status])));
114
		return STATUS_ERROR;
115
	}
116
	if (strncmp(version, PG_KRB4_VERSION, KRB_SENDAUTH_VLEN) != 0)
117
	{
118 119 120
		ereport(LOG,
				(errmsg("kerberos protocol version \"%s\" != \"%s\"",
						version, PG_KRB4_VERSION)));
121
		return STATUS_ERROR;
122
	}
123
	if (strncmp(port->user_name, auth_data.pname, SM_DATABASE_USER) != 0)
124
	{
125 126 127
		ereport(LOG,
				(errmsg("kerberos user name \"%s\" != \"%s\"",
						port->user_name, auth_data.pname)));
128
		return STATUS_ERROR;
129
	}
130
	return STATUS_OK;
131 132
}

133
#else
134

135
static int
136
pg_krb4_recvauth(Port *port)
137
{
138 139 140
	ereport(LOG,
			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
			 errmsg("kerberos v4 not implemented on this server")));
141
	return STATUS_ERROR;
142
}
143
#endif   /* KRB4 */
144

145

146 147 148 149 150 151
#ifdef KRB5
/*----------------------------------------------------------------
 * MIT Kerberos authentication system - protocol version 5
 *----------------------------------------------------------------
 */

Bruce Momjian's avatar
Bruce Momjian committed
152 153
#include <krb5.h>
#include <com_err.h>
154 155 156

/*
 * pg_an_to_ln -- return the local name corresponding to an authentication
157
 *				  name
158 159
 *
 * XXX Assumes that the first aname component is the user name.  This is NOT
160 161 162 163 164 165 166
 *	   necessarily so, since an aname can actually be something out of your
 *	   worst X.400 nightmare, like
 *		  ORGANIZATION=U. C. Berkeley/NAME=Paul M. Aoki@CS.BERKELEY.EDU
 *	   Note that the MIT an_to_ln code does the same thing if you don't
 *	   provide an aname mapping database...it may be a better idea to use
 *	   krb5_an_to_ln, except that it punts if multiple components are found,
 *	   and we can't afford to punt.
167
 */
168
static char *
169 170
pg_an_to_ln(char *aname)
{
171
	char	   *p;
172 173 174

	if ((p = strchr(aname, '/')) || (p = strchr(aname, '@')))
		*p = '\0';
175
	return aname;
176 177
}

Bruce Momjian's avatar
Bruce Momjian committed
178

Bruce Momjian's avatar
Bruce Momjian committed
179
/*
Bruce Momjian's avatar
Bruce Momjian committed
180 181
 * Various krb5 state which is not connection specfic, and a flag to
 * indicate whether we have initialised it yet.
Bruce Momjian's avatar
Bruce Momjian committed
182
 */
183
static int	pg_krb5_initialised;
Bruce Momjian's avatar
Bruce Momjian committed
184 185 186 187 188
static krb5_context pg_krb5_context;
static krb5_keytab pg_krb5_keytab;
static krb5_principal pg_krb5_server;


Bruce Momjian's avatar
Bruce Momjian committed
189
static int
Bruce Momjian's avatar
Bruce Momjian committed
190
pg_krb5_init(void)
Bruce Momjian's avatar
Bruce Momjian committed
191
{
Bruce Momjian's avatar
Bruce Momjian committed
192
	krb5_error_code retval;
193

Bruce Momjian's avatar
Bruce Momjian committed
194 195 196 197
	if (pg_krb5_initialised)
		return STATUS_OK;

	retval = krb5_init_context(&pg_krb5_context);
198 199
	if (retval)
	{
200 201 202
		ereport(LOG,
				(errmsg("kerberos init returned error %d",
						retval)));
Bruce Momjian's avatar
Bruce Momjian committed
203
		com_err("postgres", retval, "while initializing krb5");
Bruce Momjian's avatar
Bruce Momjian committed
204
		return STATUS_ERROR;
205 206
	}

207
	retval = krb5_kt_resolve(pg_krb5_context, pg_krb_server_keyfile, &pg_krb5_keytab);
208 209
	if (retval)
	{
210 211 212 213
		ereport(LOG,
				(errmsg("kerberos keytab resolve returned error %d",
						retval)));
		com_err("postgres", retval, "while resolving keytab file \"%s\"",
214
				pg_krb_server_keyfile);
Bruce Momjian's avatar
Bruce Momjian committed
215 216
		krb5_free_context(pg_krb5_context);
		return STATUS_ERROR;
217 218
	}

219
	retval = krb5_sname_to_principal(pg_krb5_context, NULL, PG_KRB_SRVNAM,
Bruce Momjian's avatar
Bruce Momjian committed
220
									 KRB5_NT_SRV_HST, &pg_krb5_server);
221 222
	if (retval)
	{
223 224 225
		ereport(LOG,
				(errmsg("kerberos sname_to_principal(\"%s\") returned error %d",
						PG_KRB_SRVNAM, retval)));
226
		com_err("postgres", retval,
227
				"while getting server principal for service \"%s\"",
228
				PG_KRB_SRVNAM);
Bruce Momjian's avatar
Bruce Momjian committed
229 230
		krb5_kt_close(pg_krb5_context, pg_krb5_keytab);
		krb5_free_context(pg_krb5_context);
231 232
		return STATUS_ERROR;
	}
Bruce Momjian's avatar
Bruce Momjian committed
233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253

	pg_krb5_initialised = 1;
	return STATUS_OK;
}


/*
 * pg_krb5_recvauth -- server routine to receive authentication information
 *					   from the client
 *
 * We still need to compare the username obtained from the client's setup
 * packet to the authenticated name, as described in pg_krb4_recvauth.	This
 * is a bit more problematic in v5, as described above in pg_an_to_ln.
 *
 * We have our own keytab file because postgres is unlikely to run as root,
 * and so cannot read the default keytab.
 */
static int
pg_krb5_recvauth(Port *port)
{
	krb5_error_code retval;
254
	int			ret;
Bruce Momjian's avatar
Bruce Momjian committed
255 256
	krb5_auth_context auth_context = NULL;
	krb5_ticket *ticket;
257
	char	   *kusername;
Bruce Momjian's avatar
Bruce Momjian committed
258 259 260 261 262 263

	ret = pg_krb5_init();
	if (ret != STATUS_OK)
		return ret;

	retval = krb5_recvauth(pg_krb5_context, &auth_context,
264
						   (krb5_pointer) & port->sock, PG_KRB_SRVNAM,
Bruce Momjian's avatar
Bruce Momjian committed
265
						   pg_krb5_server, 0, pg_krb5_keytab, &ticket);
266 267
	if (retval)
	{
268 269 270
		ereport(LOG,
				(errmsg("kerberos recvauth returned error %d",
						retval)));
271
		com_err("postgres", retval, "from krb5_recvauth");
Bruce Momjian's avatar
Bruce Momjian committed
272
		return STATUS_ERROR;
273
	}
274 275 276 277 278

	/*
	 * The "client" structure comes out of the ticket and is therefore
	 * authenticated.  Use it to check the username obtained from the
	 * postmaster startup packet.
Bruce Momjian's avatar
Bruce Momjian committed
279 280
	 *
	 * I have no idea why this is considered necessary.
281
	 */
282
#if defined(HAVE_KRB5_TICKET_ENC_PART2)
283
	retval = krb5_unparse_name(pg_krb5_context,
Bruce Momjian's avatar
Bruce Momjian committed
284
							   ticket->enc_part2->client, &kusername);
285 286 287 288 289 290
#elif defined(HAVE_KRB5_TICKET_CLIENT)
	retval = krb5_unparse_name(pg_krb5_context,
							   ticket->client, &kusername);
#else
#error "bogus configuration"
#endif
291 292
	if (retval)
	{
293 294 295
		ereport(LOG,
				(errmsg("kerberos unparse_name returned error %d",
						retval)));
296
		com_err("postgres", retval, "while unparsing client name");
Bruce Momjian's avatar
Bruce Momjian committed
297 298
		krb5_free_ticket(pg_krb5_context, ticket);
		krb5_auth_con_free(pg_krb5_context, auth_context);
299
		return STATUS_ERROR;
300
	}
Bruce Momjian's avatar
Bruce Momjian committed
301

302
	kusername = pg_an_to_ln(kusername);
303
	if (strncmp(port->user_name, kusername, SM_DATABASE_USER))
304
	{
305 306 307
		ereport(LOG,
				(errmsg("kerberos user name \"%s\" != \"%s\"",
						port->user_name, kusername)));
Bruce Momjian's avatar
Bruce Momjian committed
308
		ret = STATUS_ERROR;
309
	}
Bruce Momjian's avatar
Bruce Momjian committed
310 311
	else
		ret = STATUS_OK;
312

Bruce Momjian's avatar
Bruce Momjian committed
313 314 315 316 317
	krb5_free_ticket(pg_krb5_context, ticket);
	krb5_auth_con_free(pg_krb5_context, auth_context);
	free(kusername);

	return ret;
318 319
}

320
#else
321

322
static int
323
pg_krb5_recvauth(Port *port)
324
{
325 326 327
	ereport(LOG,
			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
			 errmsg("kerberos v5 not implemented on this server")));
328
	return STATUS_ERROR;
329
}
330
#endif   /* KRB5 */
331

332 333

/*
334 335 336 337 338 339 340 341 342 343
 * Tell the user the authentication failed, but not (much about) why.
 *
 * There is a tradeoff here between security concerns and making life
 * unnecessarily difficult for legitimate users.  We would not, for example,
 * want to report the password we were expecting to receive...
 * But it seems useful to report the username and authorization method
 * in use, and these are items that must be presumed known to an attacker
 * anyway.
 * Note that many sorts of failure report additional information in the
 * postmaster log, which we hope is only readable by good guys.
344
 */
345
static void
346
auth_failed(Port *port, int status)
347
{
348 349
	const char *authmethod = "Unknown auth method:";

350
	/*
351 352
	 * If we failed due to EOF from client, just quit; there's no point in
	 * trying to send a message to the client, and not much point in
353 354 355
	 * logging the failure in the postmaster log.  (Logging the failure
	 * might be desirable, were it not for the fact that libpq closes the
	 * connection unceremoniously if challenged for a password when it
356 357
	 * hasn't got one to send.  We'll get a useless log entry for every
	 * psql connection under password auth, even if it's perfectly
358 359 360 361 362
	 * successful, if we log STATUS_EOF events.)
	 */
	if (status == STATUS_EOF)
		proc_exit(0);

363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379
	switch (port->auth_method)
	{
		case uaReject:
			authmethod = "Rejected host:";
			break;
		case uaKrb4:
			authmethod = "Kerberos4";
			break;
		case uaKrb5:
			authmethod = "Kerberos5";
			break;
		case uaTrust:
			authmethod = "Trusted";
			break;
		case uaIdent:
			authmethod = "IDENT";
			break;
380
		case uaMD5:
381
		case uaCrypt:
382
		case uaPassword:
383 384
			authmethod = "Password";
			break;
Bruce Momjian's avatar
Bruce Momjian committed
385 386 387 388
#ifdef USE_PAM
		case uaPAM:
			authmethod = "PAM";
			break;
389
#endif   /* USE_PAM */
390 391
	}

392 393 394 395
	ereport(FATAL,
			(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
			 errmsg("%s authentication failed for user \"%s\"",
					authmethod, port->user_name)));
396
	/* doesn't return */
397 398
}

399

400
/*
401 402
 * Client authentication starts here.  If there is an error, this
 * function does not return and the backend process is terminated.
403
 */
404
void
405
ClientAuthentication(Port *port)
406
{
407
	int			status = STATUS_ERROR;
408

409
	/*
410
	 * Get the authentication method to use for this frontend/database
Bruce Momjian's avatar
Bruce Momjian committed
411 412 413
	 * combination.  Note: a failure return indicates a problem with the
	 * hba config file, not with the request.  hba.c should have dropped
	 * an error message into the postmaster logfile if it failed.
414
	 */
415
	if (hba_getauthmethod(port) != STATUS_OK)
416 417 418 419
		ereport(FATAL,
				(errcode(ERRCODE_CONFIG_FILE_ERROR),
				 errmsg("missing or erroneous pg_hba.conf file"),
				 errhint("See postmaster log for details.")));
420

421 422 423 424
	switch (port->auth_method)
	{
		case uaReject:

425 426 427 428 429 430 431 432 433 434
			/*
			 * This could have come from an explicit "reject" entry in
			 * pg_hba.conf, but more likely it means there was no matching
			 * entry.  Take pity on the poor user and issue a helpful
			 * error message.  NOTE: this is not a security breach,
			 * because all the info reported here is known at the frontend
			 * and must be assumed known to bad guys. We're merely helping
			 * out the less clueful good guys.
			 */
			{
Bruce Momjian's avatar
Bruce Momjian committed
435 436
				char	hostinfo[NI_MAXHOST];

437 438 439 440
				getnameinfo_all(&port->raddr.addr, port->raddr.salen,
								hostinfo, sizeof(hostinfo),
								NULL, 0,
								NI_NUMERICHOST);
441

442
#ifdef USE_SSL
443 444
				ereport(FATAL,
						(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
Tom Lane's avatar
Tom Lane committed
445 446 447
						 errmsg("no pg_hba.conf entry for host \"%s\", user \"%s\", database \"%s\", %s",
								hostinfo, port->user_name, port->database_name,
								port->ssl ? gettext("SSL on") : gettext("SSL off"))));
448 449 450 451 452 453
#else
				ereport(FATAL,
						(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
						 errmsg("no pg_hba.conf entry for host \"%s\", user \"%s\", database \"%s\"",
								hostinfo, port->user_name, port->database_name)));
#endif
454 455
				break;
			}
456

457
		case uaKrb4:
458 459 460
			/* Kerberos 4 only seems to work with AF_INET. */
			if (port->raddr.addr.ss_family != AF_INET
				|| port->laddr.addr.ss_family != AF_INET)
461 462 463
				ereport(FATAL,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("kerberos 4 only supports IPv4 connections")));
464 465 466
			sendAuthRequest(port, AUTH_REQ_KRB4);
			status = pg_krb4_recvauth(port);
			break;
467

468 469 470 471
		case uaKrb5:
			sendAuthRequest(port, AUTH_REQ_KRB5);
			status = pg_krb5_recvauth(port);
			break;
472

473
		case uaIdent:
474 475 476
#if defined(HAVE_STRUCT_CMSGCRED) || defined(HAVE_STRUCT_FCRED) || \
	(defined(HAVE_STRUCT_SOCKCRED) && defined(LOCAL_CREDS)) && \
	!defined(HAVE_GETPEEREID) && !defined(SO_PEERCRED)
477

478
			/*
479 480
			 * If we are doing ident on unix-domain sockets, use SCM_CREDS
			 * only if it is defined and SO_PEERCRED isn't.
481
			 */
482
#if defined(HAVE_STRUCT_FCRED) || defined(HAVE_STRUCT_SOCKCRED)
483

484
			/*
485 486 487
			 * Receive credentials on next message receipt, BSD/OS,
			 * NetBSD. We need to set this before the client sends the
			 * next packet.
488
			 */
489
			{
490 491
				int			on = 1;

492
				if (setsockopt(port->sock, 0, LOCAL_CREDS, &on, sizeof(on)) < 0)
493 494 495
					ereport(FATAL,
							(errcode_for_socket_access(),
							 errmsg("failed to enable credential receipt: %m")));
496 497
			}
#endif
Bruce Momjian's avatar
Bruce Momjian committed
498
			if (port->raddr.addr.ss_family == AF_UNIX)
499 500
				sendAuthRequest(port, AUTH_REQ_SCM_CREDS);
#endif
501
			status = authident(port);
502
			break;
503

504 505
		case uaMD5:
			sendAuthRequest(port, AUTH_REQ_MD5);
506
			status = recv_and_check_password_packet(port);
507 508
			break;

509 510 511
		case uaCrypt:
			sendAuthRequest(port, AUTH_REQ_CRYPT);
			status = recv_and_check_password_packet(port);
512 513
			break;

514 515 516 517 518
		case uaPassword:
			sendAuthRequest(port, AUTH_REQ_PASSWORD);
			status = recv_and_check_password_packet(port);
			break;

Bruce Momjian's avatar
Bruce Momjian committed
519 520 521
#ifdef USE_PAM
		case uaPAM:
			pam_port_cludge = port;
522
			status = CheckPAMAuth(port, port->user_name, "");
523
			break;
524
#endif   /* USE_PAM */
525

526 527 528
		case uaTrust:
			status = STATUS_OK;
			break;
529
	}
530 531 532 533

	if (status == STATUS_OK)
		sendAuthRequest(port, AUTH_REQ_OK);
	else
534
		auth_failed(port, status);
535
}
536

537 538

/*
539
 * Send an authentication request packet to the frontend.
540
 */
541
static void
542
sendAuthRequest(Port *port, AuthRequest areq)
543
{
544
	StringInfoData buf;
545

546
	pq_beginmessage(&buf, 'R');
547
	pq_sendint(&buf, (int32) areq, sizeof(int32));
548 549

	/* Add the salt for encrypted passwords. */
Bruce Momjian's avatar
Bruce Momjian committed
550
	if (areq == AUTH_REQ_MD5)
551 552 553
		pq_sendbytes(&buf, port->md5Salt, 4);
	else if (areq == AUTH_REQ_CRYPT)
		pq_sendbytes(&buf, port->cryptSalt, 2);
554

555
	pq_endmessage(&buf);
556 557

	/*
Bruce Momjian's avatar
Bruce Momjian committed
558 559
	 * Flush message so client will see it, except for AUTH_REQ_OK, which
	 * need not be sent until we are ready for queries.
560 561 562
	 */
	if (areq != AUTH_REQ_OK)
		pq_flush();
563 564
}

565

Bruce Momjian's avatar
Bruce Momjian committed
566 567 568 569 570 571 572
#ifdef USE_PAM

/*
 * PAM conversation function
 */

static int
573 574
pam_passwd_conv_proc(int num_msg, const struct pam_message ** msg,
					 struct pam_response ** resp, void *appdata_ptr)
Bruce Momjian's avatar
Bruce Momjian committed
575
{
576 577 578 579
	if (num_msg != 1 || msg[0]->msg_style != PAM_PROMPT_ECHO_OFF)
	{
		switch (msg[0]->msg_style)
		{
Bruce Momjian's avatar
Bruce Momjian committed
580
			case PAM_ERROR_MSG:
581 582 583
				ereport(LOG,
						(errmsg("error from underlying PAM layer: %s",
								msg[0]->msg)));
Bruce Momjian's avatar
Bruce Momjian committed
584 585
				return PAM_CONV_ERR;
			default:
586 587 588
				ereport(LOG,
						(errmsg("unsupported PAM conversation %d/%s",
								msg[0]->msg_style, msg[0]->msg)));
Bruce Momjian's avatar
Bruce Momjian committed
589 590 591 592
				return PAM_CONV_ERR;
		}
	}

593 594 595 596 597
	if (!appdata_ptr)
	{
		/*
		 * Workaround for Solaris 2.6 where the PAM library is broken and
		 * does not pass appdata_ptr to the conversation routine
Bruce Momjian's avatar
Bruce Momjian committed
598 599 600 601
		 */
		appdata_ptr = pam_passwd;
	}

602 603 604
	/*
	 * Password wasn't passed to PAM the first time around - let's go ask
	 * the client to send a password, which we then stuff into PAM.
Bruce Momjian's avatar
Bruce Momjian committed
605
	 */
606 607
	if (strlen(appdata_ptr) == 0)
	{
608
		char	   *passwd;
Bruce Momjian's avatar
Bruce Momjian committed
609

610 611
		sendAuthRequest(pam_port_cludge, AUTH_REQ_PASSWORD);
		passwd = recv_password_packet(pam_port_cludge);
Bruce Momjian's avatar
Bruce Momjian committed
612

613 614
		if (passwd == NULL)
			return PAM_CONV_ERR;	/* client didn't want to send password */
Bruce Momjian's avatar
Bruce Momjian committed
615

616
		if (strlen(passwd) == 0)
617
		{
618 619
			ereport(LOG,
					(errmsg("empty password returned by client")));
Bruce Momjian's avatar
Bruce Momjian committed
620 621
			return PAM_CONV_ERR;
		}
622
		appdata_ptr = passwd;
Bruce Momjian's avatar
Bruce Momjian committed
623 624
	}

625 626
	/*
	 * Explicitly not using palloc here - PAM will free this memory in
Bruce Momjian's avatar
Bruce Momjian committed
627 628 629
	 * pam_end()
	 */
	*resp = calloc(num_msg, sizeof(struct pam_response));
630 631
	if (!*resp)
	{
632 633 634
		ereport(LOG,
				(errcode(ERRCODE_OUT_OF_MEMORY),
				 errmsg("out of memory")));
635
		return PAM_CONV_ERR;
Bruce Momjian's avatar
Bruce Momjian committed
636 637 638 639 640 641 642 643 644 645 646 647 648 649 650
	}

	(*resp)[0].resp = strdup((char *) appdata_ptr);
	(*resp)[0].resp_retcode = 0;

	return ((*resp)[0].resp ? PAM_SUCCESS : PAM_CONV_ERR);
}


/*
 * Check authentication against PAM.
 */
static int
CheckPAMAuth(Port *port, char *user, char *password)
{
651
	int			retval;
Bruce Momjian's avatar
Bruce Momjian committed
652 653 654
	pam_handle_t *pamh = NULL;

	/*
655 656
	 * Apparently, Solaris 2.6 is broken, and needs ugly static variable
	 * workaround
Bruce Momjian's avatar
Bruce Momjian committed
657 658 659
	 */
	pam_passwd = password;

660 661 662 663
	/*
	 * Set the application data portion of the conversation struct This is
	 * later used inside the PAM conversation to pass the password to the
	 * authentication module.
Bruce Momjian's avatar
Bruce Momjian committed
664
	 */
665 666
	pam_passw_conv.appdata_ptr = (char *) password;		/* from password above,
														 * not allocated */
Bruce Momjian's avatar
Bruce Momjian committed
667 668

	/* Optionally, one can set the service name in pg_hba.conf */
669 670 671
	if (port->auth_arg && port->auth_arg[0] != '\0')
		retval = pam_start(port->auth_arg, "pgsql@",
						   &pam_passw_conv, &pamh);
672
	else
673 674
		retval = pam_start(PGSQL_PAM_SERVICE, "pgsql@",
						   &pam_passw_conv, &pamh);
Bruce Momjian's avatar
Bruce Momjian committed
675

676 677
	if (retval != PAM_SUCCESS)
	{
678 679 680
		ereport(LOG,
				(errmsg("Failed to create PAM authenticator: %s",
						pam_strerror(pamh, retval))));
681
		pam_passwd = NULL;		/* Unset pam_passwd */
Bruce Momjian's avatar
Bruce Momjian committed
682 683 684
		return STATUS_ERROR;
	}

685 686 687
	retval = pam_set_item(pamh, PAM_USER, user);

	if (retval != PAM_SUCCESS)
688
	{
689 690 691
		ereport(LOG,
				(errmsg("pam_set_item(PAM_USER) failed: %s",
						pam_strerror(pamh, retval))));
692
		pam_passwd = NULL;		/* Unset pam_passwd */
Bruce Momjian's avatar
Bruce Momjian committed
693 694
		return STATUS_ERROR;
	}
695 696 697 698

	retval = pam_set_item(pamh, PAM_CONV, &pam_passw_conv);

	if (retval != PAM_SUCCESS)
699
	{
700 701 702
		ereport(LOG,
				(errmsg("pam_set_item(PAM_CONV) failed: %s",
						pam_strerror(pamh, retval))));
703
		pam_passwd = NULL;		/* Unset pam_passwd */
Bruce Momjian's avatar
Bruce Momjian committed
704 705
		return STATUS_ERROR;
	}
706 707 708 709

	retval = pam_authenticate(pamh, 0);

	if (retval != PAM_SUCCESS)
710
	{
711 712 713
		ereport(LOG,
				(errmsg("pam_authenticate failed: %s",
						pam_strerror(pamh, retval))));
714
		pam_passwd = NULL;		/* Unset pam_passwd */
Bruce Momjian's avatar
Bruce Momjian committed
715 716
		return STATUS_ERROR;
	}
717 718 719 720

	retval = pam_acct_mgmt(pamh, 0);

	if (retval != PAM_SUCCESS)
721
	{
722 723 724
		ereport(LOG,
				(errmsg("pam_acct_mgmt failed: %s",
						pam_strerror(pamh, retval))));
725
		pam_passwd = NULL;		/* Unset pam_passwd */
Bruce Momjian's avatar
Bruce Momjian committed
726 727 728
		return STATUS_ERROR;
	}

729
	retval = pam_end(pamh, retval);
Bruce Momjian's avatar
Bruce Momjian committed
730

731 732
	if (retval != PAM_SUCCESS)
	{
733 734 735
		ereport(LOG,
				(errmsg("failed to release PAM authenticator: %s",
						pam_strerror(pamh, retval))));
Bruce Momjian's avatar
Bruce Momjian committed
736
	}
737

Bruce Momjian's avatar
Bruce Momjian committed
738
	pam_passwd = NULL;			/* Unset pam_passwd */
739 740

	return (retval == PAM_SUCCESS ? STATUS_OK : STATUS_ERROR);
Bruce Momjian's avatar
Bruce Momjian committed
741
}
742
#endif   /* USE_PAM */
743

744

745
/*
746 747 748
 * Collect password response packet from frontend.
 *
 * Returns NULL if couldn't get password, else palloc'd string.
749
 */
750 751
static char *
recv_password_packet(Port *port)
752
{
753 754
	StringInfoData buf;

755 756 757 758 759 760 761 762 763 764 765 766 767 768 769
	if (PG_PROTOCOL_MAJOR(port->proto) >= 3)
	{
		/* Expect 'p' message type */
		int		mtype;

		mtype = pq_getbyte();
		if (mtype != 'p')
		{
			/*
			 * If the client just disconnects without offering a password,
			 * don't make a log entry.  This is legal per protocol spec and
			 * in fact commonly done by psql, so complaining just clutters
			 * the log.
			 */
			if (mtype != EOF)
770 771 772 773
				ereport(COMMERROR,
						(errcode(ERRCODE_PROTOCOL_VIOLATION),
						 errmsg("expected password response, got msg type %d",
								mtype)));
774 775 776 777 778 779 780 781 782
			return NULL;		/* EOF or bad message type */
		}
	}
	else
	{
		/* For pre-3.0 clients, avoid log entry if they just disconnect */
		if (pq_peekbyte() == EOF)
			return NULL;		/* EOF */
	}
783

784
	initStringInfo(&buf);
785
	if (pq_getmessage(&buf, 1000)) /* receive password */
786
	{
787
		/* EOF - pq_getmessage already logged a suitable message */
788
		pfree(buf.data);
789
		return NULL;
790
	}
Bruce Momjian's avatar
Bruce Momjian committed
791

792
	/*
793 794 795
	 * Apply sanity check: password packet length should agree with length
	 * of contained string.  Note it is safe to use strlen here because
	 * StringInfo is guaranteed to have an appended '\0'.
796
	 */
797
	if (strlen(buf.data) + 1 != buf.len)
798 799 800
		ereport(COMMERROR,
				(errcode(ERRCODE_PROTOCOL_VIOLATION),
				 errmsg("invalid password packet size")));
801

802
	/* Do not echo password to logs, for security. */
803 804
	ereport(DEBUG5,
			(errmsg("received password packet")));
805

806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832
	/*
	 * Return the received string.  Note we do not attempt to do any
	 * character-set conversion on it; since we don't yet know the
	 * client's encoding, there wouldn't be much point.
	 */
	return buf.data;
}


/*
 * Called when we have sent an authorization request for a password.
 * Get the response and check it.
 */
static int
recv_and_check_password_packet(Port *port)
{
	char	   *passwd;
	int			result;

	passwd = recv_password_packet(port);

	if (passwd == NULL)
		return STATUS_EOF;		/* client wouldn't send password */

	result = md5_crypt_verify(port, port->user_name, passwd);

	pfree(passwd);
Bruce Momjian's avatar
Bruce Momjian committed
833

834
	return result;
835
}