d9bda20849
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.
225 lines
4.3 KiB
C
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;
|
|
}
|