auth.c 15.7 KB
Newer Older
1 2
/*-------------------------------------------------------------------------
 *
3
 * auth.c
4
 *	  Routines to handle network authentication
5
 *
6
 * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
B
Add:  
Bruce Momjian 已提交
7
 * Portions Copyright (c) 1994, Regents of the University of California
8 9 10
 *
 *
 * IDENTIFICATION
B
Bruce Momjian 已提交
11
 *	  $Header: /cvsroot/pgsql/src/backend/libpq/auth.c,v 1.54 2001/07/21 00:29:56 momjian Exp $
12 13 14
 *
 *-------------------------------------------------------------------------
 */
15 16

#include "postgres.h"
17

18
#include <sys/types.h>			/* needed by in.h on Ultrix */
19 20
#include <netinet/in.h>
#include <arpa/inet.h>
M
Marc G. Fournier 已提交
21

22
#include "libpq/auth.h"
B
Bruce Momjian 已提交
23
#include "libpq/crypt.h"
24
#include "libpq/hba.h"
B
Bruce Momjian 已提交
25
#include "libpq/libpq.h"
26
#include "libpq/password.h"
27
#include "libpq/pqformat.h"
B
Bruce Momjian 已提交
28
#include "miscadmin.h"
29

30 31
static void sendAuthRequest(Port *port, AuthRequest areq);

32 33 34
static int	checkPassword(Port *port, char *user, char *password);
static int	old_be_recvauth(Port *port);
static int	map_old_to_new(Port *port, UserAuth old, int status);
35
static void auth_failed(Port *port);
36

37 38
static int	recv_and_check_password_packet(Port *port);
static int	recv_and_check_passwordv0(Port *port);
39

B
Bruce Momjian 已提交
40
char	   *pg_krb_server_keyfile;
41 42


43 44 45 46 47 48
#ifdef KRB4
/*----------------------------------------------------------------
 * MIT Kerberos authentication system - protocol version 4
 *----------------------------------------------------------------
 */

49
#include "krb.h"
50 51 52

/*
 * pg_krb4_recvauth -- server routine to receive authentication information
53
 *					   from the client
54 55 56 57 58 59 60
 *
 * 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
B
Bruce Momjian 已提交
61
pg_krb4_recvauth(Port *port)
62
{
B
Bruce Momjian 已提交
63 64 65 66 67 68 69
	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;
70 71 72 73

	strcpy(instance, "*");		/* don't care, but arg gets expanded
								 * anyway */
	status = krb_recvauth(krbopts,
74
						  port->sock,
75 76 77
						  &clttkt,
						  PG_KRB_SRVNAM,
						  instance,
78 79
						  &port->raddr.in,
						  &port->laddr.in,
80
						  &auth_data,
81
						  pg_krb_server_keyfile,
82 83 84 85
						  key_sched,
						  version);
	if (status != KSUCCESS)
	{
86 87 88
		snprintf(PQerrormsg, PQERRORMSG_LENGTH,
				 "pg_krb4_recvauth: kerberos error: %s\n",
				 krb_err_txt[status]);
89 90
		fputs(PQerrormsg, stderr);
		pqdebug("%s", PQerrormsg);
91
		return STATUS_ERROR;
92 93 94
	}
	if (strncmp(version, PG_KRB4_VERSION, KRB_SENDAUTH_VLEN))
	{
95 96 97
		snprintf(PQerrormsg, PQERRORMSG_LENGTH,
				 "pg_krb4_recvauth: protocol version != \"%s\"\n",
				 PG_KRB4_VERSION);
98 99
		fputs(PQerrormsg, stderr);
		pqdebug("%s", PQerrormsg);
100
		return STATUS_ERROR;
101
	}
102
	if (strncmp(port->user, auth_data.pname, SM_USER))
103
	{
104
		snprintf(PQerrormsg, PQERRORMSG_LENGTH,
B
Bruce Momjian 已提交
105 106
				 "pg_krb4_recvauth: name \"%s\" != \"%s\"\n",
				 port->user, auth_data.pname);
107 108
		fputs(PQerrormsg, stderr);
		pqdebug("%s", PQerrormsg);
109
		return STATUS_ERROR;
110
	}
111
	return STATUS_OK;
112 113
}

114 115
#else
static int
116
pg_krb4_recvauth(Port *port)
117
{
118
	snprintf(PQerrormsg, PQERRORMSG_LENGTH,
119
		 "pg_krb4_recvauth: Kerberos not implemented on this server.\n");
120 121
	fputs(PQerrormsg, stderr);
	pqdebug("%s", PQerrormsg);
122

123
	return STATUS_ERROR;
124
}
125
#endif	 /* KRB4 */
126

127

128 129 130 131 132 133
#ifdef KRB5
/*----------------------------------------------------------------
 * MIT Kerberos authentication system - protocol version 5
 *----------------------------------------------------------------
 */

B
Bruce Momjian 已提交
134 135
#include <krb5.h>
#include <com_err.h>
136 137 138

/*
 * pg_an_to_ln -- return the local name corresponding to an authentication
139
 *				  name
140 141
 *
 * XXX Assumes that the first aname component is the user name.  This is NOT
142 143 144 145 146 147 148
 *	   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.
149
 */
150
static char *
151 152
pg_an_to_ln(char *aname)
{
153
	char	   *p;
154 155 156

	if ((p = strchr(aname, '/')) || (p = strchr(aname, '@')))
		*p = '\0';
157
	return aname;
158 159
}

B
Bruce Momjian 已提交
160

B
Bruce Momjian 已提交
161
/*
B
Bruce Momjian 已提交
162 163
 * Various krb5 state which is not connection specfic, and a flag to
 * indicate whether we have initialised it yet.
B
Bruce Momjian 已提交
164
 */
B
Bruce Momjian 已提交
165
static int	pg_krb5_initialised;
B
Bruce Momjian 已提交
166 167 168 169 170
static krb5_context pg_krb5_context;
static krb5_keytab pg_krb5_keytab;
static krb5_principal pg_krb5_server;


B
Bruce Momjian 已提交
171
static int
B
Bruce Momjian 已提交
172
pg_krb5_init(void)
B
Bruce Momjian 已提交
173
{
B
Bruce Momjian 已提交
174
	krb5_error_code retval;
175

B
Bruce Momjian 已提交
176 177 178 179
	if (pg_krb5_initialised)
		return STATUS_OK;

	retval = krb5_init_context(&pg_krb5_context);
B
Bruce Momjian 已提交
180 181
	if (retval)
	{
B
Bruce Momjian 已提交
182
		snprintf(PQerrormsg, PQERRORMSG_LENGTH,
B
Bruce Momjian 已提交
183 184 185
				 "pg_krb5_init: krb5_init_context returned"
				 " Kerberos error %d\n", retval);
		com_err("postgres", retval, "while initializing krb5");
B
Bruce Momjian 已提交
186
		return STATUS_ERROR;
187 188
	}

189
	retval = krb5_kt_resolve(pg_krb5_context, pg_krb_server_keyfile, &pg_krb5_keytab);
B
Bruce Momjian 已提交
190 191
	if (retval)
	{
B
Bruce Momjian 已提交
192 193 194
		snprintf(PQerrormsg, PQERRORMSG_LENGTH,
				 "pg_krb5_init: krb5_kt_resolve returned"
				 " Kerberos error %d\n", retval);
B
Bruce Momjian 已提交
195
		com_err("postgres", retval, "while resolving keytab file %s",
196
				pg_krb_server_keyfile);
B
Bruce Momjian 已提交
197 198
		krb5_free_context(pg_krb5_context);
		return STATUS_ERROR;
199 200
	}

B
Bruce Momjian 已提交
201
	retval = krb5_sname_to_principal(pg_krb5_context, NULL, PG_KRB_SRVNAM,
B
Bruce Momjian 已提交
202
									 KRB5_NT_SRV_HST, &pg_krb5_server);
B
Bruce Momjian 已提交
203 204
	if (retval)
	{
205
		snprintf(PQerrormsg, PQERRORMSG_LENGTH,
B
Bruce Momjian 已提交
206 207
				 "pg_krb5_init: krb5_sname_to_principal returned"
				 " Kerberos error %d\n", retval);
B
Bruce Momjian 已提交
208
		com_err("postgres", retval,
B
Bruce Momjian 已提交
209
				"while getting server principal for service %s",
210
				pg_krb_server_keyfile);
B
Bruce Momjian 已提交
211 212
		krb5_kt_close(pg_krb5_context, pg_krb5_keytab);
		krb5_free_context(pg_krb5_context);
213 214
		return STATUS_ERROR;
	}
B
Bruce Momjian 已提交
215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235

	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;
B
Bruce Momjian 已提交
236
	int			ret;
B
Bruce Momjian 已提交
237 238
	krb5_auth_context auth_context = NULL;
	krb5_ticket *ticket;
B
Bruce Momjian 已提交
239
	char	   *kusername;
B
Bruce Momjian 已提交
240 241 242 243 244 245

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

	retval = krb5_recvauth(pg_krb5_context, &auth_context,
B
Bruce Momjian 已提交
246
						   (krb5_pointer) & port->sock, PG_KRB_SRVNAM,
B
Bruce Momjian 已提交
247
						   pg_krb5_server, 0, pg_krb5_keytab, &ticket);
B
Bruce Momjian 已提交
248 249
	if (retval)
	{
B
Bruce Momjian 已提交
250 251 252
		snprintf(PQerrormsg, PQERRORMSG_LENGTH,
				 "pg_krb5_recvauth: krb5_recvauth returned"
				 " Kerberos error %d\n", retval);
B
Bruce Momjian 已提交
253
		com_err("postgres", retval, "from krb5_recvauth");
B
Bruce Momjian 已提交
254
		return STATUS_ERROR;
B
Bruce Momjian 已提交
255
	}
256 257 258 259 260

	/*
	 * The "client" structure comes out of the ticket and is therefore
	 * authenticated.  Use it to check the username obtained from the
	 * postmaster startup packet.
B
Bruce Momjian 已提交
261 262
	 *
	 * I have no idea why this is considered necessary.
263
	 */
B
Bruce Momjian 已提交
264
	retval = krb5_unparse_name(pg_krb5_context,
B
Bruce Momjian 已提交
265
							   ticket->enc_part2->client, &kusername);
B
Bruce Momjian 已提交
266 267
	if (retval)
	{
268
		snprintf(PQerrormsg, PQERRORMSG_LENGTH,
B
Bruce Momjian 已提交
269 270
				 "pg_krb5_recvauth: krb5_unparse_name returned"
				 " Kerberos error %d\n", retval);
B
Bruce Momjian 已提交
271
		com_err("postgres", retval, "while unparsing client name");
B
Bruce Momjian 已提交
272 273
		krb5_free_ticket(pg_krb5_context, ticket);
		krb5_auth_con_free(pg_krb5_context, auth_context);
274
		return STATUS_ERROR;
275
	}
B
Bruce Momjian 已提交
276

277
	kusername = pg_an_to_ln(kusername);
B
Bruce Momjian 已提交
278
	if (strncmp(port->user, kusername, SM_USER))
279
	{
280
		snprintf(PQerrormsg, PQERRORMSG_LENGTH,
B
Bruce Momjian 已提交
281
			  "pg_krb5_recvauth: user name \"%s\" != krb5 name \"%s\"\n",
B
Bruce Momjian 已提交
282 283
				 port->user, kusername);
		ret = STATUS_ERROR;
284
	}
B
Bruce Momjian 已提交
285 286
	else
		ret = STATUS_OK;
B
Bruce Momjian 已提交
287

B
Bruce Momjian 已提交
288 289 290 291 292
	krb5_free_ticket(pg_krb5_context, ticket);
	krb5_auth_con_free(pg_krb5_context, auth_context);
	free(kusername);

	return ret;
293 294
}

295
#else
296
static int
297
pg_krb5_recvauth(Port *port)
298
{
299
	snprintf(PQerrormsg, PQERRORMSG_LENGTH,
B
Bruce Momjian 已提交
300
		 "pg_krb5_recvauth: Kerberos not implemented on this server.\n");
301 302
	fputs(PQerrormsg, stderr);
	pqdebug("%s", PQerrormsg);
303

304
	return STATUS_ERROR;
305
}
306
#endif	 /* KRB5 */
307

308 309 310 311

/*
 * Handle a v0 password packet.
 */
M
 
Marc G. Fournier 已提交
312
static int
313
recv_and_check_passwordv0(Port *port)
314
{
315 316
	int32		len;
	char	   *buf;
317
	PasswordPacketV0 *pp;
318 319 320 321
	char	   *user,
			   *password,
			   *cp,
			   *start;
322

323 324 325 326 327 328
	pq_getint(&len, 4);
	len -= 4;
	buf = palloc(len);
	pq_getbytes(buf, len);

	pp = (PasswordPacketV0 *) buf;
329 330 331 332 333 334 335

	/*
	 * The packet is supposed to comprise the user name and the password
	 * as C strings.  Be careful the check that this is the case.
	 */
	user = password = NULL;

336
	len -= sizeof(pp->unused);
337 338 339

	cp = start = pp->data;

340 341
	while (len-- > 0)
		if (*cp++ == '\0')
342
		{
343 344 345 346 347 348 349
			if (user == NULL)
				user = start;
			else
			{
				password = start;
				break;
			}
350

351 352
			start = cp;
		}
353 354

	if (user == NULL || password == NULL)
355
	{
356
		snprintf(PQerrormsg, PQERRORMSG_LENGTH,
B
Bruce Momjian 已提交
357
				 "pg_password_recvauth: badly formed password packet.\n");
358 359
		fputs(PQerrormsg, stderr);
		pqdebug("%s", PQerrormsg);
360

361
		pfree(buf);
362
		auth_failed(port);
363
	}
364 365
	else
	{
366 367
		int			status;
		UserAuth	saved;
368 369 370 371 372 373 374 375

		/* Check the password. */

		saved = port->auth_method;
		port->auth_method = uaPassword;

		status = checkPassword(port, user, password);

376
		pfree(buf);
377 378 379 380 381 382
		port->auth_method = saved;

		/* Adjust the result if necessary. */
		if (map_old_to_new(port, uaPassword, status) != STATUS_OK)
			auth_failed(port);
	}
M
 
Marc G. Fournier 已提交
383

384
	return STATUS_OK;
385
}
386 387


388
/*
389 390 391 392 393 394 395 396 397 398
 * 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.
399
 */
400
static void
401
auth_failed(Port *port)
402
{
403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429
	const char *authmethod = "Unknown auth method:";

	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;
		case uaPassword:
			authmethod = "Password";
			break;
		case uaCrypt:
			authmethod = "Password";
			break;
	}

430 431
	elog(FATAL, "%s authentication failed for user \"%s\"",
		 authmethod, port->user);
432 433
}

434

435
/*
436 437
 * Client authentication starts here.  If there is an error, this
 * function does not return and the backend process is terminated.
438
 */
439
void
440
ClientAuthentication(Port *port)
441
{
442
	int status = STATUS_ERROR;
443

444
	/*
445
	 * Get the authentication method to use for this frontend/database
B
Bruce Momjian 已提交
446 447 448
	 * 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.
449
	 */
450
	if (hba_getauthmethod(port) != STATUS_OK)
451
		elog(FATAL, "Missing or erroneous pg_hba.conf file, see postmaster log for details");
452

453
	/* Handle old style authentication. */
454
	else if (PG_PROTOCOL_MAJOR(port->proto) == 0)
455
	{
456 457
		if (old_be_recvauth(port) != STATUS_OK)
			auth_failed(port);
458
		return;
459
	}
460

461 462 463 464
	/* Handle new style authentication. */
	switch (port->auth_method)
	{
		case uaReject:
B
Bruce Momjian 已提交
465 466 467 468 469 470 471 472 473
		/*
		 * 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.
		 */
474 475 476 477 478
		{
			const char *hostinfo = "localhost";

			if (port->raddr.sa.sa_family == AF_INET)
				hostinfo = inet_ntoa(port->raddr.in.sin_addr);
B
Bruce Momjian 已提交
479
			elog(FATAL,
480 481 482 483 484
				 "No pg_hba.conf entry for host %s, user %s, database %s",
				 hostinfo, port->user, port->database);
			return;
		}
		break;
485

486 487 488 489
		case uaKrb4:
			sendAuthRequest(port, AUTH_REQ_KRB4);
			status = pg_krb4_recvauth(port);
			break;
490

491 492 493 494
		case uaKrb5:
			sendAuthRequest(port, AUTH_REQ_KRB5);
			status = pg_krb5_recvauth(port);
			break;
495

496 497 498 499
		case uaIdent:
			status = authident(&port->raddr.in, &port->laddr.in,
							   port->user, port->auth_arg);
			break;
500

501 502 503 504
		case uaPassword:
			sendAuthRequest(port, AUTH_REQ_PASSWORD);
			status = recv_and_check_password_packet(port);
			break;
505

506 507 508 509
		case uaCrypt:
			sendAuthRequest(port, AUTH_REQ_CRYPT);
			status = recv_and_check_password_packet(port);
			break;
510

511 512 513
		case uaTrust:
			status = STATUS_OK;
			break;
514
	}
515 516 517 518 519

	if (status == STATUS_OK)
		sendAuthRequest(port, AUTH_REQ_OK);
	else
		auth_failed(port);
520
}
521

522 523

/*
524
 * Send an authentication request packet to the frontend.
525
 */
526
static void
527
sendAuthRequest(Port *port, AuthRequest areq)
528
{
529
	StringInfoData buf;
530

531 532 533
	pq_beginmessage(&buf);
	pq_sendbyte(&buf, 'R');
	pq_sendint(&buf, (int32) areq, sizeof(int32));
534 535 536

	/* Add the salt for encrypted passwords. */
	if (areq == AUTH_REQ_CRYPT)
537
	{
538 539
		pq_sendint(&buf, port->salt[0], 1);
		pq_sendint(&buf, port->salt[1], 1);
540
	}
541

542 543
	pq_endmessage(&buf);
	pq_flush();
544 545 546 547 548 549 550
}



/*
 * Called when we have received the password packet.
 */
M
 
Marc G. Fournier 已提交
551
static int
552
recv_and_check_password_packet(Port *port)
553
{
554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569
	StringInfoData buf;
	int32		len;
	int			result;

	if (pq_getint(&len, 4) == EOF)
		return STATUS_ERROR;	/* client didn't want to send password */
	initStringInfo(&buf);
	pq_getstr(&buf);

	if (DebugLvl)
		fprintf(stderr, "received password packet with len=%d, pw=%s\n",
				len, buf.data);

	result = checkPassword(port, port->user, buf.data);
	pfree(buf.data);
	return result;
570 571
}

572

573
/*
574 575
 * Handle `password' and `crypt' records. If an auth argument was
 * specified, use the respective file. Else use pg_shadow passwords.
576
 */
577 578
static int
checkPassword(Port *port, char *user, char *password)
579
{
580 581
	if (port->auth_arg[0] != '\0')
		return verify_password(port, user, password);
582 583 584 585 586

	return crypt_verify(port, user, password);
}


587 588 589 590
/*
 * Server demux routine for incoming authentication information for protocol
 * version 0.
 */
591 592
static int
old_be_recvauth(Port *port)
593
{
594 595
	int			status;
	MsgType		msgtype = (MsgType) port->proto;
596 597 598 599

	/* Handle the authentication that's offered. */

	switch (msgtype)
600 601 602 603
	{
		case STARTUP_KRB4_MSG:
			status = map_old_to_new(port, uaKrb4, pg_krb4_recvauth(port));
			break;
604

605 606 607
		case STARTUP_KRB5_MSG:
			status = map_old_to_new(port, uaKrb5, pg_krb5_recvauth(port));
			break;
608

609 610 611
		case STARTUP_MSG:
			status = map_old_to_new(port, uaTrust, STATUS_OK);
			break;
612

613
		case STARTUP_PASSWORD_MSG:
614 615
			status = recv_and_check_passwordv0(port);
			break;
616

617 618
		default:
			fprintf(stderr, "Invalid startup message type: %u\n", msgtype);
619

620 621
			return STATUS_OK;
	}
622 623 624

	return status;
}
625

626 627

/*
628
 * The old style authentication has been done.	Modify the result of this (eg.
629 630 631
 * allow the connection anyway, disallow it anyway, or use the result)
 * depending on what authentication we really want to use.
 */
632 633
static int
map_old_to_new(Port *port, UserAuth old, int status)
634 635 636
{
	switch (port->auth_method)
	{
637 638
		case uaCrypt:
		case uaReject:
639
			status = STATUS_ERROR;
640
			break;
641

642 643 644 645
		case uaKrb4:
			if (old != uaKrb4)
				status = STATUS_ERROR;
			break;
646

647 648 649 650
		case uaKrb5:
			if (old != uaKrb5)
				status = STATUS_ERROR;
			break;
651

652 653 654
		case uaTrust:
			status = STATUS_OK;
			break;
655

656 657 658 659 660 661 662 663
		case uaIdent:
			status = authident(&port->raddr.in, &port->laddr.in,
							   port->user, port->auth_arg);
			break;

		case uaPassword:
			if (old != uaPassword)
				status = STATUS_ERROR;
664

665
			break;
666
	}
667

668
	return status;
669
}