Mercurial > hg > nnchat
view libnnchat.c @ 354:c01e42fc9adb
More work on SOCKS proxy support, should work now.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Thu, 23 Jun 2011 08:26:48 +0300 |
parents | b54c8545dcb0 |
children | ba2aa110755b |
line wrap: on
line source
/* * NNChat - Custom chat client for NewbieNudes.com chatrooms * Written by Matti 'ccr' Hämäläinen * (C) Copyright 2008-2011 Tecnic Software productions (TNSP) */ #include "libnnchat.h" #ifdef __WIN32 const char *hstrerror(int err) { static char buf[64]; snprintf(buf, sizeof(buf), "Error #%d", err); return buf; } int nn_get_socket_errno(void) { return WSAGetLastError(); } const char *nn_get_socket_errstr(int err) { static char buf[64]; switch (err) { case WSAEADDRINUSE: return "Address already in use"; case WSAECONNABORTED: return "Software caused connection abort"; case WSAECONNREFUSED: return "Connection refused"; case WSAECONNRESET: return "Connection reset by peer"; case WSAEHOSTUNREACH: return "No route to host"; case WSAENETDOWN: return "Network is down"; case WSAETIMEDOUT: return "Connection timed out"; case WSAHOST_NOT_FOUND: return "Host not found"; case WSAVERNOTSUPPORTED: return "Wrong WinSock DLL version"; default: snprintf(buf, sizeof(buf), "Error #%d", err); return buf; break; } } #else int nn_get_socket_errno(void) { return errno; } const char *nn_get_socket_errstr(int err) { return strerror(err); } #endif void nn_conn_err(nn_conn_t *conn, const char *fmt, ...) { if (conn->errfunc) { va_list ap; va_start(ap, fmt); conn->errfunc(conn, fmt, ap); va_end(ap); } } static void nn_conn_msg(nn_conn_t *conn, const char *fmt, ...) { if (conn->msgfunc) { va_list ap; va_start(ap, fmt); conn->msgfunc(conn, fmt, ap); va_end(ap); } } struct hostent *nn_resolve_host(nn_conn_t *conn, const char *name) { struct hostent *res = gethostbyname(name); if (res == NULL) nn_conn_err(conn, "Could not resolve hostname: %s\n", strerror(h_errno)); else nn_conn_msg(conn, "True hostname for %s is %s\n", name, res->h_name); return res; } static const char *nn_proxy_types[] = { "none", "SOCKS 4", "SOCKS 4a", NULL }; nn_conn_t * nn_conn_new( void (*errfunc)(nn_conn_t *conn, const char *fmt, va_list ap), void (*msgfunc)(nn_conn_t *conn, const char *fmt, va_list ap)) { nn_conn_t *conn = th_calloc(1, sizeof(nn_conn_t)); if (conn == NULL) return NULL; conn->errfunc = errfunc; conn->msgfunc = msgfunc; return conn; } int nn_conn_set_proxy(nn_conn_t *conn, int type, int port, const char *host) { if (conn == NULL) return -1; conn->proxy.type = type; conn->proxy.port = port; conn->proxy.host = th_strdup(host); if (host != NULL) { conn->proxy.hst = nn_resolve_host(conn, host); if (conn->proxy.hst != NULL) conn->proxy.addr = *(struct in_addr *) (conn->proxy.hst->h_addr); else { conn->proxy.addr.s_addr = 0; } } else return -2; return 0; } int nn_conn_open(nn_conn_t *conn, const int port, const char *host) { struct sockaddr_in dest; static const char *userid = "James Bond"; if (conn == NULL) return -1; conn->port = port; if (host != NULL) { conn->host = th_strdup(host); conn->hst = nn_resolve_host(conn, host); } if (conn->hst != NULL) conn->addr = *(struct in_addr *) (conn->hst->h_addr); else { conn->addr.s_addr = 0; } /* Prepare for connection */ if (conn->proxy.type > NN_PROXY_NONE && conn->proxy.type < NN_PROXY_LAST) { dest.sin_family = AF_INET; dest.sin_port = htons(conn->proxy.port); dest.sin_addr = conn->proxy.addr; nn_conn_msg(conn, "Connecting to %s proxy %s:%d ...\n", nn_proxy_types[conn->proxy.type], inet_ntoa(dest.sin_addr), port); } else { dest.sin_family = AF_INET; dest.sin_port = htons(port); dest.sin_addr = conn->addr; nn_conn_msg(conn, "Connecting to %s:%d ...\n", inet_ntoa(dest.sin_addr), port); } if ((conn->socket = socket(PF_INET, SOCK_STREAM, 0)) == -1) { conn->err = nn_get_socket_errno(); nn_conn_err(conn, "Could not open socket: %s\n", nn_get_socket_errstr(conn->err)); goto error; } nn_conn_msg(conn, "Using socket %d.\n", conn->socket); if (connect(conn->socket, (struct sockaddr *) &dest, sizeof(dest)) == -1) { conn->err = nn_get_socket_errno(); nn_conn_err(conn, "Could not connect: %s\n", nn_get_socket_errstr(conn->err)); goto error; } FD_ZERO(&(conn->sockfds)); FD_SET(conn->socket, &(conn->sockfds)); /* Proxy-specific setup */ if (conn->proxy.type == NN_PROXY_SOCKS4 || conn->proxy.type == NN_PROXY_SOCKS4A) { struct nn_socks_t *socksh; size_t bufsiz = sizeof(struct nn_socks_t) + strlen(userid) + 1 + strlen(conn->host) + 1; char *ptr, *buf; int tries, status = -1; ptr = buf = th_malloc(bufsiz); if (buf == NULL) { conn->err = -1; nn_conn_err(conn, "Could not allocate memory for SOCKS negotiation buffer, %d bytes.\n", bufsiz); goto error; } /* Create SOCKS 4/4A request */ nn_conn_msg(conn, "Initializing proxy negotiation.\n"); socksh = (struct nn_socks_t *) buf; socksh->version = 4; socksh->command = SOCKS_CMD_CONNECT; socksh->port = htons(port); if (conn->proxy.type == NN_PROXY_SOCKS4A) socksh->addr = htonl(0x00000032); else socksh->addr = conn->addr.s_addr; ptr += sizeof(struct nn_socks_t); strcpy(ptr, userid); ptr += strlen(userid) + 1; strcpy(ptr, conn->host); /* Send request */ if (!nn_conn_send_buf(conn, buf, bufsiz)) { th_free(buf); nn_conn_err(conn, "Error sending SOCKS proxy request.\n"); goto error; } th_free(buf); /* Wait for SOCKS server to reply */ for (tries = 0; tries < 20; tries++) { status = nn_conn_pull(conn); if (status <= 0) break; } /* Check results */ if (status == 0) { struct nn_socks_res_t *res = (struct nn_socks_res_t *) &(conn->buf); if (res->nb != 0) { nn_conn_err(conn, "Invalid SOCKS server reply, does not begin with NUL byte (%d).\n", res->nb); goto error; } if (res->result != 0x5a) { char *s = NULL; switch (res->result) { case 0x5b: s = "Request rejected or failed"; break; case 0x5c: s = "Request failed because client is not running identd (or not reachable from the server)"; break; case 0x5d: s = "Request failed because client's identd could not confirm the user ID string in the request"; break; default: s = "Unknown SOCKS error response"; break; } nn_conn_err(conn, "SOCKS setup failed, 0x%02x: %s.\n", res->result, s); goto error; } } else goto error; } conn->status = NN_CONN_OPEN; return 0; error: conn->status = NN_CONN_CLOSED; return -2; } void nn_conn_close(nn_conn_t *conn) { if (conn == NULL) return; if (conn->socket >= 0) { #ifdef __WIN32 closesocket(conn->socket); #else close(conn->socket); #endif conn->socket = -1; } th_free(conn->host); th_free(conn->proxy.host); conn->status = NN_CONN_CLOSED; th_free(conn); } BOOL nn_conn_send_buf(nn_conn_t *conn, const char *buf, const size_t len) { size_t bufLeft = len; const char *bufPtr = buf; while (bufLeft > 0) { ssize_t bufSent; bufSent = send(conn->socket, bufPtr, bufLeft, 0); if (bufSent < 0) { conn->err = nn_get_socket_errno(); nn_conn_err(conn, "nn_conn_send_buf() failed: %s", nn_get_socket_errstr(conn->err)); return FALSE; } bufLeft -= bufSent; bufPtr += bufSent; } return TRUE; } int nn_conn_pull(nn_conn_t *conn) { int result; struct timeval socktv; fd_set tmpfds; if (conn == NULL) return -1; conn->ptr = conn->buf; /* Check for incoming data */ socktv.tv_sec = 0; socktv.tv_usec = NN_DELAY_USEC; tmpfds = conn->sockfds; if ((result = select(conn->socket + 1, &tmpfds, NULL, NULL, &socktv)) == -1) { conn->err = nn_get_socket_errno(); if (conn->err != EINTR) { nn_conn_err(conn, "Error occured in select(%d, sockfds): %d, %s\n", socket, conn->err, nn_get_socket_errstr(conn->err)); return -1; } } else if (FD_ISSET(conn->socket, &tmpfds)) { conn->got = recv(conn->socket, conn->ptr, NN_CONNBUF_SIZE, 0); if (conn->got < 0) { conn->err = nn_get_socket_errno(); nn_conn_err(conn, "Error in recv: %d, %s\n", conn->err, nn_get_socket_errstr(conn->err)); return -2; } else if (conn->got == 0) { nn_conn_err(conn, "Server closed connection.\n"); conn->status = NN_CONN_CLOSED; return -3; } else { /* Handle protocol data */ conn->buf[conn->got] = 0; return 0; } } return 1; } BOOL nn_conn_check(nn_conn_t *conn) { if (conn == NULL) return FALSE; return conn->err == 0 && conn->status == NN_CONN_OPEN; } BOOL nn_network_init(void) { #ifdef __WIN32 /* Initialize WinSock, if needed */ WSADATA wsaData; int err = WSAStartup(0x0101, &wsaData); if (err != 0) { THERR("Could not initialize WinSock library (err=%d).\n", err); return FALSE; } #endif return TRUE; } void nn_network_close(void) { #ifdef __WIN32 WSACleanup(); #endif } #define PUSHCHAR(x) th_vputch(&result, &resSize, &resPos, x) #define PUSHSTR(x) th_vputs(&result, &resSize, &resPos, x) char *nn_encode_str1(const char *str) { const char *s = str; char *result; size_t resSize, resPos = 0; if (str == NULL) return NULL; resSize = strlen(str) + NN_ALLOC_SIZE; if ((result = th_malloc(resSize)) == NULL) return NULL; while (*s) { switch (*s) { case 32: PUSHCHAR('+'); break; default: if (th_isalnum(*s)) PUSHCHAR(*s); else { char tmpStr[4]; snprintf(tmpStr, sizeof(tmpStr), "%2X", (unsigned char) *s); PUSHCHAR('%'); PUSHSTR(tmpStr); } break; } s++; } PUSHCHAR(0); return result; } static int getHexDigit(const int c, const int shift) { int i; if (c >= 'A' && c <= 'F') i = c - 'A' + 10; else if (c >= 'a' && c <= 'f') i = c - 'a' + 10; else if (c >= '0' && c <= '9') i = c - '0'; else return -1; return i << shift; } char *nn_decode_str1(const char *str) { const char *s = str; char *result; size_t resSize, resPos = 0; int c; if (str == NULL) return NULL; resSize = strlen(str) + NN_ALLOC_SIZE; if ((result = th_malloc(resSize)) == NULL) return NULL; while (*s) { switch (*s) { case '+': PUSHCHAR(' '); s++; break; case '½': /* Escape these .. */ PUSHCHAR('½'); PUSHCHAR('½'); s++; break; case '\r': PUSHCHAR(' '); s++; break; case '%': s++; if (*s == '%') PUSHCHAR('%'); else if ((c = getHexDigit(*s, 4)) >= 0) { int i = getHexDigit(*(++s), 0); if (i >= 0) { PUSHCHAR(c | i); } else { PUSHCHAR('§'); PUSHCHAR(*s); } } else { PUSHCHAR('§'); PUSHCHAR(*s); } s++; break; default: PUSHCHAR(*s); s++; } } PUSHCHAR(0); return result; } char *nn_strip_tags(const char *str) { const char *s = str; char *result; size_t resSize, resPos = 0; if (str == NULL) return NULL; resSize = strlen(str) + NN_ALLOC_SIZE; if ((result = th_malloc(resSize)) == NULL) return NULL; while (*s) { if (*s == '<') { while (*s && *s != '>') s++; if (*s == '>') s++; } else PUSHCHAR(*s++); } PUSHCHAR(0); return result; } typedef struct { char c; char *ent; } html_entity_t; static const html_entity_t HTMLEntities[] = { { '<', "<" }, { '>', ">" }, }; static const int numHTMLEntities = sizeof(HTMLEntities) / sizeof(HTMLEntities[0]); char *nn_encode_str2(const char *str) { const char *s = str; char *result; size_t resSize, resPos = 0; if (str == NULL) return NULL; resSize = strlen(str) + NN_ALLOC_SIZE; if ((result = th_malloc(resSize)) == NULL) return NULL; while (*s) { int i; BOOL found = FALSE; for (i = 0; i < numHTMLEntities; i++) if (HTMLEntities[i].c == *s) { PUSHSTR(HTMLEntities[i].ent); found = TRUE; break; } if (!found) PUSHCHAR(*s); s++; } PUSHCHAR(0); return result; } char *nn_decode_str2(const char *str) { const char *s = str; char *result; size_t resSize, resPos = 0; if (str == NULL) return NULL; resSize = strlen(str); if ((result = th_malloc(resSize)) == NULL) return NULL; while (*s) { if (*s == '&') { int i; BOOL found = FALSE; for (i = 0; i < numHTMLEntities; i++) { const html_entity_t *ent = &HTMLEntities[i]; int len = strlen(ent->ent); if (!strncmp(s, ent->ent, len)) { PUSHCHAR(ent->c); s += len; found = TRUE; break; } } if (!found) PUSHCHAR(*s++); } else PUSHCHAR(*s++); } PUSHCHAR(0); return result; } char *nn_dbldecode_str(const char *str) { char *res, *tmp; if ((tmp = nn_decode_str1(str)) == NULL) return NULL; res = nn_decode_str2(tmp); th_free(tmp); return res; } char *nn_dblencode_str(const char *str) { char *res, *tmp; if ((tmp = nn_encode_str2(str)) == NULL) return NULL; res = nn_encode_str1(tmp); th_free(tmp); return res; } BOOL nn_conn_send_msg(nn_conn_t *conn, const char *user, const char *fmt, ...) { char *tmp, *msg; va_list ap; va_start(ap, fmt); tmp = th_strdup_vprintf(fmt, ap); va_end(ap); if (tmp == NULL) return FALSE; msg = th_strdup_printf("<USER>%s</USER><MESSAGE>%s</MESSAGE>", user, tmp); th_free(tmp); if (msg != NULL) { BOOL ret = nn_conn_send_buf(conn, msg, strlen(msg) + 1); th_free(msg); return ret; } else return FALSE; } int nn_editbuf_write(nn_editbuf_t *buf, ssize_t pos, int ch) { if (buf->len+1 >= buf->size) return -3; if (pos < 0) return -1; else if (pos >= buf->len) { buf->data[buf->len++] = ch; } else { buf->data[pos] = ch; } return 0; } int nn_editbuf_insert(nn_editbuf_t *buf, ssize_t pos, int ch) { if (buf->len+1 >= buf->size) return -3; if (pos < 0) return -1; else if (pos >= buf->len) { buf->data[buf->len] = ch; } else { memmove(&(buf->data[pos+1]), &(buf->data[pos]), buf->len - pos + 1); buf->data[pos] = ch; } buf->len++; return 0; } int nn_editbuf_delete(nn_editbuf_t *buf, ssize_t pos) { if (pos < 0) return -1; else if (pos < buf->len) { memmove(&(buf->data[pos]), &(buf->data[pos+1]), buf->len - pos); buf->len--; return 0; } else return -2; } void nn_editbuf_clear(nn_editbuf_t *buf) { buf->len = 0; buf->pos = 0; } nn_editbuf_t * nn_editbuf_new(ssize_t n) { nn_editbuf_t *res = th_calloc(1, sizeof(nn_editbuf_t)); res->data = (char *) th_malloc(n); res->size = n; return res; } void nn_editbuf_free(nn_editbuf_t *buf) { if (buf != NULL) { th_free(buf->data); th_free(buf); } } nn_editbuf_t * nn_editbuf_copy(nn_editbuf_t *src) { nn_editbuf_t *res; assert(src != NULL); if (src == NULL) return NULL; if ((res = nn_editbuf_new(src->size)) == NULL) return NULL; memcpy(res->data, src->data, src->size); res->pos = res->len = src->len; return res; } char * nn_editbuf_get_string(nn_editbuf_t *buf, ssize_t start, ssize_t end) { char *str; ssize_t siz; if (buf == NULL) return NULL; if (start < 0 || end > buf->len || start >= buf->len) return NULL; if (end < 0) { siz = buf->len - start + 1; } else if (start <= end) { siz = end - start + 1; } else return NULL; if ((str = th_malloc(siz + 1)) == NULL) return NULL; memcpy(str, buf->data + start, siz); str[siz] = 0; return str; } void nn_editbuf_setpos(nn_editbuf_t *buf, ssize_t pos) { assert(buf != NULL); if (pos < 0) buf->pos = 0; else if (pos >= buf->len) buf->pos = buf->len; else buf->pos = pos; } static uint8_t nn_hash_user(const char *name) { /* int n = 0; const uint8_t *c = (uint8_t *)name; uint8_t hash = 0xff; while (*c && n < 4) { hash = (hash << 1) ^ tolower(*c); c++; n++; } return (hash & 0xff); */ return tolower(name[0]); } static void nn_user_insert(nn_user_t **list, nn_user_t *node) { node->next = *list; *list = node; } nn_user_t *nn_userhash_foreach(const nn_userhash_t *list, int (*func)(const nn_user_t *)) { int i; if (list == NULL) return NULL; for (i = 0; i < NN_NUM_BUCKETS; i++) if (list->buckets[i] != NULL) { nn_user_t *curr = list->buckets[i]; while (curr != NULL) { if (func(curr) != 0) return curr; curr = curr->next; } } return NULL; } nn_user_t *nn_user_find(const nn_userhash_t *list, const char *name) { uint8_t hash; if (list == NULL) return NULL; hash = nn_hash_user(name); if (list->buckets[hash] != NULL) { nn_user_t *curr = list->buckets[hash]; while (curr != NULL) { if (strcasecmp(curr->name, name) == 0) return curr; curr = curr->next; } } return NULL; } static nn_user_t *nn_user_match_do(nn_user_t *list, const char *pattern, size_t len) { nn_user_t *curr = list; while (curr != NULL) { if (len <= strlen(curr->name) && strncasecmp(curr->name, pattern, len) == 0) return curr; curr = curr->next; } return NULL; } nn_user_t *nn_user_match(const nn_userhash_t *list, const char *pattern, const char *current, BOOL again) { uint8_t hash; if (list == NULL || pattern == NULL) return NULL; hash = nn_hash_user(pattern); if (list->buckets[hash] != NULL) { nn_user_t *curr = list->buckets[hash]; size_t len = strlen(pattern); if (current != NULL) { nn_user_t *found = NULL; while (curr != NULL) { if (strcasecmp(curr->name, current) == 0) { if (again) return curr; found = curr->next; break; } curr = curr->next; } if (found != NULL && (found = nn_user_match_do(found, pattern, len)) != NULL) return found; } if ((curr = nn_user_match_do(list->buckets[hash], pattern, len)) != NULL) return curr; } return NULL; } nn_userhash_t *nn_userhash_new(void) { return th_calloc(1, sizeof(nn_userhash_t)); } int nn_userhash_insert(nn_userhash_t *list, const char *name) { uint8_t hash; nn_user_t *user; /* Check arguments */ if (list == NULL || name == NULL) return -1; /* Check if username is already there */ if (nn_user_find(list, name) != NULL) return 1; /* No, we'll add it */ if ((user = th_calloc(1, sizeof(nn_user_t))) == NULL) return -3; user->name = th_strdup(name); if (user->name == NULL) return -4; hash = nn_hash_user(name); nn_user_insert(&(list->buckets[hash]), user); return 0; } int nn_userhash_delete(nn_userhash_t *list, const char *name) { uint8_t hash; /* Check arguments */ if (list == NULL || name == NULL) return -1; /* Check if username is already there */ hash = nn_hash_user(name); if (list->buckets[hash] != NULL) { nn_user_t *curr, *prev; curr = list->buckets[hash]; prev = NULL; while (curr != NULL) { if (strcasecmp(curr->name, name) == 0) { if (prev) prev->next = curr->next; else list->buckets[hash] = curr->next; nn_user_free(curr); return 0; } else { prev = curr; curr = curr->next; } } } return 1; } nn_user_t *nn_user_copy(const nn_user_t *src) { nn_user_t *user; if (src == NULL) return NULL; if ((user = th_calloc(1, sizeof(nn_user_t))) == NULL) return NULL; /* Copy relevant data */ user->name = th_strdup(src->name); user->lastspoke = src->lastspoke; user->joined = src->joined; return user; } void nn_user_free(nn_user_t *user) { th_free(user->name); th_free(user); } void nn_user_free_list(nn_user_t *list) { nn_user_t *next, *curr = list; while (curr != NULL) { next = curr->next; nn_user_free(curr); curr = next; } } void nn_userhash_free(nn_userhash_t *hash) { int i; if (hash == NULL) return; for (i = 0; i < NN_NUM_BUCKETS; i++) { nn_user_free_list(hash->buckets[i]); hash->buckets[i] = NULL; } th_free(hash); } char *nn_username_encode(char *str) { unsigned char *c = (unsigned char *) str; if (str == NULL) return NULL; for (; *c ; c++) if (*c == ' ') *c = 255; return str; } char *nn_username_decode(char *str) { unsigned char *c = (unsigned char *) str; if (str == NULL) return NULL; for (; *c ; c++) if (*c == 255) *c = ' '; return str; } nn_window_t *nn_window_new(const char *id) { nn_window_t *res = th_calloc(1, sizeof(nn_window_t)); if (res == NULL) return NULL; res->data = th_ringbuf_new(NN_BACKBUF_LEN, th_free); if (res->data == NULL) { th_free(res); return NULL; } res->id = th_strdup(id); return res; } void nn_window_free(nn_window_t *win) { if (win != NULL) { th_ringbuf_free(win->data); th_free(win->id); th_free(win); } } nn_strtuple_t *nn_strtuple_new(size_t len, char *str) { nn_strtuple_t *tuple = th_calloc(1, sizeof(nn_strtuple_t)); tuple->len = len; tuple->str = str; return tuple; } void nn_strtuple_free(nn_strtuple_t *tuple) { th_free(tuple->str); th_free(tuple); }