--- libaitcli/src/telnet.c 2011/03/16 18:28:06 1.1 +++ libaitcli/src/telnet.c 2011/03/16 18:28:06 1.1.2.1 @@ -0,0 +1,512 @@ +/************************************************************************* +* (C) 2010 AITNET ltd - Sofia/Bulgaria - +* by Michael Pounov +* +* $Author: misho $ +* $Id: telnet.c,v 1.1.2.1 2011/03/16 18:28:06 misho Exp $ +* +*************************************************************************/ +#ifndef NDEBUG + #define TELOPTS + #define TELCMDS +#endif +#include "global.h" + + +/* + * cli_telnetRecv() Telnet receive commands, negotiate with telnet peer + * @sock = socket for communication + * @attr = received attributes list, must be free after use, but if NULL receive in binary mode + * @nAttr = received attributes list size, if is NULL receive in binary mode + * @pdata = received data in supplied buffer + * @datLen = buffer pdata size + * return: 0 not present data; -1 error:: can`t read; -2 timeout; -3 EOF; >0 number of received bytes +*/ +int +cli_telnetRecv(int sock, struct telnetAttrs **attr, int *nAttr, void *pdata, int datLen) +{ + int readLen, ret, pos; + u_char buf[BUFSIZ], *data = NULL; + struct telnetAttrs ta; + register int i; +#ifdef SELECT_WAIT_FOR_READ + fd_set fds; + struct timeval tv = { DEFAULT_TELNET_TIMEOUT, 0 }; +#endif + + if (attr && nAttr) { + *attr = NULL; + *nAttr = 0; + } + + data = pdata ? pdata : NULL; + if (!data || !datLen || BUFSIZ < datLen) { + cli_SetErr(EINVAL, "Data buffer or size is not valid!"); + return -1; + } else + memset(data, 0, datLen); + +#ifdef SELECT_WAIT_FOR_READ + FD_ZERO(&fds); + FD_SET(sock, &fds); + if ((ret = select(sock + 1, &fds, NULL, NULL, &tv)) == -1) { + LOGERR; + return -1; + } + if (!ret) { + cli_SetErr(ETIMEDOUT, "Timeout!"); + return -2; // Timeout + } else + ret = 0; +#endif + + memset(buf, 0, BUFSIZ); + readLen = read(sock, buf, BUFSIZ); + if (-1 == readLen) { + LOGERR; + return -1; + } else + if (!readLen) + return -3; // EOF + + // receive in binary mode ... + if (!attr || !nAttr) { + memcpy(data, buf, readLen > datLen ? datLen : readLen); + return readLen; + } + + // telnet ascii mode ... + for (ret = pos = i = 0; i < readLen && pos < datLen; i++) { + // raw(clear) mode + if (!ret) { + if (IAC != buf[i]) { + data[pos++] = buf[i]; + } else { + ret = 1; + memset(&ta, 0, sizeof ta); + } + + continue; + } + + // check for command + if (1 == ret) { + if (xEOF > buf[i]) { + data[pos++] = buf[i - 1]; + data[pos++] = buf[i]; + + goto cmd_exit; + } else + ta.ta_cmd = buf[i]; + if (SB > ta.ta_cmd) { + (*nAttr)++; + *attr = realloc(*attr, sizeof(struct telnetAttrs) * *nAttr); + if (!*attr) { + LOGERR; + return -1; + } else + memcpy(&(*attr)[*nAttr - 1], &ta, sizeof(struct telnetAttrs)); + + goto cmd_exit; + } + if (IAC > ta.ta_cmd) { + ret = 2; + continue; + } +cmd_exit: + ret = 0; + continue; + } + + // check for option + if (2 == ret) { + if (!(TELOPT_KERMIT >= buf[i]) && TELOPT_EXOPL != buf[i]) { + data[pos++] = buf[i - 2]; + data[pos++] = buf[i - 1]; + data[pos++] = buf[i]; + + goto opt_exit; + } else + ta.ta_opt = buf[i]; + if (SB != ta.ta_cmd) { + (*nAttr)++; + *attr = realloc(*attr, sizeof(struct telnetAttrs) * *nAttr); + if (!*attr) { + LOGERR; + return -1; + } else + memcpy(&(*attr)[*nAttr - 1], &ta, sizeof(struct telnetAttrs)); + + goto opt_exit; + } + + ret = 3; + continue; +opt_exit: + ret = 0; + continue; + } + + // sub-option + if (3 == ret) { + if (ta.ta_sublen < MAX_SUB_LEN) { + if (SE == buf[i] && IAC == ta.ta_sub[ta.ta_sublen - 1]) { + ta.ta_sublen--; + ta.ta_sub[ta.ta_sublen] = 0; + + (*nAttr)++; + *attr = realloc(*attr, sizeof(struct telnetAttrs) * *nAttr); + if (!*attr) { + LOGERR; + return -1; + } else + memcpy(&(*attr)[*nAttr - 1], &ta, sizeof(struct telnetAttrs)); + ret = 0; + } else + ta.ta_sub[ta.ta_sublen++] = buf[i]; + } else { + cli_SetErr(EPROTONOSUPPORT, "Protocol limitation in sub-option to %d!", MAX_SUB_LEN); + free(*attr); + *nAttr = 0; + return -1; + } + } + } + + return pos; +} + +#ifndef NDEBUG + +/* + * cli_telnet_DumpAttrs() Telnet debug attributes list, if NDEBUG defined not include + * @attr = attributes list + * @nAttr = attributes list size + * return: none +*/ +void +cli_telnet_DumpAttrs(struct telnetAttrs *attr, int nAttr) +{ + register int i; + + for (i = 0; i < nAttr; i++) { + printf("DUMP:: Attribute(%d) = %s %s Sub(%d) => %s\n", i, + TELCMD(attr[i].ta_cmd), TELOPT(attr[i].ta_opt), + attr[i].ta_sublen, attr[i].ta_sub); + } +} + +#endif + +/* + * cli_telnetSend() Telnet send commands, negotiate with telnet peer + * @sock = socket for communication + * @attr = send attributes list + * @nAttr = send attributes list size + * @data = data for send + * @datLen = data size + * @Term = Terminate with GA (Go Ahead), 1 send after data GA command + * return: 0 not sended commands; -1 error:: can`t send; >0 number of sended bytes +*/ +int +cli_telnetSend(int sock, struct telnetAttrs *attr, int nAttr, void *data, int datLen, int Term) +{ + register int i; + int writeLen, pos = 0, len = 0; + u_char *buf = NULL; + + /* add commands */ + + if (attr && nAttr) { + for (i = 0; i < nAttr; i++) { + len = 2; // IAC CMD + if (attr[i].ta_cmd > GA && attr[i].ta_cmd < IAC) { + len++; // added OPT + + if (SB == attr[i].ta_cmd) { + len += 2; // added IAC SE to end ;) + len += attr[i].ta_sublen; + } + } + + buf = realloc(buf, pos + len); + if (!buf) { + LOGERR; + return -1; + } + + buf[pos++] = IAC; + buf[pos++] = attr[i].ta_cmd; + if (attr[i].ta_cmd > GA && attr[i].ta_cmd < IAC) { + buf[pos++] = attr[i].ta_opt; + + if (SB == attr[i].ta_cmd) { + memcpy(buf + pos, attr[i].ta_sub, attr[i].ta_sublen); + pos += attr[i].ta_sublen; + buf[pos++] = IAC; + buf[pos++] = SE; + } + } + } + } + + /* add data */ + + if (data && datLen) { + buf = realloc(buf, pos + datLen); + if (!buf) { + LOGERR; + return -1; + } + + memcpy(buf + pos, data, datLen); + pos += datLen; + } + + /* add GA after end of all */ + + if (Term) { + buf = realloc(buf, pos + 2); + if (!buf) { + LOGERR; + return -1; + } + + buf[pos++] = IAC; + buf[pos++] = GA; + } + + writeLen = write(sock, buf, pos); + if (-1 == writeLen) + LOGERR; + + if (buf) + free(buf); + return writeLen; +} + + +/* + * cli_telnet_Get_SubOpt() Telnet get sub option function + * @attr = input attribute + * @code = sub-option code for opt + * @data = sub-option data + * @datLen = data size set max size in input, output return copy size + * return: -1 can`t get option; !=-1 option code +*/ +inline int +cli_telnet_Get_SubOpt(struct telnetAttrs *attr, u_char *code, void *data, u_char *datLen) +{ + u_char *pos, len; + + if (!attr || !data || !datLen || !*datLen) + return -1; + if (SB != attr->ta_cmd || !attr->ta_sublen) { + cli_SetErr(ENOTSUP, "Wrong attribute argument!"); + return -1; + } else { + pos = attr->ta_sub; + len = attr->ta_sublen; + } + + memset(data, 0, *datLen); + if (0xFF != *code) { + *code = *pos++; + len--; + } + + *datLen = len > *datLen ? *datLen : len; + memcpy(data, pos, *datLen); + + return attr->ta_opt; +} + +/* + * cli_telnet_Set_SubOpt() Telnet set sub option function + * @attr = output attribute + * @opt = attribute option + * @code = sub-option code for opt, if 0xff not specified + * @data = sub-option data, if NULL not specified + * @datLen = data size, if 0 not specified + * return: -1 can`t set sub-otion; 0 ok +*/ +inline int +cli_telnet_Set_SubOpt(struct telnetAttrs *attr, u_char opt, u_char code, void *data, u_char datLen) +{ + u_char len; + + if (!attr) + return -1; + if (!(TELOPT_KERMIT >= opt) && TELOPT_EXOPL != opt) { + cli_SetErr(EINVAL, "Invalid option argument!"); + return -1; + } + + memset(attr, 0, sizeof(struct telnetAttrs)); + attr->ta_cmd = SB; + attr->ta_opt = opt; + + if (0xFF != code) { + attr->ta_sublen++; + attr->ta_sub[0] = code; + } + + if (data && datLen) { + len = MAX_SUB_LEN > datLen ? datLen : MAX_SUB_LEN - 1; + attr->ta_sublen += len; + memcpy(attr->ta_sub + 1, data, len); + } + + return 0; +} + +/* + * cli_telnet_GetCmd() Telnet get command + * @attr = input attribute + * return: -1 can`t get command; !=-1 command <<24 return sublen, <<8 return option, <<0 command +*/ +inline u_int +cli_telnet_GetCmd(struct telnetAttrs *attr) +{ + u_int ret = 0; + + if (!attr) + return -1; + if (xEOF > attr->ta_cmd) { + cli_SetErr(ENOTSUP, "Wrong attribute command argument!"); + return -1; + } + if (GA < attr->ta_cmd && !(TELOPT_KERMIT >= attr->ta_opt) && TELOPT_EXOPL != attr->ta_opt) { + cli_SetErr(ENOTSUP, "Wrong attribute option argument!"); + return -1; + } + + ret = attr->ta_sublen << 24; + ret |= attr->ta_opt << 8; + ret |= attr->ta_cmd; + + return ret; +} + +/* + * cli_telnet_SetCmd() Telnet set command + * @attr = input attribute + * @cmd = command + * @opt = option, if 0xff not specified + * @arg1 = sub-option code, if 0xff not specified + * @arg2 = sub-option data, if NULL not specified + * @arg3 = sub-option data size, if 0 not specified data + * return: -1 can`t set command; !=-1 ok +*/ +inline int +cli_telnet_SetCmd(struct telnetAttrs *attr, u_char cmd, u_char opt, ...) +{ + va_list lst; + u_char res; + void *ptr; + + if (!attr) + return -1; + else + memset(attr, 0, sizeof(struct telnetAttrs)); + + if (xEOF > cmd) { + cli_SetErr(EINVAL, "Invalid command argument!"); + return -1; + } else + attr->ta_cmd = cmd; + if (GA < attr->ta_cmd) { + if (!(TELOPT_KERMIT >= attr->ta_opt) && TELOPT_EXOPL != attr->ta_opt) { + cli_SetErr(EINVAL, "Invalid option argument!"); + return -1; + } else + attr->ta_opt = opt; + } + if (SB == attr->ta_cmd) { + va_start(lst, opt); + res = (u_char) va_arg(lst, int); + if (0xff != res) { + *attr->ta_sub = res; + attr->ta_sublen++; + } + ptr = va_arg(lst, void*); + res = (u_char) va_arg(lst, int); + if (ptr && MAX_SUB_LEN > res) { + memcpy(attr->ta_sub + 1, ptr, res); + attr->ta_sublen += res; + } + va_end(lst); + } + + return 0; +} + + +/* + * cli_telnet_Answer() Automatic generate commands answer to send from telnet + * @caps = Array of capability options + * @nCaps = number of capability options + * @attr = input attribute + * @nAttr = number of input attributes + * @ans = output answered attributes, must be free() after use + * @Ans = number of output answered attributes + * return: -1 can`t answer; !=-1 ok +*/ +int +cli_telnet_Answer(u_char *caps, int nCaps, struct telnetAttrs *attr, int nAttr, + struct telnetAttrs **ans, int *Ans) +{ + register int i, j; + int flg; + struct telnetAttrs ta; + + if (!caps || !nCaps || !attr || !nAttr || !ans || !Ans) + return -1; + else { + *ans = NULL; + *Ans = 0; + } + + for (i = 0; i < nAttr; i++) { + if (SB > attr[i].ta_cmd || IAC == attr[i].ta_cmd || DONT == attr[i].ta_cmd || WONT == attr[i].ta_cmd) + continue; + + for (flg = -1, j = 0; j < nCaps; j++) { + if (!(TELOPT_KERMIT >= CAP(caps[j])) && TELOPT_EXOPL != CAP(caps[j])) + continue; + + if (attr[i].ta_opt == CAP(caps[j])) { + flg = j; + break; + } + } + + // make attribute ... + if (flg > -1) { + (*Ans)++; + *ans = realloc(*ans, sizeof(struct telnetAttrs) * *Ans); + if (!*ans) { + LOGERR; + return -1; + } else + memset(&ta, 0, sizeof ta); + + ta.ta_opt = attr[i].ta_opt; + switch (attr[i].ta_cmd) { + case DO: + ta.ta_cmd = SUP_CAPS(caps[flg]) ? WILL : WONT; + break; + case WILL: + ta.ta_cmd = SUP_CAPS(caps[flg]) ? DO : DONT; + break; + case SB: + ta.ta_cmd = SB; + break; + } + + memcpy(&(*ans)[*Ans - 1], &ta, sizeof(struct telnetAttrs)); + } + } + + return 0; +}