summaryrefslogtreecommitdiff
path: root/src/rexmpp_socks.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/rexmpp_socks.c')
-rw-r--r--src/rexmpp_socks.c121
1 files changed, 121 insertions, 0 deletions
diff --git a/src/rexmpp_socks.c b/src/rexmpp_socks.c
new file mode 100644
index 0000000..48046bb
--- /dev/null
+++ b/src/rexmpp_socks.c
@@ -0,0 +1,121 @@
+#include <memory.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <errno.h>
+#include <stdint.h>
+#include <arpa/inet.h>
+
+#include "rexmpp_socks.h"
+
+enum socks_err
+rexmpp_socks_proceed (rexmpp_socks_t *s)
+{
+ ssize_t ret;
+ if (s->io_state == REXMPP_SOCKS_WRITING) {
+ ssize_t ret = send(s->fd, s->buf + s->buf_sent,
+ s->buf_len - s->buf_sent, 0);
+ if (ret > 0) {
+ s->buf_sent += ret;
+ if (s->buf_len == s->buf_sent) {
+ s->buf_len = 0;
+ s->io_state = REXMPP_SOCKS_READING;
+ }
+ } else if (errno == EAGAIN) {
+ return REXMPP_SOCKS_E_AGAIN;
+ } else {
+ return REXMPP_SOCKS_E_TCP;
+ }
+ } else if (s->io_state == REXMPP_SOCKS_READING) {
+ ret = recv(s->fd, s->buf + s->buf_len,
+ REXMPP_SOCKS_BUF_LEN - s->buf_len, 0);
+ if (ret > 0) {
+ s->buf_len += ret;
+ if (s->buf[0] != 5) {
+ return REXMPP_SOCKS_E_VERSION;
+ }
+ if (s->buf_len >= 2) {
+ s->socks_error = s->buf[1];
+ }
+ if (s->stage == REXMPP_SOCKS_AUTH) {
+ if (s->buf_len > 2) {
+ return REXMPP_SOCKS_E_REPLY;
+ }
+ if (s->buf_len == 2) {
+ if (s->socks_error != 0) {
+ return REXMPP_SOCKS_E_SOCKS;
+ }
+ /* It's okay to not authenticate, now we send a command. */
+ s->buf[0] = 5; /* SOCKS version 5 */
+ s->buf[1] = 1; /* Connect */
+ s->buf[2] = 0; /* Reserved */
+ s->buf[3] = 3; /* Domain name (todo: IP addresses) */
+ size_t len = strlen(s->host);
+ s->buf[4] = len;
+ memcpy(s->buf + 5, s->host, len);
+ uint16_t port = htons(s->port);
+ memcpy(s->buf + 5 + len, &port, 2);
+ s->buf_len = 7 + len;
+ s->buf_sent = 0;
+ s->stage = REXMPP_SOCKS_CMD;
+ s->io_state = REXMPP_SOCKS_WRITING;
+ return rexmpp_socks_proceed(s);
+ }
+ } else if (s->stage == REXMPP_SOCKS_CMD) {
+ if (s->buf_len >= 5) {
+ size_t full_len = 6;
+ if (s->buf[3] == 1) { /* IPv4 */
+ full_len += 4;
+ } else if (s->buf[3] == 3) { /* Domain name */
+ full_len += s->buf[4] + 1;
+ } else if (s->buf[3] == 4) { /* IPv6 */
+ full_len += 16;
+ } else {
+ return REXMPP_SOCKS_E_REPLY;
+ }
+ if (s->buf_len > full_len) {
+ return REXMPP_SOCKS_E_REPLY;
+ }
+ if (s->buf_len == full_len) {
+ if (s->socks_error != 0) {
+ return REXMPP_SOCKS_E_SOCKS;
+ }
+ /* We're done. */
+ s->stage = REXMPP_SOCKS_DONE;
+ return REXMPP_SOCKS_CONNECTED;
+ }
+ }
+ }
+ } else if (errno == EAGAIN) {
+ return REXMPP_SOCKS_E_AGAIN;
+ } else {
+ return REXMPP_SOCKS_E_TCP;
+ }
+ }
+ return REXMPP_SOCKS_E_AGAIN;
+}
+
+enum socks_err
+rexmpp_socks_init (rexmpp_socks_t *s,
+ int fd,
+ const char *host,
+ uint16_t port)
+{
+ s->fd = fd;
+ s->host = host;
+ s->port = port;
+ s->socks_error = 0;
+
+ if (strlen(host) > 255) {
+ return REXMPP_SOCKS_E_HOST;
+ }
+
+ /* Request authentication. */
+ s->stage = REXMPP_SOCKS_AUTH;
+ s->io_state = REXMPP_SOCKS_WRITING;
+ s->buf[0] = 5; /* SOCKS version 5 */
+ s->buf[1] = 1; /* 1 supported method */
+ s->buf[2] = 0; /* "no authentication required" */
+ s->buf_len = 3;
+ s->buf_sent = 0;
+ return rexmpp_socks_proceed(s);
+}