this is my patched version of ii - the suckless irc client.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

525 lines
15 KiB

9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
  1. /* (C)opyright MMV-MMVI Anselm R. Garbe <garbeam at gmail dot com>
  2. * (C)opyright MMV-MMXI Nico Golde <nico at ngolde dot de>
  3. * See LICENSE file for license details. */
  4. #include <errno.h>
  5. #include <netdb.h>
  6. #include <sys/types.h>
  7. #include <sys/stat.h>
  8. #include <sys/socket.h>
  9. #include <sys/select.h>
  10. #include <netinet/in.h>
  11. #include <stdio.h>
  12. #include <stdlib.h>
  13. #include <limits.h>
  14. #include <fcntl.h>
  15. #include <string.h>
  16. #include <pwd.h>
  17. #include <signal.h>
  18. #include <ctype.h>
  19. #include <time.h>
  20. #include <unistd.h>
  21. #include <openssl/rand.h>
  22. #include <openssl/ssl.h>
  23. #include <openssl/err.h>
  24. #ifndef PIPE_BUF /* FreeBSD don't know PIPE_BUF */
  25. #define PIPE_BUF 4096
  26. #endif
  27. #define PING_TIMEOUT 300
  28. #define SERVER_PORT 6667
  29. #define SSL_SERVER_PORT 6697
  30. #define WRITE(con, mes, len) (use_ssl ? SSL_write(irc->sslHandle, mes, len) : write(con->irc, mes, len))
  31. #define READ(fd, buf, size) (from_server && use_ssl ? SSL_read(irc->sslHandle, buf, size) : read(fd, buf, size))
  32. typedef struct {
  33. int irc;
  34. SSL *sslHandle;
  35. SSL_CTX *sslContext;
  36. } conn;
  37. enum { TOK_NICKSRV = 0, TOK_USER, TOK_CMD, TOK_CHAN, TOK_ARG, TOK_TEXT, TOK_LAST };
  38. typedef struct Channel Channel;
  39. struct Channel {
  40. int fd;
  41. char *name;
  42. Channel *next;
  43. };
  44. conn *irc;
  45. static int use_ssl;
  46. static time_t last_response;
  47. static Channel *channels = NULL;
  48. static char *host = "irc.freenode.net";
  49. static char nick[32]; /* might change while running */
  50. static char path[_POSIX_PATH_MAX];
  51. static char message[PIPE_BUF]; /* message buf used for communication */
  52. static void usage() {
  53. fputs("ii - irc it - " VERSION "\n"
  54. "(C)opyright MMV-MMVI Anselm R. Garbe\n"
  55. "(C)opyright MMV-MMXI Nico Golde\n"
  56. "usage: ii [-i <irc dir>] [-s <host>] [-p <port>] [-e ssl]\n"
  57. " [-n <nick>] [-k <password>] [-f <fullname>]\n", stderr);
  58. exit(EXIT_FAILURE);
  59. }
  60. static char *striplower(char *s) {
  61. char *p = NULL;
  62. for(p = s; p && *p; p++) {
  63. if(*p == '/') *p = ',';
  64. *p = tolower(*p);
  65. }
  66. return s;
  67. }
  68. /* creates directories top-down, if necessary */
  69. static void create_dirtree(const char *dir) {
  70. char tmp[256];
  71. char *p = NULL;
  72. size_t len;
  73. snprintf(tmp, sizeof(tmp),"%s",dir);
  74. len = strlen(tmp);
  75. if(tmp[len - 1] == '/')
  76. tmp[len - 1] = 0;
  77. for(p = tmp + 1; *p; p++)
  78. if(*p == '/') {
  79. *p = 0;
  80. mkdir(tmp, S_IRWXU);
  81. *p = '/';
  82. }
  83. mkdir(tmp, S_IRWXU);
  84. }
  85. static int get_filepath(char *filepath, size_t len, char *channel, char *file) {
  86. if(channel) {
  87. if(!snprintf(filepath, len, "%s/%s", path, channel))
  88. return 0;
  89. create_dirtree(filepath);
  90. return snprintf(filepath, len, "%s/%s/%s", path, channel, file);
  91. }
  92. return snprintf(filepath, len, "%s/%s", path, file);
  93. }
  94. static void create_filepath(char *filepath, size_t len, char *channel, char *suffix) {
  95. if(!get_filepath(filepath, len, striplower(channel), suffix)) {
  96. fputs("ii: path to irc directory too long\n", stderr);
  97. exit(EXIT_FAILURE);
  98. }
  99. }
  100. static int open_channel(char *name) {
  101. static char infile[256];
  102. create_filepath(infile, sizeof(infile), name, "in");
  103. if(access(infile, F_OK) == -1)
  104. mkfifo(infile, S_IRWXU);
  105. return open(infile, O_RDONLY | O_NONBLOCK, 0);
  106. }
  107. static void add_channel(char *cname) {
  108. Channel *c;
  109. int fd;
  110. char *name = striplower(cname);
  111. for(c = channels; c; c = c->next)
  112. if(!strcmp(name, c->name))
  113. return; /* already handled */
  114. fd = open_channel(name);
  115. if(fd == -1) {
  116. printf("ii: exiting, cannot create in channel: %s\n", name);
  117. exit(EXIT_FAILURE);
  118. }
  119. c = calloc(1, sizeof(Channel));
  120. if(!c) {
  121. perror("ii: cannot allocate memory");
  122. exit(EXIT_FAILURE);
  123. }
  124. if(!channels) channels = c;
  125. else {
  126. c->next = channels;
  127. channels = c;
  128. }
  129. c->fd = fd;
  130. c->name = strdup(name);
  131. }
  132. static void rm_channel(Channel *c) {
  133. Channel *p;
  134. if(channels == c) channels = channels->next;
  135. else {
  136. for(p = channels; p && p->next != c; p = p->next);
  137. if(p->next == c)
  138. p->next = c->next;
  139. }
  140. free(c->name);
  141. free(c);
  142. }
  143. static void login(char *key, char *fullname) {
  144. if(key) snprintf(message, PIPE_BUF,
  145. "PASS %s\r\nNICK %s\r\nUSER %s localhost %s :%s\r\n", key,
  146. nick, nick, host, fullname ? fullname : nick);
  147. else snprintf(message, PIPE_BUF, "NICK %s\r\nUSER %s localhost %s :%s\r\n",
  148. nick, nick, host, fullname ? fullname : nick);
  149. WRITE(irc, message, strlen(message)); /* login */
  150. }
  151. conn *tcpopen(unsigned short port) {
  152. int fd;
  153. conn *c;
  154. struct sockaddr_in sin;
  155. struct hostent *hp = gethostbyname(host);
  156. memset(&sin, 0, sizeof(struct sockaddr_in));
  157. if(!hp) {
  158. perror("ii: cannot retrieve host information");
  159. exit(EXIT_FAILURE);
  160. }
  161. sin.sin_family = AF_INET;
  162. memcpy(&sin.sin_addr, hp->h_addr, hp->h_length);
  163. sin.sin_port = htons(port);
  164. if((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
  165. perror("ii: cannot create socket");
  166. exit(EXIT_FAILURE);
  167. }
  168. if(connect(fd, (const struct sockaddr *) &sin, sizeof(sin)) < 0) {
  169. perror("ii: cannot connect to host");
  170. exit(EXIT_FAILURE);
  171. }
  172. c = malloc(sizeof(conn));
  173. c->irc = fd;
  174. if(use_ssl) {
  175. c->sslHandle = NULL;
  176. c->sslContext = NULL;
  177. SSL_load_error_strings();
  178. SSL_library_init();
  179. c->sslContext = SSL_CTX_new(SSLv23_client_method());
  180. if(c->sslContext == NULL)
  181. ERR_print_errors_fp(stderr);
  182. c->sslHandle = SSL_new(c->sslContext);
  183. if(!SSL_set_fd(c->sslHandle, c->irc)
  184. || (SSL_connect(c->sslHandle) != 1))
  185. ERR_print_errors_fp(stderr);
  186. }
  187. return c;
  188. }
  189. static size_t tokenize(char **result, size_t reslen, char *str, char delim) {
  190. char *p = NULL, *n = NULL;
  191. size_t i;
  192. if(!str)
  193. return 0;
  194. for(n = str; *n == ' '; n++);
  195. p = n;
  196. for(i = 0; *n != 0;) {
  197. if(i == reslen)
  198. return 0;
  199. if(i > TOK_CHAN - TOK_CMD && strtol(result[0], NULL, 10) > 0) delim=':'; /* workaround non-RFC compliant messages */
  200. if(*n == delim) {
  201. *n = 0;
  202. result[i++] = p;
  203. p = ++n;
  204. } else
  205. n++;
  206. }
  207. if(i<reslen && p < n && strlen(p))
  208. result[i++] = p;
  209. return i; /* number of tokens */
  210. }
  211. static void print_out(char *channel, char *buf) {
  212. static char outfile[256], server[256], buft[18];
  213. FILE *out = NULL;
  214. time_t t = time(0);
  215. if(channel) snprintf(server, sizeof(server), "-!- %s", channel);
  216. if(strstr(buf, server)) channel="";
  217. create_filepath(outfile, sizeof(outfile), channel, "out");
  218. if(!(out = fopen(outfile, "a"))) return;
  219. if(channel && channel[0]) add_channel(channel);
  220. strftime(buft, sizeof(buft), "%F %R", localtime(&t));
  221. fprintf(out, "%s %s\n", buft, buf);
  222. fclose(out);
  223. }
  224. static void proc_channels_privmsg(char *channel, char *buf) {
  225. snprintf(message, PIPE_BUF, "<%s> %s", nick, buf);
  226. print_out(channel, message);
  227. snprintf(message, PIPE_BUF, "PRIVMSG %s :%s\r\n", channel, buf);
  228. WRITE(irc, message, strlen(message));
  229. }
  230. static void proc_channels_input(Channel *c, char *buf) {
  231. /* static char infile[256]; */
  232. char *p = NULL;
  233. if(buf[0] != '/' && buf[0] != 0) {
  234. proc_channels_privmsg(c->name, buf);
  235. return;
  236. }
  237. message[0] = '\0';
  238. if(buf[2] == ' ' || buf[2] == '\0') switch (buf[1]) {
  239. case 'j':
  240. p = strchr(&buf[3], ' ');
  241. if(p) *p = 0;
  242. if((buf[3]=='#')||(buf[3]=='&')||(buf[3]=='+')||(buf[3]=='!')){
  243. if(p) snprintf(message, PIPE_BUF, "JOIN %s %s\r\n", &buf[3], p + 1); /* password protected channel */
  244. else snprintf(message, PIPE_BUF, "JOIN %s\r\n", &buf[3]);
  245. add_channel(&buf[3]);
  246. }
  247. else if(p){
  248. add_channel(&buf[3]);
  249. proc_channels_privmsg(&buf[3], p + 1);
  250. return;
  251. }
  252. break;
  253. case 't':
  254. if(strlen(buf)>=3) snprintf(message, PIPE_BUF, "TOPIC %s :%s\r\n", c->name, &buf[3]);
  255. break;
  256. case 'a':
  257. if(strlen(buf)>=3){
  258. snprintf(message, PIPE_BUF, "-!- %s is away \"%s\"", nick, &buf[3]);
  259. print_out(c->name, message);
  260. }
  261. if(buf[2] == 0 || strlen(buf)<3) /* or used to make else part safe */
  262. snprintf(message, PIPE_BUF, "AWAY\r\n");
  263. else
  264. snprintf(message, PIPE_BUF, "AWAY :%s\r\n", &buf[3]);
  265. break;
  266. case 'n':
  267. if(strlen(buf)>=3){
  268. snprintf(nick, sizeof(nick),"%s", &buf[3]);
  269. snprintf(message, PIPE_BUF, "NICK %s\r\n", &buf[3]);
  270. }
  271. break;
  272. case 'l':
  273. if(c->name[0] == 0)
  274. return;
  275. if(buf[2] == ' ' && strlen(buf)>=3)
  276. snprintf(message, PIPE_BUF, "PART %s :%s\r\n", c->name, &buf[3]);
  277. else
  278. snprintf(message, PIPE_BUF,
  279. "PART %s :ii - 500 SLOC are too much\r\n", c->name);
  280. WRITE(irc, message, strlen(message));
  281. close(c->fd);
  282. /*create_filepath(infile, sizeof(infile), c->name, "in");
  283. unlink(infile); */
  284. rm_channel(c);
  285. return;
  286. break;
  287. default:
  288. snprintf(message, PIPE_BUF, "%s\r\n", &buf[1]);
  289. break;
  290. }
  291. else
  292. snprintf(message, PIPE_BUF, "%s\r\n", &buf[1]);
  293. if (message[0] != '\0')
  294. WRITE(irc, message, strlen(message));
  295. }
  296. static void proc_server_cmd(char *buf) {
  297. char *argv[TOK_LAST], *cmd = NULL, *p = NULL;
  298. int i;
  299. if(!buf || *buf=='\0')
  300. return;
  301. for(i = 0; i < TOK_LAST; i++)
  302. argv[i] = NULL;
  303. /* <message> ::= [':' <prefix> <SPACE> ] <command> <params> <crlf>
  304. <prefix> ::= <servername> | <nick> [ '!' <user> ] [ '@' <host> ]
  305. <command> ::= <letter> { <letter> } | <number> <number> <number>
  306. <SPACE> ::= ' ' { ' ' }
  307. <params> ::= <SPACE> [ ':' <trailing> | <middle> <params> ]
  308. <middle> ::= <Any *non-empty* sequence of octets not including SPACE
  309. or NUL or CR or LF, the first of which may not be ':'>
  310. <trailing> ::= <Any, possibly *empty*, sequence of octets not including NUL or CR or LF>
  311. <crlf> ::= CR LF */
  312. if(buf[0] == ':') { /* check prefix */
  313. if (!(p = strchr(buf, ' '))) return;
  314. *p = 0;
  315. for(++p; *p == ' '; p++);
  316. cmd = p;
  317. argv[TOK_NICKSRV] = &buf[1];
  318. if((p = strchr(buf, '!'))) {
  319. *p = 0;
  320. argv[TOK_USER] = ++p;
  321. }
  322. } else
  323. cmd = buf;
  324. /* remove CRLFs */
  325. for(p = cmd; p && *p != 0; p++)
  326. if(*p == '\r' || *p == '\n')
  327. *p = 0;
  328. if((p = strchr(cmd, ':'))) {
  329. *p = 0;
  330. argv[TOK_TEXT] = ++p;
  331. }
  332. tokenize(&argv[TOK_CMD], TOK_LAST - TOK_CMD, cmd, ' ');
  333. if(!argv[TOK_CMD] || !strncmp("PONG", argv[TOK_CMD], 5)) {
  334. return;
  335. } else if(!strncmp("PING", argv[TOK_CMD], 5)) {
  336. snprintf(message, PIPE_BUF, "PONG %s\r\n", argv[TOK_TEXT]);
  337. WRITE(irc, message, strlen(message));
  338. return;
  339. } else if(!argv[TOK_NICKSRV] || !argv[TOK_USER]) { /* server command */
  340. snprintf(message, PIPE_BUF, "%s%s", argv[TOK_ARG] ? argv[TOK_ARG] : "", argv[TOK_TEXT] ? argv[TOK_TEXT] : "");
  341. print_out(0, message);
  342. return;
  343. } else if(!strncmp("ERROR", argv[TOK_CMD], 6))
  344. snprintf(message, PIPE_BUF, "-!- error %s", argv[TOK_TEXT] ? argv[TOK_TEXT] : "unknown");
  345. else if(!strncmp("JOIN", argv[TOK_CMD], 5)) {
  346. if (argv[TOK_TEXT] != NULL)
  347. argv[TOK_CHAN] = argv[TOK_TEXT];
  348. snprintf(message, PIPE_BUF, "-!- %s(%s) has joined %s", argv[TOK_NICKSRV], argv[TOK_USER], argv[TOK_CHAN]);
  349. } else if(!strncmp("PART", argv[TOK_CMD], 5)) {
  350. snprintf(message, PIPE_BUF, "-!- %s(%s) has left %s", argv[TOK_NICKSRV], argv[TOK_USER], argv[TOK_CHAN]);
  351. } else if(!strncmp("MODE", argv[TOK_CMD], 5))
  352. snprintf(message, PIPE_BUF, "-!- %s changed mode/%s -> %s %s", argv[TOK_NICKSRV], argv[TOK_CMD + 1] ? argv[TOK_CMD + 1] : "" , argv[TOK_CMD + 2]? argv[TOK_CMD + 2] : "", argv[TOK_CMD + 3] ? argv[TOK_CMD + 3] : "");
  353. else if(!strncmp("QUIT", argv[TOK_CMD], 5))
  354. snprintf(message, PIPE_BUF, "-!- %s(%s) has quit \"%s\"", argv[TOK_NICKSRV], argv[TOK_USER], argv[TOK_TEXT] ? argv[TOK_TEXT] : "");
  355. else if(!strncmp("NICK", argv[TOK_CMD], 5))
  356. snprintf(message, PIPE_BUF, "-!- %s changed nick to %s", argv[TOK_NICKSRV], argv[TOK_TEXT]);
  357. else if(!strncmp("TOPIC", argv[TOK_CMD], 6))
  358. snprintf(message, PIPE_BUF, "-!- %s changed topic to \"%s\"", argv[TOK_NICKSRV], argv[TOK_TEXT] ? argv[TOK_TEXT] : "");
  359. else if(!strncmp("KICK", argv[TOK_CMD], 5))
  360. snprintf(message, PIPE_BUF, "-!- %s kicked %s (\"%s\")", argv[TOK_NICKSRV], argv[TOK_ARG], argv[TOK_TEXT] ? argv[TOK_TEXT] : "");
  361. else if(!strncmp("NOTICE", argv[TOK_CMD], 7))
  362. snprintf(message, PIPE_BUF, "-!- \"%s\")", argv[TOK_TEXT] ? argv[TOK_TEXT] : "");
  363. else if(!strncmp("PRIVMSG", argv[TOK_CMD], 8))
  364. snprintf(message, PIPE_BUF, "<%s> %s", argv[TOK_NICKSRV], argv[TOK_TEXT] ? argv[TOK_TEXT] : "");
  365. if(!argv[TOK_CHAN] || !strncmp(argv[TOK_CHAN], nick, strlen(nick)))
  366. print_out(argv[TOK_NICKSRV], message);
  367. else
  368. print_out(argv[TOK_CHAN], message);
  369. }
  370. static int read_line(int fd, size_t res_len, char *buf, int from_server) {
  371. size_t i = 0;
  372. char c = 0;
  373. do {
  374. if(READ(fd, &c, sizeof(char)) != sizeof(char))
  375. return -1;
  376. buf[i++] = c;
  377. }
  378. while(c != '\n' && i < res_len);
  379. buf[i - 1] = 0; /* eliminates '\n' */
  380. return 0;
  381. }
  382. static void handle_channels_input(Channel *c) {
  383. static char buf[PIPE_BUF];
  384. if(read_line(c->fd, PIPE_BUF, buf, 0) == -1) {
  385. close(c->fd);
  386. int fd = open_channel(c->name);
  387. if(fd != -1)
  388. c->fd = fd;
  389. else
  390. rm_channel(c);
  391. return;
  392. }
  393. proc_channels_input(c, buf);
  394. }
  395. static void handle_server_output() {
  396. static char buf[PIPE_BUF];
  397. if(read_line(irc->irc, PIPE_BUF, buf, 1) == -1) {
  398. perror("ii: remote host closed connection");
  399. exit(EXIT_FAILURE);
  400. }
  401. proc_server_cmd(buf);
  402. }
  403. static void run() {
  404. Channel *c;
  405. int r, maxfd;
  406. fd_set rd;
  407. struct timeval tv;
  408. char ping_msg[512];
  409. snprintf(ping_msg, sizeof(ping_msg), "PING %s\r\n", host);
  410. for(;;) {
  411. FD_ZERO(&rd);
  412. maxfd = irc->irc;
  413. FD_SET(irc->irc, &rd);
  414. for(c = channels; c; c = c->next) {
  415. if(maxfd < c->fd)
  416. maxfd = c->fd;
  417. FD_SET(c->fd, &rd);
  418. }
  419. tv.tv_sec = 120;
  420. tv.tv_usec = 0;
  421. r = select(maxfd + 1, &rd, 0, 0, &tv);
  422. if(r < 0) {
  423. if(errno == EINTR)
  424. continue;
  425. perror("ii: error on select()");
  426. exit(EXIT_FAILURE);
  427. } else if(r == 0) {
  428. if(time(NULL) - last_response >= PING_TIMEOUT) {
  429. print_out(NULL, "-!- ii shutting down: ping timeout");
  430. exit(EXIT_FAILURE);
  431. }
  432. WRITE(irc, ping_msg, strlen(ping_msg));
  433. continue;
  434. }
  435. if(FD_ISSET(irc->irc, &rd)) {
  436. handle_server_output();
  437. last_response = time(NULL);
  438. }
  439. for(c = channels; c; c = c->next)
  440. if(FD_ISSET(c->fd, &rd))
  441. handle_channels_input(c);
  442. }
  443. }
  444. int main(int argc, char *argv[]) {
  445. int i;
  446. unsigned short port = SERVER_PORT;
  447. struct passwd *spw = getpwuid(getuid());
  448. char *key = NULL, *fullname = NULL;
  449. char prefix[_POSIX_PATH_MAX];
  450. if(!spw) {
  451. fputs("ii: getpwuid() failed\n", stderr);
  452. exit(EXIT_FAILURE);
  453. }
  454. snprintf(nick, sizeof(nick), "%s", spw->pw_name);
  455. snprintf(prefix, sizeof(prefix),"%s/irc", spw->pw_dir);
  456. if (argc <= 1 || (argc == 2 && argv[1][0] == '-' && argv[1][1] == 'h')) usage();
  457. for(i = 1; (i + 1 < argc) && (argv[i][0] == '-'); i++) {
  458. switch (argv[i][1]) {
  459. case 'i': snprintf(prefix,sizeof(prefix),"%s", argv[++i]); break;
  460. case 's': host = argv[++i]; break;
  461. case 'p': port = strtol(argv[++i], NULL, 10); break;
  462. case 'n': snprintf(nick,sizeof(nick),"%s", argv[++i]); break;
  463. case 'k': key = getenv(argv[++i]); break;
  464. case 'e': use_ssl = 1; ++i; break;
  465. case 'f': fullname = argv[++i]; break;
  466. default: usage(); break;
  467. }
  468. }
  469. if(use_ssl)
  470. port = port == SERVER_PORT ? SSL_SERVER_PORT : port;
  471. irc = tcpopen(port);
  472. if(!snprintf(path, sizeof(path), "%s/%s", prefix, host)) {
  473. fputs("ii: path to irc directory too long\n", stderr);
  474. exit(EXIT_FAILURE);
  475. }
  476. create_dirtree(path);
  477. add_channel(""); /* master channel */
  478. login(key, fullname);
  479. run();
  480. return EXIT_SUCCESS;
  481. }