1 /*-------------------------------------------------------------------------
4 * Routines to handle host based authentication (that's the scheme
5 * wherein you authenticate a user by seeing what IP address the system
6 * says he comes from and possibly using ident).
10 * $Header: /cvsroot/pgsql/src/backend/libpq/hba.c,v 1.16 1997/03/12 21:17:53 scrappy Exp $
12 *-------------------------------------------------------------------------
19 #include <sys/socket.h>
20 #include <sys/types.h> /* needed by in.h on Ultrix */
21 #include <netinet/in.h>
22 #include <arpa/inet.h>
26 #include <miscadmin.h>
27 #include <libpq/libpq.h>
28 #include <libpq/pqcomm.h>
29 #include <libpq/hba.h>
30 #include <port/inet_aton.h> /* For inet_aton() */
33 /* Some standard C libraries, including GNU, have an isblank() function.
34 Others, including Solaris, do not. So we have our own.
37 isblank(const char c) {
38 return(c == ' ' || c == 9 /* tab */);
44 next_token(FILE *fp, char *buf, const int bufsz) {
45 /*--------------------------------------------------------------------------
46 Grab one token out of fp. Tokens are strings of non-blank
47 characters bounded by blank characters, beginning of line, and end
48 of line. Blank means space or tab. Return the token as *buf.
49 Leave file positioned to character immediately after the token or
50 EOF, whichever comes first. If no more tokens on line, return null
51 string as *buf and position file to beginning of next line or EOF,
52 whichever comes first.
53 --------------------------------------------------------------------------*/
55 char *eb = buf+(bufsz-1);
57 /* Move over inital token-delimiting blanks */
58 while (isblank(c = getc(fp))) ;
61 /* build a token in buf of next characters up to EOF, eol, or blank. */
62 while (c != EOF && c != '\n' && !isblank(c)) {
63 if (buf < eb) *buf++ = c;
65 /* Put back the char right after the token (putting back EOF is ok) */
75 read_through_eol(FILE *file) {
79 while (c != '\n' && c != EOF);
85 read_hba_entry2(FILE *file, enum Userauth *userauth_p, char usermap_name[],
86 bool *error_p, bool *matches_p, bool find_password_entries) {
87 /*--------------------------------------------------------------------------
88 Read from file FILE the rest of a host record, after the mask field,
89 and return the interpretation of it as *userauth_p, usermap_name, and
91 ---------------------------------------------------------------------------*/
96 /* Get authentication type token. */
97 next_token(file, buf, sizeof(buf));
98 userauth_valid = false;
102 userauth_valid = true;
103 if(strcmp(buf, "trust") == 0) {
105 } else if(strcmp(buf, "ident") == 0) {
107 } else if(strcmp(buf, "password") == 0) {
108 *userauth_p = Password;
110 userauth_valid = false;
113 if((find_password_entries && strcmp(buf, "password") == 0) ||
114 (!find_password_entries && strcmp(buf, "password") != 0)) {
121 if(!userauth_valid || !*matches_p || *error_p) {
122 if (!userauth_valid) {
125 read_through_eol(file);
127 /* Get the map name token, if any */
128 next_token(file, buf, sizeof(buf));
129 if (buf[0] == '\0') {
131 usermap_name[0] = '\0';
133 strncpy(usermap_name, buf, USERMAP_NAME_SIZE);
134 next_token(file, buf, sizeof(buf));
135 if (buf[0] != '\0') {
137 read_through_eol(file);
138 } else *error_p = false;
146 process_hba_record(FILE *file,
147 const struct in_addr ip_addr, const char database[],
148 bool *matches_p, bool *error_p,
149 enum Userauth *userauth_p, char usermap_name[],
150 bool find_password_entries) {
151 /*---------------------------------------------------------------------------
152 Process the non-comment record in the config file that is next on the file.
153 See if it applies to a connection to a host with IP address "ip_addr"
154 to a database named "database[]". If so, return *matches_p true
155 and *userauth_p and usermap_name[] as the values from the entry.
156 If not, return matches_p false. If the record has a syntax error,
157 return *error_p true, after issuing a message to stderr. If no error,
158 leave *error_p as it was.
159 ---------------------------------------------------------------------------*/
160 char buf[MAX_TOKEN]; /* A token from the record */
162 /* Read the record type field */
163 next_token(file, buf, sizeof(buf));
164 if (buf[0] == '\0') *matches_p = false;
166 /* if this isn't a "host" record, it can't match. */
167 if (strcmp(buf, "host") != 0) {
169 read_through_eol(file);
171 /* It's a "host" record. Read the database name field. */
172 next_token(file, buf, sizeof(buf));
173 if (buf[0] == '\0') *matches_p = false;
175 /* If this record isn't for our database, ignore it. */
176 if (strcmp(buf, database) != 0 && strcmp(buf, "all") != 0) {
178 read_through_eol(file);
180 /* Read the IP address field */
181 next_token(file, buf, sizeof(buf));
182 if (buf[0] == '\0') *matches_p = false;
184 int valid; /* Field is valid dotted decimal */
185 /* Remember the IP address field and go get mask field */
186 struct in_addr file_ip_addr; /* IP address field value */
188 valid = inet_aton(buf, &file_ip_addr);
191 read_through_eol(file);
193 /* Read the mask field */
194 next_token(file, buf, sizeof(buf));
195 if (buf[0] == '\0') *matches_p = false;
198 /* Got mask. Now see if this record is for our host. */
199 valid = inet_aton(buf, &mask);
202 read_through_eol(file);
204 if (((file_ip_addr.s_addr ^ ip_addr.s_addr) & mask.s_addr)
207 read_through_eol(file);
209 /* This is the record we're looking for. Read
210 the rest of the info from it.
212 read_hba_entry2(file, userauth_p, usermap_name,
213 error_p, matches_p, find_password_entries);
216 "process_hba_record: invalid syntax in "
218 "for host record for IP address %s\n",
219 inet_ntoa(file_ip_addr));
220 fputs(PQerrormsg, stderr);
221 pqdebug("%s", PQerrormsg);
237 process_open_config_file(FILE *file,
238 const struct in_addr ip_addr, const char database[],
239 bool *host_ok_p, enum Userauth *userauth_p,
240 char usermap_name[], bool find_password_entries) {
241 /*---------------------------------------------------------------------------
242 This function does the same thing as find_hba_entry, only with
243 the config file already open on stream descriptor "file".
244 ----------------------------------------------------------------------------*/
246 /* We've processed a record that applies to our connection */
248 /* Said record has invalid syntax. */
249 bool eof; /* We've reached the end of the file we're reading */
251 found_entry = false; /* initial value */
252 error = false; /* initial value */
253 eof = false; /* initial value */
254 while (!eof && !found_entry && !error) {
255 /* Process a line from the config file */
257 int c; /* a character read from the file */
259 c = getc(file); ungetc(c, file);
260 if (c == EOF) eof = true;
262 if (c == '#') read_through_eol(file);
264 process_hba_record(file, ip_addr, database,
265 &found_entry, &error, userauth_p, usermap_name,
266 find_password_entries);
271 if (error) *host_ok_p = false;
272 else *host_ok_p = true;
273 } else *host_ok_p = false;
279 find_hba_entry(const char DataDir[], const struct in_addr ip_addr,
280 const char database[],
281 bool *host_ok_p, enum Userauth *userauth_p,
282 char usermap_name[], bool find_password_entries) {
283 /*--------------------------------------------------------------------------
284 Read the config file and find an entry that allows connection from
285 host "ip_addr" to database "database". If not found, return
286 *host_ok_p == false. If found, return *userauth_p and *usermap_name
287 representing the contents of that entry.
289 When a record has invalid syntax, we either ignore it or reject the
290 connection (depending on where it's invalid). No message or anything.
291 We need to fix that some day.
293 If we don't find or can't access the config file, we issue an error
294 message and deny the connection.
296 If we find a file by the old name of the config file (pg_hba), we issue
297 an error message because it probably needs to be converted. He didn't
298 follow directions and just installed his old hba file in the new database
301 ---------------------------------------------------------------------------*/
305 FILE *file; /* The config file we have to read */
308 /* The name of old config file that better not exist. */
310 /* Fail if config file by old name exists. */
313 /* put together the full pathname to the old config file */
314 old_conf_file = (char *) malloc((strlen(DataDir) +
315 strlen(OLD_CONF_FILE)+2)*sizeof(char));
316 sprintf(old_conf_file, "%s/%s", DataDir, OLD_CONF_FILE);
318 rc = stat(old_conf_file, &statbuf);
320 /* Old config file exists. Tell this guy he needs to upgrade. */
322 "A file exists by the name used for host-based authentication "
323 "in prior releases of Postgres (%s). The name and format of "
324 "the configuration file have changed, so this file should be "
327 fputs(PQerrormsg, stderr);
328 pqdebug("%s", PQerrormsg);
330 char *conf_file; /* The name of the config file we have to read */
332 /* put together the full pathname to the config file */
333 conf_file = (char *) malloc((strlen(DataDir) +
334 strlen(CONF_FILE)+2)*sizeof(char));
335 sprintf(conf_file, "%s/%s", DataDir, CONF_FILE);
337 file = fopen(conf_file, "r");
339 /* The open of the config file failed. */
344 "find_hba_entry: Host-based authentication config file "
345 "does not exist or permissions are not setup correctly! "
346 "Unable to open file \"%s\".\n",
348 fputs(PQerrormsg, stderr);
349 pqdebug("%s", PQerrormsg);
351 process_open_config_file(file, ip_addr, database, host_ok_p, userauth_p,
352 usermap_name, find_password_entries);
363 interpret_ident_response(char ident_response[],
364 bool *error_p, char ident_username[]) {
365 /*----------------------------------------------------------------------------
366 Parse the string "ident_response[]" as a response from a query to an Ident
367 server. If it's a normal response indicating a username, return
368 *error_p == false and the username as ident_username[]. If it's anything
369 else, return *error_p == true and ident_username[] undefined.
370 ----------------------------------------------------------------------------*/
371 char *cursor; /* Cursor into ident_response[] */
373 cursor = &ident_response[0];
375 /* Ident's response, in the telnet tradition, should end in crlf (\r\n). */
376 if (strlen(ident_response) < 2) *error_p = true;
377 else if (ident_response[strlen(ident_response)-2] != '\r') *error_p = true;
379 while (*cursor != ':' && *cursor != '\r') cursor++; /* skip port field */
381 if (*cursor != ':') *error_p = true;
383 /* We're positioned to colon before response type field */
384 char response_type[80];
385 int i; /* Index into response_type[] */
386 cursor++; /* Go over colon */
387 while (isblank(*cursor)) cursor++; /* skip blanks */
389 while (*cursor != ':' && *cursor != '\r' && !isblank(*cursor)
390 && i < sizeof(response_type)-1)
391 response_type[i++] = *cursor++;
392 response_type[i] = '\0';
393 while (isblank(*cursor)) cursor++; /* skip blanks */
394 if (strcmp(response_type, "USERID") != 0)
397 /* It's a USERID response. Good. "cursor" should be pointing to
398 the colon that precedes the operating system type.
400 if (*cursor != ':') *error_p = true;
402 cursor++; /* Go over colon */
403 /* Skip over operating system field. */
404 while (*cursor != ':' && *cursor != '\r') cursor++;
405 if (*cursor != ':') *error_p = true;
407 int i; /* Index into ident_username[] */
408 cursor ++; /* Go over colon */
409 while (isblank(*cursor)) cursor++; /* skip blanks */
410 /* Rest of line is username. Copy it over. */
412 while (*cursor != '\r' && i < IDENT_USERNAME_MAX)
413 ident_username[i++] = *cursor++;
414 ident_username[i] = '\0';
426 ident(const struct in_addr remote_ip_addr, const struct in_addr local_ip_addr,
427 const ushort remote_port, const ushort local_port,
428 bool *ident_failed, char ident_username[]) {
429 /*--------------------------------------------------------------------------
430 Talk to the ident server on host "remote_ip_addr" and find out who
431 owns the tcp connection from his port "remote_port" to port
432 "local_port_addr" on host "local_ip_addr". Return the username the
433 ident server gives as "ident_username[]".
435 IP addresses and port numbers are in network byte order.
437 But iff we're unable to get the information from ident, return
438 *ident_failed == true (and ident_username[] undefined).
439 ----------------------------------------------------------------------------*/
442 /* File descriptor for socket on which we talk to Ident */
444 int rc; /* Return code from a locally called function */
446 sock_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
449 "Failed to create socket on which to talk to Ident server. "
450 "socket() returned errno = %s (%d)\n",
451 strerror(errno), errno);
452 fputs(PQerrormsg, stderr);
453 pqdebug("%s", PQerrormsg);
455 struct sockaddr_in ident_server;
456 /* Socket address of Ident server on the system from which client
457 is attempting to connect to us.
459 ident_server.sin_family = AF_INET;
460 ident_server.sin_port = htons(IDENT_PORT);
461 ident_server.sin_addr = remote_ip_addr;
462 rc = connect(sock_fd,
463 (struct sockaddr *) &ident_server, sizeof(ident_server));
466 "Unable to connect to Ident server on the host which is "
467 "trying to connect to Postgres "
468 "(IP address %s, Port %d). "
470 inet_ntoa(remote_ip_addr), IDENT_PORT, strerror(errno), errno);
471 fputs(PQerrormsg, stderr);
472 pqdebug("%s", PQerrormsg);
473 *ident_failed = true;
475 char ident_query[80];
476 /* The query we send to the Ident server */
477 sprintf(ident_query, "%d,%d\n",
478 ntohs(remote_port), ntohs(local_port));
479 rc = send(sock_fd, ident_query, strlen(ident_query), 0);
482 "Unable to send query to Ident server on the host which is "
483 "trying to connect to Postgres (Host %s, Port %d),"
484 "even though we successfully connected to it. "
486 inet_ntoa(remote_ip_addr), IDENT_PORT, strerror(errno), errno);
487 fputs(PQerrormsg, stderr);
488 pqdebug("%s", PQerrormsg);
489 *ident_failed = true;
491 char ident_response[80+IDENT_USERNAME_MAX];
492 rc = recv(sock_fd, ident_response, sizeof(ident_response)-1, 0);
495 "Unable to receive response from Ident server "
496 "on the host which is "
497 "trying to connect to Postgres (Host %s, Port %d),"
498 "even though we successfully sent our query to it. "
500 inet_ntoa(remote_ip_addr), IDENT_PORT,
501 strerror(errno), errno);
502 fputs(PQerrormsg, stderr);
503 pqdebug("%s", PQerrormsg);
504 *ident_failed = true;
506 bool error; /* response from Ident is garbage. */
507 ident_response[rc] = '\0';
508 interpret_ident_response(ident_response, &error, ident_username);
509 *ident_failed = error;
520 parse_map_record(FILE *file,
521 char file_map[], char file_pguser[], char file_iuser[]) {
522 /*---------------------------------------------------------------------------
523 Take the noncomment line which is next on file "file" and interpret
524 it as a line in a usermap file. Specifically, return the first
525 3 tokens as file_map, file_iuser, and file_pguser, respectively. If
526 there are fewer than 3 tokens, return null strings for the missing
529 ---------------------------------------------------------------------------*/
531 /* A token read from the file */
533 /* Set defaults in case fields not in file */
535 file_pguser[0] = '\0';
536 file_iuser[0] = '\0';
538 next_token(file, buf, sizeof(buf));
540 strcpy(file_map, buf);
541 next_token(file, buf, sizeof(buf));
543 strcpy(file_iuser, buf);
544 next_token(file, buf, sizeof(buf));
546 strcpy(file_pguser, buf);
547 read_through_eol(file);
556 verify_against_open_usermap(FILE *file,
558 const char ident_username[],
559 const char usermap_name[],
560 bool *checks_out_p) {
561 /*--------------------------------------------------------------------------
562 This function does the same thing as verify_against_usermap,
563 only with the config file already open on stream descriptor "file".
564 ---------------------------------------------------------------------------*/
565 bool match; /* We found a matching entry in the map file */
566 bool eof; /* We've reached the end of the file we're reading */
568 match = false; /* initial value */
569 eof = false; /* initial value */
570 while (!eof && !match) {
571 /* Process a line from the map file */
573 int c; /* a character read from the file */
575 c = getc(file); ungetc(c, file);
576 if (c == EOF) eof = true;
578 if (c == '#') read_through_eol(file);
580 /* The following are fields read from a record of the file */
581 char file_map[MAX_TOKEN+1];
582 char file_pguser[MAX_TOKEN+1];
583 char file_iuser[MAX_TOKEN+1];
585 parse_map_record(file, file_map, file_pguser, file_iuser);
586 if (strcmp(file_map, usermap_name) == 0 &&
587 strcmp(file_pguser, pguser) == 0 &&
588 strcmp(file_iuser, ident_username) == 0)
593 *checks_out_p = match;
599 verify_against_usermap(const char DataDir[],
601 const char ident_username[],
602 const char usermap_name[],
603 bool *checks_out_p) {
604 /*--------------------------------------------------------------------------
605 See if the user with ident username "ident_username" is allowed to act
606 as Postgres user "pguser" according to usermap "usermap_name". Look
607 it up in the usermap file.
609 Special case: For usermap "sameuser", don't look in the usermap
610 file. That's an implied map where "pguser" must be identical to
611 "ident_username" in order to be authorized.
613 Iff authorized, return *checks_out_p == true.
615 --------------------------------------------------------------------------*/
617 if (usermap_name[0] == '\0') {
618 *checks_out_p = false;
620 "verify_against_usermap: hba configuration file does not "
621 "have the usermap field filled in in the entry that pertains "
622 "to this connection. That field is essential for Ident-based "
623 "authentication.\n");
624 fputs(PQerrormsg, stderr);
625 pqdebug("%s", PQerrormsg);
626 } else if (strcmp(usermap_name, "sameuser") == 0) {
627 if (strcmp(ident_username, pguser) == 0) *checks_out_p = true;
628 else *checks_out_p = false;
630 FILE *file; /* The map file we have to read */
632 char *map_file; /* The name of the map file we have to read */
634 /* put together the full pathname to the map file */
635 map_file = (char *) malloc((strlen(DataDir) +
636 strlen(MAP_FILE)+2)*sizeof(char));
637 sprintf(map_file, "%s/%s", DataDir, MAP_FILE);
639 file = fopen(map_file, "r");
641 /* The open of the map file failed. */
643 *checks_out_p = false;
646 "verify_against_usermap: usermap file for Ident-based "
648 "does not exist or permissions are not setup correctly! "
649 "Unable to open file \"%s\".\n",
651 fputs(PQerrormsg, stderr);
652 pqdebug("%s", PQerrormsg);
654 verify_against_open_usermap(file,
655 pguser, ident_username, usermap_name,
668 authident(const char DataDir[],
669 const Port port, const char postgres_username[],
670 const char usermap_name[],
672 /*---------------------------------------------------------------------------
673 Talk to the ident server on the remote host and find out who owns the
674 connection described by "port". Then look in the usermap file under
675 the usermap usermap_name[] and see if that user is equivalent to
676 Postgres user user[].
678 Return *authentic_p true iff yes.
679 ---------------------------------------------------------------------------*/
681 /* We were unable to get ident to give us a username */
682 char ident_username[IDENT_USERNAME_MAX+1];
683 /* The username returned by ident */
685 ident(port.raddr.sin_addr, port.laddr.sin_addr,
686 port.raddr.sin_port, port.laddr.sin_port,
687 &ident_failed, ident_username);
689 if (ident_failed) *authentic_p = false;
692 verify_against_usermap(DataDir,
693 postgres_username, ident_username, usermap_name,
695 if (checks_out) *authentic_p = true;
696 else *authentic_p = false;
703 hba_recvauth(const Port *port, const char database[], const char user[],
704 const char DataDir[]) {
705 /*---------------------------------------------------------------------------
706 Determine if the TCP connection described by "port" is with someone
707 allowed to act as user "user" and access database "database". Return
708 STATUS_OK if yes; STATUS_ERROR if not.
709 ----------------------------------------------------------------------------*/
711 /* There's an entry for this database and remote host in the pg_hba file */
712 char usermap_name[USERMAP_NAME_SIZE+1];
713 /* The name of the map pg_hba specifies for this connection (or special
716 enum Userauth userauth;
717 /* The type of user authentication pg_hba specifies for this connection */
719 /* Our eventual return value */
722 find_hba_entry(DataDir, port->raddr.sin_addr, database,
723 &host_ok, &userauth, usermap_name,
724 false /* don't find password entries of type 'password' */);
726 if (!host_ok) retvalue = STATUS_ERROR;
730 retvalue = STATUS_OK;
733 /* Here's where we need to call up ident and authenticate the user */
735 bool authentic; /* He is who he says he is. */
737 authident(DataDir, *port, user, usermap_name, &authentic);
739 if (authentic) retvalue = STATUS_OK;
740 else retvalue = STATUS_ERROR;
744 retvalue = STATUS_ERROR;
752 /*----------------------------------------------------------------
753 * This version of hba was written by Bryan Henderson
754 * in September 1996 for Release 6.0. It changed the format of the
755 * hba file and added ident function.
757 * Here are some notes about the original host based authentication
758 * the preceded this one.
760 * based on the securelib package originally written by William
761 * LeFebvre, EECS Department, Northwestern University
762 * (phil@eecs.nwu.edu) - orginal configuration file code handling
763 * by Sam Horrocks (sam@ics.uci.edu)
765 * modified and adapted for use with Postgres95 by Paul Fisher
766 * (pnfisher@unity.ncsu.edu)
768 -----------------------------------------------------------------*/