auth.c 18.0 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.19 1997/12/04 00:26:50 scrappy 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
#include <libpq/crypt.h>
75

76
static int	be_getauthsvc(MsgType msgtype);
77

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

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

/*
 * 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.
 */
101 102

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

105
#else
106
static int	useHostBasedAuth = 0;
107

108 109 110 111 112 113 114 115
#endif

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

116
static struct authsvc authsvcs[] = {
117 118 119 120 121 122
	{"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},
123
#else
124
	{"kerberos", STARTUP_KRB4_MSG, 1},
125
#endif
126 127
	{"password", STARTUP_PASSWORD_MSG, 1},
	{"crypt", STARTUP_CRYPT_MSG, 1}
128 129
};

130
static n_authsvcs = sizeof(authsvcs) / sizeof(struct authsvc);
131 132

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

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

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

/*
 * pg_krb4_recvauth -- server routine to receive authentication information
149
 *					   from the client
150 151 152 153 154 155 156 157
 *
 * 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,
158 159 160
				 struct sockaddr_in * laddr,
				 struct sockaddr_in * raddr,
				 char *username)
161
{
162 163 164 165 166 167 168
	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;
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 211 212

	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);
213 214
}

215
#endif							/* !FRONTEND */
216

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

230
	return (STATUS_ERROR);
231
}
232 233

#endif							/* KRB4 */
234

235

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

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

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

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

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

/*
275
 * pg_krb5_recvauth -- server routine to receive authentication information
276
 *					   from the client
277 278
 *
 * We still need to compare the username obtained from the client's setup
279
 * packet to the authenticated name, as described in pg_krb4_recvauth.	This
280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297
 * 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,
298 299 300
				 struct sockaddr_in * laddr,
				 struct sockaddr_in * raddr,
				 char *username)
301
{
302 303 304 305
	char		servbuf[MAXHOSTNAMELEN + 1 +
									sizeof(PG_KRB_SRVNAM)];
	char	   *hostp,
			   *kusername = (char *) NULL;
306
	krb5_error_code code;
307 308 309
	krb5_principal client,
				server;
	krb5_address sender_addr;
310
	krb5_rdreq_key_proc keyproc = (krb5_rdreq_key_proc) NULL;
311
	krb5_pointer keyprocarg = (krb5_pointer) NULL;
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 365 366

	/*
	 * 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);
	}
367
	krb5_free_principal(server);
368 369 370 371 372 373 374 375 376 377 378 379 380 381 382

	/*
	 * 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);
	}
383
	krb5_free_principal(client);
384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402
	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);
	}
403
	free(kusername);
404
	return (STATUS_OK);
405 406
}

407
#endif							/* !FRONTEND */
408 409


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

423
	return (STATUS_ERROR);
424
}
425 426

#endif							/* KRB5 */
427

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

	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;
	}
443

444 445
	user = buf.data;
	password = buf.data + strlen(user) + 1;
446

447
	return verify_password(user, password, port, database, DataDir);
448 449
}

450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471
static int
crypt_recvauth(Port *port)
{
      PacketBuf       buf;
      char       *user,
                         *password;

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

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

      return crypt_verify(port, user, password);
}

472 473 474 475
/*
 * be_recvauth -- server demux routine for incoming authentication information
 */
int
476
be_recvauth(MsgType msgtype_arg, Port *port, char *username, StartupInfo *sp)
477
{
478
	MsgType		msgtype;
479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511

	/*
	 * 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);
512
	}
513 514 515

	switch (msgtype)
	{
516 517 518 519 520 521 522 523 524
		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);
			}
525 526
			if (pg_krb4_recvauth(port->sock, (struct sockaddr_in *) &port->laddr,
								 (struct sockaddr_in *) &port->raddr,
527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544
								 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);
			}
545 546
			if (pg_krb5_recvauth(port->sock, (struct sockaddr_in *) &port->laddr,
								 (struct sockaddr_in *) &port->raddr,
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 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597
								 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;
598 599 600 601
		case STARTUP_CRYPT_MSG:
			if (crypt_recvauth(port) != STATUS_OK)
                          return STATUS_ERROR;
                        break;
602
		default:
603
			sprintf(PQerrormsg,
604 605
					"be_recvauth: unrecognized message type: %d\n",
					msgtype);
606 607 608
			fputs(PQerrormsg, stderr);
			pqdebug("%s", PQerrormsg);
			return (STATUS_ERROR);
609
	}
610
	return (STATUS_OK);
611 612 613 614
}

/*
 * be_setauthsvc -- enable/disable the authentication services currently
615
 *					selected for use by the backend
616
 * be_getauthsvc -- returns whether a particular authentication system
617 618
 *					(indicated by its message type) is permitted by the
 *					current selections
619 620
 *
 * be_setauthsvc encodes the command-line syntax that
621
 *		-a "<service-name>"
622
 * enables a service, whereas
623
 *		-a "no<service-name>"
624 625 626 627 628
 * disables it.
 */
void
be_setauthsvc(char *name)
{
629 630 631
	int			i,
				j;
	int			turnon = 1;
632 633 634 635 636 637 638

	if (!name)
		return;
	if (!strncmp("no", name, 2))
	{
		turnon = 0;
		name += 2;
639
	}
640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658
	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;
659 660
}

661
static int
662 663
be_getauthsvc(MsgType msgtype)
{
664
	int			i;
665 666 667 668 669

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