Skip to content
Permalink
Browse files

Add CAP support. Closes #423

This is the first hack at adding a basic CAP support functionality to Eggdrop. Much more work will be required down the road. This merge will be closely tied to the upcoming SASL capability merge, as it appears SASL needs to do stuff in the middle of the CAP negotitation phase, so in preparation there is a little more SASL-specific code in here than would be otherwise preferred, but that is a problem (is it?) to solve another day. 

Basically, Eggdrop will now query a server via CAP LS at connect. It will store that list of server-supported capabilities, then request the user-specified capabilities that exist within that list (and not capabilities that are not supported by the server). If the user has not configured a CAP-required capability in the config, then Eggdrop will not send a REQ request to the server (the initial LS will always happen though).

https://ircv3.net/specs/core/capability-negotiation was used to implement CAP

* Initial CAP support commit
* Update tcl-commands.doc with cap usage
* Do not request unsupported CAP capes from server
* Don't duplicate already-requested capes in cape list
* Suppress request line if empty
* Don't send END before SASL is done
* Prevent CAP request dupes after server reconnect
  • Loading branch information...
vanosg committed Jul 14, 2019
1 parent 5c5a3f7 commit 96ada33e530fb883cff8693ba04b0e669322ceaf
@@ -154,6 +154,16 @@ clearqueue <queue>

Module: server

^^^^^^^^^^^^^^^^^^^^^^^
cap <sub-command> [arg]
^^^^^^^^^^^^^^^^^^^^^^^

Description: sends a raw CAP command to the server. Sub-commands can be the valid CAP client sub-ccommands of LS, LIST, REQ, or END; or the eggdrop internal commands of -list or -active. -list will show the capabilities supported on the current server, -active will show any capabilities already negotiated by Eggdrop with the server. If sending a REQ, use arg to pass the requested capability to the server.

Returns: nothing

Module: server

User Record Manipulation Commands
---------------------------------

@@ -121,6 +121,8 @@ static void msgq_clear(struct msgq_head *qh);
static int stack_limit;
static char *realservername;

static int sasl = 0;

#include "servmsg.c"

#define MAXPENALTY 10
@@ -1425,6 +1427,7 @@ static tcl_ints my_tcl_ints[] = {
#ifdef TLS
{"ssl-verify-server", &tls_vfyserver, 0},
#endif
{"sasl", &sasl, 0},
{NULL, NULL, 0}
};

@@ -1817,6 +1820,8 @@ static void server_report(int idx, int details)
if (hq.tot)
dprintf(idx, " %s %d%% (%d msgs)\n", IRC_HELPQUEUE,
(int) ((float) (hq.tot * 100.0) / (float) maxqmsg), (int) hq.tot);
dprintf(idx, " Active CAP negotiations: %s\n", (strlen(cap.negotiated) > 0) ?
cap.negotiated : "None" );

if (details) {
int size = server_expmem();
@@ -20,6 +20,8 @@
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/

#define CAPMAX 511 /* CAP list string max */

#ifndef _EGG_MOD_SERVER_SERVER_H
#define _EGG_MOD_SERVER_SERVER_H

@@ -100,6 +102,14 @@ struct server_list {
char *realname;
};

typedef struct cap_list {
char supported[CAPMAX]; /* Capes supportd by IRCD */
char negotiated[CAPMAX]; /* Common capes between IRCD and client */
char desired[CAPMAX]; /* Capes Eggdrop wants to request from IRCD */
} cap_list;

extern struct cap_list cap;

/* Available net types. */
enum {
NETT_EFNET = 0, /* EFnet */
@@ -22,10 +22,14 @@

#include "../irc.mod/irc.h"
#include "../channels.mod/channels.h"
#include "server.h"

static time_t last_ctcp = (time_t) 0L;
static int count_ctcp = 0;
static char altnick_char = 0;
struct cap_list cap = {"", "", ""};
char capes[64][32] = {{ 0 }};


/* We try to change to a preferred unique nick here. We always first try the
* specified alternate nick. If that fails, we repeatedly modify the nick
@@ -37,7 +41,7 @@ static char altnick_char = 0;
* ^--------- given, alternate nick
*
* The last added character is always saved in altnick_char. At the very first
* attempt (were altnick_char is 0), we try the alternate nick without any
* attempt (where altnick_char is 0), we try the alternate nick without any
* additions.
*
* fixed by guppy (1999/02/24) and Fabian (1999/11/26)
@@ -982,6 +986,9 @@ static void disconnect_server(int idx)
{
if (server_online > 0)
check_tcl_event("disconnect-server");
strcpy(cap.supported, "");
strcpy(cap.negotiated, "");
strcpy(cap.desired, "");
server_online = 0;
if (realservername)
nfree(realservername);
@@ -1148,6 +1155,120 @@ static int got465(char *from, char *msg)
return 1;
}

/*
* Invalid CAP command
*/
static int got410(char *from, char *msg) {
char *cmd;

newsplit(&msg);

putlog(LOG_SERV, "*", "%s", msg);
cmd = newsplit(&msg);
putlog(LOG_MISC, "*", "CAP sub-command %s not supported", cmd);

return 1;
}

static int got421(char *from, char *msg) {
newsplit(&msg);
putlog(LOG_SERV, "*", "%s reported an error: %s", from, msg);

return 1;
}

/*
* Helper function to add CAP capability to next empty array slot
*/
void add_cape(char *cape) {
int i = 0;
for (i = 0; i < (sizeof capes / sizeof capes[0]); i++) {
if (!strlen(capes[i])) {
strlcpy(capes[i], cape, sizeof capes[0]);
break;
}
}
}

/*
* Add desired CAP requests to an array for easier parsing against supported
* server capabilities later on
*/
void create_cap_req() {
memset(capes, 0, sizeof capes[0]);
if (sasl) {
add_cape("sasl");
}
}

static int gotcap(char *from, char *msg) {
char *cmd, *match;
int len, i = 0;

newsplit(&msg);
putlog(LOG_DEBUG, "*", "CAP: %s", msg);
cmd = newsplit(&msg);
fixcolon(msg);
if (!strcmp(cmd, "LS")) {
putlog(LOG_DEBUG, "*", "CAP: %s supports CAP sub-commands: %s", from, msg);
strlcpy(cap.supported, msg, sizeof cap.supported);
create_cap_req();
/* Check each desired cape against supported capes on server */
for (i = 0; i < (sizeof capes / sizeof capes[0]); i++) {
if (strlen(capes[i])) {
if (strstr(cap.supported, capes[i])) {
strncat(cap.desired, capes[i], (CAPMAX - strlen(cap.desired) - 1));
strncat(cap.desired, " ", (CAPMAX - strlen(cap.desired) - 1));
} else {
putlog(LOG_DEBUG, "*","CAP: desired capability %s not supported "
"on %s, removing from request...", capes[i], from);
}
}
}
if (strlen(cap.desired) > 0) {
putlog(LOG_DEBUG, "*", "CAP: Requesting %scapabilities from server", cap.desired);
dprintf(DP_MODE, "CAP REQ :%s\n", cap.desired);
} else {
dprintf(DP_MODE, "CAP END\n");
}
} else if (!strcmp(cmd, "LIST")) {
putlog(LOG_SERV, "*", "CAP: Negotiated CAP capabilities: %s", msg);
strlcpy(cap.negotiated, msg, sizeof cap.negotiated);
} else if (!strcmp(cmd, "ACK")) {
if (msg[0] == '-') {
msg++;
len = strlen(msg); /* Remove capability from .negotiated list */
while ((match = strstr(cap.negotiated, msg))) {
*match = '\0';
strcat(cap.negotiated, match+len);
}
putlog (LOG_SERV, "*", "CAP: Disabled %s with %s", msg, from);
} else {
putlog(LOG_SERV, "*", "CAP: Successfully negotiated %s with %s", msg, from);
if (!strstr(cap.negotiated, msg)) { //TODO break apart msg in case multiple capes requeseted at once
strncat(cap.negotiated, msg, (sizeof cap.negotiated -
strlen(cap.negotiated) - 1));
}
/* If a negotiated capability requires immediate action by Eggdrop,
* add it here */
if (strstr(msg, "sasl") != NULL) {
putlog(LOG_SERV, "*", "SASL AUTH CALL GOES HERE!"); //TODO
}
}
if (!strstr(cap.negotiated, "sasl")) {
dprintf(DP_MODE, "CAP END\n");
}
} else if (!strcmp(cmd, "NAK")) {
putlog(LOG_SERV, "*", "CAP: Requested capability change %s rejected by %s",
msg, from);
dprintf(DP_MODE, "CAP END\n"); /* TODO: Handle whatever caused it to reject? */
} else if (!strcmp(cmd, "NEW")) { //TODO: CAP 302 stuff?
// Do things
} else if (!strcmp(cmd, "DEL")) { // TODO: CAP 302 stuff?
// Do things
}
return 1;
}

static cmd_t my_raw_binds[] = {
{"PRIVMSG", "", (IntFunc) gotmsg, NULL},
@@ -1158,6 +1279,10 @@ static cmd_t my_raw_binds[] = {
{"WALLOPS", "", (IntFunc) gotwall, NULL},
{"001", "", (IntFunc) got001, NULL},
{"303", "", (IntFunc) got303, NULL},
{"311", "", (IntFunc) got311, NULL},
{"318", "", (IntFunc) whoispenalty, NULL},
{"410", "", (IntFunc) got410, NULL},
{"421", "", (IntFunc) got421, NULL},
{"432", "", (IntFunc) got432, NULL},
{"433", "", (IntFunc) got433, NULL},
{"437", "", (IntFunc) got437, NULL},
@@ -1170,8 +1295,7 @@ static cmd_t my_raw_binds[] = {
/* ircu2.10.10 has a bug when a client is throttled ERROR is sent wrong */
{"ERROR:", "", (IntFunc) goterror, NULL},
{"KICK", "", (IntFunc) gotkick, NULL},
{"318", "", (IntFunc) whoispenalty, NULL},
{"311", "", (IntFunc) got311, NULL},
{"CAP", "", (IntFunc) gotcap, NULL},
{NULL, NULL, NULL, NULL}
};

@@ -1324,6 +1448,8 @@ static void server_resolve_success(int servidx)
/* Start alternate nicks from the beginning */
altnick_char = 0;
check_tcl_event("preinit-server");
/* See if server supports CAP command */
dprintf(DP_MODE, "CAP LS\n");
if (pass[0])
dprintf(DP_MODE, "PASS %s\n", pass);
dprintf(DP_MODE, "NICK %s\n", botname);
@@ -1,7 +1,6 @@
/*
* tclserv.c -- part of server.mod
*/
/*
*
* Copyright (C) 1997 Robey Pointer
* Copyright (C) 1999 - 2019 Eggheads Development Team
*
@@ -163,6 +162,30 @@ static int tcl_puthelp STDVAR
return TCL_OK;
}

/* Tcl interface to send CAP messages to server */
static int tcl_cap STDVAR {
char s[CAPMAX];
BADARGS(2, 3, " sub-cmd ?arg?");

simple_sprintf(s, "CAP requires a sub-command");
if (!strcasecmp(argv[1], "-list")) {
Tcl_AppendResult(irp, cap.supported, NULL);
return TCL_OK;
} else if (!strcasecmp(argv[1], "-active")) {
Tcl_AppendResult(irp, cap.negotiated, NULL);
return TCL_OK;
}
if (argc == 2) {
simple_sprintf(s, "CAP %s", argv[1]);
}
else if (argc == 3) {
simple_sprintf(s, "CAP %s :%s", argv[1], argv[2]);
}
dprintf(DP_SERVER, "%s\n", s);
return TCL_OK;
}


static int tcl_jump STDVAR
{
BADARGS(1, 4, " ?server? ?port? ?pass?");
@@ -311,6 +334,7 @@ static int tcl_queuesize STDVAR

static tcl_cmds my_tcl_cmds[] = {
{"jump", tcl_jump},
{"cap", tcl_cap},
{"isbotnick", tcl_isbotnick},
{"clearqueue", tcl_clearqueue},
{"queuesize", tcl_queuesize},

0 comments on commit 96ada33

Please sign in to comment.
You can’t perform that action at this time.