
/******************************************************************************
**
**  This program is free software; you can redistribute it and/or
**  modify it, however, you cannot sell it.
**
**  This program is distributed in the hope that it will be useful,
**  but WITHOUT ANY WARRANTY; without even the implied warranty of
**  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
**
**  You should have received a copy of the license attached to the
**  use of this software.  If not, visit www.shmoo.com/osiris for
**  details.
**
******************************************************************************/

/*****************************************************************************
**
**  File:    md_notify.c
**  Date:    July 1, 2003
**
**  Author:  Brian Wotring
**  Purpose: notify admin of scan logs, initially this will be via email.
**
******************************************************************************/

#include "libosiris.h"
#include "libosirism.h"
#include "libosirisdb.h"
#include "libosirisctl.h"
#include "libfileapi.h"

#include "md_hosts.h"
#include "md_log.h"
#include "md_config.h"
#include "md_compare.h"
#include "md_notify.h"

#include "common.h"
#include "logging.h"


extern OSI_MANAGEMENT_CONFIG *config;

#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>

#include <sys/types.h>

#include <openssl/ssl.h>
#include <openssl/err.h>

#ifdef WIN32

#include <winsock.h>
#include <errno.h>
#include <io.h>

#define EWOULDBLOCK WSAEWOULDBLOCK

#else   /* WIN32 */

#include <sys/socket.h>
#include <sys/errno.h>
#include <sys/time.h>

#include <netinet/in.h>
#include <netdb.h>
#include <unistd.h>

#endif  /* WIN32 */

#include <fcntl.h>

#include "utilities.h"
#include "socketapi.h"

#ifndef WIN32
extern int errno;
extern int h_errno;
#endif  /* WIN32 */


static int verbose = 0;

#define OSIRIS_SMTP_PORT        25

#define OSIRIS_SMTP_CMD_DATA    "DATA\r\n"
#define OSIRIS_SMTP_CMD_QUIT    "QUIT\r\n"

#define OSIRIS_SMTP_CMD_BUFSZ   1024

#define MAX_SMTP_LINE   128

#define LOGFILE_PERMISSIONS 0600

#define NOTIFY_SCHEDULE_FAIL_FORMAT  "The scheduler was unable to initiate the following scan:\n\nhost: %s\nscheduled scan time: %s\n\nThe scheduler produced the following error message:\n\n%s"

#define NOTIFY_UPDATE_URL_FORMAT "\nIf these changes are approved, visit the \
URL below to set the latest scan database to be the trusted database. \
Or, login to the management console and set the trusted database to %s.  \
If these notifications persist, you may need to modify the scan config for \
this host or set some notification filters.\
\n\n    <https://%s:%d?host=%s&base_db=%s>\n\n"

#define NOTIFY_TEST_MESSAGE "This is a test notification message sent by the \
Osiris management console.\n"

/* 
 * Read a response from the SMTP server
 */

static int
read_smtp_response(int sock, char *expect)
{
    int i, pos;
    char buf[MAX_SMTP_LINE];

    /* not expecting a response, just succeed */

    if (expect == NULL) {
        return 0;
    }

    pos = 0;

    while (pos < MAX_SMTP_LINE) {

        if ((i = osi_read(sock, &buf[pos], MAX_SMTP_LINE - pos)) <= 0) {
            break;
        }

        while (i > 0) {

            if (buf[pos++] == '\n') {

                buf[pos] = '\0';

                if (strncmp(expect, &buf[0], strlen(expect)) == 0)
                {
                    /* server response matched the expected response */

                    log_info( LOG_HIGH, NULL, "%s", &buf[0] );
                    return 0;
                }

                else
                {
                    log_error(LOG_HIGH, NULL,
                          "unrecognized smtp response: (%s) expected: (%s)",
                           &buf[0], expect);

                    return 1;
                }
            }

            i--;
        }
    }

    return 1;
}

/*
 * Write a command string to the SMTP server, checking it's response
 */
static int
send_smtp_command(int sock, char *cmd, char *rsp)
{
    log_info( LOG_HIGH, NULL, "%s", cmd );

    /* write a command to the SMTP server */

    if ((osi_write(sock, cmd, strlen(cmd))) == 0)
    {
        log_error( LOG_HIGH, NULL,
                   "error sending command (%s) to smtp server.", cmd  );
    }

    /* read the response from the SMTP server */
    return read_smtp_response(sock, rsp);
}

/*
 * Open and write the contents of 'logfile' to the given socket
 */
static int
send_smtp_file(int sock, char *logfile)
{
    FILE *fp = NULL;
    int len = 0;
    char buf[OSIRIS_SMTP_CMD_BUFSZ];


    if ((fp = osi_fopen( logfile, "r", LOGFILE_PERMISSIONS)) == NULL)
    {
        log_error( LOG_HIGH, NULL,
                   "notification: error opening log file (%s).",
                   logfile );
        return 1;
    }

    for (;;) {

        /* read a buffer from logfile */
        len = fread(buf, sizeof(char), (sizeof(buf) - 1), fp);

        if (len == 0) {
            break;
        }

        buf[len] = '\0';

        log_info( LOG_HIGH, NULL, "len %d (%s)", len, buf);

        /* write the buffer to the SMTP server */
        if ((osi_write(sock, buf, len)) == 0)
        {
            log_error(LOG_HIGH, NULL, "unable to send data to smtp server.");
            break;
        }
    }

    fclose(fp);

    return 0;
}

void osi_notify_admin_scan_failed( const char *host,
                                   unsigned long scheduled_time,
                                   const char *message )
{
    int sock = 0;

    int smtp_port = config->notify_smtp_port;
    char *smtp_host = config->notify_smtp_host;
    char *email = config->notify_email;

    char *schedule;

    char buf[OSIRIS_SMTP_CMD_BUFSZ];
    char this_host[MAX_HOSTNAME_LENGTH];

    if( host == NULL )
    {
        log_error( LOG_HIGH, "unknown", "null host" );
        return;
    }

    if( email == NULL )
    {
        log_error( LOG_HIGH, host,
                  "notification: receipient email address not set.");
        return;
    }

    if( smtp_host == NULL )
    {
        log_error( LOG_HIGH, host,
                  "notification: SMTP server not set.");
        return;
    }

    /* if we were given a scheduled time, we get it as a string. */

    if( scheduled_time > 0 )
    {
        schedule = osi_time_to_string( scheduled_time );
    }

    else
    {
        schedule = "<unknown>";
    }

    if( smtp_port == 0 )
    {
        /* fallback to the default SMTP port */

        smtp_port = OSIRIS_SMTP_PORT;
    }

    /* connect to the SMTP server */

    if( ( sock = osi_connect_to_host_on_port( smtp_host, smtp_port ) ) == 0 )
    {
        log_error( LOG_HIGH, host,
                  "notification: unable to connect to (%s:%d).",
                  smtp_host, smtp_port );
        return;
    }

    /* read the SMTP servers initial greeting */

    if( read_smtp_response( sock, "220" ) )
    {
        log_error( LOG_HIGH, host,
                  "unfriendly SMTP server... failed to send failure message." );

        osi_close_socket( sock );
        return;
    }

    do
    {
        /* HELO */
        osi_snprintf(buf, OSIRIS_SMTP_CMD_BUFSZ, "HELO %s\r\n", smtp_host);
        if (send_smtp_command(sock, buf, "250"))
            break;

        /* MAIL - sender uses same addresss as receipient */
        osi_snprintf(buf, OSIRIS_SMTP_CMD_BUFSZ, "MAIL FROM: <%s>\r\n", email);
        if (send_smtp_command(sock, buf, "250"))
            break;

        /* RCPT */
        osi_snprintf(buf, OSIRIS_SMTP_CMD_BUFSZ, "RCPT TO: <%s>\r\n", email);
        if (send_smtp_command(sock, buf, "250"))
            break;

        /* DATA */
        if( send_smtp_command( sock, OSIRIS_SMTP_CMD_DATA, "354" ) )
        {
            break;
        }

        if( gethostname( this_host, MAX_HOSTNAME_LENGTH ) )
        {
            osi_snprintf(this_host, MAX_HOSTNAME_LENGTH, "UNKNOWN HOST");
        }

        osi_snprintf(buf, OSIRIS_SMTP_CMD_BUFSZ,
                     "To: %s\r\n"
                     "From: \"Osiris IDS\" <osirismd@%s>\r\n"
                     "Subject: failed to start scheduled scan [host: %s]\r\n",
                     email, this_host, host);

        if( send_smtp_command( sock, buf, NULL ) )
        {
            break;
        }

        /* send a pretty message body. */
       
        osi_snprintf( buf, sizeof( buf ), NOTIFY_SCHEDULE_FAIL_FORMAT,
                      host, schedule, message );

        osi_write( sock, buf, strlen( buf ) );
        send_smtp_command( sock, "\r\n.\r\n", NULL );

    } while( 0 );

    /* QUIT */

    send_smtp_command(sock, OSIRIS_SMTP_CMD_QUIT,  NULL);
    osi_close_socket(sock);

    return;
}

void
osi_notify_admin(OSI_DB_CTX *context)
{
    int sock = 0;
    int diff_count = context->diff_count;
    int smtp_port = config->notify_smtp_port;

    char *smtp_host = config->notify_smtp_host;
    char *email = config->notify_email;
    char *logfile  = context->filepath;

    char buf[OSIRIS_SMTP_CMD_BUFSZ];    /* KISS !malloc */
    char host[MAX_HOSTNAME_LENGTH];

    if (email == NULL) {
        log_error(LOG_HIGH, context->host,
                  "notification: receipient email address not set.");
        return;
    }

    if (logfile == NULL) {
        log_error(LOG_HIGH, context->host,
                  "notification: log file not set.");
        return;
    }

    if (config->notify_smtp_host == NULL) {
        log_error(LOG_HIGH, context->host,
                  "notification: SMTP server not set.");
        return;
    }

    log_info(LOG_LOW, context->host,
             "notifying %s using %s:%d, sending contents of log file: %s, "
             "containing %d entries.",
             email, smtp_host, smtp_port,logfile, diff_count);

    if (smtp_port == 0) {
        /* fallback to the default SMTP port */
        smtp_port = OSIRIS_SMTP_PORT;           
    }

    /* connect to the SMTP server */
    if ((sock = osi_connect_to_host_on_port(smtp_host, smtp_port)) == 0) {
        log_error(LOG_HIGH, context->host,
                  "notification: unable to connect to (%s:%d).",
                  smtp_host, smtp_port);
        return;
    }

    /* read the SMTP servers initial greeting */
    if (read_smtp_response(sock, "220")) {
        log_error(LOG_HIGH, context->host,
                  "unfriendly SMTP server... failed to send log.");
        osi_close_socket(sock);
        return;
    }

    do {

        /* HELO */
        osi_snprintf(buf, OSIRIS_SMTP_CMD_BUFSZ, "HELO %s\r\n", smtp_host);
        if (send_smtp_command(sock, buf, "250"))
            break;

        /* MAIL - sender uses same addresss as receipient */
        osi_snprintf(buf, OSIRIS_SMTP_CMD_BUFSZ, "MAIL FROM: <%s>\r\n", email);
        if (send_smtp_command(sock, buf, "250"))
            break;

        /* RCPT */
        osi_snprintf(buf, OSIRIS_SMTP_CMD_BUFSZ, "RCPT TO: <%s>\r\n", email);
        if (send_smtp_command(sock, buf, "250"))
            break;

        /* DATA */
        if (send_smtp_command(sock, OSIRIS_SMTP_CMD_DATA, "354"))
            break;

        /* BODY */

        if( config->http_host[0] && strcmp( config->http_host, "" ) != 0 )
        {
            osi_snprintf( host, MAX_HOSTNAME_LENGTH, "%s", config->http_host );
        }

        else
        {
            if (gethostname(host, MAX_HOSTNAME_LENGTH)) 
            {
                osi_snprintf(host, MAX_HOSTNAME_LENGTH, "%s", "UNKNOWN HOST");
            }
        }

        osi_snprintf(buf, OSIRIS_SMTP_CMD_BUFSZ,
                     "To: %s\r\n"
                     "From: \"Osiris IDS\" <osirismd@%s>\r\n"
                     "Subject: scan log - [%d changes][host: %s]\r\n",
                     email, host, diff_count, context->host);

        if (send_smtp_command(sock, buf, NULL))
        {
            break;
        }

        /* before sending the file, we prepend a URL that enables */
        /* the recipient to approve of these changes by updating  */
        /* the trusted database via https.                        */

        if( config->http_port > 0 )
        {
            osi_snprintf( buf, sizeof( buf ), NOTIFY_UPDATE_URL_FORMAT,
                          context->db2_name, host, config->http_port,
                          context->host, context->db2_name );

            if ((osi_write(sock, buf, strlen(buf))) == 0)
            {
                log_error( LOG_HIGH, NULL,
                           "unable to send data to smtp server." );

                break;
            } 
        }
        
        /* send the log file contents. */
 
        send_smtp_file( sock, logfile );
        send_smtp_command(sock, "\r\n.\r\n", NULL);

    } while (0);

    /* QUIT */

    send_smtp_command(sock, OSIRIS_SMTP_CMD_QUIT,  NULL);
    osi_close_socket(sock);

    return;
}

osi_bool osi_notify_send_test( OSI_ERROR *error )
{
    int sock = 0;

    int smtp_port = config->notify_smtp_port;
    char *smtp_host = config->notify_smtp_host;
    char *email = config->notify_email;

    char buf[OSIRIS_SMTP_CMD_BUFSZ];
    char this_host[MAX_HOSTNAME_LENGTH];

    if( error == NULL )
    {
        return FALSE;
    }

    memset( error, 0, sizeof( OSI_ERROR ) );

    if( email == NULL )
    {
        osi_strlcpy( error->message,
                     "notification: receipient email address not set.",
                     sizeof( error->message ) );

        log_error( LOG_HIGH, NULL, error->message );

        return FALSE;
    }

    if( smtp_host == NULL )
    {
        osi_strlcpy( error->message, "notification: SMTP server not set",
                     sizeof( error->message ) );

        log_error( LOG_HIGH, NULL, error->message );
        return FALSE;
    }

    if( smtp_port == 0 )
    {
        /* fallback to the default SMTP port */

        smtp_port = OSIRIS_SMTP_PORT;
    }

    /* connect to the SMTP server */

    if( ( sock = osi_connect_to_host_on_port( smtp_host, smtp_port ) ) == 0 )
    {
        osi_snprintf( error->message, sizeof( error->message ),
                      "notification: unable to connect to (%s:%d).",
                      smtp_host, smtp_port );

        log_error( LOG_HIGH, NULL, error->message );
        return FALSE;
    }

    /* read the SMTP servers initial greeting */

    if( read_smtp_response( sock, "220" ) )
    {
        osi_strlcpy( error->message,
                     "unfriendly SMTP server, failed to send test message.",
                     sizeof( error->message ) );

        log_error( LOG_HIGH, NULL, error->message );

        osi_close_socket( sock );
        return FALSE;
    }

    do
    {
        /* HELO */
        osi_snprintf(buf, OSIRIS_SMTP_CMD_BUFSZ, "HELO %s\r\n", smtp_host);
        if (send_smtp_command(sock, buf, "250"))
            break;

        /* MAIL - sender uses same addresss as receipient */
        osi_snprintf(buf, OSIRIS_SMTP_CMD_BUFSZ, "MAIL FROM: <%s>\r\n", email);
        if (send_smtp_command(sock, buf, "250"))
            break;

        /* RCPT */
        osi_snprintf(buf, OSIRIS_SMTP_CMD_BUFSZ, "RCPT TO: <%s>\r\n", email);
        if (send_smtp_command(sock, buf, "250"))
            break;

        /* DATA */
        if( send_smtp_command( sock, OSIRIS_SMTP_CMD_DATA, "354" ) )
        {
            break;
        }

        if( gethostname( this_host, MAX_HOSTNAME_LENGTH ) )
        {
            osi_snprintf(this_host, MAX_HOSTNAME_LENGTH, "UNKNOWN HOST");
        }

        osi_snprintf(buf, OSIRIS_SMTP_CMD_BUFSZ,
                     "To: %s\r\n"
                     "From: \"Osiris IDS\" <osirismd@%s>\r\n"
                     "Subject: [osiris test message]\r\n",
                     email, this_host );

        if( send_smtp_command( sock, buf, NULL ) )
        {
            break;
        }

        /* send a pretty message body. */

        osi_strlcpy( buf, NOTIFY_TEST_MESSAGE, sizeof( buf ) );
        osi_write( sock, buf, strlen( buf ) );
        send_smtp_command( sock, "\r\n.\r\n", NULL );

    } while( 0 );

    /* QUIT */

    send_smtp_command(sock, OSIRIS_SMTP_CMD_QUIT,  NULL);
    osi_close_socket(sock);

    return TRUE;
}
