ratox

FIFO based tox client
git clone git://git.2f30.org/ratox.git
Log | Files | Refs | README | LICENSE

commit e0cd01eef6b8f2d83e2ca3f4d87b015f2305b7db
parent 3c390f75591337768bcd95f585d41ebdc822fb1b
Author: sin <sin@2f30.org>
Date:   Wed, 17 Sep 2014 19:50:59 +0100

Rename ratatox to ratox

Diffstat:
Makefile | 4++--
config.mk | 2+-
ratatox.c | 1188-------------------------------------------------------------------------------
ratox.c | 1188+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 1191 insertions(+), 1191 deletions(-)

diff --git a/Makefile b/Makefile @@ -3,7 +3,7 @@ include config.mk .POSIX: .SUFFIXES: .c .o -SRC = ratatox.c +SRC = ratox.c OBJ = $(SRC:.c=.o) BIN = $(SRC:.c=) @@ -11,7 +11,7 @@ BIN = $(SRC:.c=) all: options bin options: - @echo ratatox build options: + @echo ratox build options: @echo "CFLAGS = $(CFLAGS)" @echo "LDFLAGS = $(LDFLAGS)" @echo "CC = $(CC)" diff --git a/config.mk b/config.mk @@ -1,4 +1,4 @@ -# ratatox version +# ratox version VERSION = 0.0 # paths diff --git a/ratatox.c b/ratatox.c @@ -1,1188 +0,0 @@ -/* See LICENSE file for copyright and license details. */ -#include <sys/select.h> -#include <sys/stat.h> -#include <sys/types.h> - -#include <ctype.h> -#include <errno.h> -#include <fcntl.h> -#include <limits.h> -#include <stdarg.h> -#include <stdint.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <time.h> -#include <unistd.h> - -#include <tox/tox.h> - -#include "arg.h" -#include "queue.h" - -#define LEN(x) (sizeof (x) / sizeof *(x)) -#define DATAFILE ".ratatox.data" - -struct node { - const char *addr; - uint16_t port; - uint8_t key[TOX_CLIENT_ID_SIZE]; -}; - -#include "config.h" - -struct file { - int type; - const char *name; - int flags; - mode_t mode; -}; - -enum { - IN, - OUT, - ERR, - NR_GFILES -}; - -struct slot { - const char *name; - void (*cb)(void *); - int outtype; - int outmode; - int fd[NR_GFILES]; -}; - -enum { - NAME, - STATUS, - REQUEST, -}; - -enum { - FIFO, - OUT_F, - STATIC, - FOLDER -}; - -static void setname(void *); -static void setstatusmsg(void *); -static void sendfriendreq(void *); - -static struct slot gslots[] = { - [NAME] = { .name = "name", .cb = setname, .outtype = STATIC }, - [STATUS] = { .name = "status", .cb = setstatusmsg, .outtype = STATIC }, - [REQUEST] = { .name = "request", .cb = sendfriendreq, .outtype = FOLDER, .outmode = 0755 }, -}; - -static struct file gfiles[] = { - { .type = FIFO, .name = "in", .flags = O_RDWR | O_NONBLOCK, .mode = 0644}, - { .type = OUT_F, .name = "out", .flags = O_WRONLY | O_TRUNC | O_CREAT, .mode = 0644}, - { .type = OUT_F, .name = "err", .flags = O_WRONLY | O_TRUNC | O_CREAT, .mode = 0644}, -}; - -enum { - TEXT_IN_FIFO, - FILE_IN_FIFO, - NR_FFIFOS -}; - -/* Friend related FIFOs, they go in <friend-id/{text,file}_in */ -static struct file ffifos[] = { - { .type = FIFO, .name = "text_in", .flags = O_RDWR | O_NONBLOCK, .mode = 0644 }, - { .type = FIFO, .name = "file_in", .flags = O_RDWR | O_NONBLOCK, .mode = 0644 }, -}; - -enum { - TRANSFER_NONE, - TRANSFER_INITIATED, - TRANSFER_INPROGRESS, - TRANSFER_DONE -}; - -struct transfer { - uint8_t fnum; - uint8_t *buf; - int chunksz; - ssize_t n; - int pending; - int state; -}; - -struct friend { - /* null terminated name */ - char namestr[TOX_MAX_NAME_LENGTH + 1]; - int fid; - uint8_t id[TOX_CLIENT_ID_SIZE]; - /* null terminated id */ - char idstr[2 * TOX_CLIENT_ID_SIZE + 1]; - int fd[NR_FFIFOS]; - struct transfer t; - TAILQ_ENTRY(friend) entry; -}; - -struct request { - uint8_t id[TOX_CLIENT_ID_SIZE]; - /* null terminated id */ - char idstr[2 * TOX_CLIENT_ID_SIZE + 1]; - /* null terminated friend request message */ - char *msgstr; - TAILQ_ENTRY(request) entry; -}; - -char *argv0; - -static TAILQ_HEAD(friendhead, friend) friendhead = TAILQ_HEAD_INITIALIZER(friendhead); -static TAILQ_HEAD(reqhead, request) reqhead = TAILQ_HEAD_INITIALIZER(reqhead); - -static Tox *tox; - -static void printrat(void); -static void cbconnstatus(Tox *, int32_t, uint8_t, void *); -static void cbfriendmessage(Tox *, int32_t, const uint8_t *, uint16_t, void *); -static void cbfriendrequest(Tox *, const uint8_t *, const uint8_t *, uint16_t, void *); -static void cbnamechange(Tox *, int32_t, const uint8_t *, uint16_t, void *); -static void cbstatusmessage(Tox *, int32_t, const uint8_t *, uint16_t, void *); -static void cbuserstatus(Tox *, int32_t, uint8_t, void *); -static void cbfilecontrol(Tox *, int32_t, uint8_t, uint8_t, uint8_t, const uint8_t *, uint16_t, void *); -static void sendfriendfile(struct friend *); -static void dataload(void); -static void datasave(void); -static int localinit(void); -static int toxinit(void); -static int toxconnect(void); -static void id2str(uint8_t *, char *); -static void str2id(char *, uint8_t *); -static struct friend *friendcreate(int32_t); -static void friendload(void); -static int cmdrun(void); -static int cmdaccept(char *, size_t); -static int cmdhelp(char *, size_t); -static void writeline(const char *, const char *, const char *, ...); -static void loop(void); - -static char qsep[] = " \t\r\n"; - -/* tokenization routines taken from Plan9 */ -static char * -qtoken(char *s, char *sep) -{ - int quoting; - char *t; - - quoting = 0; - t = s; /* s is output string, t is input string */ - while(*t!='\0' && (quoting || strchr(sep, *t)==NULL)) { - if(*t != '\'') { - *s++ = *t++; - continue; - } - /* *t is a quote */ - if(!quoting) { - quoting = 1; - t++; - continue; - } - /* quoting and we're on a quote */ - if(t[1] != '\'') { - /* end of quoted section; absorb closing quote */ - t++; - quoting = 0; - continue; - } - /* doubled quote; fold one quote into two */ - t++; - *s++ = *t++; - } - if(*s != '\0') { - *s = '\0'; - if(t == s) - t++; - } - return t; -} - -static int -tokenize(char *s, char **args, int maxargs) -{ - int nargs; - - for(nargs=0; nargs<maxargs; nargs++) { - while(*s!='\0' && strchr(qsep, *s)!=NULL) - s++; - if(*s == '\0') - break; - args[nargs] = s; - s = qtoken(s, qsep); - } - - return nargs; -} - -static void -printrat(void) -{ - printf("\033[31m"); - printf(" , .\n"); - printf(" (\\,;,/)\n"); - printf(" (o o)\\//,\n"); - printf(" \\ / \\,\n"); - printf(" `+'( ( \\ )\n"); - printf(" // \\ |_./\n"); - printf(" '~' '~----'\tratatox v"VERSION"\n"); - printf("\033[0m"); -} - -static void -printout(const char *fmt, ...) -{ - va_list ap; - char buft[64]; - time_t t; - - va_start(ap, fmt); - t = time(NULL); - strftime(buft, sizeof(buft), "%F %R", localtime(&t)); - printf("%s ", buft); - vfprintf(stdout, fmt, ap); - va_end(ap); -} - -static void -cbconnstatus(Tox *m, int32_t fid, uint8_t status, void *udata) -{ - struct friend *f; - uint8_t name[TOX_MAX_NAME_LENGTH + 1]; - char path[PATH_MAX]; - int r; - - r = tox_get_name(tox, fid, name); - if (r < 0) { - fprintf(stderr, "tox_get_name() on fid %d failed\n", fid); - exit(EXIT_FAILURE); - } - name[r] = '\0'; - - printout("%s %s\n", r == 0 ? (uint8_t *)"Anonymous" : name, - status == 0 ? "went offline" : "came online"); - - TAILQ_FOREACH(f, &friendhead, entry) { - if (f->fid == fid) { - snprintf(path, sizeof(path), "%s/online", f->idstr); - writeline(path, "w", status == 0 ? "0\n" : "1\n"); - return; - } - } - - friendcreate(fid); -} - -static void -cbfriendmessage(Tox *m, int32_t fid, const uint8_t *data, uint16_t len, void *udata) -{ - struct friend *f; - uint8_t msg[len + 1]; - char buft[64]; - char path[PATH_MAX]; - time_t t; - - memcpy(msg, data, len); - msg[len] = '\0'; - - TAILQ_FOREACH(f, &friendhead, entry) { - if (f->fid == fid) { - t = time(NULL); - strftime(buft, sizeof(buft), "%F %R", localtime(&t)); - snprintf(path, sizeof(path), "%s/text_out", f->idstr); - writeline(path, "a", "%s %s\n", buft, msg); - printout("%s %s\n", - f->namestr[0] == '\0' ? "Anonymous" : f->namestr, msg); - break; - } - } -} - -static void -cbfriendrequest(Tox *m, const uint8_t *id, const uint8_t *data, uint16_t len, void *udata) -{ - struct request *req; - - req = calloc(1, sizeof(*req)); - if (!req) { - perror("calloc"); - exit(EXIT_FAILURE); - } - memcpy(req->id, id, TOX_CLIENT_ID_SIZE); - id2str(req->id, req->idstr); - - if (len > 0) { - req->msgstr = malloc(len + 1); - if (!req->msgstr) { - perror("malloc"); - exit(EXIT_FAILURE); - } - memcpy(req->msgstr, data, len); - req->msgstr[len] = '\0'; - } - - TAILQ_INSERT_TAIL(&reqhead, req, entry); - - printout("Pending request from %s with message: %s\n", - req->idstr, req->msgstr); -} - -static void -cbnamechange(Tox *m, int32_t fid, const uint8_t *data, uint16_t len, void *user) -{ - struct friend *f; - uint8_t name[len + 1]; - char path[PATH_MAX]; - - memcpy(name, data, len); - name[len] = '\0'; - - TAILQ_FOREACH(f, &friendhead, entry) { - if (f->fid == fid) { - snprintf(path, sizeof(path), "%s/name", f->idstr); - writeline(path, "w", "%s\n", name); - if (memcmp(f->namestr, name, len + 1) == 0) - break; - printout("%s -> %s\n", f->namestr[0] == '\0' ? - "Anonymous" : f->namestr, name); - memcpy(f->namestr, name, len + 1); - break; - } - } - datasave(); -} - -static void -cbstatusmessage(Tox *m, int32_t fid, const uint8_t *data, uint16_t len, void *udata) -{ - struct friend *f; - uint8_t statusmsg[len + 1]; - char path[PATH_MAX]; - - memcpy(statusmsg, data, len); - statusmsg[len] = '\0'; - - TAILQ_FOREACH(f, &friendhead, entry) { - if (f->fid == fid) { - snprintf(path, sizeof(path), "%s/statusmsg", f->idstr); - writeline(path, "w", "%s\n", statusmsg); - printout("%s changed status: %s\n", - f->namestr[0] == '\0' ? "Anonymous" : f->namestr, statusmsg); - break; - } - } - datasave(); -} - -static void -cbuserstatus(Tox *m, int32_t fid, uint8_t status, void *udata) -{ - struct friend *f; - char *statusstr[] = { "none", "away", "busy" }; - - if (status >= LEN(statusstr)) { - fprintf(stderr, "received invalid user status: %d\n", status); - return; - } - - TAILQ_FOREACH(f, &friendhead, entry) { - if (f->fid == fid) { - printout("%s changed user status: %s\n", - f->namestr[0] == '\0' ? "Anonymous" : f->namestr, - statusstr[status]); - break; - } - } -} - -static void -cbfilecontrol(Tox *m, int32_t fid, uint8_t rec_sen, uint8_t fnum, uint8_t ctrltype, - const uint8_t *data, uint16_t len, void *udata) -{ - struct friend *f; - - switch (ctrltype) { - case TOX_FILECONTROL_ACCEPT: - if (rec_sen == 1) { - TAILQ_FOREACH(f, &friendhead, entry) { - if (f->fid != fid) - continue; - f->t.fnum = fnum; - f->t.chunksz = tox_file_data_size(tox, fnum); - f->t.buf = malloc(f->t.chunksz); - if (!f->t.buf) { - perror("malloc"); - exit(EXIT_FAILURE); - } - f->t.n = 0; - f->t.pending = 0; - f->t.state = TRANSFER_INPROGRESS; - break; - } - } - break; - case TOX_FILECONTROL_FINISHED: - if (rec_sen == 1) { - TAILQ_FOREACH(f, &friendhead, entry) { - if (f->fid != fid) - continue; - f->t.state = TRANSFER_DONE; - break; - } - } - break; - default: - fprintf(stderr, "Unhandled file control type: %d\n", ctrltype); - break; - }; -} - -static void -sendfriendfile(struct friend *f) -{ - ssize_t n; - - while (1) { - /* attempt to transmit the pending buffer */ - if (f->t.pending == 1) { - if (tox_file_send_data(tox, f->fid, f->t.fnum, f->t.buf, f->t.n) == -1) { - /* bad luck - we will try again later */ - break; - } - f->t.pending = 0; - } - /* grab another buffer from the FIFO */ - n = read(f->fd[FILE_IN_FIFO], f->t.buf, f->t.chunksz); - if (n < 0) { - if (errno == EINTR) - continue; - /* go back to select() until the fd is readable */ - if (errno == EWOULDBLOCK) - break; - perror("read"); - exit(EXIT_FAILURE); - } - /* we are done */ - if (n == 0) { - tox_file_send_control(tox, f->fid, 0, f->t.fnum, - TOX_FILECONTROL_FINISHED, NULL, 0); - f->t.state = TRANSFER_DONE; - break; - } - /* store transfer size in case we can't send it right now */ - f->t.n = n; - if (tox_file_send_data(tox, f->fid, f->t.fnum, f->t.buf, f->t.n) == -1) { - /* ok we will have to send it later, flip state */ - f->t.pending = 1; - return; - } - } -} - -static void -sendfriendtext(struct friend *f) -{ - uint8_t buf[TOX_MAX_MESSAGE_LENGTH]; - ssize_t n; - -again: - n = read(f->fd[TEXT_IN_FIFO], buf, sizeof(buf)); - if (n < 0) { - if (errno == EINTR) - goto again; - /* go back to select() until the fd is readable */ - if (errno == EWOULDBLOCK) - return; - perror("read"); - exit(EXIT_FAILURE); - } - if (buf[n - 1] == '\n') - n--; - tox_send_message(tox, f->fid, buf, n); -} - -static void -dataload(void) -{ - FILE *fp; - size_t sz; - uint8_t *data; - int r; - - fp = fopen(DATAFILE, "r"); - if (!fp) - return; - - fseek(fp, 0, SEEK_END); - sz = ftell(fp); - rewind(fp); - - data = malloc(sz); - if (!data) { - perror("malloc"); - exit(EXIT_FAILURE); - } - - if (fread(data, 1, sz, fp) != sz || ferror(fp)) { - fprintf(stderr, "failed to read %s\n", DATAFILE); - exit(EXIT_FAILURE); - } - r = tox_load(tox, data, sz); - if (r < 0) { - fprintf(stderr, "tox_load() failed\n"); - exit(EXIT_FAILURE); - } - if (r == 1) - printf("Found encrypted data in %s\n", DATAFILE); - - free(data); - fclose(fp); -} - -static void -datasave(void) -{ - FILE *fp; - size_t sz; - uint8_t *data; - - fp = fopen(DATAFILE, "w"); - if (!fp) { - fprintf(stderr, "can't open %s for writing\n", DATAFILE); - exit(EXIT_FAILURE); - } - - sz = tox_size(tox); - data = malloc(sz); - if (!data) { - perror("malloc"); - exit(EXIT_FAILURE); - } - - tox_save(tox, data); - if (fwrite(data, 1, sz, fp) != sz || ferror(fp)) { - fprintf(stderr, "failed to write %s\n", DATAFILE); - exit(EXIT_FAILURE); - } - - free(data); - fclose(fp); -} - -static int -localinit(void) -{ - uint8_t name[TOX_MAX_NAME_LENGTH + 1]; - uint8_t address[TOX_FRIEND_ADDRESS_SIZE]; - uint8_t statusmsg[TOX_MAX_STATUSMESSAGE_LENGTH + 1]; - FILE *fp; - int r; - size_t i, m; - - for (i = 0; i < LEN(gslots); i++) { - r = mkdir(gslots[i].name, 0755); - if (r < 0 && errno != EEXIST) { - perror("mkdir"); - exit(EXIT_FAILURE); - } - r = chdir(gslots[i].name); - if (r < 0) { - perror("chdir"); - exit(EXIT_FAILURE); - } - for (m = 0; m < LEN(gfiles); m++) { - if (gfiles[m].type == FIFO) { - r = mkfifo(gfiles[m].name, gfiles[m].mode); - if (r < 0 && errno != EEXIST) { - perror("mkfifo"); - exit(EXIT_FAILURE); - } - r = open(gfiles[m].name, gfiles[m].flags, 0); - if (r < 0) { - perror("open"); - exit(EXIT_FAILURE); - } - gslots[i].fd[m] = r; - } else if (gfiles[m].type == OUT_F) { - if (gslots[i].outtype == STATIC) { - r = open(gfiles[m].name, gfiles[m].flags, gfiles[m].mode); - if (r < 0) { - perror("open"); - exit(EXIT_FAILURE); - } - gslots[i].fd[m] = r; - } else if (gslots[i].outtype == FOLDER) { - r = mkdir(gfiles[m].name, gslots[i].outmode); - if (r < 0 && errno != EEXIST) { - perror("mkdir"); - exit(EXIT_FAILURE); - } - gslots[i].fd[m] = 0; - } - } - } - chdir(".."); - } - - /* Dump current name */ - r = tox_get_self_name(tox, name); - if (r > sizeof(name) - 1) - r = sizeof(name) - 1; - name[r] = '\0'; - ftruncate(gslots[NAME].fd[OUT], 0); - dprintf(gslots[NAME].fd[OUT], "%s\n", name); - - /* Dump status message */ - r = tox_get_self_status_message(tox, statusmsg, - sizeof(statusmsg) - 1); - if (r > sizeof(statusmsg) - 1) - r = sizeof(statusmsg) - 1; - statusmsg[r] = '\0'; - ftruncate(gslots[STATUS].fd[OUT], 0); - dprintf(gslots[STATUS].fd[OUT], "%s\n", name); - - /* Dump ID */ - fp = fopen("id", "w"); - if (!fp) { - perror("fopen"); - exit(EXIT_FAILURE); - } - tox_get_address(tox, address); - for (i = 0; i < TOX_FRIEND_ADDRESS_SIZE; i++) - fprintf(fp, "%02X", address[i]); - fputc('\n', fp); - fclose(fp); - - return 0; -} - -static int -toxinit(void) -{ - /* IPv4 only */ - tox = tox_new(0); - dataload(); - datasave(); - tox_callback_connection_status(tox, cbconnstatus, NULL); - tox_callback_friend_message(tox, cbfriendmessage, NULL); - tox_callback_friend_request(tox, cbfriendrequest, NULL); - tox_callback_name_change(tox, cbnamechange, NULL); - tox_callback_status_message(tox, cbstatusmessage, NULL); - tox_callback_user_status(tox, cbuserstatus, NULL); - tox_callback_file_control(tox, cbfilecontrol, NULL); - return 0; -} - -static int -toxconnect(void) -{ - struct node *bn; - size_t i; - - for (i = 0; i < LEN(nodes); i++) { - bn = &nodes[i]; - tox_bootstrap_from_address(tox, bn->addr, bn->port, bn->key); - } - return 0; -} - -static void -id2str(uint8_t *id, char *idstr) -{ - char hex[] = "0123456789ABCDEF"; - int i; - - for (i = 0; i < TOX_CLIENT_ID_SIZE; i++) { - *idstr++ = hex[(id[i] >> 4) & 0xf]; - *idstr++ = hex[id[i] & 0xf]; - } - *idstr = '\0'; -} - -static void -str2id(char *idstr, uint8_t *id) -{ - size_t i, len = strlen(idstr) / 2; - char *p = idstr; - - for (i = 0; i < len; ++i, p += 2) - sscanf(p, "%2hhx", &id[i]); -} - -static struct friend * -friendcreate(int32_t fid) -{ - char path[PATH_MAX]; - struct friend *f; - uint8_t statusmsg[TOX_MAX_STATUSMESSAGE_LENGTH + 1]; - size_t i; - int r; - - f = calloc(1, sizeof(*f)); - if (!f) { - perror("calloc"); - exit(EXIT_FAILURE); - } - - r = tox_get_name(tox, fid, (uint8_t *)f->namestr); - if (r < 0) { - fprintf(stderr, "tox_get_name() on fid %d failed\n", fid); - exit(EXIT_FAILURE); - } - f->namestr[r] = '\0'; - - f->fid = fid; - tox_get_client_id(tox, f->fid, f->id); - id2str(f->id, f->idstr); - - r = mkdir(f->idstr, 0755); - if (r < 0 && errno != EEXIST) { - perror("mkdir"); - exit(EXIT_FAILURE); - } - - for (i = 0; i < LEN(ffifos); i++) { - snprintf(path, sizeof(path), "%s/%s", f->idstr, - ffifos[i].name); - r = mkfifo(path, ffifos[i].mode); - if (r < 0 && errno != EEXIST) { - perror("mkfifo"); - exit(EXIT_FAILURE); - } - r = open(path, ffifos[i].flags, 0); - if (r < 0) { - perror("open"); - exit(EXIT_FAILURE); - } - f->fd[i] = r; - } - - snprintf(path, sizeof(path), "%s/name", f->idstr); - writeline(path, "w", "%s\n", f->namestr); - snprintf(path, sizeof(path), "%s/online", f->idstr); - writeline(path, "w", tox_get_friend_connection_status(tox, fid) == 0 ? "0\n" : "1\n"); - r = tox_get_status_message_size(tox, fid); - if (r > sizeof(statusmsg) - 1) - r = sizeof(statusmsg) - 1; - statusmsg[r] = '\0'; - snprintf(path, sizeof(path), "%s/statusmsg", f->idstr); - writeline(path, "w", "%s\n", statusmsg); - snprintf(path, sizeof(path), "%s/text_out", f->idstr); - writeline(path, "a", ""); - - TAILQ_INSERT_TAIL(&friendhead, f, entry); - - return f; -} - -static void -friendload(void) -{ - int32_t *fids; - uint32_t sz; - uint32_t i; - - sz = tox_count_friendlist(tox); - fids = malloc(sz); - if (!fids) { - perror("malloc"); - exit(EXIT_FAILURE); - } - - tox_get_friendlist(tox, fids, sz); - - for (i = 0; i < sz; i++) - friendcreate(fids[i]); - - free(fids); -} - -struct cmd { - const char *cmd; - int (*cb)(char *, size_t); - const char *usage; -} cmds[] = { - { .cmd = "a", .cb = cmdaccept, .usage = "usage: a [id]\tAccept or list pending requests\n" }, - { .cmd = "h", .cb = cmdhelp, .usage = NULL }, -}; - -static int -cmdaccept(char *cmd, size_t sz) -{ - struct request *req, *tmp; - char *args[2]; - int r; - int found = 0; - - r = tokenize(cmd, args, 2); - - if (r == 1) { - TAILQ_FOREACH(req, &reqhead, entry) { - printout("Pending request from %s with message: %s\n", - req->idstr, req->msgstr); - found = 1; - } - if (found == 0) - printf("No pending requests\n"); - } else { - for (req = TAILQ_FIRST(&reqhead); req; req = tmp) { - tmp = TAILQ_NEXT(req, entry); - if (strcmp(req->idstr, args[1]) == 0) { - tox_add_friend_norequest(tox, req->id); - printout("Accepted friend request for %s\n", req->idstr); - datasave(); - TAILQ_REMOVE(&reqhead, req, entry); - free(req->msgstr); - free(req); - break; - } - } - } - - return 0; -} - -static int -cmdhelp(char *cmd, size_t sz) -{ - size_t i; - - for (i = 0; i < LEN(cmds); i++) - if (cmds[i].usage) - fprintf(stderr, "%s", cmds[i].usage); - return 0; -} - -static int -cmdrun(void) -{ - char cmd[BUFSIZ]; - ssize_t n; - size_t i; - -again: - n = read(STDIN_FILENO, cmd, sizeof(cmd) - 1); - if (n < 0) { - if (errno == EINTR) - goto again; - perror("read"); - exit(EXIT_FAILURE); - } - if (n == 0) - return 0; - cmd[n] = '\0'; - if (cmd[strlen(cmd) - 1] == '\n') - cmd[strlen(cmd) - 1] = '\0'; - if (cmd[0] == '\0') - return 0; - - for (i = 0; i < LEN(cmds); i++) - if (cmd[0] == cmds[i].cmd[0]) - if (cmd[1] == '\0' || isspace((int)cmd[1])) - return (*cmds[i].cb)(cmd, strlen(cmd)); - - fprintf(stderr, "Unknown command '%s', type h for help\n", cmd); - return -1; -} - -static void -writeline(const char *path, const char *mode, - const char *fmt, ...) -{ - FILE *fp; - va_list ap; - - fp = fopen(path, mode); - if (!fp) { - perror("fopen"); - exit(EXIT_FAILURE); - } - va_start(ap, fmt); - vfprintf(fp, fmt, ap); - va_end(ap); - fclose(fp); -} - -static void -setname(void *data) -{ - uint8_t name[TOX_MAX_NAME_LENGTH + 1]; - int r; - -again: - r = read(gslots[NAME].fd[IN], name, sizeof(name) - 1); - if (r < 0) { - if (errno == EINTR) - goto again; - if (errno == EWOULDBLOCK) - return; - perror("read"); - return; - } - if (name[r - 1] == '\n') - r--; - name[r] = '\0'; - tox_set_name(tox, name, r); - datasave(); - printout("Changed name to %s\n", name); - ftruncate(gslots[NAME].fd[OUT], 0); - dprintf(gslots[NAME].fd[OUT], "%s\n", name); -} - -static void -setstatusmsg(void *data) -{ - uint8_t statusmsg[TOX_MAX_STATUSMESSAGE_LENGTH + 1]; - int r; - -again: - r = read(gslots[STATUS].fd[IN], statusmsg, sizeof(statusmsg) - 1); - if (r < 0) { - if (errno == EINTR) - goto again; - if (errno == EWOULDBLOCK) - return; - perror("read"); - return; - } - if (statusmsg[r - 1] == '\n') - r--; - statusmsg[r] = '\0'; - tox_set_status_message(tox, statusmsg, r); - datasave(); - printout("Changed status message to %s\n", statusmsg); - ftruncate(gslots[STATUS].fd[OUT], 0); - dprintf(gslots[STATUS].fd[OUT], "%s\n", statusmsg); -} - -static void -sendfriendreq(void *data) -{ - char *p; - uint8_t id[TOX_FRIEND_ADDRESS_SIZE]; - uint8_t buf[BUFSIZ], *msg = "ratatox is awesome!"; - int r; - -again: - r = read(gslots[REQUEST].fd[IN], buf, sizeof(buf) - 1); - if (r < 0) { - if (errno == EINTR) - goto again; - if (errno == EWOULDBLOCK) - return; - perror("read"); - return; - } - buf[r] = '\0'; - - for (p = buf; *p && isspace(*p) == 0; p++) - ; - if (*p != '\0') { - *p = '\0'; - while (isspace(*p++) != 0) - ; - if (*p != '\0') - msg = p; - if (msg[strlen(msg) - 1] == '\n') - msg[strlen(msg) - 1] = '\0'; - } - str2id(buf, id); - - r = tox_add_friend(tox, id, buf, strlen(buf)); - if (r < 0) - ftruncate(gslots[REQUEST].fd[ERR], 0); - switch (r) { - case TOX_FAERR_TOOLONG: - dprintf(gslots[REQUEST].fd[ERR], "Message is too long\n"); - break; - case TOX_FAERR_NOMESSAGE: - dprintf(gslots[REQUEST].fd[ERR], "Please add a message to your request\n"); - break; - case TOX_FAERR_OWNKEY: - dprintf(gslots[REQUEST].fd[ERR], "That appears to be your own ID\n"); - break; - case TOX_FAERR_ALREADYSENT: - dprintf(gslots[REQUEST].fd[ERR], "Friend request already sent\n"); - break; - case TOX_FAERR_UNKNOWN: - dprintf(gslots[REQUEST].fd[ERR], "Unknown error while sending your request\n"); - break; - case TOX_FAERR_BADCHECKSUM: - dprintf(gslots[REQUEST].fd[ERR], "Bad checksum while verifying address\n"); - break; - case TOX_FAERR_SETNEWNOSPAM: - dprintf(gslots[REQUEST].fd[ERR], "Friend already added but nospam doesn't match\n"); - break; - default: - printout("Friend request sent\n"); - break; - } - datasave(); -} - -static void -loop(void) -{ - struct friend *f; - time_t t0, t1; - int connected = 0; - int i, n; - int fdmax; - fd_set rfds; - struct timeval tv; - - t0 = time(NULL); - printout("Connecting to DHT...\n"); - toxconnect(); - while (1) { - if (tox_isconnected(tox) == 1) { - if (connected == 0) { - printout("Connected to DHT\n"); - connected = 1; - } - } else { - connected = 0; - t1 = time(NULL); - if (t1 > t0 + 5) { - t0 = time(NULL); - printout("Connecting to DHT...\n"); - toxconnect(); - } - } - tox_do(tox); - - FD_ZERO(&rfds); - FD_SET(STDIN_FILENO, &rfds); - fdmax = STDIN_FILENO; - - for (i = 0; i < LEN(gslots); i++) { - FD_SET(gslots[i].fd[IN], &rfds); - if (gslots[i].fd[IN] > fdmax) - fdmax = gslots[i].fd[IN]; - } - - TAILQ_FOREACH(f, &friendhead, entry) { - /* Only monitor friends that are online */ - if (tox_get_friend_connection_status(tox, f->fid) == 1) { - for (i = 0; i < NR_FFIFOS; i++) { - FD_SET(f->fd[i], &rfds); - if (f->fd[i] > fdmax) - fdmax = f->fd[i]; - } - } - } - - tv.tv_sec = 0; - tv.tv_usec = tox_do_interval(tox) * 1000; - n = select(fdmax + 1, &rfds, NULL, NULL, &tv); - if (n < 0) { - if (errno == EINTR) - continue; - perror("select"); - exit(EXIT_FAILURE); - } - - /* Check for broken transfers, i.e. the friend went offline - * in the middle of a transfer. - */ - TAILQ_FOREACH(f, &friendhead, entry) { - if (tox_get_friend_connection_status(tox, f->fid) == 0) { - if (f->t.state != TRANSFER_NONE) { - printout("Stale transfer detected, friend offline\n"); - f->t.state = TRANSFER_NONE; - free(f->t.buf); - } - } - } - - /* If we hit the receiver too hard, we will run out of - * local buffer slots. In that case tox_file_send_data() - * will return -1 and we will have to queue the buffer to - * send it later. If this is the last buffer read from - * the FIFO, then select() won't make the fd readable again - * so we have to check if there's anything pending to be - * sent. - */ - TAILQ_FOREACH(f, &friendhead, entry) { - if (tox_get_friend_connection_status(tox, f->fid) == 0) - continue; - if (f->t.state == TRANSFER_NONE) - continue; - if (f->t.pending == 0) - continue; - switch (f->t.state) { - case TRANSFER_INPROGRESS: - sendfriendfile(f); - if (f->t.state == TRANSFER_DONE) { - printout("Transfer complete\n"); - f->t.state = TRANSFER_NONE; - free(f->t.buf); - } - break; - } - } - - if (n == 0) - continue; - - if (FD_ISSET(STDIN_FILENO, &rfds) != 0) - cmdrun(); - - for (i = 0; i < LEN(gslots); i++) { - if (FD_ISSET(gslots[i].fd[IN], &rfds) == 0) - continue; - (*gslots[i].cb)(NULL); - } - - TAILQ_FOREACH(f, &friendhead, entry) { - for (i = 0; i < NR_FFIFOS; i++) { - if (FD_ISSET(f->fd[i], &rfds) == 0) - continue; - switch (i) { - case TEXT_IN_FIFO: - sendfriendtext(f); - break; - case FILE_IN_FIFO: - switch (f->t.state) { - case TRANSFER_NONE: - /* prepare a new transfer */ - f->t.state = TRANSFER_INITIATED; - tox_new_file_sender(tox, f->fid, - 0, (uint8_t *)"file", strlen("file") + 1); - printout("Initiated transfer to %s\n", - f->namestr[0] == '\0' ? "Anonymous" : f->namestr); - break; - case TRANSFER_INPROGRESS: - sendfriendfile(f); - if (f->t.state == TRANSFER_DONE) { - printout("Transfer complete\n"); - f->t.state = TRANSFER_NONE; - free(f->t.buf); - } - break; - } - break; - default: - fprintf(stderr, "Unhandled FIFO read\n"); - } - } - } - } -} - -int -main(int argc, char *argv[]) -{ - printrat(); - printf("Type h for help\n"); - toxinit(); - localinit(); - friendload(); - loop(); - return EXIT_SUCCESS; -} diff --git a/ratox.c b/ratox.c @@ -0,0 +1,1188 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/select.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stdarg.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#include <tox/tox.h> + +#include "arg.h" +#include "queue.h" + +#define LEN(x) (sizeof (x) / sizeof *(x)) +#define DATAFILE ".ratox.data" + +struct node { + const char *addr; + uint16_t port; + uint8_t key[TOX_CLIENT_ID_SIZE]; +}; + +#include "config.h" + +struct file { + int type; + const char *name; + int flags; + mode_t mode; +}; + +enum { + IN, + OUT, + ERR, + NR_GFILES +}; + +struct slot { + const char *name; + void (*cb)(void *); + int outtype; + int outmode; + int fd[NR_GFILES]; +}; + +enum { + NAME, + STATUS, + REQUEST, +}; + +enum { + FIFO, + OUT_F, + STATIC, + FOLDER +}; + +static void setname(void *); +static void setstatusmsg(void *); +static void sendfriendreq(void *); + +static struct slot gslots[] = { + [NAME] = { .name = "name", .cb = setname, .outtype = STATIC }, + [STATUS] = { .name = "status", .cb = setstatusmsg, .outtype = STATIC }, + [REQUEST] = { .name = "request", .cb = sendfriendreq, .outtype = FOLDER, .outmode = 0755 }, +}; + +static struct file gfiles[] = { + { .type = FIFO, .name = "in", .flags = O_RDWR | O_NONBLOCK, .mode = 0644}, + { .type = OUT_F, .name = "out", .flags = O_WRONLY | O_TRUNC | O_CREAT, .mode = 0644}, + { .type = OUT_F, .name = "err", .flags = O_WRONLY | O_TRUNC | O_CREAT, .mode = 0644}, +}; + +enum { + TEXT_IN_FIFO, + FILE_IN_FIFO, + NR_FFIFOS +}; + +/* Friend related FIFOs, they go in <friend-id/{text,file}_in */ +static struct file ffifos[] = { + { .type = FIFO, .name = "text_in", .flags = O_RDWR | O_NONBLOCK, .mode = 0644 }, + { .type = FIFO, .name = "file_in", .flags = O_RDWR | O_NONBLOCK, .mode = 0644 }, +}; + +enum { + TRANSFER_NONE, + TRANSFER_INITIATED, + TRANSFER_INPROGRESS, + TRANSFER_DONE +}; + +struct transfer { + uint8_t fnum; + uint8_t *buf; + int chunksz; + ssize_t n; + int pending; + int state; +}; + +struct friend { + /* null terminated name */ + char namestr[TOX_MAX_NAME_LENGTH + 1]; + int fid; + uint8_t id[TOX_CLIENT_ID_SIZE]; + /* null terminated id */ + char idstr[2 * TOX_CLIENT_ID_SIZE + 1]; + int fd[NR_FFIFOS]; + struct transfer t; + TAILQ_ENTRY(friend) entry; +}; + +struct request { + uint8_t id[TOX_CLIENT_ID_SIZE]; + /* null terminated id */ + char idstr[2 * TOX_CLIENT_ID_SIZE + 1]; + /* null terminated friend request message */ + char *msgstr; + TAILQ_ENTRY(request) entry; +}; + +char *argv0; + +static TAILQ_HEAD(friendhead, friend) friendhead = TAILQ_HEAD_INITIALIZER(friendhead); +static TAILQ_HEAD(reqhead, request) reqhead = TAILQ_HEAD_INITIALIZER(reqhead); + +static Tox *tox; + +static void printrat(void); +static void cbconnstatus(Tox *, int32_t, uint8_t, void *); +static void cbfriendmessage(Tox *, int32_t, const uint8_t *, uint16_t, void *); +static void cbfriendrequest(Tox *, const uint8_t *, const uint8_t *, uint16_t, void *); +static void cbnamechange(Tox *, int32_t, const uint8_t *, uint16_t, void *); +static void cbstatusmessage(Tox *, int32_t, const uint8_t *, uint16_t, void *); +static void cbuserstatus(Tox *, int32_t, uint8_t, void *); +static void cbfilecontrol(Tox *, int32_t, uint8_t, uint8_t, uint8_t, const uint8_t *, uint16_t, void *); +static void sendfriendfile(struct friend *); +static void dataload(void); +static void datasave(void); +static int localinit(void); +static int toxinit(void); +static int toxconnect(void); +static void id2str(uint8_t *, char *); +static void str2id(char *, uint8_t *); +static struct friend *friendcreate(int32_t); +static void friendload(void); +static int cmdrun(void); +static int cmdaccept(char *, size_t); +static int cmdhelp(char *, size_t); +static void writeline(const char *, const char *, const char *, ...); +static void loop(void); + +static char qsep[] = " \t\r\n"; + +/* tokenization routines taken from Plan9 */ +static char * +qtoken(char *s, char *sep) +{ + int quoting; + char *t; + + quoting = 0; + t = s; /* s is output string, t is input string */ + while(*t!='\0' && (quoting || strchr(sep, *t)==NULL)) { + if(*t != '\'') { + *s++ = *t++; + continue; + } + /* *t is a quote */ + if(!quoting) { + quoting = 1; + t++; + continue; + } + /* quoting and we're on a quote */ + if(t[1] != '\'') { + /* end of quoted section; absorb closing quote */ + t++; + quoting = 0; + continue; + } + /* doubled quote; fold one quote into two */ + t++; + *s++ = *t++; + } + if(*s != '\0') { + *s = '\0'; + if(t == s) + t++; + } + return t; +} + +static int +tokenize(char *s, char **args, int maxargs) +{ + int nargs; + + for(nargs=0; nargs<maxargs; nargs++) { + while(*s!='\0' && strchr(qsep, *s)!=NULL) + s++; + if(*s == '\0') + break; + args[nargs] = s; + s = qtoken(s, qsep); + } + + return nargs; +} + +static void +printrat(void) +{ + printf("\033[31m"); + printf(" , .\n"); + printf(" (\\,;,/)\n"); + printf(" (o o)\\//,\n"); + printf(" \\ / \\,\n"); + printf(" `+'( ( \\ )\n"); + printf(" // \\ |_./\n"); + printf(" '~' '~----'\tratox v"VERSION"\n"); + printf("\033[0m"); +} + +static void +printout(const char *fmt, ...) +{ + va_list ap; + char buft[64]; + time_t t; + + va_start(ap, fmt); + t = time(NULL); + strftime(buft, sizeof(buft), "%F %R", localtime(&t)); + printf("%s ", buft); + vfprintf(stdout, fmt, ap); + va_end(ap); +} + +static void +cbconnstatus(Tox *m, int32_t fid, uint8_t status, void *udata) +{ + struct friend *f; + uint8_t name[TOX_MAX_NAME_LENGTH + 1]; + char path[PATH_MAX]; + int r; + + r = tox_get_name(tox, fid, name); + if (r < 0) { + fprintf(stderr, "tox_get_name() on fid %d failed\n", fid); + exit(EXIT_FAILURE); + } + name[r] = '\0'; + + printout("%s %s\n", r == 0 ? (uint8_t *)"Anonymous" : name, + status == 0 ? "went offline" : "came online"); + + TAILQ_FOREACH(f, &friendhead, entry) { + if (f->fid == fid) { + snprintf(path, sizeof(path), "%s/online", f->idstr); + writeline(path, "w", status == 0 ? "0\n" : "1\n"); + return; + } + } + + friendcreate(fid); +} + +static void +cbfriendmessage(Tox *m, int32_t fid, const uint8_t *data, uint16_t len, void *udata) +{ + struct friend *f; + uint8_t msg[len + 1]; + char buft[64]; + char path[PATH_MAX]; + time_t t; + + memcpy(msg, data, len); + msg[len] = '\0'; + + TAILQ_FOREACH(f, &friendhead, entry) { + if (f->fid == fid) { + t = time(NULL); + strftime(buft, sizeof(buft), "%F %R", localtime(&t)); + snprintf(path, sizeof(path), "%s/text_out", f->idstr); + writeline(path, "a", "%s %s\n", buft, msg); + printout("%s %s\n", + f->namestr[0] == '\0' ? "Anonymous" : f->namestr, msg); + break; + } + } +} + +static void +cbfriendrequest(Tox *m, const uint8_t *id, const uint8_t *data, uint16_t len, void *udata) +{ + struct request *req; + + req = calloc(1, sizeof(*req)); + if (!req) { + perror("calloc"); + exit(EXIT_FAILURE); + } + memcpy(req->id, id, TOX_CLIENT_ID_SIZE); + id2str(req->id, req->idstr); + + if (len > 0) { + req->msgstr = malloc(len + 1); + if (!req->msgstr) { + perror("malloc"); + exit(EXIT_FAILURE); + } + memcpy(req->msgstr, data, len); + req->msgstr[len] = '\0'; + } + + TAILQ_INSERT_TAIL(&reqhead, req, entry); + + printout("Pending request from %s with message: %s\n", + req->idstr, req->msgstr); +} + +static void +cbnamechange(Tox *m, int32_t fid, const uint8_t *data, uint16_t len, void *user) +{ + struct friend *f; + uint8_t name[len + 1]; + char path[PATH_MAX]; + + memcpy(name, data, len); + name[len] = '\0'; + + TAILQ_FOREACH(f, &friendhead, entry) { + if (f->fid == fid) { + snprintf(path, sizeof(path), "%s/name", f->idstr); + writeline(path, "w", "%s\n", name); + if (memcmp(f->namestr, name, len + 1) == 0) + break; + printout("%s -> %s\n", f->namestr[0] == '\0' ? + "Anonymous" : f->namestr, name); + memcpy(f->namestr, name, len + 1); + break; + } + } + datasave(); +} + +static void +cbstatusmessage(Tox *m, int32_t fid, const uint8_t *data, uint16_t len, void *udata) +{ + struct friend *f; + uint8_t statusmsg[len + 1]; + char path[PATH_MAX]; + + memcpy(statusmsg, data, len); + statusmsg[len] = '\0'; + + TAILQ_FOREACH(f, &friendhead, entry) { + if (f->fid == fid) { + snprintf(path, sizeof(path), "%s/statusmsg", f->idstr); + writeline(path, "w", "%s\n", statusmsg); + printout("%s changed status: %s\n", + f->namestr[0] == '\0' ? "Anonymous" : f->namestr, statusmsg); + break; + } + } + datasave(); +} + +static void +cbuserstatus(Tox *m, int32_t fid, uint8_t status, void *udata) +{ + struct friend *f; + char *statusstr[] = { "none", "away", "busy" }; + + if (status >= LEN(statusstr)) { + fprintf(stderr, "received invalid user status: %d\n", status); + return; + } + + TAILQ_FOREACH(f, &friendhead, entry) { + if (f->fid == fid) { + printout("%s changed user status: %s\n", + f->namestr[0] == '\0' ? "Anonymous" : f->namestr, + statusstr[status]); + break; + } + } +} + +static void +cbfilecontrol(Tox *m, int32_t fid, uint8_t rec_sen, uint8_t fnum, uint8_t ctrltype, + const uint8_t *data, uint16_t len, void *udata) +{ + struct friend *f; + + switch (ctrltype) { + case TOX_FILECONTROL_ACCEPT: + if (rec_sen == 1) { + TAILQ_FOREACH(f, &friendhead, entry) { + if (f->fid != fid) + continue; + f->t.fnum = fnum; + f->t.chunksz = tox_file_data_size(tox, fnum); + f->t.buf = malloc(f->t.chunksz); + if (!f->t.buf) { + perror("malloc"); + exit(EXIT_FAILURE); + } + f->t.n = 0; + f->t.pending = 0; + f->t.state = TRANSFER_INPROGRESS; + break; + } + } + break; + case TOX_FILECONTROL_FINISHED: + if (rec_sen == 1) { + TAILQ_FOREACH(f, &friendhead, entry) { + if (f->fid != fid) + continue; + f->t.state = TRANSFER_DONE; + break; + } + } + break; + default: + fprintf(stderr, "Unhandled file control type: %d\n", ctrltype); + break; + }; +} + +static void +sendfriendfile(struct friend *f) +{ + ssize_t n; + + while (1) { + /* attempt to transmit the pending buffer */ + if (f->t.pending == 1) { + if (tox_file_send_data(tox, f->fid, f->t.fnum, f->t.buf, f->t.n) == -1) { + /* bad luck - we will try again later */ + break; + } + f->t.pending = 0; + } + /* grab another buffer from the FIFO */ + n = read(f->fd[FILE_IN_FIFO], f->t.buf, f->t.chunksz); + if (n < 0) { + if (errno == EINTR) + continue; + /* go back to select() until the fd is readable */ + if (errno == EWOULDBLOCK) + break; + perror("read"); + exit(EXIT_FAILURE); + } + /* we are done */ + if (n == 0) { + tox_file_send_control(tox, f->fid, 0, f->t.fnum, + TOX_FILECONTROL_FINISHED, NULL, 0); + f->t.state = TRANSFER_DONE; + break; + } + /* store transfer size in case we can't send it right now */ + f->t.n = n; + if (tox_file_send_data(tox, f->fid, f->t.fnum, f->t.buf, f->t.n) == -1) { + /* ok we will have to send it later, flip state */ + f->t.pending = 1; + return; + } + } +} + +static void +sendfriendtext(struct friend *f) +{ + uint8_t buf[TOX_MAX_MESSAGE_LENGTH]; + ssize_t n; + +again: + n = read(f->fd[TEXT_IN_FIFO], buf, sizeof(buf)); + if (n < 0) { + if (errno == EINTR) + goto again; + /* go back to select() until the fd is readable */ + if (errno == EWOULDBLOCK) + return; + perror("read"); + exit(EXIT_FAILURE); + } + if (buf[n - 1] == '\n') + n--; + tox_send_message(tox, f->fid, buf, n); +} + +static void +dataload(void) +{ + FILE *fp; + size_t sz; + uint8_t *data; + int r; + + fp = fopen(DATAFILE, "r"); + if (!fp) + return; + + fseek(fp, 0, SEEK_END); + sz = ftell(fp); + rewind(fp); + + data = malloc(sz); + if (!data) { + perror("malloc"); + exit(EXIT_FAILURE); + } + + if (fread(data, 1, sz, fp) != sz || ferror(fp)) { + fprintf(stderr, "failed to read %s\n", DATAFILE); + exit(EXIT_FAILURE); + } + r = tox_load(tox, data, sz); + if (r < 0) { + fprintf(stderr, "tox_load() failed\n"); + exit(EXIT_FAILURE); + } + if (r == 1) + printf("Found encrypted data in %s\n", DATAFILE); + + free(data); + fclose(fp); +} + +static void +datasave(void) +{ + FILE *fp; + size_t sz; + uint8_t *data; + + fp = fopen(DATAFILE, "w"); + if (!fp) { + fprintf(stderr, "can't open %s for writing\n", DATAFILE); + exit(EXIT_FAILURE); + } + + sz = tox_size(tox); + data = malloc(sz); + if (!data) { + perror("malloc"); + exit(EXIT_FAILURE); + } + + tox_save(tox, data); + if (fwrite(data, 1, sz, fp) != sz || ferror(fp)) { + fprintf(stderr, "failed to write %s\n", DATAFILE); + exit(EXIT_FAILURE); + } + + free(data); + fclose(fp); +} + +static int +localinit(void) +{ + uint8_t name[TOX_MAX_NAME_LENGTH + 1]; + uint8_t address[TOX_FRIEND_ADDRESS_SIZE]; + uint8_t statusmsg[TOX_MAX_STATUSMESSAGE_LENGTH + 1]; + FILE *fp; + int r; + size_t i, m; + + for (i = 0; i < LEN(gslots); i++) { + r = mkdir(gslots[i].name, 0755); + if (r < 0 && errno != EEXIST) { + perror("mkdir"); + exit(EXIT_FAILURE); + } + r = chdir(gslots[i].name); + if (r < 0) { + perror("chdir"); + exit(EXIT_FAILURE); + } + for (m = 0; m < LEN(gfiles); m++) { + if (gfiles[m].type == FIFO) { + r = mkfifo(gfiles[m].name, gfiles[m].mode); + if (r < 0 && errno != EEXIST) { + perror("mkfifo"); + exit(EXIT_FAILURE); + } + r = open(gfiles[m].name, gfiles[m].flags, 0); + if (r < 0) { + perror("open"); + exit(EXIT_FAILURE); + } + gslots[i].fd[m] = r; + } else if (gfiles[m].type == OUT_F) { + if (gslots[i].outtype == STATIC) { + r = open(gfiles[m].name, gfiles[m].flags, gfiles[m].mode); + if (r < 0) { + perror("open"); + exit(EXIT_FAILURE); + } + gslots[i].fd[m] = r; + } else if (gslots[i].outtype == FOLDER) { + r = mkdir(gfiles[m].name, gslots[i].outmode); + if (r < 0 && errno != EEXIST) { + perror("mkdir"); + exit(EXIT_FAILURE); + } + gslots[i].fd[m] = 0; + } + } + } + chdir(".."); + } + + /* Dump current name */ + r = tox_get_self_name(tox, name); + if (r > sizeof(name) - 1) + r = sizeof(name) - 1; + name[r] = '\0'; + ftruncate(gslots[NAME].fd[OUT], 0); + dprintf(gslots[NAME].fd[OUT], "%s\n", name); + + /* Dump status message */ + r = tox_get_self_status_message(tox, statusmsg, + sizeof(statusmsg) - 1); + if (r > sizeof(statusmsg) - 1) + r = sizeof(statusmsg) - 1; + statusmsg[r] = '\0'; + ftruncate(gslots[STATUS].fd[OUT], 0); + dprintf(gslots[STATUS].fd[OUT], "%s\n", name); + + /* Dump ID */ + fp = fopen("id", "w"); + if (!fp) { + perror("fopen"); + exit(EXIT_FAILURE); + } + tox_get_address(tox, address); + for (i = 0; i < TOX_FRIEND_ADDRESS_SIZE; i++) + fprintf(fp, "%02X", address[i]); + fputc('\n', fp); + fclose(fp); + + return 0; +} + +static int +toxinit(void) +{ + /* IPv4 only */ + tox = tox_new(0); + dataload(); + datasave(); + tox_callback_connection_status(tox, cbconnstatus, NULL); + tox_callback_friend_message(tox, cbfriendmessage, NULL); + tox_callback_friend_request(tox, cbfriendrequest, NULL); + tox_callback_name_change(tox, cbnamechange, NULL); + tox_callback_status_message(tox, cbstatusmessage, NULL); + tox_callback_user_status(tox, cbuserstatus, NULL); + tox_callback_file_control(tox, cbfilecontrol, NULL); + return 0; +} + +static int +toxconnect(void) +{ + struct node *bn; + size_t i; + + for (i = 0; i < LEN(nodes); i++) { + bn = &nodes[i]; + tox_bootstrap_from_address(tox, bn->addr, bn->port, bn->key); + } + return 0; +} + +static void +id2str(uint8_t *id, char *idstr) +{ + char hex[] = "0123456789ABCDEF"; + int i; + + for (i = 0; i < TOX_CLIENT_ID_SIZE; i++) { + *idstr++ = hex[(id[i] >> 4) & 0xf]; + *idstr++ = hex[id[i] & 0xf]; + } + *idstr = '\0'; +} + +static void +str2id(char *idstr, uint8_t *id) +{ + size_t i, len = strlen(idstr) / 2; + char *p = idstr; + + for (i = 0; i < len; ++i, p += 2) + sscanf(p, "%2hhx", &id[i]); +} + +static struct friend * +friendcreate(int32_t fid) +{ + char path[PATH_MAX]; + struct friend *f; + uint8_t statusmsg[TOX_MAX_STATUSMESSAGE_LENGTH + 1]; + size_t i; + int r; + + f = calloc(1, sizeof(*f)); + if (!f) { + perror("calloc"); + exit(EXIT_FAILURE); + } + + r = tox_get_name(tox, fid, (uint8_t *)f->namestr); + if (r < 0) { + fprintf(stderr, "tox_get_name() on fid %d failed\n", fid); + exit(EXIT_FAILURE); + } + f->namestr[r] = '\0'; + + f->fid = fid; + tox_get_client_id(tox, f->fid, f->id); + id2str(f->id, f->idstr); + + r = mkdir(f->idstr, 0755); + if (r < 0 && errno != EEXIST) { + perror("mkdir"); + exit(EXIT_FAILURE); + } + + for (i = 0; i < LEN(ffifos); i++) { + snprintf(path, sizeof(path), "%s/%s", f->idstr, + ffifos[i].name); + r = mkfifo(path, ffifos[i].mode); + if (r < 0 && errno != EEXIST) { + perror("mkfifo"); + exit(EXIT_FAILURE); + } + r = open(path, ffifos[i].flags, 0); + if (r < 0) { + perror("open"); + exit(EXIT_FAILURE); + } + f->fd[i] = r; + } + + snprintf(path, sizeof(path), "%s/name", f->idstr); + writeline(path, "w", "%s\n", f->namestr); + snprintf(path, sizeof(path), "%s/online", f->idstr); + writeline(path, "w", tox_get_friend_connection_status(tox, fid) == 0 ? "0\n" : "1\n"); + r = tox_get_status_message_size(tox, fid); + if (r > sizeof(statusmsg) - 1) + r = sizeof(statusmsg) - 1; + statusmsg[r] = '\0'; + snprintf(path, sizeof(path), "%s/statusmsg", f->idstr); + writeline(path, "w", "%s\n", statusmsg); + snprintf(path, sizeof(path), "%s/text_out", f->idstr); + writeline(path, "a", ""); + + TAILQ_INSERT_TAIL(&friendhead, f, entry); + + return f; +} + +static void +friendload(void) +{ + int32_t *fids; + uint32_t sz; + uint32_t i; + + sz = tox_count_friendlist(tox); + fids = malloc(sz); + if (!fids) { + perror("malloc"); + exit(EXIT_FAILURE); + } + + tox_get_friendlist(tox, fids, sz); + + for (i = 0; i < sz; i++) + friendcreate(fids[i]); + + free(fids); +} + +struct cmd { + const char *cmd; + int (*cb)(char *, size_t); + const char *usage; +} cmds[] = { + { .cmd = "a", .cb = cmdaccept, .usage = "usage: a [id]\tAccept or list pending requests\n" }, + { .cmd = "h", .cb = cmdhelp, .usage = NULL }, +}; + +static int +cmdaccept(char *cmd, size_t sz) +{ + struct request *req, *tmp; + char *args[2]; + int r; + int found = 0; + + r = tokenize(cmd, args, 2); + + if (r == 1) { + TAILQ_FOREACH(req, &reqhead, entry) { + printout("Pending request from %s with message: %s\n", + req->idstr, req->msgstr); + found = 1; + } + if (found == 0) + printf("No pending requests\n"); + } else { + for (req = TAILQ_FIRST(&reqhead); req; req = tmp) { + tmp = TAILQ_NEXT(req, entry); + if (strcmp(req->idstr, args[1]) == 0) { + tox_add_friend_norequest(tox, req->id); + printout("Accepted friend request for %s\n", req->idstr); + datasave(); + TAILQ_REMOVE(&reqhead, req, entry); + free(req->msgstr); + free(req); + break; + } + } + } + + return 0; +} + +static int +cmdhelp(char *cmd, size_t sz) +{ + size_t i; + + for (i = 0; i < LEN(cmds); i++) + if (cmds[i].usage) + fprintf(stderr, "%s", cmds[i].usage); + return 0; +} + +static int +cmdrun(void) +{ + char cmd[BUFSIZ]; + ssize_t n; + size_t i; + +again: + n = read(STDIN_FILENO, cmd, sizeof(cmd) - 1); + if (n < 0) { + if (errno == EINTR) + goto again; + perror("read"); + exit(EXIT_FAILURE); + } + if (n == 0) + return 0; + cmd[n] = '\0'; + if (cmd[strlen(cmd) - 1] == '\n') + cmd[strlen(cmd) - 1] = '\0'; + if (cmd[0] == '\0') + return 0; + + for (i = 0; i < LEN(cmds); i++) + if (cmd[0] == cmds[i].cmd[0]) + if (cmd[1] == '\0' || isspace((int)cmd[1])) + return (*cmds[i].cb)(cmd, strlen(cmd)); + + fprintf(stderr, "Unknown command '%s', type h for help\n", cmd); + return -1; +} + +static void +writeline(const char *path, const char *mode, + const char *fmt, ...) +{ + FILE *fp; + va_list ap; + + fp = fopen(path, mode); + if (!fp) { + perror("fopen"); + exit(EXIT_FAILURE); + } + va_start(ap, fmt); + vfprintf(fp, fmt, ap); + va_end(ap); + fclose(fp); +} + +static void +setname(void *data) +{ + uint8_t name[TOX_MAX_NAME_LENGTH + 1]; + int r; + +again: + r = read(gslots[NAME].fd[IN], name, sizeof(name) - 1); + if (r < 0) { + if (errno == EINTR) + goto again; + if (errno == EWOULDBLOCK) + return; + perror("read"); + return; + } + if (name[r - 1] == '\n') + r--; + name[r] = '\0'; + tox_set_name(tox, name, r); + datasave(); + printout("Changed name to %s\n", name); + ftruncate(gslots[NAME].fd[OUT], 0); + dprintf(gslots[NAME].fd[OUT], "%s\n", name); +} + +static void +setstatusmsg(void *data) +{ + uint8_t statusmsg[TOX_MAX_STATUSMESSAGE_LENGTH + 1]; + int r; + +again: + r = read(gslots[STATUS].fd[IN], statusmsg, sizeof(statusmsg) - 1); + if (r < 0) { + if (errno == EINTR) + goto again; + if (errno == EWOULDBLOCK) + return; + perror("read"); + return; + } + if (statusmsg[r - 1] == '\n') + r--; + statusmsg[r] = '\0'; + tox_set_status_message(tox, statusmsg, r); + datasave(); + printout("Changed status message to %s\n", statusmsg); + ftruncate(gslots[STATUS].fd[OUT], 0); + dprintf(gslots[STATUS].fd[OUT], "%s\n", statusmsg); +} + +static void +sendfriendreq(void *data) +{ + char *p; + uint8_t id[TOX_FRIEND_ADDRESS_SIZE]; + uint8_t buf[BUFSIZ], *msg = "ratox is awesome!"; + int r; + +again: + r = read(gslots[REQUEST].fd[IN], buf, sizeof(buf) - 1); + if (r < 0) { + if (errno == EINTR) + goto again; + if (errno == EWOULDBLOCK) + return; + perror("read"); + return; + } + buf[r] = '\0'; + + for (p = buf; *p && isspace(*p) == 0; p++) + ; + if (*p != '\0') { + *p = '\0'; + while (isspace(*p++) != 0) + ; + if (*p != '\0') + msg = p; + if (msg[strlen(msg) - 1] == '\n') + msg[strlen(msg) - 1] = '\0'; + } + str2id(buf, id); + + r = tox_add_friend(tox, id, buf, strlen(buf)); + if (r < 0) + ftruncate(gslots[REQUEST].fd[ERR], 0); + switch (r) { + case TOX_FAERR_TOOLONG: + dprintf(gslots[REQUEST].fd[ERR], "Message is too long\n"); + break; + case TOX_FAERR_NOMESSAGE: + dprintf(gslots[REQUEST].fd[ERR], "Please add a message to your request\n"); + break; + case TOX_FAERR_OWNKEY: + dprintf(gslots[REQUEST].fd[ERR], "That appears to be your own ID\n"); + break; + case TOX_FAERR_ALREADYSENT: + dprintf(gslots[REQUEST].fd[ERR], "Friend request already sent\n"); + break; + case TOX_FAERR_UNKNOWN: + dprintf(gslots[REQUEST].fd[ERR], "Unknown error while sending your request\n"); + break; + case TOX_FAERR_BADCHECKSUM: + dprintf(gslots[REQUEST].fd[ERR], "Bad checksum while verifying address\n"); + break; + case TOX_FAERR_SETNEWNOSPAM: + dprintf(gslots[REQUEST].fd[ERR], "Friend already added but nospam doesn't match\n"); + break; + default: + printout("Friend request sent\n"); + break; + } + datasave(); +} + +static void +loop(void) +{ + struct friend *f; + time_t t0, t1; + int connected = 0; + int i, n; + int fdmax; + fd_set rfds; + struct timeval tv; + + t0 = time(NULL); + printout("Connecting to DHT...\n"); + toxconnect(); + while (1) { + if (tox_isconnected(tox) == 1) { + if (connected == 0) { + printout("Connected to DHT\n"); + connected = 1; + } + } else { + connected = 0; + t1 = time(NULL); + if (t1 > t0 + 5) { + t0 = time(NULL); + printout("Connecting to DHT...\n"); + toxconnect(); + } + } + tox_do(tox); + + FD_ZERO(&rfds); + FD_SET(STDIN_FILENO, &rfds); + fdmax = STDIN_FILENO; + + for (i = 0; i < LEN(gslots); i++) { + FD_SET(gslots[i].fd[IN], &rfds); + if (gslots[i].fd[IN] > fdmax) + fdmax = gslots[i].fd[IN]; + } + + TAILQ_FOREACH(f, &friendhead, entry) { + /* Only monitor friends that are online */ + if (tox_get_friend_connection_status(tox, f->fid) == 1) { + for (i = 0; i < NR_FFIFOS; i++) { + FD_SET(f->fd[i], &rfds); + if (f->fd[i] > fdmax) + fdmax = f->fd[i]; + } + } + } + + tv.tv_sec = 0; + tv.tv_usec = tox_do_interval(tox) * 1000; + n = select(fdmax + 1, &rfds, NULL, NULL, &tv); + if (n < 0) { + if (errno == EINTR) + continue; + perror("select"); + exit(EXIT_FAILURE); + } + + /* Check for broken transfers, i.e. the friend went offline + * in the middle of a transfer. + */ + TAILQ_FOREACH(f, &friendhead, entry) { + if (tox_get_friend_connection_status(tox, f->fid) == 0) { + if (f->t.state != TRANSFER_NONE) { + printout("Stale transfer detected, friend offline\n"); + f->t.state = TRANSFER_NONE; + free(f->t.buf); + } + } + } + + /* If we hit the receiver too hard, we will run out of + * local buffer slots. In that case tox_file_send_data() + * will return -1 and we will have to queue the buffer to + * send it later. If this is the last buffer read from + * the FIFO, then select() won't make the fd readable again + * so we have to check if there's anything pending to be + * sent. + */ + TAILQ_FOREACH(f, &friendhead, entry) { + if (tox_get_friend_connection_status(tox, f->fid) == 0) + continue; + if (f->t.state == TRANSFER_NONE) + continue; + if (f->t.pending == 0) + continue; + switch (f->t.state) { + case TRANSFER_INPROGRESS: + sendfriendfile(f); + if (f->t.state == TRANSFER_DONE) { + printout("Transfer complete\n"); + f->t.state = TRANSFER_NONE; + free(f->t.buf); + } + break; + } + } + + if (n == 0) + continue; + + if (FD_ISSET(STDIN_FILENO, &rfds) != 0) + cmdrun(); + + for (i = 0; i < LEN(gslots); i++) { + if (FD_ISSET(gslots[i].fd[IN], &rfds) == 0) + continue; + (*gslots[i].cb)(NULL); + } + + TAILQ_FOREACH(f, &friendhead, entry) { + for (i = 0; i < NR_FFIFOS; i++) { + if (FD_ISSET(f->fd[i], &rfds) == 0) + continue; + switch (i) { + case TEXT_IN_FIFO: + sendfriendtext(f); + break; + case FILE_IN_FIFO: + switch (f->t.state) { + case TRANSFER_NONE: + /* prepare a new transfer */ + f->t.state = TRANSFER_INITIATED; + tox_new_file_sender(tox, f->fid, + 0, (uint8_t *)"file", strlen("file") + 1); + printout("Initiated transfer to %s\n", + f->namestr[0] == '\0' ? "Anonymous" : f->namestr); + break; + case TRANSFER_INPROGRESS: + sendfriendfile(f); + if (f->t.state == TRANSFER_DONE) { + printout("Transfer complete\n"); + f->t.state = TRANSFER_NONE; + free(f->t.buf); + } + break; + } + break; + default: + fprintf(stderr, "Unhandled FIFO read\n"); + } + } + } + } +} + +int +main(int argc, char *argv[]) +{ + printrat(); + printf("Type h for help\n"); + toxinit(); + localinit(); + friendload(); + loop(); + return EXIT_SUCCESS; +}