5 Commits
Author SHA1 Message Date
emmett1 f6514a0b37 silent PONG replies 2026-04-09 07:41:58 +08:00
emmett1 6a85dc86cf fix names not update when user leaves 2026-04-09 07:39:26 +08:00
emmett1 1f38d54b39 fix some channels not auto join 2026-04-08 23:04:55 +08:00
emmett1 5a43139f75 fix sasl authenticate again 2026-04-07 08:08:37 +08:00
emmett1 f52d54ccc7 fix authenticate 2026-04-06 22:41:48 +08:00
+104 -30
View File
@@ -151,6 +151,7 @@ typedef struct {
int mention; int mention;
int scroll; int scroll;
char topic[MAX_LINE]; char topic[MAX_LINE];
int names_pending; /* 1 while receiving fresh 353 NAMES reply */
} Channel; } Channel;
typedef struct { typedef struct {
@@ -162,7 +163,7 @@ typedef enum {
EV_CONNECTED, EV_STATUS, EV_ERROR, EV_SERVER_TEXT, EV_CONNECTED, EV_STATUS, EV_ERROR, EV_SERVER_TEXT,
EV_PRIVMSG, EV_JOIN, EV_PART, EV_QUIT_MSG, EV_PRIVMSG, EV_JOIN, EV_PART, EV_QUIT_MSG,
EV_NICK_CHANGE, EV_NAMES, EV_KICK, EV_RAW, EV_NICK_CHANGE, EV_NAMES, EV_KICK, EV_RAW,
EV_RECONNECT, EV_TOPIC EV_RECONNECT, EV_TOPIC, EV_NAMES_END
} EvType; } EvType;
typedef struct { typedef struct {
@@ -196,6 +197,7 @@ typedef struct {
int reconnect_pending; int reconnect_pending;
pthread_t reconnect_tid; pthread_t reconnect_tid;
int sasl_auth_sent; /* guard: only send AUTHENTICATE once per session */
/* per-server event pipe */ /* per-server event pipe */
int evpipe[2]; int evpipe[2];
@@ -579,15 +581,16 @@ static const char b64tab[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static void base64_encode(const unsigned char *in, int inlen, char *out) { static void base64_encode(const unsigned char *in, int inlen, char *out) {
int i=0, j=0; int j = 0;
while (i < inlen) { for (int i = 0; i < inlen; i += 3) {
unsigned int a=i<inlen?(unsigned char)in[i++]:0; unsigned int a = (unsigned char)in[i];
unsigned int b=i<inlen?(unsigned char)in[i++]:0; unsigned int b = (i+1 < inlen) ? (unsigned char)in[i+1] : 0;
unsigned int c=i<inlen?(unsigned char)in[i++]:0; unsigned int c = (i+2 < inlen) ? (unsigned char)in[i+2] : 0;
unsigned int n = (a << 16) | (b << 8) | c; unsigned int n = (a << 16) | (b << 8) | c;
out[j++]=b64tab[(n>>18)&63]; out[j++]=b64tab[(n>>12)&63]; out[j++] = b64tab[(n >> 18) & 63];
out[j++]=(i-2<=inlen)?b64tab[(n>>6)&63]:'='; out[j++] = b64tab[(n >> 12) & 63];
out[j++]=(i-1<=inlen)?b64tab[n&63]:'='; out[j++] = (i+1 < inlen) ? b64tab[(n >> 6) & 63] : '=';
out[j++] = (i+2 < inlen) ? b64tab[n & 63] : '=';
} }
out[j] = '\0'; out[j] = '\0';
} }
@@ -604,25 +607,65 @@ static void handle_irc_line(int si, const char *raw) {
return; return;
} }
/* SASL */ if (strcmp(m.cmd,"PONG")==0) return; /* ignore server's PONG replies */
/* SASL / CAP negotiation */
if (strcmp(m.cmd,"CAP")==0) { if (strcmp(m.cmd,"CAP")==0) {
char sub[16]=""; char sub[16]="";
if (m.nparams>=2) str_upper(sub, m.params[1], sizeof(sub)); if (m.nparams>=2) str_upper(sub, m.params[1], sizeof(sub));
if (strcmp(sub,"ACK")==0 && strstr(m.trail,"sasl")) { 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"); srv_send_raw(si, "AUTHENTICATE PLAIN");
}
} else {
srv_send_raw(si, "CAP END");
}
} else if (strcmp(sub,"NAK")==0) { } 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, "NICK %s", s->nick);
srv_sendf(si, "USER %s 0 * :sirc", s->nick); srv_sendf(si, "USER %s 0 * :sirc", s->nick);
} }
return; return;
} }
if (strcmp(m.cmd,"AUTHENTICATE")==0 && strcmp(m.trail,"+")==0) { 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]; char payload[512];
int plen=snprintf(payload,sizeof(payload),"%s%c%s%c%s", int ulen = strlen(s->sasl_user);
s->sasl_user,0,s->sasl_user,0,s->sasl_pass); int plen_pass = strlen(s->sasl_pass);
char enc[700]; base64_encode((unsigned char*)payload,plen,enc); /* 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); srv_sendf(si, "AUTHENTICATE %s", enc);
}
}
return; return;
} }
if (strcmp(m.cmd,"903")==0) { 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; 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 */ /* AWAY */
if (strcmp(m.cmd,"301")==0) { 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; int sasl_pending=(s->sasl_user[0]&&s->sasl_pass[0])?1:0;
if (sasl_pending) s->sasl_auth_sent = 0;
srv_send_raw(si,"CAP REQ :sasl"); if (sasl_pending) {
else { /* 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,"NICK %s",s->nick);
srv_sendf(si,"USER %s 0 * :sirc",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)); memset(s,0,sizeof(Server));
strncpy(s->host,"irc.libera.chat",MAX_HOST-1); strncpy(s->host,"irc.libera.chat",MAX_HOST-1);
s->port=6697; s->port=6697;
strncpy(s->nick,"sirc_user",MAX_NICK-1); strncpy(s->nick,"circ_user",MAX_NICK-1);
s->use_tls=1; s->use_tls=1;
s->sock=-1; s->sock=-1;
pthread_mutex_init(&s->send_lock,NULL); pthread_mutex_init(&s->send_lock,NULL);
@@ -1068,13 +1118,21 @@ static int srv_alloc(void) {
} }
static void srv_add_autojoin(int si, const char *chanlist) { static void srv_add_autojoin(int si, const char *chanlist) {
char tmp[512]; strncpy(tmp,chanlist,511); char tmp[MAX_LINE]; strncpy(tmp, chanlist, MAX_LINE-1);
char *tok = strtok(tmp, ","); char *tok = strtok(tmp, ",");
while (tok) { while (tok) {
/* trim leading and trailing spaces */
while (*tok == ' ') tok++; while (*tok == ' ') tok++;
char *end = tok + strlen(tok) - 1;
while (end > tok && *end == ' ') *end-- = '\0';
if (*tok && g_srv[si].autojoin_count < MAX_AUTOJOIN) { if (*tok && g_srv[si].autojoin_count < MAX_AUTOJOIN) {
char ch[MAX_CHAN]; strncpy(ch, tok, MAX_CHAN-1); char ch[MAX_CHAN]; strncpy(ch, tok, MAX_CHAN-1);
if (ch[0] != '#') { memmove(ch+1, ch, strlen(ch)+1); ch[0]='#'; } 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); 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; if (!f) return;
/* defaults that apply before any [server] block */ /* 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 */ /* current server being parsed; -1 = not inside a [server] block */
int cur_si=-1; int cur_si=-1;
@@ -1450,15 +1508,20 @@ static void build_windows(void) {
/* ── event handler ─────────────────────────────────────────────────────────── */ /* ── event handler ─────────────────────────────────────────────────────────── */
static void do_rejoin_channels(int si) { static void do_rejoin_channels(int si) {
int any=0; /* always join every configured autojoin channel */
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++) for (int j = 0; j < g_srv[si].autojoin_count; j++)
srv_sendf(si, "JOIN %s", g_srv[si].autojoin[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: { case EV_NAMES: {
int ci=chan_find(si,ev->chan); if(ci<0) break; 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_adduser(&g_chans[ci], ev->extra[0], ev->nick);
chan_sort_users(&g_chans[ci]); chan_sort_users(&g_chans[ci]);
break; break;
@@ -1611,6 +1679,12 @@ static void handle_event(const Event *ev) {
break; break;
} }
case EV_NAMES_END: {
int ci=chan_find(si,ev->chan); if(ci<0) break;
g_chans[ci].names_pending = 0;
break;
}
} }
} }