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 */

Attachment: signature.asc
Description: This is a digitally signed message part.

Reply via email to