From 317281f66189f7c08d1136298d68c758ecb13f41 Mon Sep 17 00:00:00 2001
From: Jasmin Dizdarevic <jasmin.dizdarevic@gmail.com>
Date: Fri, 15 Jul 2011 00:51:37 +0200
Subject: [PATCH] Email notification for jobs and SQL execution from file. Version change to 4.

---
 include/job.h       |    1 +
 include/mailer.h    |    7 +
 include/misc.h      |    1 +
 include/pgAgent.h   |    4 +
 job.cpp             |  110 +++++++++++++++-
 mailer.c            |  349 +++++++++++++++++++++++++++++++++++++++++++++++++++
 misc.cpp            |   12 ++
 pgAgent.cpp         |   29 ++++-
 pgagent.sql         |   30 ++++-
 pgagent_upgrade.sql |   46 ++++++--
 unix.cpp            |    2 +
 win32.cpp           |    5 +-
 12 files changed, 571 insertions(+), 25 deletions(-)
 create mode 100644 include/mailer.h
 create mode 100644 mailer.c

diff --git a/include/job.h b/include/job.h
index 038973d..30bd08f 100644
--- a/include/job.h
+++ b/include/job.h
@@ -29,6 +29,7 @@ protected:
     DBconn *threadConn;
     wxString jobid, logid;
     wxString status;
+	void SendMail(wxString &reciepient, wxString &subject, wxString &body);
 };
 
 
diff --git a/include/mailer.h b/include/mailer.h
new file mode 100644
index 0000000..8c94cde
--- /dev/null
+++ b/include/mailer.h
@@ -0,0 +1,7 @@
+
+#ifndef MAILER_H
+#define MAILER_H
+#endif
+
+
+int sendmail(char *pMailServer, int smtpPort, char *pMailTo, char *pMailCC, char *pMailBCC, char *pMailFrom, char *pMailSubject, char *pMailBody);
\ No newline at end of file
diff --git a/include/misc.h b/include/misc.h
index b33779d..9c130ff 100644
--- a/include/misc.h
+++ b/include/misc.h
@@ -19,5 +19,6 @@ void setOptions(int argc, char **argv, const wxString& executable);
 wxString getArg(int &argc, char** &argv);
 wxString NumToStr(const long l);
 
+
 #endif // MISC_H
 
diff --git a/include/pgAgent.h b/include/pgAgent.h
index 37cd194..317ce6b 100644
--- a/include/pgAgent.h
+++ b/include/pgAgent.h
@@ -22,12 +22,16 @@
 #include "connection.h"
 #include "job.h"
 
+
 extern long longWait;
 extern long shortWait;
 extern long minLogLevel;
 extern wxString connectString;
 extern wxString serviceDBname;
 extern wxString backendPid;
+extern wxString smtpServer;
+extern wxString smtpFromAddress;
+extern bool smtpConfigurationState;
 
 #ifndef __WXMSW__
 extern bool runInForeground;
diff --git a/job.cpp b/job.cpp
index 0611c93..45a2155 100644
--- a/job.cpp
+++ b/job.cpp
@@ -1,4 +1,4 @@
-//////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////
 //
 // pgAgent - PostgreSQL Tools
 // $Id: job.cpp 4875 2006-01-06 21:06:46Z dpage $
@@ -8,18 +8,25 @@
 // job.cpp - pgAgent job
 //
 //////////////////////////////////////////////////////////////////////////
-
 #include "pgAgent.h"
 
 #include <wx/file.h>
 #include <wx/filefn.h>
 #include <wx/filename.h>
+#include <wx/textfile.h>
 #include <wx/process.h>
+#include <wx/tokenzr.h>
+#include "mailer.h"
+
 
 #ifndef __WIN32__
 #include <errno.h>
 #endif
 
+
+
+
+
 Job::Job(DBconn *conn, const wxString &jid)
 {
     threadConn=conn;
@@ -77,6 +84,25 @@ Job::~Job()
 int Job::Execute()
 {
     int rc=0;
+	
+	wxString notKind, notRecp, jobName;
+
+
+	if(smtpConfigurationState)
+	{	
+		DBresult *jobInfo = threadConn->Execute(
+			wxT("SELECT jobname, coalesce(jobmailnottype,'') as jobmailnottype,")
+			wxT(" coalesce(jobmailnotrecp,'') as jobmailnotrecp")
+			wxT(" FROM pgagent.pga_job WHERE jobid = ") + jobid);	
+		if(jobInfo) 
+		{
+			notKind = jobInfo->GetString(wxT("jobmailnottype"));
+			notRecp = jobInfo->GetString(wxT("jobmailnotrecp"));
+			jobName = jobInfo->GetString(wxT("jobname"));
+		}
+		delete jobInfo;				
+	}
+	
     DBresult *steps=threadConn->Execute(
         wxT("SELECT * ")
         wxT("  FROM pgagent.pga_jobstep ")
@@ -89,7 +115,7 @@ int Job::Execute()
         status=wxT("i");
         return -1;
     }
-
+	wxString mailNotContent, mailNotSubject; /**/
     while (steps->HasData())
     {
         DBconn *stepConn;
@@ -125,16 +151,49 @@ int Job::Execute()
 
         switch ((int) steps->GetString(wxT("jstkind"))[0])
         {
+			case 'f':
             case 's':
             {
                 wxString jstdbname = steps->GetString(wxT("jstdbname"));
                 wxString jstconnstr = steps->GetString(wxT("jstconnstr"));
-                
+				wxString type = steps->GetString(wxT("jstkind"));
+                wxString code = steps->GetString(wxT("jstcode"));
+
+				if(type == wxT("f")) 
+				{
+					wxString content;					
+					wxTextFile *codeFile = new wxTextFile(code);
+					if(!codeFile->Exists()) 
+					{
+						output = wxString::Format(_("Could not open file: \"%s\""), code.c_str());
+						LogMessage(output, LOG_WARNING);
+						rc = -1;						
+						break;
+					}					
+					codeFile->Open();															
+					while(!codeFile->Eof()) 
+					{
+						content += codeFile->GetNextLine();
+					}
+					codeFile->Close();
+					delete codeFile;
+
+					if(content == wxT("")) 
+					{
+						output = wxString::Format(_("File \"%s\" is empty."), code.c_str());
+						LogMessage(output, LOG_WARNING);
+						rc = -1;
+						break;
+					}
+					code = content;
+
+				}
+
                 stepConn=DBconn::Get(jstconnstr, jstdbname);
                 if (stepConn)
                 {
                     LogMessage(wxString::Format(_("Executing SQL step %s (part of job %s)"), stepid.c_str(), jobid.c_str()), LOG_DEBUG);
-                    rc=stepConn->ExecuteVoid(steps->GetString(wxT("jstcode")));
+                    rc=stepConn->ExecuteVoid(code);
                     output = stepConn->GetLastError();
                     stepConn->Return();
                 }
@@ -324,19 +383,57 @@ int Job::Execute()
             wxT("       jslresult = ") + NumToStr(rc) + wxT(", jslstatus = '") + stepstatus + wxT("', ")
             wxT("       jsloutput = ") + threadConn->qtDbString(output) + wxT(" ")
             wxT(" WHERE jslid=") + jslid);
+
+		
         if (rc != 1 || stepstatus == wxT("f"))
         {
-            status = wxT("f");
+			/* fail notification on first failed jobstep*/
+            status = wxT("f");			
+			mailNotSubject = wxString::Format(wxT("pgAgent: Error in processing Job(ID): %s(%s)"), jobName.c_str(), jobid.c_str());
+			mailNotContent = wxString::Format(wxT("Error occured at step '%s %s': "), steps->GetString(wxT("jstname")).c_str(), output.c_str());
+			if(notKind == status || notKind == wxT("b")) 
+			{ 
+				Job::SendMail(notRecp, mailNotSubject, mailNotContent); 
+			} 			
             return -1;
         }
         steps->MoveNext();
     }
+	/* success notification only one time per job */
+	mailNotSubject = wxString::Format(wxT("Job(ID) %s(%s) run successfully."), jobName.c_str(), jobid.c_str()); 
+	mailNotContent = wxT("");
+	if(notKind == wxT("s") || notKind == wxT("b")) 
+	{ 
+		Job::SendMail(notRecp, mailNotSubject, mailNotContent); 
+	}
+
     delete steps;
 
     status = wxT("s");
     return 0;
 }
 
+void Job::SendMail(wxString &reciepient, wxString &subject, wxString &body) 
+{
+	
+	if(!smtpConfigurationState)
+	{	
+		//SMTP Server not configured properly, nothing to do.
+		LogMessage(wxT("SMTP Server not set, exiting Job::SendMail."), LOG_DEBUG);
+		return;
+	}
+	
+	LogMessage(wxString::Format(_("Trying to send mail. Recipients: %s/Subject: %s/Body: %s"), reciepient.c_str(),subject.c_str(),body.c_str()), LOG_DEBUG);
+	int rc = sendmail(smtpServer.char_str(), 25, reciepient.char_str(), NULL, NULL, smtpFromAddress.char_str(), subject.char_str(), body.char_str());
+	if(rc != 0)
+	{
+		LogMessage(wxT("Email could not be delivered: "), LOG_WARNING);
+		return;
+	}
+	LogMessage(wxString::Format(_("Email sent. SMTP RC is: %s"), rc), LOG_DEBUG);	
+	return;
+}
+
 
 
 JobThread::JobThread(const wxString &jid)
@@ -372,3 +469,4 @@ void *JobThread::Entry()
 
     return(NULL);
 }
+
diff --git a/mailer.c b/mailer.c
new file mode 100644
index 0000000..b899e11
--- /dev/null
+++ b/mailer.c
@@ -0,0 +1,349 @@
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+
+
+
+#ifdef _WIN32
+#pragma warning (disable : 4996 4005 )
+
+
+
+#include <windows.h>
+
+
+
+#pragma comment(lib, "ws2_32.lib")
+#else
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <unistd.h>
+#endif
+
+#define BUFF_SIZE 1024
+#define SMTP_PORT 25
+
+
+int validMail(char *address) {
+
+    int len = 0;
+    
+    if(!address)
+        return 0;
+        
+    while(address[len] != '\0') {
+        if(address[len] == '@')
+            return 1;
+        len++;
+    }
+    return 0;
+}
+
+int readn(int fd, char *ptr, int size) {
+
+    int nleft;
+    int nread;
+
+    nleft = size;
+    while(nleft > 0) {
+        if((nread = recv(fd, ptr, nleft, 0)) < 0) {
+            if (nleft == size)
+                return(-1); /* error, return -1 */
+            else
+                break; /* error, return amount read so far */
+        } else if(nread == 0) {
+            break; /* EOF */
+        }
+        nleft -= nread;
+        ptr += nread;
+    }
+    return (size - nleft); /* return >= 0 */
+}
+
+int writen(int fd, char *ptr, int n) {
+
+    int nleft;
+    int nwritten;
+
+    nleft = n;
+    while(nleft > 0) {
+        if((nwritten = send(fd, ptr, nleft, 0)) < 0) {
+            if(nleft == n)
+                return(-1); /* error, return -1 */
+            else
+                break; /* error, return amount written so far */
+        } else if (nwritten == 0) {
+            break;
+        }
+        nleft -= nwritten;
+        ptr += nwritten;
+    }
+    return(n - nleft); /* return >= 0 */
+}
+
+int serverParse(int fd, int code) {
+
+	int i = 0;
+    int count = 0;
+    int len = 0;
+    char line[BUFF_SIZE + 1];
+
+    len = recv(fd, line, BUFF_SIZE, 0);
+    if(len < 0) {
+        fprintf(stderr, "Could not read bytes from socket (recv errno %d).\r\n", errno);
+        return (-1);
+    }
+
+    while(line[i] != (int)' ' && i < len) {
+        count = count * 10 + (line[i] - '0');
+        i++;
+    }
+
+    if(count == code)
+        return 0;
+
+    return 1;
+}
+
+int sendmail(char *pMailServer, int smtpPort, char *pMailTo, char *pMailCC, char *pMailBCC, char *pMailFrom, char *pMailSubject, char *pMailBody) {
+
+	int res = 0;
+	int sock;
+    struct hostent *pHostinfo;
+    struct sockaddr_in host_addr;
+
+    char pCommand[BUFF_SIZE] = {'\0'};
+    
+    char *pChar = NULL;
+    char buff[BUFF_SIZE];
+
+#ifdef _WIN32
+    WSADATA wsaData;
+#endif
+
+    /* Check parameter... */
+    if(pMailServer == NULL) {
+        fprintf(stderr,  "pMailServer parameter not set.\r\n");
+        return(EXIT_FAILURE);
+    }
+    if(pMailTo == NULL) {
+        fprintf(stderr,  "pMailTo parameternot set.\r\n");
+        return(EXIT_FAILURE);
+    }
+    if(pMailFrom == NULL) {
+        fprintf(stderr,  "pMailFrom parameter not set.\r\n");
+        return(EXIT_FAILURE);
+    }
+    if(pMailSubject == NULL) {
+        fprintf(stderr, "pMailSubject parameter not set.\r\n");
+        return(EXIT_FAILURE);
+    }
+    if(pMailBody == NULL) {
+        fprintf(stderr, "pMailBody parameter not set.\r\n");
+        return(EXIT_FAILURE);
+    }
+
+    if(pMailTo != NULL && !validMail(pMailTo)) {
+        fprintf(stderr, "pMailTo parameter not valid.\r\n");
+        return(EXIT_FAILURE);
+    }
+    if(pMailFrom != NULL && !validMail(pMailFrom)) {
+        fprintf(stderr, "pMailFrom parameter not valid.\r\n");
+        return(EXIT_FAILURE);
+    }
+    if(pMailCC != NULL && !validMail(pMailCC)) {
+        fprintf(stderr, "pMailTo parameter not valid.\r\n");
+        return(EXIT_FAILURE);
+    }
+    if(pMailBCC != NULL && !validMail(pMailBCC)) {
+        fprintf(stderr, "pMailTo parameter not valid.\r\n");
+        return(EXIT_FAILURE);
+    }
+
+    /* initialize Winsock */
+#ifdef _WIN32
+	if (WSAStartup(MAKEWORD(1, 1), &wsaData) != 0) {
+        fprintf(stderr, "Not able to initialize WinSock.\r\n");
+        return(EXIT_FAILURE);
+    }
+#endif
+
+    /* initialize socket */
+    sock = socket(AF_INET, SOCK_STREAM, 0);
+    if (sock == -1) {
+        fprintf(stderr, "Not able to initialize Socket (errno %d).\r\n", errno);
+        return(EXIT_FAILURE);
+    }
+
+    /* Set mailserver address */
+    memset( &host_addr, 0, sizeof(host_addr));
+    host_addr.sin_family = AF_INET;
+
+	if(smtpPort > 0 && smtpPort < 65000)
+		host_addr.sin_port = htons(smtpPort);
+	else
+		host_addr.sin_port = htons(SMTP_PORT);
+
+    host_addr.sin_addr.s_addr = inet_addr(pMailServer);
+    if(host_addr.sin_addr.s_addr == INADDR_NONE) {
+        /* servername is dns address */
+        pHostinfo = gethostbyname(pMailServer);
+        if(pHostinfo == NULL) {
+            fprintf(stderr, "Not able to resolve dns name (errno %d).\r\n", errno);
+            return(EXIT_FAILURE);
+        }
+        memcpy((char*)&host_addr.sin_addr.s_addr, pHostinfo->h_addr, pHostinfo->h_length);
+    }
+
+
+    /* Set up connection */
+    if(connect(sock, (struct sockaddr *)&host_addr, sizeof(struct sockaddr)) == -1) {
+        fprintf(stderr, "Not able to connect to server (errno %d).\r\n", res);
+        return(EXIT_FAILURE);
+    }
+
+    /* Handshake + data transfer */
+    res = serverParse(sock, 220);
+    if(res == -1 || res == 1) {
+        fprintf(stderr, "Handshake error (220).\r\n");
+        return(EXIT_FAILURE);
+    }
+
+    sprintf(pCommand, "HELO %s\r\n", pMailServer);
+    writen(sock, pCommand, strlen(pCommand));
+    res = serverParse(sock, 250);
+    if(res == -1 || res == 1) {
+        fprintf(stderr, "Handshake error (HELO 250).\r\n");
+        return(EXIT_FAILURE);
+    }
+
+    sprintf(pCommand, "MAIL FROM: <%s>\r\n", pMailFrom);
+    writen(sock, pCommand, strlen(pCommand));
+    res = serverParse(sock, 250);
+    if(res == -1 || res == 1) {
+        fprintf(stderr, "Handshake error (MAIL FROM 250).\r\n");
+        return(EXIT_FAILURE);
+    }
+    
+        
+    strncpy(buff, pMailTo, strlen(pMailTo) + 1);
+    pChar = strtok(buff, ",");
+    while(pChar != NULL) {
+        
+        if(!validMail(pChar)) {
+            fprintf(stderr, "pMailTo not valid. Skip %s\r\n", pChar);
+        } else {            
+            sprintf(pCommand, "RCPT TO: %s\r\n", pChar);
+            writen(sock, pCommand, strlen(pCommand));
+            res = serverParse(sock, 250);
+            if(res == -1 || res == 1) {
+                fprintf(stderr, "Handshake error (RCPT TO 250).(%s)\r\n", pCommand);
+                return(EXIT_FAILURE);
+            }
+        }
+        pChar = strtok(NULL, ",");
+    }
+    
+    if(pMailCC != NULL) {
+        strncpy(buff, pMailCC, strlen(pMailCC) + 1);
+        pChar = strtok(buff, ",");
+        while(pChar != NULL) {
+            
+            if(!validMail(pChar)) {
+                fprintf(stderr, "pMailCC not valid. Skip %s\r\n", pChar);
+            } else {            
+                sprintf(pCommand, "RCPT TO: %s\r\n", pChar);
+                writen(sock, pCommand, strlen(pCommand));
+                res = serverParse(sock, 250);
+                if(res == -1 || res == 1) {
+                    fprintf(stderr, "Handshake error (RCPT TO 250).(%s)\r\n", pCommand);
+                    return(EXIT_FAILURE);
+                }
+            }
+            pChar = strtok(NULL, ",");
+        }
+    }
+    
+    if(pMailBCC != NULL) {
+        strncpy(buff, pMailBCC, strlen(pMailBCC) + 1);
+        pChar = strtok(buff, ",");
+        while(pChar != NULL) {
+            
+            if(!validMail(pChar)) {
+                fprintf(stderr, "pMailBCC not valid. Skip %s\r\n", pChar);
+            } else {
+                sprintf(pCommand, "RCPT TO: %s\r\n", pChar);
+                writen(sock, pCommand, strlen(pCommand));
+                res = serverParse(sock, 250);
+                if(res == -1 || res == 1) {
+					fprintf(stderr, "Handshake error (RCPT TO 250).(%s)\r\n", pCommand);
+					return(EXIT_FAILURE);
+                }
+            }
+            pChar = strtok(NULL, ",");
+        }
+    }
+
+    sprintf(pCommand, "DATA\r\n");
+    writen(sock, pCommand, strlen(pCommand));
+    res = serverParse(sock, 354);
+    if(res == -1 || res == 1) {
+		fprintf(stderr, "Handshake error (DATA 354).\r\n");
+		return(EXIT_FAILURE);
+    }
+
+    sprintf(pCommand, "Subject: %s\r\n", pMailSubject);
+    writen(sock, pCommand, strlen(pCommand));
+
+    sprintf(pCommand, "TO: %s\r\n", pMailTo);
+    writen(sock, pCommand, strlen(pCommand));
+    
+    if(pMailCC != NULL) {
+        sprintf(pCommand, "CC: %s\r\n", pMailCC);
+        writen(sock, pCommand, strlen(pCommand));
+    }
+    
+    if(pMailBCC != NULL) {
+        sprintf(pCommand, "BCC: %s\r\n", pMailBCC);
+        writen(sock, pCommand, strlen(pCommand));
+    }
+
+    sprintf(pCommand, "\r\n%s\r\n.\r\n", pMailBody);
+    writen(sock, pCommand, strlen(pCommand));
+    res = serverParse(sock, 250);
+    if(res == -1 || res == 1) {
+		fprintf(stderr, "Handshake error (. 250).\r\n");
+		return(EXIT_FAILURE);
+    }
+	
+
+    sprintf(pCommand, "QUIT\r\n");
+    writen(sock, pCommand, strlen(pCommand));
+
+    /* Close Sockets */
+#ifdef _WIN32
+    closesocket(sock);
+    WSACleanup();
+#else
+    close(sock);
+#endif
+    
+    return(EXIT_SUCCESS);
+}
+
+
+/*int main(void) {
+	
+	int i = 0;
+	for(i = 0; i < 10; i++) {
+		printf("%d\n", i);
+		sendmail("mail.vvb", 25, "rene.pichler@vvb.at", NULL, NULL, "rene.pichler@vvb.at", "Testsubject", "Testmailbody");
+	}
+
+	getchar();
+	return 0;
+}*/
\ No newline at end of file
diff --git a/misc.cpp b/misc.cpp
index 6d78268..df57518 100644
--- a/misc.cpp
+++ b/misc.cpp
@@ -16,6 +16,7 @@
 #include <unistd.h>
 #endif
 
+
 // In unix.c or win32.c
 void usage(const wxString& executable);
 
@@ -72,6 +73,17 @@ void setOptions(int argc, char **argv, const wxString& executable)
                         minLogLevel = val;
                     break;
                 }
+				case 'a': 
+				{
+					smtpServer = getArg(argc, argv);
+					break;
+				}
+				case 'f':
+				{
+					smtpFromAddress = getArg(argc, argv);
+					break;
+				}
+
 #ifndef __WXMSW__
                 case 'f':
                 {
diff --git a/pgAgent.cpp b/pgAgent.cpp
index 31b6615..451b82e 100644
--- a/pgAgent.cpp
+++ b/pgAgent.cpp
@@ -3,13 +3,15 @@
 // pgAgent - PostgreSQL Tools
 // $Id: pgAgent.cpp 4875 2006-01-06 21:06:46Z dpage $
 // Copyright (C) 2002 - 2010, The pgAdmin Development Team
-// This software is released under the PostgreSQL Licence
+// This software is released under the Artistic Licence
 //
 // pgAgent.cpp - pgAgent main entry
 //
 //////////////////////////////////////////////////////////////////////////
 
 #include "pgAgent.h"
+#include <wx/socket.h>
+
 
 #ifndef __WXMSW__
 #include <unistd.h>
@@ -21,6 +23,9 @@ wxString backendPid;
 long longWait=30;
 long shortWait=5;
 long minLogLevel=LOG_ERROR;
+wxString smtpServer;
+wxString smtpFromAddress;
+bool smtpConfigurationState;
 
 #define MAXATTEMPTS 10
 
@@ -38,6 +43,7 @@ int MainRestartLoop(DBconn *serviceConn)
 
     int rc;
 
+	
     LogMessage(_("Clearing zombies"), LOG_DEBUG);
     rc=serviceConn->ExecuteVoid(wxT("CREATE TEMP TABLE pga_tmp_zombies(jagpid int4)"));
 
@@ -75,12 +81,22 @@ int MainRestartLoop(DBconn *serviceConn)
     rc=serviceConn->ExecuteVoid(wxT("DROP TABLE pga_tmp_zombies"));
 
     wxString hostname = wxGetFullHostName();
-
-    rc=serviceConn->ExecuteVoid(
+	rc=serviceConn->ExecuteVoid(
         wxT("INSERT INTO pgagent.pga_jobagent (jagpid, jagstation) SELECT pg_backend_pid(), '") + hostname + wxT("'"));
     if (rc < 0)
         return rc;
 
+	//set info, if email notification is configured properly	
+	if(smtpServer != wxT("") && smtpFromAddress != wxT("")) 
+	{
+		serviceConn->ExecuteVoid(wxT("UPDATE pgagent.pga_jobagent SET jagemailconfigured = true WHERE jagpid = pg_backend_pid()"));
+		smtpConfigurationState = true;
+	}
+	else 
+	{
+		smtpConfigurationState = false;
+	}
+
     while (1)
     {
         bool foundJobToExecute=false;
@@ -130,7 +146,11 @@ int MainRestartLoop(DBconn *serviceConn)
 
 void MainLoop()
 {
+
+	
+	
     int attemptCount = 1;
+	smtpConfigurationState = false;
 
     // OK, let's get down to business
     do
@@ -197,6 +217,9 @@ void MainLoop()
                 strSchemaVerMisMatch.Printf(_("Unsupported schema version: %s. Version %s is required - please run pgagent_upgrade.sql."), strPgAgentSchemaVer.c_str(), currentPgAgentVersion.c_str());
                 LogMessage(strSchemaVerMisMatch, LOG_ERROR);
             }
+			
+
+
 
 #ifdef WIN32
 			Initialized();
diff --git a/pgagent.sql b/pgagent.sql
index af88f77..4da1f2d 100644
--- a/pgagent.sql
+++ b/pgagent.sql
@@ -20,7 +20,8 @@ COMMENT ON SCHEMA pgagent IS 'pgAgent system tables';
 CREATE TABLE pgagent.pga_jobagent (
 jagpid               int4                 NOT NULL PRIMARY KEY,
 jaglogintime         timestamptz          NOT NULL DEFAULT current_timestamp,
-jagstation           text                 NOT NULL
+jagstation           text                 NOT NULL,
+jagemailconfigured 	 boolean 			  DEFAULT FALSE
 ) WITHOUT OIDS;
 COMMENT ON TABLE pgagent.pga_jobagent IS 'Active job agents';
 
@@ -40,6 +41,19 @@ INSERT INTO pgagent.pga_jobclass (jclname) VALUES ('Data Summarisation');
 INSERT INTO pgagent.pga_jobclass (jclname) VALUES ('Miscellaneous');
 
 
+--Email notification support
+CREATE TABLE pgagent.pga_jobmailnottype (
+	jmntypeid text,
+	jmntypename text,
+	PRIMARY KEY (jmntypeid)
+);
+COMMENT ON TABLE pgagent.pga_jobmailnottype IS 'Mail notification events';
+
+INSERT INTO pgagent.pga_jobmailnottype VALUES ('','None');
+INSERT INTO pgagent.pga_jobmailnottype VALUES ('f','Failure');
+INSERT INTO pgagent.pga_jobmailnottype VALUES ('s','Success');
+INSERT INTO pgagent.pga_jobmailnottype VALUES ('b','Failure + Success');
+
 
 CREATE TABLE pgagent.pga_job (
 jobid                serial               NOT NULL PRIMARY KEY,
@@ -52,7 +66,9 @@ jobcreated           timestamptz          NOT NULL DEFAULT current_timestamp,
 jobchanged           timestamptz          NOT NULL DEFAULT current_timestamp,
 jobagentid           int4                 NULL REFERENCES pgagent.pga_jobagent(jagpid) ON DELETE SET NULL ON UPDATE RESTRICT,
 jobnextrun           timestamptz          NULL,
-joblastrun           timestamptz          NULL
+joblastrun           timestamptz          NULL,
+jobmailnottype 		 text				  NULL REFERENCES pgagent.pga_jobmailnottype (jmntypeid) MATCH SIMPLE ON UPDATE RESTRICT ON DELETE RESTRICT,
+jobmailnotrecp		 text				  NULL
 ) WITHOUT OIDS;
 COMMENT ON TABLE pgagent.pga_job IS 'Job main entry';
 COMMENT ON COLUMN pgagent.pga_job.jobagentid IS 'Agent that currently executes this job.';
@@ -65,16 +81,16 @@ jstjobid             int4                 NOT NULL REFERENCES pgagent.pga_job (j
 jstname              text                 NOT NULL,
 jstdesc              text                 NOT NULL DEFAULT '',
 jstenabled           bool                 NOT NULL DEFAULT true,
-jstkind              char                 NOT NULL CHECK (jstkind IN ('b', 's')), -- batch, sql
+jstkind              char                 NOT NULL CHECK (jstkind IN ('b', 's', 'f')), -- batch, sql, file
 jstcode              text                 NOT NULL,
-jstconnstr           text                 NOT NULL DEFAULT '' CHECK ((jstconnstr != '' AND jstkind = 's' ) OR (jstconnstr = '' AND (jstkind = 'b' OR jstdbname != ''))),
-jstdbname            name                 NOT NULL DEFAULT '' CHECK ((jstdbname != '' AND jstkind = 's' ) OR (jstdbname = '' AND (jstkind = 'b' OR jstconnstr != ''))),
+jstconnstr           text                 NOT NULL DEFAULT '' CHECK ((jstconnstr != '' AND jstkind IN ('s','f')) OR (jstconnstr = '' AND (jstkind = 'b' OR jstdbname != ''))),
+jstdbname            name                 NOT NULL DEFAULT '' CHECK ((jstdbname != '' AND jstkind IN ('s','f')) OR (jstdbname = '' AND (jstkind = 'b' OR jstconnstr != ''))),
 jstonerror           char                 NOT NULL CHECK (jstonerror IN ('f', 's', 'i')) DEFAULT 'f', -- fail, success, ignore
 jscnextrun           timestamptz          NULL
 ) WITHOUT OIDS;
 CREATE INDEX pga_jobstep_jobid ON pgagent.pga_jobstep(jstjobid);
 COMMENT ON TABLE pgagent.pga_jobstep IS 'Job step to be executed';
-COMMENT ON COLUMN pgagent.pga_jobstep.jstkind IS 'Kind of jobstep: s=sql, b=batch';
+COMMENT ON COLUMN pgagent.pga_jobstep.jstkind IS 'Kind of jobstep: s=sql, b=batch, f=file';
 COMMENT ON COLUMN pgagent.pga_jobstep.jstonerror IS 'What to do if step returns an error: f=fail the job, s=mark step as succeeded and continue, i=mark as fail but ignore it and proceed';
 
 
@@ -148,7 +164,7 @@ CREATE OR REPLACE FUNCTION pgagent.pgagent_schema_version() RETURNS int2 AS '
 BEGIN
     -- RETURNS PGAGENT MAJOR VERSION
     -- WE WILL CHANGE THE MAJOR VERSION, ONLY IF THERE IS A SCHEMA CHANGE
-    RETURN 3;
+    RETURN 4;
 END;
 ' LANGUAGE 'plpgsql' VOLATILE;
 
diff --git a/pgagent_upgrade.sql b/pgagent_upgrade.sql
index ec1ec1d..7a802a0 100644
--- a/pgagent_upgrade.sql
+++ b/pgagent_upgrade.sql
@@ -7,16 +7,46 @@
 // pgagent_upgrade.sql - Upgrade pgAgent tables and functions
 //
 */
+--Email notification support
+CREATE TABLE pgagent.pga_jobmailnottype (
+	jmntypeid text,
+	jmntypename text,
+	PRIMARY KEY (jmntypeid)
+);
+COMMENT ON TABLE pgagent.pga_jobmailnottype is 'Mail notification events';
 
-CREATE OR REPLACE FUNCTION pgagent.pgagent_schema_version() RETURNS int2 AS '
+INSERT INTO pgagent.pga_jobmailnottype VALUES ('','None');
+INSERT INTO pgagent.pga_jobmailnottype VALUES ('f','Failure');
+INSERT INTO pgagent.pga_jobmailnottype VALUES ('s','Success');
+INSERT INTO pgagent.pga_jobmailnottype VALUES ('b','Failure + Success');
+
+ALTER TABLE pgagent.pga_job ADD COLUMN jobmailnottype text;
+ALTER TABLE pgagent.pga_job ADD COLUMN jobmailnotrecp text;
+
+ALTER TABLE pgagent.pga_job ADD CONSTRAINT pga_job_jobjmntypeid_fkey FOREIGN KEY (jobmailnottype)
+      REFERENCES pgagent.pga_jobmailnottype (jmntypeid) MATCH SIMPLE
+      ON UPDATE RESTRICT ON DELETE RESTRICT;
+	  
+ALTER TABLE pgagent.pga_jobagent ADD COLUMN jagemailconfigured boolean default false;
+
+--Jobstep SQL-File support
+ALTER TABLE pgagent.pga_jobstep DROP CONSTRAINT pga_jobstep_check;	
+ALTER TABLE pgagent.pga_jobstep DROP CONSTRAINT pga_jobstep_check1;	
+ALTER TABLE pgagent.pga_jobstep DROP CONSTRAINT pga_jobstep_jstkind_check;	
+ALTER TABLE pgagent.pga_jobstep ADD CONSTRAINT pga_jobstep_check CHECK (jstconnstr <> ''::text AND (jstkind = ANY (ARRAY['s'::bpchar, 'f'::bpchar])) OR jstconnstr = ''::text AND (jstkind = 'b'::bpchar OR jstdbname <> ''::name));
+ALTER TABLE pgagent.pga_jobstep ADD CONSTRAINT pga_jobstep_check1 CHECK (jstdbname <> ''::name AND (jstkind = ANY (ARRAY['s'::bpchar, 'f'::bpchar])) OR jstdbname = ''::name AND (jstkind = 'b'::bpchar OR jstconnstr <> ''::text));
+ALTER TABLE pgagent.pga_jobstep ADD CONSTRAINT pga_jobstep_jstkind_check CHECK (jstkind = ANY (ARRAY['b'::bpchar, 's'::bpchar, 'f'::bpchar]));
+
+--Update schema version
+CREATE OR REPLACE FUNCTION pgagent.pgagent_schema_version()
+  RETURNS smallint AS
+$BODY$
 BEGIN
     -- RETURNS PGAGENT MAJOR VERSION
     -- WE WILL CHANGE THE MAJOR VERSION, ONLY IF THERE IS A SCHEMA CHANGE
-    RETURN 3;
+    RETURN 4;
 END;
-' LANGUAGE 'plpgsql' VOLATILE;
-
-ALTER TABLE pgagent.pga_jobstep ADD COLUMN jstconnstr text NOT NULL DEFAULT '' CONSTRAINT pga_jobstep_connstr_check CHECK ((jstconnstr != '' AND jstkind = 's' ) OR (jstconnstr = '' AND (jstkind = 'b' OR jstdbname != '')));
-ALTER TABLE pgagent.pga_jobstep DROP CONSTRAINT pga_jobstep_check;
-
-ALTER TABLE pgagent.pga_jobstep ADD CONSTRAINT pga_jobstep_dbname_check CHECK ((jstdbname != '' AND jstkind = 's' ) OR (jstdbname = '' AND (jstkind = 'b' OR jstconnstr != '')));
+$BODY$
+  LANGUAGE plpgsql VOLATILE
+  COST 100;
+  
\ No newline at end of file
diff --git a/unix.cpp b/unix.cpp
index 0626031..6d0e543 100644
--- a/unix.cpp
+++ b/unix.cpp
@@ -28,6 +28,8 @@ void usage(const wxString &executable)
     wxPrintf(_("-f run in the foreground (do not detach from the terminal)\n"));
     wxPrintf(_("-t <poll time interval in seconds (default 10)>\n"));
     wxPrintf(_("-r <retry period after connection abort in seconds (>=10, default 30)>\n"));
+	wxPrintf(_("-e <smtp server for email notification>\n"));
+	wxPrintf(_("-a <smtp from address>\n"));
     wxPrintf(_("-s <log file (messages are logged to STDOUT if not specified>\n"));
     wxPrintf(_("-l <logging verbosity (ERROR=0, WARNING=1, DEBUG=2, default 0)>\n"));
 }
diff --git a/win32.cpp b/win32.cpp
index 9847f6e..8f25993 100644
--- a/win32.cpp
+++ b/win32.cpp
@@ -309,7 +309,7 @@ void CALLBACK serviceMain(DWORD argc, LPTSTR *argv)
         if (initService())
         {
             serviceStatus.dwCurrentState = SERVICE_RUNNING;
-            serviceStatus.dwWaitHint = 1000;
+            serviceStatus.dwWaitHint = shortWait*1000;
         }
         else
             serviceStatus.dwCurrentState = SERVICE_STOPPED;
@@ -449,6 +449,8 @@ void usage(const wxString &executable)
     wxPrintf(_("-d <displayname>\n"));
     wxPrintf(_("-t <poll time interval in seconds (default 10)>\n"));
     wxPrintf(_("-r <retry period after connection abort in seconds (>=10, default 30)>\n"));
+	wxPrintf(_("-a <smtp server for email notification>\n"));
+	wxPrintf(_("-f <smtp from address>\n"));
     wxPrintf(_("-l <logging verbosity (ERROR=0, WARNING=1, DEBUG=2, default 0)>\n"));
 }
 
@@ -499,6 +501,7 @@ void main(int argc, char **argv)
     {
         wxString displayname = _("PostgreSQL Scheduling Agent - ") + serviceName;
         wxString args = wxT("RUN ") + serviceName;
+		
 
         while (argc-- > 0)
         {
-- 
1.7.4.msysgit.0
