Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f6514a0b37 | ||
|
|
6a85dc86cf | ||
|
|
1f38d54b39 | ||
|
|
5a43139f75 | ||
|
|
f52d54ccc7 |
@@ -151,6 +151,7 @@ typedef struct {
|
||||
int mention;
|
||||
int scroll;
|
||||
char topic[MAX_LINE];
|
||||
int names_pending; /* 1 while receiving fresh 353 NAMES reply */
|
||||
} Channel;
|
||||
|
||||
typedef struct {
|
||||
@@ -162,7 +163,7 @@ typedef enum {
|
||||
EV_CONNECTED, EV_STATUS, EV_ERROR, EV_SERVER_TEXT,
|
||||
EV_PRIVMSG, EV_JOIN, EV_PART, EV_QUIT_MSG,
|
||||
EV_NICK_CHANGE, EV_NAMES, EV_KICK, EV_RAW,
|
||||
EV_RECONNECT, EV_TOPIC
|
||||
EV_RECONNECT, EV_TOPIC, EV_NAMES_END
|
||||
} EvType;
|
||||
|
||||
typedef struct {
|
||||
@@ -196,6 +197,7 @@ typedef struct {
|
||||
|
||||
int reconnect_pending;
|
||||
pthread_t reconnect_tid;
|
||||
int sasl_auth_sent; /* guard: only send AUTHENTICATE once per session */
|
||||
|
||||
/* per-server event pipe */
|
||||
int evpipe[2];
|
||||
@@ -579,17 +581,18 @@ static const char b64tab[] =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
|
||||
static void base64_encode(const unsigned char *in, int inlen, char *out) {
|
||||
int i=0, j=0;
|
||||
while (i < inlen) {
|
||||
unsigned int a=i<inlen?(unsigned char)in[i++]:0;
|
||||
unsigned int b=i<inlen?(unsigned char)in[i++]:0;
|
||||
unsigned int c=i<inlen?(unsigned char)in[i++]:0;
|
||||
unsigned int n=(a<<16)|(b<<8)|c;
|
||||
out[j++]=b64tab[(n>>18)&63]; out[j++]=b64tab[(n>>12)&63];
|
||||
out[j++]=(i-2<=inlen)?b64tab[(n>>6)&63]:'=';
|
||||
out[j++]=(i-1<=inlen)?b64tab[n&63]:'=';
|
||||
int j = 0;
|
||||
for (int i = 0; i < inlen; i += 3) {
|
||||
unsigned int a = (unsigned char)in[i];
|
||||
unsigned int b = (i+1 < inlen) ? (unsigned char)in[i+1] : 0;
|
||||
unsigned int c = (i+2 < inlen) ? (unsigned char)in[i+2] : 0;
|
||||
unsigned int n = (a << 16) | (b << 8) | c;
|
||||
out[j++] = b64tab[(n >> 18) & 63];
|
||||
out[j++] = b64tab[(n >> 12) & 63];
|
||||
out[j++] = (i+1 < inlen) ? b64tab[(n >> 6) & 63] : '=';
|
||||
out[j++] = (i+2 < inlen) ? b64tab[n & 63] : '=';
|
||||
}
|
||||
out[j]='\0';
|
||||
out[j] = '\0';
|
||||
}
|
||||
|
||||
/* ── IRC line handler (net thread — uses si to tag every event) ────────────── */
|
||||
@@ -604,25 +607,65 @@ static void handle_irc_line(int si, const char *raw) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* SASL */
|
||||
if (strcmp(m.cmd,"PONG")==0) return; /* ignore server's PONG replies */
|
||||
|
||||
/* SASL / CAP negotiation */
|
||||
if (strcmp(m.cmd,"CAP")==0) {
|
||||
char sub[16]="";
|
||||
if (m.nparams>=2) str_upper(sub, m.params[1], sizeof(sub));
|
||||
if (strcmp(sub,"ACK")==0 && strstr(m.trail,"sasl")) {
|
||||
srv_send_raw(si, "AUTHENTICATE PLAIN");
|
||||
else if (m.nparams>=1) str_upper(sub, m.params[0], sizeof(sub));
|
||||
|
||||
/* log all CAP lines to status for visibility */
|
||||
char capdbg[MAX_LINE];
|
||||
snprintf(capdbg,sizeof(capdbg),"[CAP] %s",raw);
|
||||
ev_simple(EV_STATUS, si, NULL, NULL, capdbg);
|
||||
|
||||
if (strcmp(sub,"LS")==0) {
|
||||
/* server advertising capabilities -- request sasl */
|
||||
srv_send_raw(si, "CAP REQ :sasl");
|
||||
} else if (strcmp(sub,"ACK")==0) {
|
||||
const char *caps = m.trail[0] ? m.trail : (m.nparams>0?m.params[m.nparams-1]:"");
|
||||
if (strstr(caps,"sasl")) {
|
||||
if (!s->sasl_auth_sent) {
|
||||
s->sasl_auth_sent = 1;
|
||||
srv_send_raw(si, "AUTHENTICATE PLAIN");
|
||||
}
|
||||
} else {
|
||||
srv_send_raw(si, "CAP END");
|
||||
}
|
||||
} else if (strcmp(sub,"NAK")==0) {
|
||||
ev_simple(EV_ERROR, si, NULL, NULL, "Server rejected SASL CAP.");
|
||||
ev_simple(EV_ERROR, si, NULL, NULL, "Server rejected SASL CAP -- connecting without SASL.");
|
||||
srv_send_raw(si, "CAP END");
|
||||
srv_sendf(si, "NICK %s", s->nick);
|
||||
srv_sendf(si, "USER %s 0 * :sirc", s->nick);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (strcmp(m.cmd,"AUTHENTICATE")==0 && strcmp(m.trail,"+")==0) {
|
||||
char payload[512];
|
||||
int plen=snprintf(payload,sizeof(payload),"%s%c%s%c%s",
|
||||
s->sasl_user,0,s->sasl_user,0,s->sasl_pass);
|
||||
char enc[700]; base64_encode((unsigned char*)payload,plen,enc);
|
||||
srv_sendf(si, "AUTHENTICATE %s", enc);
|
||||
if (strcmp(m.cmd,"AUTHENTICATE")==0) {
|
||||
/* server sends AUTHENTICATE + (trail or param) to prompt us */
|
||||
const char *challenge = m.trail[0] ? m.trail :
|
||||
(m.nparams>0 ? m.params[0] : "");
|
||||
if (strcmp(challenge,"+")==0) {
|
||||
/* build PLAIN payload: authzid\0authcid\0passwd
|
||||
must use memcpy — sasl_user/pass may not contain \0
|
||||
but we need to embed \0 separators manually */
|
||||
char payload[512];
|
||||
int ulen = strlen(s->sasl_user);
|
||||
int plen_pass = strlen(s->sasl_pass);
|
||||
/* layout: [sasl_user]\0[sasl_user]\0[sasl_pass] */
|
||||
int total = ulen + 1 + ulen + 1 + plen_pass;
|
||||
if (total < (int)sizeof(payload)) {
|
||||
int pos = 0;
|
||||
memcpy(payload + pos, s->sasl_user, ulen); pos += ulen;
|
||||
payload[pos++] = '\0';
|
||||
memcpy(payload + pos, s->sasl_user, ulen); pos += ulen;
|
||||
payload[pos++] = '\0';
|
||||
memcpy(payload + pos, s->sasl_pass, plen_pass); pos += plen_pass;
|
||||
char enc[700];
|
||||
base64_encode((unsigned char*)payload, total, enc);
|
||||
srv_sendf(si, "AUTHENTICATE %s", enc);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (strcmp(m.cmd,"903")==0) {
|
||||
@@ -841,7 +884,12 @@ static void handle_irc_line(int si, const char *raw) {
|
||||
ev_simple(EV_STATUS, si, NULL, NULL, "[list] end"); return;
|
||||
}
|
||||
|
||||
if (strcmp(m.cmd,"366")==0) return;
|
||||
if (strcmp(m.cmd,"366")==0) {
|
||||
/* end of NAMES — reset pending flag so next /names clears again */
|
||||
const char *ch = m.nparams>=2 ? m.params[1] : (m.nparams>=1 ? m.params[0] : "");
|
||||
ev_simple(EV_NAMES_END, si, NULL, ch, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
/* AWAY */
|
||||
if (strcmp(m.cmd,"301")==0) {
|
||||
@@ -968,9 +1016,11 @@ static void *net_thread(void *arg) {
|
||||
}
|
||||
|
||||
int sasl_pending=(s->sasl_user[0]&&s->sasl_pass[0])?1:0;
|
||||
if (sasl_pending)
|
||||
srv_send_raw(si,"CAP REQ :sasl");
|
||||
else {
|
||||
s->sasl_auth_sent = 0;
|
||||
if (sasl_pending) {
|
||||
/* CAP LS 302: proper negotiation, handler sends CAP REQ :sasl on LS reply */
|
||||
srv_send_raw(si,"CAP LS 302");
|
||||
} else {
|
||||
srv_sendf(si,"NICK %s",s->nick);
|
||||
srv_sendf(si,"USER %s 0 * :sirc",s->nick);
|
||||
}
|
||||
@@ -1060,7 +1110,7 @@ static int srv_alloc(void) {
|
||||
memset(s,0,sizeof(Server));
|
||||
strncpy(s->host,"irc.libera.chat",MAX_HOST-1);
|
||||
s->port=6697;
|
||||
strncpy(s->nick,"sirc_user",MAX_NICK-1);
|
||||
strncpy(s->nick,"circ_user",MAX_NICK-1);
|
||||
s->use_tls=1;
|
||||
s->sock=-1;
|
||||
pthread_mutex_init(&s->send_lock,NULL);
|
||||
@@ -1068,16 +1118,24 @@ static int srv_alloc(void) {
|
||||
}
|
||||
|
||||
static void srv_add_autojoin(int si, const char *chanlist) {
|
||||
char tmp[512]; strncpy(tmp,chanlist,511);
|
||||
char *tok=strtok(tmp,",");
|
||||
char tmp[MAX_LINE]; strncpy(tmp, chanlist, MAX_LINE-1);
|
||||
char *tok = strtok(tmp, ",");
|
||||
while (tok) {
|
||||
while (*tok==' ') tok++;
|
||||
if (*tok && g_srv[si].autojoin_count<MAX_AUTOJOIN) {
|
||||
char ch[MAX_CHAN]; strncpy(ch,tok,MAX_CHAN-1);
|
||||
if (ch[0]!='#') { memmove(ch+1,ch,strlen(ch)+1); ch[0]='#'; }
|
||||
strncpy(g_srv[si].autojoin[g_srv[si].autojoin_count++],ch,MAX_CHAN-1);
|
||||
/* trim leading and trailing spaces */
|
||||
while (*tok == ' ') tok++;
|
||||
char *end = tok + strlen(tok) - 1;
|
||||
while (end > tok && *end == ' ') *end-- = '\0';
|
||||
if (*tok && g_srv[si].autojoin_count < MAX_AUTOJOIN) {
|
||||
char ch[MAX_CHAN]; strncpy(ch, tok, MAX_CHAN-1);
|
||||
if (ch[0] != '#') { memmove(ch+1, ch, strlen(ch)+1); ch[0]='#'; }
|
||||
/* avoid duplicates */
|
||||
int dup = 0;
|
||||
for (int i = 0; i < g_srv[si].autojoin_count; i++)
|
||||
if (strcasecmp(g_srv[si].autojoin[i], ch) == 0) { dup=1; break; }
|
||||
if (!dup)
|
||||
strncpy(g_srv[si].autojoin[g_srv[si].autojoin_count++], ch, MAX_CHAN-1);
|
||||
}
|
||||
tok=strtok(NULL,",");
|
||||
tok = strtok(NULL, ",");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1094,7 +1152,7 @@ static void load_config(const char *path) {
|
||||
if (!f) return;
|
||||
|
||||
/* defaults that apply before any [server] block */
|
||||
char def_nick[MAX_NICK]="sirc_user";
|
||||
char def_nick[MAX_NICK]="circ_user";
|
||||
|
||||
/* current server being parsed; -1 = not inside a [server] block */
|
||||
int cur_si=-1;
|
||||
@@ -1450,15 +1508,20 @@ static void build_windows(void) {
|
||||
/* ── event handler ─────────────────────────────────────────────────────────── */
|
||||
|
||||
static void do_rejoin_channels(int si) {
|
||||
int any=0;
|
||||
for (int i=0;i<g_chan_count;i++) {
|
||||
if (g_chans[i].srv==si && g_chans[i].name[0]=='#') {
|
||||
srv_sendf(si,"JOIN %s",g_chans[i].name); any=1;
|
||||
}
|
||||
}
|
||||
if (!any) {
|
||||
for (int j=0;j<g_srv[si].autojoin_count;j++)
|
||||
srv_sendf(si,"JOIN %s",g_srv[si].autojoin[j]);
|
||||
/* always join every configured autojoin channel */
|
||||
for (int j = 0; j < g_srv[si].autojoin_count; j++)
|
||||
srv_sendf(si, "JOIN %s", g_srv[si].autojoin[j]);
|
||||
|
||||
/* also rejoin any channels open from a previous session that aren't
|
||||
in the autojoin list (e.g. channels joined manually before disconnect) */
|
||||
for (int i = 0; i < g_chan_count; i++) {
|
||||
if (g_chans[i].srv != si || g_chans[i].name[0] != '#') continue;
|
||||
int already = 0;
|
||||
for (int j = 0; j < g_srv[si].autojoin_count; j++)
|
||||
if (strcasecmp(g_srv[si].autojoin[j], g_chans[i].name) == 0)
|
||||
{ already = 1; break; }
|
||||
if (!already)
|
||||
srv_sendf(si, "JOIN %s", g_chans[i].name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1591,6 +1654,11 @@ static void handle_event(const Event *ev) {
|
||||
|
||||
case EV_NAMES: {
|
||||
int ci=chan_find(si,ev->chan); if(ci<0) break;
|
||||
/* first nick of a fresh NAMES reply — wipe the stale list */
|
||||
if (!g_chans[ci].names_pending) {
|
||||
g_chans[ci].user_count = 0;
|
||||
g_chans[ci].names_pending = 1;
|
||||
}
|
||||
chan_adduser(&g_chans[ci], ev->extra[0], ev->nick);
|
||||
chan_sort_users(&g_chans[ci]);
|
||||
break;
|
||||
@@ -1611,6 +1679,12 @@ static void handle_event(const Event *ev) {
|
||||
break;
|
||||
}
|
||||
|
||||
case EV_NAMES_END: {
|
||||
int ci=chan_find(si,ev->chan); if(ci<0) break;
|
||||
g_chans[ci].names_pending = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user