sic/sic.c
Hiltjo Posthuma d9bda20849 fix undefined behaviour of using isspace ctype function
cast all ctype(3) functions argument to (unsigned char) to avoid UB

POSIX says:
"The c argument is an int, the value of which the application shall ensure is a
character representable as an unsigned char or equal to the value of the macro
EOF. If the argument has any other value, the behavior is undefined."

Many libc cast implicitly the value, but NetBSD for example does not, which is
probably the correct thing to interpret it.
2021-05-06 01:09:21 +02:00

225 lines
4.3 KiB
C

/* See LICENSE file for license details. */
#include <sys/select.h>
#include <ctype.h>
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include "arg.h"
#include "config.h"
char *argv0;
static char *host = DEFAULT_HOST;
static char *port = DEFAULT_PORT;
static char *password;
static char nick[32];
static char bufin[4096];
static char bufout[4096];
static char channel[256];
static time_t trespond;
static FILE *srv;
#undef strlcpy
#include "strlcpy.c"
#include "util.c"
static void
pout(char *channel, char *fmt, ...) {
static char timestr[80];
time_t t;
va_list ap;
va_start(ap, fmt);
vsnprintf(bufout, sizeof bufout, fmt, ap);
va_end(ap);
t = time(NULL);
strftime(timestr, sizeof timestr, TIMESTAMP_FORMAT, localtime(&t));
fprintf(stdout, "%-12s: %s %s\n", channel, timestr, bufout);
}
static void
sout(char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
vsnprintf(bufout, sizeof bufout, fmt, ap);
va_end(ap);
fprintf(srv, "%s\r\n", bufout);
}
static void
privmsg(char *channel, char *msg) {
if(channel[0] == '\0') {
pout("", "No channel to send to");
return;
}
pout(channel, "<%s> %s", nick, msg);
sout("PRIVMSG %s :%s", channel, msg);
}
static void
parsein(char *s) {
char c, *p;
if(s[0] == '\0')
return;
skip(s, '\n');
if(s[0] != COMMAND_PREFIX_CHARACTER) {
privmsg(channel, s);
return;
}
c = *++s;
if(c != '\0' && isspace((unsigned char)s[1])) {
p = s + 2;
switch(c) {
case 'j':
sout("JOIN %s", p);
if(channel[0] == '\0')
strlcpy(channel, p, sizeof channel);
return;
case 'l':
s = eat(p, isspace, 1);
p = eat(s, isspace, 0);
if(!*s)
s = channel;
if(*p)
*p++ = '\0';
if(!*p)
p = DEFAULT_PARTING_MESSAGE;
sout("PART %s :%s", s, p);
return;
case 'm':
s = eat(p, isspace, 1);
p = eat(s, isspace, 0);
if(*p)
*p++ = '\0';
privmsg(s, p);
return;
case 's':
strlcpy(channel, p, sizeof channel);
return;
}
}
sout("%s", s);
}
static void
parsesrv(char *cmd) {
char *usr, *par, *txt;
usr = host;
if(!cmd || !*cmd)
return;
if(cmd[0] == ':') {
usr = cmd + 1;
cmd = skip(usr, ' ');
if(cmd[0] == '\0')
return;
skip(usr, '!');
}
skip(cmd, '\r');
par = skip(cmd, ' ');
txt = skip(par, ':');
trim(par);
if(!strcmp("PONG", cmd))
return;
if(!strcmp("PRIVMSG", cmd))
pout(par, "<%s> %s", usr, txt);
else if(!strcmp("PING", cmd))
sout("PONG %s", txt);
else {
pout(usr, ">< %s (%s): %s", cmd, par, txt);
if(!strcmp("NICK", cmd) && !strcmp(usr, nick))
strlcpy(nick, txt, sizeof nick);
}
}
static void
usage(void) {
eprint("usage: sic [-h host] [-p port] [-n nick] [-k keyword] [-v]\n", argv0);
}
int
main(int argc, char *argv[]) {
struct timeval tv;
const char *user = getenv("USER");
int n;
fd_set rd;
strlcpy(nick, user ? user : "unknown", sizeof nick);
ARGBEGIN {
case 'h':
host = EARGF(usage());
break;
case 'p':
port = EARGF(usage());
break;
case 'n':
strlcpy(nick, EARGF(usage()), sizeof nick);
break;
case 'k':
password = EARGF(usage());
break;
case 'v':
eprint("sic-"VERSION", © 2005-2014 Kris Maglione, Anselm R. Garbe, Nico Golde\n");
break;
default:
usage();
} ARGEND;
/* init */
srv = fdopen(dial(host, port), "r+");
if (!srv)
eprint("fdopen:");
/* login */
if(password)
sout("PASS %s", password);
sout("NICK %s", nick);
sout("USER %s localhost %s :%s", nick, host, nick);
fflush(srv);
setbuf(stdout, NULL);
setbuf(srv, NULL);
setbuf(stdin, NULL);
#ifdef __OpenBSD__
if (pledge("stdio", NULL) == -1)
eprint("error: pledge:");
#endif
for(;;) { /* main loop */
FD_ZERO(&rd);
FD_SET(0, &rd);
FD_SET(fileno(srv), &rd);
tv.tv_sec = 120;
tv.tv_usec = 0;
n = select(fileno(srv) + 1, &rd, 0, 0, &tv);
if(n < 0) {
if(errno == EINTR)
continue;
eprint("sic: error on select():");
}
else if(n == 0) {
if(time(NULL) - trespond >= 300)
eprint("sic shutting down: parse timeout\n");
sout("PING %s", host);
continue;
}
if(FD_ISSET(fileno(srv), &rd)) {
if(fgets(bufin, sizeof bufin, srv) == NULL)
eprint("sic: remote host closed connection\n");
parsesrv(bufin);
trespond = time(NULL);
}
if(FD_ISSET(0, &rd)) {
if(fgets(bufin, sizeof bufin, stdin) == NULL)
eprint("sic: broken pipe\n");
parsein(bufin);
}
}
return 0;
}