hba.c 39.7 KB
Newer Older
1 2
/*-------------------------------------------------------------------------
 *
3
 * hba.c
4 5 6
 *	  Routines to handle host based authentication (that's the scheme
 *	  wherein you authenticate a user by seeing what IP address the system
 *	  says he comes from and possibly using ident).
7
 *
8
 * Portions Copyright (c) 1996-2006, 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.152 2006/06/20 19:56:52 tgl Exp $
14 15 16
 *
 *-------------------------------------------------------------------------
 */
17 18
#include "postgres.h"

19
#include <ctype.h>
20
#include <pwd.h>
Bruce Momjian's avatar
Bruce Momjian committed
21
#include <fcntl.h>
22
#include <sys/param.h>
23 24 25
#include <sys/socket.h>
#if defined(HAVE_STRUCT_CMSGCRED) || defined(HAVE_STRUCT_FCRED) || defined(HAVE_STRUCT_SOCKCRED)
#include <sys/uio.h>
26 27
#include <sys/ucred.h>
#endif
28 29
#include <netinet/in.h>
#include <arpa/inet.h>
30
#include <unistd.h>
31

32
#include "libpq/libpq.h"
Bruce Momjian's avatar
Bruce Momjian committed
33
#include "miscadmin.h"
34
#include "nodes/pg_list.h"
35
#include "storage/fd.h"
36
#include "utils/flatfiles.h"
37
#include "utils/guc.h"
38

39

40
#define atooid(x)  ((Oid) strtoul((x), NULL, 10))
41
#define atoxid(x)  ((TransactionId) strtoul((x), NULL, 10))
42

43
/* Max size of username ident server can return */
44 45
#define IDENT_USERNAME_MAX 512

Bruce Momjian's avatar
Bruce Momjian committed
46
/* Standard TCP port number for Ident service.	Assigned by IANA */
47 48
#define IDENT_PORT 113

Bruce Momjian's avatar
Bruce Momjian committed
49
/* This is used to separate values in multi-valued column strings */
Bruce Momjian's avatar
Bruce Momjian committed
50
#define MULTI_VALUE_SEP "\001"
Bruce Momjian's avatar
Bruce Momjian committed
51

52 53
#define MAX_TOKEN	256

54 55
/*
 * These variables hold the pre-parsed contents of the hba and ident
56 57
 * configuration files, as well as the flat auth file.
 * Each is a list of sublists, one sublist for
58
 * each (non-empty, non-comment) line of the file.	Each sublist's
59 60 61 62 63
 * first item is an integer line number (so we can give somewhat-useful
 * location info in error messages).  Remaining 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.
 */
64

65
/* pre-parsed content of HBA config file and corresponding line #s */
Bruce Momjian's avatar
Bruce Momjian committed
66 67 68
static List *hba_lines = NIL;
static List *hba_line_nums = NIL;

69
/* pre-parsed content of ident usermap file and corresponding line #s */
Bruce Momjian's avatar
Bruce Momjian committed
70 71 72
static List *ident_lines = NIL;
static List *ident_line_nums = NIL;

73 74 75
/* pre-parsed content of flat auth file and corresponding line #s */
static List *role_lines = NIL;
static List *role_line_nums = NIL;
Bruce Momjian's avatar
Bruce Momjian committed
76 77

/* sorted entries so we can do binary search lookups */
78 79
static List **role_sorted = NULL;		/* sorted role list, for bsearch() */
static int	role_length;
80

81
static void tokenize_file(const char *filename, FILE *file,
82
			  List **lines, List **line_nums);
83
static char *tokenize_inc_file(const char *outer_filename,
84
				  const char *inc_filename);
85

86
/*
87 88
 * isblank() exists in the ISO C99 spec, but it's not very portable yet,
 * so provide our own version.
89
 */
90
static bool
91
pg_isblank(const char c)
92
{
93
	return c == ' ' || c == '\t' || c == '\r';
94 95
}

96

97
/*
98
 * Grab one token out of fp. Tokens are strings of non-blank
99 100 101 102 103 104 105 106 107 108 109 110 111 112
 * characters bounded by blank characters, commas, beginning of line, and
 * end of line. Blank means space or tab. Tokens can be delimited by
 * double quotes (and usually are, in current usage).
 *
 * 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
113
 * database names specially, by appending a newline to them.
114
 */
115
static bool
116
next_token(FILE *fp, char *buf, int bufsz)
117
{
118
	int			c;
119
	char	   *start_buf = buf;
120
	char	   *end_buf = buf + (bufsz - 2);
Bruce Momjian's avatar
Bruce Momjian committed
121 122
	bool		in_quote = false;
	bool		was_quote = false;
Bruce Momjian's avatar
Bruce Momjian committed
123
	bool		saw_quote = false;
124

125 126
	Assert(end_buf > start_buf);

Bruce Momjian's avatar
Bruce Momjian committed
127
	/* Move over initial whitespace and commas */
128
	while ((c = getc(fp)) != EOF && (pg_isblank(c) || c == ','))
129
		;
130

131 132 133
	if (c == EOF || c == '\n')
	{
		*buf = '\0';
134
		return false;
135 136 137
	}

	/*
138 139
	 * Build a token in buf of next characters up to EOF, EOL, unquoted comma,
	 * or unquoted whitespace.
140 141 142
	 */
	while (c != EOF && c != '\n' &&
		   (!pg_isblank(c) || in_quote == true))
143
	{
144 145 146 147 148 149 150 151 152 153 154 155 156
		/* 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)
		{
157
			*buf = '\0';
158 159
			ereport(LOG,
					(errcode(ERRCODE_CONFIG_FILE_ERROR),
160 161
			   errmsg("authentication file token too long, skipping: \"%s\"",
					  start_buf)));
162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181
			/* Discard remainder of line */
			while ((c = getc(fp)) != EOF && c != '\n')
				;
			break;
		}

		if (c != '"' || (c == '"' && was_quote))
			*buf++ = c;

		/* We pass back the comma so the caller knows there is more */
		if ((pg_isblank(c) || c == ',') && !in_quote)
			break;

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

		if (c == '"')
182
		{
183 184
			in_quote = !in_quote;
			saw_quote = true;
185
		}
186

187
		c = getc(fp);
188
	}
189

190
	/*
191 192
	 * 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).
193 194 195 196 197
	 */
	if (c != EOF)
		ungetc(c, fp);

	*buf = '\0';
198

Bruce Momjian's avatar
Bruce Momjian committed
199 200 201
	if (!saw_quote &&
		(strcmp(start_buf, "all") == 0 ||
		 strcmp(start_buf, "sameuser") == 0 ||
202 203
		 strcmp(start_buf, "samegroup") == 0 ||
		 strcmp(start_buf, "samerole") == 0))
204 205 206
	{
		/* append newline to a magical keyword */
		*buf++ = '\n';
207
		*buf = '\0';
208
	}
209 210

	return (saw_quote || buf > start_buf);
211 212
}

Bruce Momjian's avatar
Bruce Momjian committed
213
/*
Bruce Momjian's avatar
Bruce Momjian committed
214 215 216
 *	 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.
217
 *
218
 * The result is a palloc'd string, or NULL if we have reached EOL.
Bruce Momjian's avatar
Bruce Momjian committed
219 220
 */
static char *
221
next_token_expand(const char *filename, FILE *file)
Bruce Momjian's avatar
Bruce Momjian committed
222 223 224
{
	char		buf[MAX_TOKEN];
	char	   *comma_str = pstrdup("");
225
	bool		got_something = false;
Bruce Momjian's avatar
Bruce Momjian committed
226 227
	bool		trailing_comma;
	char	   *incbuf;
228
	int			needed;
Bruce Momjian's avatar
Bruce Momjian committed
229 230 231

	do
	{
232
		if (!next_token(file, buf, sizeof(buf)))
Bruce Momjian's avatar
Bruce Momjian committed
233 234
			break;

235 236 237
		got_something = true;

		if (strlen(buf) > 0 && buf[strlen(buf) - 1] == ',')
Bruce Momjian's avatar
Bruce Momjian committed
238 239
		{
			trailing_comma = true;
Bruce Momjian's avatar
Bruce Momjian committed
240
			buf[strlen(buf) - 1] = '\0';
Bruce Momjian's avatar
Bruce Momjian committed
241 242 243 244 245 246
		}
		else
			trailing_comma = false;

		/* Is this referencing a file? */
		if (buf[0] == '@')
247
			incbuf = tokenize_inc_file(filename, buf + 1);
Bruce Momjian's avatar
Bruce Momjian committed
248 249 250
		else
			incbuf = pstrdup(buf);

251 252 253 254
		needed = strlen(comma_str) + strlen(incbuf) + 1;
		if (trailing_comma)
			needed++;
		comma_str = repalloc(comma_str, needed);
Bruce Momjian's avatar
Bruce Momjian committed
255 256 257
		strcat(comma_str, incbuf);
		if (trailing_comma)
			strcat(comma_str, MULTI_VALUE_SEP);
258
		pfree(incbuf);
Bruce Momjian's avatar
Bruce Momjian committed
259 260
	} while (trailing_comma);

261 262 263 264 265 266
	if (!got_something)
	{
		pfree(comma_str);
		return NULL;
	}

Bruce Momjian's avatar
Bruce Momjian committed
267 268 269
	return comma_str;
}

270

Bruce Momjian's avatar
Bruce Momjian committed
271 272 273
/*
 * Free memory used by lines/tokens (i.e., structure built by tokenize_file)
 */
274
static void
275
free_lines(List **lines, List **line_nums)
276
{
277 278 279 280 281 282
	/*
	 * Either both must be non-NULL, or both must be NULL
	 */
	Assert((*lines != NIL && *line_nums != NIL) ||
		   (*lines == NIL && *line_nums == NIL));

Bruce Momjian's avatar
Bruce Momjian committed
283 284
	if (*lines)
	{
285
		/*
Bruce Momjian's avatar
Bruce Momjian committed
286 287 288 289
		 * "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.
290 291
		 */
		ListCell   *line;
292

Bruce Momjian's avatar
Bruce Momjian committed
293 294 295
		foreach(line, *lines)
		{
			List	   *ln = lfirst(line);
296
			ListCell   *token;
Bruce Momjian's avatar
Bruce Momjian committed
297

298
			foreach(token, ln)
Bruce Momjian's avatar
Bruce Momjian committed
299 300
				pfree(lfirst(token));
			/* free the sublist structure itself */
301
			list_free(ln);
Bruce Momjian's avatar
Bruce Momjian committed
302 303
		}
		/* free the list structure itself */
304
		list_free(*lines);
Bruce Momjian's avatar
Bruce Momjian committed
305 306 307
		/* clear the static variable */
		*lines = NIL;
	}
308 309 310 311 312 313

	if (*line_nums)
	{
		list_free(*line_nums);
		*line_nums = NIL;
	}
Bruce Momjian's avatar
Bruce Momjian committed
314 315 316 317
}


static char *
318 319
tokenize_inc_file(const char *outer_filename,
				  const char *inc_filename)
Bruce Momjian's avatar
Bruce Momjian committed
320 321 322
{
	char	   *inc_fullname;
	FILE	   *inc_file;
Bruce Momjian's avatar
Bruce Momjian committed
323
	List	   *inc_lines;
324 325
	List	   *inc_line_nums;
	ListCell   *line;
326
	char	   *comma_str;
Bruce Momjian's avatar
Bruce Momjian committed
327

328 329 330 331 332 333 334 335 336 337 338 339 340 341 342
	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);
	}
Bruce Momjian's avatar
Bruce Momjian committed
343 344

	inc_file = AllocateFile(inc_fullname, "r");
345
	if (inc_file == NULL)
Bruce Momjian's avatar
Bruce Momjian committed
346
	{
347 348 349 350
		ereport(LOG,
				(errcode_for_file_access(),
				 errmsg("could not open secondary authentication file \"@%s\" as \"%s\": %m",
						inc_filename, inc_fullname)));
Bruce Momjian's avatar
Bruce Momjian committed
351 352
		pfree(inc_fullname);

353 354
		/* return single space, it matches nothing */
		return pstrdup(" ");
Bruce Momjian's avatar
Bruce Momjian committed
355 356 357
	}

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

Bruce Momjian's avatar
Bruce Momjian committed
360
	FreeFile(inc_file);
361
	pfree(inc_fullname);
Bruce Momjian's avatar
Bruce Momjian committed
362

363
	/* Create comma-separated string from List */
364
	comma_str = pstrdup("");
Bruce Momjian's avatar
Bruce Momjian committed
365 366
	foreach(line, inc_lines)
	{
Bruce Momjian's avatar
Bruce Momjian committed
367 368
		List	   *token_list = (List *) lfirst(line);
		ListCell   *token;
Bruce Momjian's avatar
Bruce Momjian committed
369

370
		foreach(token, token_list)
Bruce Momjian's avatar
Bruce Momjian committed
371
		{
372 373
			int			oldlen = strlen(comma_str);
			int			needed;
374 375 376 377 378 379

			needed = oldlen + strlen(lfirst(token)) + 1;
			if (oldlen > 0)
				needed++;
			comma_str = repalloc(comma_str, needed);
			if (oldlen > 0)
Bruce Momjian's avatar
Bruce Momjian committed
380 381 382 383 384
				strcat(comma_str, MULTI_VALUE_SEP);
			strcat(comma_str, lfirst(token));
		}
	}

385
	free_lines(&inc_lines, &inc_line_nums);
Bruce Momjian's avatar
Bruce Momjian committed
386

387 388 389 390 391 392 393
	/* if file is empty, return single space rather than empty string */
	if (strlen(comma_str) == 0)
	{
		pfree(comma_str);
		return pstrdup(" ");
	}

Bruce Momjian's avatar
Bruce Momjian committed
394
	return comma_str;
395 396 397
}


398
/*
399 400 401
 * 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.
402 403
 *
 * filename must be the absolute path to the target file.
404
 */
405
static void
406 407
tokenize_file(const char *filename, FILE *file,
			  List **lines, List **line_nums)
408
{
409
	List	   *current_line = NIL;
410
	int			line_number = 1;
Bruce Momjian's avatar
Bruce Momjian committed
411
	char	   *buf;
412

413 414
	*lines = *line_nums = NIL;

415
	while (!feof(file))
416
	{
417
		buf = next_token_expand(filename, file);
418

Bruce Momjian's avatar
Bruce Momjian committed
419
		/* add token to list, unless we are at EOL or comment start */
420
		if (buf)
421
		{
422 423 424 425 426 427 428 429
			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
430
			{
431 432
				/* append token to current line's list */
				current_line = lappend(current_line, buf);
433 434 435
			}
		}
		else
436
		{
Bruce Momjian's avatar
Bruce Momjian committed
437
			/* we are at real or logical EOL, so force a new line List */
438 439
			current_line = NIL;
			/* Advance line number whenever we reach EOL */
440
			line_number++;
441
		}
442 443 444 445
	}
}

/*
446
 * Compare two lines based on their role/member names.
447 448 449 450
 *
 * Used for bsearch() lookup.
 */
static int
451
role_bsearch_cmp(const void *role, const void *list)
452
{
453
	char	   *role2 = linitial(*(List **) list);
454

455
	return strcmp(role, role2);
456 457 458
}


Bruce Momjian's avatar
Bruce Momjian committed
459
/*
460
 * Lookup a role name in the pg_auth file
Bruce Momjian's avatar
Bruce Momjian committed
461
 */
462
List	  **
463
get_role_line(const char *role)
Bruce Momjian's avatar
Bruce Momjian committed
464
{
465
	/* On some versions of Solaris, bsearch of zero items dumps core */
466
	if (role_length == 0)
467 468
		return NULL;

469 470 471
	return (List **) bsearch((void *) role,
							 (void *) role_sorted,
							 role_length,
Bruce Momjian's avatar
Bruce Momjian committed
472
							 sizeof(List *),
473
							 role_bsearch_cmp);
Bruce Momjian's avatar
Bruce Momjian committed
474 475 476 477
}


/*
478 479 480 481
 * Does user belong to role?
 *
 * user is always the name given as the attempted login identifier.
 * We check to see if it is a member of the specified role name.
Bruce Momjian's avatar
Bruce Momjian committed
482
 */
483
static bool
484
is_member(const char *user, const char *role)
Bruce Momjian's avatar
Bruce Momjian committed
485
{
486
	List	  **line;
487
	ListCell   *line_item;
Bruce Momjian's avatar
Bruce Momjian committed
488

489 490
	if ((line = get_role_line(user)) == NULL)
		return false;			/* if user not exist, say "no" */
491

492 493 494
	/* A user always belongs to its own role */
	if (strcmp(user, role) == 0)
		return true;
495

496
	/*
497 498
	 * skip over the role name, password, valuntil, examine all the membership
	 * entries
499 500 501 502
	 */
	if (list_length(*line) < 4)
		return false;
	for_each_cell(line_item, lnext(lnext(lnext(list_head(*line)))))
503
	{
504
		if (strcmp((char *) lfirst(line_item), role) == 0)
505
			return true;
Bruce Momjian's avatar
Bruce Momjian committed
506
	}
507

508
	return false;
Bruce Momjian's avatar
Bruce Momjian committed
509 510 511
}

/*
512 513 514 515 516
 * 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.
Bruce Momjian's avatar
Bruce Momjian committed
517
 */
518
static bool
519
check_role(const char *role, char *param_str)
Bruce Momjian's avatar
Bruce Momjian committed
520
{
Bruce Momjian's avatar
Bruce Momjian committed
521
	char	   *tok;
Bruce Momjian's avatar
Bruce Momjian committed
522

523 524 525
	for (tok = strtok(param_str, MULTI_VALUE_SEP);
		 tok != NULL;
		 tok = strtok(NULL, MULTI_VALUE_SEP))
Bruce Momjian's avatar
Bruce Momjian committed
526 527
	{
		if (tok[0] == '+')
528
		{
529
			if (is_member(role, tok + 1))
530
				return true;
Bruce Momjian's avatar
Bruce Momjian committed
531
		}
532
		else if (strcmp(tok, role) == 0 ||
533
				 strcmp(tok, "all\n") == 0)
534
			return true;
Bruce Momjian's avatar
Bruce Momjian committed
535
	}
536

537
	return false;
Bruce Momjian's avatar
Bruce Momjian committed
538 539 540
}

/*
541
 * Check to see if db/role combination matches param string.
542 543 544 545
 *
 * 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.
Bruce Momjian's avatar
Bruce Momjian committed
546
 */
547
static bool
548
check_db(const char *dbname, const char *role, char *param_str)
Bruce Momjian's avatar
Bruce Momjian committed
549
{
Bruce Momjian's avatar
Bruce Momjian committed
550
	char	   *tok;
Bruce Momjian's avatar
Bruce Momjian committed
551

552 553 554
	for (tok = strtok(param_str, MULTI_VALUE_SEP);
		 tok != NULL;
		 tok = strtok(NULL, MULTI_VALUE_SEP))
Bruce Momjian's avatar
Bruce Momjian committed
555
	{
556
		if (strcmp(tok, "all\n") == 0)
557
			return true;
558
		else if (strcmp(tok, "sameuser\n") == 0)
Bruce Momjian's avatar
Bruce Momjian committed
559
		{
560
			if (strcmp(dbname, role) == 0)
561
				return true;
562
		}
563 564
		else if (strcmp(tok, "samegroup\n") == 0 ||
				 strcmp(tok, "samerole\n") == 0)
Bruce Momjian's avatar
Bruce Momjian committed
565
		{
566
			if (is_member(role, dbname))
567
				return true;
Bruce Momjian's avatar
Bruce Momjian committed
568 569
		}
		else if (strcmp(tok, dbname) == 0)
570
			return true;
571
	}
572
	return false;
573 574 575 576
}


/*
577
 *	Scan the rest of a host record (after the mask field)
578
 *	and return the interpretation of it as *userauth_p, *auth_arg_p, and
579
 *	*error_p.  *line_item points to the next token of the line, and is
580
 *	advanced over successfully-read tokens.
581 582
 */
static void
583 584
parse_hba_auth(ListCell **line_item, UserAuth *userauth_p,
			   char **auth_arg_p, bool *error_p)
585
{
586
	char	   *token;
587

588 589
	*auth_arg_p = NULL;

590
	if (!*line_item)
591
	{
592 593 594
		*error_p = true;
		return;
	}
595 596

	token = lfirst(*line_item);
597 598 599 600 601 602 603 604 605 606 607 608 609 610
	if (strcmp(token, "trust") == 0)
		*userauth_p = uaTrust;
	else if (strcmp(token, "ident") == 0)
		*userauth_p = uaIdent;
	else if (strcmp(token, "password") == 0)
		*userauth_p = uaPassword;
	else if (strcmp(token, "krb5") == 0)
		*userauth_p = uaKrb5;
	else if (strcmp(token, "reject") == 0)
		*userauth_p = uaReject;
	else if (strcmp(token, "md5") == 0)
		*userauth_p = uaMD5;
	else if (strcmp(token, "crypt") == 0)
		*userauth_p = uaCrypt;
Bruce Momjian's avatar
Bruce Momjian committed
611
#ifdef USE_PAM
612 613
	else if (strcmp(token, "pam") == 0)
		*userauth_p = uaPAM;
614 615 616 617
#endif
#ifdef USE_LDAP
    else if (strcmp(token,"ldap") == 0)
        *userauth_p = uaLDAP;
Bruce Momjian's avatar
Bruce Momjian committed
618
#endif
619 620 621 622
	else
	{
		*error_p = true;
		return;
623
	}
624
	*line_item = lnext(*line_item);
625

626
	/* Get the authentication argument token, if any */
627
	if (*line_item)
628
	{
629
		token = lfirst(*line_item);
630
		*auth_arg_p = pstrdup(token);
631
		*line_item = lnext(*line_item);
632
		/* If there is more on the line, it is an error */
633
		if (*line_item)
634
			*error_p = true;
635
	}
636 637 638
}


639
/*
640
 *	Process one line from the hba config file.
641
 *
642 643 644 645
 *	See if it applies to a connection from a host with IP address port->raddr
 *	to a database named port->database.  If so, return *found_p true
 *	and fill in the auth arguments into the appropriate port fields.
 *	If not, leave *found_p as it was.  If the record has a syntax error,
646
 *	return *error_p true, after issuing a message to the log.  If no error,
647
 *	leave *error_p as it was.
648
 */
649
static void
650 651
parse_hba(List *line, int line_num, hbaPort *port,
		  bool *found_p, bool *error_p)
652
{
Bruce Momjian's avatar
Bruce Momjian committed
653 654
	char	   *token;
	char	   *db;
655
	char	   *role;
656
	struct addrinfo *gai_result;
Bruce Momjian's avatar
Bruce Momjian committed
657
	struct addrinfo hints;
Bruce Momjian's avatar
Bruce Momjian committed
658
	int			ret;
659 660 661
	struct sockaddr_storage addr;
	struct sockaddr_storage mask;
	char	   *cidr_slash;
662
	ListCell   *line_item;
663

664
	line_item = list_head(line);
665
	/* Check the record type. */
666
	token = lfirst(line_item);
667
	if (strcmp(token, "local") == 0)
668 669
	{
		/* Get the database. */
670 671
		line_item = lnext(line_item);
		if (!line_item)
672
			goto hba_syntax;
673
		db = lfirst(line_item);
674

675
		/* Get the role. */
676 677
		line_item = lnext(line_item);
		if (!line_item)
Bruce Momjian's avatar
Bruce Momjian committed
678
			goto hba_syntax;
679
		role = lfirst(line_item);
Bruce Momjian's avatar
Bruce Momjian committed
680

681 682
		line_item = lnext(line_item);
		if (!line_item)
683
			goto hba_syntax;
Bruce Momjian's avatar
Bruce Momjian committed
684 685

		/* Read the rest of the line. */
686 687
		parse_hba_auth(&line_item, &port->auth_method,
					   &port->auth_arg, error_p);
688 689
		if (*error_p)
			goto hba_syntax;
690

691
		/* Disallow auth methods that always need TCP/IP sockets to work */
692
		if (port->auth_method == uaKrb5)
693
			goto hba_syntax;
694

695 696
		/* Does not match if connection isn't AF_UNIX */
		if (!IS_AF_UNIX(port->raddr.addr.ss_family))
697 698
			return;
	}
699
	else if (strcmp(token, "host") == 0
Bruce Momjian's avatar
Bruce Momjian committed
700 701
			 || strcmp(token, "hostssl") == 0
			 || strcmp(token, "hostnossl") == 0)
702
	{
703

Bruce Momjian's avatar
Bruce Momjian committed
704
		if (token[4] == 's')	/* "hostssl" */
705
		{
706 707
#ifdef USE_SSL
			/* Record does not match if we are not on an SSL connection */
708
			if (!port->ssl)
709
				return;
710 711 712 713 714

			/* Placeholder to require specific SSL level, perhaps? */
			/* Or a client certificate */

			/* Since we were on SSL, proceed as with normal 'host' mode */
715
#else
716
			/* We don't accept this keyword at all if no SSL support */
717
			goto hba_syntax;
718
#endif
719
		}
720
#ifdef USE_SSL
Bruce Momjian's avatar
Bruce Momjian committed
721
		else if (token[4] == 'n')		/* "hostnossl" */
722 723 724 725 726 727
		{
			/* Record does not match if we are on an SSL connection */
			if (port->ssl)
				return;
		}
#endif
728 729

		/* Get the database. */
730 731
		line_item = lnext(line_item);
		if (!line_item)
732
			goto hba_syntax;
733
		db = lfirst(line_item);
734

735
		/* Get the role. */
736 737
		line_item = lnext(line_item);
		if (!line_item)
Bruce Momjian's avatar
Bruce Momjian committed
738
			goto hba_syntax;
739
		role = lfirst(line_item);
Bruce Momjian's avatar
Bruce Momjian committed
740

Bruce Momjian's avatar
Bruce Momjian committed
741
		/* Read the IP address field. (with or without CIDR netmask) */
742 743
		line_item = lnext(line_item);
		if (!line_item)
744
			goto hba_syntax;
745
		token = lfirst(line_item);
746

Bruce Momjian's avatar
Bruce Momjian committed
747
		/* Check if it has a CIDR suffix and if so isolate it */
Bruce Momjian's avatar
Bruce Momjian committed
748
		cidr_slash = strchr(token, '/');
Bruce Momjian's avatar
Bruce Momjian committed
749 750
		if (cidr_slash)
			*cidr_slash = '\0';
751

752
		/* Get the IP address either way */
Bruce Momjian's avatar
Bruce Momjian committed
753 754 755 756 757 758 759 760 761
		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;

762
		ret = pg_getaddrinfo_all(token, NULL, &hints, &gai_result);
763
		if (ret || !gai_result)
Bruce Momjian's avatar
Bruce Momjian committed
764
		{
765 766
			ereport(LOG,
					(errcode(ERRCODE_CONFIG_FILE_ERROR),
767 768 769
			   errmsg("invalid IP address \"%s\" in file \"%s\" line %d: %s",
					  token, HbaFileName, line_num,
					  gai_strerror(ret))));
Bruce Momjian's avatar
Bruce Momjian committed
770 771
			if (cidr_slash)
				*cidr_slash = '/';
772
			if (gai_result)
773
				pg_freeaddrinfo_all(hints.ai_family, gai_result);
774
			goto hba_other_error;
Bruce Momjian's avatar
Bruce Momjian committed
775
		}
776

777 778 779
		if (cidr_slash)
			*cidr_slash = '/';

780
		memcpy(&addr, gai_result->ai_addr, gai_result->ai_addrlen);
781
		pg_freeaddrinfo_all(hints.ai_family, gai_result);
782

Bruce Momjian's avatar
Bruce Momjian committed
783 784 785
		/* Get the netmask */
		if (cidr_slash)
		{
786 787
			if (pg_sockaddr_cidr_mask(&mask, cidr_slash + 1,
									  addr.ss_family) < 0)
788
				goto hba_syntax;
Bruce Momjian's avatar
Bruce Momjian committed
789 790 791 792
		}
		else
		{
			/* Read the mask field. */
793 794
			line_item = lnext(line_item);
			if (!line_item)
Bruce Momjian's avatar
Bruce Momjian committed
795
				goto hba_syntax;
796
			token = lfirst(line_item);
Bruce Momjian's avatar
Bruce Momjian committed
797

798
			ret = pg_getaddrinfo_all(token, NULL, &hints, &gai_result);
799 800
			if (ret || !gai_result)
			{
801 802
				ereport(LOG,
						(errcode(ERRCODE_CONFIG_FILE_ERROR),
803 804 805
				  errmsg("invalid IP mask \"%s\" in file \"%s\" line %d: %s",
						 token, HbaFileName, line_num,
						 gai_strerror(ret))));
806
				if (gai_result)
807
					pg_freeaddrinfo_all(hints.ai_family, gai_result);
808
				goto hba_other_error;
809
			}
810

811
			memcpy(&mask, gai_result->ai_addr, gai_result->ai_addrlen);
812
			pg_freeaddrinfo_all(hints.ai_family, gai_result);
Bruce Momjian's avatar
Bruce Momjian committed
813

814
			if (addr.ss_family != mask.ss_family)
815 816 817
			{
				ereport(LOG,
						(errcode(ERRCODE_CONFIG_FILE_ERROR),
Peter Eisentraut's avatar
Peter Eisentraut committed
818
						 errmsg("IP address and mask do not match in file \"%s\" line %d",
819
								HbaFileName, line_num)));
820 821
				goto hba_other_error;
			}
Bruce Momjian's avatar
Bruce Momjian committed
822
		}
823

824
		if (addr.ss_family != port->raddr.addr.ss_family)
825 826
		{
			/*
827 828 829
			 * Wrong address family.  We allow only one case: if the file has
			 * IPv4 and the port is IPv6, promote the file address to IPv6 and
			 * try to match that way.
830 831
			 */
#ifdef HAVE_IPV6
832
			if (addr.ss_family == AF_INET &&
833 834
				port->raddr.addr.ss_family == AF_INET6)
			{
835 836
				pg_promote_v4_to_v6_addr(&addr);
				pg_promote_v4_to_v6_mask(&mask);
837 838
			}
			else
Bruce Momjian's avatar
Bruce Momjian committed
839
#endif   /* HAVE_IPV6 */
840
			{
841
				/* Line doesn't match client port, so ignore it. */
842 843 844 845
				return;
			}
		}

846
		/* Ignore line if client port is not in the matching addr range. */
847
		if (!pg_range_sockaddr(&port->raddr.addr, &addr, &mask))
848 849
			return;

850
		/* Read the rest of the line. */
851 852
		line_item = lnext(line_item);
		if (!line_item)
853
			goto hba_syntax;
854 855
		parse_hba_auth(&line_item, &port->auth_method,
					   &port->auth_arg, error_p);
856
		if (*error_p)
857
			goto hba_syntax;
858
	}
859
	else
860
		goto hba_syntax;
861

862
	/* Does the entry match database and role? */
863
	if (!check_db(port->database_name, port->user_name, db))
Bruce Momjian's avatar
Bruce Momjian committed
864
		return;
865
	if (!check_role(port->user_name, role))
Bruce Momjian's avatar
Bruce Momjian committed
866 867
		return;

868 869
	/* Success */
	*found_p = true;
870 871
	return;

872
hba_syntax:
873
	if (line_item)
874 875
		ereport(LOG,
				(errcode(ERRCODE_CONFIG_FILE_ERROR),
876 877 878
			  errmsg("invalid entry in file \"%s\" at line %d, token \"%s\"",
					 HbaFileName, line_num,
					 (char *) lfirst(line_item))));
879 880 881
	else
		ereport(LOG,
				(errcode(ERRCODE_CONFIG_FILE_ERROR),
882 883
				 errmsg("missing field in file \"%s\" at end of line %d",
						HbaFileName, line_num)));
884

885 886
	/* Come here if suitable message already logged */
hba_other_error:
887
	*error_p = true;
888 889 890
}


891
/*
892
 *	Scan the (pre-parsed) hba file line by line, looking for a match
893
 *	to the port's connection request.
894 895 896
 */
static bool
check_hba(hbaPort *port)
897
{
898 899
	bool		found_entry = false;
	bool		error = false;
900 901
	ListCell   *line;
	ListCell   *line_num;
Bruce Momjian's avatar
Bruce Momjian committed
902

903
	forboth(line, hba_lines, line_num, hba_line_nums)
904
	{
905 906
		parse_hba(lfirst(line), lfirst_int(line_num),
				  port, &found_entry, &error);
907 908
		if (found_entry || error)
			break;
909
	}
910

911 912
	if (!error)
	{
913
		/* If no matching entry was found, synthesize 'reject' entry. */
914
		if (!found_entry)
915
			port->auth_method = uaReject;
916
		return true;
917
	}
918 919
	else
		return false;
920
}
921 922


Bruce Momjian's avatar
Bruce Momjian committed
923
/*
924
 *	 Load role/password mapping file
Bruce Momjian's avatar
Bruce Momjian committed
925 926
 */
void
927
load_role(void)
Bruce Momjian's avatar
Bruce Momjian committed
928
{
929
	char	   *filename;
930
	FILE	   *role_file;
Bruce Momjian's avatar
Bruce Momjian committed
931

932
	/* Discard any old data */
933 934
	if (role_lines || role_line_nums)
		free_lines(&role_lines, &role_line_nums);
935
	if (role_sorted)
936 937 938
		pfree(role_sorted);
	role_sorted = NULL;
	role_length = 0;
Bruce Momjian's avatar
Bruce Momjian committed
939

940
	/* Read in the file contents */
941 942
	filename = auth_getflatfilename();
	role_file = AllocateFile(filename, "r");
943

944
	if (role_file == NULL)
945 946 947 948 949 950 951
	{
		/* no complaint if not there */
		if (errno != ENOENT)
			ereport(LOG,
					(errcode_for_file_access(),
					 errmsg("could not open file \"%s\": %m", filename)));
		pfree(filename);
Bruce Momjian's avatar
Bruce Momjian committed
952
		return;
953 954
	}

955
	tokenize_file(filename, role_file, &role_lines, &role_line_nums);
956

957
	FreeFile(role_file);
958
	pfree(filename);
Bruce Momjian's avatar
Bruce Momjian committed
959

960 961 962
	/* create array for binary searching */
	role_length = list_length(role_lines);
	if (role_length)
Bruce Momjian's avatar
Bruce Momjian committed
963
	{
964 965
		int			i = 0;
		ListCell   *line;
Bruce Momjian's avatar
Bruce Momjian committed
966

Bruce Momjian's avatar
Bruce Momjian committed
967
		/* We assume the flat file was written already-sorted */
968 969 970
		role_sorted = palloc(role_length * sizeof(List *));
		foreach(line, role_lines)
			role_sorted[i++] = lfirst(line);
Bruce Momjian's avatar
Bruce Momjian committed
971 972 973 974
	}
}


Marc G. Fournier's avatar
Marc G. Fournier committed
975
/*
976
 * Read the config file and create a List of Lists of tokens in the file.
Marc G. Fournier's avatar
Marc G. Fournier committed
977
 */
Bruce Momjian's avatar
Bruce Momjian committed
978
void
979
load_hba(void)
980
{
981
	FILE	   *file;
982

983 984
	if (hba_lines || hba_line_nums)
		free_lines(&hba_lines, &hba_line_nums);
985

986 987
	file = AllocateFile(HbaFileName, "r");
	/* Failure is fatal since with no HBA entries we can do nothing... */
Bruce Momjian's avatar
Bruce Momjian committed
988
	if (file == NULL)
989 990
		ereport(FATAL,
				(errcode_for_file_access(),
991
				 errmsg("could not open configuration file \"%s\": %m",
992
						HbaFileName)));
993

994
	tokenize_file(HbaFileName, file, &hba_lines, &hba_line_nums);
995
	FreeFile(file);
996 997
}

998 999 1000 1001 1002 1003 1004 1005 1006
/*
 * Read and parse one line from the flat pg_database file.
 *
 * Returns TRUE on success, FALSE if EOF; bad data causes elog(FATAL).
 *
 * Output parameters:
 *	dbname: gets database name (must be of size NAMEDATALEN bytes)
 *	dboid: gets database OID
 *	dbtablespace: gets database's default tablespace's OID
1007
 *	dbfrozenxid: gets database's frozen XID
1008
 *	dbvacuumxid: gets database's vacuum XID
1009 1010 1011 1012 1013
 *
 * This is not much related to the other functions in hba.c, but we put it
 * here because it uses the next_token() infrastructure.
 */
bool
1014
read_pg_database_line(FILE *fp, char *dbname, Oid *dboid,
1015 1016
					  Oid *dbtablespace, TransactionId *dbfrozenxid,
					  TransactionId *dbvacuumxid)
1017 1018 1019 1020 1021
{
	char		buf[MAX_TOKEN];

	if (feof(fp))
		return false;
1022
	if (!next_token(fp, buf, sizeof(buf)))
1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037
		return false;
	if (strlen(buf) >= NAMEDATALEN)
		elog(FATAL, "bad data in flat pg_database file");
	strcpy(dbname, buf);
	next_token(fp, buf, sizeof(buf));
	if (!isdigit((unsigned char) buf[0]))
		elog(FATAL, "bad data in flat pg_database file");
	*dboid = atooid(buf);
	next_token(fp, buf, sizeof(buf));
	if (!isdigit((unsigned char) buf[0]))
		elog(FATAL, "bad data in flat pg_database file");
	*dbtablespace = atooid(buf);
	next_token(fp, buf, sizeof(buf));
	if (!isdigit((unsigned char) buf[0]))
		elog(FATAL, "bad data in flat pg_database file");
1038
	*dbfrozenxid = atoxid(buf);
1039 1040 1041 1042
	next_token(fp, buf, sizeof(buf));
	if (!isdigit((unsigned char) buf[0]))
		elog(FATAL, "bad data in flat pg_database file");
	*dbvacuumxid = atoxid(buf);
1043
	/* expect EOL next */
1044
	if (next_token(fp, buf, sizeof(buf)))
1045 1046 1047
		elog(FATAL, "bad data in flat pg_database file");
	return true;
}
1048

1049
/*
1050 1051
 *	Process one line from the ident config file.
 *
1052
 *	Take the line and compare it to the needed map, pg_role and ident_user.
1053
 *	*found_p and *error_p are set according to our results.
1054
 */
1055
static void
1056
parse_ident_usermap(List *line, int line_number, const char *usermap_name,
1057
					const char *pg_role, const char *ident_user,
1058
					bool *found_p, bool *error_p)
1059
{
1060
	ListCell   *line_item;
1061 1062
	char	   *token;
	char	   *file_map;
1063
	char	   *file_pgrole;
1064
	char	   *file_ident_user;
1065 1066

	*found_p = false;
1067
	*error_p = false;
1068 1069

	Assert(line != NIL);
1070
	line_item = list_head(line);
1071 1072

	/* Get the map token (must exist) */
1073
	token = lfirst(line_item);
1074 1075
	file_map = token;

1076
	/* Get the ident user token */
1077
	line_item = lnext(line_item);
1078
	if (!line_item)
1079
		goto ident_syntax;
1080
	token = lfirst(line_item);
1081
	file_ident_user = token;
1082

1083
	/* Get the PG rolename token */
1084
	line_item = lnext(line_item);
1085
	if (!line_item)
1086
		goto ident_syntax;
1087
	token = lfirst(line_item);
1088
	file_pgrole = token;
1089 1090 1091

	/* Match? */
	if (strcmp(file_map, usermap_name) == 0 &&
1092
		strcmp(file_pgrole, pg_role) == 0 &&
1093 1094
		strcmp(file_ident_user, ident_user) == 0)
		*found_p = true;
1095

1096 1097 1098
	return;

ident_syntax:
1099 1100 1101 1102
	ereport(LOG,
			(errcode(ERRCODE_CONFIG_FILE_ERROR),
			 errmsg("missing entry in file \"%s\" at end of line %d",
					IdentFileName, line_number)));
1103 1104 1105 1106 1107
	*error_p = true;
}


/*
1108
 *	Scan the (pre-parsed) ident usermap file line by line, looking for a match
1109
 *
1110
 *	See if the user with ident username "ident_user" is allowed to act
1111
 *	as Postgres user "pgrole" according to usermap "usermap_name".
1112
 *
1113 1114
 *	Special case: For usermap "samerole", don't look in the usermap
 *	file.  That's an implied map where "pgrole" must be identical to
1115
 *	"ident_user" in order to be authorized.
1116
 *
1117
 *	Iff authorized, return true.
1118 1119 1120
 */
static bool
check_ident_usermap(const char *usermap_name,
1121
					const char *pg_role,
1122 1123
					const char *ident_user)
{
1124 1125
	bool		found_entry = false,
				error = false;
1126

1127
	if (usermap_name == NULL || usermap_name[0] == '\0')
1128
	{
1129 1130
		ereport(LOG,
				(errcode(ERRCODE_CONFIG_FILE_ERROR),
1131
		   errmsg("cannot use Ident authentication without usermap field")));
1132 1133
		found_entry = false;
	}
1134 1135
	else if (strcmp(usermap_name, "sameuser\n") == 0 ||
			 strcmp(usermap_name, "samerole\n") == 0)
1136
	{
1137
		if (strcmp(pg_role, ident_user) == 0)
1138 1139 1140 1141 1142 1143
			found_entry = true;
		else
			found_entry = false;
	}
	else
	{
Bruce Momjian's avatar
Bruce Momjian committed
1144 1145
		ListCell   *line_cell,
				   *num_cell;
1146 1147

		forboth(line_cell, ident_lines, num_cell, ident_line_nums)
1148
		{
1149
			parse_ident_usermap(lfirst(line_cell), lfirst_int(num_cell),
1150
								usermap_name, pg_role, ident_user,
1151
								&found_entry, &error);
1152 1153 1154 1155 1156 1157 1158 1159 1160
			if (found_entry || error)
				break;
		}
	}
	return found_entry;
}


/*
1161
 * Read the ident config file and create a List of Lists of tokens in the file.
1162
 */
Bruce Momjian's avatar
Bruce Momjian committed
1163
void
1164
load_ident(void)
1165
{
1166
	FILE	   *file;
Bruce Momjian's avatar
Bruce Momjian committed
1167

1168 1169
	if (ident_lines || ident_line_nums)
		free_lines(&ident_lines, &ident_line_nums);
1170

1171
	file = AllocateFile(IdentFileName, "r");
1172 1173
	if (file == NULL)
	{
1174
		/* not fatal ... we just won't do any special ident maps */
1175 1176
		ereport(LOG,
				(errcode_for_file_access(),
1177
				 errmsg("could not open Ident usermap file \"%s\": %m",
1178
						IdentFileName)));
1179 1180 1181
	}
	else
	{
1182
		tokenize_file(IdentFileName, file, &ident_lines, &ident_line_nums);
1183 1184 1185 1186 1187 1188
		FreeFile(file);
	}
}


/*
1189
 *	Parse the string "*ident_response" as a response from a query to an Ident
Bruce Momjian's avatar
Bruce Momjian committed
1190
 *	server.  If it's a normal response indicating a user name, return true
Bruce Momjian's avatar
Bruce Momjian committed
1191
 *	and store the user name at *ident_user. If it's anything else,
1192
 *	return false.
1193 1194
 */
static bool
1195
interpret_ident_response(const char *ident_response,
1196 1197
						 char *ident_user)
{
1198
	const char *cursor = ident_response;		/* Cursor into *ident_response */
1199 1200

	/*
1201
	 * Ident's response, in the telnet tradition, should end in crlf (\r\n).
1202 1203
	 */
	if (strlen(ident_response) < 2)
1204
		return false;
1205
	else if (ident_response[strlen(ident_response) - 2] != '\r')
1206
		return false;
1207 1208 1209 1210 1211 1212
	else
	{
		while (*cursor != ':' && *cursor != '\r')
			cursor++;			/* skip port field */

		if (*cursor != ':')
1213
			return false;
1214 1215 1216
		else
		{
			/* We're positioned to colon before response type field */
1217
			char		response_type[80];
1218
			int			i;		/* Index into *response_type */
1219 1220

			cursor++;			/* Go over colon */
1221
			while (pg_isblank(*cursor))
1222 1223
				cursor++;		/* skip blanks */
			i = 0;
1224
			while (*cursor != ':' && *cursor != '\r' && !pg_isblank(*cursor) &&
1225
				   i < (int) (sizeof(response_type) - 1))
1226 1227
				response_type[i++] = *cursor++;
			response_type[i] = '\0';
1228
			while (pg_isblank(*cursor))
1229 1230
				cursor++;		/* skip blanks */
			if (strcmp(response_type, "USERID") != 0)
1231
				return false;
1232 1233 1234
			else
			{
				/*
1235 1236
				 * It's a USERID response.  Good.  "cursor" should be pointing
				 * to the colon that precedes the operating system type.
1237 1238
				 */
				if (*cursor != ':')
1239
					return false;
1240 1241 1242 1243 1244 1245 1246
				else
				{
					cursor++;	/* Go over colon */
					/* Skip over operating system field. */
					while (*cursor != ':' && *cursor != '\r')
						cursor++;
					if (*cursor != ':')
1247
						return false;
1248 1249
					else
					{
1250
						int			i;	/* Index into *ident_user */
1251 1252

						cursor++;		/* Go over colon */
1253
						while (pg_isblank(*cursor))
1254
							cursor++;	/* skip blanks */
Bruce Momjian's avatar
Bruce Momjian committed
1255
						/* Rest of line is user name.  Copy it over. */
1256 1257
						i = 0;
						while (*cursor != '\r' && i < IDENT_USERNAME_MAX)
1258 1259 1260
							ident_user[i++] = *cursor++;
						ident_user[i] = '\0';
						return true;
1261 1262 1263 1264 1265
					}
				}
			}
		}
	}
1266 1267 1268
}


1269
/*
1270 1271
 *	Talk to the ident server on host "remote_ip_addr" and find out who
 *	owns the tcp connection from his port "remote_port" to port
Bruce Momjian's avatar
Bruce Momjian committed
1272
 *	"local_port_addr" on host "local_ip_addr".	Return the user name the
1273
 *	ident server gives as "*ident_user".
1274
 *
1275
 *	IP addresses and port numbers are in network byte order.
1276
 *
1277
 *	But iff we're unable to get the information from ident, return false.
1278
 */
1279
static bool
Bruce Momjian's avatar
Bruce Momjian committed
1280 1281
ident_inet(const SockAddr remote_addr,
		   const SockAddr local_addr,
1282
		   char *ident_user)
1283
{
1284 1285 1286
	int			sock_fd,		/* File descriptor for socket on which we talk
								 * to Ident */
				rc;				/* Return code from a locally called function */
1287
	bool		ident_return;
Bruce Momjian's avatar
Bruce Momjian committed
1288 1289 1290 1291 1292
	char		remote_addr_s[NI_MAXHOST];
	char		remote_port[NI_MAXSERV];
	char		local_addr_s[NI_MAXHOST];
	char		local_port[NI_MAXSERV];
	char		ident_port[NI_MAXSERV];
1293 1294
	char		ident_query[80];
	char		ident_response[80 + IDENT_USERNAME_MAX];
Bruce Momjian's avatar
Bruce Momjian committed
1295 1296 1297
	struct addrinfo *ident_serv = NULL,
			   *la = NULL,
				hints;
Bruce Momjian's avatar
Bruce Momjian committed
1298

1299
	/*
1300 1301
	 * Might look a little weird to first convert it to text and then back to
	 * sockaddr, but it's protocol independent.
1302
	 */
1303 1304 1305 1306 1307 1308 1309 1310
	pg_getnameinfo_all(&remote_addr.addr, remote_addr.salen,
					   remote_addr_s, sizeof(remote_addr_s),
					   remote_port, sizeof(remote_port),
					   NI_NUMERICHOST | NI_NUMERICSERV);
	pg_getnameinfo_all(&local_addr.addr, local_addr.salen,
					   local_addr_s, sizeof(local_addr_s),
					   local_port, sizeof(local_port),
					   NI_NUMERICHOST | NI_NUMERICSERV);
Bruce Momjian's avatar
Bruce Momjian committed
1311 1312 1313 1314 1315 1316 1317 1318 1319 1320

	snprintf(ident_port, sizeof(ident_port), "%d", IDENT_PORT);
	hints.ai_flags = AI_NUMERICHOST;
	hints.ai_family = remote_addr.addr.ss_family;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_protocol = 0;
	hints.ai_addrlen = 0;
	hints.ai_canonname = NULL;
	hints.ai_addr = NULL;
	hints.ai_next = NULL;
1321
	rc = pg_getaddrinfo_all(remote_addr_s, ident_port, &hints, &ident_serv);
Bruce Momjian's avatar
Bruce Momjian committed
1322 1323
	if (rc || !ident_serv)
	{
1324
		if (ident_serv)
1325
			pg_freeaddrinfo_all(hints.ai_family, ident_serv);
1326
		return false;			/* we don't expect this to happen */
1327
	}
1328 1329 1330 1331 1332 1333 1334 1335 1336

	hints.ai_flags = AI_NUMERICHOST;
	hints.ai_family = local_addr.addr.ss_family;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_protocol = 0;
	hints.ai_addrlen = 0;
	hints.ai_canonname = NULL;
	hints.ai_addr = NULL;
	hints.ai_next = NULL;
1337
	rc = pg_getaddrinfo_all(local_addr_s, NULL, &hints, &la);
Bruce Momjian's avatar
Bruce Momjian committed
1338 1339
	if (rc || !la)
	{
1340
		if (la)
1341
			pg_freeaddrinfo_all(hints.ai_family, la);
1342
		return false;			/* we don't expect this to happen */
1343
	}
Bruce Momjian's avatar
Bruce Momjian committed
1344

Bruce Momjian's avatar
Bruce Momjian committed
1345
	sock_fd = socket(ident_serv->ai_family, ident_serv->ai_socktype,
1346 1347
					 ident_serv->ai_protocol);
	if (sock_fd < 0)
1348
	{
1349 1350
		ereport(LOG,
				(errcode_for_socket_access(),
1351
				 errmsg("could not create socket for Ident connection: %m")));
1352
		ident_return = false;
1353
		goto ident_inet_done;
1354
	}
Bruce Momjian's avatar
Bruce Momjian committed
1355

1356
	/*
1357 1358 1359
	 * Bind to the address which the client originally contacted, otherwise
	 * the ident server won't be able to match up the right connection. This
	 * is necessary if the PostgreSQL server is running on an IP alias.
1360 1361 1362
	 */
	rc = bind(sock_fd, la->ai_addr, la->ai_addrlen);
	if (rc != 0)
1363
	{
1364 1365 1366 1367 1368 1369 1370
		ereport(LOG,
				(errcode_for_socket_access(),
				 errmsg("could not bind to local address \"%s\": %m",
						local_addr_s)));
		ident_return = false;
		goto ident_inet_done;
	}
Bruce Momjian's avatar
Bruce Momjian committed
1371

Bruce Momjian's avatar
Bruce Momjian committed
1372
	rc = connect(sock_fd, ident_serv->ai_addr,
1373 1374 1375 1376 1377
				 ident_serv->ai_addrlen);
	if (rc != 0)
	{
		ereport(LOG,
				(errcode_for_socket_access(),
1378
				 errmsg("could not connect to Ident server at address \"%s\", port %s: %m",
1379 1380 1381 1382
						remote_addr_s, ident_port)));
		ident_return = false;
		goto ident_inet_done;
	}
1383

1384 1385 1386
	/* The query we send to the Ident server */
	snprintf(ident_query, sizeof(ident_query), "%s,%s\r\n",
			 remote_port, local_port);
1387

1388 1389 1390 1391 1392
	/* loop in case send is interrupted */
	do
	{
		rc = send(sock_fd, ident_query, strlen(ident_query), 0);
	} while (rc < 0 && errno == EINTR);
1393

1394 1395 1396 1397
	if (rc < 0)
	{
		ereport(LOG,
				(errcode_for_socket_access(),
1398
				 errmsg("could not send query to Ident server at address \"%s\", port %s: %m",
1399 1400 1401 1402
						remote_addr_s, ident_port)));
		ident_return = false;
		goto ident_inet_done;
	}
1403

1404 1405 1406 1407 1408 1409 1410 1411 1412
	do
	{
		rc = recv(sock_fd, ident_response, sizeof(ident_response) - 1, 0);
	} while (rc < 0 && errno == EINTR);

	if (rc < 0)
	{
		ereport(LOG,
				(errcode_for_socket_access(),
1413
				 errmsg("could not receive response from Ident server at address \"%s\", port %s: %m",
1414 1415 1416
						remote_addr_s, ident_port)));
		ident_return = false;
		goto ident_inet_done;
1417
	}
1418 1419 1420

	ident_response[rc] = '\0';
	ident_return = interpret_ident_response(ident_response, ident_user);
1421 1422
	if (!ident_return)
		ereport(LOG,
1423 1424
			(errmsg("invalidly formatted response from Ident server: \"%s\"",
					ident_response)));
1425 1426 1427 1428

ident_inet_done:
	if (sock_fd >= 0)
		closesocket(sock_fd);
1429 1430
	pg_freeaddrinfo_all(remote_addr.addr.ss_family, ident_serv);
	pg_freeaddrinfo_all(local_addr.addr.ss_family, la);
1431
	return ident_return;
1432 1433
}

1434
/*
1435 1436
 *	Ask kernel about the credentials of the connecting process and
 *	determine the symbolic name of the corresponding user.
1437
 *
1438 1439
 *	Returns either true and the username put into "ident_user",
 *	or false if we were unable to determine the username.
1440
 */
1441 1442
#ifdef HAVE_UNIX_SOCKETS

1443 1444 1445
static bool
ident_unix(int sock, char *ident_user)
{
1446 1447
#if defined(HAVE_GETPEEREID)
	/* OpenBSD style:  */
Bruce Momjian's avatar
Bruce Momjian committed
1448 1449
	uid_t		uid;
	gid_t		gid;
1450 1451 1452
	struct passwd *pass;

	errno = 0;
Bruce Momjian's avatar
Bruce Momjian committed
1453
	if (getpeereid(sock, &uid, &gid) != 0)
1454 1455
	{
		/* We didn't get a valid credentials struct. */
1456 1457
		ereport(LOG,
				(errcode_for_socket_access(),
1458
				 errmsg("could not get peer credentials: %m")));
1459 1460 1461 1462 1463 1464 1465
		return false;
	}

	pass = getpwuid(uid);

	if (pass == NULL)
	{
1466
		ereport(LOG,
1467
				(errmsg("local user with ID %d does not exist",
1468
						(int) uid)));
1469 1470 1471 1472 1473 1474
		return false;
	}

	StrNCpy(ident_user, pass->pw_name, IDENT_USERNAME_MAX + 1);

	return true;
1475
#elif defined(SO_PEERCRED)
1476
	/* Linux style: use getsockopt(SO_PEERCRED) */
1477
	struct ucred peercred;
1478
	ACCEPT_TYPE_ARG3 so_len = sizeof(peercred);
1479 1480 1481 1482 1483 1484 1485
	struct passwd *pass;

	errno = 0;
	if (getsockopt(sock, SOL_SOCKET, SO_PEERCRED, &peercred, &so_len) != 0 ||
		so_len != sizeof(peercred))
	{
		/* We didn't get a valid credentials struct. */
1486 1487
		ereport(LOG,
				(errcode_for_socket_access(),
1488
				 errmsg("could not get peer credentials: %m")));
1489 1490 1491 1492 1493 1494 1495
		return false;
	}

	pass = getpwuid(peercred.uid);

	if (pass == NULL)
	{
1496
		ereport(LOG,
1497
				(errmsg("local user with ID %d does not exist",
1498
						(int) peercred.uid)));
1499 1500 1501
		return false;
	}

1502
	StrNCpy(ident_user, pass->pw_name, IDENT_USERNAME_MAX + 1);
1503 1504

	return true;
1505
#elif defined(HAVE_STRUCT_CMSGCRED) || defined(HAVE_STRUCT_FCRED) || (defined(HAVE_STRUCT_SOCKCRED) && defined(LOCAL_CREDS))
1506 1507 1508
	struct msghdr msg;

/* Credentials structure */
1509
#if defined(HAVE_STRUCT_CMSGCRED)
1510
	typedef struct cmsgcred Cred;
1511

1512
#define cruid cmcred_uid
1513
#elif defined(HAVE_STRUCT_FCRED)
1514
	typedef struct fcred Cred;
1515

1516
#define cruid fc_uid
1517
#elif defined(HAVE_STRUCT_SOCKCRED)
1518
	typedef struct sockcred Cred;
1519

1520
#define cruid sc_uid
1521
#endif
1522
	Cred	   *cred;
1523 1524

	/* Compute size without padding */
1525 1526
	char		cmsgmem[ALIGN(sizeof(struct cmsghdr)) + ALIGN(sizeof(Cred))];	/* for NetBSD */

1527
	/* Point to start of first structure */
1528
	struct cmsghdr *cmsg = (struct cmsghdr *) cmsgmem;
1529 1530

	struct iovec iov;
1531
	char		buf;
1532 1533 1534 1535 1536
	struct passwd *pw;

	memset(&msg, 0, sizeof(msg));
	msg.msg_iov = &iov;
	msg.msg_iovlen = 1;
1537
	msg.msg_control = (char *) cmsg;
1538 1539 1540 1541
	msg.msg_controllen = sizeof(cmsgmem);
	memset(cmsg, 0, sizeof(cmsgmem));

	/*
1542
	 * The one character which is received here is not meaningful; its
1543 1544
	 * purposes is only to make sure that recvmsg() blocks long enough for the
	 * other side to send its credentials.
1545 1546 1547 1548 1549 1550 1551 1552
	 */
	iov.iov_base = &buf;
	iov.iov_len = 1;

	if (recvmsg(sock, &msg, 0) < 0 ||
		cmsg->cmsg_len < sizeof(cmsgmem) ||
		cmsg->cmsg_type != SCM_CREDS)
	{
1553 1554
		ereport(LOG,
				(errcode_for_socket_access(),
1555
				 errmsg("could not get peer credentials: %m")));
1556 1557 1558
		return false;
	}

1559
	cred = (Cred *) CMSG_DATA(cmsg);
1560

1561
	pw = getpwuid(cred->cruid);
1562

1563 1564
	if (pw == NULL)
	{
1565
		ereport(LOG,
1566
				(errmsg("local user with ID %d does not exist",
1567
						(int) cred->cruid)));
1568 1569 1570
		return false;
	}

1571
	StrNCpy(ident_user, pw->pw_name, IDENT_USERNAME_MAX + 1);
1572 1573 1574

	return true;
#else
1575 1576
	ereport(LOG,
			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
1577
			 errmsg("Ident authentication is not supported on local connections on this platform")));
1578

1579
	return false;
1580
#endif
1581
}
Bruce Momjian's avatar
Bruce Momjian committed
1582
#endif   /* HAVE_UNIX_SOCKETS */
Bruce Momjian's avatar
Bruce Momjian committed
1583 1584


1585
/*
1586 1587 1588 1589
 *	Determine the username of the initiator of the connection described
 *	by "port".	Then look in the usermap file under the usermap
 *	port->auth_arg and see if that user is equivalent to Postgres user
 *	port->user.
1590
 *
1591
 *	Return STATUS_OK if yes, STATUS_ERROR if no match (or couldn't get info).
1592 1593
 */
int
1594
authident(hbaPort *port)
1595
{
1596
	char		ident_user[IDENT_USERNAME_MAX + 1];
1597

Bruce Momjian's avatar
Bruce Momjian committed
1598
	switch (port->raddr.addr.ss_family)
1599 1600
	{
		case AF_INET:
Bruce Momjian's avatar
Bruce Momjian committed
1601 1602 1603 1604
#ifdef	HAVE_IPV6
		case AF_INET6:
#endif
			if (!ident_inet(port->raddr, port->laddr, ident_user))
1605 1606
				return STATUS_ERROR;
			break;
1607 1608

#ifdef HAVE_UNIX_SOCKETS
1609 1610 1611 1612
		case AF_UNIX:
			if (!ident_unix(port->sock, ident_user))
				return STATUS_ERROR;
			break;
1613 1614
#endif

1615 1616 1617
		default:
			return STATUS_ERROR;
	}
1618

1619
	ereport(DEBUG2,
Peter Eisentraut's avatar
Peter Eisentraut committed
1620
			(errmsg("Ident protocol identifies remote user as \"%s\"",
1621 1622
					ident_user)));

1623
	if (check_ident_usermap(port->auth_arg, port->user_name, ident_user))
1624
		return STATUS_OK;
1625
	else
1626
		return STATUS_ERROR;
1627 1628 1629
}


1630
/*
1631 1632 1633
 *	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.
1634
 *
1635 1636 1637
 *	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.
1638
 */
1639
int
1640
hba_getauthmethod(hbaPort *port)
1641
{
1642 1643 1644
	if (check_hba(port))
		return STATUS_OK;
	else
1645
		return STATUS_ERROR;
1646
}