From f75d3c0cd014448b6ab0ea3e19ba2ecf0b0dae61 Mon Sep 17 00:00:00 2001 From: emmett1 Date: Sun, 14 Jun 2026 11:56:40 +0800 Subject: updates --- .gitignore | 1 + sirc.1 | 4 +- sirc.c | 133 +++++++++++++++++++++++++++++++++++++++++++++---------------- 3 files changed, 102 insertions(+), 36 deletions(-) diff --git a/.gitignore b/.gitignore index 5971120..45e7cf1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ sirc.o sirc +CLAUDE.md diff --git a/sirc.1 b/sirc.1 index 3696f42..07a5aba 100644 --- a/sirc.1 +++ b/sirc.1 @@ -499,7 +499,7 @@ PREFIX=/usr/local make install .BR weechat (1), .BR nc (1), .BR openssl (1) -.SH AUTHORS -Emmett and Claude. +.SH AUTHOR +Emmett1 .SH LICENSE MIT diff --git a/sirc.c b/sirc.c index a28e145..3445d28 100644 --- a/sirc.c +++ b/sirc.c @@ -188,14 +188,14 @@ typedef struct { char sasl_pass[256]; /* runtime */ - int connected; + volatile int connected; volatile int net_stop; pthread_t net_tid; pthread_mutex_t send_lock; int sock; SSL *ssl; - int reconnect_pending; + volatile int reconnect_pending; pthread_t reconnect_tid; int sasl_auth_sent; /* guard: only send AUTHENTICATE once per session */ @@ -208,11 +208,12 @@ typedef struct { static Server g_srv[MAX_SERVERS]; static int g_srv_count = 0; static SSL_CTX *g_ssl_ctx = NULL; +static pthread_mutex_t g_ssl_mutex = PTHREAD_MUTEX_INITIALIZER; /* channels (flat array, each has a .srv index) */ static Channel g_chans[MAX_CHANS_TOTAL]; static int g_chan_count = 0; -static int g_active = 0; /* index into g_chans; -1 = no channels */ +static int g_active = -1; /* index into g_chans; -1 = no channels */ /* global ignore list */ static char g_ignore[MAX_IGNORE][MAX_NICK]; @@ -254,6 +255,7 @@ static void str_upper(char *dst, const char *src, int n) { static int str_icase_starts(const char *hay, const char *needle) { while (*needle) { + if (!*hay) return 0; if (tolower((unsigned char)*hay) != tolower((unsigned char)*needle)) return 0; hay++; needle++; @@ -284,7 +286,7 @@ static int chan_find(int srv, const char *name) { static int chan_add(int srv, const char *name) { int i = chan_find(srv, name); if (i >= 0) return i; - if (g_chan_count >= MAX_CHANS_TOTAL) return 0; + if (g_chan_count >= MAX_CHANS_TOTAL) return -1; i = g_chan_count++; memset(&g_chans[i], 0, sizeof(Channel)); strncpy(g_chans[i].name, name, MAX_CHAN-1); @@ -345,7 +347,8 @@ static void chan_addline(Channel *ch, int flag, const char *text) { /* get the status channel for a server (create if needed) */ static int srv_status_chan(int srv) { - return chan_add(srv, "*status*"); + int ci = chan_add(srv, "*status*"); + return ci >= 0 ? ci : 0; } static void srv_status_msg(int srv, const char *msg) { @@ -377,7 +380,7 @@ static int user_find(Channel *ch, const char *nick) { static void chan_adduser(Channel *ch, char mode, const char *nick) { int idx = user_find(ch, nick); - char entry[MAX_NICK]; + char entry[MAX_NICK] = {0}; entry[0] = mode ? mode : ' '; strncpy(entry+1, nick, MAX_NICK-2); if (idx >= 0) { @@ -488,7 +491,12 @@ static void ignore_remove(const char *nick) { /* ── event pipe ────────────────────────────────────────────────────────────── */ static void ev_push(const Event *ev) { - write(g_evpipe[1], ev, sizeof(Event)); + /* non-blocking pipe: retry once on EAGAIN so events aren't silently lost */ + if (write(g_evpipe[1], ev, sizeof(Event)) < 0) { + struct timespec ts = {0, 10000000}; /* 10 ms */ + nanosleep(&ts, NULL); + write(g_evpipe[1], ev, sizeof(Event)); + } } static void ev_simple(EvType type, int srv, const char *nick, @@ -521,8 +529,10 @@ static int srv_send_raw(int si, const char *line) { static void srv_sendf(int si, const char *fmt, ...) { char buf[MAX_LINE]; va_list ap; va_start(ap, fmt); - vsnprintf(buf, sizeof(buf), fmt, ap); + int n = vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); + if (n >= (int)sizeof(buf)) + ev_simple(EV_ERROR, si, NULL, NULL, "send truncated (increase MAX_LINE)"); srv_send_raw(si, buf); } @@ -952,6 +962,9 @@ static void srv_close(int si) { static int srv_connect(int si) { Server *s=&g_srv[si]; + /* close any previous connection first (e.g. leftover from /connect) */ + if (s->use_tls && s->ssl) { SSL_shutdown(s->ssl); SSL_free(s->ssl); s->ssl=NULL; } + if (s->sock>=0) { close(s->sock); s->sock=-1; } char portstr[8]; snprintf(portstr,sizeof(portstr),"%d",s->port); struct addrinfo hints,*res,*r; memset(&hints,0,sizeof(hints)); @@ -962,7 +975,7 @@ static int srv_connect(int si) { int fd=socket(r->ai_family,r->ai_socktype,r->ai_protocol); if (fd<0) continue; int flags=fcntl(fd,F_GETFL,0); - fcntl(fd,F_SETFL,flags|O_NONBLOCK); + if (flags>=0) fcntl(fd,F_SETFL,flags|O_NONBLOCK); int rc=connect(fd,r->ai_addr,r->ai_addrlen); if (rc==0||errno==EINPROGRESS) { fd_set wfds; FD_ZERO(&wfds); FD_SET(fd,&wfds); @@ -978,16 +991,20 @@ static int srv_connect(int si) { freeaddrinfo(res); if (s->sock<0) return -1; int flags=fcntl(s->sock,F_GETFL,0); - fcntl(s->sock,F_SETFL,flags&~O_NONBLOCK); + if (flags>=0) fcntl(s->sock,F_SETFL,flags&~O_NONBLOCK); if (s->use_tls) { + pthread_mutex_lock(&g_ssl_mutex); if (!g_ssl_ctx) { SSL_library_init(); SSL_load_error_strings(); g_ssl_ctx=SSL_CTX_new(TLS_client_method()); - if (!g_ssl_ctx) return -1; - SSL_CTX_set_verify(g_ssl_ctx,SSL_VERIFY_PEER,NULL); - SSL_CTX_set_default_verify_paths(g_ssl_ctx); + if (g_ssl_ctx) { + SSL_CTX_set_verify(g_ssl_ctx,SSL_VERIFY_PEER,NULL); + SSL_CTX_set_default_verify_paths(g_ssl_ctx); + } } + pthread_mutex_unlock(&g_ssl_mutex); + if (!g_ssl_ctx) return -1; s->ssl=SSL_new(g_ssl_ctx); SSL_set_fd(s->ssl,s->sock); SSL_set_tlsext_host_name(s->ssl,s->host); @@ -1004,6 +1021,10 @@ static void *net_thread(void *arg) { NetArg *na=(NetArg*)arg; int si=na->si; free(na); Server *s=&g_srv[si]; + /* snapshot config that may be changed concurrently by main thread */ + char mynick[MAX_NICK]; + strncpy(mynick, s->nick, MAX_NICK-1); mynick[MAX_NICK-1]='\0'; + char buf[MAX_LINE]; snprintf(buf,sizeof(buf),"Connecting to %s:%d%s...", s->host,s->port,s->use_tls?" (TLS)":""); @@ -1021,14 +1042,15 @@ static void *net_thread(void *arg) { /* 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); + srv_sendf(si,"NICK %s",mynick); + srv_sendf(si,"USER %s 0 * :sirc",mynick); } char readbuf[8192], linebuf[MAX_LINE*4]; int linelen=0; time_t last_ping=time(NULL); + int intentional = 0; while (!s->net_stop) { if (time(NULL)-last_ping>PING_INTERVAL) { srv_sendf(si,"PING :%s",s->host); @@ -1062,8 +1084,11 @@ static void *net_thread(void *arg) { last_ping=time(NULL); } - srv_close(si); - ev_simple(EV_RECONNECT, si, NULL, NULL, NULL); + if (s->net_stop) intentional = 1; + if (!intentional) { + srv_close(si); + ev_simple(EV_RECONNECT, si, NULL, NULL, NULL); + } return NULL; } @@ -1083,7 +1108,9 @@ static void *reconnect_thread(void *arg) { static void start_net_thread(int si) { g_srv[si].net_stop=0; NetArg *na=malloc(sizeof(NetArg)); na->si=si; - pthread_create(&g_srv[si].net_tid, NULL, net_thread, na); + pthread_t tid; + pthread_create(&tid, NULL, net_thread, na); + pthread_detach(tid); } static void schedule_reconnect(int si) { @@ -1110,7 +1137,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,"circ_user",MAX_NICK-1); + strncpy(s->nick,"sirc_user",MAX_NICK-1); s->use_tls=1; s->sock=-1; pthread_mutex_init(&s->send_lock,NULL); @@ -1140,7 +1167,7 @@ static void srv_add_autojoin(int si, const char *chanlist) { } static void load_config(const char *path) { - char candidates[2][MAX_HOST]; + char candidates[2][MAX_HOST] = {{0},{0}}; const char *home=getenv("HOME"); if (home) { snprintf(candidates[0],MAX_HOST,"%s/.sirc",home); @@ -1152,7 +1179,7 @@ static void load_config(const char *path) { if (!f) return; /* defaults that apply before any [server] block */ - char def_nick[MAX_NICK]="circ_user"; + char def_nick[MAX_NICK]="sirc_user"; /* current server being parsed; -1 = not inside a [server] block */ int cur_si=-1; @@ -1460,6 +1487,10 @@ static void draw_input(void) { } static void draw_all(void) { + if (g_active < 0 || g_chan_count == 0) { + /* no channels yet — only draw input line */ + draw_input(); doupdate(); return; + } draw_status_bar(); draw_channels(); draw_chat(); draw_users(); draw_input(); doupdate(); } @@ -1552,6 +1583,7 @@ static void handle_event(const Event *ev) { if (ev->chan[0]) { int ci=chan_find(si,ev->chan); if (ci<0) ci=chan_add(si,ev->chan); + if (ci<0) ci=srv_status_chan(si); chan_addline(&g_chans[ci],F_STATUS,ev->text); if (ci==g_active) g_chans[ci].unread=0; } else { @@ -1568,6 +1600,7 @@ static void handle_event(const Event *ev) { const char *target=ev->chan, *text=ev->text; const char *dest=(target[0]=='#')?target:ev->nick; int ci=chan_find(si,dest); if(ci<0) ci=chan_add(si,dest); + if (ci<0) ci=srv_status_chan(si); if (text[0]=='\x01'&&strncmp(text+1,"ACTION",6)==0) { const char *act=text+8; @@ -1593,6 +1626,7 @@ static void handle_event(const Event *ev) { case EV_JOIN: { int ci=chan_find(si,ev->chan); if(ci<0) ci=chan_add(si,ev->chan); + if (ci<0) ci=srv_status_chan(si); if (strcmp(ev->nick,s->nick)==0) { g_chans[ci].user_count=0; g_active=ci; @@ -1602,6 +1636,7 @@ static void handle_event(const Event *ev) { char msg[MAX_LINE]; snprintf(msg,sizeof(msg),"-> %s joined",ev->nick); chan_addline(&g_chans[ci],F_JOIN,msg); chan_adduser(&g_chans[ci], 0, ev->nick); + chan_sort_users(&g_chans[ci]); } break; } @@ -1619,8 +1654,11 @@ static void handle_event(const Event *ev) { for (int i=0;inick)==0){found=1;break;} + for (int j=0;jnick)==0){found=1;break;} + } if (found) { char msg[MAX_LINE]; snprintf(msg,sizeof(msg),"<- %s quit (%s)",ev->nick,ev->text); chan_addline(&g_chans[i],F_JOIN,msg); @@ -1669,7 +1707,8 @@ static void handle_event(const Event *ev) { char msg[MAX_LINE]; snprintf(msg,sizeof(msg),"X %s was kicked by %s (%s)",ev->extra,ev->nick,ev->text); chan_addline(&g_chans[ci],F_JOIN,msg); - chan_removeuser(&g_chans[ci],ev->extra); + if (strcmp(ev->extra,s->nick)==0) chan_remove(ci); + else chan_removeuser(&g_chans[ci],ev->extra); break; } @@ -1768,16 +1807,18 @@ static void process_input(const char *text) { if (strcmp(cmd,"/join")==0) { char ch[MAX_CHAN]; strncpy(ch,arg[0]?arg:g_srv[si].autojoin_count?g_srv[si].autojoin[0]:"#general",MAX_CHAN-1); if(ch[0]!='#'){memmove(ch+1,ch,strlen(ch)+1);ch[0]='#';} - chan_add(si,ch); + if(chan_add(si,ch)<0) srv_status_msg(si,"Channel list full."); if(s->connected) srv_sendf(si,"JOIN %s",ch); else srv_status_msg(si,"[not connected]"); } else if (strcmp(cmd,"/part")==0) { + if(g_active<0||g_active>=g_chan_count){srv_status_msg(si,"Not in a channel.");return;} const char *ch=arg[0]?arg:g_chans[g_active].name; if(s->connected) srv_sendf(si,"PART %s :bye",ch); else srv_status_msg(si,"[not connected]"); } else if (strcmp(cmd,"/cycle")==0) { + if(g_active<0||g_active>=g_chan_count){srv_status_msg(si,"Not in a channel.");return;} const char *ch=arg[0]?arg:g_chans[g_active].name; if(ch[0]!='#'){srv_status_msg(si,"Not in a channel.");return;} if(!s->connected){srv_status_msg(si,"[not connected]");return;} @@ -1795,13 +1836,14 @@ static void process_input(const char *text) { } else if (strcmp(cmd,"/msg")==0) { char tgt[MAX_NICK],msg[MAX_LINE]; if(sscanf(arg,"%63s %[^\n]",tgt,msg)==2) { - chan_add(si,tgt); + int ci=chan_add(si,tgt); + if(ci<0) ci=srv_status_chan(si); if(s->connected) srv_sendf(si,"PRIVMSG %s :%s",tgt,msg); - int ci=chan_find(si,tgt); - if(ci>=0){ char full[MAX_LINE]; snprintf(full,sizeof(full),"<%s> %s",s->nick,msg); chan_addline(&g_chans[ci],F_ME,full); } + { char full[MAX_LINE]; snprintf(full,sizeof(full),"<%s> %s",s->nick,msg); chan_addline(&g_chans[ci],F_ME,full); } } } else if (strcmp(cmd,"/me")==0) { + if(g_active<0||g_active>=g_chan_count){srv_status_msg(si,"Not in a channel.");return;} const char *cn=g_chans[g_active].name; if(cn[0]=='*'){srv_status_msg(si,"Not in a channel.");return;} if(!s->connected){srv_status_msg(si,"[not connected]");return;} @@ -1827,6 +1869,7 @@ static void process_input(const char *text) { } else srv_status_msg(si,"Usage: /ctcp "); } else if (strcmp(cmd,"/names")==0) { + if(g_active<0||g_active>=g_chan_count){srv_status_msg(si,"Not in a channel.");return;} const char *ch=arg[0]?arg:g_chans[g_active].name; if(ch[0]=='*'){srv_status_msg(si,"Not in a channel.");return;} if(s->connected) srv_sendf(si,"NAMES %s",ch); @@ -1845,6 +1888,7 @@ static void process_input(const char *text) { } } else if (strcmp(cmd,"/topic")==0) { + if(g_active<0||g_active>=g_chan_count){srv_status_msg(si,"Not in a channel.");return;} const char *ch=g_chans[g_active].name; if(ch[0]=='*'){srv_status_msg(si,"Not in a channel.");return;} if(!s->connected){srv_status_msg(si,"[not connected]");return;} @@ -1857,6 +1901,7 @@ static void process_input(const char *text) { srv_sendf(si,"WHOIS %s",arg); } else if (strcmp(cmd,"/who")==0) { + if(g_active<0||g_active>=g_chan_count){srv_status_msg(si,"Not in a channel.");return;} const char *tgt=arg[0]?arg:g_chans[g_active].name; if(!s->connected){srv_status_msg(si,"[not connected]");return;} srv_sendf(si,"WHO %s",tgt); @@ -1875,6 +1920,7 @@ static void process_input(const char *text) { srv_send_raw(si,"AWAY"); } else if (strcmp(cmd,"/invite")==0) { + if(g_active<0||g_active>=g_chan_count){srv_status_msg(si,"Not in a channel.");return;} char tgt[MAX_NICK],ch[MAX_CHAN]; ch[0]='\0'; if(sscanf(arg,"%63s %63s",tgt,ch)<1){srv_status_msg(si,"Usage: /invite [#chan]");return;} if(!ch[0]) strncpy(ch,g_chans[g_active].name,MAX_CHAN-1); @@ -1892,7 +1938,11 @@ static void process_input(const char *text) { } else if (strcmp(cmd,"/mode")==0) { if(!s->connected){srv_status_msg(si,"[not connected]");return;} - if(!arg[0]) srv_sendf(si,"MODE %s",g_chans[g_active].name); + if(!arg[0]) { + if(g_active<0||g_active>=g_chan_count||g_chans[g_active].name[0]=='*') + {srv_status_msg(si,"Not in a channel.");return;} + srv_sendf(si,"MODE %s",g_chans[g_active].name); + } else srv_sendf(si,"MODE %s",arg); } else if (strcmp(cmd,"/raw")==0||strcmp(cmd,"/quote")==0) { @@ -1911,7 +1961,11 @@ static void process_input(const char *text) { g_srv[nsi].port=newport; /* inherit nick from current server */ strncpy(g_srv[nsi].nick,s->nick,MAX_NICK-1); - chan_add(nsi,"*status*"); + if(chan_add(nsi,"*status*")<0) { + srv_status_msg(si,"Channel list full."); + g_srv_count--; /* undo srv_alloc */ + return; + } start_net_thread(nsi); srv_status_fmt(nsi,"Connecting to %s:%d...",newhost,newport); @@ -1933,6 +1987,7 @@ static void process_input(const char *text) { else srv_status_fmt(si,"%s is not ignored",arg); } else if (strcmp(cmd,"/clear")==0) { + if(g_active<0||g_active>=g_chan_count) return; g_chans[g_active].line_count=0; g_chans[g_active].line_head=0; g_chans[g_active].scroll=0; @@ -1999,10 +2054,17 @@ static void handle_key(int key) { if (key=='\t') { tab_complete(); return; } tab_reset(); + if (g_active<0||g_active>=g_chan_count) { + /* no channels — only accept resize and printable input */ + if (key=='\n'||key=='\r'||key==KEY_ENTER) { + g_input[0]='\0'; g_input_len=0; g_input_cur=0; + } + return; + } Channel *ch=&g_chans[g_active]; - if (key==14) { g_active=chan_next(g_active,1); g_chans[g_active].unread=0; g_chans[g_active].mention=0; return; } - if (key==16) { g_active=chan_next(g_active,-1); g_chans[g_active].unread=0; g_chans[g_active].mention=0; return; } + if (key==14) { g_active=chan_next(g_active,1); if(g_active>=0){g_chans[g_active].unread=0; g_chans[g_active].mention=0;} return; } + if (key==16) { g_active=chan_next(g_active,-1); if(g_active>=0){g_chans[g_active].unread=0; g_chans[g_active].mention=0;} return; } if (key==KEY_PPAGE) { int ch2; getmaxyx(win_chat,ch2,(int){0}); ch->scroll+=ch2/2; return; } if (key==KEY_NPAGE) { int ch2; getmaxyx(win_chat,ch2,(int){0}); ch->scroll-=ch2/2; if(ch->scroll<0)ch->scroll=0; return; } @@ -2118,8 +2180,11 @@ int main(int argc, char **argv) { if (cli_chan && !cli_host) srv_add_autojoin(i, cli_chan); } - /* create status channels */ - for(int i=0;i 0) g_active = 0; /* event pipe */ if(pipe(g_evpipe)<0){perror("pipe");return 1;} -- cgit v1.2.3