diff options
Diffstat (limited to 'src/pmdas/cisco/telnet.c')
-rw-r--r-- | src/pmdas/cisco/telnet.c | 755 |
1 files changed, 755 insertions, 0 deletions
diff --git a/src/pmdas/cisco/telnet.c b/src/pmdas/cisco/telnet.c new file mode 100644 index 0000000..bcee1db --- /dev/null +++ b/src/pmdas/cisco/telnet.c @@ -0,0 +1,755 @@ +/* + * Copyright (c) 2012 Red Hat. + * Copyright (c) 1995-2004 Silicon Graphics, Inc. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * 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. See the GNU General Public License + * for more details. + */ + +#include <sys/types.h> +#include <unistd.h> +#include <stdio.h> +#include <string.h> +#include <ctype.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <sys/socket.h> +#include <sys/wait.h> +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <syslog.h> +#include "./cisco.h" + +extern int port; + +int +conn_cisco(cisco_t * cp) +{ + __pmFdSet wfds; + __pmSockAddr *myaddr; + void *enumIx; + int flags = 0; + int fd; + int ret; + + fd = -1; + enumIx = NULL; + for (myaddr = __pmHostEntGetSockAddr(cp->hostinfo, &enumIx); + myaddr != NULL; + myaddr = __pmHostEntGetSockAddr(cp->hostinfo, &enumIx)) { + /* Create a socket */ + if (__pmSockAddrIsInet(myaddr)) + fd = __pmCreateSocket(); + else if (__pmSockAddrIsIPv6(myaddr)) + fd = __pmCreateIPv6Socket(); + else + continue; + if (fd < 0) { + __pmSockAddrFree(myaddr); + continue; /* Try the next address */ + } + + /* Attempt to connect */ + flags = __pmConnectTo(fd, myaddr, cp->port); + __pmSockAddrFree(myaddr); + + if (flags < 0) { + /* + * Mark failure in case we fall out the end of the loop + * and try next address. fd has been closed in __pmConnectTo(). + */ + setoserror(ECONNREFUSED); + fd = -1; + continue; + } + + /* FNDELAY and we're in progress - wait on select */ + __pmFD_ZERO(&wfds); + __pmFD_SET(fd, &wfds); + ret = __pmSelectWrite(fd+1, &wfds, NULL); + + /* Was the connection successful? */ + if (ret == 0) + setoserror(ETIMEDOUT); + else if (ret > 0) { + ret = __pmConnectCheckError(fd); + if (ret == 0) + break; + setoserror(ret); + } + + /* Unsuccessful connection. */ + __pmCloseSocket(fd); + fd = -1; + } /* loop over addresses */ + + if (fd == -1) { + fprintf(stderr, "conn_cisco(%s): connect: %s\n", + cp->host, netstrerror()); + return -1; + } + + fd = __pmConnectRestoreFlags(fd, flags); + if (fd < 0) { + fprintf(stderr, "conn_cisco(%s): setsockopt: %s\n", + cp->host, netstrerror()); + return -1; + } + + return fd; +} + +static void +skip2eol(FILE *f) +{ + int c; + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL2) + fprintf(stderr, "skip2eol:"); +#endif + + while ((c = fgetc(f)) != EOF) { + if (c == '\n') + break; +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL2) + fprintf(stderr, "%c", c); +#endif + } +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL2) + fputc('\n', stderr); +#endif +} + +char * +mygetwd(FILE *f, char *prompt) +{ + char *p; + int c; + static char buf[1024]; + int len_prompt = strlen(prompt); + int found_prompt = 0; + + p = buf; + + while ((c = fgetc(f)) != EOF) { + if (c == '\r' || c == '\n' || c == ' ' || c == '\t') { + if (p == buf) + continue; + break; + } + *p++ = c; + if (p-buf >= len_prompt && strncmp(&p[-len_prompt], prompt, len_prompt) == 0) { + found_prompt = 1; + break; + } + } + *p = '\0'; + + if (feof(f)) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL2) + fprintf(stderr, "mygetwd: EOF fd=%d\n", fileno(f)); +#endif + return NULL; + } + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL2) + fprintf(stderr, "mygetwd: fd=%d wd=\"%s\"%s\n", fileno(f), buf, found_prompt ? " [prompt]" : ""); +#endif + + return buf; +} + +/* + * The CISCO "show interface" command output is parsed. + * + * See the file Samples for examples. + * + * The parser is a Finite State Automaton (FSA) that follows these + * rules: + * + * SHOW_INT style ... uses "show int <interface>" command + * state token next state + * NOISE <interface name> IN_REPORT + * IN_REPORT Description: skip rest of line, IN_REPORT + * IN_REPORT <prompt> DONE + * IN_REPORT minute RATE + * IN_REPORT second RATE + * IN_REPORT input, BYTES_IN + * IN_REPORT output, BYTES_OUT + * IN_REPORT BW BW + * RATE input skip next token, RATE_IN + * RATE output skip next token, RATE_OUT + * RATE_IN <number> (rate_in) IN_REPORT + * RATE_OUT <number> (rate_out) IN_REPORT + * BYTES_IN <number> (bytes_in) IN_REPORT + * BYTES_OUT <number> (bytes_out) IN_REPORT + * BW <number> (bandwidth) IN_REPORT + * + * SHOW_FRAME style ... uses "show frame pvc int <interface>" command + * state token next state + * NOISE <interface name> IN_REPORT + * IN_REPORT Description: skip rest of line, IN_REPORT + * IN_REPORT <prompt> DONE + * IN_REPORT 1st bytes BYTES_IN + * IN_REPORT 2nd bytes BYTES_OUT + * IN_REPORT 3rd bytes BYTES_OUT_BCAST + * BYTES_IN <number> (bytes_in) IN_REPORT + * BYTES_OUT <number> (bytes_out) IN_REPORT + * BYTES_OUT_BCAST <number> (bytes_out_bcast) IN_REPORT + * + * Note lines are terminated with \r + */ + +#define DONE -1 +#define NOISE 0 +#define IN_REPORT 1 +#define RATE 2 +#define RATE_IN 3 +#define RATE_OUT 4 +#define BYTES_IN 5 +#define BYTES_OUT 6 +#define BW 7 +#define BYTES_OUT_BCAST 8 + +#ifdef PCP_DEBUG +static char *statestr[] = { + "done", "noise", "in_report", + "rate", "rate_in", "rate_out", "bytes_in", "bytes_out", "bw", + "bytes_out_bcast" +}; +#endif + +int +dousername(cisco_t *cp, char **pw_prompt) +{ + char *w; + int len, done = 0; + int len_prompt = strlen(cp->prompt); + + for ( ; ; ) { + w = mygetwd(cp->fin, cp->prompt); + if (w == NULL) + break; + if (strlen(w) >= len_prompt && strncmp(&w[strlen(w)-len_prompt], cp->prompt, len_prompt) == 0) + break; +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL0) + fprintf(stderr, "Username:? got - %s\n", w); +#endif + if (strcmp(w, USERPROMPT) == 0) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL1) { + fprintf(stderr, "Send username: %s\n", cp->username); + } +#endif + fprintf(cp->fout, "%s\n", cp->username); + fflush(cp->fout); + for ( ; ; ) { + w = mygetwd(cp->fin, cp->prompt); + if (w == NULL || strcmp(w, USERPROMPT) == 0) + /* closed connection or Username re-prompt */ + break; + len = strlen(w); + if ((len >= len_prompt && strncmp(&w[len-len_prompt], cp->prompt, len_prompt) == 0) || + w[len-1] == ':') { + /* command prompt or passwd */ + if (w[len-1] == ':') + *pw_prompt = w; + done = 1; + break; + } + } + break; + } + } + + if (done == 0) { + fprintf(stderr, "Error: Cisco username negotiation failed for \"%s\"\n", + cp->host); + fprintf(stderr, +"To check that a username is required, enter the following command:\n" +" $ telnet %s\n" +"If the prompt \"%s\" does not appear, no username is required.\n" +"Otherwise, enter the username \"%s\" to check that this\n" +"is correct.\n", +cp->host, USERPROMPT, cp->username); + } + + return done; +} + +int +dopasswd(cisco_t *cp, char *pw_prompt) +{ + char *w; + int done = 0; + int len_prompt = strlen(cp->prompt); + + for ( ; ; ) { + if (pw_prompt) /* dousername may have read passwd prompt */ + w = pw_prompt; + else + w = mygetwd(cp->fin, cp->prompt); + pw_prompt = NULL; + if (w == NULL) + break; + if (strlen(w) >= len_prompt && strncmp(&w[strlen(w)-len_prompt], cp->prompt, len_prompt) == 0) + break; +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL0) + fprintf(stderr, "Password:? got - %s\n", w); +#endif + if (strcmp(w, PWPROMPT) == 0) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL1) { + fprintf(stderr, "Send passwd: %s\n", cp->passwd); + } +#endif + fprintf(cp->fout, "%s\n", cp->passwd); + fflush(cp->fout); + for ( ; ; ) { + w = mygetwd(cp->fin, cp->prompt); + if (w == NULL || strcmp(w, PWPROMPT) == 0) + /* closed connection or user-level password re-prompt */ + break; + if (strlen(w) >= len_prompt && strncmp(&w[strlen(w)-len_prompt], cp->prompt, len_prompt) == 0) { + /* command prompt */ + done = 1; + break; + } + } + break; + } + } + + if (done == 0) { + fprintf(stderr, "Error: Cisco user-level password negotiation failed for \"%s\"\n", + cp->host); + fprintf(stderr, +"To check that a user-level password is required, enter the following command:\n" +" $ telnet %s\n" +"If the prompt \"%s\" does not appear, no user-level password is required.\n" +"Otherwise, enter the user-level password \"%s\" to check that this\n" +"is correct.\n", +cp->host, PWPROMPT, cp->passwd); + } + + return done; +} + +static int timeout; + +void +onalarm(int dummy) +{ + timeout = 1; +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL0) { + fprintf(stderr, "Alarm timeout!\n"); + } +#endif + +} + +static int +get_fr_bw(cisco_t *cp, char *interface) +{ + int state = NOISE; + int bandwidth = -1; + char *w; + int len_prompt = strlen(cp->prompt); + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL1) { + fprintf(stderr, "Send: s%s\n", interface); + } +#endif + fprintf(cp->fout, "show int s%s\n", interface); + fflush(cp->fout); +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL2) + fprintf(stderr, "BW Parse:"); +#endif + while (state != DONE) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL2) + fprintf(stderr, "[%s] ", statestr[state+1]); +#endif + w = mygetwd(cp->fin, cp->prompt); + if (w == NULL || timeout) { + /* + * End of File (telenet timeout?) + */ + alarm(0); + return -1; + } + switch (state) { + + case NOISE: + if (strncmp(w, "Serial", 6) == 0 && strcmp(&w[6], interface) == 0) + state = IN_REPORT; + break; + + case IN_REPORT: + if (strcmp(w, "Description:") == 0) + skip2eol(cp->fin); + else if (strlen(w) >= len_prompt && strncmp(&w[strlen(w)-len_prompt], cp->prompt, len_prompt) == 0) + state = DONE; + else if (strcmp(w, "BW") == 0) + state = BW; + break; + + case BW: + sscanf(w, "%d", &bandwidth); + bandwidth *= 1000; /* Kbit -> bytes/sec */ + bandwidth /= 8; + state = IN_REPORT; + break; + + } + } + alarm(0); +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL0) { + fprintf(stderr, "Extracted bandwidth: %d bytes/sec\n", bandwidth); + } +#endif + return bandwidth; +} + +#define SHOW_INT 1 +#define SHOW_FRAME 2 + +int +grab_cisco(intf_t *ip) +{ + int style; + int next_state; + int state = NOISE; + int skip = 0; + int i; + int namelen; + char *pw_prompt = NULL; + char *w; + int fd; + int fd2; + int nval = 0; + cisco_t *cp = ip->cp; + intf_t tmp; + int len_prompt = strlen(cp->prompt); + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL0) { + fprintf(stderr, "grab_cisco(%s:%s):\n", cp->host, ip->interface); + } +#endif + + tmp.bandwidth = tmp.rate_in = tmp.rate_out = -1; + tmp.bytes_in = tmp.bytes_out = tmp.bytes_out_bcast = -1; + + if (cp->fin == NULL) { + fd = conn_cisco(cp); + if (fd < 0) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL0) + fprintf(stderr, "grab_cisco(%s:%s): connect failed: %s\n", + cp->host, ip->interface, netstrerror()); +#endif + return -1; + } + else { + cp->fin = fdopen (fd, "r"); + if ((fd2 = dup(fd)) < 0) { + perror("dup"); + exit(1); + } + cp->fout = fdopen (fd2, "w"); +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL0) { + fprintf(stderr, "grab_cisco(%s:%s): connected fin=%d fout=%d", + cp->host, ip->interface, fileno(cp->fin), fileno(cp->fout)); + if (cp->username != NULL) + fprintf(stderr, " username=%s", cp->username); + else + fprintf(stderr, " NO username"); + if (cp->passwd != NULL) + fprintf(stderr, " passwd=%s", cp->passwd); + else + fprintf(stderr, " NO passwd"); + fputc('\n', stderr); + } +#endif + + if (cp->username != NULL) { + /* + * Username stuff ... + */ + if (dousername(cp, &pw_prompt) == 0) { + fclose(cp->fin); + fclose(cp->fout); + cp->fin = cp->fout = NULL; + return -1; + } + } + if (cp->passwd != NULL) { + /* + * User-level password stuff ... + */ + if (dopasswd(cp, pw_prompt) == 0) { + fclose(cp->fin); + fclose(cp->fout); + cp->fin = cp->fout = NULL; + return -1; + } + } +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL1) { + fprintf(stderr, "Send: \n"); + } +#endif + fprintf(cp->fout, "\n"); + fflush(cp->fout); + } +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL1) { + fprintf(stderr, "Send: terminal length 0\n"); + } +#endif + fprintf(cp->fout, "terminal length 0\n"); + fflush(cp->fout); + } + + timeout = 0; + signal(SIGALRM, onalarm); + /* + * Choice of timeout here is somewhat arbitrary ... for a long + * time this was 5 (seconds), but then testing with an entry + * level Model 800 ADSL router revealed that up to 20 seconds + * was required to generate the expected output. + */ + alarm(20); + + style = SHOW_INT; /* default Cisco command */ + if (ip->interface[0] == 's' && strchr(ip->interface, '.') != NULL) { + /* + * Frame-relay PVC on subinterface for s2/3.7 style interface name + */ + style = SHOW_FRAME; + if (ip->bandwidth == -2) { + /* + * one-trip initialzation ... need show int s2/3.7 to + * get bandwidth + */ + ip->bandwidth = get_fr_bw(cp, &ip->interface[1]); + } + tmp.bandwidth = ip->bandwidth; + if (tmp.bandwidth != -1) + nval++; + } + if (style == SHOW_FRAME) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL1) { + fprintf(stderr, "Send: show frame pvc int s%s\n", &ip->interface[1]); + } +#endif + fprintf(cp->fout, "show frame pvc int s%s\n", &ip->interface[1]); + next_state = BYTES_IN; + } + else { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL1) { + fprintf(stderr, "Send: show int %s\n", ip->interface); + } +#endif + fprintf(cp->fout, "show int %s\n", ip->interface); + } + fflush(cp->fout); + state = NOISE; +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL2) { + fprintf(stderr, "Parse:"); + fflush(stderr); + } +#endif + while (state != DONE) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL2) { + fprintf(stderr, "[%s] ", statestr[state+1]); + fflush(stderr); + } +#endif + w = mygetwd(cp->fin, cp->prompt); + if (w == NULL || timeout) { + /* + * End of File (telenet timeout?) + * ... mark as closed, and try again at next request + */ +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL0) + fprintf(stderr, "grab_cisco(%s:%s): forced disconnect fin=%d\n", + cp->host, ip->interface, fileno(cp->fin)); +#endif + fclose(cp->fin); + fclose(cp->fout); + cp->fin = cp->fout = NULL; + alarm(0); + return -1; + } + switch (state) { + + case NOISE: + for (i = 0; i < num_intf_tab; i++) { + namelen = strlen(intf_tab[i].name); + if (strncmp(w, intf_tab[i].name, namelen) == 0) { + state = IN_REPORT; + break; + } + } + break; + + case IN_REPORT: + if (strcmp(w, "Description:") == 0) + skip2eol(cp->fin); + if (strlen(w) >= len_prompt && strncmp(&w[strlen(w)-len_prompt], cp->prompt, len_prompt) == 0) + state = DONE; + else if (style == SHOW_INT) { + if (strcmp(w, "minute") == 0 || strcmp(w, "second") == 0) + state = RATE; + else if (strcmp(w, "input,") == 0) + state = BYTES_IN; + else if (strcmp(w, "output,") == 0) + state = BYTES_OUT; + else if (strcmp(w, "BW") == 0) + state = BW; + } + else if (style == SHOW_FRAME) { + if (strcmp(w, "bytes") == 0) { + if (next_state == BYTES_IN) { + state = BYTES_IN; + next_state = BYTES_OUT; + } + else if (next_state == BYTES_OUT) { + state = BYTES_OUT; + next_state = BYTES_OUT_BCAST; + } + else if (next_state == BYTES_OUT_BCAST) { + state = BYTES_OUT_BCAST; + next_state = IN_REPORT; + } + else + state = next_state; + } + } + break; + + case RATE: + if (strcmp(w, "input") == 0) { + skip = 1; + state = RATE_IN; + } + else if (strcmp(w, "output") == 0) { + skip = 1; + state = RATE_OUT; + } + break; + + case RATE_IN: + if (skip-- == 0) { + tmp.rate_in = atol(w) / 8; + nval++; + state = IN_REPORT; + } + break; + + case RATE_OUT: + if (skip-- == 0) { + tmp.rate_out = atol(w) / 8; + nval++; + state = IN_REPORT; + } + break; + + case BYTES_IN: + tmp.bytes_in = strtoull(w, NULL, 10); + nval++; + state = IN_REPORT; + break; + + case BYTES_OUT: + tmp.bytes_out = strtoull(w, NULL, 10); + nval++; + state = IN_REPORT; + break; + + case BYTES_OUT_BCAST: + tmp.bytes_out_bcast = strtoull(w, NULL, 10); + nval++; + state = IN_REPORT; + break; + + case BW: + sscanf(w, "%d", &tmp.bandwidth); + tmp.bandwidth *= 1000; /* Kbit -> bytes/sec */ + tmp.bandwidth /= 8; + nval++; + state = IN_REPORT; + break; + + } + } + alarm(0); +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL0) { + fprintf(stderr, "Extracted %d values ...\n", nval); + if (tmp.bandwidth != 0xffffffff) + fprintf(stderr, "bandwidth: %d bytes/sec\n", tmp.bandwidth); + else + fprintf(stderr, "bandwidth: ? bytes/sec\n"); + fprintf(stderr, "recent rate (bytes/sec):"); + if (tmp.rate_in != 0xffffffff) + fprintf(stderr, " %d in", tmp.rate_in); + else + fprintf(stderr, " ? in"); + if (tmp.rate_out != 0xffffffff) + fprintf(stderr, " %d out", tmp.rate_out); + else + fprintf(stderr, " ? out"); + fprintf(stderr, "\ntotal bytes:"); + if (tmp.bytes_in != 0xffffffffffffffffLL) + fprintf(stderr, " %llu in", (unsigned long long)tmp.bytes_in); + else + fprintf(stderr, " ? in"); + if (tmp.bytes_out != 0xffffffffffffffffLL) + fprintf(stderr, " %llu out", (unsigned long long)tmp.bytes_out); + else + fprintf(stderr, " ? out"); + if (tmp.bytes_out_bcast != 0xffffffffffffffffLL) + fprintf(stderr, " %llu out_bcast", (unsigned long long)tmp.bytes_out_bcast); + else + fprintf(stderr, " ? out_bcast"); + fprintf(stderr, "\n\n"); + } +#endif + + /* pretend this is atomic */ + ip->bandwidth = tmp.bandwidth; + ip->rate_in = tmp.rate_in; + ip->rate_out = tmp.rate_out; + ip->bytes_in = tmp.bytes_in; + ip->bytes_out = tmp.bytes_out; + ip->bytes_out_bcast = tmp.bytes_out_bcast; + + return nval; +} |