pg_hba.conf caching - Mailing list pgsql-patches
From | Bruce Momjian |
---|---|
Subject | pg_hba.conf caching |
Date | |
Msg-id | 200107210031.f6L0V2o20575@candle.pha.pa.us Whole thread Raw |
Responses |
Re: pg_hba.conf caching
|
List | pgsql-patches |
Attached is a patch that caches the non-comment contents of pg_hba.conf as a List of list of tokens. It uses that to test each authentication request. SIGHUP reloads from the file, and a cache of pg_ident.conf. I replaced the File reading code with token storage and list traveral. The only tricky part was moving the Postmaster memory clearing later in the code so I had the file contents in postgres.c. -- Bruce Momjian | http://candle.pha.pa.us pgman@candle.pha.pa.us | (610) 853-3000 + If your life is a hard drive, | 830 Blythe Avenue + Christ can be your backup. | Drexel Hill, Pennsylvania 19026 Index: src/backend/libpq/hba.c =================================================================== RCS file: /home/projects/pgsql/cvsroot/pgsql/src/backend/libpq/hba.c,v retrieving revision 1.55 diff -c -r1.55 hba.c *** src/backend/libpq/hba.c 2001/02/10 02:31:26 1.55 --- src/backend/libpq/hba.c 2001/07/21 00:12:41 *************** *** 22,27 **** --- 22,28 ---- #include "libpq/libpq.h" #include "miscadmin.h" + #include "nodes/pg_list.h" #include "storage/fd.h" *************** *** 31,36 **** --- 32,44 ---- #define IDENT_USERNAME_MAX 512 /* Max size of username ident server can return */ + static List *hba_lines = NULL; /* A list of lists: entry for every line, + * list of tokens on each line. + */ + + static List *ident_lines = NULL;/* A list of lists: entry for every line, + * list of tokens on each line. + */ /* Some standard C libraries, including GNU, have an isblank() function. Others, including Solaris, do not. So we have our own. *************** *** 38,68 **** static bool isblank(const char c) { ! return c == ' ' || c == 9 /* tab */ ; } static void next_token(FILE *fp, char *buf, const int bufsz) { - /*-------------------------------------------------------------------------- - Grab one token out of fp. Tokens are strings of non-blank - characters bounded by blank characters, beginning of line, and end - of line. Blank means space or tab. Return the token as *buf. - Leave file positioned to character immediately after the token or - EOF, whichever comes first. If no more tokens on line, return null - string as *buf and position file to beginning of next line or EOF, - whichever comes first. - --------------------------------------------------------------------------*/ int c; char *eb = buf + (bufsz - 1); /* Move over inital token-delimiting blanks */ ! while (isblank(c = getc(fp))); if (c != '\n') { - /* * build a token in buf of next characters up to EOF, eol, or * blank. --- 46,76 ---- static bool isblank(const char c) { ! return c == ' ' || c == 0x09;/* tab */ } + /* + * Grab one token out of fp. Tokens are strings of non-blank + * characters bounded by blank characters, beginning of line, and end + * of line. Blank means space or tab. Return the token as *buf. + * Leave file positioned to character immediately after the token or + * EOF, whichever comes first. If no more tokens on line, return null + * string as *buf and position file to beginning of next line or EOF, + * whichever comes first. + */ static void next_token(FILE *fp, char *buf, const int bufsz) { int c; char *eb = buf + (bufsz - 1); /* Move over inital token-delimiting blanks */ ! while (isblank(c = getc(fp))) ! ; if (c != '\n') { /* * build a token in buf of next characters up to EOF, eol, or * blank. *************** *** 72,231 **** if (buf < eb) *buf++ = c; c = getc(fp); - - /* - * Put back the char right after the token (putting back EOF - * is ok) - */ } ungetc(c, fp); } *buf = '\0'; } - static void ! read_through_eol(FILE *file) { int c; ! do ! c = getc(file); ! while (c != '\n' && c != EOF); } - static void ! read_hba_entry2(FILE *file, UserAuth *userauth_p, char *auth_arg, ! bool *error_p) { - /*-------------------------------------------------------------------------- - Read from file FILE the rest of a host record, after the mask field, - and return the interpretation of it as *userauth_p, auth_arg, and - *error_p. - ---------------------------------------------------------------------------*/ char buf[MAX_TOKEN]; ! ! /* Get authentication type token. */ ! next_token(file, buf, sizeof(buf)); ! if (strcmp(buf, "trust") == 0) ! *userauth_p = uaTrust; ! else if (strcmp(buf, "ident") == 0) ! *userauth_p = uaIdent; ! else if (strcmp(buf, "password") == 0) ! *userauth_p = uaPassword; ! else if (strcmp(buf, "krb4") == 0) ! *userauth_p = uaKrb4; ! else if (strcmp(buf, "krb5") == 0) ! *userauth_p = uaKrb5; ! else if (strcmp(buf, "reject") == 0) ! *userauth_p = uaReject; ! else if (strcmp(buf, "crypt") == 0) ! *userauth_p = uaCrypt; ! else { ! *error_p = true; if (buf[0] != '\0') ! read_through_eol(file); } if (!*error_p) { /* Get the authentication argument token, if any */ ! next_token(file, buf, sizeof(buf)); ! if (buf[0] == '\0') auth_arg[0] = '\0'; else { ! StrNCpy(auth_arg, buf, MAX_AUTH_ARG - 1); ! next_token(file, buf, sizeof(buf)); ! if (buf[0] != '\0') ! { *error_p = true; - read_through_eol(file); - } } } } - static void ! process_hba_record(FILE *file, hbaPort *port, bool *matches_p, bool *error_p) { ! /*--------------------------------------------------------------------------- ! Process the non-comment record in the config file that is next on the file. ! See if it applies to a connection to a host with IP address "*raddr" ! to a database named "*database". If so, return *matches_p true ! and *userauth_p and *auth_arg as the values from the entry. ! If not, leave *matches_p as it was. If the record has a syntax error, ! return *error_p true, after issuing a message to stderr. If no error, ! leave *error_p as it was. ! ---------------------------------------------------------------------------*/ ! char db[MAX_TOKEN], ! buf[MAX_TOKEN]; ! /* Read the record type field. */ ! ! next_token(file, buf, sizeof(buf)); ! ! if (buf[0] == '\0') ! return; ! /* Check the record type. */ ! ! if (strcmp(buf, "local") == 0) { /* Get the database. */ ! ! next_token(file, db, sizeof(db)); ! ! if (db[0] == '\0') ! goto syntax; ! /* Read the rest of the line. */ ! ! read_hba_entry2(file, &port->auth_method, port->auth_arg, error_p); /* * For now, disallow methods that need AF_INET sockets to work. */ - if (!*error_p && (port->auth_method == uaIdent || port->auth_method == uaKrb4 || port->auth_method == uaKrb5)) ! *error_p = true; - if (*error_p) - goto syntax; - /* * If this record isn't for our database, or this is the wrong * sort of connection, ignore it. */ - if ((strcmp(db, port->database) != 0 && strcmp(db, "all") != 0 && (strcmp(db, "sameuser") != 0 || strcmp(port->database, port->user) != 0)) || port->raddr.sa.sa_family != AF_UNIX) return; } ! else if (strcmp(buf, "host") == 0 || strcmp(buf, "hostssl") == 0) { ! struct in_addr file_ip_addr, ! mask; ! bool discard = 0;/* Discard this entry */ #ifdef USE_SSL /* If SSL, then check that we are on SSL */ ! if (strcmp(buf, "hostssl") == 0) { if (!port->ssl) ! discard = 1; /* Placeholder to require specific SSL level, perhaps? */ /* Or a client certificate */ --- 80,293 ---- if (buf < eb) *buf++ = c; c = getc(fp); } + /* + * Put back the char right after the token (putting back EOF + * is ok) + */ ungetc(c, fp); } *buf = '\0'; } static void ! read_to_eol(FILE *file) { int c; ! while ((c = getc(file)) != '\n' && c != EOF) ! ; } + /* + * Process the file line by line and create a list of list of tokens. + */ static void ! tokenize_file(FILE *file, List **lines) { char buf[MAX_TOKEN]; ! List *next_line = NIL; ! bool comment_found = false; ! while (1) { ! next_token(file, buf, sizeof(buf)); ! if (feof(file)) ! break; + /* trim off comment, even if inside a token */ + if (strstr(buf,"#") != NULL) + { + *strstr(buf,"#") = '\0'; + comment_found = true; + } + + /* add token to list */ if (buf[0] != '\0') ! { ! if (next_line == NIL) ! { ! /* make a new line List */ ! next_line = lcons(pstrdup(buf), NIL); ! *lines = lappend(*lines, next_line); ! } ! else ! /* append token to line */ ! next_line = lappend(next_line, pstrdup(buf)); ! } ! else ! /* force a new List line */ ! next_line = NIL; ! ! if (comment_found) ! { ! /* Skip the rest of the line */ ! read_to_eol(file); ! next_line = NIL; ! comment_found = false; ! } ! } ! } ! ! ! /* ! * Free memory used by lines/tokens ! */ ! static void free_lines(List **lines) ! { ! if (*lines) ! { ! List *line, *token; ! ! foreach(line, *lines) ! { ! foreach(token,lfirst(line)) ! pfree(lfirst(token)); ! freeList(lfirst(line)); ! } ! freeList(*lines); ! *lines = NULL; } + } + + /* + * Read from file FILE the rest of a host record, after the mask field, + * and return the interpretation of it as *userauth_p, auth_arg, and + * *error_p. + */ + static void + parse_hba_auth(List *line, UserAuth *userauth_p, char *auth_arg, + bool *error_p) + { + char *token = NULL; + + if (!line) + *error_p = true; + else + { + /* Get authentication type token. */ + token = lfirst(line); + 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, "krb4") == 0) + *userauth_p = uaKrb4; + else if (strcmp(token, "krb5") == 0) + *userauth_p = uaKrb5; + else if (strcmp(token, "reject") == 0) + *userauth_p = uaReject; + else if (strcmp(token, "crypt") == 0) + *userauth_p = uaCrypt; + else + *error_p = true; + } + if (!*error_p) { /* Get the authentication argument token, if any */ ! line = lnext(line); ! if (!line) auth_arg[0] = '\0'; else { ! StrNCpy(auth_arg, token, MAX_AUTH_ARG - 1); ! /* If there is more on the line, it is an error */ ! if (lnext(line)) *error_p = true; } } } + /* + * Process the non-comment lines in the config file. + * + * See if it applies to a connection to a host with IP address "*raddr" + * to a database named "*database". If so, return *found_p true + * and *userauth_p and *auth_arg as the values from the entry. + * If not, leave *found_p as it was. If the record has a syntax error, + * return *error_p true, after issuing a message to stderr. If no error, + * leave *error_p as it was. + */ static void ! parse_hba(List *line, hbaPort *port, bool *found_p, bool *error_p) { ! char *db; ! char *token; ! Assert(line != NIL); ! token = lfirst(line); /* Check the record type. */ ! if (strcmp(token, "local") == 0) { /* Get the database. */ ! line = lnext(line); ! if (!line) ! goto hba_syntax; ! db = lfirst(line); ! ! line = lnext(line); ! if (!line) ! goto hba_syntax; /* Read the rest of the line. */ ! parse_hba_auth(line, &port->auth_method, port->auth_arg, error_p); ! if (*error_p) ! goto hba_syntax; /* * For now, disallow methods that need AF_INET sockets to work. */ if (!*error_p && (port->auth_method == uaIdent || port->auth_method == uaKrb4 || port->auth_method == uaKrb5)) ! goto hba_syntax; /* * If this record isn't for our database, or this is the wrong * sort of connection, ignore it. */ if ((strcmp(db, port->database) != 0 && strcmp(db, "all") != 0 && (strcmp(db, "sameuser") != 0 || strcmp(port->database, port->user) != 0)) || port->raddr.sa.sa_family != AF_UNIX) return; } ! else if (strcmp(token, "host") == 0 || strcmp(token, "hostssl") == 0) { ! struct in_addr file_ip_addr, mask; #ifdef USE_SSL /* If SSL, then check that we are on SSL */ ! if (strcmp(token, "hostssl") == 0) { if (!port->ssl) ! return; /* Placeholder to require specific SSL level, perhaps? */ /* Or a client certificate */ *************** *** 234,300 **** } #else /* If not SSL, we don't support this */ ! if (strcmp(buf, "hostssl") == 0) ! goto syntax; #endif /* Get the database. */ ! ! next_token(file, db, sizeof(db)); ! ! if (db[0] == '\0') ! goto syntax; /* Read the IP address field. */ - next_token(file, buf, sizeof(buf)); - - if (buf[0] == '\0') - goto syntax; - /* Remember the IP address field and go get mask field. */ - if (!inet_aton(buf, &file_ip_addr)) - { - read_through_eol(file); - goto syntax; - } - /* Read the mask field. */ ! ! next_token(file, buf, sizeof(buf)); ! if (buf[0] == '\0') ! goto syntax; - if (!inet_aton(buf, &mask)) - { - read_through_eol(file); - goto syntax; - } - /* * This is the record we're looking for. Read the rest of the * info from it. */ ! ! read_hba_entry2(file, &port->auth_method, port->auth_arg, error_p); ! if (*error_p) ! goto syntax; ! ! /* ! * If told to discard earlier. Moved down here so we don't get ! * "out of sync" with the file. ! */ ! if (discard) ! return; /* * If this record isn't for our database, or this is the wrong * sort of connection, ignore it. */ - if ((strcmp(db, port->database) != 0 && strcmp(db, "all") != 0 && (strcmp(db, "sameuser") != 0 || strcmp(port->database, port->user) != 0)) || port->raddr.sa.sa_family != AF_INET || --- 296,345 ---- } #else /* If not SSL, we don't support this */ ! if (strcmp(token, "hostssl") == 0) ! goto hba_syntax; #endif /* Get the database. */ ! line = lnext(line); ! if (!line) ! goto hba_syntax; ! db = lfirst(line); /* Read the IP address field. */ + line = lnext(line); + if (!line) + goto hba_syntax; + token = lfirst(line); /* Remember the IP address field and go get mask field. */ + if (!inet_aton(token, &file_ip_addr)) + goto hba_syntax; /* Read the mask field. */ ! line = lnext(line); ! if (!line) ! goto hba_syntax; ! token = lfirst(line); ! if (!inet_aton(token, &mask)) ! goto hba_syntax; /* * This is the record we're looking for. Read the rest of the * info from it. */ ! line = lnext(line); ! if (!line) ! goto hba_syntax; ! parse_hba_auth(line, &port->auth_method, port->auth_arg, error_p); if (*error_p) ! goto hba_syntax; /* * If this record isn't for our database, or this is the wrong * sort of connection, ignore it. */ if ((strcmp(db, port->database) != 0 && strcmp(db, "all") != 0 && (strcmp(db, "sameuser") != 0 || strcmp(port->database, port->user) != 0)) || port->raddr.sa.sa_family != AF_INET || *************** *** 302,399 **** return; } else ! { ! read_through_eol(file); ! goto syntax; ! } ! *matches_p = true; ! return; ! syntax: snprintf(PQerrormsg, PQERRORMSG_LENGTH, ! "process_hba_record: invalid syntax in pg_hba.conf file\n"); fputs(PQerrormsg, stderr); pqdebug("%s", PQerrormsg); *error_p = true; } ! ! static void ! process_open_config_file(FILE *file, hbaPort *port, bool *hba_ok_p) { ! /*--------------------------------------------------------------------------- ! This function does the same thing as find_hba_entry, only with ! the config file already open on stream descriptor "file". ! ----------------------------------------------------------------------------*/ ! bool found_entry = false; /* found an applicable entry? */ ! bool error = false; /* found an erroneous entry? */ ! bool eof = false; /* end of hba file */ ! ! while (!eof && !found_entry && !error) ! { ! /* Process a line from the config file */ ! int c = getc(file); ! ! if (c == EOF) ! eof = true; ! else ! { ! ungetc(c, file); ! if (c == '#') ! read_through_eol(file); ! else ! process_hba_record(file, port, &found_entry, &error); ! } } if (!error) { /* If no matching entry was found, synthesize 'reject' entry. */ - if (!found_entry) port->auth_method = uaReject; ! ! *hba_ok_p = true; } } - - static void - find_hba_entry(hbaPort *port, bool *hba_ok_p) - { /* ! * Read the config file and find an entry that allows connection from ! * host "raddr", user "user", to database "database". If found, ! * return *hba_ok_p = true and *userauth_p and *auth_arg representing ! * the contents of that entry. If there is no matching entry, we ! * set *hba_ok_p = true, *userauth_p = uaReject. ! * ! * If the config file is unreadable or contains invalid syntax, we ! * issue a diagnostic message to stderr (ie, the postmaster log file) ! * and return without changing *hba_ok_p. ! * * If we find a file by the old name of the config file (pg_hba), we issue * an error message because it probably needs to be converted. He didn't * follow directions and just installed his old hba file in the new database * system. */ int fd, bufsize; FILE *file; /* The config file we have to read */ char *old_conf_file; ! /* The name of old config file that better not exist. */ ! ! /* Fail if config file by old name exists. */ ! ! ! /* put together the full pathname to the old config file */ bufsize = (strlen(DataDir) + strlen(OLD_CONF_FILE) + 2) * sizeof(char); old_conf_file = (char *) palloc(bufsize); snprintf(old_conf_file, bufsize, "%s/%s", DataDir, OLD_CONF_FILE); --- 347,421 ---- return; } else ! goto hba_syntax; ! /* Success */ ! *found_p = true; return; ! hba_syntax: snprintf(PQerrormsg, PQERRORMSG_LENGTH, ! "parse_hba: invalid syntax in pg_hba.conf file\n"); fputs(PQerrormsg, stderr); pqdebug("%s", PQerrormsg); *error_p = true; + return; } ! /* ! * Process the hba file line by line. ! */ ! static bool ! check_hba(hbaPort *port) { ! List *line; ! bool found_entry = false; ! bool error = false; ! ! foreach (line, hba_lines) ! { ! parse_hba(lfirst(line), port, &found_entry, &error); ! if (found_entry || error) ! break; } if (!error) { /* If no matching entry was found, synthesize 'reject' entry. */ if (!found_entry) port->auth_method = uaReject; ! return true; } + else + return false; } /* ! * Read the config file and create a List of Lists of tokens in the file. * If we find a file by the old name of the config file (pg_hba), we issue * an error message because it probably needs to be converted. He didn't * follow directions and just installed his old hba file in the new database * system. */ + static void + load_hba() + { int fd, bufsize; FILE *file; /* The config file we have to read */ char *old_conf_file; ! if (hba_lines) ! free_lines(&hba_lines); ! /* ! * The name of old config file that better not exist. ! * Fail if config file by old name exists. ! * Put together the full pathname to the old config file. ! */ bufsize = (strlen(DataDir) + strlen(OLD_CONF_FILE) + 2) * sizeof(char); old_conf_file = (char *) palloc(bufsize); snprintf(old_conf_file, bufsize, "%s/%s", DataDir, OLD_CONF_FILE); *************** *** 403,413 **** /* Old config file exists. Tell this guy he needs to upgrade. */ close(fd); snprintf(PQerrormsg, PQERRORMSG_LENGTH, ! "A file exists by the name used for host-based authentication " ! "in prior releases of Postgres (%s). The name and format of " ! "the configuration file have changed, so this file should be " ! "converted.\n", ! old_conf_file); fputs(PQerrormsg, stderr); pqdebug("%s", PQerrormsg); } --- 425,434 ---- /* Old config file exists. Tell this guy he needs to upgrade. */ close(fd); snprintf(PQerrormsg, PQERRORMSG_LENGTH, ! "A file exists by the name used for host-based authentication " ! "in prior releases of Postgres (%s). The name and format of " ! "the configuration file have changed, so this file should be " ! "converted.\n", old_conf_file); fputs(PQerrormsg, stderr); pqdebug("%s", PQerrormsg); } *************** *** 425,440 **** if (file == NULL) { /* The open of the config file failed. */ - snprintf(PQerrormsg, PQERRORMSG_LENGTH, ! "find_hba_entry: Unable to open authentication config file \"%s\": %s\n", conf_file, strerror(errno)); fputs(PQerrormsg, stderr); pqdebug("%s", PQerrormsg); } else { ! process_open_config_file(file, port, hba_ok_p); FreeFile(file); } pfree(conf_file); --- 446,460 ---- if (file == NULL) { /* The open of the config file failed. */ snprintf(PQerrormsg, PQERRORMSG_LENGTH, ! "load_hba: Unable to open authentication config file \"%s\": %s\n", conf_file, strerror(errno)); fputs(PQerrormsg, stderr); pqdebug("%s", PQerrormsg); } else { ! tokenize_file(file, &hba_lines); FreeFile(file); } pfree(conf_file); *************** *** 443,477 **** } static void ! interpret_ident_response(char *ident_response, ! bool *error_p, char *ident_username) { ! /*---------------------------------------------------------------------------- ! Parse the string "*ident_response" as a response from a query to an Ident ! server. If it's a normal response indicating a username, return ! *error_p == false and the username as *ident_username. If it's anything ! else, return *error_p == true and *ident_username undefined. ! ----------------------------------------------------------------------------*/ ! char *cursor; /* Cursor into *ident_response */ ! cursor = &ident_response[0]; /* * Ident's response, in the telnet tradition, should end in crlf * (\r\n). */ if (strlen(ident_response) < 2) ! *error_p = true; else if (ident_response[strlen(ident_response) - 2] != '\r') ! *error_p = true; else { while (*cursor != ':' && *cursor != '\r') cursor++; /* skip port field */ if (*cursor != ':') ! *error_p = true; else { /* We're positioned to colon before response type field */ --- 463,637 ---- } + /* + * Take the line and compare it to the needed map, pg_user and ident_user. + */ static void ! parse_ident_usermap(List *line, const char *usermap_name, const char *pg_user, ! const char *ident_user, bool *found_p, bool *error_p) ! { ! char *token; ! char *file_map; ! char *file_pguser; ! char *file_ident_user; ! ! *error_p = false; ! *found_p = false; ! ! /* A token read from the file */ ! Assert(line != NIL); ! token = lfirst(line); ! file_map = token; ! ! line = lnext(line); ! if (!line) ! goto ident_syntax; ! token = lfirst(line); ! if (token[0] != '\0') ! { ! file_ident_user = token; ! line = lnext(line); ! if (!line) ! goto ident_syntax; ! token = lfirst(line); ! if (token[0] != '\0') ! { ! file_pguser = token; ! if (strcmp(file_map, usermap_name) == 0 && ! strcmp(file_pguser, pg_user) == 0 && ! strcmp(file_ident_user, ident_user) == 0) ! *found_p = true; ! } ! } ! ! return; ! ! ident_syntax: ! snprintf(PQerrormsg, PQERRORMSG_LENGTH, ! "parse_ident_usermap: invalid syntax in pg_ident.conf file\n"); ! fputs(PQerrormsg, stderr); ! pqdebug("%s", PQerrormsg); ! *error_p = true; ! return; ! } ! ! ! /* ! * Process the ident usermap file line by line. ! */ ! static bool ! check_ident_usermap(const char *usermap_name, ! const char *pg_user, ! const char *ident_user) ! { ! List *line; ! bool found_entry = false, error = false; ! ! if (usermap_name[0] == '\0') ! { ! snprintf(PQerrormsg, PQERRORMSG_LENGTH, ! "load_ident_usermap: hba configuration file does not " ! "have the usermap field filled in in the entry that pertains " ! "to this connection. That field is essential for Ident-based " ! "authentication.\n"); ! fputs(PQerrormsg, stderr); ! pqdebug("%s", PQerrormsg); ! found_entry = false; ! } ! else if (strcmp(usermap_name, "sameuser") == 0) ! { ! if (strcmp(pg_user, ident_user) == 0) ! found_entry = true; ! else ! found_entry = false; ! } ! else ! { ! foreach(line, ident_lines) ! { ! parse_ident_usermap(lfirst(line), usermap_name, pg_user, ! ident_user, &found_entry, &error); ! if (found_entry || error) ! break; ! } ! } ! return found_entry; ! } ! ! ! /* ! * See if the user with ident username "ident_user" is allowed to act ! * as Postgres user "pguser" according to usermap "usermap_name". Look ! * it up in the usermap file. ! * ! * Special case: For usermap "sameuser", don't look in the usermap ! * file. That's an implied map where "pguser" must be identical to ! * "ident_user" in order to be authorized. ! * ! * Iff authorized, return *checks_out_p == true. ! */ ! static void ! load_ident() { ! FILE *file; /* The map file we have to read */ ! char *map_file; /* The name of the map file we have to ! * read */ ! int bufsize; ! if (ident_lines) ! free_lines(&ident_lines); + /* put together the full pathname to the map file */ + bufsize = (strlen(DataDir) + strlen(USERMAP_FILE) + 2) * sizeof(char); + map_file = (char *) palloc(bufsize); + snprintf(map_file, bufsize, "%s/%s", DataDir, USERMAP_FILE); + + file = AllocateFile(map_file, PG_BINARY_R); + if (file == NULL) + { + /* The open of the map file failed. */ + snprintf(PQerrormsg, PQERRORMSG_LENGTH, + "load_ident_usermap: Unable to open usermap file \"%s\": %s\n", + map_file, strerror(errno)); + fputs(PQerrormsg, stderr); + pqdebug("%s", PQerrormsg); + } + else + { + tokenize_file(file, &ident_lines); + FreeFile(file); + } + pfree(map_file); + } + + + /* + * Parse the string "*ident_response" as a response from a query to an Ident + * server. If it's a normal response indicating a username, return + * *error_p == false and the username as *ident_user. If it's anything + * else, return *error_p == true and *ident_user undefined. + */ + static bool + interpret_ident_response(char *ident_response, + char *ident_user) + { + char *cursor = ident_response;/* Cursor into *ident_response */ + /* * Ident's response, in the telnet tradition, should end in crlf * (\r\n). */ if (strlen(ident_response) < 2) ! return false; else if (ident_response[strlen(ident_response) - 2] != '\r') ! return false; else { while (*cursor != ':' && *cursor != '\r') cursor++; /* skip port field */ if (*cursor != ':') ! return false; else { /* We're positioned to colon before response type field */ *************** *** 482,505 **** while (isblank(*cursor)) cursor++; /* skip blanks */ i = 0; ! while (*cursor != ':' && *cursor != '\r' && !isblank(*cursor) ! && i < (int) (sizeof(response_type) - 1)) response_type[i++] = *cursor++; response_type[i] = '\0'; while (isblank(*cursor)) cursor++; /* skip blanks */ if (strcmp(response_type, "USERID") != 0) ! *error_p = true; else { - /* * It's a USERID response. Good. "cursor" should be * pointing to the colon that precedes the operating * system type. */ if (*cursor != ':') ! *error_p = true; else { cursor++; /* Go over colon */ --- 642,664 ---- while (isblank(*cursor)) cursor++; /* skip blanks */ i = 0; ! while (*cursor != ':' && *cursor != '\r' && !isblank(*cursor) && ! i < (int) (sizeof(response_type) - 1)) response_type[i++] = *cursor++; response_type[i] = '\0'; while (isblank(*cursor)) cursor++; /* skip blanks */ if (strcmp(response_type, "USERID") != 0) ! return false; else { /* * It's a USERID response. Good. "cursor" should be * pointing to the colon that precedes the operating * system type. */ if (*cursor != ':') ! return false; else { cursor++; /* Go over colon */ *************** *** 507,516 **** while (*cursor != ':' && *cursor != '\r') cursor++; if (*cursor != ':') ! *error_p = true; else { ! int i; /* Index into *ident_username */ cursor++; /* Go over colon */ while (isblank(*cursor)) --- 666,675 ---- while (*cursor != ':' && *cursor != '\r') cursor++; if (*cursor != ':') ! return false; else { ! int i; /* Index into *ident_user */ cursor++; /* Go over colon */ while (isblank(*cursor)) *************** *** 518,526 **** /* Rest of line is username. Copy it over. */ i = 0; while (*cursor != '\r' && i < IDENT_USERNAME_MAX) ! ident_username[i++] = *cursor++; ! ident_username[i] = '\0'; ! *error_p = false; } } } --- 677,685 ---- /* Rest of line is username. Copy it over. */ i = 0; while (*cursor != '\r' && i < IDENT_USERNAME_MAX) ! ident_user[i++] = *cursor++; ! ident_user[i] = '\0'; ! return true; } } } *************** *** 528,567 **** } } - ! static void ! ident(const struct in_addr remote_ip_addr, const struct in_addr local_ip_addr, ! const ushort remote_port, const ushort local_port, ! bool *ident_failed, char *ident_username) ! { ! /*-------------------------------------------------------------------------- ! 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 ! "local_port_addr" on host "local_ip_addr". Return the username the ! ident server gives as "*ident_username". ! ! IP addresses and port numbers are in network byte order. ! ! But iff we're unable to get the information from ident, return ! *ident_failed == true (and *ident_username undefined). ! ----------------------------------------------------------------------------*/ int sock_fd, /* File descriptor for socket on which we * talk to Ident */ rc; /* Return code from a locally called * function */ sock_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_IP); if (sock_fd == -1) { snprintf(PQerrormsg, PQERRORMSG_LENGTH, ! "Failed to create socket on which to talk to Ident server. " ! "socket() returned errno = %s (%d)\n", ! strerror(errno), errno); fputs(PQerrormsg, stderr); pqdebug("%s", PQerrormsg); } else { --- 687,726 ---- } } ! /* ! * 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 ! * "local_port_addr" on host "local_ip_addr". Return the username the ! * ident server gives as "*ident_user". + * IP addresses and port numbers are in network byte order. + * But iff we're unable to get the information from ident, return + * false. + */ + static int + ident(const struct in_addr remote_ip_addr, + const struct in_addr local_ip_addr, + const ushort remote_port, + const ushort local_port, + char *ident_user) + { int sock_fd, /* File descriptor for socket on which we * talk to Ident */ rc; /* Return code from a locally called * function */ + bool ident_return; sock_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_IP); if (sock_fd == -1) { snprintf(PQerrormsg, PQERRORMSG_LENGTH, ! "Failed to create socket on which to talk to Ident server. " ! "socket() returned errno = %s (%d)\n", strerror(errno), errno); fputs(PQerrormsg, stderr); pqdebug("%s", PQerrormsg); + ident_return = false; } else { *************** *** 595,607 **** { snprintf(PQerrormsg, PQERRORMSG_LENGTH, "Unable to connect to Ident server on the host which is " ! "trying to connect to Postgres " ! "(IP address %s, Port %d). " ! "errno = %s (%d)\n", ! inet_ntoa(remote_ip_addr), IDENT_PORT, strerror(errno), errno); fputs(PQerrormsg, stderr); pqdebug("%s", PQerrormsg); ! *ident_failed = true; } else { --- 754,766 ---- { snprintf(PQerrormsg, PQERRORMSG_LENGTH, "Unable to connect to Ident server on the host which is " ! "trying to connect to Postgres " ! "(IP address %s, Port %d). " ! "errno = %s (%d)\n", ! inet_ntoa(remote_ip_addr), IDENT_PORT, strerror(errno), errno); fputs(PQerrormsg, stderr); pqdebug("%s", PQerrormsg); ! ident_return = false; } else { *************** *** 614,873 **** if (rc < 0) { snprintf(PQerrormsg, PQERRORMSG_LENGTH, ! "Unable to send query to Ident server on the host which is " ! "trying to connect to Postgres (Host %s, Port %d)," ! "even though we successfully connected to it. " ! "errno = %s (%d)\n", ! inet_ntoa(remote_ip_addr), IDENT_PORT, strerror(errno), errno); fputs(PQerrormsg, stderr); pqdebug("%s", PQerrormsg); ! *ident_failed = true; } else { char ident_response[80 + IDENT_USERNAME_MAX]; ! rc = recv(sock_fd, ident_response, sizeof(ident_response) - 1, 0); if (rc < 0) { snprintf(PQerrormsg, PQERRORMSG_LENGTH, ! "Unable to receive response from Ident server " ! "on the host which is " ! "trying to connect to Postgres (Host %s, Port %d)," ! "even though we successfully sent our query to it. " ! "errno = %s (%d)\n", ! inet_ntoa(remote_ip_addr), IDENT_PORT, ! strerror(errno), errno); fputs(PQerrormsg, stderr); pqdebug("%s", PQerrormsg); ! *ident_failed = true; } else { - bool error; /* response from Ident is garbage. */ - ident_response[rc] = '\0'; ! interpret_ident_response(ident_response, &error, ident_username); ! *ident_failed = error; } } close(sock_fd); } - } - } - - - - static void - parse_map_record(FILE *file, - char *file_map, char *file_pguser, char *file_iuser) - { - /*--------------------------------------------------------------------------- - Take the noncomment line which is next on file "file" and interpret - it as a line in a usermap file. Specifically, return the first - 3 tokens as file_map, file_iuser, and file_pguser, respectively. If - there are fewer than 3 tokens, return null strings for the missing - ones. - - ---------------------------------------------------------------------------*/ - char buf[MAX_TOKEN]; - - /* A token read from the file */ - - /* Set defaults in case fields not in file */ - file_map[0] = '\0'; - file_pguser[0] = '\0'; - file_iuser[0] = '\0'; - - next_token(file, buf, sizeof(buf)); - if (buf[0] != '\0') - { - strcpy(file_map, buf); - next_token(file, buf, sizeof(buf)); - if (buf[0] != '\0') - { - strcpy(file_iuser, buf); - next_token(file, buf, sizeof(buf)); - if (buf[0] != '\0') - { - strcpy(file_pguser, buf); - read_through_eol(file); - return; - } - } - snprintf(PQerrormsg, PQERRORMSG_LENGTH, - "Incomplete line in pg_ident: %s", file_map); - fputs(PQerrormsg, stderr); - pqdebug("%s", PQerrormsg); } } - ! static void ! verify_against_open_usermap(FILE *file, ! const char *pguser, ! const char *ident_username, ! const char *usermap_name, ! bool *checks_out_p) ! { ! /*-------------------------------------------------------------------------- ! This function does the same thing as verify_against_usermap, ! only with the config file already open on stream descriptor "file". ! ---------------------------------------------------------------------------*/ ! bool match; /* We found a matching entry in the map ! * file */ ! bool eof; /* We've reached the end of the file we're ! * reading */ ! ! match = false; /* initial value */ ! eof = false; /* initial value */ ! while (!eof && !match) ! { ! /* Process a line from the map file */ ! ! int c; /* a character read from the file */ ! ! c = getc(file); ! ungetc(c, file); ! if (c == EOF) ! eof = true; ! else ! { ! if (c == '#') ! read_through_eol(file); ! else ! { ! /* The following are fields read from a record of the file */ ! char file_map[MAX_TOKEN + 1]; ! char file_pguser[MAX_TOKEN + 1]; ! char file_iuser[MAX_TOKEN + 1]; ! ! parse_map_record(file, file_map, file_pguser, file_iuser); ! if (strcmp(file_map, usermap_name) == 0 && ! strcmp(file_pguser, pguser) == 0 && ! strcmp(file_iuser, ident_username) == 0) ! match = true; ! } ! } ! } ! *checks_out_p = match; ! } ! ! ! ! static void ! verify_against_usermap(const char *pguser, ! const char *ident_username, ! const char *usermap_name, ! bool *checks_out_p) { ! /*-------------------------------------------------------------------------- ! See if the user with ident username "ident_username" is allowed to act ! as Postgres user "pguser" according to usermap "usermap_name". Look ! it up in the usermap file. ! ! Special case: For usermap "sameuser", don't look in the usermap ! file. That's an implied map where "pguser" must be identical to ! "ident_username" in order to be authorized. ! ! Iff authorized, return *checks_out_p == true. ! --------------------------------------------------------------------------*/ ! if (usermap_name[0] == '\0') ! { ! *checks_out_p = false; ! snprintf(PQerrormsg, PQERRORMSG_LENGTH, ! "verify_against_usermap: hba configuration file does not " ! "have the usermap field filled in in the entry that pertains " ! "to this connection. That field is essential for Ident-based " ! "authentication.\n"); ! fputs(PQerrormsg, stderr); ! pqdebug("%s", PQerrormsg); ! } ! else if (strcmp(usermap_name, "sameuser") == 0) ! { ! if (strcmp(ident_username, pguser) == 0) ! *checks_out_p = true; ! else ! *checks_out_p = false; ! } else ! { ! FILE *file; /* The map file we have to read */ ! char *map_file; /* The name of the map file we have to ! * read */ ! int bufsize; ! ! /* put together the full pathname to the map file */ ! bufsize = (strlen(DataDir) + strlen(USERMAP_FILE) + 2) * sizeof(char); ! map_file = (char *) palloc(bufsize); ! snprintf(map_file, bufsize, "%s/%s", DataDir, USERMAP_FILE); ! ! file = AllocateFile(map_file, PG_BINARY_R); ! if (file == NULL) ! { ! /* The open of the map file failed. */ ! ! snprintf(PQerrormsg, PQERRORMSG_LENGTH, ! "verify_against_usermap: Unable to open usermap file \"%s\": %s\n", ! map_file, strerror(errno)); ! fputs(PQerrormsg, stderr); ! pqdebug("%s", PQerrormsg); ! ! *checks_out_p = false; ! } ! else ! { ! verify_against_open_usermap(file, ! pguser, ident_username, usermap_name, ! checks_out_p); ! FreeFile(file); ! } ! pfree(map_file); ! ! ! } } ! int ! authident(struct sockaddr_in * raddr, struct sockaddr_in * laddr, ! const char *postgres_username, ! const char *auth_arg) { - /*--------------------------------------------------------------------------- - Talk to the ident server on the remote host and find out who owns the - connection described by "port". Then look in the usermap file under - the usermap *auth_arg and see if that user is equivalent to - Postgres user *user. - - Return STATUS_OK if yes. - ---------------------------------------------------------------------------*/ - bool checks_out; - bool ident_failed; - - /* We were unable to get ident to give us a username */ - char ident_username[IDENT_USERNAME_MAX + 1]; - - /* The username returned by ident */ - - ident(raddr->sin_addr, laddr->sin_addr, - raddr->sin_port, laddr->sin_port, - &ident_failed, ident_username); ! if (ident_failed) return STATUS_ERROR; ! ! verify_against_usermap(postgres_username, ident_username, auth_arg, ! &checks_out); ! return checks_out ? STATUS_OK : STATUS_ERROR; } #ifdef CYR_RECODE #define CHARSET_FILE "charset.conf" #define MAX_CHARSETS 10 --- 773,878 ---- if (rc < 0) { snprintf(PQerrormsg, PQERRORMSG_LENGTH, ! "Unable to send query to Ident server on the host which is " ! "trying to connect to Postgres (Host %s, Port %d)," ! "even though we successfully connected to it. " ! "errno = %s (%d)\n", ! inet_ntoa(remote_ip_addr), IDENT_PORT, strerror(errno), errno); fputs(PQerrormsg, stderr); pqdebug("%s", PQerrormsg); ! ident_return = false; } else { char ident_response[80 + IDENT_USERNAME_MAX]; ! rc = recv(sock_fd, ident_response, ! sizeof(ident_response) - 1, 0); if (rc < 0) { snprintf(PQerrormsg, PQERRORMSG_LENGTH, ! "Unable to receive response from Ident server " ! "on the host which is " ! "trying to connect to Postgres (Host %s, Port %d)," ! "even though we successfully sent our query to it. " ! "errno = %s (%d)\n", ! inet_ntoa(remote_ip_addr), IDENT_PORT, ! strerror(errno), errno); fputs(PQerrormsg, stderr); pqdebug("%s", PQerrormsg); ! ident_return = false; } else { ident_response[rc] = '\0'; ! ident_return = interpret_ident_response(ident_response, ! ident_user); } } close(sock_fd); } } + return ident_return; } ! /* ! * Talk to the ident server on the remote host and find out who owns the ! * connection described by "port". Then look in the usermap file under ! * the usermap *auth_arg and see if that user is equivalent to ! * Postgres user *user. ! * ! * Return STATUS_OK if yes. ! */ ! int ! authident(struct sockaddr_in *raddr, struct sockaddr_in *laddr, ! const char *pg_user, const char *auth_arg) { ! /* We were unable to get ident to give us a username */ ! char ident_user[IDENT_USERNAME_MAX + 1]; ! /* The username returned by ident */ ! if (!ident(raddr->sin_addr, laddr->sin_addr, ! raddr->sin_port, laddr->sin_port, ident_user)) ! return STATUS_ERROR; ! if (check_ident_usermap(auth_arg, pg_user, ident_user)) ! return STATUS_OK; else ! return STATUS_ERROR; } ! /* ! * Determine what authentication method should be used when accessing database ! * "database" from frontend "raddr", user "user". Return the method, ! * an optional argument, and STATUS_OK. ! * 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. ! */ int ! hba_getauthmethod(hbaPort *port) { ! if (check_hba(port)) ! return STATUS_OK; ! else return STATUS_ERROR; ! } ! /* ! * Clear tokenized file contents and force reload on next use. ! */ ! void load_hba_and_ident(void) ! { ! load_hba(); ! load_ident(); } + /* Character set stuff. Not sure it really belongs in this file. */ + #ifdef CYR_RECODE #define CHARSET_FILE "charset.conf" #define MAX_CHARSETS 10 *************** *** 882,889 **** char Table[MAX_TOKEN]; }; static bool ! InRange(char *buf, int host) { int valid, i, --- 887,895 ---- char Table[MAX_TOKEN]; }; + static bool ! CharSetInRange(char *buf, int host) { int valid, i, *************** *** 989,995 **** else { if (c == '#') ! read_through_eol(file); else { /* Read the key */ --- 995,1001 ---- else { if (c == '#') ! read_to_eol(file); else { /* Read the key */ *************** *** 1009,1015 **** next_token(file, buf, sizeof(buf)); if (buf[0] != '\0') { ! if (InRange(buf, host)) { /* Read the charset */ next_token(file, buf, sizeof(buf)); --- 1015,1021 ---- next_token(file, buf, sizeof(buf)); if (buf[0] != '\0') { ! if (CharSetInRange(buf, host)) { /* Read the charset */ next_token(file, buf, sizeof(buf)); *************** *** 1050,1056 **** } break; } ! read_through_eol(file); } } } --- 1056,1062 ---- } break; } ! read_to_eol(file); } } } *************** *** 1066,1088 **** pfree((struct CharsetItem *) ChArray[i]); } } - #endif - int - hba_getauthmethod(hbaPort *port) - { - /*--------------------------------------------------------------------------- - Determine what authentication method should be used when accessing database - "database" from frontend "raddr", user "user". Return the method, - an optional argument, and STATUS_OK. - 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. - ----------------------------------------------------------------------------*/ - bool hba_ok = false; - find_hba_entry(port, &hba_ok); - return hba_ok ? STATUS_OK : STATUS_ERROR; - } --- 1072,1078 ---- Index: src/backend/postmaster/postmaster.c =================================================================== RCS file: /home/projects/pgsql/cvsroot/pgsql/src/backend/postmaster/postmaster.c,v retrieving revision 1.231 diff -c -r1.231 postmaster.c *** src/backend/postmaster/postmaster.c 2001/07/03 16:52:12 1.231 --- src/backend/postmaster/postmaster.c 2001/07/21 00:12:43 *************** *** 809,814 **** --- 809,816 ---- nSockets = initMasks(&readmask, &writemask); + load_hba_and_ident(); + for (;;) { Port *port; *************** *** 874,879 **** --- 876,882 ---- if (got_SIGHUP) { got_SIGHUP = false; + load_hba_and_ident(); ProcessConfigFile(PGC_SIGHUP); } *************** *** 993,999 **** buf = palloc(len); pq_getbytes(buf, len); ! packet = buf; /* --- 996,1002 ---- buf = palloc(len); pq_getbytes(buf, len); ! packet = buf; /* *************** *** 1479,1485 **** #endif /* * Check if this child was the statistics collector. If ! * so, start a new one. */ if (pgstat_ispgstat(pid)) { --- 1482,1488 ---- #endif /* * Check if this child was the statistics collector. If ! * so, start a new one. */ if (pgstat_ispgstat(pid)) { *************** *** 1987,2004 **** av[ac++] = "-o"; av[ac++] = ttybuf; } - av[ac] = (char *) NULL; - - /* - * Release postmaster's working memory context so that backend can - * recycle the space. Note this does not trash *MyProcPort, because - * ConnCreate() allocated that space with malloc() ... else we'd need - * to copy the Port data here. - */ - MemoryContextSwitchTo(TopMemoryContext); - MemoryContextDelete(PostmasterContext); - PostmasterContext = NULL; /* * Debug: print arguments being passed to backend --- 1990,1996 ---- Index: src/backend/tcop/postgres.c =================================================================== RCS file: /home/projects/pgsql/cvsroot/pgsql/src/backend/tcop/postgres.c,v retrieving revision 1.227 diff -c -r1.227 postgres.c *** src/backend/tcop/postgres.c 2001/06/29 16:05:56 1.227 --- src/backend/tcop/postgres.c 2001/07/21 00:12:44 *************** *** 1120,1126 **** unsigned short remote_port; char *potential_DataDir = NULL; ! /* * Catch standard options before doing much else. This even works on * systems without getopt_long. --- 1120,1126 ---- unsigned short remote_port; char *potential_DataDir = NULL; ! /* * Catch standard options before doing much else. This even works on * systems without getopt_long. *************** *** 1144,1158 **** * * If we are running under the postmaster, this is done already. */ ! if (!IsUnderPostmaster) { SetProcessingMode(InitProcessing); EnableExceptionHandling(true); MemoryContextInit(); } - - if (IsUnderPostmaster) - ClientAuthentication(MyProcPort); /* might not return */ /* * Set default values for command-line options. --- 1144,1169 ---- * * If we are running under the postmaster, this is done already. */ ! if (IsUnderPostmaster) ! { ! MemoryContextSwitchTo(TopMemoryContext); ! ClientAuthentication(MyProcPort); /* might not return */ ! /* ! * Release postmaster's working memory context so that backend can ! * recycle the space. Note this does not trash *MyProcPort, because ! * ConnCreate() allocated that space with malloc() ... else we'd need ! * to copy the Port data here. We delete it here because the ! * authorization file tokens are stored in this context. ! */ ! MemoryContextDelete(PostmasterContext); ! PostmasterContext = NULL; ! } ! else { SetProcessingMode(InitProcessing); EnableExceptionHandling(true); MemoryContextInit(); } /* * Set default values for command-line options. Index: src/include/libpq/hba.h =================================================================== RCS file: /home/projects/pgsql/cvsroot/pgsql/src/include/libpq/hba.h,v retrieving revision 1.19 diff -c -r1.19 hba.h *** src/include/libpq/hba.h 2001/03/22 04:00:47 1.19 --- src/include/libpq/hba.h 2001/07/21 00:12:45 *************** *** 40,47 **** typedef struct Port hbaPort; ! int hba_getauthmethod(hbaPort *port); int authident(struct sockaddr_in * raddr, struct sockaddr_in * laddr, const char *postgres_username, const char *auth_arg); #endif --- 40,48 ---- typedef struct Port hbaPort; ! int hba_getauthmethod(hbaPort *port); int authident(struct sockaddr_in * raddr, struct sockaddr_in * laddr, const char *postgres_username, const char *auth_arg); + void load_hba_and_ident(void); #endif
pgsql-patches by date: