auth.c 17.2 KB
Newer Older
1 2 3
/*-------------------------------------------------------------------------
 *
 * auth.c--
4
 *	  Routines to handle network authentication
5 6 7 8 9
 *
 * Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
10
 *	  $Header: /cvsroot/pgsql/src/backend/libpq/auth.c,v 1.18 1997/11/17 16:10:06 thomas Exp $
11 12 13 14 15 16
 *
 *-------------------------------------------------------------------------
 */
/*
 * INTERFACE ROUTINES
 *
17 18 19 20
 *	   backend (postmaster) routines:
 *		be_recvauth				receive authentication information
 *		be_setauthsvc			do/do not permit an authentication service
 *		be_getauthsvc			is an authentication service permitted?
21
 *
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
 *	 NOTES
 *		To add a new authentication system:
 *		0. If you can't do your authentication over an existing socket,
 *		   you lose -- get ready to hack around this framework instead of
 *		   using it.  Otherwise, you can assume you have an initialized
 *		   and empty connection to work with.  (Please don't leave leftover
 *		   gunk in the connection after the authentication transactions, or
 *		   the POSTGRES routines that follow will be very unhappy.)
 *		1. Write a set of routines that:
 *				let a client figure out what user/principal name to use
 *				send authentication information (client side)
 *				receive authentication information (server side)
 *		   You can include both routines in this file, using #ifdef FRONTEND
 *		   to separate them.
 *		2. Edit libpq/pqcomm.h and assign a MsgType for your protocol.
 *		3. Edit the static "struct authsvc" array and the generic
 *		   {be,fe}_{get,set}auth{name,svc} routines in this file to reflect
 *		   the new service.  You may have to change the arguments of these
 *		   routines; they basically just reflect what Kerberos v4 needs.
 *		4. Hack on src/{,bin}/Makefile.global and src/{backend,libpq}/Makefile
 *		   to add library and CFLAGS hooks -- basically, grep the Makefile
 *		   hierarchy for KRBVERS to see where you need to add things.
44
 *
45 46
 *		Send mail to post_hackers@postgres.Berkeley.EDU if you have to make
 *		any changes to arguments, etc.	Context diffs would be nice, too.
47
 *
48 49 50
 *		Someday, this cruft will go away and magically be replaced by a
 *		nice interface based on the GSS API or something.  For now, though,
 *		there's no (stable) UNIX security API to work with...
51 52 53 54
 *
 */
#include <stdio.h>
#include <string.h>
55
#include <sys/param.h>			/* for MAXHOSTNAMELEN on most */
56
#ifndef  MAXHOSTNAMELEN
57
#include <netdb.h>				/* for MAXHOSTNAMELEN on some */
58
#endif
59
#include <pwd.h>
60
#include <ctype.h>				/* isspace() declaration */
61

62
#include <sys/types.h>			/* needed by in.h on Ultrix */
63 64
#include <netinet/in.h>
#include <arpa/inet.h>
M
Marc G. Fournier 已提交
65

M
Marc G. Fournier 已提交
66 67
#include <postgres.h>
#include <miscadmin.h>
M
Marc G. Fournier 已提交
68

M
Marc G. Fournier 已提交
69 70 71 72
#include <libpq/auth.h>
#include <libpq/libpq.h>
#include <libpq/libpq-be.h>
#include <libpq/hba.h>
73
#include <libpq/password.h>
74

75
static int	be_getauthsvc(MsgType msgtype);
76

77 78 79 80 81
/*----------------------------------------------------------------
 * common definitions for generic fe/be routines
 *----------------------------------------------------------------
 */

82 83
struct authsvc
{
84 85 86
	char		name[16];		/* service nickname (for command line) */
	MsgType		msgtype;		/* startup packet header type */
	int			allowed;		/* initially allowed (before command line
87
								 * option parsing)? */
88 89 90 91 92 93 94 95 96 97 98 99
};

/*
 * Command-line parsing routines use this structure to map nicknames
 * onto service types (and the startup packets to use with them).
 *
 * Programs receiving an authentication request use this structure to
 * decide which authentication service types are currently permitted.
 * By default, all authentication systems compiled into the system are
 * allowed.  Unauthenticated connections are disallowed unless there
 * isn't any authentication system.
 */
100 101

#if defined(HBA)
102
static int	useHostBasedAuth = 1;
103

104
#else
105
static int	useHostBasedAuth = 0;
106

107 108 109 110 111 112 113 114
#endif

#if defined(KRB4) || defined(KRB5) || defined(HBA)
#define UNAUTH_ALLOWED 0
#else
#define UNAUTH_ALLOWED 1
#endif

115
static struct authsvc authsvcs[] = {
116 117 118 119 120 121
	{"unauth", STARTUP_UNAUTH_MSG, UNAUTH_ALLOWED},
	{"hba", STARTUP_HBA_MSG, 1},
	{"krb4", STARTUP_KRB4_MSG, 1},
	{"krb5", STARTUP_KRB5_MSG, 1},
#if defined(KRB5)
	{"kerberos", STARTUP_KRB5_MSG, 1},
122
#else
123
	{"kerberos", STARTUP_KRB4_MSG, 1},
124
#endif
125
	{"password", STARTUP_PASSWORD_MSG, 1}
126 127
};

128
static n_authsvcs = sizeof(authsvcs) / sizeof(struct authsvc);
129 130

#ifdef KRB4
131 132 133
/* This has to be ifdef'd out because krb.h does exist.  This needs
   to be fixed.
*/
134 135 136 137 138
/*----------------------------------------------------------------
 * MIT Kerberos authentication system - protocol version 4
 *----------------------------------------------------------------
 */

M
Marc G. Fournier 已提交
139
#include <krb.h>
140 141 142

#ifdef FRONTEND
/* moves to src/libpq/fe-auth.c  */
143
#else							/* !FRONTEND */
144 145 146

/*
 * pg_krb4_recvauth -- server routine to receive authentication information
147
 *					   from the client
148 149 150 151 152 153 154 155
 *
 * 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
pg_krb4_recvauth(int sock,
156 157 158
				 struct sockaddr_in * laddr,
				 struct sockaddr_in * raddr,
				 char *username)
159
{
160 161 162 163 164 165 166
	long		krbopts = 0;	/* one-way authentication */
	KTEXT_ST	clttkt;
	char		instance[INST_SZ];
	AUTH_DAT	auth_data;
	Key_schedule key_sched;
	char		version[KRB_SENDAUTH_VLEN];
	int			status;
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210

	strcpy(instance, "*");		/* don't care, but arg gets expanded
								 * anyway */
	status = krb_recvauth(krbopts,
						  sock,
						  &clttkt,
						  PG_KRB_SRVNAM,
						  instance,
						  raddr,
						  laddr,
						  &auth_data,
						  PG_KRB_SRVTAB,
						  key_sched,
						  version);
	if (status != KSUCCESS)
	{
		sprintf(PQerrormsg,
				"pg_krb4_recvauth: kerberos error: %s\n",
				krb_err_txt[status]);
		fputs(PQerrormsg, stderr);
		pqdebug("%s", PQerrormsg);
		return (STATUS_ERROR);
	}
	if (strncmp(version, PG_KRB4_VERSION, KRB_SENDAUTH_VLEN))
	{
		sprintf(PQerrormsg,
				"pg_krb4_recvauth: protocol version != \"%s\"\n",
				PG_KRB4_VERSION);
		fputs(PQerrormsg, stderr);
		pqdebug("%s", PQerrormsg);
		return (STATUS_ERROR);
	}
	if (username && *username &&
		strncmp(username, auth_data.pname, NAMEDATALEN))
	{
		sprintf(PQerrormsg,
				"pg_krb4_recvauth: name \"%s\" != \"%s\"\n",
				username,
				auth_data.pname);
		fputs(PQerrormsg, stderr);
		pqdebug("%s", PQerrormsg);
		return (STATUS_ERROR);
	}
	return (STATUS_OK);
211 212
}

213
#endif							/* !FRONTEND */
214

215 216 217
#else
static int
pg_krb4_recvauth(int sock,
218 219 220
				 struct sockaddr_in * laddr,
				 struct sockaddr_in * raddr,
				 char *username)
221
{
222 223 224 225 226
	sprintf(PQerrormsg,
			"pg_krb4_recvauth: Kerberos not implemented on this "
			"server.\n");
	fputs(PQerrormsg, stderr);
	pqdebug("%s", PQerrormsg);
227

228
	return (STATUS_ERROR);
229
}
230 231

#endif							/* KRB4 */
232

233

234
#ifdef KRB5
235 236 237
/* This needs to be ifdef'd out because krb5.h doesn't exist.  This needs
   to be fixed.
*/
238 239 240 241 242
/*----------------------------------------------------------------
 * MIT Kerberos authentication system - protocol version 5
 *----------------------------------------------------------------
 */

M
Marc G. Fournier 已提交
243
#include <krb5/krb5.h>
244 245 246

/*
 * pg_an_to_ln -- return the local name corresponding to an authentication
247
 *				  name
248 249
 *
 * XXX Assumes that the first aname component is the user name.  This is NOT
250 251 252 253 254 255 256
 *	   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.
257
 */
258
static char *
259 260
pg_an_to_ln(char *aname)
{
261
	char	   *p;
262 263 264 265

	if ((p = strchr(aname, '/')) || (p = strchr(aname, '@')))
		*p = '\0';
	return (aname);
266 267 268 269
}

#ifdef FRONTEND
/* moves to src/libpq/fe-auth.c  */
270
#else							/* !FRONTEND */
271 272

/*
273
 * pg_krb5_recvauth -- server routine to receive authentication information
274
 *					   from the client
275 276
 *
 * We still need to compare the username obtained from the client's setup
277
 * packet to the authenticated name, as described in pg_krb4_recvauth.	This
278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295
 * is a bit more problematic in v5, as described above in pg_an_to_ln.
 *
 * In addition, as described above in pg_krb5_sendauth, we still need to
 * canonicalize the server name v4-style before constructing a principal
 * from it.  Again, this is kind of iffy.
 *
 * Finally, we need to tangle with the fact that v5 doesn't let you explicitly
 * set server keytab file names -- you have to feed lower-level routines a
 * function to retrieve the contents of a keytab, along with a single argument
 * that allows them to open the keytab.  We assume that a server keytab is
 * always a real file so we can allow people to specify their own filenames.
 * (This is important because the POSTGRES keytab needs to be readable by
 * non-root users/groups; the v4 tools used to force you do dump a whole
 * host's worth of keys into a file, effectively forcing you to use one file,
 * but kdb5_edit allows you to select which principals to dump.  Yay!)
 */
static int
pg_krb5_recvauth(int sock,
296 297 298
				 struct sockaddr_in * laddr,
				 struct sockaddr_in * raddr,
				 char *username)
299
{
300 301 302 303
	char		servbuf[MAXHOSTNAMELEN + 1 +
									sizeof(PG_KRB_SRVNAM)];
	char	   *hostp,
			   *kusername = (char *) NULL;
304
	krb5_error_code code;
305 306 307
	krb5_principal client,
				server;
	krb5_address sender_addr;
308
	krb5_rdreq_key_proc keyproc = (krb5_rdreq_key_proc) NULL;
309
	krb5_pointer keyprocarg = (krb5_pointer) NULL;
310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364

	/*
	 * Set up server side -- since we have no ticket file to make this
	 * easy, we construct our own name and parse it.  See note on
	 * canonicalization above.
	 */
	strcpy(servbuf, PG_KRB_SRVNAM);
	*(hostp = servbuf + (sizeof(PG_KRB_SRVNAM) - 1)) = '/';
	if (gethostname(++hostp, MAXHOSTNAMELEN) < 0)
		strcpy(hostp, "localhost");
	if (hostp = strchr(hostp, '.'))
		*hostp = '\0';
	if (code = krb5_parse_name(servbuf, &server))
	{
		sprintf(PQerrormsg,
			  "pg_krb5_recvauth: Kerberos error %d in krb5_parse_name\n",
				code);
		com_err("pg_krb5_recvauth", code, "in krb5_parse_name");
		return (STATUS_ERROR);
	}

	/*
	 * krb5_sendauth needs this to verify the address in the client
	 * authenticator.
	 */
	sender_addr.addrtype = raddr->sin_family;
	sender_addr.length = sizeof(raddr->sin_addr);
	sender_addr.contents = (krb5_octet *) & (raddr->sin_addr);

	if (strcmp(PG_KRB_SRVTAB, ""))
	{
		keyproc = krb5_kt_read_service_key;
		keyprocarg = PG_KRB_SRVTAB;
	}

	if (code = krb5_recvauth((krb5_pointer) & sock,
							 PG_KRB5_VERSION,
							 server,
							 &sender_addr,
							 (krb5_pointer) NULL,
							 keyproc,
							 keyprocarg,
							 (char *) NULL,
							 (krb5_int32 *) NULL,
							 &client,
							 (krb5_ticket **) NULL,
							 (krb5_authenticator **) NULL))
	{
		sprintf(PQerrormsg,
				"pg_krb5_recvauth: Kerberos error %d in krb5_recvauth\n",
				code);
		com_err("pg_krb5_recvauth", code, "in krb5_recvauth");
		krb5_free_principal(server);
		return (STATUS_ERROR);
	}
365
	krb5_free_principal(server);
366 367 368 369 370 371 372 373 374 375 376 377 378 379 380

	/*
	 * The "client" structure comes out of the ticket and is therefore
	 * authenticated.  Use it to check the username obtained from the
	 * postmaster startup packet.
	 */
	if ((code = krb5_unparse_name(client, &kusername)))
	{
		sprintf(PQerrormsg,
			"pg_krb5_recvauth: Kerberos error %d in krb5_unparse_name\n",
				code);
		com_err("pg_krb5_recvauth", code, "in krb5_unparse_name");
		krb5_free_principal(client);
		return (STATUS_ERROR);
	}
381
	krb5_free_principal(client);
382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400
	if (!kusername)
	{
		sprintf(PQerrormsg,
				"pg_krb5_recvauth: could not decode username\n");
		fputs(PQerrormsg, stderr);
		pqdebug("%s", PQerrormsg);
		return (STATUS_ERROR);
	}
	kusername = pg_an_to_ln(kusername);
	if (username && strncmp(username, kusername, NAMEDATALEN))
	{
		sprintf(PQerrormsg,
				"pg_krb5_recvauth: name \"%s\" != \"%s\"\n",
				username, kusername);
		fputs(PQerrormsg, stderr);
		pqdebug("%s", PQerrormsg);
		free(kusername);
		return (STATUS_ERROR);
	}
401
	free(kusername);
402
	return (STATUS_OK);
403 404
}

405
#endif							/* !FRONTEND */
406 407


408
#else
409
static int
410
pg_krb5_recvauth(int sock,
411 412 413
				 struct sockaddr_in * laddr,
				 struct sockaddr_in * raddr,
				 char *username)
414
{
415 416 417 418 419
	sprintf(PQerrormsg,
			"pg_krb5_recvauth: Kerberos not implemented on this "
			"server.\n");
	fputs(PQerrormsg, stderr);
	pqdebug("%s", PQerrormsg);
420

421
	return (STATUS_ERROR);
422
}
423 424

#endif							/* KRB5 */
425

426
static int
427
pg_password_recvauth(Port *port, char *database, char *DataDir)
428
{
429 430 431
	PacketBuf	buf;
	char	   *user,
			   *password;
432 433 434 435 436 437 438 439 440

	if (PacketReceive(port, &buf, BLOCKING) != STATUS_OK)
	{
		sprintf(PQerrormsg,
				"pg_password_recvauth: failed to receive authentication packet.\n");
		fputs(PQerrormsg, stderr);
		pqdebug("%s", PQerrormsg);
		return STATUS_ERROR;
	}
441

442 443
	user = buf.data;
	password = buf.data + strlen(user) + 1;
444

445
	return verify_password(user, password, port, database, DataDir);
446 447
}

448 449 450 451
/*
 * be_recvauth -- server demux routine for incoming authentication information
 */
int
452
be_recvauth(MsgType msgtype_arg, Port *port, char *username, StartupInfo *sp)
453
{
454
	MsgType		msgtype;
455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487

	/*
	 * A message type of STARTUP_MSG (which once upon a time was the only
	 * startup message type) means user wants us to choose.  "unauth" is
	 * what used to be the only choice, but installation may choose "hba"
	 * instead.
	 */
	if (msgtype_arg == STARTUP_MSG)
	{
		if (useHostBasedAuth)
			msgtype = STARTUP_HBA_MSG;
		else
			msgtype = STARTUP_UNAUTH_MSG;
	}
	else
		msgtype = msgtype_arg;


	if (!username)
	{
		sprintf(PQerrormsg,
				"be_recvauth: no user name passed\n");
		fputs(PQerrormsg, stderr);
		pqdebug("%s", PQerrormsg);
		return (STATUS_ERROR);
	}
	if (!port)
	{
		sprintf(PQerrormsg,
				"be_recvauth: no port structure passed\n");
		fputs(PQerrormsg, stderr);
		pqdebug("%s", PQerrormsg);
		return (STATUS_ERROR);
488
	}
489 490 491

	switch (msgtype)
	{
492 493 494 495 496 497 498 499 500
		case STARTUP_KRB4_MSG:
			if (!be_getauthsvc(msgtype))
			{
				sprintf(PQerrormsg,
						"be_recvauth: krb4 authentication disallowed\n");
				fputs(PQerrormsg, stderr);
				pqdebug("%s", PQerrormsg);
				return (STATUS_ERROR);
			}
501 502
			if (pg_krb4_recvauth(port->sock, (struct sockaddr_in *) &port->laddr,
								 (struct sockaddr_in *) &port->raddr,
503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520
								 username) != STATUS_OK)
			{
				sprintf(PQerrormsg,
						"be_recvauth: krb4 authentication failed\n");
				fputs(PQerrormsg, stderr);
				pqdebug("%s", PQerrormsg);
				return (STATUS_ERROR);
			}
			break;
		case STARTUP_KRB5_MSG:
			if (!be_getauthsvc(msgtype))
			{
				sprintf(PQerrormsg,
						"be_recvauth: krb5 authentication disallowed\n");
				fputs(PQerrormsg, stderr);
				pqdebug("%s", PQerrormsg);
				return (STATUS_ERROR);
			}
521 522
			if (pg_krb5_recvauth(port->sock, (struct sockaddr_in *) &port->laddr,
								 (struct sockaddr_in *) &port->raddr,
523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574
								 username) != STATUS_OK)
			{
				sprintf(PQerrormsg,
						"be_recvauth: krb5 authentication failed\n");
				fputs(PQerrormsg, stderr);
				pqdebug("%s", PQerrormsg);
				return (STATUS_ERROR);
			}
			break;
		case STARTUP_UNAUTH_MSG:
			if (!be_getauthsvc(msgtype))
			{
				sprintf(PQerrormsg,
						"be_recvauth: "
						"unauthenticated connections disallowed\n");
				fputs(PQerrormsg, stderr);
				pqdebug("%s", PQerrormsg);
				return (STATUS_ERROR);
			}
			break;
		case STARTUP_HBA_MSG:
			if (hba_recvauth(port, sp->database, sp->user, DataDir) != STATUS_OK)
			{
				sprintf(PQerrormsg,
					  "be_recvauth: host-based authentication failed\n");
				fputs(PQerrormsg, stderr);
				pqdebug("%s", PQerrormsg);
				return (STATUS_ERROR);
			}
			break;
		case STARTUP_PASSWORD_MSG:
			if (!be_getauthsvc(msgtype))
			{
				sprintf(PQerrormsg,
						"be_recvauth: "
						"plaintext password authentication disallowed\n");
				fputs(PQerrormsg, stderr);
				pqdebug("%s", PQerrormsg);
				return (STATUS_ERROR);
			}
			if (pg_password_recvauth(port, sp->database, DataDir) != STATUS_OK)
			{

				/*
				 * pg_password_recvauth or lower-level routines have
				 * already set
				 */
				/* the error message											 */
				return (STATUS_ERROR);
			}
			break;
		default:
575
			sprintf(PQerrormsg,
576 577
					"be_recvauth: unrecognized message type: %d\n",
					msgtype);
578 579 580
			fputs(PQerrormsg, stderr);
			pqdebug("%s", PQerrormsg);
			return (STATUS_ERROR);
581
	}
582
	return (STATUS_OK);
583 584 585 586
}

/*
 * be_setauthsvc -- enable/disable the authentication services currently
587
 *					selected for use by the backend
588
 * be_getauthsvc -- returns whether a particular authentication system
589 590
 *					(indicated by its message type) is permitted by the
 *					current selections
591 592
 *
 * be_setauthsvc encodes the command-line syntax that
593
 *		-a "<service-name>"
594
 * enables a service, whereas
595
 *		-a "no<service-name>"
596 597 598 599 600
 * disables it.
 */
void
be_setauthsvc(char *name)
{
601 602 603
	int			i,
				j;
	int			turnon = 1;
604 605 606 607 608 609 610

	if (!name)
		return;
	if (!strncmp("no", name, 2))
	{
		turnon = 0;
		name += 2;
611
	}
612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630
	if (name[0] == '\0')
		return;
	for (i = 0; i < n_authsvcs; ++i)
		if (!strcmp(name, authsvcs[i].name))
		{
			for (j = 0; j < n_authsvcs; ++j)
				if (authsvcs[j].msgtype == authsvcs[i].msgtype)
					authsvcs[j].allowed = turnon;
			break;
		}
	if (i == n_authsvcs)
	{
		sprintf(PQerrormsg,
				"be_setauthsvc: invalid name %s, ignoring...\n",
				name);
		fputs(PQerrormsg, stderr);
		pqdebug("%s", PQerrormsg);
	}
	return;
631 632
}

633
static int
634 635
be_getauthsvc(MsgType msgtype)
{
636
	int			i;
637 638 639 640 641

	for (i = 0; i < n_authsvcs; ++i)
		if (msgtype == authsvcs[i].msgtype)
			return (authsvcs[i].allowed);
	return (0);
642
}