tMerge branch 'toktok'. - ratox - FIFO based tox client
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit d10df26852b6db53156e1f892e29239435c66299
 (DIR) parent 799e250bbc9cf89bb09b7cfa4afbc4dbb9a46657
 (HTM) Author: pranomostro <pranomestro@gmail.com>
       Date:   Fri,  3 Mar 2017 15:39:30 +0100
       
       Merge branch 'toktok'.
       
       This merge updates ratox to toktoks toxcore, which is being developed, and
       adds groupchats (although lacking audio group chats at the moment).
       
       Diffstat:
         M README                              |      13 ++++++++++++-
         M config.def.h                        |       3 +++
         M nodes.h                             |      60 +++++++++++++++++--------------
         M ratox.1                             |      24 ++++++++++++++++++++++++
         M ratox.c                             |     500 +++++++++++++++++++++++++++++--
       
       5 files changed, 539 insertions(+), 61 deletions(-)
       ---
 (DIR) diff --git a/README b/README
       t@@ -53,6 +53,15 @@ to help explain the semantics of the individual files.
        |   |-- text_in                        # 'echo yo dude > text_in' to send a text to this friend
        |   `-- text_out                # 'tail -f text_out' to dump to stdout any text received
        |
       +|-- 00000000
       +|   |-- members                 # list of people in the conference
       +|   |-- invite                  # 'echo 0A734CBA717CEB7883D.... >invite' to invite
       +|   |-- leave                   # 'echo 1 >leave' to leave the conference
       +|   |-- title_in                # 'echo new-title >title_in' to update the conference title
       +|   |-- title_out               # contains the current title
       +|   |-- text_in                 # 'echo blablahumbla >text_in' to message the other conference members
       +|   |-- text_out                # contains the messages sent so far in the conference
       +|
        |-- id                                # 'cat id' to show your own ID, you can give this to your friends
        |
        |-- name                        # changing your nick
       t@@ -86,7 +95,7 @@ Features
        
        1 v 1 messaging: Yes
        File transfer: Yes
       -Group chat: No
       +Group chat: Yes
        Audio: Yes
        Video: No
        DNS discovery: No
       t@@ -115,6 +124,8 @@ NOTE: Some of these features are not intended to be developed
        in ratox itself but rather in external scripts[1] that are built upon
        ratox.
        
       +Group chats do not have audio yet.
       +
        
        Examples
        ========
 (DIR) diff --git a/config.def.h b/config.def.h
       t@@ -20,6 +20,9 @@
        #define VIDEOHEIGHT       720
        #define VIDEOBITRATE      2500
        
       +static int   friendmsg_log = 1;
       +static int   confmsg_log   = 0;
       +
        static char *savefile        = ".ratox.tox";
        static int   encryptsavefile = 0;
        
 (DIR) diff --git a/nodes.h b/nodes.h
       t@@ -1,11 +1,5 @@
        static struct node nodes[] = {
                {
       -                .addr4 = "tox.zodiaclabs.org",
       -                .addr6 = "v6.tox.zodiaclabs.org",
       -                .port = 33445,
       -                .idstr = "A09162D68618E742FFBCA1C2C70385E6679604B2D80EA6E84AD0996A1AC8A074"
       -        },
       -        {
                        .addr4 = "biribiri.org",
                        .addr6 = NULL,
                        .port = 33445,
       t@@ -36,12 +30,6 @@ static struct node nodes[] = {
                        .idstr = "1D5A5F2F5D6233058BF0259B09622FB40B482E4FA0931EB8FD3AB8E7BF7DAF6F"
                },
                {
       -                .addr4 = "108.61.165.198",
       -                .addr6 = "2001:19f0:5000:8121:5054:ff:fe1c:9ded",
       -                .port = 33445,
       -                .idstr = "8E7D0B859922EF569298B4D261A8CCB5FEA14FB91ED412A7603A585A25698832"
       -        },
       -        {
                        .addr4 = "194.249.212.109",
                        .addr6 = "2001:1470:fbfe::109",
                        .port = 33445,
       t@@ -132,6 +120,12 @@ static struct node nodes[] = {
                        .idstr = "1A56EA3EDF5DF4C0AEABBF3C2E4E603890F87E983CAC8A0D532A335F2C6E3E1F"
                },
                {
       +                .addr4 = "109.75.40.105",
       +                .addr6 = "2001:470:70d6::1",
       +                .port = 33445,
       +                .idstr = "2B9CD794424FD579044EC2FC5252B23DF8B4AAF239C25074F70B1090C3F8C83A"
       +        },
       +        {
                        .addr4 = "toxnode.nek0.net",
                        .addr6 = "toxnode.nek0.net",
                        .port = 33445,
       t@@ -198,6 +192,12 @@ static struct node nodes[] = {
                        .idstr = "D3EB45181B343C2C222A5BCF72B760638E15ED87904625AAD351C594EEFAE03E"
                },
                {
       +                .addr4 = "shigure.eve.moe",
       +                .addr6 = "shigure.eve.moe",
       +                .port = 33445,
       +                .idstr = "1A480A53FAF2CBBFCC382D786C43E69EEE23F22C7C23A7CFEB6180A373E23E54"
       +        },
       +        {
                        .addr4 = "tox.deadteam.org",
                        .addr6 = "tox.deadteam.org",
                        .port = 33445,
       t@@ -222,10 +222,10 @@ static struct node nodes[] = {
                        .idstr = "2555763C8C460495B14157D234DD56B86300A2395554BCAE4621AC345B8C1B1B"
                },
                {
       -                .addr4 = "77.37.160.178",
       +                .addr4 = "77.37.142.179",
                        .addr6 = NULL,
       -                .port = 33440,
       -                .idstr = "CE678DEAFA29182EFD1B0C5B9BC6999E5A20B50A1A6EC18B91C8EBB591712416"
       +                .port = 33445,
       +                .idstr = "98F5830A426C6BF165F895F04B897AFC4F57331B4BE0561F583C9F323194227B"
                },
                {
                        .addr4 = "85.21.144.224",
       t@@ -246,10 +246,16 @@ static struct node nodes[] = {
                        .idstr = "BEB71F97ED9C99C04B8489BB75579EB4DC6AB6F441B603D63533122F1858B51D"
                },
                {
       -                .addr4 = "202.36.75.162",
       -                .addr6 = NULL,
       +                .addr4 = "plfgr.eu.org",
       +                .addr6 = "plfgr.eu.org",
                        .port = 33445,
       -                .idstr = "F202E0936ABEE09067F55B0955C3FF6A84ABEED3C750A9EB930D926D03248F4C"
       +                .idstr = "F5A2E533EC720927FA970F508D949D5958F37889F039F50C905010244842656E"
       +        },
       +        {
       +                .addr4 = "completelyunoriginal.moe",
       +                .addr6 = "completelyunoriginal.moe",
       +                .port = 33445,
       +                .idstr = "FBC7DED0B0B662D81094D91CC312D6CDF12A7B16C7FFB93817143116B510C13E"
                },
                {
                        .addr4 = "46.101.197.175",
       t@@ -258,21 +264,21 @@ static struct node nodes[] = {
                        .idstr = "CD133B521159541FB1D326DE9850F5E56A6C724B5B8E5EB5CD8D950408E95707"
                },
                {
       -                .addr4 = "shigure.eve.moe",
       -                .addr6 = "shigure.eve.moe",
       +                .addr4 = "tox.zodiaclabs.org",
       +                .addr6 = "v6.tox.zodiaclabs.org",
                        .port = 33445,
       -                .idstr = "1A480A53FAF2CBBFCC382D786C43E69EEE23F22C7C23A7CFEB6180A373E23E54"
       +                .idstr = "A09162D68618E742FFBCA1C2C70385E6679604B2D80EA6E84AD0996A1AC8A074"
                },
                {
       -                .addr4 = "81.4.110.149",
       -                .addr6 = "2a00:d880:3:2::8bdc:f19",
       +                .addr4 = "108.61.165.198",
       +                .addr6 = "2001:19f0:5000:8121:5054:ff:fe1c:9ded",
                        .port = 33445,
       -                .idstr = "9E7BD4793FFECA7F32238FA2361040C09025ED3333744483CA6F3039BFF0211E"
       +                .idstr = "8E7D0B859922EF569298B4D261A8CCB5FEA14FB91ED412A7603A585A25698832"
                },
                {
       -                .addr4 = "192.99.168.140",
       +                .addr4 = "77.37.160.178",
                        .addr6 = NULL,
       -                .port = 33445,
       -                .idstr = "6A4D0607A296838434A6A7DDF99F50EF9D60A2C510BBF31FE538A25CB6B4652F"
       +                .port = 33440,
       +                .idstr = "CE678DEAFA29182EFD1B0C5B9BC6999E5A20B50A1A6EC18B91C8EBB591712416"
                }
        };
 (DIR) diff --git a/ratox.1 b/ratox.1
       t@@ -58,6 +58,10 @@ Status message slot.
        Request slot. Send a friend request by piping the Tox ID to \fBin\fR.  Incoming
        requests are listed as FIFOs in \fBout/\fR. Echo \fB1\fR | \fB0\fR to
        accept | reject them.
       +.It Ar conf/
       +Conference management slot. A conference is created by writing it's title to in. Invites
       +to conferences are FIFOs in \fBout/\fR. Their name is id_cookie (the cookie is random data).
       +They behave like request FIFOs.
        .El
        .Ss Friend slots
        Each friend is represented with a folder in the base-directory named after
       t@@ -107,6 +111,26 @@ Send a text message by piping data to this FIFO.
        .It Ar text_out
        Contains text messages from the friend.
        .El
       +.Ss Conference slots
       +Each conference is represented with a folder in the directory named after the
       +8-digit conference number. The files in the conference folder are an interface
       +for the respective conference.
       +.Bl -tag -width 13n
       +.It Ar members
       +Contains a list of  members of the conference.
       +.It Ar invite
       +Write the ID of a friend to this FIFO to invite him to the conference.
       +.It Ar leave
       +Write to this file to leave the conference.
       +.It Ar title_in
       +Write here to change the title of the conference.
       +.It Ar title_out
       +Contains the title of the conference.
       +.It Ar text_in
       +Echo \fBmessage\fR to send a text message to the conference.
       +.It Ar text_out
       +Contains the messages send in the conference so far.
       +.El
        .Ss Misc files
        .Bl -tag -width 13n
        .It Ar id
 (DIR) diff --git a/ratox.c b/ratox.c
       t@@ -3,8 +3,6 @@
        #include <sys/stat.h>
        #include <sys/types.h>
        
       -#include <arpa/inet.h>
       -
        #include <ctype.h>
        #include <dirent.h>
        #include <errno.h>
       t@@ -88,8 +86,9 @@ static void setstatus(void *);
        static void setuserstate(void *);
        static void sendfriendreq(void *);
        static void setnospam(void *);
       +static void newconf(void *);
        
       -enum { NAME, STATUS, STATE, REQUEST, NOSPAM };
       +enum { NAME, STATUS, STATE, REQUEST, NOSPAM, CONF };
        
        static struct slot gslots[] = {
                [NAME]    = { .name = "name",         .cb = setname,              .outisfolder = 0, .dirfd = -1, .fd = {-1, -1, -1} },
       t@@ -97,6 +96,7 @@ static struct slot gslots[] = {
                [STATE]   = { .name = "state",         .cb = setuserstate,  .outisfolder = 0, .dirfd = -1, .fd = {-1, -1, -1} },
                [REQUEST] = { .name = "request", .cb = sendfriendreq, .outisfolder = 1, .dirfd = -1, .fd = {-1, -1, -1} },
                [NOSPAM]  = { .name = "nospam",         .cb = setnospam,     .outisfolder = 0, .dirfd = -1, .fd = {-1, -1, -1} },
       +        [CONF]    = { .name = "conf",    .cb = newconf,       .outisfolder = 1, .dirfd = -1, .fd = {-1, -1, -1} },
        };
        
        enum { FTEXT_IN, FFILE_IN, FCALL_IN, FTEXT_OUT, FFILE_OUT, FCALL_OUT,
       t@@ -118,6 +118,18 @@ static struct file ffiles[] = {
                [FCALL_STATE] = { .type = STATIC, .name = "call_state",          .flags = O_WRONLY | O_TRUNC  | O_CREAT },
        };
        
       +enum { CMEMBERS, CINVITE, CLEAVE, CTITLE_IN, CTITLE_OUT, CTEXT_IN, CTEXT_OUT };
       +
       +static struct file cfiles[] = {
       +        [CMEMBERS]    = { .type = STATIC, .name = "members",      .flags = O_WRONLY | O_TRUNC  | O_CREAT },
       +        [CINVITE]     = { .type = FIFO,          .name = "invite",          .flags = O_RDONLY | O_NONBLOCK         },
       +        [CLEAVE]      = { .type = FIFO,   .name = "leave",          .flags = O_RDONLY | O_NONBLOCK         },
       +        [CTITLE_IN]   = { .type = FIFO,   .name = "title_in",          .flags = O_RDONLY | O_NONBLOCK         },
       +        [CTITLE_OUT]  = { .type = STATIC, .name = "title_out",          .flags = O_WRONLY | O_TRUNC  | O_CREAT },
       +        [CTEXT_IN]    = { .type = FIFO,          .name = "text_in",          .flags = O_RDONLY | O_NONBLOCK         },
       +        [CTEXT_OUT]   = { .type = STATIC, .name = "text_out",          .flags = O_WRONLY | O_APPEND | O_CREAT },
       +};
       +
        static char *ustate[] = {
                [TOX_USER_STATUS_NONE]    = "available",
                [TOX_USER_STATUS_AWAY]    = "away",
       t@@ -162,6 +174,14 @@ struct friend {
                TAILQ_ENTRY(friend) entry;
        };
        
       +struct conference {
       +        uint32_t num;
       +        char     numstr[2 * sizeof(uint32_t) + 1];
       +        int      dirfd;
       +        int      fd[LEN(cfiles)];
       +        TAILQ_ENTRY(conference) entry;
       +};
       +
        struct request {
                uint8_t id[TOX_PUBLIC_KEY_SIZE];
                char    idstr[2 * TOX_PUBLIC_KEY_SIZE + 1];
       t@@ -170,8 +190,19 @@ struct request {
                TAILQ_ENTRY(request) entry;
        };
        
       +struct invite {
       +        char         *fifoname;
       +        uint8_t        *cookie;
       +        size_t         cookielen;
       +        uint32_t inviter;
       +        int         fd;
       +        TAILQ_ENTRY(invite) entry;
       +};
       +
        static TAILQ_HEAD(friendhead, friend) friendhead = TAILQ_HEAD_INITIALIZER(friendhead);
       +static TAILQ_HEAD(confhead, conference) confhead = TAILQ_HEAD_INITIALIZER(confhead);
        static TAILQ_HEAD(reqhead, request) reqhead = TAILQ_HEAD_INITIALIZER(reqhead);
       +static TAILQ_HEAD(invhead, invite) invhead = TAILQ_HEAD_INITIALIZER(invhead);
        
        static Tox *tox;
        static ToxAV *toxav;
       t@@ -197,6 +228,7 @@ static void cbcalldata(ToxAV *, uint32_t, const int16_t *, size_t, uint8_t, uint
        
        static void cancelcall(struct friend *, char *);
        static void sendfriendcalldata(struct friend *);
       +static void writemembers(struct conference *);
        
        static void cbconnstatus(Tox *, uint32_t, TOX_CONNECTION, void *);
        static void cbfriendmessage(Tox *, uint32_t, TOX_MESSAGE_TYPE, const uint8_t *, size_t, void *);
       t@@ -208,10 +240,18 @@ static void cbfilecontrol(Tox *, uint32_t, uint32_t, TOX_FILE_CONTROL, void *);
        static void cbfilesendreq(Tox *, uint32_t, uint32_t, uint32_t, uint64_t, const uint8_t *, size_t, void *);
        static void cbfiledata(Tox *, uint32_t, uint32_t, uint64_t, const uint8_t *, size_t, void *);
        
       +static void cbconfinvite(Tox *, uint32_t, TOX_CONFERENCE_TYPE, const uint8_t *, size_t, void *);
       +static void cbconfmessage(Tox *, uint32_t, uint32_t, TOX_MESSAGE_TYPE, const uint8_t *, size_t, void *);
       +static void cbconftitle(Tox *, uint32_t, uint32_t, const uint8_t *, size_t, void *);
       +static void cbconfmembers(Tox *, uint32_t, uint32_t, TOX_CONFERENCE_STATE_CHANGE, void *);
       +
        static void canceltxtransfer(struct friend *);
        static void cancelrxtransfer(struct friend *);
        static void sendfriendtext(struct friend *);
        static void removefriend(struct friend *);
       +static void invitefriend(struct conference *);
       +static void sendconftext(struct conference *);
       +static void updatetitle(struct conference *);
        static int readpass(const char *, uint8_t **, uint32_t *);
        static void dataload(struct Tox_Options *);
        static void datasave(void);
       t@@ -221,8 +261,10 @@ static int toxconnect(void);
        static void id2str(uint8_t *, char *);
        static void str2id(char *, uint8_t *);
        static void friendcreate(uint32_t);
       +static void confcreate(uint32_t);
        static void friendload(void);
        static void frienddestroy(struct friend *);
       +static void confdestroy(struct conference *);
        static void loop(void);
        static void initshutdown(int);
        static void toxshutdown(void);
       t@@ -444,6 +486,122 @@ cbcalldata(ToxAV *av, uint32_t fnum, const int16_t *data, size_t len,
        }
        
        static void
       +cbconfinvite(Tox *m, uint32_t frnum, TOX_CONFERENCE_TYPE type, const uint8_t *cookie, size_t clen, void * udata)
       +{
       +        size_t i, j, namelen;
       +        struct file invfifo;
       +        struct invite *inv;
       +        uint8_t id[TOX_PUBLIC_KEY_SIZE];
       +
       +        if(type != TOX_CONFERENCE_TYPE_TEXT) {
       +                weprintf(": %d : Only text conference supported at the moment\n");
       +                return;
       +        }
       +
       +        if (!tox_friend_get_public_key(tox, frnum, id, NULL)) {
       +                weprintf(": %d : Key: Failed to get for invite\n", frnum);
       +                return;
       +        }
       +
       +        inv = calloc(1, sizeof(*inv));
       +        if (!inv)
       +                eprintf("calloc:");
       +        inv->fd = -1;
       +
       +        inv->inviter = frnum;
       +        inv->cookielen = clen;
       +        inv->cookie = malloc(inv->cookielen);
       +        if (!inv->cookie)
       +                eprintf("malloc:");
       +
       +        memcpy(inv->cookie, cookie, clen);
       +
       +        namelen = 2 * TOX_PUBLIC_KEY_SIZE + 1 + 2 * clen + 2;
       +        inv->fifoname = malloc(namelen);
       +        if (!inv->fifoname)
       +                eprintf("malloc:");
       +
       +        i = 0;
       +        id2str(id, inv->fifoname);
       +        i += 2 * TOX_PUBLIC_KEY_SIZE;
       +        inv->fifoname[i] = '_';
       +        i++;
       +        for(j = 0; j < clen; i+=2, j++)
       +                sprintf(inv->fifoname + i, "%02X", cookie[j]);
       +        i++;
       +        inv->fifoname[i] = '\0';
       +
       +        invfifo.name = inv->fifoname;
       +        invfifo.flags = O_RDONLY | O_NONBLOCK;
       +        fiforeset(gslots[CONF].fd[OUT], &inv->fd, invfifo);
       +
       +        TAILQ_INSERT_TAIL(&invhead, inv, entry);
       +
       +        logmsg("Invite > %s\n", inv->fifoname);
       +}
       +
       +static void
       +cbconfmessage(Tox *m, uint32_t cnum, uint32_t pnum, TOX_MESSAGE_TYPE type, const uint8_t *data, size_t len, void *udata)
       +{
       +        struct  conference *c;
       +        time_t  t;
       +        uint8_t msg[len + 1], namt[TOX_MAX_NAME_LENGTH + 1];
       +        char    buft[64];
       +
       +        memcpy(msg, data, len);
       +        msg[len] = '\0';
       +
       +        TAILQ_FOREACH(c, &confhead, entry) {
       +                if (c->num == cnum) {
       +                        t = time(NULL);
       +                        strftime(buft, sizeof(buft), "%F %R", localtime(&t));
       +                        if (!tox_conference_peer_get_name(tox, c->num, pnum, namt, NULL)) {
       +                                weprintf("Unable to obtain name for peer %d in conference %s\n", pnum, c->numstr);
       +                                return;
       +                        }
       +                        namt[tox_conference_peer_get_name_size(tox, c->num, pnum, NULL)] = '\0';
       +                        dprintf(c->fd[CTEXT_OUT], "%s <%s> %s\n", buft, namt, msg);
       +                        if (confmsg_log)
       +                                logmsg("%s: %s <%s> %s\n", c->numstr, buft, namt, msg);
       +                        break;
       +                }
       +        }
       +}
       +
       +static void
       +cbconftitle(Tox *m, uint32_t cnum, uint32_t pnum, const uint8_t *data, size_t len, void * udata)
       +{
       +        struct  conference *c;
       +        char    title[TOX_MAX_NAME_LENGTH + 1];
       +
       +        memcpy(title, data, len);
       +        title[len] = '\0';
       +
       +        TAILQ_FOREACH(c, &confhead, entry) {
       +                if (c->num == cnum) {
       +                        ftruncate(c->fd[CTITLE_OUT], 0);
       +                        lseek(c->fd[CTITLE_OUT], 0, SEEK_SET);
       +                        dprintf(c->fd[CTITLE_OUT], "%s\n", title);
       +                        logmsg(": %s : Title > %s\n", c->numstr, title);
       +                        break;
       +                }
       +        }
       +}
       +
       +static void
       +cbconfmembers(Tox *m, uint32_t cnum, uint32_t pnum, TOX_CONFERENCE_STATE_CHANGE type, void *udata)
       +{
       +        struct  conference *c;
       +
       +        TAILQ_FOREACH(c, &confhead, entry) {
       +                if (c->num == cnum) {
       +                        writemembers(c);
       +                        break;
       +                }
       +        }
       +}
       +
       +static void
        cancelcall(struct friend *f, char *action)
        {
                logmsg(": %s : Audio > %s\n", f->name, action);
       t@@ -508,6 +666,33 @@ sendfriendcalldata(struct friend *f)
        }
        
        static void
       +writemembers(struct conference *c)
       +{
       +        size_t i;
       +        uint32_t peers, pnum;
       +        uint8_t name[TOX_MAX_NAME_LENGTH + 1];
       +        TOX_ERR_CONFERENCE_PEER_QUERY err;
       +
       +        /*The peer list is written when we invite the members by the callback*/
       +        ftruncate(c->fd[CMEMBERS], 0);
       +        peers = tox_conference_peer_count(tox, c->num, &err);
       +
       +        if (err != TOX_ERR_CONFERENCE_PEER_QUERY_OK) {
       +                weprintf("Unable to obtain peer count for conference %d\n", c->num);
       +                return;
       +        }
       +        for (pnum = 0; pnum < peers; pnum++) {
       +                if (!tox_conference_peer_get_name(tox, c->num, pnum, name, NULL)) {
       +                        weprintf("Unable to obtain the name for peer %d\n", pnum);
       +                } else {
       +                        i = tox_conference_peer_get_name_size(tox, c->num, pnum, NULL);
       +                        name[i] = '\0';
       +                        dprintf(c->fd[CMEMBERS], "%s\n", name);
       +                }
       +        }
       +}
       +
       +static void
        cbconnstatus(Tox *m, uint32_t frnum, TOX_CONNECTION status, void *udata)
        {
                struct friend *f;
       t@@ -568,7 +753,8 @@ cbfriendmessage(Tox *m, uint32_t frnum, TOX_MESSAGE_TYPE type, const uint8_t *da
                                t = time(NULL);
                                strftime(buft, sizeof(buft), "%F %R", localtime(&t));
                                dprintf(f->fd[FTEXT_OUT], "%s %s\n", buft, msg);
       -                        logmsg(": %s > %s\n", f->name, msg);
       +                        if (friendmsg_log)
       +                                logmsg(": %s > %s\n", f->name, msg);
                                break;
                        }
                }
       t@@ -811,7 +997,8 @@ cbfilesendreq(Tox *m, uint32_t frnum, uint32_t fnum, uint32_t kind, uint64_t fsz
        }
        
        static void
       -cbfiledata(Tox *m, uint32_t frnum, uint32_t fnum, uint64_t pos, const uint8_t *data, size_t len, void *udata)
       +cbfiledata(Tox *m, uint32_t frnum, uint32_t fnum, uint64_t pos,
       +           const uint8_t *data, size_t len, void *udata)
        {
                struct   friend *f;
                ssize_t  n;
       t@@ -913,6 +1100,72 @@ removefriend(struct friend *f)
                frienddestroy(f);
        }
        
       +static void
       +invitefriend(struct conference *c)
       +{
       +        ssize_t n;
       +        char buf[2 * TOX_ADDRESS_SIZE + 1];
       +        struct friend *f;
       +
       +        n = fiforead(c->dirfd, &c->fd[CINVITE], cfiles[CINVITE], buf, sizeof(buf));
       +
       +        if (n > sizeof(buf) || n <= 0)
       +                return;
       +        if (buf[n - 1] == '\n')
       +                buf[n - 1] = '\0';
       +
       +        TAILQ_FOREACH(f, &friendhead, entry)
       +                if (!memcmp(buf, f->idstr, sizeof(f->idstr)))
       +                        break;
       +        if (!f) {
       +                logmsg("Conference %s > no friend with id %s found\n", c->numstr, buf);
       +                return;
       +        }
       +        if (tox_friend_get_connection_status(tox, f->num, NULL) == TOX_CONNECTION_NONE) {
       +                logmsg("Conference %s > %s not online, can't be invited\n", c->numstr, buf);
       +                return;
       +        }
       +        if (!tox_conference_invite(tox, f->num, c->num, NULL))
       +                weprintf("Failed to invite %s\n", buf);
       +        else
       +                logmsg("Conference %s > Invite %s\n", c->numstr, buf);
       +}
       +
       +static void
       +sendconftext(struct conference *c)
       +{
       +        ssize_t n;
       +        uint8_t buf[TOX_MAX_MESSAGE_LENGTH];
       +
       +        n = fiforead(c->dirfd, &c->fd[CTEXT_IN], cfiles[CTEXT_IN], buf, sizeof(buf));
       +        if (n <= 0)
       +                return;
       +        if (buf[n - 1] == '\n' && n > 1)
       +                n--;
       +        if (!tox_conference_send_message(tox, c->num, TOX_MESSAGE_TYPE_NORMAL,
       +            buf, n, NULL))
       +                weprintf("%s: Message : Failed to send, error %d\n", c->numstr);
       +}
       +
       +static void
       +updatetitle(struct conference *c)
       +{
       +        ssize_t n;
       +        uint8_t title[TOX_MAX_STATUS_MESSAGE_LENGTH + 1];
       +
       +        n = fiforead(c->dirfd, &c->fd[CTITLE_IN], cfiles[CTITLE_IN], title, sizeof(title) - 1);
       +        if (n <= 0)
       +                return;
       +        if (title[n - 1] == '\n')
       +                n--;
       +        title[n] = '\0';
       +        if (!tox_conference_set_title(tox, c->num, title, n, NULL)) {
       +                weprintf("%s : Title : Failed to set to \"%s\"\n", title, c->numstr);
       +                return;
       +        }
       +        logmsg("Conference %s > Title > %s\n", c->numstr, title);
       +}
       +
        static int
        readpass(const char *prompt, uint8_t **target, uint32_t *len)
        {
       t@@ -1120,7 +1373,7 @@ localinit(void)
        
                /* Dump Nospam */
                ftruncate(gslots[NOSPAM].fd[OUT], 0);
       -        dprintf(gslots[NOSPAM].fd[OUT], "%08X\n", ntohl(tox_self_get_nospam(tox)));
       +        dprintf(gslots[NOSPAM].fd[OUT], "%08X\n", tox_self_get_nospam(tox));
        
                return 0;
        }
       t@@ -1158,22 +1411,27 @@ toxinit(void)
        
                framesize = (AUDIOSAMPLERATE * AUDIOFRAME * AUDIOCHANNELS) / 1000;
        
       -        tox_callback_friend_connection_status(tox, cbconnstatus, NULL);
       -        tox_callback_friend_message(tox, cbfriendmessage, NULL);
       -        tox_callback_friend_request(tox, cbfriendrequest, NULL);
       -        tox_callback_friend_name(tox, cbnamechange, NULL);
       -        tox_callback_friend_status_message(tox, cbstatusmessage, NULL);
       -        tox_callback_friend_status(tox, cbfriendstate, NULL);
       -        tox_callback_file_recv_control(tox, cbfilecontrol, NULL);
       -        tox_callback_file_recv(tox, cbfilesendreq, NULL);
       -        tox_callback_file_recv_chunk(tox, cbfiledata, NULL);
       -        tox_callback_file_chunk_request(tox, cbfiledatareq, NULL);
       +        tox_callback_friend_connection_status(tox, cbconnstatus);
       +        tox_callback_friend_message(tox, cbfriendmessage);
       +        tox_callback_friend_request(tox, cbfriendrequest);
       +        tox_callback_friend_name(tox, cbnamechange);
       +        tox_callback_friend_status_message(tox, cbstatusmessage);
       +        tox_callback_friend_status(tox, cbfriendstate);
       +        tox_callback_file_recv_control(tox, cbfilecontrol);
       +        tox_callback_file_recv(tox, cbfilesendreq);
       +        tox_callback_file_recv_chunk(tox, cbfiledata);
       +        tox_callback_file_chunk_request(tox, cbfiledatareq);
        
                toxav_callback_call(toxav, cbcallinvite, NULL);
                toxav_callback_call_state(toxav, cbcallstate, NULL);
        
                toxav_callback_audio_receive_frame(toxav, cbcalldata, NULL);
        
       +        tox_callback_conference_invite(tox, cbconfinvite);
       +        tox_callback_conference_message(tox, cbconfmessage);
       +        tox_callback_conference_title(tox, cbconftitle);
       +        tox_callback_conference_namelist_change(tox, cbconfmembers);
       +
                if (toxopt.savedata_data)
                        free((void *)toxopt.savedata_data);
        
       t@@ -1326,6 +1584,64 @@ friendcreate(uint32_t frnum)
        }
        
        static void
       +confcreate(uint32_t cnum)
       +{
       +        struct conference *c;
       +        DIR         *d;
       +        size_t         i;
       +        int         r;
       +        uint8_t         title[TOX_MAX_NAME_LENGTH + 1];
       +        TOX_ERR_CONFERENCE_TITLE err;
       +
       +        c = calloc(1, sizeof(*c));
       +        if(!c)
       +                eprintf("calloc:");
       +        c->num = cnum;
       +        sprintf(c->numstr, "%08X", c->num);
       +        r = mkdir(c->numstr, 0777);
       +        if(r < 0 && errno != EEXIST)
       +                eprintf("mkdir %s:", c->numstr);
       +
       +        d = opendir(c->numstr);
       +        if (!d)
       +                eprintf("opendir %s:", c->numstr);
       +
       +        r = dirfd(d);
       +        if (r < 0)
       +                eprintf("dirfd %s:", c->numstr);
       +        c->dirfd = r;
       +
       +        for (i = 0; i < LEN(cfiles); i++) {
       +                c->fd[i] = -1;
       +                if (cfiles[i].type == FIFO) {
       +                        fiforeset(c->dirfd, &c->fd[i], cfiles[i]);
       +                } else if (cfiles[i].type == STATIC) {
       +                        c->fd[i] = fifoopen(c->dirfd, cfiles[i]);
       +                }
       +        }
       +
       +        writemembers(c);
       +
       +        /* No warning is printed here in the case of an error
       +         * because this always fails when joining after an invite,
       +         * but cbconftitle() is called in the next iteration afterwards,
       +         * so it doesn't matter after all.
       +         */
       +
       +        i = tox_conference_get_title_size(tox, c->num, &err);
       +        if (err != TOX_ERR_CONFERENCE_TITLE_OK)
       +                i = 0;
       +        tox_conference_get_title(tox, c->num, title, NULL);
       +        title[i] = '\0';
       +        ftruncate(c->fd[CTITLE_OUT], 0);
       +        dprintf(c->fd[CTITLE_OUT], "%s\n", title);
       +
       +        TAILQ_INSERT_TAIL(&confhead, c, entry);
       +
       +        logmsg("Conference %s > Created\n", c->numstr);
       +}
       +
       +static void
        frienddestroy(struct friend *f)
        {
                size_t i;
       t@@ -1346,6 +1662,22 @@ frienddestroy(struct friend *f)
        }
        
        static void
       +confdestroy(struct conference *c)
       +{
       +        size_t i;
       +
       +        for (i = 0; i <LEN(cfiles); i++) {
       +                if(c->dirfd != -1) {
       +                        unlinkat(c->dirfd, cfiles[i].name, 0);
       +                        if (c->fd[i] != -1)
       +                                close(c->fd[i]);
       +                }
       +        }
       +        rmdir(c->numstr);
       +        TAILQ_REMOVE(&confhead, c, entry);
       +}
       +
       +static void
        friendload(void)
        {
                size_t sz;
       t@@ -1526,7 +1858,7 @@ setnospam(void *data)
                }
        
                nsval = strtoul((char *)nospam, NULL, 16);
       -        tox_self_set_nospam(tox, htonl(nsval));
       +        tox_self_set_nospam(tox, nsval);
                datasave();
                logmsg("Nospam > %08X\n", nsval);
                ftruncate(gslots[NOSPAM].fd[OUT], 0);
       t@@ -1544,18 +1876,44 @@ end:
        }
        
        static void
       +newconf(void *data)
       +{
       +        uint32_t cnum;
       +        size_t n;
       +        char title[TOX_MAX_NAME_LENGTH + 1];
       +
       +        n = fiforead(gslots[CONF].dirfd, &gslots[CONF].fd[IN], gfiles[IN],
       +                     title, sizeof(title) - 1);
       +        if (n <= 0)
       +                return;
       +        if (title[n - 1] == '\n')
       +                n--;
       +        title[n] = '\0';
       +        cnum = tox_conference_new(tox, NULL);
       +        if (cnum == UINT32_MAX) {
       +                weprintf("Failed to create new conference\n");
       +                return;
       +        }
       +        if (!tox_conference_set_title(tox, cnum, (uint8_t *)title, n, NULL))
       +                weprintf("Failed to set conference title to \"%s\"", title);
       +        confcreate(cnum);
       +}
       +
       +static void
        loop(void)
        {
       -        struct file reqfifo;
       +        struct file reqfifo, invfifo;
                struct friend *f, *ftmp;
                struct request *req, *rtmp;
       +        struct conference *c, *ctmp;
       +        struct invite *inv, *itmp;
                struct timeval tv;
                fd_set rfds;
                time_t t0, t1, c0, c1;
                size_t i;
                int    connected = 0, n, r, fd, fdmax;
       -        char   tstamp[64], c;
       -        uint32_t frnum;
       +        char   tstamp[64], ch;
       +        uint32_t frnum, cnum;
        
                t0 = time(NULL);
                logmsg("DHT > Connecting\n");
       t@@ -1583,7 +1941,7 @@ loop(void)
                                        toxconnect();
                                }
                        }
       -                tox_iterate(tox);
       +                tox_iterate(tox, NULL);
                        toxav_iterate(toxav);
        
                        /* Prepare select-fd-set */
       t@@ -1596,6 +1954,9 @@ loop(void)
                        TAILQ_FOREACH(req, &reqhead, entry)
                                FD_APPEND(req->fd);
        
       +                TAILQ_FOREACH(inv, &invhead, entry)
       +                        FD_APPEND(inv->fd);
       +
                        TAILQ_FOREACH(f, &friendhead, entry) {
                                /* Only monitor friends that are online */
                                if (tox_friend_get_connection_status(tox, f->num, NULL) != TOX_CONNECTION_NONE) {
       t@@ -1609,6 +1970,13 @@ loop(void)
                                FD_APPEND(f->fd[FREMOVE]);
                        }
        
       +                TAILQ_FOREACH(c, &confhead, entry) {
       +                        FD_APPEND(c->fd[CLEAVE]);
       +                        FD_APPEND(c->fd[CTITLE_IN]);
       +                        FD_APPEND(c->fd[CTEXT_IN]);
       +                        FD_APPEND(c->fd[CINVITE]);
       +                }
       +
                        tv.tv_sec = 0;
                        tv.tv_usec = interval(tox, toxav) * 1000;
                        n = select(fdmax + 1, &rfds, NULL, NULL, &tv);
       t@@ -1655,6 +2023,7 @@ loop(void)
                                }
                        }
        
       +
                        /* Answer pending calls */
                        TAILQ_FOREACH(f, &friendhead, entry) {
                                if (tox_friend_get_connection_status(tox, f->num, NULL) == TOX_CONNECTION_NONE)
       t@@ -1715,9 +2084,9 @@ loop(void)
                                reqfifo.name = req->idstr;
                                reqfifo.flags = O_RDONLY | O_NONBLOCK;
                                if (fiforead(gslots[REQUEST].fd[OUT], &req->fd, reqfifo,
       -                                     &c, 1) != 1)
       +                                     &ch, 1) != 1)
                                        continue;
       -                        if (c != '0' && c != '1')
       +                        if (ch != '0' && ch != '1')
                                        continue;
                                frnum = tox_friend_add_norequest(tox, req->id, NULL);
                                if (frnum == UINT32_MAX) {
       t@@ -1725,7 +2094,7 @@ loop(void)
                                        fiforeset(gslots[REQUEST].fd[OUT], &req->fd, reqfifo);
                                        continue;
                                }
       -                        if (c == '1') {
       +                        if (ch == '1') {
                                        friendcreate(frnum);
                                        logmsg("Request : %s > Accepted\n", req->idstr);
                                        datasave();
       t@@ -1740,6 +2109,48 @@ loop(void)
                                free(req);
                        }
        
       +                for (inv = TAILQ_FIRST(&invhead); inv; inv = itmp) {
       +                        itmp = TAILQ_NEXT(inv, entry);
       +                        if (FD_ISSET(inv->fd, &rfds) == 0)
       +                                continue;
       +                        invfifo.name = inv->fifoname;
       +                        invfifo.flags = O_RDONLY | O_NONBLOCK;
       +                        if (fiforead(gslots[CONF].fd[OUT], &inv->fd, invfifo,
       +                            &ch, 1) != 1)
       +                                continue;
       +                        if (ch != '0' && ch != '1')
       +                                continue;
       +                        else if (ch == '1'){
       +                                cnum = tox_conference_join(tox, inv->inviter, (uint8_t *)inv->cookie,
       +                                                           inv->cookielen, NULL);
       +                                if(cnum == UINT32_MAX)
       +                                        weprintf("Failed to join conference\n");
       +                                else
       +                                        confcreate(cnum);
       +                        }
       +                        unlinkat(gslots[CONF].fd[OUT], inv->fifoname, 0);
       +                        close(inv->fd);
       +                        TAILQ_REMOVE(&invhead, inv, entry);
       +                        free(inv->fifoname);
       +                        free(inv->cookie);
       +                        free(inv);
       +                }
       +
       +                for (c = TAILQ_FIRST(&confhead); c; c = ctmp) {
       +                        ctmp = TAILQ_NEXT(c, entry);
       +                        if (FD_ISSET(c->fd[CINVITE], &rfds))
       +                                invitefriend(c);
       +                        if (FD_ISSET(c->fd[CLEAVE], &rfds)) {
       +                                logmsg("Conference %s > Leave\n", c->numstr);
       +                                tox_conference_delete(tox, c->num, NULL);
       +                                confdestroy(c);
       +                        }
       +                        if (FD_ISSET(c->fd[CTEXT_IN], &rfds))
       +                                sendconftext(c);
       +                        if (FD_ISSET(c->fd[CTITLE_IN], &rfds))
       +                                updatetitle(c);
       +                }
       +
                        for (f = TAILQ_FIRST(&friendhead); f; f = ftmp) {
                                ftmp = TAILQ_NEXT(f, entry);
                                if (FD_ISSET(f->fd[FTEXT_IN], &rfds))
       t@@ -1805,7 +2216,9 @@ toxshutdown(void)
        {
                struct friend *f, *ftmp;
                struct request *r, *rtmp;
       -        size_t    i, m;
       +        struct conference *c, *ctmp;
       +        struct invite *i, *itmp;
       +        size_t    s, m;
        
                logmsg("Shutdown\n");
        
       t@@ -1817,6 +2230,12 @@ toxshutdown(void)
                        frienddestroy(f);
                }
        
       +        /* Conferences */
       +        for (c = TAILQ_FIRST(&confhead); c; c=ctmp) {
       +                ctmp = TAILQ_NEXT(c, entry);
       +                confdestroy(c);
       +        }
       +
                /* Requests */
                for (r = TAILQ_FIRST(&reqhead); r; r = rtmp) {
                        rtmp = TAILQ_NEXT(r, entry);
       t@@ -1831,18 +2250,33 @@ toxshutdown(void)
                        free(r);
                }
        
       +        /* Invites */
       +        for (i = TAILQ_FIRST(&invhead); i; i = itmp) {
       +                itmp = TAILQ_NEXT(i, entry);
       +
       +                if(gslots[CONF].fd[OUT] != -1) {
       +                        unlinkat(gslots[CONF].fd[OUT], i->fifoname, 0);
       +                        if (i->fd != -1)
       +                                close(i->fd);
       +                }
       +                TAILQ_REMOVE(&invhead, i, entry);
       +                free(i->fifoname);
       +                free(i->cookie);
       +                free(i);
       +        }
       +
                /* Global files and slots */
       -        for (i = 0; i < LEN(gslots); i++) {
       +        for (s = 0; s < LEN(gslots); s++) {
                        for (m = 0; m < LEN(gfiles); m++) {
       -                        if (gslots[i].dirfd != -1) {
       -                                unlinkat(gslots[i].dirfd, gfiles[m].name,
       -                                         (gslots[i].outisfolder && m == OUT)
       +                        if (gslots[s].dirfd != -1) {
       +                                unlinkat(gslots[s].dirfd, gfiles[m].name,
       +                                         (gslots[s].outisfolder && m == OUT)
                                                 ? AT_REMOVEDIR : 0);
       -                                if (gslots[i].fd[m] != -1)
       -                                        close(gslots[i].fd[m]);
       +                                if (gslots[s].fd[m] != -1)
       +                                        close(gslots[s].fd[m]);
                                }
       -                }
       -                rmdir(gslots[i].name);
       +                        }
       +                rmdir(gslots[s].name);
                }
                unlink("id");
                if (idfd != -1)