hba.c 45.5 KB
Newer Older
1 2
/*-------------------------------------------------------------------------
 *
3
 * hba.c
4 5
 *	  Routines to handle host based authentication (that's the scheme
 *	  wherein you authenticate a user by seeing what IP address the system
6
 *	  says he comes from and choosing authentication method based on it).
7
 *
8
 * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
9 10 11 12
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
13
 *	  $PostgreSQL: pgsql/src/backend/libpq/hba.c,v 1.197 2010/02/02 19:09:37 mha Exp $
14 15 16
 *
 *-------------------------------------------------------------------------
 */
17 18
#include "postgres.h"

19
#include <ctype.h>
20
#include <pwd.h>
B
Bruce Momjian 已提交
21
#include <fcntl.h>
22
#include <sys/param.h>
23
#include <sys/socket.h>
24 25
#include <netinet/in.h>
#include <arpa/inet.h>
26
#include <unistd.h>
27

28
#include "libpq/ip.h"
29
#include "libpq/libpq.h"
30
#include "regex/regex.h"
31
#include "replication/walsender.h"
32
#include "storage/fd.h"
33
#include "utils/acl.h"
34
#include "utils/guc.h"
35
#include "utils/lsyscache.h"
36

37

38
#define atooid(x)  ((Oid) strtoul((x), NULL, 10))
39
#define atoxid(x)  ((TransactionId) strtoul((x), NULL, 10))
40

B
Bruce Momjian 已提交
41
/* This is used to separate values in multi-valued column strings */
B
Bruce Momjian 已提交
42
#define MULTI_VALUE_SEP "\001"
B
Bruce Momjian 已提交
43

44 45
#define MAX_TOKEN	256

46 47 48 49 50 51 52 53
/* callback data for check_network_callback */
typedef struct check_network_data
{
	IPCompareMethod method;		/* test method */
	SockAddr   *raddr;			/* client's actual address */
	bool		result;			/* set to true if match */
} check_network_data;

54
/* pre-parsed content of HBA config file: list of HbaLine structs */
55 56
static List *parsed_hba_lines = NIL;

57
/*
58 59 60 61 62 63 64
 * These variables hold the pre-parsed contents of the ident usermap
 * configuration file.  ident_lines is a list of sublists, one sublist for
 * each (non-empty, non-comment) line of the file.  The sublist items are
 * palloc'd strings, one string per token on the line.  Note there will always
 * be at least one token, since blank lines are not entered in the data
 * structure.  ident_line_nums is an integer list containing the actual line
 * number for each line represented in ident_lines.
65
 */
B
Bruce Momjian 已提交
66 67 68
static List *ident_lines = NIL;
static List *ident_line_nums = NIL;

69

70
static void tokenize_file(const char *filename, FILE *file,
B
Bruce Momjian 已提交
71
			  List **lines, List **line_nums);
72
static char *tokenize_inc_file(const char *outer_filename,
B
Bruce Momjian 已提交
73
				  const char *inc_filename);
74

75
/*
76 77
 * isblank() exists in the ISO C99 spec, but it's not very portable yet,
 * so provide our own version.
78
 */
79
bool
80
pg_isblank(const char c)
81
{
82
	return c == ' ' || c == '\t' || c == '\r';
83 84
}

85

86
/*
87
 * Grab one token out of fp. Tokens are strings of non-blank
88 89
 * characters bounded by blank characters, commas, beginning of line, and
 * end of line. Blank means space or tab. Tokens can be delimited by
90
 * double quotes (this allows the inclusion of blanks, but not newlines).
91 92 93 94 95 96 97 98 99 100 101
 *
 * The token, if any, is returned at *buf (a buffer of size bufsz).
 *
 * If successful: store null-terminated token at *buf and return TRUE.
 * If no more tokens on line: set *buf = '\0' and return FALSE.
 *
 * Leave file positioned at the character immediately after the token or EOF,
 * whichever comes first. If no more tokens on line, position the file to the
 * beginning of the next line or EOF, whichever comes first.
 *
 * Handle comments. Treat unquoted keywords that might be role names or
102 103 104
 * database names specially, by appending a newline to them.  Also, when
 * a token is terminated by a comma, the comma is included in the returned
 * token.
105
 */
106
static bool
107
next_token(FILE *fp, char *buf, int bufsz)
108
{
109
	int			c;
110
	char	   *start_buf = buf;
111
	char	   *end_buf = buf + (bufsz - 2);
B
Bruce Momjian 已提交
112 113
	bool		in_quote = false;
	bool		was_quote = false;
B
Bruce Momjian 已提交
114
	bool		saw_quote = false;
115

116 117
	Assert(end_buf > start_buf);

B
Bruce Momjian 已提交
118
	/* Move over initial whitespace and commas */
119
	while ((c = getc(fp)) != EOF && (pg_isblank(c) || c == ','))
120
		;
121

122 123 124
	if (c == EOF || c == '\n')
	{
		*buf = '\0';
125
		return false;
126 127 128
	}

	/*
B
Bruce Momjian 已提交
129 130
	 * Build a token in buf of next characters up to EOF, EOL, unquoted comma,
	 * or unquoted whitespace.
131 132
	 */
	while (c != EOF && c != '\n' &&
133
		   (!pg_isblank(c) || in_quote))
134
	{
135 136 137 138 139 140 141 142 143 144 145 146 147
		/* skip comments to EOL */
		if (c == '#' && !in_quote)
		{
			while ((c = getc(fp)) != EOF && c != '\n')
				;
			/* If only comment, consume EOL too; return EOL */
			if (c != EOF && buf == start_buf)
				c = getc(fp);
			break;
		}

		if (buf >= end_buf)
		{
148
			*buf = '\0';
149 150
			ereport(LOG,
					(errcode(ERRCODE_CONFIG_FILE_ERROR),
B
Bruce Momjian 已提交
151 152
			   errmsg("authentication file token too long, skipping: \"%s\"",
					  start_buf)));
153 154 155 156 157 158
			/* Discard remainder of line */
			while ((c = getc(fp)) != EOF && c != '\n')
				;
			break;
		}

159
		if (c != '"' || was_quote)
160 161 162
			*buf++ = c;

		/* We pass back the comma so the caller knows there is more */
163
		if (c == ',' && !in_quote)
164 165 166 167 168 169 170 171 172
			break;

		/* Literal double-quote is two double-quotes */
		if (in_quote && c == '"')
			was_quote = !was_quote;
		else
			was_quote = false;

		if (c == '"')
173
		{
174 175
			in_quote = !in_quote;
			saw_quote = true;
176
		}
177

178
		c = getc(fp);
179
	}
180

181
	/*
B
Bruce Momjian 已提交
182 183
	 * Put back the char right after the token (critical in case it is EOL,
	 * since we need to detect end-of-line at next call).
184 185 186 187 188
	 */
	if (c != EOF)
		ungetc(c, fp);

	*buf = '\0';
189

B
Bruce Momjian 已提交
190 191 192
	if (!saw_quote &&
		(strcmp(start_buf, "all") == 0 ||
		 strcmp(start_buf, "sameuser") == 0 ||
193
		 strcmp(start_buf, "samegroup") == 0 ||
194 195
		 strcmp(start_buf, "samerole") == 0 ||
		 strcmp(start_buf, "replication") == 0))
196 197 198
	{
		/* append newline to a magical keyword */
		*buf++ = '\n';
199
		*buf = '\0';
200
	}
201 202

	return (saw_quote || buf > start_buf);
203 204
}

B
Bruce Momjian 已提交
205
/*
B
Bruce Momjian 已提交
206 207 208
 *	 Tokenize file and handle file inclusion and comma lists. We have
 *	 to  break	apart  the	commas	to	expand	any  file names then
 *	 reconstruct with commas.
209
 *
210
 * The result is a palloc'd string, or NULL if we have reached EOL.
B
Bruce Momjian 已提交
211 212
 */
static char *
213
next_token_expand(const char *filename, FILE *file)
B
Bruce Momjian 已提交
214 215 216
{
	char		buf[MAX_TOKEN];
	char	   *comma_str = pstrdup("");
217
	bool		got_something = false;
B
Bruce Momjian 已提交
218 219
	bool		trailing_comma;
	char	   *incbuf;
220
	int			needed;
B
Bruce Momjian 已提交
221 222 223

	do
	{
224
		if (!next_token(file, buf, sizeof(buf)))
B
Bruce Momjian 已提交
225 226
			break;

227 228 229
		got_something = true;

		if (strlen(buf) > 0 && buf[strlen(buf) - 1] == ',')
B
Bruce Momjian 已提交
230 231
		{
			trailing_comma = true;
B
Bruce Momjian 已提交
232
			buf[strlen(buf) - 1] = '\0';
B
Bruce Momjian 已提交
233 234 235 236 237 238
		}
		else
			trailing_comma = false;

		/* Is this referencing a file? */
		if (buf[0] == '@')
239
			incbuf = tokenize_inc_file(filename, buf + 1);
B
Bruce Momjian 已提交
240 241 242
		else
			incbuf = pstrdup(buf);

243 244 245 246
		needed = strlen(comma_str) + strlen(incbuf) + 1;
		if (trailing_comma)
			needed++;
		comma_str = repalloc(comma_str, needed);
B
Bruce Momjian 已提交
247 248 249
		strcat(comma_str, incbuf);
		if (trailing_comma)
			strcat(comma_str, MULTI_VALUE_SEP);
250
		pfree(incbuf);
B
Bruce Momjian 已提交
251 252
	} while (trailing_comma);

253 254 255 256 257 258
	if (!got_something)
	{
		pfree(comma_str);
		return NULL;
	}

B
Bruce Momjian 已提交
259 260 261
	return comma_str;
}

262

B
Bruce Momjian 已提交
263 264 265
/*
 * Free memory used by lines/tokens (i.e., structure built by tokenize_file)
 */
266
static void
267
free_lines(List **lines, List **line_nums)
268
{
269 270 271 272 273 274
	/*
	 * Either both must be non-NULL, or both must be NULL
	 */
	Assert((*lines != NIL && *line_nums != NIL) ||
		   (*lines == NIL && *line_nums == NIL));

B
Bruce Momjian 已提交
275 276
	if (*lines)
	{
277
		/*
B
Bruce Momjian 已提交
278 279 280 281
		 * "lines" is a list of lists; each of those sublists consists of
		 * palloc'ed tokens, so we want to free each pointed-to token in a
		 * sublist, followed by the sublist itself, and finally the whole
		 * list.
282 283
		 */
		ListCell   *line;
284

B
Bruce Momjian 已提交
285 286 287
		foreach(line, *lines)
		{
			List	   *ln = lfirst(line);
288
			ListCell   *token;
B
Bruce Momjian 已提交
289

290
			foreach(token, ln)
B
Bruce Momjian 已提交
291 292
				pfree(lfirst(token));
			/* free the sublist structure itself */
293
			list_free(ln);
B
Bruce Momjian 已提交
294 295
		}
		/* free the list structure itself */
296
		list_free(*lines);
B
Bruce Momjian 已提交
297 298 299
		/* clear the static variable */
		*lines = NIL;
	}
300 301 302 303 304 305

	if (*line_nums)
	{
		list_free(*line_nums);
		*line_nums = NIL;
	}
B
Bruce Momjian 已提交
306 307 308 309
}


static char *
310 311
tokenize_inc_file(const char *outer_filename,
				  const char *inc_filename)
B
Bruce Momjian 已提交
312 313 314
{
	char	   *inc_fullname;
	FILE	   *inc_file;
B
Bruce Momjian 已提交
315
	List	   *inc_lines;
316 317
	List	   *inc_line_nums;
	ListCell   *line;
318
	char	   *comma_str;
B
Bruce Momjian 已提交
319

320 321 322 323 324 325 326 327 328 329 330 331 332 333 334
	if (is_absolute_path(inc_filename))
	{
		/* absolute path is taken as-is */
		inc_fullname = pstrdup(inc_filename);
	}
	else
	{
		/* relative path is relative to dir of calling file */
		inc_fullname = (char *) palloc(strlen(outer_filename) + 1 +
									   strlen(inc_filename) + 1);
		strcpy(inc_fullname, outer_filename);
		get_parent_directory(inc_fullname);
		join_path_components(inc_fullname, inc_fullname, inc_filename);
		canonicalize_path(inc_fullname);
	}
B
Bruce Momjian 已提交
335 336

	inc_file = AllocateFile(inc_fullname, "r");
337
	if (inc_file == NULL)
B
Bruce Momjian 已提交
338
	{
339 340 341 342
		ereport(LOG,
				(errcode_for_file_access(),
				 errmsg("could not open secondary authentication file \"@%s\" as \"%s\": %m",
						inc_filename, inc_fullname)));
B
Bruce Momjian 已提交
343 344
		pfree(inc_fullname);

345 346
		/* return single space, it matches nothing */
		return pstrdup(" ");
B
Bruce Momjian 已提交
347 348 349
	}

	/* There is possible recursion here if the file contains @ */
350 351
	tokenize_file(inc_fullname, inc_file, &inc_lines, &inc_line_nums);

B
Bruce Momjian 已提交
352
	FreeFile(inc_file);
353
	pfree(inc_fullname);
B
Bruce Momjian 已提交
354

355
	/* Create comma-separated string from List */
356
	comma_str = pstrdup("");
B
Bruce Momjian 已提交
357 358
	foreach(line, inc_lines)
	{
B
Bruce Momjian 已提交
359 360
		List	   *token_list = (List *) lfirst(line);
		ListCell   *token;
B
Bruce Momjian 已提交
361

362
		foreach(token, token_list)
B
Bruce Momjian 已提交
363
		{
B
Bruce Momjian 已提交
364 365
			int			oldlen = strlen(comma_str);
			int			needed;
366 367 368 369 370 371

			needed = oldlen + strlen(lfirst(token)) + 1;
			if (oldlen > 0)
				needed++;
			comma_str = repalloc(comma_str, needed);
			if (oldlen > 0)
B
Bruce Momjian 已提交
372 373 374 375 376
				strcat(comma_str, MULTI_VALUE_SEP);
			strcat(comma_str, lfirst(token));
		}
	}

377
	free_lines(&inc_lines, &inc_line_nums);
B
Bruce Momjian 已提交
378

379 380 381 382 383 384 385
	/* if file is empty, return single space rather than empty string */
	if (strlen(comma_str) == 0)
	{
		pfree(comma_str);
		return pstrdup(" ");
	}

B
Bruce Momjian 已提交
386
	return comma_str;
387 388 389
}


390
/*
391 392 393
 * Tokenize the given file, storing the resulting data into two lists:
 * a list of sublists, each sublist containing the tokens in a line of
 * the file, and a list of line numbers.
394 395
 *
 * filename must be the absolute path to the target file.
396
 */
397
static void
398 399
tokenize_file(const char *filename, FILE *file,
			  List **lines, List **line_nums)
400
{
401
	List	   *current_line = NIL;
402
	int			line_number = 1;
B
Bruce Momjian 已提交
403
	char	   *buf;
404

405 406
	*lines = *line_nums = NIL;

407
	while (!feof(file))
408
	{
409
		buf = next_token_expand(filename, file);
410

B
Bruce Momjian 已提交
411
		/* add token to list, unless we are at EOL or comment start */
412
		if (buf)
413
		{
414 415 416 417 418 419 420 421
			if (current_line == NIL)
			{
				/* make a new line List, record its line number */
				current_line = lappend(current_line, buf);
				*lines = lappend(*lines, current_line);
				*line_nums = lappend_int(*line_nums, line_number);
			}
			else
422
			{
423 424
				/* append token to current line's list */
				current_line = lappend(current_line, buf);
425 426 427
			}
		}
		else
428
		{
B
Bruce Momjian 已提交
429
			/* we are at real or logical EOL, so force a new line List */
430 431
			current_line = NIL;
			/* Advance line number whenever we reach EOL */
432
			line_number++;
433
		}
434 435 436
	}
}

B
Bruce Momjian 已提交
437 438

/*
439 440
 * Does user belong to role?
 *
441
 * userid is the OID of the role given as the attempted login identifier.
442
 * We check to see if it is a member of the specified role name.
B
Bruce Momjian 已提交
443
 */
444
static bool
445
is_member(Oid userid, const char *role)
B
Bruce Momjian 已提交
446
{
447
	Oid			roleid;
B
Bruce Momjian 已提交
448

449
	if (!OidIsValid(userid))
450
		return false;			/* if user not exist, say "no" */
451

452
	roleid = get_roleid(role);
453

454 455
	if (!OidIsValid(roleid))
		return false;			/* if target role not exist, say "no" */
456

457 458
	/* See if user is directly or indirectly a member of role */
	return is_member_of_role(userid, roleid);
B
Bruce Momjian 已提交
459 460 461
}

/*
462 463 464 465 466
 * Check comma-separated list for a match to role, allowing group names.
 *
 * NB: param_str is destructively modified!  In current usage, this is
 * okay only because this code is run after forking off from the postmaster,
 * and so it doesn't matter that we clobber the stored hba info.
B
Bruce Momjian 已提交
467
 */
468
static bool
469
check_role(const char *role, Oid roleid, char *param_str)
B
Bruce Momjian 已提交
470
{
B
Bruce Momjian 已提交
471
	char	   *tok;
B
Bruce Momjian 已提交
472

473 474 475
	for (tok = strtok(param_str, MULTI_VALUE_SEP);
		 tok != NULL;
		 tok = strtok(NULL, MULTI_VALUE_SEP))
B
Bruce Momjian 已提交
476 477
	{
		if (tok[0] == '+')
478
		{
479
			if (is_member(roleid, tok + 1))
480
				return true;
B
Bruce Momjian 已提交
481
		}
482
		else if (strcmp(tok, role) == 0 ||
483
				 strcmp(tok, "all\n") == 0)
484
			return true;
B
Bruce Momjian 已提交
485
	}
486

487
	return false;
B
Bruce Momjian 已提交
488 489 490
}

/*
491
 * Check to see if db/role combination matches param string.
492 493 494 495
 *
 * NB: param_str is destructively modified!  In current usage, this is
 * okay only because this code is run after forking off from the postmaster,
 * and so it doesn't matter that we clobber the stored hba info.
B
Bruce Momjian 已提交
496
 */
497
static bool
498
check_db(const char *dbname, const char *role, Oid roleid, char *param_str)
B
Bruce Momjian 已提交
499
{
B
Bruce Momjian 已提交
500
	char	   *tok;
B
Bruce Momjian 已提交
501

502 503 504
	for (tok = strtok(param_str, MULTI_VALUE_SEP);
		 tok != NULL;
		 tok = strtok(NULL, MULTI_VALUE_SEP))
B
Bruce Momjian 已提交
505
	{
506
		if (strcmp(tok, "all\n") == 0)
507
			return true;
508
		else if (strcmp(tok, "sameuser\n") == 0)
B
Bruce Momjian 已提交
509
		{
510
			if (strcmp(dbname, role) == 0)
511
				return true;
512
		}
513 514
		else if (strcmp(tok, "samegroup\n") == 0 ||
				 strcmp(tok, "samerole\n") == 0)
B
Bruce Momjian 已提交
515
		{
516
			if (is_member(roleid, dbname))
517
				return true;
B
Bruce Momjian 已提交
518
		}
519 520 521
		else if (strcmp(tok, "replication\n") == 0 &&
				 am_walsender)
			return true;
B
Bruce Momjian 已提交
522
		else if (strcmp(tok, dbname) == 0)
523
			return true;
524
	}
525
	return false;
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 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620
/*
 * Check to see if a connecting IP matches the given address and netmask.
 */
static bool
check_ip(SockAddr *raddr, struct sockaddr *addr, struct sockaddr *mask)
{
	if (raddr->addr.ss_family == addr->sa_family)
	{
		/* Same address family */
		if (!pg_range_sockaddr(&raddr->addr,
							   (struct sockaddr_storage*)addr,
		                       (struct sockaddr_storage*)mask))
			return false;
	}
#ifdef HAVE_IPV6
	else if (addr->sa_family == AF_INET &&
			 raddr->addr.ss_family == AF_INET6)
	{
		/*
		 * If we're connected on IPv6 but the file specifies an IPv4 address
		 * to match against, promote the latter to an IPv6 address
		 * before trying to match the client's address.
		 */
		struct sockaddr_storage addrcopy,
					maskcopy;

		memcpy(&addrcopy, &addr, sizeof(addrcopy));
		memcpy(&maskcopy, &mask, sizeof(maskcopy));
		pg_promote_v4_to_v6_addr(&addrcopy);
		pg_promote_v4_to_v6_mask(&maskcopy);

		if (!pg_range_sockaddr(&raddr->addr, &addrcopy, &maskcopy))
			return false;
	}
#endif   /* HAVE_IPV6 */
	else
	{
		/* Wrong address family, no IPV6 */
		return false;
	}

	return true;
}

/*
 * pg_foreach_ifaddr callback: does client addr match this machine interface?
 */
static void
check_network_callback(struct sockaddr *addr, struct sockaddr *netmask,
					   void *cb_data)
{
	check_network_data *cn = (check_network_data *) cb_data;
	struct sockaddr_storage mask;

	/* Already found a match? */
	if (cn->result)
		return;

	if (cn->method == ipCmpSameHost)
	{
		/* Make an all-ones netmask of appropriate length for family */
		pg_sockaddr_cidr_mask(&mask, NULL, addr->sa_family);
		cn->result = check_ip(cn->raddr, addr, (struct sockaddr*) &mask);
	}
	else
	{
		/* Use the netmask of the interface itself */
		cn->result = check_ip(cn->raddr, addr, netmask);
	}
}

/*
 * Use pg_foreach_ifaddr to check a samehost or samenet match
 */
static bool
check_same_host_or_net(SockAddr *raddr, IPCompareMethod method)
{
	check_network_data cn;

	cn.method = method;
	cn.raddr = raddr;
	cn.result = false;

	errno = 0;
	if (pg_foreach_ifaddr(check_network_callback, &cn) < 0)
	{
		elog(LOG, "error enumerating network interfaces: %m");
		return false;
	}

	return cn.result;
}

621

622 623 624
/*
 * Macros used to check and report on invalid configuration options.
 * INVALID_AUTH_OPTION = reports when an option is specified for a method where it's
625
 *						 not supported.
626
 * REQUIRE_AUTH_OPTION = same as INVALID_AUTH_OPTION, except it also checks if the
627 628
 *						 method is actually the one specified. Used as a shortcut when
 *						 the option is only valid for one authentication method.
629
 * MANDATORY_AUTH_ARG  = check if a required option is set for an authentication method,
630
 *						 reporting error if it's not.
631 632 633 634
 */
#define INVALID_AUTH_OPTION(optname, validmethods) do {\
	ereport(LOG, \
			(errcode(ERRCODE_CONFIG_FILE_ERROR), \
635 636 637
			 /* translator: the second %s is a list of auth methods */ \
			 errmsg("authentication option \"%s\" is only valid for authentication methods %s", \
					optname, _(validmethods)), \
638 639
			 errcontext("line %d of configuration file \"%s\"", \
					line_num, HbaFileName))); \
640
	return false; \
641 642 643 644
} while (0);

#define REQUIRE_AUTH_OPTION(methodval, optname, validmethods) do {\
	if (parsedline->auth_method != methodval) \
645
		INVALID_AUTH_OPTION(optname, validmethods); \
646 647 648 649 650 651
} while (0);

#define MANDATORY_AUTH_ARG(argvar, argname, authname) do {\
	if (argvar == NULL) {\
		ereport(LOG, \
				(errcode(ERRCODE_CONFIG_FILE_ERROR), \
652
				 errmsg("authentication method \"%s\" requires argument \"%s\" to be set", \
653 654 655
						authname, argname), \
				 errcontext("line %d of configuration file \"%s\"", \
						line_num, HbaFileName))); \
656
		return false; \
657 658 659 660
	} \
} while (0);


661
/*
662 663
 * Parse one line in the hba config file and store the result in
 * a HbaLine structure.
664
 */
665 666
static bool
parse_hba_line(List *line, int line_num, HbaLine *parsedline)
667
{
B
Bruce Momjian 已提交
668
	char	   *token;
669
	struct addrinfo *gai_result;
B
Bruce Momjian 已提交
670
	struct addrinfo hints;
B
Bruce Momjian 已提交
671
	int			ret;
672
	char	   *cidr_slash;
673
	char	   *unsupauth;
674
	ListCell   *line_item;
675

676
	line_item = list_head(line);
677 678 679

	parsedline->linenumber = line_num;

680
	/* Check the record type. */
681
	token = lfirst(line_item);
682
	if (strcmp(token, "local") == 0)
683
	{
684
		parsedline->conntype = ctLocal;
685
	}
686
	else if (strcmp(token, "host") == 0
B
Bruce Momjian 已提交
687 688
			 || strcmp(token, "hostssl") == 0
			 || strcmp(token, "hostnossl") == 0)
689
	{
690

B
Bruce Momjian 已提交
691
		if (token[4] == 's')	/* "hostssl" */
692
		{
693
#ifdef USE_SSL
694
			parsedline->conntype = ctHostSSL;
695
#else
696 697 698
			ereport(LOG,
					(errcode(ERRCODE_CONFIG_FILE_ERROR),
					 errmsg("hostssl not supported on this platform"),
699
				 errhint("compile with --enable-ssl to use SSL connections"),
700
					 errcontext("line %d of configuration file \"%s\"",
701
								line_num, HbaFileName)));
702
			return false;
703
#endif
704
		}
705
#ifdef USE_SSL
B
Bruce Momjian 已提交
706
		else if (token[4] == 'n')		/* "hostnossl" */
707
		{
708
			parsedline->conntype = ctHostNoSSL;
709 710
		}
#endif
711
		else
712 713 714 715
		{
			/* "host", or "hostnossl" and SSL support not built in */
			parsedline->conntype = ctHost;
		}
716
	}							/* record type */
717
	else
718 719 720 721 722 723
	{
		ereport(LOG,
				(errcode(ERRCODE_CONFIG_FILE_ERROR),
				 errmsg("invalid connection type \"%s\"",
						token),
				 errcontext("line %d of configuration file \"%s\"",
724
							line_num, HbaFileName)));
725 726
		return false;
	}
727

728 729 730
	/* Get the database. */
	line_item = lnext(line_item);
	if (!line_item)
731 732 733 734 735
	{
		ereport(LOG,
				(errcode(ERRCODE_CONFIG_FILE_ERROR),
				 errmsg("end-of-line before database specification"),
				 errcontext("line %d of configuration file \"%s\"",
736
							line_num, HbaFileName)));
737 738
		return false;
	}
739
	parsedline->database = pstrdup(lfirst(line_item));
740

741 742 743
	/* Get the role. */
	line_item = lnext(line_item);
	if (!line_item)
744 745 746 747 748
	{
		ereport(LOG,
				(errcode(ERRCODE_CONFIG_FILE_ERROR),
				 errmsg("end-of-line before role specification"),
				 errcontext("line %d of configuration file \"%s\"",
749
							line_num, HbaFileName)));
750 751
		return false;
	}
752
	parsedline->role = pstrdup(lfirst(line_item));
B
Bruce Momjian 已提交
753

754 755
	if (parsedline->conntype != ctLocal)
	{
B
Bruce Momjian 已提交
756
		/* Read the IP address field. (with or without CIDR netmask) */
757 758
		line_item = lnext(line_item);
		if (!line_item)
759 760 761
		{
			ereport(LOG,
					(errcode(ERRCODE_CONFIG_FILE_ERROR),
P
Peter Eisentraut 已提交
762
					 errmsg("end-of-line before IP address specification"),
763
					 errcontext("line %d of configuration file \"%s\"",
764
								line_num, HbaFileName)));
765 766
			return false;
		}
767 768 769 770
		token = lfirst(line_item);

		/* Is it equal to 'samehost' or 'samenet'? */
		if (strcmp(token, "samehost") == 0)
B
Bruce Momjian 已提交
771
		{
772 773
			/* Any IP on this host is allowed to connect */
			parsedline->ip_cmp_method = ipCmpSameHost;
B
Bruce Momjian 已提交
774
		}
775
		else if (strcmp(token, "samenet") == 0)
B
Bruce Momjian 已提交
776
		{
777 778
			/* Any IP on the host's subnets is allowed to connect */
			parsedline->ip_cmp_method = ipCmpSameNet;
B
Bruce Momjian 已提交
779 780 781
		}
		else
		{
782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801
			/* IP and netmask are specified */
			parsedline->ip_cmp_method = ipCmpMask;

			/* need a modifiable copy of token */
			token = pstrdup(token);

			/* Check if it has a CIDR suffix and if so isolate it */
			cidr_slash = strchr(token, '/');
			if (cidr_slash)
				*cidr_slash = '\0';

			/* Get the IP address either way */
			hints.ai_flags = AI_NUMERICHOST;
			hints.ai_family = PF_UNSPEC;
			hints.ai_socktype = 0;
			hints.ai_protocol = 0;
			hints.ai_addrlen = 0;
			hints.ai_canonname = NULL;
			hints.ai_addr = NULL;
			hints.ai_next = NULL;
B
Bruce Momjian 已提交
802

803
			ret = pg_getaddrinfo_all(token, NULL, &hints, &gai_result);
804 805
			if (ret || !gai_result)
			{
806 807
				ereport(LOG,
						(errcode(ERRCODE_CONFIG_FILE_ERROR),
808
						 errmsg("invalid IP address \"%s\": %s",
809
								token, gai_strerror(ret)),
810
						 errcontext("line %d of configuration file \"%s\"",
811
									line_num, HbaFileName)));
812
				if (gai_result)
813
					pg_freeaddrinfo_all(hints.ai_family, gai_result);
814
				pfree(token);
815
				return false;
816
			}
817

818 819
			memcpy(&parsedline->addr, gai_result->ai_addr,
				   gai_result->ai_addrlen);
820
			pg_freeaddrinfo_all(hints.ai_family, gai_result);
B
Bruce Momjian 已提交
821

822 823
			/* Get the netmask */
			if (cidr_slash)
824
			{
825 826 827
				if (pg_sockaddr_cidr_mask(&parsedline->mask, cidr_slash + 1,
										  parsedline->addr.ss_family) < 0)
				{
828
					*cidr_slash = '/';		/* restore token for message */
829 830 831 832 833 834
					ereport(LOG,
							(errcode(ERRCODE_CONFIG_FILE_ERROR),
							 errmsg("invalid CIDR mask in address \"%s\"",
									token),
							 errcontext("line %d of configuration file \"%s\"",
										line_num, HbaFileName)));
835
					pfree(token);
836 837
					return false;
				}
838
				pfree(token);
839 840 841 842
			}
			else
			{
				/* Read the mask field. */
843
				pfree(token);
844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881
				line_item = lnext(line_item);
				if (!line_item)
				{
					ereport(LOG,
							(errcode(ERRCODE_CONFIG_FILE_ERROR),
							 errmsg("end-of-line before netmask specification"),
							 errcontext("line %d of configuration file \"%s\"",
										line_num, HbaFileName)));
					return false;
				}
				token = lfirst(line_item);

				ret = pg_getaddrinfo_all(token, NULL, &hints, &gai_result);
				if (ret || !gai_result)
				{
					ereport(LOG,
							(errcode(ERRCODE_CONFIG_FILE_ERROR),
							 errmsg("invalid IP mask \"%s\": %s",
									token, gai_strerror(ret)),
							 errcontext("line %d of configuration file \"%s\"",
										line_num, HbaFileName)));
					if (gai_result)
						pg_freeaddrinfo_all(hints.ai_family, gai_result);
					return false;
				}

				memcpy(&parsedline->mask, gai_result->ai_addr,
					   gai_result->ai_addrlen);
				pg_freeaddrinfo_all(hints.ai_family, gai_result);

				if (parsedline->addr.ss_family != parsedline->mask.ss_family)
				{
					ereport(LOG,
							(errcode(ERRCODE_CONFIG_FILE_ERROR),
							 errmsg("IP address and mask do not match in file \"%s\" line %d",
									HbaFileName, line_num)));
					return false;
				}
882
			}
B
Bruce Momjian 已提交
883
		}
884
	}							/* != ctLocal */
885 886 887 888

	/* Get the authentication method */
	line_item = lnext(line_item);
	if (!line_item)
889 890 891 892 893
	{
		ereport(LOG,
				(errcode(ERRCODE_CONFIG_FILE_ERROR),
				 errmsg("end-of-line before authentication method"),
				 errcontext("line %d of configuration file \"%s\"",
894
							line_num, HbaFileName)));
895 896
		return false;
	}
897
	token = lfirst(line_item);
898

899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926
	unsupauth = NULL;
	if (strcmp(token, "trust") == 0)
		parsedline->auth_method = uaTrust;
	else if (strcmp(token, "ident") == 0)
		parsedline->auth_method = uaIdent;
	else if (strcmp(token, "password") == 0)
		parsedline->auth_method = uaPassword;
	else if (strcmp(token, "krb5") == 0)
#ifdef KRB5
		parsedline->auth_method = uaKrb5;
#else
		unsupauth = "krb5";
#endif
	else if (strcmp(token, "gss") == 0)
#ifdef ENABLE_GSS
		parsedline->auth_method = uaGSS;
#else
		unsupauth = "gss";
#endif
	else if (strcmp(token, "sspi") == 0)
#ifdef ENABLE_SSPI
		parsedline->auth_method = uaSSPI;
#else
		unsupauth = "sspi";
#endif
	else if (strcmp(token, "reject") == 0)
		parsedline->auth_method = uaReject;
	else if (strcmp(token, "md5") == 0)
927 928 929 930 931 932 933 934
	{
		if (Db_user_namespace)
		{
			ereport(LOG,
					(errcode(ERRCODE_CONFIG_FILE_ERROR),
					 errmsg("MD5 authentication is not supported when \"db_user_namespace\" is enabled")));
			return false;
		}
935
		parsedline->auth_method = uaMD5;
936
	}
937 938 939 940 941 942 943 944 945 946 947
	else if (strcmp(token, "pam") == 0)
#ifdef USE_PAM
		parsedline->auth_method = uaPAM;
#else
		unsupauth = "pam";
#endif
	else if (strcmp(token, "ldap") == 0)
#ifdef USE_LDAP
		parsedline->auth_method = uaLDAP;
#else
		unsupauth = "ldap";
948 949 950 951 952 953
#endif
	else if (strcmp(token, "cert") == 0)
#ifdef USE_SSL
		parsedline->auth_method = uaCert;
#else
		unsupauth = "cert";
954
#endif
955 956
	else if (strcmp(token, "radius")== 0)
		parsedline->auth_method = uaRADIUS;
957 958 959 960 961 962
	else
	{
		ereport(LOG,
				(errcode(ERRCODE_CONFIG_FILE_ERROR),
				 errmsg("invalid authentication method \"%s\"",
						token),
963
				 errcontext("line %d of configuration file \"%s\"",
964
							line_num, HbaFileName)));
965
		return false;
966 967 968 969 970 971 972 973
	}

	if (unsupauth)
	{
		ereport(LOG,
				(errcode(ERRCODE_CONFIG_FILE_ERROR),
				 errmsg("invalid authentication method \"%s\": not supported on this platform",
						token),
974
				 errcontext("line %d of configuration file \"%s\"",
975
							line_num, HbaFileName)));
976
		return false;
977 978 979 980 981 982 983 984
	}

	/* Invalid authentication combinations */
	if (parsedline->conntype == ctLocal &&
		parsedline->auth_method == uaKrb5)
	{
		ereport(LOG,
				(errcode(ERRCODE_CONFIG_FILE_ERROR),
985
			 errmsg("krb5 authentication is not supported on local sockets"),
986
				 errcontext("line %d of configuration file \"%s\"",
987
							line_num, HbaFileName)));
988
		return false;
989 990
	}

991 992 993 994 995 996 997 998 999 1000 1001
	if (parsedline->conntype != ctHostSSL &&
		parsedline->auth_method == uaCert)
	{
		ereport(LOG,
				(errcode(ERRCODE_CONFIG_FILE_ERROR),
				 errmsg("cert authentication is only supported on hostssl connections"),
				 errcontext("line %d of configuration file \"%s\"",
							line_num, HbaFileName)));
		return false;
	}

1002 1003
	/* Parse remaining arguments */
	while ((line_item = lnext(line_item)) != NULL)
1004
	{
1005
		char	   *c;
1006

1007 1008
		token = lfirst(line_item);

1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021
		c = strchr(token, '=');
		if (c == NULL)
		{
			/*
			 * Got something that's not a name=value pair.
			 *
			 * XXX: attempt to do some backwards compatible parsing here?
			 */
			ereport(LOG,
					(errcode(ERRCODE_CONFIG_FILE_ERROR),
					 errmsg("authentication option not in name=value format: %s", token),
					 errcontext("line %d of configuration file \"%s\"",
								line_num, HbaFileName)));
1022
			return false;
1023 1024
		}
		else
1025
		{
1026
			*c++ = '\0';		/* token now holds "name", c holds "value" */
1027 1028 1029 1030 1031
			if (strcmp(token, "map") == 0)
			{
				if (parsedline->auth_method != uaIdent &&
					parsedline->auth_method != uaKrb5 &&
					parsedline->auth_method != uaGSS &&
1032 1033
					parsedline->auth_method != uaSSPI &&
					parsedline->auth_method != uaCert)
1034
					INVALID_AUTH_OPTION("map", gettext_noop("ident, krb5, gssapi, sspi and cert"));
1035 1036
				parsedline->usermap = pstrdup(c);
			}
1037 1038 1039
			else if (strcmp(token, "clientcert") == 0)
			{
				/*
1040 1041 1042
				 * Since we require ctHostSSL, this really can never happen on
				 * non-SSL-enabled builds, so don't bother checking for
				 * USE_SSL.
1043 1044 1045 1046 1047 1048
				 */
				if (parsedline->conntype != ctHostSSL)
				{
					ereport(LOG,
							(errcode(ERRCODE_CONFIG_FILE_ERROR),
							 errmsg("clientcert can only be configured for \"hostssl\" rows"),
1049 1050
						   errcontext("line %d of configuration file \"%s\"",
									  line_num, HbaFileName)));
1051 1052 1053 1054 1055 1056 1057 1058 1059 1060
					return false;
				}
				if (strcmp(c, "1") == 0)
				{
					if (!secure_loaded_verify_locations())
					{
						ereport(LOG,
								(errcode(ERRCODE_CONFIG_FILE_ERROR),
								 errmsg("client certificates can only be checked if a root certificate store is available"),
								 errdetail("make sure the root certificate store is present and readable"),
1061 1062
						   errcontext("line %d of configuration file \"%s\"",
									  line_num, HbaFileName)));
1063 1064 1065 1066 1067
						return false;
					}
					parsedline->clientcert = true;
				}
				else
1068 1069 1070 1071 1072 1073
				{
					if (parsedline->auth_method == uaCert)
					{
						ereport(LOG,
								(errcode(ERRCODE_CONFIG_FILE_ERROR),
								 errmsg("clientcert can not be set to 0 when using \"cert\" authentication"),
1074 1075
						   errcontext("line %d of configuration file \"%s\"",
									  line_num, HbaFileName)));
1076 1077
						return false;
					}
1078
					parsedline->clientcert = false;
1079
				}
1080
			}
1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106
			else if (strcmp(token, "pamservice") == 0)
			{
				REQUIRE_AUTH_OPTION(uaPAM, "pamservice", "pam");
				parsedline->pamservice = pstrdup(c);
			}
			else if (strcmp(token, "ldaptls") == 0)
			{
				REQUIRE_AUTH_OPTION(uaLDAP, "ldaptls", "ldap");
				if (strcmp(c, "1") == 0)
					parsedline->ldaptls = true;
				else
					parsedline->ldaptls = false;
			}
			else if (strcmp(token, "ldapserver") == 0)
			{
				REQUIRE_AUTH_OPTION(uaLDAP, "ldapserver", "ldap");
				parsedline->ldapserver = pstrdup(c);
			}
			else if (strcmp(token, "ldapport") == 0)
			{
				REQUIRE_AUTH_OPTION(uaLDAP, "ldapport", "ldap");
				parsedline->ldapport = atoi(c);
				if (parsedline->ldapport == 0)
				{
					ereport(LOG,
							(errcode(ERRCODE_CONFIG_FILE_ERROR),
1107
							 errmsg("invalid LDAP port number: \"%s\"", c),
1108 1109
						   errcontext("line %d of configuration file \"%s\"",
									  line_num, HbaFileName)));
1110
					return false;
1111 1112
				}
			}
1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132
			else if (strcmp(token, "ldapbinddn") == 0)
			{
				REQUIRE_AUTH_OPTION(uaLDAP, "ldapbinddn", "ldap");
				parsedline->ldapbinddn = pstrdup(c);
			}
			else if (strcmp(token, "ldapbindpasswd") == 0)
			{
				REQUIRE_AUTH_OPTION(uaLDAP, "ldapbindpasswd", "ldap");
				parsedline->ldapbindpasswd = pstrdup(c);
			}
			else if (strcmp(token, "ldapsearchattribute") == 0)
			{
				REQUIRE_AUTH_OPTION(uaLDAP, "ldapsearchattribute", "ldap");
				parsedline->ldapsearchattribute = pstrdup(c);
			}
			else if (strcmp(token, "ldapbasedn") == 0)
			{
				REQUIRE_AUTH_OPTION(uaLDAP, "ldapbasedn", "ldap");
				parsedline->ldapbasedn = pstrdup(c);
			}
1133 1134 1135 1136 1137 1138
			else if (strcmp(token, "ldapprefix") == 0)
			{
				REQUIRE_AUTH_OPTION(uaLDAP, "ldapprefix", "ldap");
				parsedline->ldapprefix = pstrdup(c);
			}
			else if (strcmp(token, "ldapsuffix") == 0)
1139
			{
1140 1141
				REQUIRE_AUTH_OPTION(uaLDAP, "ldapsuffix", "ldap");
				parsedline->ldapsuffix = pstrdup(c);
1142
			}
1143 1144 1145 1146 1147 1148 1149 1150 1151 1152
			else if (strcmp(token, "krb_server_hostname") == 0)
			{
				REQUIRE_AUTH_OPTION(uaKrb5, "krb_server_hostname", "krb5");
				parsedline->krb_server_hostname = pstrdup(c);
			}
			else if (strcmp(token, "krb_realm") == 0)
			{
				if (parsedline->auth_method != uaKrb5 &&
					parsedline->auth_method != uaGSS &&
					parsedline->auth_method != uaSSPI)
1153
					INVALID_AUTH_OPTION("krb_realm", gettext_noop("krb5, gssapi and sspi"));
1154 1155
				parsedline->krb_realm = pstrdup(c);
			}
1156 1157 1158 1159 1160
			else if (strcmp(token, "include_realm") == 0)
			{
				if (parsedline->auth_method != uaKrb5 &&
					parsedline->auth_method != uaGSS &&
					parsedline->auth_method != uaSSPI)
1161
					INVALID_AUTH_OPTION("include_realm", gettext_noop("krb5, gssapi and sspi"));
1162 1163 1164 1165 1166
				if (strcmp(c, "1") == 0)
					parsedline->include_realm = true;
				else
					parsedline->include_realm = false;
			}
1167 1168 1169
			else if (strcmp(token, "radiusserver") == 0)
			{
				REQUIRE_AUTH_OPTION(uaRADIUS, "radiusserver", "radius");
1170 1171 1172 1173 1174 1175 1176

				MemSet(&hints, 0, sizeof(hints));
				hints.ai_socktype = SOCK_DGRAM;
				hints.ai_family = AF_UNSPEC;

				ret = pg_getaddrinfo_all(c, NULL, &hints, &gai_result);
				if (ret || !gai_result)
1177 1178 1179
				{
					ereport(LOG,
							(errcode(ERRCODE_CONFIG_FILE_ERROR),
1180 1181
							 errmsg("could not translate RADIUS server name \"%s\" to address: %s",
									c, gai_strerror(ret)),
1182 1183
						   errcontext("line %d of configuration file \"%s\"",
									  line_num, HbaFileName)));
1184 1185
					if (gai_result)
						pg_freeaddrinfo_all(hints.ai_family, gai_result);
1186 1187
					return false;
				}
1188
				pg_freeaddrinfo_all(hints.ai_family, gai_result);
1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214
				parsedline->radiusserver = pstrdup(c);
			}
			else if (strcmp(token, "radiusport") == 0)
			{
				REQUIRE_AUTH_OPTION(uaRADIUS, "radiusport", "radius");
				parsedline->radiusport = atoi(c);
				if (parsedline->radiusport == 0)
				{
					ereport(LOG,
							(errcode(ERRCODE_CONFIG_FILE_ERROR),
							 errmsg("invalid RADIUS port number: \"%s\"", c),
						   errcontext("line %d of configuration file \"%s\"",
									  line_num, HbaFileName)));
					return false;
				}
			}
			else if (strcmp(token, "radiussecret") == 0)
			{
				REQUIRE_AUTH_OPTION(uaRADIUS, "radiussecret", "radius");
				parsedline->radiussecret = pstrdup(c);
			}
			else if (strcmp(token, "radiusidentifier") == 0)
			{
				REQUIRE_AUTH_OPTION(uaRADIUS, "radiusidentifier", "radius");
				parsedline->radiusidentifier = pstrdup(c);
			}
1215 1216
			else
			{
1217 1218
				ereport(LOG,
						(errcode(ERRCODE_CONFIG_FILE_ERROR),
1219
				 errmsg("unknown authentication option name: \"%s\"", token),
1220 1221
						 errcontext("line %d of configuration file \"%s\"",
									line_num, HbaFileName)));
1222
				return false;
1223 1224
			}
		}
1225
	}
1226 1227

	/*
1228 1229
	 * Check if the selected authentication method has any mandatory arguments
	 * that are not set.
1230 1231 1232 1233
	 */
	if (parsedline->auth_method == uaLDAP)
	{
		MANDATORY_AUTH_ARG(parsedline->ldapserver, "ldapserver", "ldap");
1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264

		/*
		 * LDAP can operate in two modes: either with a direct bind, using
		 * ldapprefix and ldapsuffix, or using a search+bind,
		 * using ldapbasedn, ldapbinddn, ldapbindpasswd and ldapsearchattribute.
		 * Disallow mixing these parameters.
		 */
		if (parsedline->ldapprefix || parsedline->ldapsuffix)
		{
			if (parsedline->ldapbasedn ||
				parsedline->ldapbinddn ||
				parsedline->ldapbindpasswd ||
				parsedline->ldapsearchattribute)
			{
				ereport(LOG,
						(errcode(ERRCODE_CONFIG_FILE_ERROR),
						 errmsg("cannot use ldapbasedn, ldapbinddn, ldapbindpasswd or ldapsearchattribute together with ldapprefix"),
						 errcontext("line %d of configuration file \"%s\"",
									line_num, HbaFileName)));
				return false;
			}
		}
		else if (!parsedline->ldapbasedn)
		{
			ereport(LOG,
					(errcode(ERRCODE_CONFIG_FILE_ERROR),
					 errmsg("authentication method \"ldap\" requires argument \"ldapbasedn\", \"ldapprefix\" or \"ldapsuffix\" to be set"),
					 errcontext("line %d of configuration file \"%s\"",
								line_num, HbaFileName)));
			return false;
		}
1265
	}
1266

1267 1268 1269 1270 1271 1272
	if (parsedline->auth_method == uaRADIUS)
	{
		MANDATORY_AUTH_ARG(parsedline->radiusserver, "radiusserver", "radius");
		MANDATORY_AUTH_ARG(parsedline->radiussecret, "radiussecret", "radius");
	}

1273 1274 1275 1276 1277 1278 1279
	/*
	 * Enforce any parameters implied by other settings.
	 */
	if (parsedline->auth_method == uaCert)
	{
		parsedline->clientcert = true;
	}
1280

1281
	return true;
1282 1283 1284
}


1285
/*
1286
 *	Scan the (pre-parsed) hba file line by line, looking for a match
1287
 *	to the port's connection request.
1288 1289 1290
 */
static bool
check_hba(hbaPort *port)
1291
{
1292
	Oid			roleid;
1293
	ListCell   *line;
1294
	HbaLine    *hba;
B
Bruce Momjian 已提交
1295

1296 1297 1298
	/* Get the target role's OID.  Note we do not error out for bad role. */
	roleid = get_roleid(port->user_name);

1299
	foreach(line, parsed_hba_lines)
1300
	{
1301
		hba = (HbaLine *) lfirst(line);
1302

1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334
		/* Check connection type */
		if (hba->conntype == ctLocal)
		{
			if (!IS_AF_UNIX(port->raddr.addr.ss_family))
				continue;
		}
		else
		{
			if (IS_AF_UNIX(port->raddr.addr.ss_family))
				continue;

			/* Check SSL state */
#ifdef USE_SSL
			if (port->ssl)
			{
				/* Connection is SSL, match both "host" and "hostssl" */
				if (hba->conntype == ctHostNoSSL)
					continue;
			}
			else
			{
				/* Connection is not SSL, match both "host" and "hostnossl" */
				if (hba->conntype == ctHostSSL)
					continue;
			}
#else
			/* No SSL support, so reject "hostssl" lines */
			if (hba->conntype == ctHostSSL)
				continue;
#endif

			/* Check IP address */
1335
			switch (hba->ip_cmp_method)
1336
			{
1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350
				case ipCmpMask:
					if (!check_ip(&port->raddr,
								  (struct sockaddr *) &hba->addr,
								  (struct sockaddr *) &hba->mask))
						continue;
					break;
				case ipCmpSameHost:
				case ipCmpSameNet:
					if (!check_same_host_or_net(&port->raddr,
												hba->ip_cmp_method))
						continue;
					break;
				default:
					/* shouldn't get here, but deem it no-match if so */
1351 1352
					continue;
			}
1353
		}						/* != ctLocal */
1354 1355

		/* Check database and role */
1356 1357
		if (!check_db(port->database_name, port->user_name, roleid,
					  hba->database))
1358 1359
			continue;

1360
		if (!check_role(port->user_name, roleid, hba->role))
1361 1362 1363 1364
			continue;

		/* Found a record that matched! */
		port->hba = hba;
1365
		return true;
1366
	}
1367 1368 1369 1370 1371 1372 1373

	/* If no matching entry was found, synthesize 'reject' entry. */
	hba = palloc0(sizeof(HbaLine));
	hba->auth_method = uaReject;
	port->hba = hba;
	return true;

1374 1375
	/*
	 * XXX: Return false only happens if we have a parsing error, which we can
1376 1377
	 * no longer have (parsing now in postmaster). Consider changing API.
	 */
1378
}
1379

1380
/*
1381
 * Free an HbaLine structure
1382 1383 1384 1385 1386 1387 1388 1389
 */
static void
free_hba_record(HbaLine *record)
{
	if (record->database)
		pfree(record->database);
	if (record->role)
		pfree(record->role);
1390 1391
	if (record->usermap)
		pfree(record->usermap);
1392 1393 1394 1395 1396 1397 1398 1399
	if (record->pamservice)
		pfree(record->pamservice);
	if (record->ldapserver)
		pfree(record->ldapserver);
	if (record->ldapprefix)
		pfree(record->ldapprefix);
	if (record->ldapsuffix)
		pfree(record->ldapsuffix);
1400 1401 1402 1403
	if (record->krb_server_hostname)
		pfree(record->krb_server_hostname);
	if (record->krb_realm)
		pfree(record->krb_realm);
1404
	pfree(record);
1405
}
B
Bruce Momjian 已提交
1406

M
 
Marc G. Fournier 已提交
1407
/*
1408
 * Free all records on the parsed HBA list
M
 
Marc G. Fournier 已提交
1409
 */
1410 1411 1412
static void
clean_hba_list(List *lines)
{
1413
	ListCell   *line;
1414 1415 1416

	foreach(line, lines)
	{
1417 1418
		HbaLine    *parsed = (HbaLine *) lfirst(line);

1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432
		if (parsed)
			free_hba_record(parsed);
	}
	list_free(lines);
}

/*
 * Read the config file and create a List of HbaLine records for the contents.
 *
 * The configuration is read into a temporary list, and if any parse error occurs
 * the old list is kept in place and false is returned. Only if the whole file
 * parses Ok is the list replaced, and the function returns true.
 */
bool
1433
load_hba(void)
1434
{
1435
	FILE	   *file;
1436 1437 1438 1439 1440 1441
	List	   *hba_lines = NIL;
	List	   *hba_line_nums = NIL;
	ListCell   *line,
			   *line_num;
	List	   *new_parsed_lines = NIL;
	bool		ok = true;
1442

1443
	file = AllocateFile(HbaFileName, "r");
B
Bruce Momjian 已提交
1444
	if (file == NULL)
1445
	{
1446
		ereport(LOG,
1447
				(errcode_for_file_access(),
1448
				 errmsg("could not open configuration file \"%s\": %m",
1449
						HbaFileName)));
1450

1451 1452
		/*
		 * Caller will take care of making this a FATAL error in case this is
1453 1454
		 * the initial startup. If it happens on reload, we just keep the old
		 * version around.
1455 1456 1457
		 */
		return false;
	}
1458

1459
	tokenize_file(HbaFileName, file, &hba_lines, &hba_line_nums);
1460
	FreeFile(file);
1461 1462 1463 1464

	/* Now parse all the lines */
	forboth(line, hba_lines, line_num, hba_line_nums)
	{
1465
		HbaLine    *newline;
1466 1467 1468 1469 1470

		newline = palloc0(sizeof(HbaLine));

		if (!parse_hba_line(lfirst(line), lfirst_int(line_num), newline))
		{
1471
			/* Parse error in the file, so indicate there's a problem */
1472
			free_hba_record(newline);
1473
			ok = false;
1474 1475

			/*
1476 1477 1478
			 * Keep parsing the rest of the file so we can report errors on
			 * more than the first row. Error has already been reported in the
			 * parsing function, so no need to log it here.
1479 1480
			 */
			continue;
1481 1482 1483 1484 1485
		}

		new_parsed_lines = lappend(new_parsed_lines, newline);
	}

1486 1487 1488
	/* Free the temporary lists */
	free_lines(&hba_lines, &hba_line_nums);

1489 1490 1491 1492 1493 1494 1495
	if (!ok)
	{
		/* Parsing failed at one or more rows, so bail out */
		clean_hba_list(new_parsed_lines);
		return false;
	}

1496 1497 1498 1499 1500
	/* Loaded new file successfully, replace the one we use */
	clean_hba_list(parsed_hba_lines);
	parsed_hba_lines = new_parsed_lines;

	return true;
1501 1502
}

1503
/*
1504 1505
 *	Process one line from the ident config file.
 *
1506
 *	Take the line and compare it to the needed map, pg_role and ident_user.
1507
 *	*found_p and *error_p are set according to our results.
1508
 */
1509
static void
1510
parse_ident_usermap(List *line, int line_number, const char *usermap_name,
1511
					const char *pg_role, const char *ident_user,
1512
					bool case_insensitive, bool *found_p, bool *error_p)
1513
{
1514
	ListCell   *line_item;
1515 1516
	char	   *token;
	char	   *file_map;
1517
	char	   *file_pgrole;
1518
	char	   *file_ident_user;
1519 1520

	*found_p = false;
1521
	*error_p = false;
1522 1523

	Assert(line != NIL);
1524
	line_item = list_head(line);
1525 1526

	/* Get the map token (must exist) */
1527
	token = lfirst(line_item);
1528 1529
	file_map = token;

1530
	/* Get the ident user token */
1531
	line_item = lnext(line_item);
1532
	if (!line_item)
1533
		goto ident_syntax;
1534
	token = lfirst(line_item);
1535
	file_ident_user = token;
1536

1537
	/* Get the PG rolename token */
1538
	line_item = lnext(line_item);
1539
	if (!line_item)
1540
		goto ident_syntax;
1541
	token = lfirst(line_item);
1542
	file_pgrole = token;
1543

1544 1545 1546 1547
	if (strcmp(file_map, usermap_name) != 0)
		/* Line does not match the map name we're looking for, so just abort */
		return;

1548
	/* Match? */
1549
	if (file_ident_user[0] == '/')
1550
	{
1551
		/*
1552 1553 1554 1555
		 * When system username starts with a slash, treat it as a regular
		 * expression. In this case, we process the system username as a
		 * regular expression that returns exactly one match. This is replaced
		 * for \1 in the database username string, if present.
1556 1557 1558 1559 1560 1561 1562 1563 1564
		 */
		int			r;
		regex_t		re;
		regmatch_t	matches[2];
		pg_wchar   *wstr;
		int			wlen;
		char	   *ofs;
		char	   *regexp_pgrole;

1565 1566
		wstr = palloc((strlen(file_ident_user + 1) + 1) * sizeof(pg_wchar));
		wlen = pg_mb2wchar_with_len(file_ident_user + 1, wstr, strlen(file_ident_user + 1));
1567 1568

		/*
1569 1570
		 * XXX: Major room for optimization: regexps could be compiled when
		 * the file is loaded and then re-used in every connection.
1571 1572 1573 1574
		 */
		r = pg_regcomp(&re, wstr, wlen, REG_ADVANCED);
		if (r)
		{
1575
			char		errstr[100];
1576 1577

			pg_regerror(r, &re, errstr, sizeof(errstr));
1578
			ereport(LOG,
1579
					(errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
1580
					 errmsg("invalid regular expression \"%s\": %s", file_ident_user + 1, errstr)));
1581 1582 1583 1584 1585 1586 1587 1588 1589 1590

			pfree(wstr);
			*error_p = true;
			return;
		}
		pfree(wstr);

		wstr = palloc((strlen(ident_user) + 1) * sizeof(pg_wchar));
		wlen = pg_mb2wchar_with_len(ident_user, wstr, strlen(ident_user));

1591
		r = pg_regexec(&re, wstr, wlen, 0, NULL, 2, matches, 0);
1592 1593
		if (r)
		{
1594
			char		errstr[100];
1595 1596 1597 1598 1599

			if (r != REG_NOMATCH)
			{
				/* REG_NOMATCH is not an error, everything else is */
				pg_regerror(r, &re, errstr, sizeof(errstr));
1600
				ereport(LOG,
1601
						(errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
1602
						 errmsg("regular expression match for \"%s\" failed: %s", file_ident_user + 1, errstr)));
1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615
				*error_p = true;
			}

			pfree(wstr);
			pg_regfree(&re);
			return;
		}
		pfree(wstr);

		if ((ofs = strstr(file_pgrole, "\\1")) != NULL)
		{
			/* substitution of the first argument requested */
			if (matches[1].rm_so < 0)
1616 1617
			{
				ereport(LOG,
1618
						(errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
1619
						 errmsg("regular expression \"%s\" has no subexpressions as requested by backreference in \"%s\"",
1620
								file_ident_user + 1, file_pgrole)));
1621 1622 1623 1624
				pg_regfree(&re);
				*error_p = true;
				return;
			}
1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635

			/*
			 * length: original length minus length of \1 plus length of match
			 * plus null terminator
			 */
			regexp_pgrole = palloc0(strlen(file_pgrole) - 2 + (matches[1].rm_eo - matches[1].rm_so) + 1);
			strncpy(regexp_pgrole, file_pgrole, (ofs - file_pgrole));
			memcpy(regexp_pgrole + strlen(regexp_pgrole),
				   ident_user + matches[1].rm_so,
				   matches[1].rm_eo - matches[1].rm_so);
			strcat(regexp_pgrole, ofs + 2);
1636 1637 1638 1639 1640 1641 1642 1643 1644
		}
		else
		{
			/* no substitution, so copy the match */
			regexp_pgrole = pstrdup(file_pgrole);
		}

		pg_regfree(&re);

1645 1646 1647 1648
		/*
		 * now check if the username actually matched what the user is trying
		 * to connect as
		 */
1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661
		if (case_insensitive)
		{
			if (pg_strcasecmp(regexp_pgrole, pg_role) == 0)
				*found_p = true;
		}
		else
		{
			if (strcmp(regexp_pgrole, pg_role) == 0)
				*found_p = true;
		}
		pfree(regexp_pgrole);

		return;
1662 1663 1664
	}
	else
	{
1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677
		/* Not regular expression, so make complete match */
		if (case_insensitive)
		{
			if (pg_strcasecmp(file_pgrole, pg_role) == 0 &&
				pg_strcasecmp(file_ident_user, ident_user) == 0)
				*found_p = true;
		}
		else
		{
			if (strcmp(file_pgrole, pg_role) == 0 &&
				strcmp(file_ident_user, ident_user) == 0)
				*found_p = true;
		}
1678
	}
1679

1680 1681 1682
	return;

ident_syntax:
1683 1684 1685 1686
	ereport(LOG,
			(errcode(ERRCODE_CONFIG_FILE_ERROR),
			 errmsg("missing entry in file \"%s\" at end of line %d",
					IdentFileName, line_number)));
1687 1688 1689 1690 1691
	*error_p = true;
}


/*
1692
 *	Scan the (pre-parsed) ident usermap file line by line, looking for a match
1693
 *
1694
 *	See if the user with ident username "ident_user" is allowed to act
1695
 *	as Postgres user "pgrole" according to usermap "usermap_name".
1696
 *
1697
 *	Special case: Usermap NULL, equivalent to what was previously called
1698
 *	"sameuser" or "samerole", means don't look in the usermap
1699
 *	file.  That's an implied map where "pgrole" must be identical to
1700
 *	"ident_user" in order to be authorized.
1701
 *
1702
 *	Iff authorized, return STATUS_OK, otherwise return STATUS_ERROR.
1703
 */
1704 1705
int
check_usermap(const char *usermap_name,
1706 1707 1708
			  const char *pg_role,
			  const char *auth_user,
			  bool case_insensitive)
1709
{
1710 1711
	bool		found_entry = false,
				error = false;
1712

1713
	if (usermap_name == NULL || usermap_name[0] == '\0')
1714
	{
1715 1716 1717 1718 1719
		if (case_insensitive)
		{
			if (pg_strcasecmp(pg_role, auth_user) == 0)
				return STATUS_OK;
		}
1720 1721
		else
		{
1722 1723 1724 1725 1726 1727 1728
			if (strcmp(pg_role, auth_user) == 0)
				return STATUS_OK;
		}
		ereport(LOG,
				(errmsg("provided username (%s) and authenticated username (%s) don't match",
						auth_user, pg_role)));
		return STATUS_ERROR;
1729 1730 1731
	}
	else
	{
B
Bruce Momjian 已提交
1732 1733
		ListCell   *line_cell,
				   *num_cell;
1734 1735

		forboth(line_cell, ident_lines, num_cell, ident_line_nums)
1736
		{
1737
			parse_ident_usermap(lfirst(line_cell), lfirst_int(num_cell),
1738
						  usermap_name, pg_role, auth_user, case_insensitive,
1739
								&found_entry, &error);
1740 1741 1742 1743
			if (found_entry || error)
				break;
		}
	}
1744 1745 1746
	if (!found_entry && !error)
	{
		ereport(LOG,
1747 1748 1749
		(errmsg("no match in usermap for user \"%s\" authenticated as \"%s\"",
				pg_role, auth_user),
		 errcontext("usermap \"%s\"", usermap_name)));
1750
	}
1751
	return found_entry ? STATUS_OK : STATUS_ERROR;
1752 1753 1754 1755
}


/*
1756
 * Read the ident config file and create a List of Lists of tokens in the file.
1757
 */
B
Bruce Momjian 已提交
1758
void
1759
load_ident(void)
1760
{
1761
	FILE	   *file;
B
Bruce Momjian 已提交
1762

1763 1764
	if (ident_lines || ident_line_nums)
		free_lines(&ident_lines, &ident_line_nums);
1765

1766
	file = AllocateFile(IdentFileName, "r");
1767 1768
	if (file == NULL)
	{
1769
		/* not fatal ... we just won't do any special ident maps */
1770 1771
		ereport(LOG,
				(errcode_for_file_access(),
1772
				 errmsg("could not open Ident usermap file \"%s\": %m",
1773
						IdentFileName)));
1774 1775 1776
	}
	else
	{
1777
		tokenize_file(IdentFileName, file, &ident_lines, &ident_line_nums);
1778 1779 1780 1781 1782
		FreeFile(file);
	}
}


1783

1784
/*
1785 1786 1787
 *	Determine what authentication method should be used when accessing database
 *	"database" from frontend "raddr", user "user".	Return the method and
 *	an optional argument (stored in fields of *port), and STATUS_OK.
1788
 *
1789 1790 1791
 *	Note that STATUS_ERROR indicates a problem with the hba config file.
 *	If the file is OK but does not contain any entry matching the request,
 *	we return STATUS_OK and method = uaReject.
1792
 */
1793
int
1794
hba_getauthmethod(hbaPort *port)
1795
{
1796 1797 1798
	if (check_hba(port))
		return STATUS_OK;
	else
1799
		return STATUS_ERROR;
1800
}