Hi all, this patch adds the possibility to map the login-rolename to a different rolename actually used for permissions.
What is it used for? I'm working with smartcard based TLS-authentication to connect to the PG server. Authentication is done with the keys and certificates from the card within the TLS handshake. Certificate-CN and login-username have to be the same or have to match by the "pg_ident.conf". The role actually used for permissions is always the login-username. This patch allowes, to change the actually permissions to a role based on the certificate-CN. It is realised by an additional column in "pg_ident.conf". When using ODBC, you have to setup a fixed username which is used for login. Different permissions depending on the CN of the certificate on the current smartcard could be achieved by the following line: # MAPNAME SYSTEM-USERNAME PG-USERNAME EFFECTIVE-USERNAME ssl-user /(.*) dummy \1 The extension could be similar used for kerberos authentication, too. Bytheway I refactored the pg_ident-code a little bit, to avoid duplicated code and to allow substitution of more than one match (\2, \3 etc). Questions (I'm quite new to the PG-sources and used to write Ruby code): - Is this something useful - or is there a much easier way? - Are there any implementation shortcomings? regards Lars Kanis
diff -ur postgresql-8.4rc1.orig/src/backend/libpq/auth.c postgresql-8.4rc1/src/backend/libpq/auth.c --- postgresql-8.4rc1.orig/src/backend/libpq/auth.c 2009-06-11 16:48:57.000000000 +0200 +++ postgresql-8.4rc1/src/backend/libpq/auth.c 2009-06-29 14:02:40.000000000 +0200 @@ -777,7 +777,7 @@ } ret = check_usermap(port->hba->usermap, port->user_name, kusername, - pg_krb_caseins_users); + pg_krb_caseins_users, &port->user_name); krb5_free_ticket(pg_krb5_context, ticket); krb5_auth_con_free(pg_krb5_context, auth_context); @@ -1069,7 +1069,7 @@ } ret = check_usermap(port->hba->usermap, port->user_name, gbuf.value, - pg_krb_caseins_users); + pg_krb_caseins_users, &port->user_name); gss_release_buffer(&lmin_s, &gbuf); @@ -1360,12 +1360,12 @@ namebuf = palloc(strlen(accountname) + strlen(domainname) + 2); sprintf(namebuf, "%...@%s", accountname, domainname); - retval = check_usermap(port->hba->usermap, port->user_name, namebuf, true); + retval = check_usermap(port->hba->usermap, port->user_name, namebuf, true, &port->user_name); pfree(namebuf); return retval; } else - return check_usermap(port->hba->usermap, port->user_name, accountname, true); + return check_usermap(port->hba->usermap, port->user_name, accountname, true, &port->user_name); } #endif /* ENABLE_SSPI */ @@ -1847,7 +1847,7 @@ return STATUS_ERROR; } - return check_usermap(port->hba->usermap, port->user_name, ident_user, false); + return check_usermap(port->hba->usermap, port->user_name, ident_user, false, &port->user_name); } @@ -2184,9 +2184,9 @@ port->user_name))); return STATUS_ERROR; } - + /* Just pass the certificate CN to the usermap check */ - return check_usermap(port->hba->usermap, port->user_name, port->peer_cn, false); + return check_usermap(port->hba->usermap, port->user_name, port->peer_cn, false, &port->user_name); } #endif diff -ur postgresql-8.4rc1.orig/src/backend/libpq/hba.c postgresql-8.4rc1/src/backend/libpq/hba.c --- postgresql-8.4rc1.orig/src/backend/libpq/hba.c 2009-06-11 16:48:58.000000000 +0200 +++ postgresql-8.4rc1/src/backend/libpq/hba.c 2009-06-29 15:08:08.000000000 +0200 @@ -31,6 +31,7 @@ #include "storage/fd.h" #include "utils/flatfiles.h" #include "utils/guc.h" +#include "utils/memutils.h" @@ -1418,6 +1419,68 @@ return true; } +/* case (in-)sensitive string compare */ +static int strcmp_with_case( const char *str1, const char *str2, bool case_insensitive ){ + if (case_insensitive) + { + return pg_strcasecmp(str1, str2); + } + else + { + return strcmp(str1, str2); + } +} + +/* + Substitudes "\1", "\2", etc. within subst_in_str, based on the regexp-matches in extract_from_str. + + returns substituded string. It has to be pfree'd. +*/ +static char *regexp_substitude(size_t nr_matches, regmatch_t *matches, const char *extract_from_str, const char *subst_in_str){ + char *ofs; + char *psubst_out_str; + int nr_match; + char *psubst_in_str; + + psubst_in_str = psubst_out_str = pstrdup(subst_in_str); + + for(nr_match = 1; nr_match <= nr_matches; nr_match++){ + char subst_marker[5]; + + snprintf(subst_marker, sizeof(subst_marker), "\\%d", nr_match); + + if ((ofs = strstr(psubst_in_str, subst_marker)) != NULL) + { + /* substitution of the first argument requested */ + if (matches[nr_match].rm_so < 0) + { + pfree(psubst_in_str); + return NULL; + } + + /* + * length: original length minus length of \1 plus length of match + * plus null terminator + */ + psubst_out_str = palloc0(strlen(psubst_in_str) - 2 + (matches[nr_match].rm_eo - matches[nr_match].rm_so) + 1); + strncpy(psubst_out_str, psubst_in_str, (ofs - psubst_in_str)); + memcpy(psubst_out_str + strlen(psubst_out_str), + extract_from_str + matches[nr_match].rm_so, + matches[nr_match].rm_eo - matches[nr_match].rm_so); + strcat(psubst_out_str, ofs + 2); + } + else + { + /* no substitution, so copy the match */ + psubst_out_str = pstrdup(psubst_in_str); + } + pfree(psubst_in_str); + psubst_in_str = psubst_out_str; + } + return psubst_out_str; +} + + /* * Process one line from the ident config file. * @@ -1427,12 +1490,13 @@ static void parse_ident_usermap(List *line, int line_number, const char *usermap_name, const char *pg_role, const char *ident_user, - bool case_insensitive, bool *found_p, bool *error_p) + bool case_insensitive, bool *found_p, bool *error_p, const char **effective_role) { ListCell *line_item; char *token; char *file_map; char *file_pgrole; + char *file_effective_role = NULL; char *file_ident_user; *found_p = false; @@ -1459,6 +1523,11 @@ token = lfirst(line_item); file_pgrole = token; + /* Get the PG effective role token */ + line_item = lnext(line_item); + if (line_item) + file_effective_role = lfirst(line_item); + if (strcmp(file_map, usermap_name) != 0) /* Line does not match the map name we're looking for, so just abort */ return; @@ -1474,10 +1543,9 @@ */ int r; regex_t re; - regmatch_t matches[2]; + regmatch_t matches[10]; pg_wchar *wstr; int wlen; - char *ofs; char *regexp_pgrole; wstr = palloc((strlen(file_ident_user + 1) + 1) * sizeof(pg_wchar)); @@ -1506,7 +1574,7 @@ wstr = palloc((strlen(ident_user) + 1) * sizeof(pg_wchar)); wlen = pg_mb2wchar_with_len(ident_user, wstr, strlen(ident_user)); - r = pg_regexec(&re, wstr, wlen, 0, NULL, 2, matches, 0); + r = pg_regexec(&re, wstr, wlen, 0, NULL, sizeof(matches)/sizeof(*matches), matches, 0); if (r) { char errstr[100]; @@ -1527,30 +1595,18 @@ } pfree(wstr); - if ((ofs = strstr(file_pgrole, "\\1")) != NULL) + regexp_pgrole = regexp_substitude(sizeof(matches)/sizeof(*matches), matches, ident_user, file_pgrole); + + if (!regexp_pgrole) { - /* substitution of the first argument requested */ - if (matches[1].rm_so < 0) - ereport(ERROR, - (errcode(ERRCODE_INVALID_REGULAR_EXPRESSION), - errmsg("regular expression \"%s\" has no subexpressions as requested by backreference in \"%s\"", - file_ident_user + 1, file_pgrole))); - - /* - * length: original length minus length of \1 plus length of match - * plus null terminator - */ - regexp_pgrole = palloc0(strlen(file_pgrole) - 2 + (matches[1].rm_eo - matches[1].rm_so) + 1); - strncpy(regexp_pgrole, file_pgrole, (ofs - file_pgrole)); - memcpy(regexp_pgrole + strlen(regexp_pgrole), - ident_user + matches[1].rm_so, - matches[1].rm_eo - matches[1].rm_so); - strcat(regexp_pgrole, ofs + 2); - } - else - { - /* no substitution, so copy the match */ - regexp_pgrole = pstrdup(file_pgrole); + ereport(ERROR, + (errcode(ERRCODE_INVALID_REGULAR_EXPRESSION), + errmsg("regular expression \"%s\" has no subexpressions as requested by backreference in \"%s\"", + file_ident_user + 1, file_pgrole))); + + pg_regfree(&re); + *error_p = true; + return; } pg_regfree(&re); @@ -1559,15 +1615,28 @@ * now check if the username actually matched what the user is trying * to connect as */ - if (case_insensitive) - { - if (pg_strcasecmp(regexp_pgrole, pg_role) == 0) - *found_p = true; - } - else + if (strcmp_with_case(regexp_pgrole, pg_role, case_insensitive) == 0) { - if (strcmp(regexp_pgrole, pg_role) == 0) - *found_p = true; + if (file_effective_role) + { + char *eff_role; + + eff_role = regexp_substitude(sizeof(matches)/sizeof(*matches), matches, ident_user, file_effective_role); + if (!eff_role) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_REGULAR_EXPRESSION), + errmsg("regular expression \"%s\" has no subexpressions as requested by backreference in \"%s\"", + file_ident_user + 1, file_effective_role))); + + *error_p = true; + return; + } + + *effective_role = MemoryContextStrdup(TopMemoryContext, eff_role); + } + + *found_p = true; } pfree(regexp_pgrole); @@ -1576,17 +1645,13 @@ else { /* Not regular expression, so make complete match */ - if (case_insensitive) - { - if (pg_strcasecmp(file_pgrole, pg_role) == 0 && - pg_strcasecmp(file_ident_user, ident_user) == 0) - *found_p = true; - } - else + if (strcmp_with_case(file_pgrole, pg_role, case_insensitive) == 0 && + strcmp_with_case(file_ident_user, ident_user, case_insensitive) == 0) { - if (strcmp(file_pgrole, pg_role) == 0 && - strcmp(file_ident_user, ident_user) == 0) - *found_p = true; + if (file_effective_role) + *effective_role = MemoryContextStrdup(TopMemoryContext, file_effective_role); + + *found_p = true; } } @@ -1618,10 +1683,12 @@ check_usermap(const char *usermap_name, const char *pg_role, const char *auth_user, - bool case_insensitive) + bool case_insensitive, + const char **effective_role) { bool found_entry = false, - error = false; + error = false; + *effective_role = pg_role; if (usermap_name == NULL || usermap_name[0] == '\0') { @@ -1649,7 +1716,7 @@ { parse_ident_usermap(lfirst(line_cell), lfirst_int(num_cell), usermap_name, pg_role, auth_user, case_insensitive, - &found_entry, &error); + &found_entry, &error, effective_role); if (found_entry || error) break; } diff -ur postgresql-8.4rc1.orig/src/include/libpq/hba.h postgresql-8.4rc1/src/include/libpq/hba.h --- postgresql-8.4rc1.orig/src/include/libpq/hba.h 2009-06-11 16:49:11.000000000 +0200 +++ postgresql-8.4rc1/src/include/libpq/hba.h 2009-06-25 13:24:41.000000000 +0200 @@ -72,7 +72,7 @@ Oid *dbtablespace, TransactionId *dbfrozenxid); extern int check_usermap(const char *usermap_name, const char *pg_role, const char *auth_user, - bool case_sensitive); + bool case_sensitive, const char **effective_role); extern bool pg_isblank(const char c); #endif /* HBA_H */
signature.asc
Description: This is a digitally signed message part.