summaryrefslogtreecommitdiff
path: root/ratox.c
diff options
context:
space:
mode:
Diffstat (limited to 'ratox.c')
-rw-r--r--ratox.c1188
1 files changed, 1188 insertions, 0 deletions
diff --git a/ratox.c b/ratox.c
new file mode 100644
index 0000000..e6ecdf2
--- /dev/null
+++ 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;
+}