text: /*-------------------------------------------------------------------------
   *
   * alert.c
   *
   * Send alerts via SMTP (email) or SNMP INFORM messsages.
   *
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you under the Apache License, Version 2.0 (the
   * "License"); you may not use this file except in compliance
   * with the License.  You may obtain a copy of the License at
   *
   *   http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing,
   * software distributed under the License is distributed on an
   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   * KIND, either express or implied.  See the License for the
   * specific language governing permissions and limitations
   * under the License.
   *
   *-------------------------------------------------------------------------
   */
  #if !defined(_XOPEN_SOURCE) || _XOPEN_SOURCE<600
  #undef _XOPEN_SOURCE
  #define _XOPEN_SOURCE 600
  #endif
  #if !defined(_POSIX_C_SOURCE) || _POSIX_C_SOURCE<200112L
  #undef _POSIX_C_SOURCE
  /* Define to activate features from IEEE Stds 1003.1-2001 */
  #define _POSIX_C_SOURCE 200112L
  #endif
  #include "postgres.h"
  #include "pg_config.h"  /* Adding this helps eclipse see that USE_EMAIL and USE_SNMP are set */
  
  #include <fcntl.h>
  #include <signal.h>
  #include <time.h>
  #include <unistd.h>
  #include <sys/stat.h>
  #include <sys/time.h>
  #include <stdio.h>
  
  #include <sys/socket.h>
  #include <netdb.h>
  #include <netinet/in.h>
  #ifdef HAVE_NETINET_TCP_H
  #include <netinet/tcp.h>
  #endif
  #include <arpa/inet.h>
  
  #include "lib/stringinfo.h"
  
  #include "pgtime.h"
  
  #include "postmaster/syslogger.h"
  #include "postmaster/sendalert.h"
  #include "utils/guc.h"
  #include "utils/elog.h"
  #include "utils/builtins.h"
  #include "sendalert_common.h"
  
  extern int	PostPortNumber;
  
  #ifdef USE_EMAIL
  #ifdef USE_SSL
  #include <openssl/ssl.h>
  #endif
  /* SASL (RFC 2222) client library API */
  #include <auth-client.h>
  #include <libesmtp.h>
  #endif
  
  #if USE_SNMP
  #include <net-snmp/net-snmp-config.h>
  #include <net-snmp/definitions.h>
  #include <net-snmp/types.h>
  
  //#include <net-snmp/utilities.h>
  #include <net-snmp/session_api.h>
  #include <net-snmp/pdu_api.h>
  #include <net-snmp/mib_api.h>
  #include <net-snmp/varbind_api.h>
  //#include <net-snmp/config_api.h>
  #include <net-snmp/output_api.h>
  
  
  oid             objid_enterprise[] = { 1, 3, 6, 1, 4, 1, 3, 1, 1 };
  oid             objid_sysdescr[] = { 1, 3, 6, 1, 2, 1, 1, 1, 0 };
  oid             objid_sysuptime[] = { 1, 3, 6, 1, 2, 1, 1, 3, 0 };
  oid             objid_snmptrap[] = { 1, 3, 6, 1, 6, 3, 1, 1, 4, 1, 0 };
  oid				objid_rdbmsrelstate[] = { 1, 3, 6, 1, 2, 1, 39, 1, 9, 1, 1 };
  
  // {iso(1) identified-organization(3) dod(6) internet(1) private(4) enterprises(1) gpdbMIB(31327) gpdbObjects(1) gpdbAlertMsg(1)}
  oid				objid_gpdbAlertMsg[] = { 1, 3, 6, 1, 4, 1, 31327, 1, 1 };
  oid				objid_gpdbAlertSeverity[] = { 1, 3, 6, 1, 4, 1, 31327, 1, 2 };
  oid				objid_gpdbAlertSqlstate[] = { 1, 3, 6, 1, 4, 1, 31327, 1, 3 };
  oid				objid_gpdbAlertDetail[] = { 1, 3, 6, 1, 4, 1, 31327, 1, 4 };
  oid				objid_gpdbAlertSqlStmt[] = { 1, 3, 6, 1, 4, 1, 31327, 1, 5 };
  oid				objid_gpdbAlertSystemName[] = { 1, 3, 6, 1, 4, 1, 31327, 1, 6 };
  #endif
  
  static bool SplitString(char *rawstring, char delimiter, List **namelist);
  
  #ifdef USE_EMAIL
  
  static char * get_str_from_chunk(CSVChunkStr *chunkstr,
  		const PipeProtoChunk *saved_chunks);
  static const char * messagebody_cb(void **buf, int *len, void *arg);
  static void print_recipient_status(smtp_recipient_t recipient,
  		const char *mailbox, void *arg  __attribute__((unused)));
  static int authinteract(auth_client_request_t request, char **result,
  		int fields, void *arg);
  static int tlsinteract(char *buf, int buflen, int rwflag  __attribute__((unused)), void *arg  __attribute__((unused)));
  static int handle_invalid_peer_certificate(long vfy_result);
  static void event_cb(smtp_session_t session, int event_no, void *arg, ...);
  static void monitor_cb (const char *buf, int buflen, int writing, void *arg);
  
  static int send_alert_via_email(const GpErrorData * errorData, const char * subject, const char * email_priority);
  #endif
  
  #if USE_SNMP
  static int send_snmp_inform_or_trap();
  extern pg_time_t	MyStartTime;
  #endif
  
  
  #ifdef USE_EMAIL
  
  int send_alert_from_chunks(const PipeProtoChunk *chunk,
  		const PipeProtoChunk * saved_chunks_in)
  {
  
  	int ret = -1;
  	GpErrorData errorData;
  
  	CSVChunkStr chunkstr =
  	{ chunk, chunk->data + sizeof(GpErrorDataFixFields) };
  
  	memset(&errorData, 0, sizeof(errorData));
  
  	memcpy(&errorData.fix_fields, chunk->data, sizeof(errorData.fix_fields));
  
  	if (chunk == NULL)
  		return -1;
  	if (chunk->hdr.len == 0)
  		return -1;
  	if (chunk->hdr.zero != 0)
  		return -1;
  
  	if (chunk->hdr.log_format != 'c')
  		elog(ERROR,"send_alert_from_chunks only works when CSV logging is enabled");
  
  	errorData.username = get_str_from_chunk(&chunkstr,saved_chunks_in);
  	errorData.databasename = get_str_from_chunk(&chunkstr,saved_chunks_in);
  	errorData.remote_host = get_str_from_chunk(&chunkstr,saved_chunks_in);
  	errorData.remote_port = get_str_from_chunk(&chunkstr,saved_chunks_in);
  	errorData.error_severity = get_str_from_chunk(&chunkstr,saved_chunks_in);
  	errorData.sql_state = get_str_from_chunk(&chunkstr,saved_chunks_in);
  	errorData.error_message = get_str_from_chunk(&chunkstr,saved_chunks_in);
  	errorData.error_detail = get_str_from_chunk(&chunkstr,saved_chunks_in);
  	errorData.error_hint = get_str_from_chunk(&chunkstr,saved_chunks_in);
  	errorData.internal_query = get_str_from_chunk(&chunkstr,saved_chunks_in);
  	errorData.error_context = get_str_from_chunk(&chunkstr,saved_chunks_in);
  	errorData.debug_query_string = get_str_from_chunk(&chunkstr,saved_chunks_in);
  	errorData.error_func_name = get_str_from_chunk(&chunkstr,saved_chunks_in);
  	errorData.error_filename = get_str_from_chunk(&chunkstr,saved_chunks_in);
  	errorData.stacktrace = get_str_from_chunk(&chunkstr,saved_chunks_in);
  
  
  	PG_TRY();
  	{
  	ret = send_alert(&errorData);
  	}
  	PG_CATCH();
  	{
  		elog(LOG,"send_alert failed.  Not sending the alert");
  		free(errorData.stacktrace ); errorData.stacktrace = NULL;
  		free((char *)errorData.error_filename ); errorData.error_filename = NULL;
  		free((char *)errorData.error_func_name ); errorData.error_func_name = NULL;
  		free(errorData.debug_query_string ); errorData.debug_query_string = NULL;
  		free(errorData.error_context); errorData.error_context = NULL;
  		free(errorData.internal_query ); errorData.internal_query = NULL;
  		free(errorData.error_hint ); errorData.error_hint = NULL;
  		free(errorData.error_detail ); errorData.error_detail = NULL;
  		free(errorData.error_message ); errorData.error_message = NULL;
  		free(errorData.sql_state ); errorData.sql_state = NULL;
  		free((char *)errorData.error_severity ); errorData.error_severity = NULL;
  		free(errorData.remote_port ); errorData.remote_port = NULL;
  		free(errorData.remote_host ); errorData.remote_host = NULL;
  		free(errorData.databasename ); errorData.databasename = NULL;
  		free(errorData.username ); errorData.username = NULL;
  		/* Carry on with error handling. */
  		PG_RE_THROW();
  	}
  	PG_END_TRY();
  
  	// Don't forget to free them!  Best in reverse order of the mallocs.
  
  	free(errorData.stacktrace ); errorData.stacktrace = NULL;
  	free((char *)errorData.error_filename ); errorData.error_filename = NULL;
  	free((char *)errorData.error_func_name ); errorData.error_func_name = NULL;
  	free(errorData.debug_query_string ); errorData.debug_query_string = NULL;
  	free(errorData.error_context); errorData.error_context = NULL;
  	free(errorData.internal_query ); errorData.internal_query = NULL;
  	free(errorData.error_hint ); errorData.error_hint = NULL;
  	free(errorData.error_detail ); errorData.error_detail = NULL;
  	free(errorData.error_message ); errorData.error_message = NULL;
  	free(errorData.sql_state ); errorData.sql_state = NULL;
  	free((char *)errorData.error_severity ); errorData.error_severity = NULL;
  	free(errorData.remote_port ); errorData.remote_port = NULL;
  	free(errorData.remote_host ); errorData.remote_host = NULL;
  	free(errorData.databasename ); errorData.databasename = NULL;
  	free(errorData.username ); errorData.username = NULL;
  
  	return ret;
  }
  
  #endif
  
  #if USE_SNMP
  static int send_snmp_inform_or_trap(const GpErrorData * errorData, const char * subject, const char * severity)
  {
  
  	netsnmp_session session, *ss = NULL;
  	netsnmp_pdu    *pdu, *response;
  	int				status;
  	char            csysuptime[20];
  	static bool 	snmp_initialized = false;
  	static char myhostname[255];	/* gethostname usually is limited to 65 chars out, but make this big to be safe */
  	char	   *rawstring = NULL;
  	List	   *elemlist = NIL;
  	ListCell   *l = NULL;
  
  	/*
  	 * "inform" messages get a positive acknowledgement response from the SNMP manager.
  	 * If it doesn't come, the message might be resent.
  	 *
  	 * "trap" messages are one-way, and we have no idea if the manager received it.
  	 * But, it's faster and cheaper, and no need to retry.  So some people might prefer it.
  	 */
  	bool inform = strcmp(gp_snmp_use_inform_or_trap,"inform") == 0;
  
  
  	if (gp_snmp_monitor_address == NULL || gp_snmp_monitor_address[0] == '\0')
  	{
  		static bool firsttime = 1;
  
  		ereport(firsttime ? LOG : DEBUG1,(errmsg("SNMP inform/trap alerts are disabled"),errOmitLocation(true)));
  		firsttime = false;
  
  		return -1;
  	}
  
  
  	/*
  	 * SNMP managers are required to handle messages up to at least 484 bytes long, but I believe most existing
  	 * managers support messages up to one packet (ethernet frame) in size, 1472 bytes.
  	 *
  	 * But, should we take that chance?  Or play it safe and limit the message to 484 bytes?
  	 */
  
  	elog(DEBUG2,"send_snmp_inform_or_trap");
  
  	if (!snmp_initialized)
  	{
  		snmp_enable_stderrlog();
  
  		if (gp_snmp_debug_log != NULL && gp_snmp_debug_log[0] != '\0')
  		{
  			snmp_enable_filelog(gp_snmp_debug_log, 1);
  
  			//debug_register_tokens("ALL");
  			snmp_set_do_debugging(1);
  		}
  
  		/*
  		 * Initialize the SNMP library.  This also reads the MIB database.
  		 */
  		/* Add GPDB-MIB to the list to be loaded */
  		putenv("MIBS=+GPDB-MIB:SNMP-FRAMEWORK-MIB:SNMPv2-CONF:SNMPv2-TC:SNMPv2-TC");
  
  		init_snmp("sendalert");
  
  		snmp_initialized = true;
  
  		{
  			char portnum[16];
  			myhostname[0] = '\0';
  
  			if (gethostname(myhostname, sizeof(myhostname)) == 0)
  			{
  				strcat(myhostname,":");
  				pg_ltoa(PostPortNumber,portnum);
  				strcat(myhostname,portnum);
  			}
  		}
  	}
  
  	/*
  	 * Trap/Inform messages always start with the system up time. (SysUpTime.0)
  	 *
  	 * This presumably would be the uptime of GPDB, not the machine it is running on, I think.
  	 *
  	 * Use Postmaster's "MyStartTime" as a way to get that.
  	 */
  
  	sprintf(csysuptime, "%ld", (long)(time(NULL) - MyStartTime));
  
  
  	/*
  	// ERRCODE_DISK_FULL could be reported vi rbmsMIB rdbmsTraps rdbmsOutOfSpace trap.
  	// But it appears we never generate that error?
  
  	// ERRCODE_ADMIN_SHUTDOWN means SysAdmin aborted somebody's request.  Not interesting?
  
  	// ERRCODE_CRASH_SHUTDOWN sounds interesting, but I don't see that we ever generate it.
  
  	// ERRCODE_CANNOT_CONNECT_NOW means we are starting up, shutting down, in recovery, or Too many users are logged on.
  
  	// abnormal database system shutdown
  	*/
  
  
  
  	/*
  	 * The gpdbAlertSeverity is a crude attempt to classify some of these messages based on severity,
  	 * where OK means everything is running normal, Down means everything is shut down, degraded would be
  	 * for times when some segments are down, but the system is up, The others are maybe useful in the future
  	 *
  	 *  gpdbSevUnknown(0),
  	 *	gpdbSevOk(1),
  	 *	gpdbSevWarning(2),
  	 *	gpdbSevError(3),
  	 *	gpdbSevFatal(4),
  	 *	gpdbSevPanic(5),
  	 *	gpdbSevSystemDegraded(6),
  	 *	gpdbSevSystemDown(7)
  	 */
  
  
  	char detail[MAX_ALERT_STRING+1];
  	snprintf(detail, MAX_ALERT_STRING, "%s", errorData->error_detail);
  	detail[127] = '\0';
  
  	char sqlstmt[MAX_ALERT_STRING+1];
  	char * sqlstmtp = errorData->debug_query_string;
  	if (sqlstmtp == NULL || sqlstmtp[0] == '\0')
  		sqlstmtp = errorData->internal_query;
  	if (sqlstmtp == NULL)
  		sqlstmtp = "";
  
  
  	snprintf(sqlstmt, MAX_ALERT_STRING, "%s", sqlstmtp);
  	sqlstmt[MAX_ALERT_STRING] = '\0';
  
  
  	/* Need a modifiable copy of To list */
  	rawstring = pstrdup(gp_snmp_monitor_address);
  
  	/* Parse string into list of identifiers */
  	if (!SplitString(rawstring, ';', &elemlist))
  	{
  		/* syntax error in list */
  		ereport(LOG,
  				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
  				 errmsg("invalid list syntax for \"gp_snmp_monitor_address\"")));
  		return -1;
  	}
  
  
  	/*
  	 * This session is just a template, and doesn't need to be connected.
  	 * It is used by snmp_add(), which copies this info, opens the new session, and assigns the transport.
  	 */
  	snmp_sess_init( &session );	/* Initialize session to default values */
  	session.version = SNMP_VERSION_2c;
  	session.timeout = SNMP_DEFAULT_TIMEOUT;
  	session.retries = SNMP_DEFAULT_RETRIES;
  	session.remote_port = 162; 							// I think this isn't used by net-snmp any more.
  
  	/*if (strchr(session.peername,':')==NULL)
  		strcat(session.peername,":162");*/
  	session.community = (u_char *)gp_snmp_community;
  	session.community_len = strlen((const char *)session.community);  // SNMP_DEFAULT_COMMUNITY_LEN means "public"
  	session.callback_magic = NULL;
  
  	foreach(l, elemlist)
  	{
  		char	   *cur_gp_snmp_monitor_address = (char *) lfirst(l);
  
  		if (cur_gp_snmp_monitor_address == NULL || cur_gp_snmp_monitor_address[0] == '\0')
  			continue;
  
  		session.peername = cur_gp_snmp_monitor_address;
  		/*
  		 * If we try to "snmp_open( &session )", net-snmp will set up a connection to that
  		 * endpoint on port 161, assuming we are the network monitor, and the other side is an agent.
  		 *
  		 * But we are pretending to be an agent, sending traps to the NM, so we don't need this.
  		 */
  
  		/*if (!snmp_open( &session ))   	// Don't open the session here!
  		{
  			const char     *str;
  			int             xerr;
  			xerr = snmp_errno;
  			str = snmp_api_errstring(xerr);
  			elog(LOG, "snmp_open: %s", str);
  			return -1;
  		}*/
  
  		/*
  		 * This call copies the info from "session" to "ss", assigns the transport, and opens the session.
  		 * We must specify "snmptrap" so the transport will know we want port 162 by default.
  		 */
  		ss = snmp_add(&session,
  					  netsnmp_transport_open_client("snmptrap", cur_gp_snmp_monitor_address),
  					  NULL, NULL);
  		if (ss == NULL) {
  			/*
  			 * diagnose netsnmp_transport_open_client and snmp_add errors with
  			 * the input netsnmp_session pointer
  			 */
  			{
  				char           *err;
  				snmp_error(&session, NULL, NULL, &err);
  				elog(LOG, "send_alert snmp_add of %s failed: %s", cur_gp_snmp_monitor_address, err);
  				free(err);
  			}
  			return -1;
  		}
  
  		/*
  		 * We need to create the pdu each time, as it gets freed when we send a trap.
  		 */
  		pdu = snmp_pdu_create(inform ? SNMP_MSG_INFORM : SNMP_MSG_TRAP2);
  		if (!pdu)
  		{
  			const char     *str;
  			int             xerr;
  			xerr = snmp_errno;
  			str = snmp_api_errstring(xerr);
  			elog(LOG, "Failed to create notification PDU: %s", str);
  			return -1;
  		}
  
  		/*
  		 * Trap/Inform messages always start with the system up time. (SysUpTime.0)
  		 * We use Postmaster's "MyStartTime" as a way to get that.
  		 */
  
  		snmp_add_var(pdu, objid_sysuptime,
  							sizeof(objid_sysuptime) / sizeof(oid),
  							't', (const char *)csysuptime);
  
  	#if 0
  		/*
  		 * In the future, we might want to send RDBMS-MIB::rdbmsStateChange when the system becomes unavailable or
  		 * partially unavailable.  This code, which is not currently used, shows how to build the pdu for
  		 * that trap.
  		 */
  		/* {iso(1) identified-organization(3) dod(6) internet(1) mgmt(2) mib-2(1) rdbmsMIB(39) rdbmsTraps(2) rdbmsStateChange(1)} */
  		snmp_add_var(pdu, objid_snmptrap,
  							sizeof(objid_snmptrap) / sizeof(oid),
  							'o', "1.3.6.1.2.1.39.2.1");  // rdbmsStateChange
  
  
  		snmp_add_var(pdu, objid_rdbmsrelstate,
  							sizeof(objid_rdbmsrelstate) / sizeof(oid),
  							'i', "5");  // 4 = restricted, 5 = unavailable
  	#endif
  
  		/* {iso(1) identified-organization(3) dod(6) internet(1) private(4) enterprises(1) gpdbMIB(31327) gpdbTraps(5) gpdbTrapsList(0) gpdbAlert(1)} */
  
  		/*
  		 * We could specify this trap oid by name, rather than numeric oid, but then if the GPDB-MIB wasn't
  		 * found, we'd get an error.  Using the numeric oid means we can still work without the MIB loaded.
  		 */
  		snmp_add_var(pdu, objid_snmptrap,
  							sizeof(objid_snmptrap) / sizeof(oid),
  							'o', "1.3.6.1.4.1.31327.5.0.1");  // gpdbAlert
  
  
  		snmp_add_var(pdu, objid_gpdbAlertMsg,
  							sizeof(objid_gpdbAlertMsg) / sizeof(oid),
  							's', subject);  // SnmpAdminString = UTF-8 text
  		snmp_add_var(pdu, objid_gpdbAlertSeverity,
  							sizeof(objid_gpdbAlertSeverity) / sizeof(oid),
  							'i', (char *)severity);
  
  		snmp_add_var(pdu, objid_gpdbAlertSqlstate,
  							sizeof(objid_gpdbAlertSqlstate) / sizeof(oid),
  							's', errorData->sql_state);
  
  		snmp_add_var(pdu, objid_gpdbAlertDetail,
  							sizeof(objid_gpdbAlertDetail) / sizeof(oid),
  							's', detail); // SnmpAdminString = UTF-8 text
  
  		snmp_add_var(pdu, objid_gpdbAlertSqlStmt,
  							sizeof(objid_gpdbAlertSqlStmt) / sizeof(oid),
  							's', sqlstmt); // SnmpAdminString = UTF-8 text
  
  		snmp_add_var(pdu, objid_gpdbAlertSystemName,
  							sizeof(objid_gpdbAlertSystemName) / sizeof(oid),
  							's', myhostname); // SnmpAdminString = UTF-8 text
  
  
  
  		elog(DEBUG2,"ready to send to %s",cur_gp_snmp_monitor_address);
  		if (inform)
  			status = snmp_synch_response(ss, pdu, &response);
  		else
  			status = snmp_send(ss, pdu) == 0;
  
  		elog(DEBUG2,"send, status %d",status);
  		if (status != STAT_SUCCESS)
  		{
  			/* Something went wrong */
  			if (ss)
  			{
  				char           *err;
  				snmp_error(ss, NULL, NULL, &err);
  				elog(LOG, "sendalert failed to send %s: %s", inform ? "inform" : "trap", err);
  				free(err);
  			}
  			else
  			{
  				elog(LOG, "sendalert failed to send %s: %s", inform ? "inform" : "trap", "Something went wrong");
  			}
  			if (!inform)
  				snmp_free_pdu(pdu);
  		}
  		else if (inform)
  			snmp_free_pdu(response);
  
  		snmp_close(ss);
  		ss = NULL;
  
  	}
  
  	/*
  	 * don't do the shutdown, to avoid the cost of starting up snmp each time
  	 * (plus, it doesn't seem to work to run snmp_init() again after a shutdown)
  	 *
  	 * It would be nice to call this when the syslogger is shutting down.
  	 */
  	/*snmp_shutdown("sendalert");*/
  
  
  	return 0;
  }
  #endif
  
  #ifdef USE_EMAIL
  static int send_alert_via_email(const GpErrorData * errorData,
  		const char * subject, const char * email_priority)
  {
  	smtp_session_t session;
  	smtp_message_t message;
  	smtp_recipient_t recipient = NULL;
  	auth_context_t authctx;
  	const smtp_status_t *status;
  
  	enum notify_flags notify = Notify_FAILURE | Notify_DELAY;
  	char *rawstring = NULL;
  	List *elemlist = NIL;
  	ListCell *l = NULL;
  	int success = 0;
          static int num_connect_failures = 0;
          static time_t last_connect_failure_ts = 0;
  
  
          if (gp_email_connect_failures && num_connect_failures >= gp_email_connect_failures) {
              if (time(0) - last_connect_failure_ts > gp_email_connect_avoid_duration) {
                  num_connect_failures = 0;
                  elog(LOG, "Retrying emails now...");
              } else {
                  elog(LOG, "Not attempting emails as of now");
                  return -1;
              }
          }
  
  	if (gp_email_to == NULL || strlen(gp_email_to) == 0)
  	{
  		static bool firsttime = 1;
  
  		ereport(firsttime ? LOG : DEBUG1,(errmsg("e-mail alerts are disabled"),errOmitLocation(true)));
  		firsttime = false;
  		return -1;
  	}
  
  	if (strlen(gp_email_from) == 0)
  	{
  		elog(LOG,"e-mail alerts are not properly configured:  No 'from:' address configured");
  		return -1;
  	}
  
  	if (strchr(gp_email_to, ';') == NULL && strchr(gp_email_to, ',') != NULL)
  	{
  		// email addrs should be separated by ';', but because we used to require ',',
  		// let's accept that if it looks OK.
  		while (strchr(gp_email_to,',') != NULL)
  		*strchr(gp_email_to,',') = ';';
  	}
  
  	// Ok, send the alert here.
  
  	static bool auth_client_init_called = false;
  	if (!auth_client_init_called)
  	{
  		auth_client_init(); // Call this only once!
  		auth_client_init_called = true;
  	}
  	session = smtp_create_session();
  	if (session == NULL)
  	{
  		elog(LOG,"Unable to send e-mail: smtp_create_session() failed %d",smtp_errno());
  		return -1;
  	}
  
  	if (log_min_messages <= DEBUG1)
  	smtp_set_monitorcb (session, monitor_cb, NULL, 1);
  
  	message = smtp_add_message(session);
  	if (message == NULL)
  	elog(LOG,"smtp_add_message() failed");
  
  	//smtp_message_reset_status(message);
  
  #ifdef USE_SSL
  	if (!smtp_starttls_enable(session, Starttls_ENABLED))
  		elog(LOG,"smtp_starttls_enable() failed");
  #endif
  
  	/*
  	 * Set the host running the SMTP server.  LibESMTP has a default port
  	 * number of 587, however this is not widely deployed so the port
  	 * is specified as 25 along with the default MTA host.
  	 */
  	if (!smtp_set_server(session,
  					gp_email_smtp_server != NULL && strlen(gp_email_smtp_server) > 0 ? gp_email_smtp_server : "localhost:25"))
  	{
  		elog(LOG,"Unable to send e-mail: smtp_set_server failed %d",smtp_errno());
  		smtp_destroy_session(session);
  		return -1;
  	}
  
  	smtp_set_eventcb(session, event_cb, NULL);
  
  	/*
  	 * Do what's needed at application level to use authentication.
  	 */
  	authctx = auth_create_context();
  	if (authctx == NULL)
  	elog(LOG,"auth_create_context() failed %d",smtp_errno());
  
  	if (!auth_set_mechanism_flags(authctx, AUTH_PLUGIN_PLAIN | AUTH_PLUGIN_ANONYMOUS, 0))
  	{
  		elog(LOG,"Can't auth_set_mechanism_flags");
  	}
  
  	auth_set_interact_cb(authctx, authinteract, NULL);
  
  	/*
  	 * From what I can tell, we aren't supposed to call auth_set_external_id() directly.
  	 * Instead, it gets called automatically based on the information in the client certificate,
  	 * which is usually the e-mail address.
  	 * Perhaps this might be needed for special circumstances?
  	 */
  	//if (gp_email_smtp_userid != NULL && gp_email_smtp_userid[0] != '\0')
  	//	auth_set_external_id(authctx, gp_email_smtp_userid);
  
  	if (!smtp_auth_set_context(session, authctx))
  	{
  		elog(LOG,"Can't smtp_auth_set_context");
  	}
  
  	/*
  	 * Use our callback for X.509 certificate passwords.  If STARTTLS is
  	 * not in use or disabled in configure, the following is harmless.
  	 */
  	smtp_starttls_set_password_cb(tlsinteract, NULL);
  
  	/*
  	 * Set the reverse path for the mail envelope.  (NULL is ok)
  	 */
  	if (strchr(gp_email_from,'<') != NULL)
  	{
  		/*
  		 * Pull out just the e-mail address
  		 */
  		char * from = pstrdup(strchr(gp_email_from,'<')+1);
  		if (strchr(from,'>') != NULL)
  		*strchr(from,'>') = '\0';
  		if (!smtp_set_reverse_path(message, from))
  		elog(LOG,"smtp_set_reverse_path failed %d",smtp_errno());
  	}
  	else
  	if (!smtp_set_reverse_path(message, gp_email_from))
  	elog(LOG,"smtp_set_reverse_path failed %d",smtp_errno());
  
  	smtp_set_header(message, "Subject", subject);
  	smtp_set_header_option(message, "Subject", Hdr_OVERRIDE, 1);
  	smtp_set_header(message, "Message-Id", NULL);
  
  	if (email_priority[0] != '\0' && email_priority[0] != '3') // priority not the default?
  	smtp_set_header(message, "X-Priority", email_priority); // set a priority.  1 == highest priority, 5 lowest
  
  
  	smtp_set_messagecb(message, messagebody_cb, (void *) errorData);
  
  	/* Need a modifiable copy of To list */
  	rawstring = pstrdup(gp_email_to);
  
  	/* Parse string into list of identifiers */
  	if (!SplitString(rawstring, ',', &elemlist))
  	{
  		/* syntax error in list */
  		ereport(LOG,
  				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
  						errmsg("invalid list syntax for \"gp_email_to\"")));
  	}
  
  #if 0
  	/*
  	 * We want to set a "To:" header.
  	 *
  	 * Some versions of libesmtp have a bug that only allows this to work for a single
  	 * e-mail recipient.  Because of that, I've changed the code to set the "To:" header
  	 * in the message body callback, but I'm not sure that is as good as doing this, since I
  	 * might not have it formatted the best way.
  	 *
  	 */
  	foreach(l, elemlist)
  	{
  		char *cur_gp_email_addr = (char *) lfirst(l);
  
  		char *free_form_text = NULL;
  		char *email_addr = cur_gp_email_addr;
  
  		/*
  		 * RFC 2822 doesn't require recipient headers but a To: header would
  		 * be nice to have if not present.
  		 *
  		 * First string is free-form text which is the real name of the person (not really used)
  		 * The second string is the e-mail address.
  		 */
  		if (strchr(cur_gp_email_addr,'<') != NULL)
  		{
  			free_form_text = pstrdup(cur_gp_email_addr);
  			*strchr(free_form_text,'<') = '\0';
  			email_addr = pstrdup(strchr(cur_gp_email_addr,'<')+1);
  			if (strchr(email_addr,'>') != NULL)
  			*strchr(email_addr,'>') = '\0';
  		}
  
  		if (!smtp_set_header(message, "To", free_form_text, email_addr));
  		{
  			char buf[128];
  
  			elog(LOG, "smtp_set_header failed:  %d  %s  for e-mail %s", smtp_errno(), smtp_strerror(smtp_errno(),
  							buf, sizeof buf), cur_gp_email_addr);
  		}
  	}
  #endif
  
  	foreach(l, elemlist)
  	{
  		char *cur_gp_email_addr = (char *) lfirst(l);
  
  		if (cur_gp_email_addr != NULL && strlen(cur_gp_email_addr) > 0)
  		{
  			char * to = cur_gp_email_addr;
  
  			/* Recipient must be in RFC2821 format */
  
  			if (strchr(cur_gp_email_addr,'<') != NULL)
  			{
  				/*
  				 * Pull out just the e-mail address
  				 */
  				to = pstrdup(strchr(cur_gp_email_addr,'<')+1);
  				if (strchr(to,'>') != NULL)
  				*strchr(to,'>') = '\0';
  			}
  
  			recipient = smtp_add_recipient(message, to);
  
  			if (recipient == NULL)
  			elog(LOG, "stmp_add_recipient failed for e-mail:  %s", cur_gp_email_addr);
  
  			smtp_dsn_set_notify(recipient, notify);
  
  			success = true;
  		}
  
  	}
  
  	if (!success && list_length(elemlist))
  	ereport(LOG,
  			(errmsg("Could not understand e-mail To: list")));
  
  	list_free(elemlist);
  	pfree(rawstring);
  
  	/*
  	 * Many e-mail transfer agents don't accept the 8BITMIME extension.  We'd
  	 * Like to use it if available, but I don't know how to do that yet.
  	 * For now, just don't set it.  The MTA will decide if it is 8BIT based on
  	 * the MIME header.
  	 */
  	/* smtp_8bitmime_set_body(message, E8bitmime_8BITMIME); */
  
  	/* Initiate a connection to the SMTP server and transfer the message. */
  	int smtp_ret = smtp_start_session(session);
  	if (smtp_ret == 0)
  	{
  		num_connect_failures++;
  		last_connect_failure_ts = time(0);  
  
  		/*
  		 * If we get here, we can't talk to the SMTP server at all
  		 */
  		int smtperr = smtp_errno();
  		char buf[128];
  		memset(buf, 0, sizeof(buf)); /* in case smtp_strerror fails */
  		smtp_strerror(smtperr, buf, sizeof buf);
  
  		elog(LOG, "SMTP server problem %d  %s", smtperr, buf);
  	}
  	else
  	{
                  
  		/* I would expect this to be always zero here, but just in case it's not */
  		if (smtp_errno() != 0)
  		{
  			int smtperr = smtp_errno();
  			char buf[128];
  			memset(buf, 0, sizeof(buf)); /* in case smtp_strerror fails */
  			smtp_strerror(smtperr, buf, sizeof buf);
  
  			elog(LOG, "SMTP server problem %d  %s", smtperr, buf);
  		}
                  num_connect_failures = 0;
  		/*
  		 * Report on the success or otherwise of the mail transfer.
  		 * This really only tells the "overall" status, so if an individual e-mail fails,
  		 * we won't see it here (even if every one of them fails!).
  		 */
  
  		/*
  		 * SMTP protocol status codes (see RFCs on SMTP)
  		 * status 250 == ok
  		 * 200 to 299 are warning or information.
  		 * >= 300 are errors
  		 */
  		status = smtp_message_transfer_status(message);
  		elog(status->code == 250 ? DEBUG1 : LOG, "overall transfer status: %d %s", status->code,
  				(status->text != NULL) ? status->text : "");
  
  		/*
  		 * This should tell us how each e-mail recipient fared.
  		 */
  		if (!smtp_enumerate_recipients(message, print_recipient_status, NULL))
  		{
  			elog(LOG,"smtp_enumerate_recipients failed %d",smtp_errno());
  		}
  	}
  
  	/* Free resources consumed by the program.
  	 */
  	if (session != NULL)
  	smtp_destroy_session(session);
  	if (authctx != NULL)
  	auth_destroy_context(authctx);
  
  	/* To save on overhead, don't call this more than necessary */
  	/* auth_client_exit(); */
  
  	return 0;
  
  }
  
  #endif
  
  
  int send_alert(const GpErrorData * errorData)
  {
  
  	char subject[128];
  	bool send_via_email = true;
  	char email_priority[2];
  	bool send_via_snmp = true;
  	char snmp_severity[2];
  
  	static char previous_subject[128];
  	pg_time_t current_time;
  	static pg_time_t previous_time;
  	static GpErrorDataFixFields previous_fix_fields;
  
  	elog(DEBUG2,"send_alert: %s: %s",errorData->error_severity, errorData->error_message);
  
  	/*
  	 * SIGPIPE must be ignored, or we will have problems.
  	 *
  	 * but this is already set in syslogger.c, so we are OK
  	 * //pqsignal(SIGPIPE, SIG_IGN);  // already set in syslogger.c
  	 *
  	 */
  
  	/* Set up a subject line for the alert.  set_alert_severity will limit it to 127 bytes just to be safe. */
  	/* Assign a severity and email priority for this alert*/
  
  	set_alert_severity(errorData,
  						subject,
  						&send_via_email,
  						email_priority,
  						&send_via_snmp,
  						snmp_severity);
  
  
  	/*
  	 * Check to see if we are sending the same message as last time.
  	 * This could mean the system is in a loop generating an error over and over,
  	 * or the application is just re-doing the same bad request over and over.
  	 *
  	 * This is pretty crude, as we don't consider loops where we alternate between a small
  	 * number of messages.
  	 */
  
  	if (strcmp(subject,previous_subject)==0)
  	{
  		/*
  		 * Looks like the same message based on the errMsg, but we need to
  		 * investigate further.
  		 */
  		bool same_message_repeated = true;
  
  		/*
  		 * If the message is from a different segDB, consider it a different message.
  		 */
  		if (errorData->fix_fields.gp_segment_id != previous_fix_fields.gp_segment_id)
  			same_message_repeated = false;
  		if (errorData->fix_fields.gp_is_primary != previous_fix_fields.gp_is_primary)
  			same_message_repeated = false;
  		/*
  		 * If the message is from a different user, consider it a different message,
  		 * unless it is a FATAL, because an application repeatedly sending in a request
  		 * that crashes (SIGSEGV) will get a new session ID each time
  		 */
  		if (errorData->fix_fields.gp_session_id != previous_fix_fields.gp_session_id)
  			if (strcmp(errorData->error_severity,"FATAL") != 0)
  				same_message_repeated = false;
  		/*
  		 * Don't consider gp_command_count, because a loop where the application is repeatedly
  		 * sending a bad request will have a changing command_count.
  		 *
  		 * Likewise, the transaction ids will be changing each time, so don't consider them.
  		 */
  
  		if (same_message_repeated)
  		{
  			current_time = (pg_time_t)time(NULL);
  			/*
  			 * This is the same alert as last time.  Limit us to one repeat alert every 30 seconds
  			 * to avoid spamming the sysAdmin's mailbox or the snmp network monitor.
  			 *
  			 * We don't just turn off the alerting until a different message comes in, because
  			 * if enough time has passed, this message might (probably?) refer to a new issue.
  			 *
  			 * Note that the message will still exist in the log, it's just that we won't
  			 * send it via e-mail or snmp notification.
  			 */
  
  			if (current_time - previous_time < 30)
  			{
  				/* Bail out here rather than send the alert. */
  				elog(DEBUG2,"Suppressing repeats of this alert messages...");
  				return -1;
  			}
  		}
  
  	}
  
  	strcpy(previous_subject, subject);
  	previous_time = (pg_time_t)time(NULL);
  	memcpy(&previous_fix_fields,&errorData->fix_fields,sizeof(GpErrorDataFixFields));
  
  #if USE_SNMP
  	if (send_via_snmp)
  		send_snmp_inform_or_trap(errorData, subject, snmp_severity);
  	else 
  		elog(DEBUG4,"Not sending via SNMP");
  #endif
  
  #ifdef USE_EMAIL
  	if (send_via_email)
  	{
  		send_alert_via_email(errorData, subject, email_priority);
  	}
  	else
  		elog(DEBUG4,"Not sending via e-mail");
  
  #endif
  
  	return 0;
  }
  
  
  static size_t
  pg_strnlen(const char *str, size_t maxlen)
  {
  	const char *p = str;
  
  	while (maxlen-- > 0 && *p)
  		p++;
  	return p - str;
  }
  
  static void move_to_next_chunk(CSVChunkStr * chunkstr,
  		const PipeProtoChunk * saved_chunks)
  {
  	Assert(chunkstr != NULL);
  	Assert(saved_chunks != NULL);
  
  	if (chunkstr->chunk != NULL)
  		if (chunkstr->p - chunkstr->chunk->data >= chunkstr->chunk->hdr.len)
  		{
  			/* switch to next chunk */
  			if (chunkstr->chunk->hdr.next >= 0)
  			{
  				chunkstr->chunk = &saved_chunks[chunkstr->chunk->hdr.next];
  				chunkstr->p = chunkstr->chunk->data;
  			}
  			else
  			{
  				/* no more chunks */
  				chunkstr->chunk = NULL;
  				chunkstr->p = NULL;
  			}
  		}
  }
  
  static char *
  get_str_from_chunk(CSVChunkStr *chunkstr, const PipeProtoChunk *saved_chunks)
  {
  	int wlen = 0;
  	int len = 0;
  	char * out = NULL;
  
  	Assert(chunkstr != NULL);
  	Assert(saved_chunks != NULL);
  
  	move_to_next_chunk(chunkstr, saved_chunks);
  
  	if (chunkstr->p == NULL)
  	{
  		return strdup("");
  	}
  
  	len = chunkstr->chunk->hdr.len - (chunkstr->p - chunkstr->chunk->data);
  
  	/* Check if the string is an empty string */
  	if (len > 0 && chunkstr->p[0] == '\0')
  	{
  		chunkstr->p++;
  		move_to_next_chunk(chunkstr, saved_chunks);
  
  		return strdup("");
  	}
  
  	if (len == 0 && chunkstr->chunk->hdr.next >= 0)
  	{
  		const PipeProtoChunk *next_chunk =
  				&saved_chunks[chunkstr->chunk->hdr.next];
  		if (next_chunk->hdr.len > 0 && next_chunk->data[0] == '\0')
  		{
  			chunkstr->p++;
  			move_to_next_chunk(chunkstr, saved_chunks);
  			return strdup("");
  		}
  	}
  
  	wlen = pg_strnlen(chunkstr->p, len);
  
  	if (wlen < len)
  	{
  		// String all contained in this chunk
  		out = malloc(wlen + 1);
  		memcpy(out, chunkstr->p, wlen + 1); // include the null byte
  		chunkstr->p += wlen + 1; // skip to start of next string.
  		return out;
  	}
  
  	out = malloc(wlen + 1);
  	memcpy(out, chunkstr->p, wlen);
  	out[wlen] = '\0';
  	chunkstr->p += wlen;
  
  	while (chunkstr->p)
  	{
  		move_to_next_chunk(chunkstr, saved_chunks);
  		if (chunkstr->p == NULL)
  			break;
  		len = chunkstr->chunk->hdr.len - (chunkstr->p - chunkstr->chunk->data);
  
  		wlen = pg_strnlen(chunkstr->p, len);
  
  		/* Write OK, don't forget to account for the trailing 0 */
  		if (wlen < len)
  		{
  			// Remainder of String all contained in this chunk
  			out = realloc(out, strlen(out) + wlen + 1);
  			strncat(out, chunkstr->p, wlen + 1); // include the null byte
  
  			chunkstr->p += wlen + 1; // skip to start of next string.
  			return out;
  		}
  		else
  		{
  			int newlen = strlen(out) + wlen;
  			out = realloc(out, newlen + 1);
  			strncat(out, chunkstr->p, wlen);
  			out[newlen] = '\0';
  
  			chunkstr->p += wlen;
  		}
  	}
  
  	return out;
  }
  
  #ifdef USE_EMAIL
  
  /*
   *  The message is read a line at a time and the newlines converted
   *  to \r\n.  Unfortunately, RFC 822 states that bare \n and \r are
   *  acceptable in messages and that individually they do not constitute a
   *  line termination.  This requirement cannot be reconciled with storing
   *  messages with Unix line terminations.  RFC 2822 rescues this situation
   *  slightly by prohibiting lone \r and \n in messages.
   *
   */
  static void add_to_message(char * message, const char * newstr_in)
  {
  	const char * newstr = newstr_in;
  	char * p;
  	if (newstr == NULL)
  		return;
  
  	/* Drop any leading \n characters:  Not sure what to do with them */
  	while (*newstr == '\n')
  		newstr++;
  
  	/* Scan for \n, and convert to \r\n */
  	while ((p = strchr(newstr,'\n')) != NULL)
  	{
  		/* Don't exceed 900 chars added to this line, so total line < 1000 */
  		if (p - newstr >= 900)
  		{
  			strncat(message, newstr, 898);
  			strcat(message, "\r\n\t");
  			newstr += 898;
  		}
  		else if (p - newstr >=2 && *(p-1) != '\r')
  		{
  			strncat(message, newstr, p - newstr);
  			strcat(message, "\r\n\t");
  			newstr = p+1;
  		}
  		else
  		{
  			strncat(message, newstr, p - newstr + 1);
  			newstr = p+1;
  		}
  	}
  	strcat(message, newstr);
  
  }
  
  static const char *
  messagebody_cb(void **buf, int *len, void *arg)
  {
  #define MAX_MESSAGE_BODY 8192
  	const GpErrorData *errorData = (GpErrorData *) arg;
  	char * message = NULL;
  	static int alreadyreturned = 0;
  
  	if (*buf == NULL)
  	{
  		alreadyreturned = 0;
  		if (len == NULL)
  			return NULL;
  
  		*buf = malloc(MAX_MESSAGE_BODY);
  		message = ((char *)(*buf));
  		message[0] = '\0';
  	}
  
  	if (len == NULL)
  	{
  		alreadyreturned = 0;
  		message = ((char *)(*buf));
  		message[0] = '\0';
  	    return NULL;
  	}
  
  	message = ((char *)(*buf));
  
  	if (alreadyreturned == 0)
  	{
  		char  lineno[16]; 
  
  		pg_ltoa(errorData->fix_fields.error_fileline,lineno);
  
  		/* Perhaps better to use text/html ? */
  
  		strcpy(message, "From: ");
  		strcat(message, gp_email_from);
  		strcat(message,"\r\n");
  		strcat(message, "To: ");
  		strcat(message,gp_email_to);
  		strcat(message,"\r\n");
  
  		strcat(message,
  			//"Return-Path: <cmcdevitt@greenplum.com>\r\n"
  			//"Subject: LibESMTP test mail\r\n"
  			"MIME-Version: 1.0\r\n"
  			"Content-Type: text/plain;\r\n"
  			"  charset=utf-8\r\n"
  			"Content-Transfer-Encoding: 8bit\r\n\r\n");
  
  		/* Lines must be < 1000 bytes long for 7bit or 8bit transfer-encoding */
  		/* Perhaps use base64 encoding instead?  Or just wrap them? */
  
  		/* How to identify which system is sending the alert?  Perhaps our hostname and port is good enough? */
  		strcat(message,"Alert from GPDB system ");
  		{
  			char myhostname[255];	/* gethostname usually is limited to 65 chars out, but make this big to be safe */
  			char portnum[16];
  			myhostname[0] = '\0';
  
  
  			if (gethostname(myhostname, sizeof(myhostname)) == 0)
  			{
  				strcat(message,myhostname);
  				strcat(message," on port ");
  				pg_ltoa(PostPortNumber,portnum);
  				strcat(message,portnum);
  			}
  		}
  		strcat(message,":\r\n\r\n");
  		if (errorData->username != NULL &&  errorData->databasename != NULL &&
  				strlen(errorData->username)>0 && strlen(errorData->databasename)>0)
  		{
  			strcat(message, errorData->username);
  			if (errorData->remote_host != NULL && strlen(errorData->remote_host) > 0)
  			{
  				if (strcmp(errorData->remote_host,"[local]")==0)
  					strcat(message, " logged on locally from master node");
  				else
  				{
  					strcat(message," logged on from host ");
  					strcat(message,errorData->remote_host);
  				}
  			}
  			strcat(message, " connected to database ");
  			strcat(message, errorData->databasename);
  			strcat(message, "\r\n");
  
  		}
  		if (errorData->fix_fields.omit_location != 't')
  		{
  			if (errorData->fix_fields.gp_segment_id != -1)
  			{
  				char segid[16];
  				pg_ltoa(errorData->fix_fields.gp_segment_id,segid);
  				strcat(message,"Error occurred on segment ");
  				strcat(message,segid);
  			}
  			else
  				strcat(message,"Error occurred on master segment");
  			strcat(message, "\r\n");
  		}
  		strcat(message, "\r\n");
  
  		strcat(message, errorData->error_severity);
  		strcat(message, ": ");
  		if (errorData->sql_state != NULL && pg_strnlen(errorData->sql_state,5)>4 &&
  				strncmp(errorData->sql_state,"XX100",5)!=0 &&
  				strncmp(errorData->sql_state,"00000",5)!=0)
  		{
  			strcat(message, "(");
  			strncat(message, errorData->sql_state, 5);
  			strcat(message, ") ");
  		}
  		add_to_message(message, errorData->error_message);
  		strcat(message, "\r\n");
  		strcat(message, "\r\n");
  
  		if (errorData->error_detail != NULL &&strlen(errorData->error_detail) > 0)
  		{
  			strcat(message, _("DETAIL:  "));
  			add_to_message(message, errorData->error_detail);
  			strcat(message, "\r\n");
  		}
  		if (errorData->error_hint != NULL &&strlen(errorData->error_hint) > 0)
  		{
  			strcat(message, _("HINT:  "));
  			add_to_message(message, errorData->error_hint);
  			strcat(message, "\r\n");
  		}
  		if (errorData->internal_query != NULL &&strlen(errorData->internal_query) > 0)
  		{
  			strcat(message, _("QUERY:  "));
  			add_to_message(message, errorData->internal_query);
  			strcat(message, "\r\n");
  		}
  		if (errorData->error_context != NULL && strlen(errorData->error_context) > 0)
  		{
  			strcat(message, _("CONTEXT:  "));
  			add_to_message(message, errorData->error_context);
  			strcat(message, "\r\n");
  		}
  		if (errorData->fix_fields.omit_location != 't')
  		{
  			if (errorData->error_filename != NULL && strlen(errorData->error_filename) > 0)
  			{
  				strcat(message,  _("LOCATION:  "));
  
  				if (errorData->error_func_name && strlen(errorData->error_func_name) > 0)
  				{
  					strcat(message, errorData->error_func_name);
  					strcat(message, ", ");
  				}
  
  				strcat(message, errorData->error_filename);
  				strcat(message, ":");
  				strcat(message, lineno);
  				strcat(message, "\r\n");
  			}
  			if (errorData->stacktrace != NULL && strlen(errorData->stacktrace) > 0)
  			{
  				strcat(message, "STACK TRACE:\r\n\t");
  				add_to_message(message, errorData->stacktrace);
  				strcat(message, "\r\n");
  			}
  		}
  		if (errorData->debug_query_string != NULL &&strlen(errorData->debug_query_string) > 0)
  		{
  			strcat(message, _("STATEMENT:  "));
  			add_to_message(message, errorData->debug_query_string);
  			strcat(message, "\r\n");
  		}
  
  	}
  
  	if (alreadyreturned < strlen(message))
  	{
  		char * p;
  		message += alreadyreturned;
  		if ((p = strchr(message,'\n')) != NULL)
  		{
  			*len = p - message + 1;
  			alreadyreturned += *len;
  		}
  		else if (strlen(message)>0)
  		{
  			*len = strlen(message);
  			alreadyreturned += *len;
  		}
  		else
  		{
  			*len = 0;
  			message = NULL;
  		}
  	}
  	else
  	{
  		*len = 0;
  		return NULL;
  	}
  
  	return message;
  }
  
  /* Callback to print the recipient status */
  static void print_recipient_status(smtp_recipient_t recipient,
  		const char *mailbox, void *arg  __attribute__((unused)))
  {
  	const smtp_status_t *status;
  
  	status = smtp_recipient_status(recipient);
  	/*
  	 * SMTP protocol status codes (see RFCs on SMTP)
  	 * status 250 == ok
  	 * 200 to 299 are warning or information.
  	 * >= 300 are errors
  	 */
  	if (status->code != 0 && status->code != 250)
  	{
  		elog(LOG, "recipient status: %s: %d %s", mailbox, status->code, status->text);
  	}
  }
  
  static int authinteract(auth_client_request_t request, char **result,
  		int fields, void *arg)
  {
  	int i;
  	
  	elog(DEBUG2, "authinteract called");
  
  	/* SASL auth requests */
  	for (i = 0; i < fields; i++)
  	{
  		if (request[i].flags & AUTH_PASS)
  		{
  			elog(DEBUG1, "authinteract asking for password: %s  %s%s",request[i].name, request[i].prompt,(request[i].flags & AUTH_CLEARTEXT) ? " (not encrypted)"
  					: "");
  			result[i] = gp_email_smtp_password;
  		}
  		else if (request[i].flags & AUTH_USER)
  		{
  			elog(DEBUG1, "authinteract asking for username: %s  %s%s",request[i].name, request[i].prompt,(request[i].flags & AUTH_CLEARTEXT) ? " (not encrypted)"
  					: "");
  			result[i] = gp_email_smtp_userid;
  
  		}
  		else if (request[i].flags & AUTH_REALM)
  		{
  			elog(LOG, "authinteract asking for realm: %s  %s%s",request[i].name, request[i].prompt,(request[i].flags & AUTH_CLEARTEXT) ? " (not encrypted)"
  					: "");
  			result[i] = "greenplum.com";
  
  		}
  		else if (result[i] == NULL)
  		{
  			return 0;
  		}
  	}
  
  	return 1;
  }
  
  static int tlsinteract(char *buf, int buflen, int rwflag  __attribute__((unused)), void *arg  __attribute__((unused)))
  {
  
  	elog(DEBUG1, "tlsinteract asking for tls password");
  
  	strcpy(buf, gp_email_smtp_password);  // This isn't right... We need the X.509 certificate's password, I think
  	return strlen(buf);
  }
  
  static int handle_invalid_peer_certificate(long vfy_result)
  {
  	const char *k = "rare error";
  	switch (vfy_result)
  	{
  	case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
  		k = "X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT";
  		break;
  	case X509_V_ERR_UNABLE_TO_GET_CRL:
  		k = "X509_V_ERR_UNABLE_TO_GET_CRL";
  		break;
  	case X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE:
  		k = "X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE";
  		break;
  	case X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE:
  		k = "X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE";
  		break;
  	case X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY:
  		k = "X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY";
  		break;
  	case X509_V_ERR_CERT_SIGNATURE_FAILURE:
  		k = "X509_V_ERR_CERT_SIGNATURE_FAILURE";
  		break;
  	case X509_V_ERR_CRL_SIGNATURE_FAILURE:
  		k = "X509_V_ERR_CRL_SIGNATURE_FAILURE";
  		break;
  	case X509_V_ERR_CERT_NOT_YET_VALID:
  		k = "X509_V_ERR_CERT_NOT_YET_VALID";
  		break;
  	case X509_V_ERR_CERT_HAS_EXPIRED:
  		k = "X509_V_ERR_CERT_HAS_EXPIRED";
  		break;
  	case X509_V_ERR_CRL_NOT_YET_VALID:
  		k = "X509_V_ERR_CRL_NOT_YET_VALID";
  		break;
  	case X509_V_ERR_CRL_HAS_EXPIRED:
  		k = "X509_V_ERR_CRL_HAS_EXPIRED";
  		break;
  	case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD:
  		k = "X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD";
  		break;
  	case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD:
  		k = "X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD";
  		break;
  	case X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD:
  		k = "X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD";
  		break;
  	case X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD:
  		k = "X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD";
  		break;
  	case X509_V_ERR_OUT_OF_MEM:
  		k = "X509_V_ERR_OUT_OF_MEM";
  		break;
  	case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
  		k = "X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT";
  		break;
  	case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
  		k = "X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN";
  		break;
  	case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
  		k = "X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY";
  		break;
  	case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE:
  		k = "X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE";
  		break;
  	case X509_V_ERR_CERT_CHAIN_TOO_LONG:
  		k = "X509_V_ERR_CERT_CHAIN_TOO_LONG";
  		break;
  	case X509_V_ERR_CERT_REVOKED:
  		k = "X509_V_ERR_CERT_REVOKED";
  		break;
  	case X509_V_ERR_INVALID_CA:
  		k = "X509_V_ERR_INVALID_CA";
  		break;
  	case X509_V_ERR_PATH_LENGTH_EXCEEDED:
  		k = "X509_V_ERR_PATH_LENGTH_EXCEEDED";
  		break;
  	case X509_V_ERR_INVALID_PURPOSE:
  		k = "X509_V_ERR_INVALID_PURPOSE";
  		break;
  	case X509_V_ERR_CERT_UNTRUSTED:
  		k = "X509_V_ERR_CERT_UNTRUSTED";
  		break;
  	case X509_V_ERR_CERT_REJECTED:
  		k = "X509_V_ERR_CERT_REJECTED";
  		break;
  	}
  
  	/*
  	 * These two errors come fgpdb-2010-02-18_184837.csvrom using self-signed certificates.  We don't care about that.
  	 */
  	if (vfy_result != X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY &&
  		vfy_result != X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN)
  		elog(DEBUG1, "SMTP_EV_INVALID_PEER_CERTIFICATE: %ld: %s", vfy_result, k);
  
  	return 1; /* Accept the problem and try to continue  */
  }
  
  /*
   * We currently just return 1 to keep libesmtp happy
   * Will turn this into a proper event handler some day
   */
  static void event_cb(smtp_session_t session, int event_no, void *arg, ...)
  {
  	va_list alist;
  	int *ok;
  
  	va_start(alist, arg);
  
  	switch (event_no)
  	{
  
  	case SMTP_EV_WEAK_CIPHER:
  		{
  			long bits;
  
  			bits = va_arg(alist, long);
  			ok = va_arg(alist, int*);
  			elog(DEBUG3,"WEAK_CIPHER bits=%ld",bits);
  			*ok = 1;
  		}
  		break;
  
  	case SMTP_EV_INVALID_PEER_CERTIFICATE:
  		{
  			long vfy_result;
  			vfy_result = va_arg(alist, long);
  			ok = va_arg(alist, int*);
  			*ok = handle_invalid_peer_certificate(vfy_result);
  		}
  		break;
  
  	case SMTP_EV_NO_PEER_CERTIFICATE:
  		{
  			ok = va_arg(alist, int*);
  			elog(DEBUG3,"NO PEER CERT");
  			*ok = 1;
  		}
  		break;
  	case SMTP_EV_WRONG_PEER_CERTIFICATE:
  		{
  			ok = va_arg(alist, int*);
  			elog(DEBUG2,"SMTP_EV_WRONG_PEER_CERTIFICATE");
  			*ok = 1;
  		}
  		break;
  	case SMTP_EV_NO_CLIENT_CERTIFICATE:
  		{
  			ok = va_arg(alist, int*);
  			elog(DEBUG2,"NO CLIENT CERT");
  			*ok = 1;
  		}
  		break;
  
  	case SMTP_EV_STARTTLS_OK:
  		elog(DEBUG2,"SMTP_EV_STARTTLS_OK");
  		break;
  	case SMTP_EV_CONNECT:
  		elog(DEBUG1,"SMTP_EV_CONNECT");
  		break;
  	case SMTP_EV_MAILSTATUS:
  	{
  		const smtp_status_t *status = NULL;
  
  		smtp_message_t msg;
  		const char * rev_path;
  		rev_path = va_arg(alist, const char*);
  		msg = va_arg(alist, smtp_message_t);
  
  		if (msg != NULL)
  		status = smtp_reverse_path_status (msg);
  
  		if (status && status->code != 0 && status->code != 250 && status->text != NULL)
  			elog(LOG,"EV_MAILSTATUS: SMTP server reports a problem %d:  %s:  %s", status->code, rev_path,  status->text);
  	}
  	break;
  	case SMTP_EV_RCPTSTATUS:
  #if 0
  	{
  
  		/*
  		 * Debugging code... Don't need this in production use, but useful during development.
  		 */
  		const smtp_status_t *status = NULL;
  
  		smtp_mailbox_t mbox;
  		mbox = va_arg(alist, smtp_mailbox_t);
  		smtp_recipient_t rcpt;
  		rcpt = va_arg(alist, smtp_recipient_t);
  		if (rcpt != NULL)
  			status = smtp_recipient_status (rcpt);
  
  		if (status && status->code != 0 && status->code != 250 && status->text != NULL)
  			elog(LOG,"SMTP server reports a  problem %d: %s", status->code, status->text);
  	}
  #endif
  		break;
  	case SMTP_EV_MESSAGEDATA:
  		break;
  	case SMTP_EV_MESSAGESENT:
  	{
  		const smtp_status_t *status = NULL;
  
  		smtp_message_t msg;
  		msg = va_arg(alist, smtp_message_t);
  		if (msg != NULL)
  			status = smtp_message_transfer_status (msg);
  
  		if (status && status->code != 0 && status->code != 250 && status->text != NULL)
  			elog(LOG,"SMTP server reports a messagesent problem %d: %s", status->code, status->text);
  		else
  			elog(DEBUG2,"EV_MESSAGESENT");
  	}
  	break;
  	case SMTP_EV_DISCONNECT:
  		elog(DEBUG2, "EV_DISCONNECT");
  		break;
  
  	case SMTP_EV_EXTNA_8BITMIME:
  		elog(DEBUG1, "SMTP_EV_EXTNA_8BITMIME");
  		break;
  
  	case SMTP_EV_EXTNA_DSN:
  		elog(DEBUG1, "SMTP_EV_EXTNA_DSN");
  		break;
  
  	default:
  		elog(LOG, "SMTP_EV_%d",event_no);
  		break;
  
  	}
  
  	va_end(alist);
  }
  
  /*
   * This routine is strictly for debugging... It dumps out our interaction with the SMTP server.
   */
  static void
  monitor_cb (const char *buf, int buflen, int writing, void *arg)
  {
    elog(DEBUG1, "SMTP %s: %.*s",(writing == SMTP_CB_HEADERS) ? "H" : writing ? "C" : "S", buflen, buf);
  }
  
  
  #endif
  
  static bool
  SplitString(char *rawstring, char delimiter,
  					  List **namelist)
  {
  	char	   *nextp = rawstring;
  	bool		done = false;
  
  	*namelist = NIL;
  
  	while (isspace((unsigned char) *nextp))
  		nextp++;				/* skip leading whitespace */
  
  	if (*nextp == '\0')
  		return true;			/* allow empty string */
  
  	/* At the top of the loop, we are at start of a new address. */
  	do
  	{
  		char	   *curname;
  		char	   *endp;
  
  		curname = nextp;
  		while (*nextp && *nextp != delimiter)
  			nextp++;
  		endp = nextp;
  		if (curname == nextp)
  			return false;	/* empty unquoted name not allowed */
  
  
  
  		while (isspace((unsigned char) *nextp))
  			nextp++;			/* skip trailing whitespace */
  
  		if (*nextp == delimiter)
  		{
  			nextp++;
  			while (isspace((unsigned char) *nextp))
  				nextp++;		/* skip leading whitespace for next */
  			/* we expect another name, so done remains false */
  		}
  		else if (*nextp == '\0')
  			done = true;
  		else
  			return false;		/* invalid syntax */
  
  		/* Now safe to overwrite separator with a null */
  		*endp = '\0';
  
  		/*
  		 * Finished isolating current name --- add it to list
  		 */
  		*namelist = lappend(*namelist, curname);
  
  		/* Loop back if we didn't reach end of string */
  	} while (!done);
  
  	return true;
  }
  