#!/usr/bin/python
"""Send NCSA message to the nagios server. Basically a more reliable replacement for snmp traps"""
import socket,optparse,struct
import logging
import signal
from zlib import crc32

host='principle.aserv.co.za'
port=5667
password='its_a_bloody_secret'


def handler(signum,frame):
    raise IOError,"Timeout"



def send_nsca(nagiosserver,servicename,returncode=0,comment="",port=5667,reportinghostname=None,password=None):
    """Send an nsca 'trap' to nagiosesrver. reportinghostname defaults to current hostname
        is password is set then xor encryption mode is assumed.
        
        The orginal struct and defines from send_nsca.c
/* initialization packet containing IV and timestamp */
typedef struct init_packet_struct{
        char      iv[TRANSMITTED_IV_SIZE];
        u_int32_t timestamp;
        }init_packet;

#define TRANSMITTED_IV_SIZE     128     /* size of IV to transmit - must be as big as largest IV needed for any crypto algorithm */
#define ENCRYPT_NONE            0       /* no encryption */
#define ENCRYPT_XOR             1       /* not really encrypted, just obfuscated */

/*************** PACKET STRUCTURE DEFINITIONS **********/
#define NSCA_PACKET_VERSION_3   3               /* packet version identifier */
#define NSCA_PACKET_VERSION_2   2               /* older packet version identifiers */
#define NSCA_PACKET_VERSION_1   1

#define MAX_HOST_ADDRESS_LENGTH     256     /* max size of a host address */
#define MAX_HOSTNAME_LENGTH 64
#define MAX_DESCRIPTION_LENGTH      128
#define MAX_PLUGINOUTPUT_LENGTH     512
#define MAX_PASSWORD_LENGTH     512

/* data packet containing service check results */
typedef struct data_packet_struct{
        int16_t   packet_version;
        u_int32_t crc32_value;
        u_int32_t timestamp;
        int16_t   return_code;
        char      host_name[MAX_HOSTNAME_LENGTH];
        char      svc_description[MAX_DESCRIPTION_LENGTH];
        char      plugin_output[MAX_PLUGINOUTPUT_LENGTH];
        }data_packet;
        
        2+4+4+2+64+128+512=
data_packet_struct="hIIh64s128s512"
    """
    if not reportinghostname:
        reportinghostname=socket.gethostname()
    if reportinghostname.find('template')>=0:
        log.info("Host name says 'template' - not reporting");
        return
    init_packet_struct="128sI" #Based on the init_packet struct from the official c header files
    packetsize=struct.calcsize(init_packet_struct)
    try:
        signal.signal(signal.SIGALRM,handler)
        signal.alarm(10) #Start deadman switch

        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.connect((nagiosserver, port))
        datain = sock.recv(packetsize) #Connect to nagios server and download the header.
        initialdata=struct.unpack(init_packet_struct,datain)
        iv,timestamp=initialdata #IV is the initial vector, used for encryption if we use XOR mode.
        crc=0 #We set crc to 0, generate the packet, calc crc for the packet and then save the crc back into the packet. Used to see if password is correct I think.
        data_packet_struct="hIIh64s128s512scc" #From the original C header file. Yay brain hemorage time.
        #The two null bytes on the end seem to be needed, I have no idea why because they don't seem to exist in the original header.
        null=chr(0) #Null values
        out=struct.pack(data_packet_struct,socket.htons(3),socket.htonl(crc),timestamp,socket.htons(returncode),reportinghostname,servicename,comment,null,null)
        crc=crc32(out) & 0xFFFFFFFF  #Recalc the crc code.
        out=struct.pack(data_packet_struct,socket.htons(3),socket.htonl(crc),timestamp,socket.htons(returncode),reportinghostname,servicename,comment,null,null)
        if password: #If there is a password then assume we are in xor mode
            newout=[]
            for i in range(len(out)):
                p=ord(out[i]) ^ ord(iv[i % len(iv)])
                newout.append(chr(p ^ ord(password[i % len(password)])))
            out="".join(newout)
        sock.send(out)
        sock.close()
        log.debug("Sent %s bytes to %s, Service: %s Code:'%s'" ,len(out),nagiosserver,servicename,returncode)
        signal.alarm(0) #Cancel deadman switch
    except IOError:
        log.error("send_nsca timeout error")


if __name__=="__main__":
    usage="Usage: %prog [<options>] [Message] " +"\n"+__doc__+"\n"
    parser=optparse.OptionParser(usage)
    parser.add_option("--host", dest="host", type="string", default=host, help="Nagios server to connect to (%s)" % (host))
    parser.add_option("--password","-p", dest="password", type="string", default=password, help="Nagios password server to connect to")
    parser.add_option("--port", dest="port", type="int", default=5667, help="Nagios port (5667)")
    parser.add_option("--servicename","-s", dest="servicename", type="string", default="IntegrityError", help="Nagios service to report (IntegrityError) ")
    parser.add_option("--result","-r", dest="result", type="int", default=0, help="Nagios result, 0 means all is good")
    parser.add_option("-v","--verbose",dest="verbose", action="store_true", default=False, help="Be verbose (ignored)")
    parser.add_option("--hostname", dest="hostname", type="string", default='', help="Our hostname")
    (options,args) = parser.parse_args()
   
    if args: message=" ".join(args)
    else: message="No message"
    
    logging.basicConfig()
    log=logging.getLogger()
    send_nsca(options.host,
        options.servicename,
        options.result,
        message,
        port=options.port,
        password=options.password,
        reportinghostname=options.hostname
        )

