>From 5aac96df52a8d006f70c4fa3f799fc2239ccb57b Mon Sep 17 00:00:00 2001
From: Craig Ringer <craig@2ndquadrant.com>
Date: Fri, 17 Oct 2014 10:15:40 +0800
Subject: [PATCH 2/2] Log a hint if pg_ident.conf or pg_hba.conf changed since
 last reload

Users often get tripped up by the fact that you have to reload the
server config to have changes to pg_hba.conf take effect. To help
them out, we now emit a HINT message when authentication fails and
pg_hba.conf or pg_ident.conf are stale, telling them that they
should check the server error log for details.

In the server error log we report that pg_hba.conf or pg_ident.conf
have changed since the last time the server configuration was
reloaded, and that they should reload the config.

No attempt is made to determine whether the change to the config files is
relevant. This is done purely based on timestamp comparisons. If the change
isn't related to the connection issue they're having then at worst they'll
reload the config file and get the same error sans the HINT.
---
 src/backend/libpq/auth.c | 70 +++++++++++++++++++++++++++++++++++++++++++-----
 src/backend/libpq/hba.c  |  8 ++++--
 src/include/libpq/auth.h |  2 ++
 3 files changed, 71 insertions(+), 9 deletions(-)

diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
new file mode 100644
index 9ad99ce..b38b3d6
*** a/src/backend/libpq/auth.c
--- b/src/backend/libpq/auth.c
***************
*** 16,24 ****
--- 16,26 ----
  #include "postgres.h"
  
  #include <sys/param.h>
+ #include <sys/stat.h>
  #include <sys/socket.h>
  #include <netinet/in.h>
  #include <arpa/inet.h>
+ #include <time.h>
  #include <unistd.h>
  
  #include "libpq/auth.h"
***************
*** 30,35 ****
--- 32,39 ----
  #include "miscadmin.h"
  #include "replication/walsender.h"
  #include "storage/ipc.h"
+ #include "utils/guc.h"
+ #include "utils/timestamp.h"
  
  
  /*----------------------------------------------------------------
*************** static void auth_failed(Port *port, int
*** 41,46 ****
--- 45,52 ----
  static char *recv_password_packet(Port *port);
  static int	recv_and_check_password_packet(Port *port, char **logdetail);
  
+ static int errhint_if_hba_conf_stale(void);
+ 
  
  /*----------------------------------------------------------------
   * Ident authentication
*************** auth_failed(Port *port, int status, char
*** 282,293 ****
  	ereport(FATAL,
  			(errcode(errcode_return),
  			 errmsg(errstr, port->user_name),
! 			 logdetail ? errdetail_log("%s", logdetail) : 0));
  
  	/* doesn't return */
  }
  
- 
  /*
   * Client authentication starts here.  If there is an error, this
   * function does not return and the backend process is terminated.
--- 288,299 ----
  	ereport(FATAL,
  			(errcode(errcode_return),
  			 errmsg(errstr, port->user_name),
! 			 logdetail ? errdetail_log("%s", logdetail) : 0,
! 			 errhint_if_hba_conf_stale()));
  
  	/* doesn't return */
  }
  
  /*
   * Client authentication starts here.  If there is an error, this
   * function does not return and the backend process is terminated.
*************** ClientAuthentication(Port *port)
*** 334,340 ****
  		{
  			ereport(FATAL,
  					(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
! 				  errmsg("connection requires a valid client certificate")));
  		}
  #else
  
--- 340,347 ----
  		{
  			ereport(FATAL,
  					(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
! 				  errmsg("connection requires a valid client certificate"),
! 				  errhint_if_hba_conf_stale()));
  		}
  #else
  
*************** ClientAuthentication(Port *port)
*** 378,389 ****
  					   (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
  						errmsg("pg_hba.conf rejects replication connection for host \"%s\", user \"%s\", %s",
  							   hostinfo, port->user_name,
! 							   port->ssl_in_use ? _("SSL on") : _("SSL off"))));
  #else
  					ereport(FATAL,
  					   (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
  						errmsg("pg_hba.conf rejects replication connection for host \"%s\", user \"%s\"",
! 							   hostinfo, port->user_name)));
  #endif
  				}
  				else
--- 385,398 ----
  					   (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
  						errmsg("pg_hba.conf rejects replication connection for host \"%s\", user \"%s\", %s",
  							   hostinfo, port->user_name,
! 							   port->ssl_in_use ? _("SSL on") : _("SSL off")),
! 						errhint_if_hba_conf_stale()));
  #else
  					ereport(FATAL,
  					   (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
  						errmsg("pg_hba.conf rejects replication connection for host \"%s\", user \"%s\"",
! 							   hostinfo, port->user_name),
! 						errhint_if_hba_conf_stale()));
  #endif
  				}
  				else
*************** ClientAuthentication(Port *port)
*** 394,406 ****
  						errmsg("pg_hba.conf rejects connection for host \"%s\", user \"%s\", database \"%s\", %s",
  							   hostinfo, port->user_name,
  							   port->database_name,
! 							   port->ssl_in_use ? _("SSL on") : _("SSL off"))));
  #else
  					ereport(FATAL,
  					   (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
  						errmsg("pg_hba.conf rejects connection for host \"%s\", user \"%s\", database \"%s\"",
  							   hostinfo, port->user_name,
! 							   port->database_name)));
  #endif
  				}
  				break;
--- 403,417 ----
  						errmsg("pg_hba.conf rejects connection for host \"%s\", user \"%s\", database \"%s\", %s",
  							   hostinfo, port->user_name,
  							   port->database_name,
! 							   port->ssl_in_use ? _("SSL on") : _("SSL off")),
! 						errhint_if_hba_conf_stale()));
  #else
  					ereport(FATAL,
  					   (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
  						errmsg("pg_hba.conf rejects connection for host \"%s\", user \"%s\", database \"%s\"",
  							   hostinfo, port->user_name,
! 							   port->database_name),
! 						errhint_if_hba_conf_stale()));
  #endif
  				}
  				break;
*************** ClientAuthentication(Port *port)
*** 453,464 ****
--- 464,477 ----
  						errmsg("no pg_hba.conf entry for replication connection from host \"%s\", user \"%s\", %s",
  							   hostinfo, port->user_name,
  							   port->ssl_in_use ? _("SSL on") : _("SSL off")),
+ 						errhint_if_hba_conf_stale(),
  						HOSTNAME_LOOKUP_DETAIL(port)));
  #else
  					ereport(FATAL,
  					   (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
  						errmsg("no pg_hba.conf entry for replication connection from host \"%s\", user \"%s\"",
  							   hostinfo, port->user_name),
+ 						errhint_if_hba_conf_stale(),
  						HOSTNAME_LOOKUP_DETAIL(port)));
  #endif
  				}
*************** ClientAuthentication(Port *port)
*** 471,476 ****
--- 484,490 ----
  							   hostinfo, port->user_name,
  							   port->database_name,
  							   port->ssl_in_use ? _("SSL on") : _("SSL off")),
+ 						errhint_if_hba_conf_stale(),
  						HOSTNAME_LOOKUP_DETAIL(port)));
  #else
  					ereport(FATAL,
*************** ClientAuthentication(Port *port)
*** 478,483 ****
--- 492,498 ----
  						errmsg("no pg_hba.conf entry for host \"%s\", user \"%s\", database \"%s\"",
  							   hostinfo, port->user_name,
  							   port->database_name),
+ 						errhint_if_hba_conf_stale(),
  						HOSTNAME_LOOKUP_DETAIL(port)));
  #endif
  				}
*************** recv_password_packet(Port *port)
*** 684,689 ****
--- 699,745 ----
  	return buf.data;
  }
  
+ /* See errhint_if_hba_conf_stale */
+ static int
+ errhint_if_cfg_stale(const char * cfgpath, const char * cfgfile)
+ {
+ 	struct stat	statbuf;
+ 
+ 	if (stat(cfgpath, &statbuf) == 0)
+ 	{
+ 		if (difftime(statbuf.st_mtime, timestamptz_to_time_t(PgReloadTime)) > 0)
+ 		{
+ 			/*
+ 			 * We intentionally only log into the server's log, and not
+ 			 * leaking this to the client.
+ 			 */
+ 			return errhint_log("%s has been changed since last server configuration reload. Reload the server configuration to apply the changes.",
+ 				cfgfile);
+ 		}
+ 	}
+ 	return 0;
+ }
+ 
+ /*
+  * If pg_hba.conf has been modified since the last reload, emit a HINT to the
+  * server error log informing the user that pg_hba.conf has changed since the
+  * last reload.
+  */
+ static int
+ errhint_if_hba_conf_stale()
+ {
+ 	return errhint_if_cfg_stale(HbaFileName, "pg_hba.conf");
+ }
+ 
+ /*
+  * Like errhint_if_hba_conf_stale but for pg_ident.conf
+  */
+ int
+ errhint_if_ident_conf_stale()
+ {
+ 	return errhint_if_cfg_stale(IdentFileName, "pg_ident.conf");
+ }
+ 
  
  /*----------------------------------------------------------------
   * MD5 authentication
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
new file mode 100644
index d43c8ff..c5b5d3d
*** a/src/backend/libpq/hba.c
--- b/src/backend/libpq/hba.c
***************
*** 28,39 ****
--- 28,41 ----
  #include "catalog/pg_collation.h"
  #include "libpq/ip.h"
  #include "libpq/libpq.h"
+ #include "libpq/auth.h"
  #include "postmaster/postmaster.h"
  #include "regex/regex.h"
  #include "replication/walsender.h"
  #include "storage/fd.h"
  #include "utils/acl.h"
  #include "utils/guc.h"
+ #include "utils/timestamp.h"
  #include "utils/lsyscache.h"
  #include "utils/memutils.h"
  
*************** check_usermap(const char *usermap_name,
*** 2098,2104 ****
  		}
  		ereport(LOG,
  				(errmsg("provided user name (%s) and authenticated user name (%s) do not match",
! 						pg_role, auth_user)));
  		return STATUS_ERROR;
  	}
  	else
--- 2100,2107 ----
  		}
  		ereport(LOG,
  				(errmsg("provided user name (%s) and authenticated user name (%s) do not match",
! 						pg_role, auth_user),
! 				 errhint_if_ident_conf_stale()));
  		return STATUS_ERROR;
  	}
  	else
*************** check_usermap(const char *usermap_name,
*** 2118,2124 ****
  	{
  		ereport(LOG,
  				(errmsg("no match in usermap \"%s\" for user \"%s\" authenticated as \"%s\"",
! 						usermap_name, pg_role, auth_user)));
  	}
  	return found_entry ? STATUS_OK : STATUS_ERROR;
  }
--- 2121,2128 ----
  	{
  		ereport(LOG,
  				(errmsg("no match in usermap \"%s\" for user \"%s\" authenticated as \"%s\"",
! 						usermap_name, pg_role, auth_user),
! 				 errhint_if_ident_conf_stale()));
  	}
  	return found_entry ? STATUS_OK : STATUS_ERROR;
  }
diff --git a/src/include/libpq/auth.h b/src/include/libpq/auth.h
new file mode 100644
index ace647a..cfc6d9c
*** a/src/include/libpq/auth.h
--- b/src/include/libpq/auth.h
*************** extern char *pg_krb_realm;
*** 22,27 ****
--- 22,29 ----
  
  extern void ClientAuthentication(Port *port);
  
+ extern int errhint_if_ident_conf_stale(void);
+ 
  /* Hook for plugins to get control in ClientAuthentication() */
  typedef void (*ClientAuthentication_hook_type) (Port *, int);
  extern PGDLLIMPORT ClientAuthentication_hook_type ClientAuthentication_hook;
-- 
2.1.0

