Ejemplo n.º 1
0
static void update_kernel(void *kernel, client_t *c)
{
	pdu_t *q, *a;
	char *s, *p = NULL;

	/* first, extract the message */
	s = c->packet.output;
	while (*s && *s != '|') s++;
	if (*s) {
		p = s + 1; *s-- = '\0';
		while (*p && isspace(*p)) p++;
		while (s > c->packet.output && isspace(*s))
			*s-- = '\0';
	}

	q = pdu_make("PUT.STATE", 0);
	pdu_extendf(q, "%u", ntohl(c->packet.timestamp));
	pdu_extendf(q, "%s:%s", c->packet.host, c->packet.service);
	pdu_extendf(q, "%u", ntohs(c->packet.status) & 0xff);
	pdu_extendf(q, "%s", c->packet.output);
	pdu_send_and_free(q, kernel);

	a = pdu_recv(kernel);
	if (strcmp(pdu_type(a), "OK") != 0) {
		logger(LOG_ERR, "NSCA gateway received an ERROR (in response to an PUT.STATE) from the kernel: %s",
		s = pdu_string(a, 1)); free(s);
		pdu_free(a);
		return;
	}
	pdu_free(a);

	while (p && *p) {
		char *k, *v;
		k = p; while (*p && !isspace(*p) && *p != '=') p++; *p++ = '\0';
		v = p; while (*p && !isspace(*p) && *p != ';') p++; *p++ = '\0';

		q = pdu_make("PUT.SAMPLE", 0);
		pdu_extendf(q, "%u", ntohl(c->packet.timestamp));
		pdu_extendf(q, "%s:%s:%s", c->packet.host, c->packet.service, k);
		pdu_extendf(q, "%s", v);
		pdu_send_and_free(q, kernel);

		a = pdu_recv(kernel);
		if (strcmp(pdu_type(a), "OK") != 0) {
			logger(LOG_ERR, "NSCA gateway received an ERROR (in response to an PUT.SAMPLE) from the kernel: %s",
				s = pdu_string(a, 1)); free(s);
			pdu_free(a);
			return;
		}
		pdu_free(a);

		while (*p && !isspace(*p)) p++;
		while (*p &&  isspace(*p)) p++;
	}
}
Ejemplo n.º 2
0
static char retransmission_indicator(unsigned char octet)
{
    switch (pdu_type(octet)) {
        case INVOKE:
        case RESULT:
        case ACK:
        case SEGMENTED_INVOKE:
        case SEGMENTED_RESULT:
        case NEGATIVE_ACK:
            return octet & 0x01;    /* .......X */
        default:
            return 0;
    }
}
Ejemplo n.º 3
0
/* Code to actually dissect the packets */
static void
dissect_wtp_common(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
{
    char          *szInfo;
    int            offCur        = 0;   /* current offset from start of WTP data */
    gint           returned_length, str_index = 0;

    unsigned char  b0;

    /* continuation flag */
    unsigned char  fCon;            /* Continue flag    */
    unsigned char  fRID;            /* Re-transmission indicator*/
    unsigned char  fTTR = '\0';        /* Transmission trailer    */
    guint          cbHeader       = 0;    /* Fixed header length    */
    guint          vHeader       = 0;    /* Variable header length*/
    int            abortType      = 0;

    /* Set up structures we'll need to add the protocol subtree and manage it */
    proto_item    *ti = NULL;
    proto_tree    *wtp_tree = NULL;

    char           pdut;
    char           clsTransaction = 3;
    int            numMissing = 0;        /* Number of missing packets in a negative ack */
    int            i;
    tvbuff_t      *wsp_tvb = NULL;
    guint8         psn = 0;        /* Packet sequence number*/
    guint16        TID = 0;        /* Transaction-Id    */
    int            dataOffset;
    gint           dataLen;

#define SZINFO_SIZE 256
    szInfo=(char *)wmem_alloc(wmem_packet_scope(), SZINFO_SIZE);

    b0 = tvb_get_guint8 (tvb, offCur + 0);
    /* Discover Concatenated PDUs */
    if (b0 == 0) {
        guint    c_fieldlen = 0;        /* Length of length-field    */
        guint    c_pdulen = 0;        /* Length of conc. PDU    */

        if (tree) {
            ti = proto_tree_add_item(tree, proto_wtp,
                    tvb, offCur, 1, ENC_NA);
            wtp_tree = proto_item_add_subtree(ti, ett_wtp_sub_pdu_tree);
            proto_item_append_text(ti, ", PDU concatenation");
        }
        offCur = 1;
        i = 1;
        while (offCur < (int) tvb_reported_length(tvb)) {
            tvbuff_t *wtp_tvb;
            /* The length of an embedded WTP PDU is coded as either:
             *    - a 7-bit value contained in one octet with highest bit == 0.
             *    - a 15-bit value contained in two octets (little endian)
             *      if the 1st octet has its highest bit == 1.
             * This means that this is NOT encoded as an uintvar-integer!!!
             */
            b0 = tvb_get_guint8(tvb, offCur + 0);
            if (b0 & 0x80) {
                c_fieldlen = 2;
                c_pdulen = ((b0 & 0x7f) << 8) | tvb_get_guint8(tvb, offCur + 1);
            } else {
                c_fieldlen = 1;
                c_pdulen = b0;
            }
            if (tree) {
                proto_tree_add_uint(wtp_tree, hf_wtp_header_sub_pdu_size,
                        tvb, offCur, c_fieldlen, c_pdulen);
            }
            if (i > 1) {
                col_append_str(pinfo->cinfo, COL_INFO, ", ");
            }
            /* Skip the length field for the WTP sub-tvb */
            wtp_tvb = tvb_new_subset_length(tvb, offCur + c_fieldlen, c_pdulen);
            dissect_wtp_common(wtp_tvb, pinfo, wtp_tree);
            offCur += c_fieldlen + c_pdulen;
            i++;
        }
        if (tree) {
            proto_item_append_text(ti, ", PDU count: %u", i);
        }
        return;
    }
    /* No concatenation */
    fCon = b0 & 0x80;
    fRID = retransmission_indicator(b0);
    pdut = pdu_type(b0);

#ifdef DEBUG
    printf("WTP packet %u: tree = %p, pdu = %s (%u) length: %u\n",
            pinfo->fd->num, tree,
            val_to_str(pdut, vals_wtp_pdu_type, "Unknown PDU type 0x%x"),
            pdut, tvb_captured_length(tvb));
#endif

    /* Develop the string to put in the Info column */
    returned_length =  g_snprintf(szInfo, SZINFO_SIZE, "WTP %s",
            val_to_str(pdut, vals_wtp_pdu_type, "Unknown PDU type 0x%x"));
    str_index += MIN(returned_length, SZINFO_SIZE-str_index);

    switch (pdut) {
        case INVOKE:
            fTTR = transmission_trailer(b0);
            TID = tvb_get_ntohs(tvb, offCur + 1);
            psn = 0;
            clsTransaction = transaction_class(tvb_get_guint8(tvb, offCur + 3));
            returned_length = g_snprintf(&szInfo[str_index], SZINFO_SIZE-str_index,
                    " Class %d", clsTransaction);
            str_index += MIN(returned_length, SZINFO_SIZE-str_index);
            cbHeader = 4;
            break;

        case SEGMENTED_INVOKE:
        case SEGMENTED_RESULT:
            fTTR = transmission_trailer(b0);
            TID = tvb_get_ntohs(tvb, offCur + 1);
            psn = tvb_get_guint8(tvb, offCur + 3);
            if (psn != 0) {
                returned_length = g_snprintf(&szInfo[str_index], SZINFO_SIZE-str_index,
                        " (%u)", psn);
                str_index += MIN(returned_length, SZINFO_SIZE-str_index);
            }
            cbHeader = 4;
            break;

        case ABORT:
            cbHeader = 4;
            break;

        case RESULT:
            fTTR = transmission_trailer(b0);
            TID = tvb_get_ntohs(tvb, offCur + 1);
            psn = 0;
            cbHeader = 3;
            break;

        case ACK:
            cbHeader = 3;
            break;

        case NEGATIVE_ACK:
            /* Variable number of missing packets */
            numMissing = tvb_get_guint8(tvb, offCur + 3);
            cbHeader = numMissing + 4;
            break;

        default:
            break;
    };
    if (fRID) {
        /*returned_length =*/ g_snprintf(&szInfo[str_index], SZINFO_SIZE-str_index, " R" );
        /*str_index += MIN(returned_length, SZINFO_SIZE-str_index);*/
    };
    /* In the interest of speed, if "tree" is NULL, don't do any work not
       necessary to generate protocol tree items. */
    if (tree) {
#ifdef DEBUG
        fprintf(stderr, "dissect_wtp: cbHeader = %d\n", cbHeader);
#endif
        /* NOTE - Length will be set when we process the TPI */
        ti = proto_tree_add_item(tree, proto_wtp, tvb, offCur, 0, ENC_NA);
#ifdef DEBUG
        fprintf(stderr, "dissect_wtp: (7) Returned from proto_tree_add_item\n");
#endif
        wtp_tree = proto_item_add_subtree(ti, ett_wtp);

        /* Code to process the packet goes here */
#ifdef DEBUG
        fprintf(stderr, "dissect_wtp: cbHeader = %d\n", cbHeader);
        fprintf(stderr, "dissect_wtp: offCur = %d\n", offCur);
#endif
        /* Add common items: only CON and PDU Type */
        proto_tree_add_item(
                wtp_tree,             /* tree */
                hf_wtp_header_flag_continue,     /* id */
                tvb,
                offCur,             /* start of highlight */
                1,                /* length of highlight*/
                b0                /* value */
                );
        proto_tree_add_item(wtp_tree, hf_wtp_header_pdu_type, tvb, offCur, 1, ENC_LITTLE_ENDIAN);

        switch(pdut) {
            case INVOKE:
                proto_tree_add_item(wtp_tree, hf_wtp_header_flag_Trailer, tvb, offCur, 1, ENC_LITTLE_ENDIAN);
                proto_tree_add_item(wtp_tree, hf_wtp_header_flag_RID, tvb, offCur, 1, ENC_LITTLE_ENDIAN);
                proto_tree_add_item(wtp_tree, hf_wtp_header_flag_TID_response, tvb, offCur + 1, 2, ENC_BIG_ENDIAN);
                proto_tree_add_item(wtp_tree, hf_wtp_header_flag_TID, tvb, offCur + 1, 2, ENC_BIG_ENDIAN);

                proto_tree_add_item(wtp_tree, hf_wtp_header_Inv_version , tvb, offCur + 3, 1, ENC_LITTLE_ENDIAN);
                proto_tree_add_item(wtp_tree, hf_wtp_header_Inv_flag_TIDNew, tvb, offCur + 3, 1, ENC_LITTLE_ENDIAN);
                proto_tree_add_item(wtp_tree, hf_wtp_header_Inv_flag_UP, tvb, offCur + 3, 1, ENC_LITTLE_ENDIAN);
                proto_tree_add_item(wtp_tree, hf_wtp_header_Inv_Reserved, tvb, offCur + 3, 1, ENC_LITTLE_ENDIAN);
                proto_tree_add_item(wtp_tree, hf_wtp_header_Inv_TransactionClass, tvb, offCur + 3, 1, ENC_LITTLE_ENDIAN);
                proto_item_append_text(ti,
                        ", PDU: Invoke (%u)"
                        ", Transaction Class: %s (%u)",
                        INVOKE,
                        val_to_str_const(clsTransaction, vals_transaction_classes, "Undefined"),
                        clsTransaction);
                break;

            case RESULT:
                proto_tree_add_item(wtp_tree, hf_wtp_header_flag_Trailer, tvb, offCur, 1, ENC_LITTLE_ENDIAN);
                proto_tree_add_item(wtp_tree, hf_wtp_header_flag_RID, tvb, offCur, 1, ENC_LITTLE_ENDIAN);
                proto_tree_add_item(wtp_tree, hf_wtp_header_flag_TID_response, tvb, offCur + 1, 2, ENC_BIG_ENDIAN);
                proto_tree_add_item(wtp_tree, hf_wtp_header_flag_TID, tvb, offCur + 1, 2, ENC_BIG_ENDIAN);
                proto_item_append_text(ti, ", PDU: Result (%u)", RESULT);
                break;

            case ACK:
                proto_tree_add_item(wtp_tree, hf_wtp_header_Ack_flag_TVETOK, tvb, offCur, 1, ENC_BIG_ENDIAN);

                proto_tree_add_item(wtp_tree, hf_wtp_header_flag_RID, tvb, offCur, 1, ENC_LITTLE_ENDIAN);
                proto_tree_add_item(wtp_tree, hf_wtp_header_flag_TID_response, tvb, offCur + 1, 2, ENC_BIG_ENDIAN);
                proto_tree_add_item(wtp_tree, hf_wtp_header_flag_TID, tvb, offCur + 1, 2, ENC_BIG_ENDIAN);
                proto_item_append_text(ti, ", PDU: ACK (%u)", ACK);
                break;

            case ABORT:
                abortType = tvb_get_guint8 (tvb, offCur) & 0x07;
                proto_tree_add_item(wtp_tree, hf_wtp_header_Abort_type , tvb, offCur , 1, ENC_LITTLE_ENDIAN);
                proto_tree_add_item(wtp_tree, hf_wtp_header_flag_TID_response, tvb, offCur + 1, 2, ENC_BIG_ENDIAN);
                proto_tree_add_item(wtp_tree, hf_wtp_header_flag_TID, tvb, offCur + 1, 2, ENC_BIG_ENDIAN);

                if (abortType == PROVIDER) {
                    guint8 reason = tvb_get_guint8(tvb, offCur + 3);
                    proto_tree_add_item( wtp_tree, hf_wtp_header_Abort_reason_provider , tvb, offCur + 3 , 1, ENC_LITTLE_ENDIAN);
                    proto_item_append_text(ti,
                            ", PDU: Abort (%u)"
                            ", Type: Provider (%u)"
                            ", Reason: %s (%u)",
                            ABORT,
                            PROVIDER,
                            val_to_str_const(reason, vals_abort_reason_provider, "Undefined"),
                            reason);
                }
                else if (abortType == USER) {
                    guint8 reason = tvb_get_guint8(tvb, offCur + 3);
                    proto_tree_add_item(wtp_tree, hf_wtp_header_Abort_reason_user , tvb, offCur + 3 , 1, ENC_LITTLE_ENDIAN);
                    proto_item_append_text(ti,
                            ", PDU: Abort (%u)"
                            ", Type: User (%u)"
                            ", Reason: %s (%u)",
                            ABORT,
                            PROVIDER,
                            val_to_str_ext_const(reason, &vals_wsp_reason_codes_ext, "Undefined"),
                            reason);
                }
                break;

            case SEGMENTED_INVOKE:
                proto_tree_add_item(wtp_tree, hf_wtp_header_flag_Trailer, tvb, offCur, 1, ENC_LITTLE_ENDIAN);
                proto_tree_add_item(wtp_tree, hf_wtp_header_flag_RID, tvb, offCur, 1, ENC_LITTLE_ENDIAN);
                proto_tree_add_item(wtp_tree, hf_wtp_header_flag_TID_response, tvb, offCur + 1, 2, ENC_BIG_ENDIAN);
                proto_tree_add_item(wtp_tree, hf_wtp_header_flag_TID, tvb, offCur + 1, 2, ENC_BIG_ENDIAN);

                proto_tree_add_item(wtp_tree, hf_wtp_header_sequence_number , tvb, offCur + 3, 1, ENC_LITTLE_ENDIAN);
                proto_item_append_text(ti,
                        ", PDU: Segmented Invoke (%u)"
                        ", Packet Sequence Number: %u",
                        SEGMENTED_INVOKE, psn);
                break;

            case SEGMENTED_RESULT:
                proto_tree_add_item(wtp_tree, hf_wtp_header_flag_Trailer, tvb, offCur, 1, ENC_LITTLE_ENDIAN);
                proto_tree_add_item(wtp_tree, hf_wtp_header_flag_RID, tvb, offCur, 1, ENC_LITTLE_ENDIAN);
                proto_tree_add_item(wtp_tree, hf_wtp_header_flag_TID_response, tvb, offCur + 1, 2, ENC_BIG_ENDIAN);
                proto_tree_add_item(wtp_tree, hf_wtp_header_flag_TID, tvb, offCur + 1, 2, ENC_BIG_ENDIAN);

                proto_tree_add_item(wtp_tree, hf_wtp_header_sequence_number , tvb, offCur + 3, 1, ENC_LITTLE_ENDIAN);
                proto_item_append_text(ti,
                        ", PDU: Segmented Result (%u)"
                        ", Packet Sequence Number: %u",
                        SEGMENTED_RESULT, psn);
                break;

            case NEGATIVE_ACK:
                proto_tree_add_item(wtp_tree, hf_wtp_header_flag_RID, tvb, offCur, 1, ENC_LITTLE_ENDIAN);
                proto_tree_add_item(wtp_tree, hf_wtp_header_flag_TID_response, tvb, offCur + 1, 2, ENC_BIG_ENDIAN);
                proto_tree_add_item(wtp_tree, hf_wtp_header_flag_TID, tvb, offCur + 1, 2, ENC_BIG_ENDIAN);

                proto_tree_add_item(wtp_tree, hf_wtp_header_missing_packets , tvb, offCur + 3, 1, ENC_LITTLE_ENDIAN);
                /* Iterate through missing packets */
                for (i = 0; i < numMissing; i++)
                {
                    proto_tree_add_item(wtp_tree, hf_wtp_header_sequence_number, tvb, offCur + 4 + i, 1, ENC_LITTLE_ENDIAN);
                }
                proto_item_append_text(ti,
                        ", PDU: Negative Ack (%u)"
                        ", Missing Packets: %u",
                        NEGATIVE_ACK, numMissing);
                break;

            default:
                break;
        };
        if (fRID) {
            proto_item_append_text(ti, ", Retransmission");
        }
    } else { /* tree is NULL */
#ifdef DEBUG
        fprintf(stderr, "dissect_wtp: (4) tree was %p\n", tree);
#endif
    }
    /* Process the variable part */
    if (fCon) {            /* Now, analyze variable part    */
        guint8    tCon;
        guint8    tByte;
        guint     tpiLen;
        tvbuff_t *tmp_tvb;

        vHeader = 0;        /* Start scan all over    */

        do {
            tByte = tvb_get_guint8(tvb, offCur + cbHeader + vHeader);
            tCon = tByte & 0x80;
            if (tByte & 0x04)    /* Long TPI    */
                tpiLen = 2 + tvb_get_guint8(tvb, offCur + cbHeader + vHeader + 1);
            else
                tpiLen = 1 + (tByte & 0x03);
            if (tree)
            {
                tmp_tvb = tvb_new_subset_length(tvb, offCur + cbHeader + vHeader, tpiLen);
                wtp_handle_tpi(wtp_tree, tmp_tvb);
            }
            vHeader += tpiLen;
        } while (tCon);
    } else {
        /* There is no variable part */
    }    /* End of variable part of header */

    /* Set the length of the WTP protocol part now we know the length of the
     * fixed and variable WTP headers */
    if (tree)
        proto_item_set_len(ti, cbHeader + vHeader);

#ifdef DEBUG
    fprintf( stderr, "dissect_wtp: cbHeader = %d\n", cbHeader );
#endif

    /*
     * Any remaining data ought to be WSP data (if not WTP ACK, NACK
     * or ABORT pdu), so, if we have any remaining data, and it's
     * not an ACK, NACK, or ABORT PDU, hand it off (defragmented) to the
     * WSP dissector.
     * Note that the last packet of a fragmented WTP message needn't
     * contain any data, so we allow payloadless packets to be
     * reassembled.  (XXX - does the reassembly code handle this
     * for packets other than the last packet?)
     *
     * Try calling a subdissector only if:
     *    - The WTP payload is ressembled in this very packet,
     *    - The WTP payload is not fragmented across packets.
     */
    dataOffset = offCur + cbHeader + vHeader;
    dataLen = tvb_reported_length_remaining(tvb, dataOffset);
    if ((dataLen >= 0) &&
            ! ((pdut==ACK) || (pdut==NEGATIVE_ACK) || (pdut==ABORT)))
    {
        /* Try to reassemble if needed, and hand over to WSP
         * A fragmented WTP packet is either:
         *    - An INVOKE with fTTR (transmission trailer) not set,
         *    - a SEGMENTED_INVOKE,
         *    - A RESULT with fTTR (transmission trailer) not set,
         *    - a SEGMENTED_RESULT.
         */
        if ( ( (pdut == SEGMENTED_INVOKE) || (pdut == SEGMENTED_RESULT)
                    || ( ((pdut == INVOKE) || (pdut == RESULT)) && (!fTTR) )
             ) && tvb_bytes_exist(tvb, dataOffset, dataLen) )
        {
            /* Try reassembling fragments */
            fragment_head *fd_wtp = NULL;
            guint32 reassembled_in = 0;
            gboolean save_fragmented = pinfo->fragmented;

            pinfo->fragmented = TRUE;
            fd_wtp = fragment_add_seq(&wtp_reassembly_table, tvb, dataOffset,
                    pinfo, TID, NULL, psn, dataLen, !fTTR, 0);
            /* XXX - fragment_add_seq() yields NULL unless Wireshark knows
             * that the packet is part of a reassembled whole. This means
             * that fd_wtp will be NULL as long as Wireshark did not encounter
             * (and process) the packet containing the last fragment.
             * This implies that Wireshark needs two passes over the data for
             * correct reassembly. At the first pass, a capture containing
             * three fragments plus a retransmssion of the last fragment
             * will progressively show:
             *
             *        Packet 1: (Unreassembled fragment 1)
             *        Packet 2: (Unreassembled fragment 2)
             *        Packet 3: (Reassembled WTP)
             *        Packet 4: (WTP payload reassembled in packet 3)
             *
             * However at subsequent evaluation (e.g., by applying a display
             * filter) the packet summary will show:
             *
             *        Packet 1: (WTP payload reassembled in packet 3)
             *        Packet 2: (WTP payload reassembled in packet 3)
             *        Packet 3: (Reassembled WTP)
             *        Packet 4: (WTP payload reassembled in packet 3)
             *
             * This is important to know, and also affects read filters!
             */
            wsp_tvb = process_reassembled_data(tvb, dataOffset, pinfo,
                    "Reassembled WTP", fd_wtp, &wtp_frag_items,
                    NULL, wtp_tree);
#ifdef DEBUG
            printf("WTP: Packet %u %s -> %d: wsp_tvb = %p, fd_wtp = %p, frame = %u\n",
                    pinfo->fd->num,
                    fd_wtp ? "Reassembled" : "Not reassembled",
                    fd_wtp ? fd_wtp->reassembled_in : -1,
                    wsp_tvb,
                    fd_wtp
                  );
#endif
            if (fd_wtp) {
                /* Reassembled */
                reassembled_in = fd_wtp->reassembled_in;
                if (pinfo->fd->num == reassembled_in) {
                    /* Reassembled in this very packet:
                     * We can safely hand the tvb to the WSP dissector */
                    call_dissector(wsp_handle, wsp_tvb, pinfo, tree);
                } else {
                    /* Not reassembled in this packet */
                    col_append_fstr(pinfo->cinfo, COL_INFO,
                            "%s (WTP payload reassembled in packet %u)",
                            szInfo, fd_wtp->reassembled_in);

                    proto_tree_add_item(wtp_tree, hf_wtp_payload, tvb, dataOffset, -1, ENC_NA);
                }
            } else {
                /* Not reassembled yet, or not reassembled at all */
                col_append_fstr(pinfo->cinfo, COL_INFO,
                        "%s (Unreassembled fragment %u)",
                        szInfo, psn);
                proto_tree_add_item(wtp_tree, hf_wtp_payload, tvb, dataOffset, -1, ENC_NA);
            }
            /* Now reset fragmentation information in pinfo */
            pinfo->fragmented = save_fragmented;
        }
        else if ( ((pdut == INVOKE) || (pdut == RESULT)) && (fTTR) )
        {
            /* Non-fragmented payload */
            wsp_tvb = tvb_new_subset_remaining(tvb, dataOffset);
            /* We can safely hand the tvb to the WSP dissector */
            call_dissector(wsp_handle, wsp_tvb, pinfo, tree);
        }
        else
        {
            /* Nothing to hand to subdissector */
            col_append_str(pinfo->cinfo, COL_INFO, szInfo);
        }
    }
    else
    {
        /* Nothing to hand to subdissector */
        col_append_str(pinfo->cinfo, COL_INFO, szInfo);
    }
}
Ejemplo n.º 4
0
int main(int argc, char **argv)
{
	OPTIONS.verbose   = 0;
	OPTIONS.endpoint  = strdup("tcp://127.0.0.1:2997");
	OPTIONS.daemonize = 1;
	OPTIONS.pidfile   = strdup("/var/run/" ME ".pid");
	OPTIONS.user      = strdup("root");
	OPTIONS.group     = strdup("root");
	OPTIONS.match     = strdup(".");
	OPTIONS.avatar    = strdup(":robot_face:");
	OPTIONS.username  = strdup("bolo");

	struct option long_opts[] = {
		{ "help",             no_argument, NULL, 'h' },
		{ "version",          no_argument, NULL, 'V' },
		{ "verbose",          no_argument, NULL, 'v' },
		{ "endpoint",   required_argument, NULL, 'e' },
		{ "foreground",       no_argument, NULL, 'F' },
		{ "pidfile",    required_argument, NULL, 'p' },
		{ "user",       required_argument, NULL, 'u' },
		{ "group",      required_argument, NULL, 'g' },
		{ "match",      required_argument, NULL, 'm' },
		{ "webhook",    required_argument, NULL, 'U' },
		{ "channel",    required_argument, NULL, 'C' },
		{ "botname",    required_argument, NULL, 'N' },
		{ "avatar",     required_argument, NULL, 'A' },
		{ 0, 0, 0, 0 },
	};
	for (;;) {
		int idx = 1;
		int c = getopt_long(argc, argv, "h?Vv+e:Fp:u:g:m:U:C:N:A:", long_opts, &idx);
		if (c == -1) break;

		switch (c) {
		case 'h':
		case '?':
			printf(ME " v%s\n", BOLO_VERSION);
			printf("Usage: " ME " [-h?FVv] [-e tcp://host:port]\n"
			       "                  [-l level]\n"
			       "                  [-u user] [-g group] [-p /path/to/pidfile]\n\n");
			printf("Options:\n");
			printf("  -?, -h               show this help screen\n");
			printf("  -F, --foreground     don't daemonize, stay in the foreground\n");
			printf("  -V, --version        show version information and exit\n");
			printf("  -v, --verbose        turn on debugging, to standard error\n");
			printf("  -e, --endpoint       bolo broadcast endpoint to connect to\n");
			printf("  -u, --user           user to run as (if daemonized)\n");
			printf("  -g, --group          group to run as (if daemonized)\n");
			printf("  -p, --pidfile        where to store the pidfile (if daemonized)\n");
			printf("  -U, --webhook        Slack webhook URL for integration\n");
			printf("  -C, --channel        channel (#channel or @user) to notify\n");
			printf("  -N, --botname        name to use for the notification robot\n");
			printf("  -A, --avatar         avatar image to use (either :emoji: or a URL)\n");
			exit(0);

		case 'V':
			logger(LOG_DEBUG, "handling -V/--version");
			printf(ME " v%s\n"
			       "Copyright (c) 2016 The Bolo Authors.  All Rights Reserved.\n",
			       BOLO_VERSION);
			exit(0);

		case 'v':
			OPTIONS.verbose++;
			break;

		case 'e':
			free(OPTIONS.endpoint);
			OPTIONS.endpoint = strdup(optarg);
			break;

		case 'F':
			OPTIONS.daemonize = 0;
			break;

		case 'p':
			free(OPTIONS.pidfile);
			OPTIONS.pidfile = strdup(optarg);
			break;

		case 'u':
			free(OPTIONS.user);
			OPTIONS.user = strdup(optarg);
			break;

		case 'g':
			free(OPTIONS.group);
			OPTIONS.group = strdup(optarg);
			break;

		case 'm':
			free(OPTIONS.match);
			OPTIONS.match = strdup(optarg);
			break;

		case 'U':
			free(OPTIONS.webhook);
			OPTIONS.webhook = strdup(optarg);
			break;

		case 'C':
			free(OPTIONS.channel);
			OPTIONS.channel = strdup(optarg);
			break;

		case 'N':
			free(OPTIONS.username);
			OPTIONS.username = strdup(optarg);
			break;

		case 'A':
			free(OPTIONS.avatar);
			OPTIONS.avatar = strdup(optarg);
			break;

		default:
			fprintf(stderr, "unhandled option flag %#02x\n", c);
			return 1;
		}
	}

	if (!OPTIONS.channel) {
		fprintf(stderr, "Missing required --channel flag.\n");
		return 1;
	}
	if (!OPTIONS.webhook) {
		fprintf(stderr, "Missing required --webhook flag.\n");
		return 1;
	}

	if (OPTIONS.daemonize) {
		log_open(ME, "daemon");
		log_level(LOG_ERR + OPTIONS.verbose, NULL);

		mode_t um = umask(0);
		if (daemonize(OPTIONS.pidfile, OPTIONS.user, OPTIONS.group) != 0) {
			fprintf(stderr, "daemonization failed: (%i) %s\n", errno, strerror(errno));
			return 3;
		}
		umask(um);
	} else {
		log_open(ME, "console");
		log_level(LOG_INFO + OPTIONS.verbose, NULL);
	}
	logger(LOG_NOTICE, "starting up");

	const char *re_err;
	int re_off;
	OPTIONS.re = pcre_compile(OPTIONS.match, 0, &re_err, &re_off, NULL);
	if (!OPTIONS.re) {
		fprintf(stderr, "Bad --match pattern (%s): %s\n", OPTIONS.match, re_err);
		exit(1);
	}
	OPTIONS.re_extra = pcre_study(OPTIONS.re, 0, &re_err);

	logger(LOG_DEBUG, "initializing curl subsystem");
	OPTIONS.curl = curl_easy_init();
	if (!OPTIONS.curl) {
		logger(LOG_ERR, "failed to initialize curl subsystem");
		return 3;
	}

	logger(LOG_DEBUG, "allocating 0MQ context");
	void *zmq = zmq_ctx_new();
	if (!zmq) {
		logger(LOG_ERR, "failed to initialize 0MQ context");
		return 3;
	}
	logger(LOG_DEBUG, "allocating 0MQ SUB socket to talk to %s", OPTIONS.endpoint);
	void *z = zmq_socket(zmq, ZMQ_SUB);
	if (!z) {
		logger(LOG_ERR, "failed to create a SUB socket");
		return 3;
	}
	logger(LOG_DEBUG, "setting subscriber filter");
	if (zmq_setsockopt(z, ZMQ_SUBSCRIBE, "", 0) != 0) {
		logger(LOG_ERR, "failed to set subscriber filter");
		return 3;
	}
	logger(LOG_DEBUG, "connecting to %s", OPTIONS.endpoint);
	if (vzmq_connect(z, OPTIONS.endpoint) != 0) {
		logger(LOG_ERR, "failed to connect to %s", OPTIONS.endpoint);
		return 3;
	}

	pdu_t *p;
	logger(LOG_INFO, "waiting for a PDU from %s", OPTIONS.endpoint);

	signal_handlers();
	while (!signalled()) {
		while ((p = pdu_recv(z))) {
			logger(LOG_INFO, "received a [%s] PDU of %i frames", pdu_type(p), pdu_size(p));

			if (strcmp(pdu_type(p), "TRANSITION") == 0 && pdu_size(p) == 6) {
				s_notify(p);
			}

			pdu_free(p);

			logger(LOG_INFO, "waiting for a PDU from %s", OPTIONS.endpoint);
		}
	}

	logger(LOG_INFO, "shutting down");
	vzmq_shutdown(z, 0);
	zmq_ctx_destroy(zmq);
	return 0;
}
Ejemplo n.º 5
0
int main(int argc, char **argv)
{
	OPTIONS.verbose = 0;
	OPTIONS.endpoint = strdup("tcp://127.0.0.1:2999");

	struct option long_opts[] = {
		{ "help",           no_argument, 0, 'h' },
		{ "verbose",        no_argument, 0, 'v' },
		{ "endpoint", required_argument, 0, 'e' },
		{ 0, 0, 0, 0 },
	};
	for (;;) {
		int idx = 1;
		int c = getopt_long(argc, argv, "h?v+e:", long_opts, &idx);
		if (c == -1) break;

		switch (c) {
		case 'h':
		case '?':
			break;

		case 'v':
			OPTIONS.verbose++;
			break;

		case 'e':
			free(OPTIONS.endpoint);
			OPTIONS.endpoint = strdup(optarg);
			break;

		default:
			fprintf(stderr, "unhandled option flag %#02x\n", c);
			return 1;
		}
	}

	if (DEBUG) fprintf(stderr, "+>> allocating 0MQ context\n");
	void *zmq = zmq_ctx_new();
	if (!zmq) {
		fprintf(stderr, "failed to initialize 0MQ context; aborting (results NOT submitted)\n");
		return 3;
	}
	if (DEBUG) fprintf(stderr, "+>> allocating 0MQ DEALER socket to talk to %s\n", OPTIONS.endpoint);
	void *z = zmq_socket(zmq, ZMQ_DEALER);
	if (!z) {
		fprintf(stderr, "failed to create a DEALER socket; aborting (results NOT submitted)\n");
		return 3;
	}
	if (DEBUG) fprintf(stderr, "+>> connecting to %s\n", OPTIONS.endpoint);
	if (vzmq_connect(z, OPTIONS.endpoint) != 0) {
		fprintf(stderr, "failed to connect to %s; aborting (results NOT submitted)\n", OPTIONS.endpoint);
		return 3;
	}

	uint8_t status;
	char *name, *code, *msg, line[8192];
	ssize_t n, offset = 0;
	while ((n = read(0, line + offset, 8192 - offset)) >= 0) {
		if (n == 0 && (!*line || *line == '\x17')) break;
		char *f[4], *p;

#define CONSUME memmove(line, p + 1, 8192 - (p - line) - 1); offset = p - line
#define MALFORMED(s) { fprintf(stderr, "malformed input line: %s; skipping\n", s); CONSUME; continue; }
		p = f[0] = line;
		while (*p && *p != '\t' && *p != '\x17') p++;;
		if (!*p)          MALFORMED("EOL after first token");
		if (*p == '\x17') MALFORMED("ETB after first token");
		*p++ ='\0';

		f[1] = p;
		while (*p && *p != '\t' && *p != '\x17') p++;;
		if (!*p)          MALFORMED("EOL after second token");
		if (*p == '\x17') MALFORMED("ETB after second token");
		*p++ = '\0';

		f[2] = p;
		while (*p && *p != '\t' && *p != '\x17') p++;;
		if (!*p) MALFORMED("EOL after third token");

		if (*p == '\x17') {
			*p = '\0';
			name = strdup(f[0]);
			code = f[1];
			msg  = f[2];
		} else {
			*p++ = '\0';
			f[3] = p;
			while (*p && *p != '\x17') p++;
			if (!*p) MALFORMED("EOL without ETB after fourth token");
			*p = '\0';

			name = string("%s:%s", f[0], f[1]);
			code = f[2];
			msg  = f[3];
		}
		if (msg[strlen(msg)-1] == '\n')
			msg[strlen(msg)-1] = '\0';

		status = UNKNOWN;
		if (strcasecmp(code, "0") == 0 || strcasecmp(code, "ok") == 0 || strcasecmp(code, "okay") == 0)
			status = OK;
		else if (strcasecmp(code, "1") == 0 || strcasecmp(code, "warn") == 0 || strcasecmp(code, "warning") == 0)
			status = WARNING;
		else if (strcasecmp(code, "2") == 0 || strcasecmp(code, "crit") == 0 || strcasecmp(code, "critical") == 0)
			status = CRITICAL;

		if (DEBUG) {
			fprintf(stderr, "+>> determined name to be '%s'\n", name);
			fprintf(stderr, "+>> determined status to be %i (from %s)\n", status, code);
			fprintf(stderr, "+>> determined summary to be '%s'\n", msg);
		}

		if (!name || !*name) {
			fprintf(stderr, "invalid name '%s'\n", name);
			return 2;
		}
		if (!msg || !*msg) {
			fprintf(stderr, "invalid message '%s'\n", msg);
			return 2;
		}
		code = string("%u", status);


		alarm(5);
		if (DEBUG) fprintf(stderr, "+>> sending [SUBMIT|%s|%s|%s] PDU\n", name, code, msg);
		if (pdu_send_and_free(pdu_make("SUBMIT", 3, name, code, msg), z) != 0) {
			fprintf(stderr, "failed to send results to %s\n", OPTIONS.endpoint);
			return 3;
		}
		if (DEBUG) fprintf(stderr, "+>> awaiting response PDU...\n");
		pdu_t *a = pdu_recv(z);
		if (!a) {
			fprintf(stderr, "no response received from %s, assume the worst.\n", OPTIONS.endpoint);
			return 4;
		}
		alarm(0);
		if (DEBUG) fprintf(stderr, "+>> received a [%s] PDU in response\n", pdu_type(a));
		if (strcmp(pdu_type(a), "ERROR") == 0) {
			fprintf(stderr, "error: %s\n", pdu_string(a, 1));
			return 4;
		}
		if (strcmp(pdu_type(a), "OK") != 0) {
			fprintf(stderr, "unknown response [%s] from %s\n", pdu_type(a), OPTIONS.endpoint);
			return 4;
		}

		CONSUME;
	}
#undef CONSUME
#undef MALFORMED

	if (DEBUG) fprintf(stderr, "+>> completed successfully.\n");
	return 0;
}
Ejemplo n.º 6
0
int main(int argc, char **argv)
{
	OPTIONS.verbose    = 0;
	OPTIONS.endpoint   = strdup("tcp://127.0.0.1:2997");
	OPTIONS.daemonize  = 1;
	OPTIONS.pidfile    = strdup("/var/run/bolo2redis.pid");
	OPTIONS.user       = strdup("root");
	OPTIONS.group      = strdup("root");
	OPTIONS.redis_host = strdup("127.0.0.1");
	OPTIONS.redis_port = 6379;

	struct option long_opts[] = {
		{ "help",             no_argument, NULL, 'h' },
		{ "version",          no_argument, NULL, 'V' },
		{ "verbose",          no_argument, NULL, 'v' },
		{ "endpoint",   required_argument, NULL, 'e' },
		{ "foreground",       no_argument, NULL, 'F' },
		{ "pidfile",    required_argument, NULL, 'p' },
		{ "user",       required_argument, NULL, 'u' },
		{ "group",      required_argument, NULL, 'g' },
		{ "host",       required_argument, NULL, 'H' },
		{ "port",       required_argument, NULL, 'P' },
		{ 0, 0, 0, 0 },
	};
	for (;;) {
		int idx = 1;
		int c = getopt_long(argc, argv, "h?Vv+e:Fp:u:g:H:P:", long_opts, &idx);
		if (c == -1) break;

		switch (c) {
		case 'h':
		case '?':
			printf("bolo2redis v%s\n", BOLO_VERSION);
			printf("Usage: bolo2redis [-h?FVv] [-e tcp://host:port]\n"
			       "                  [-H redis.host.or.ip] [-P port]\n"
			       "                  [-u user] [-g group] [-p /path/to/pidfile]\n\n");
			printf("Options:\n");
			printf("  -?, -h               show this help screen\n");
			printf("  -F, --foreground     don't daemonize, stay in the foreground\n");
			printf("  -V, --version        show version information and exit\n");
			printf("  -v, --verbose        turn on debugging, to standard error\n");
			printf("  -e, --endpoint       bolo broadcast endpoint to connect to\n");
			printf("  -H, --host           name or address of redis server\n");
			printf("  -P, --port           what port redis is running on\n");
			printf("  -u, --user           user to run as (if daemonized)\n");
			printf("  -g, --group          group to run as (if daemonized)\n");
			printf("  -p, --pidfile        where to store the pidfile (if daemonized)\n");
			exit(0);

		case 'V':
			logger(LOG_DEBUG, "handling -V/--version");
			printf("bolo2redis v%s\n"
			       "Copyright (c) 2016 The Bolo Authors.  All Rights Reserved.\n",
			       BOLO_VERSION);
			exit(0);

		case 'v':
			OPTIONS.verbose++;
			break;

		case 'e':
			free(OPTIONS.endpoint);
			OPTIONS.endpoint = strdup(optarg);
			break;

		case 'F':
			OPTIONS.daemonize = 0;
			break;

		case 'p':
			free(OPTIONS.pidfile);
			OPTIONS.pidfile = strdup(optarg);
			break;

		case 'u':
			free(OPTIONS.user);
			OPTIONS.user = strdup(optarg);
			break;

		case 'g':
			free(OPTIONS.group);
			OPTIONS.group = strdup(optarg);
			break;

		case 'H':
			free(OPTIONS.redis_host);
			OPTIONS.redis_host = strdup(optarg);
			break;

		case 'P':
			OPTIONS.redis_port = atoi(optarg);
			break;

		default:
			fprintf(stderr, "unhandled option flag %#02x\n", c);
			return 1;
		}
	}

	if (OPTIONS.daemonize) {
		log_open("bolo2redis", "daemon");
		log_level(LOG_ERR + OPTIONS.verbose, NULL);

		mode_t um = umask(0);
		if (daemonize(OPTIONS.pidfile, OPTIONS.user, OPTIONS.group) != 0) {
			fprintf(stderr, "daemonization failed: (%i) %s\n", errno, strerror(errno));
			return 3;
		}
		umask(um);
	} else {
		log_open("bolo2redis", "console");
		log_level(LOG_INFO + OPTIONS.verbose, NULL);
	}
	logger(LOG_NOTICE, "starting up");

	logger(LOG_DEBUG, "allocating 0MQ context");
	void *zmq = zmq_ctx_new();
	if (!zmq) {
		logger(LOG_ERR, "failed to initialize 0MQ context");
		return 3;
	}
	logger(LOG_DEBUG, "allocating 0MQ SUB socket to talk to %s", OPTIONS.endpoint);
	void *z = zmq_socket(zmq, ZMQ_SUB);
	if (!z) {
		logger(LOG_ERR, "failed to create a SUB socket");
		return 3;
	}
	logger(LOG_DEBUG, "setting subscriber filter");
	if (zmq_setsockopt(z, ZMQ_SUBSCRIBE, "", 0) != 0) {
		logger(LOG_ERR, "failed to set subscriber filter");
		return 3;
	}
	logger(LOG_DEBUG, "connecting to %s", OPTIONS.endpoint);
	if (vzmq_connect(z, OPTIONS.endpoint) != 0) {
		logger(LOG_ERR, "failed to connect to %s", OPTIONS.endpoint);
		return 3;
	}

	logger(LOG_INFO, "connecting to redis at %s:%i", OPTIONS.redis_host, OPTIONS.redis_port);
	redisContext *redis = redisConnect(OPTIONS.redis_host, OPTIONS.redis_port);
	if (redis != NULL && redis->err) {
		logger(LOG_ERR, "failed to connect to redis running at %s:%i: %s",
				OPTIONS.redis_host, OPTIONS.redis_port, redis->err);
		return 3;
	}

	pdu_t *p;
	logger(LOG_INFO, "waiting for a PDU from %s", OPTIONS.endpoint);

	signal_handlers();
	while (!signalled()) {
		while ((p = pdu_recv(z))) {
			logger(LOG_INFO, "received a [%s] PDU of %i frames", pdu_type(p), pdu_size(p));

			if (strcmp(pdu_type(p), "SET.KEYS") == 0 && pdu_size(p) % 2 == 1 ) {
				int i = 1;
				while (i < pdu_size(p)) {
					char *k = pdu_string(p, i++);
					char *v = pdu_string(p, i++);
					logger(LOG_DEBUG, "setting key `%s' = '%s'", k, v);

					redisReply *reply = redisCommand(redis, "SET %s %s", k, v);
					if (reply->type == REDIS_REPLY_ERROR) {
						logger(LOG_ERR, "received error from redis: %s", reply->str);
					}
					freeReplyObject(reply);

					free(k);
					free(v);
				}
			}

			pdu_free(p);
			logger(LOG_INFO, "waiting for a PDU from %s", OPTIONS.endpoint);
		}
	}

	logger(LOG_INFO, "shutting down");
	vzmq_shutdown(z, 0);
	zmq_ctx_destroy(zmq);
	return 0;
}
Ejemplo n.º 7
0
int cmd_query(int off, int argc, char **argv)
{
	char *endpoint = strdup("tcp://127.0.0.1:2998");
	struct option long_opts[] = {
		{ "endpoint", required_argument, 0, 'e' },
		{ 0, 0, 0, 0 },
	};

	optind = ++off;
	for (;;) {
		int c = getopt_long(argc, argv, "e:", long_opts, &off);
		if (c == -1) break;

		switch (c) {
		case 'e':
			free(endpoint);
			endpoint = strdup(optarg);
		default:
			fprintf(stderr, "unhandled option flag %#02x\n", c);
			return 1;
		}
	}

	void *zmq = zmq_ctx_new();
	if (!zmq) {
		fprintf(stderr, "failed to initialize 0MQ context\n");
		return 3;
	}
	void *z = zmq_socket(zmq, ZMQ_DEALER);
	if (!z) {
		fprintf(stderr, "failed to create a DEALER socket\n");
		return 3;
	}
	if (vzmq_connect(z, endpoint) != 0) {
		fprintf(stderr, "failed to connect to %s\n", endpoint);
		return 3;
	}

	pdu_t *p;
	char *a, *b, *c, *s;
	char line[8192];
	for (;;) {
		if (isatty(0)) fprintf(stderr, "> ");
		if (fgets(line, 8192, stdin) == NULL)
			break;

		a = line;
		while (*a && isspace(*a)) a++;
		if (!*a || *a == '#') continue;
		b = a;
		while (*b && !isspace(*b)) b++;

		c = b;
		if (*c) c++;
		*b = '\0';

		if (strcasecmp(a, "stat") == 0) {
			while (*c && isspace(*c)) c++;
			if (!*c) {
				fprintf(stderr, "missing state name to `stat' call\n");
				fprintf(stderr, "usage: stat <state-name>\n");
				continue;
			}

			a = c; while (*a && !isspace(*a)) a++;
			b = a; while (*b &&  isspace(*b)) b++;
			*a = '\0';
			if (*b) {
				fprintf(stderr, "too many arguments to `stat' call\n");
				fprintf(stderr, "usage: stat <state-name>\n");
				continue;
			}

			if (pdu_send_and_free(pdu_make("GET.STATE", 1, c), z) != 0) {
				fprintf(stderr, "failed to send [GET.STATE] PDU to %s; command aborted\n", endpoint);
				return 3;
			}
			p = pdu_recv(z);
			if (!p) {
				fprintf(stderr, "no response received from %s\n", endpoint);
				return 3;
			}
			if (strcmp(pdu_type(p), "ERROR") == 0) {
				fprintf(stderr, "error: %s\n", s = pdu_string(p, 1)); free(s);
				continue;
			}
			if (strcmp(pdu_type(p), "STATE") != 0) {
				fprintf(stderr, "unknown response [%s] from %s\n", pdu_type(p), endpoint);
				return 4;
			}
			fprintf(stdout, "%s ",  s = pdu_string(p, 1)); free(s); /* name      */
			fprintf(stdout, "%s ",  s = pdu_string(p, 2)); free(s); /* timestamp */
			fprintf(stdout, "%s ",  s = pdu_string(p, 3)); free(s); /* stale     */
			fprintf(stdout, "%s ",  s = pdu_string(p, 4)); free(s); /* code      */
			fprintf(stdout, "%s\n", s = pdu_string(p, 5)); free(s); /* summary   */
			pdu_free(p);

		} else if (strcasecmp(a, "set.keys") == 0) {
			while (*c && isspace(*c)) c++;
			if (!*c) {
				fprintf(stderr, "missing arguments to `set.keys' call\n");
				fprintf(stderr, "usage: set.keys key1 value1 key2 value2 ...\n");
				continue;
			}

			int n = 0;
			p = pdu_make("SET.KEYS", 0);
			while (*c) {
				a = c; while (*a && !isspace(*a)) a++;
				b = a; while (*b &&  isspace(*b)) b++;
				*a = '\0';

				n++;
				pdu_extendf(p, "%s", a);
				c = b;
			}

			if (n % 2 != 0) {
				fprintf(stderr, "odd number of arguments to `set.keys' call\n");
				fprintf(stderr, "usage: set.keys key1 value1 key2 value2 ...\n");
				pdu_free(p);
				continue;
			}

			if (pdu_send_and_free(p, z) != 0) {
				fprintf(stderr, "failed to send [SET.KEYS] PDU to %s; command aborted\n", endpoint);
				return 3;
			}
			p = pdu_recv(z);
			if (!p) {
				fprintf(stderr, "no response received from %s\n", endpoint);
				return 3;
			}

			if (strcmp(pdu_type(p), "ERROR") == 0) {
				fprintf(stderr, "error: %s\n", s = pdu_string(p, 1)); free(s);
				pdu_free(p);
				continue;
			}
			pdu_free(p);

		} else if (strcasecmp(a, "get.keys") == 0) {
			while (*c && isspace(*c)) c++;
			if (!*c) {
				fprintf(stderr, "missing arguments to `get.keys' call\n");
				fprintf(stderr, "usage: get.keys key1 key2 ...\n");
				continue;
			}

			p = pdu_make("GET.KEYS", 0);
			while (*c) {
				a = c; while (*a && !isspace(*a)) a++;
				b = a; while (*b &&  isspace(*b)) b++;
				*a = '\0';

				pdu_extendf(p, "%s", a);
				c = b;
			}

			if (pdu_send_and_free(p, z) != 0) {
				fprintf(stderr, "failed to send [GET.KEYS] PDU to %s; command aborted\n", endpoint);
				return 3;
			}
			p = pdu_recv(z);
			if (!p) {
				fprintf(stderr, "no response received from %s\n", endpoint);
				return 3;
			}

			if (strcmp(pdu_type(p), "ERROR") == 0) {
				fprintf(stderr, "error: %s\n", s = pdu_string(p, 1)); free(s);
				pdu_free(p);
				continue;
			}

			int i;
			for (i = 1; i < pdu_size(p); i += 2) {
				a = pdu_string(p, i);
				b = pdu_string(p, i + 1);

				fprintf(stdout, "%s = %s\n", a, b);

				free(a);
				free(b);
			}
			pdu_free(p);

		} else if (strcasecmp(a, "del.keys") == 0) {
			while (*c && isspace(*c)) c++;
			if (!*c) {
				fprintf(stderr, "missing argument to `del.keys' call\n");
				fprintf(stderr, "usage: del.keys key1 key2 ...\n");
				continue;
			}

			p = pdu_make("DEL.KEYS", 0);
			while (*c) {
				a = c; while (*a && !isspace(*a)) a++;
				b = a; while (*b &&  isspace(*b)) b++;
				*a = '\0';

				pdu_extendf(p, "%s", a);
				c = b;
			}

			if (pdu_send_and_free(p, z) != 0) {
				fprintf(stderr, "failed to send [DEL.KEYS] PDU to %s; command aborted\n", endpoint);
				return 3;
			}
			p = pdu_recv(z);
			if (!p) {
				fprintf(stderr, "no response received from %s\n", endpoint);
				return 3;
			}

			if (strcmp(pdu_type(p), "ERROR") == 0) {
				fprintf(stderr, "error: %s\n", s = pdu_string(p, 1)); free(s);
				pdu_free(p);
				continue;
			}
			pdu_free(p);

		} else if (strcasecmp(a, "search.keys") == 0) {
			while (*c && isspace(*c)) c++;
			if (!*c) {
				fprintf(stderr, "missing pattern argument to `search.keys' call\n");
				fprintf(stderr, "usage: search.keys <pattern>\n");
				continue;
			}

			a = c; while (*a && !isspace(*a)) a++;
			b = a; while (*b &&  isspace(*b)) b++;
			*a = '\0';
			if (*b) {
				fprintf(stderr, "too many arguments to `search.keys' call\n");
				fprintf(stderr, "usage: search.keys <pattern>\n");
				continue;
			}

			if (pdu_send_and_free(pdu_make("SEARCH.KEYS", 1, c), z) != 0) {
				fprintf(stderr, "failed to send [SEARCH.KEYS] PDU to %s; command aborted\n", endpoint);
				return 3;
			}
			p = pdu_recv(z);
			if (!p) {
				fprintf(stderr, "no response received from %s\n", endpoint);
				return 3;
			}
			if (strcmp(pdu_type(p), "ERROR") == 0) {
				fprintf(stderr, "error: %s\n", s = pdu_string(p, 1)); free(s);
				pdu_free(p);
				continue;
			}

			int i;
			for (i = 1; i < pdu_size(p); i++) {
				fprintf(stdout, "%s\n", s = pdu_string(p, i));
				free(s);
			}
			pdu_free(p);

		} else if (strcasecmp(a, "get.events") == 0) {
			char *ts = "0";
			if (*c) {
				ts = c;
				while (*c && isdigit(*c)) c++;
				*c = '\0';
			}

			if (pdu_send_and_free(pdu_make("GET.EVENTS", 1, ts), z) != 0) {
				fprintf(stderr, "failed to send [GET.EVENTS] PDU to %s; command aborted\n", endpoint);
				return 3;
			}
			p = pdu_recv(z);
			if (!p) {
				fprintf(stderr, "no response received from %s\n", endpoint);
				return 3;
			}
			if (strcmp(pdu_type(p), "ERROR") == 0) {
				fprintf(stderr, "error: %s\n", s = pdu_string(p, 1)); free(s);
				continue;
			}
			if (strcmp(pdu_type(p), "EVENTS") != 0) {
				fprintf(stderr, "unknown response [%s] from %s\n", pdu_type(p), endpoint);
				return 4;
			}
			fprintf(stdout, "%s", s = pdu_string(p, 1)); free(s);
			pdu_free(p);

		} else if (strcasecmp(a, "dump") == 0) {
			if (*c) fprintf(stderr, "ignoring useless arguments to `dump' command\n");

			if (pdu_send_and_free(pdu_make("DUMP", 0), z) != 0) {
				fprintf(stderr, "failed to send [DUMP] PDU to %s; command aborted\n", endpoint);
				return 3;
			}
			p = pdu_recv(z);
			if (!p) {
				fprintf(stderr, "no response received from %s\n", endpoint);
				return 3;
			}
			if (strcmp(pdu_type(p), "ERROR") == 0) {
				fprintf(stderr, "error: %s\n", s = pdu_string(p, 1)); free(s);
				continue;
			}
			if (strcmp(pdu_type(p), "DUMP") != 0) {
				fprintf(stderr, "unknown response [%s] from %s\n", pdu_type(p), endpoint);
				return 4;
			}
			fprintf(stdout, "%s", s = pdu_string(p, 1)); free(s);
			pdu_free(p);

		} else {
			fprintf(stderr, "unrecognized command '%s'\n", a);
			continue;
		}
	}
	return 0;
}