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.25 1997/12/09 03:10:38 scrappy Exp $
12 *-------------------------------------------------------------------------
18 #include <sys/types.h>
20 #include <sys/socket.h>
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() */
31 #include <storage/fd.h>
33 /* Some standard C libraries, including GNU, have an isblank() function.
34 Others, including Solaris, do not. So we have our own.
39 return (c == ' ' || c == 9 /* tab */ );
45 next_token(FILE *fp, char *buf, const int bufsz)
47 /*--------------------------------------------------------------------------
48 Grab one token out of fp. Tokens are strings of non-blank
49 characters bounded by blank characters, beginning of line, and end
50 of line. Blank means space or tab. Return the token as *buf.
51 Leave file positioned to character immediately after the token or
52 EOF, whichever comes first. If no more tokens on line, return null
53 string as *buf and position file to beginning of next line or EOF,
54 whichever comes first.
55 --------------------------------------------------------------------------*/
57 char *eb = buf + (bufsz - 1);
59 /* Move over inital token-delimiting blanks */
60 while (isblank(c = getc(fp)));
66 * build a token in buf of next characters up to EOF, eol, or
69 while (c != EOF && c != '\n' && !isblank(c))
76 * Put back the char right after the token (putting back EOF
88 read_through_eol(FILE *file)
94 while (c != '\n' && c != EOF);
100 read_hba_entry2(FILE *file, enum Userauth * userauth_p, char usermap_name[],
101 bool *error_p, bool *matches_p, bool find_password_entries)
103 /*--------------------------------------------------------------------------
104 Read from file FILE the rest of a host record, after the mask field,
105 and return the interpretation of it as *userauth_p, usermap_name, and
107 ---------------------------------------------------------------------------*/
112 /* Get authentication type token. */
113 next_token(file, buf, sizeof(buf));
114 userauth_valid = false;
121 userauth_valid = true;
122 if (strcmp(buf, "trust") == 0)
126 else if (strcmp(buf, "ident") == 0)
130 else if (strcmp(buf, "password") == 0)
132 *userauth_p = Password;
136 userauth_valid = false;
139 if ((find_password_entries && strcmp(buf, "password") == 0) ||
140 (!find_password_entries && strcmp(buf, "password") != 0))
150 if (!userauth_valid || !*matches_p || *error_p)
156 read_through_eol(file);
160 /* Get the map name token, if any */
161 next_token(file, buf, sizeof(buf));
165 usermap_name[0] = '\0';
169 strncpy(usermap_name, buf, USERMAP_NAME_SIZE);
170 next_token(file, buf, sizeof(buf));
174 read_through_eol(file);
185 process_hba_record(FILE *file,
186 const struct in_addr ip_addr, const char database[],
187 bool *matches_p, bool *error_p,
188 enum Userauth * userauth_p, char usermap_name[],
189 bool find_password_entries)
191 /*---------------------------------------------------------------------------
192 Process the non-comment record in the config file that is next on the file.
193 See if it applies to a connection to a host with IP address "ip_addr"
194 to a database named "database[]". If so, return *matches_p true
195 and *userauth_p and usermap_name[] as the values from the entry.
196 If not, return matches_p false. If the record has a syntax error,
197 return *error_p true, after issuing a message to stderr. If no error,
198 leave *error_p as it was.
199 ---------------------------------------------------------------------------*/
200 char buf[MAX_TOKEN]; /* A token from the record */
202 /* Read the record type field */
203 next_token(file, buf, sizeof(buf));
208 /* if this isn't a "host" record, it can't match. */
209 if (strcmp(buf, "host") != 0)
212 read_through_eol(file);
216 /* It's a "host" record. Read the database name field. */
217 next_token(file, buf, sizeof(buf));
222 /* If this record isn't for our database, ignore it. */
223 if (strcmp(buf, database) != 0 && strcmp(buf, "all") != 0)
226 read_through_eol(file);
230 /* Read the IP address field */
231 next_token(file, buf, sizeof(buf));
236 int valid; /* Field is valid dotted
240 * Remember the IP address field and go get mask
243 struct in_addr file_ip_addr; /* IP address field
246 valid = inet_aton(buf, &file_ip_addr);
250 read_through_eol(file);
254 /* Read the mask field */
255 next_token(file, buf, sizeof(buf));
263 * Got mask. Now see if this record is
266 valid = inet_aton(buf, &mask);
270 read_through_eol(file);
274 if (((file_ip_addr.s_addr ^ ip_addr.s_addr) & mask.s_addr)
278 read_through_eol(file);
284 * This is the record we're
285 * looking for. Read the rest of
288 read_hba_entry2(file, userauth_p, usermap_name,
289 error_p, matches_p, find_password_entries);
293 "process_hba_record: invalid syntax in "
295 "for host record for IP address %s\n",
296 inet_ntoa(file_ip_addr));
297 fputs(PQerrormsg, stderr);
298 pqdebug("%s", PQerrormsg);
314 process_open_config_file(FILE *file,
315 const struct in_addr ip_addr, const char database[],
316 bool *host_ok_p, enum Userauth * userauth_p,
317 char usermap_name[], bool find_password_entries)
319 /*---------------------------------------------------------------------------
320 This function does the same thing as find_hba_entry, only with
321 the config file already open on stream descriptor "file".
322 ----------------------------------------------------------------------------*/
325 /* We've processed a record that applies to our connection */
328 /* Said record has invalid syntax. */
329 bool eof; /* We've reached the end of the file we're
332 found_entry = false; /* initial value */
333 error = false; /* initial value */
334 eof = false; /* initial value */
335 while (!eof && !found_entry && !error)
337 /* Process a line from the config file */
339 int c; /* a character read from the file */
348 read_through_eol(file);
351 process_hba_record(file, ip_addr, database,
352 &found_entry, &error, userauth_p, usermap_name,
353 find_password_entries);
371 find_hba_entry(const char DataDir[], const struct in_addr ip_addr,
372 const char database[],
373 bool *host_ok_p, enum Userauth * userauth_p,
374 char usermap_name[], bool find_password_entries)
376 /*--------------------------------------------------------------------------
377 Read the config file and find an entry that allows connection from
378 host "ip_addr" to database "database". If not found, return
379 *host_ok_p == false. If found, return *userauth_p and *usermap_name
380 representing the contents of that entry.
382 When a record has invalid syntax, we either ignore it or reject the
383 connection (depending on where it's invalid). No message or anything.
384 We need to fix that some day.
386 If we don't find or can't access the config file, we issue an error
387 message and deny the connection.
389 If we find a file by the old name of the config file (pg_hba), we issue
390 an error message because it probably needs to be converted. He didn't
391 follow directions and just installed his old hba file in the new database
394 ---------------------------------------------------------------------------*/
397 FILE *file; /* The config file we have to read */
401 /* The name of old config file that better not exist. */
403 /* Fail if config file by old name exists. */
406 /* put together the full pathname to the old config file */
407 old_conf_file = (char *) palloc((strlen(DataDir) +
408 strlen(OLD_CONF_FILE) + 2) * sizeof(char));
409 sprintf(old_conf_file, "%s/%s", DataDir, OLD_CONF_FILE);
411 if ((fd = open(old_conf_file, O_RDONLY, 0)) != -1)
413 /* Old config file exists. Tell this guy he needs to upgrade. */
416 "A file exists by the name used for host-based authentication "
417 "in prior releases of Postgres (%s). The name and format of "
418 "the configuration file have changed, so this file should be "
421 fputs(PQerrormsg, stderr);
422 pqdebug("%s", PQerrormsg);
426 char *conf_file; /* The name of the config file we have to
429 /* put together the full pathname to the config file */
430 conf_file = (char *) palloc((strlen(DataDir) +
431 strlen(CONF_FILE) + 2) * sizeof(char));
432 sprintf(conf_file, "%s/%s", DataDir, CONF_FILE);
434 file = AllocateFile(conf_file, "r");
437 /* The open of the config file failed. */
442 "find_hba_entry: Host-based authentication config file "
443 "does not exist or permissions are not setup correctly! "
444 "Unable to open file \"%s\".\n",
446 fputs(PQerrormsg, stderr);
447 pqdebug("%s", PQerrormsg);
451 process_open_config_file(file, ip_addr, database, host_ok_p, userauth_p,
452 usermap_name, find_password_entries);
457 pfree(old_conf_file);
463 interpret_ident_response(char ident_response[],
464 bool *error_p, char ident_username[])
466 /*----------------------------------------------------------------------------
467 Parse the string "ident_response[]" as a response from a query to an Ident
468 server. If it's a normal response indicating a username, return
469 *error_p == false and the username as ident_username[]. If it's anything
470 else, return *error_p == true and ident_username[] undefined.
471 ----------------------------------------------------------------------------*/
472 char *cursor; /* Cursor into ident_response[] */
474 cursor = &ident_response[0];
477 * Ident's response, in the telnet tradition, should end in crlf
480 if (strlen(ident_response) < 2)
482 else if (ident_response[strlen(ident_response) - 2] != '\r')
486 while (*cursor != ':' && *cursor != '\r')
487 cursor++; /* skip port field */
493 /* We're positioned to colon before response type field */
494 char response_type[80];
495 int i; /* Index into response_type[] */
497 cursor++; /* Go over colon */
498 while (isblank(*cursor))
499 cursor++; /* skip blanks */
501 while (*cursor != ':' && *cursor != '\r' && !isblank(*cursor)
502 && i < sizeof(response_type) - 1)
503 response_type[i++] = *cursor++;
504 response_type[i] = '\0';
505 while (isblank(*cursor))
506 cursor++; /* skip blanks */
507 if (strcmp(response_type, "USERID") != 0)
513 * It's a USERID response. Good. "cursor" should be
514 * pointing to the colon that precedes the operating
521 cursor++; /* Go over colon */
522 /* Skip over operating system field. */
523 while (*cursor != ':' && *cursor != '\r')
529 int i; /* Index into ident_username[] */
531 cursor++; /* Go over colon */
532 while (isblank(*cursor))
533 cursor++; /* skip blanks */
534 /* Rest of line is username. Copy it over. */
536 while (*cursor != '\r' && i < IDENT_USERNAME_MAX)
537 ident_username[i++] = *cursor++;
538 ident_username[i] = '\0';
550 ident(const struct in_addr remote_ip_addr, const struct in_addr local_ip_addr,
551 const ushort remote_port, const ushort local_port,
552 bool *ident_failed, char ident_username[])
554 /*--------------------------------------------------------------------------
555 Talk to the ident server on host "remote_ip_addr" and find out who
556 owns the tcp connection from his port "remote_port" to port
557 "local_port_addr" on host "local_ip_addr". Return the username the
558 ident server gives as "ident_username[]".
560 IP addresses and port numbers are in network byte order.
562 But iff we're unable to get the information from ident, return
563 *ident_failed == true (and ident_username[] undefined).
564 ----------------------------------------------------------------------------*/
568 /* File descriptor for socket on which we talk to Ident */
570 int rc; /* Return code from a locally called
573 sock_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
577 "Failed to create socket on which to talk to Ident server. "
578 "socket() returned errno = %s (%d)\n",
579 strerror(errno), errno);
580 fputs(PQerrormsg, stderr);
581 pqdebug("%s", PQerrormsg);
585 struct sockaddr_in ident_server;
588 * Socket address of Ident server on the system from which client
589 * is attempting to connect to us.
591 ident_server.sin_family = AF_INET;
592 ident_server.sin_port = htons(IDENT_PORT);
593 ident_server.sin_addr = remote_ip_addr;
594 rc = connect(sock_fd,
595 (struct sockaddr *) & ident_server, sizeof(ident_server));
599 "Unable to connect to Ident server on the host which is "
600 "trying to connect to Postgres "
601 "(IP address %s, Port %d). "
603 inet_ntoa(remote_ip_addr), IDENT_PORT, strerror(errno), errno);
604 fputs(PQerrormsg, stderr);
605 pqdebug("%s", PQerrormsg);
606 *ident_failed = true;
610 char ident_query[80];
612 /* The query we send to the Ident server */
613 sprintf(ident_query, "%d,%d\n",
614 ntohs(remote_port), ntohs(local_port));
615 rc = send(sock_fd, ident_query, strlen(ident_query), 0);
619 "Unable to send query to Ident server on the host which is "
620 "trying to connect to Postgres (Host %s, Port %d),"
621 "even though we successfully connected to it. "
623 inet_ntoa(remote_ip_addr), IDENT_PORT, strerror(errno), errno);
624 fputs(PQerrormsg, stderr);
625 pqdebug("%s", PQerrormsg);
626 *ident_failed = true;
630 char ident_response[80 + IDENT_USERNAME_MAX];
632 rc = recv(sock_fd, ident_response, sizeof(ident_response) - 1, 0);
636 "Unable to receive response from Ident server "
637 "on the host which is "
638 "trying to connect to Postgres (Host %s, Port %d),"
639 "even though we successfully sent our query to it. "
641 inet_ntoa(remote_ip_addr), IDENT_PORT,
642 strerror(errno), errno);
643 fputs(PQerrormsg, stderr);
644 pqdebug("%s", PQerrormsg);
645 *ident_failed = true;
649 bool error; /* response from Ident is garbage. */
651 ident_response[rc] = '\0';
652 interpret_ident_response(ident_response, &error, ident_username);
653 *ident_failed = error;
664 parse_map_record(FILE *file,
665 char file_map[], char file_pguser[], char file_iuser[])
667 /*---------------------------------------------------------------------------
668 Take the noncomment line which is next on file "file" and interpret
669 it as a line in a usermap file. Specifically, return the first
670 3 tokens as file_map, file_iuser, and file_pguser, respectively. If
671 there are fewer than 3 tokens, return null strings for the missing
674 ---------------------------------------------------------------------------*/
677 /* A token read from the file */
679 /* Set defaults in case fields not in file */
681 file_pguser[0] = '\0';
682 file_iuser[0] = '\0';
684 next_token(file, buf, sizeof(buf));
687 strcpy(file_map, buf);
688 next_token(file, buf, sizeof(buf));
691 strcpy(file_iuser, buf);
692 next_token(file, buf, sizeof(buf));
695 strcpy(file_pguser, buf);
696 read_through_eol(file);
705 verify_against_open_usermap(FILE *file,
707 const char ident_username[],
708 const char usermap_name[],
711 /*--------------------------------------------------------------------------
712 This function does the same thing as verify_against_usermap,
713 only with the config file already open on stream descriptor "file".
714 ---------------------------------------------------------------------------*/
715 bool match; /* We found a matching entry in the map
717 bool eof; /* We've reached the end of the file we're
720 match = false; /* initial value */
721 eof = false; /* initial value */
722 while (!eof && !match)
724 /* Process a line from the map file */
726 int c; /* a character read from the file */
735 read_through_eol(file);
738 /* The following are fields read from a record of the file */
739 char file_map[MAX_TOKEN + 1];
740 char file_pguser[MAX_TOKEN + 1];
741 char file_iuser[MAX_TOKEN + 1];
743 parse_map_record(file, file_map, file_pguser, file_iuser);
744 if (strcmp(file_map, usermap_name) == 0 &&
745 strcmp(file_pguser, pguser) == 0 &&
746 strcmp(file_iuser, ident_username) == 0)
751 *checks_out_p = match;
757 verify_against_usermap(const char DataDir[],
759 const char ident_username[],
760 const char usermap_name[],
763 /*--------------------------------------------------------------------------
764 See if the user with ident username "ident_username" is allowed to act
765 as Postgres user "pguser" according to usermap "usermap_name". Look
766 it up in the usermap file.
768 Special case: For usermap "sameuser", don't look in the usermap
769 file. That's an implied map where "pguser" must be identical to
770 "ident_username" in order to be authorized.
772 Iff authorized, return *checks_out_p == true.
774 --------------------------------------------------------------------------*/
776 if (usermap_name[0] == '\0')
778 *checks_out_p = false;
780 "verify_against_usermap: hba configuration file does not "
781 "have the usermap field filled in in the entry that pertains "
782 "to this connection. That field is essential for Ident-based "
783 "authentication.\n");
784 fputs(PQerrormsg, stderr);
785 pqdebug("%s", PQerrormsg);
787 else if (strcmp(usermap_name, "sameuser") == 0)
789 if (strcmp(ident_username, pguser) == 0)
790 *checks_out_p = true;
792 *checks_out_p = false;
796 FILE *file; /* The map file we have to read */
798 char *map_file; /* The name of the map file we have to
801 /* put together the full pathname to the map file */
802 map_file = (char *) palloc((strlen(DataDir) +
803 strlen(MAP_FILE) + 2) * sizeof(char));
804 sprintf(map_file, "%s/%s", DataDir, MAP_FILE);
806 file = AllocateFile(map_file, "r");
809 /* The open of the map file failed. */
811 *checks_out_p = false;
814 "verify_against_usermap: usermap file for Ident-based "
816 "does not exist or permissions are not setup correctly! "
817 "Unable to open file \"%s\".\n",
819 fputs(PQerrormsg, stderr);
820 pqdebug("%s", PQerrormsg);
824 verify_against_open_usermap(file,
825 pguser, ident_username, usermap_name,
838 authident(const char DataDir[],
839 const Port port, const char postgres_username[],
840 const char usermap_name[],
843 /*---------------------------------------------------------------------------
844 Talk to the ident server on the remote host and find out who owns the
845 connection described by "port". Then look in the usermap file under
846 the usermap usermap_name[] and see if that user is equivalent to
847 Postgres user user[].
849 Return *authentic_p true iff yes.
850 ---------------------------------------------------------------------------*/
853 /* We were unable to get ident to give us a username */
854 char ident_username[IDENT_USERNAME_MAX + 1];
856 /* The username returned by ident */
858 ident(port.raddr.in.sin_addr, port.laddr.in.sin_addr,
859 port.raddr.in.sin_port, port.laddr.in.sin_port,
860 &ident_failed, ident_username);
863 *authentic_p = false;
868 verify_against_usermap(DataDir,
869 postgres_username, ident_username, usermap_name,
874 *authentic_p = false;
881 hba_recvauth(const Port *port, const char database[], const char user[],
882 const char DataDir[])
884 /*---------------------------------------------------------------------------
885 Determine if the TCP connection described by "port" is with someone
886 allowed to act as user "user" and access database "database". Return
887 STATUS_OK if yes; STATUS_ERROR if not.
888 ----------------------------------------------------------------------------*/
892 * There's an entry for this database and remote host in the pg_hba
895 char usermap_name[USERMAP_NAME_SIZE + 1];
898 * The name of the map pg_hba specifies for this connection (or
899 * special value "SAMEUSER")
901 enum Userauth userauth;
904 * The type of user authentication pg_hba specifies for this
909 /* UNIX socket always OK, for now */
910 if (port->raddr.in.sin_family == AF_UNIX)
912 /* Our eventual return value */
915 find_hba_entry(DataDir, port->raddr.in.sin_addr, database,
916 &host_ok, &userauth, usermap_name,
917 false /* don't find password entries of type
921 retvalue = STATUS_ERROR;
927 retvalue = STATUS_OK;
933 * Here's where we need to call up ident and
934 * authenticate the user
937 bool authentic; /* He is who he says he
940 authident(DataDir, *port, user, usermap_name, &authentic);
943 retvalue = STATUS_OK;
945 retvalue = STATUS_ERROR;
950 retvalue = STATUS_ERROR;
958 /*----------------------------------------------------------------
959 * This version of hba was written by Bryan Henderson
960 * in September 1996 for Release 6.0. It changed the format of the
961 * hba file and added ident function.
963 * Here are some notes about the original host based authentication
964 * the preceded this one.
966 * based on the securelib package originally written by William
967 * LeFebvre, EECS Department, Northwestern University
968 * (phil@eecs.nwu.edu) - orginal configuration file code handling
969 * by Sam Horrocks (sam@ics.uci.edu)
971 * modified and adapted for use with Postgres95 by Paul Fisher
972 * (pnfisher@unity.ncsu.edu)
974 -----------------------------------------------------------------*/