blob: 3568bdb50a97df925bfce8a3d56b208beebf7bf6 [file] [log] [blame]
/*-------------------------------------------------------------------------
*
* 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;
}