commit e0cd01eef6b8f2d83e2ca3f4d87b015f2305b7db
parent 3c390f75591337768bcd95f585d41ebdc822fb1b
Author: sin <sin@2f30.org>
Date: Wed, 17 Sep 2014 19:50:59 +0100
Rename ratatox to ratox
Diffstat:
M | Makefile | | | 4 | ++-- |
M | config.mk | | | 2 | +- |
D | ratatox.c | | | 1188 | ------------------------------------------------------------------------------- |
A | 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;
+}