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. */
6 #include <netinet/in.h>
14 #include <sys/socket.h>
19 #ifndef PIPE_BUF /* FreeBSD don't know PIPE_BUF */
22 #define PING_TIMEOUT 300
23 #define SERVER_PORT 6667
24 enum { TOK_NICKSRV
= 0, TOK_USER
, TOK_CMD
, TOK_CHAN
, TOK_ARG
, TOK_TEXT
, TOK_LAST
};
26 typedef struct Channel Channel
;
34 static time_t last_response
;
35 static Channel
*channels
= NULL
;
36 static char *host
= "irc.freenode.net";
37 static char nick
[32] = "anonymous"; /* might change while running */
38 static char path
[_POSIX_PATH_MAX
];
39 static char message
[PIPE_BUF
]; /* message buf used for communication */
43 "ii - irc it - " VERSION
"\n"
44 "(C)opyright MMV-MMVI Anselm R. Garbe\n"
45 "(C)opyright MMV-MMXI Nico Golde\n"
46 "usage: ii [-i <irc dir>] [-s <host>] [-p <port>]\n"
47 " [-n <nick>] [-k <password>] [-f <fullname>]\n"
48 " [-d <directory>]\n");
52 static char *striplower(char *s
) {
54 for(p
= s
; p
&& *p
; p
++) {
55 if(*p
== '/') *p
= '_';
61 /* creates directories top-down, if necessary */
62 static void create_dirtree(const char *dir
) {
67 snprintf(tmp
, sizeof(tmp
),"%s",dir
);
69 if(tmp
[len
- 1] == '/')
71 for(p
= tmp
+ 1; *p
; p
++)
80 static int get_filepath(char *filepath
, size_t len
, char *channel
, char *file
) {
82 if(!snprintf(filepath
, len
, "%s/%s", path
, channel
))
84 create_dirtree(filepath
);
85 return snprintf(filepath
, len
, "%s/%s/%s", path
, channel
, file
);
87 return snprintf(filepath
, len
, "%s/%s", path
, file
);
90 static void create_filepath(char *filepath
, size_t len
, char *channel
, char *suffix
) {
91 if(!get_filepath(filepath
, len
, striplower(channel
), suffix
)) {
92 fprintf(stderr
, "%s", "ii: path to irc directory too long\n");
97 static int open_channel(char *name
) {
98 static char infile
[256];
99 create_filepath(infile
, sizeof(infile
), name
, "in");
100 if(access(infile
, F_OK
) == -1)
101 mkfifo(infile
, S_IRWXU
);
102 return open(infile
, O_RDONLY
| O_NONBLOCK
, 0);
105 static void add_channel(char *cname
) {
108 char *name
= striplower(cname
);
110 for(c
= channels
; c
; c
= c
->next
)
111 if(!strcmp(name
, c
->name
))
112 return; /* already handled */
114 fd
= open_channel(name
);
116 printf("ii: exiting, cannot create in channel: %s\n", name
);
119 c
= calloc(1, sizeof(Channel
));
121 perror("ii: cannot allocate memory");
124 if(!channels
) channels
= c
;
130 c
->name
= strdup(name
);
133 static void rm_channel(Channel
*c
) {
135 if(channels
== c
) channels
= channels
->next
;
137 for(p
= channels
; p
&& p
->next
!= c
; p
= p
->next
);
145 static void login(char *key
, char *fullname
) {
146 if(key
) snprintf(message
, PIPE_BUF
,
147 "PASS %s\r\nNICK %s\r\nUSER %s localhost * :%s\r\n", key
,
148 nick
, nick
, fullname
? fullname
: nick
);
149 else snprintf(message
, PIPE_BUF
, "NICK %s\r\nUSER %s localhost * :%s\r\n",
150 nick
, nick
, fullname
? fullname
: nick
);
151 write(irc
, message
, strlen(message
)); /* login */
154 static int tcpopen(unsigned short port
) {
156 struct sockaddr_in sin
;
157 struct hostent
*hp
= gethostbyname(host
);
159 memset(&sin
, 0, sizeof(struct sockaddr_in
));
161 perror("ii: cannot retrieve host information");
164 sin
.sin_family
= AF_INET
;
165 memcpy(&sin
.sin_addr
, hp
->h_addr
, hp
->h_length
);
166 sin
.sin_port
= htons(port
);
167 if((fd
= socket(AF_INET
, SOCK_STREAM
, 0)) < 0) {
168 perror("ii: cannot create socket");
171 if(connect(fd
, (const struct sockaddr
*) &sin
, sizeof(sin
)) < 0) {
172 perror("ii: cannot connect to host");
178 static size_t tokenize(char **result
, size_t reslen
, char *str
, char delim
) {
179 char *p
= NULL
, *n
= NULL
;
184 for(n
= str
; *n
== ' '; n
++);
186 for(i
= 0; *n
!= 0;) {
189 if(i
> TOK_CHAN
- TOK_CMD
&& strtol(result
[0], NULL
, 10) > 0) delim
=':'; /* workaround non-RFC compliant messages */
197 if(i
<reslen
&& p
< n
&& strlen(p
))
199 return i
; /* number of tokens */
202 static void print_out(char *channel
, char *buf
) {
203 static char outfile
[256], server
[256], buft
[18];
207 if(channel
) snprintf(server
, sizeof(server
), "-!- %s", channel
);
208 if(strstr(buf
, server
)) channel
="";
209 create_filepath(outfile
, sizeof(outfile
), channel
, "out");
210 if(!(out
= fopen(outfile
, "a"))) return;
211 if(channel
&& channel
[0]) add_channel(channel
);
213 strftime(buft
, sizeof(buft
), "%F %R", localtime(&t
));
214 fprintf(out
, "%s %s\n", buft
, buf
);
218 static void proc_channels_privmsg(char *channel
, char *buf
) {
219 snprintf(message
, PIPE_BUF
, "<%s> %s", nick
, buf
);
220 print_out(channel
, message
);
221 snprintf(message
, PIPE_BUF
, "PRIVMSG %s :%s\r\n", channel
, buf
);
222 write(irc
, message
, strlen(message
));
225 static void proc_channels_input(Channel
*c
, char *buf
) {
228 if(buf
[0] != '/' && buf
[0] != 0) {
229 proc_channels_privmsg(c
->name
, buf
);
233 if(buf
[2] == ' ' || buf
[2] == '\0') switch (buf
[1]) {
236 p
= strchr(&buf
[3], ' ');
239 if((buf
[3]=='#')||(buf
[3]=='&')||(buf
[3]=='+')||(buf
[3]=='!')){
240 if(p
) snprintf(message
, PIPE_BUF
, "JOIN %s %s\r\n", &buf
[3], p
+ 1); /* password protected channel */
241 else snprintf(message
, PIPE_BUF
, "JOIN %s\r\n", &buf
[3]);
242 add_channel(&buf
[3]);
245 if(p
&& buf
[3] != ' '){
246 add_channel(&buf
[3]);
247 proc_channels_privmsg(&buf
[3], p
+ 1);
254 p
= strchr(&buf
[3], ' ');
257 if((buf
[3]=='#')||(buf
[3]=='&')||(buf
[3]=='+')||(buf
[3]=='!')){
258 if(p
) snprintf(message
, PIPE_BUF
, "TOPIC %s :%s\r\n", &buf
[3], p
+ 1);
259 else snprintf(message
, PIPE_BUF
, "TOPIC %s\r\n", &buf
[3]);
262 if(c
->name
[0] == 0) return;
263 if(strlen(buf
)>3) snprintf(message
, PIPE_BUF
, "TOPIC %s :%s\r\n", c
->name
, &buf
[3]);
264 else snprintf(message
, PIPE_BUF
, "TOPIC %s\r\n", c
->name
);
269 snprintf(message
, PIPE_BUF
, "-!- %s is away \"%s\"", nick
, &buf
[3]);
270 print_out(c
->name
, message
);
271 snprintf(message
, PIPE_BUF
, "AWAY :%s\r\n", &buf
[3]);
272 } else snprintf(message
, PIPE_BUF
, "AWAY\r\n");
276 snprintf(nick
, sizeof(nick
),"%s", &buf
[3]);
277 snprintf(message
, PIPE_BUF
, "NICK %s\r\n", &buf
[3]);
281 if(c
->name
[0] == 0) return;
282 if(strlen(buf
)>3) snprintf(message
, PIPE_BUF
, "PART %s :%s\r\n", c
->name
, &buf
[3]);
283 else snprintf(message
, PIPE_BUF
, "PART %s\r\n", c
->name
);
284 write(irc
, message
, strlen(message
));
290 snprintf(message
, PIPE_BUF
, "%s\r\n", &buf
[1]);
293 snprintf(message
, PIPE_BUF
, "%s\r\n", &buf
[1]);
295 if (message
[0] != '\0')
296 write(irc
, message
, strlen(message
));
299 static void proc_server_cmd(char *buf
) {
300 char *argv
[TOK_LAST
], *cmd
= NULL
, *p
= NULL
;
303 if(!buf
|| *buf
=='\0')
306 for(i
= 0; i
< TOK_LAST
; i
++)
308 /* <message> ::= [':' <prefix> <SPACE> ] <command> <params> <crlf>
309 <prefix> ::= <servername> | <nick> [ '!' <user> ] [ '@' <host> ]
310 <command> ::= <letter> { <letter> } | <number> <number> <number>
311 <SPACE> ::= ' ' { ' ' }
312 <params> ::= <SPACE> [ ':' <trailing> | <middle> <params> ]
313 <middle> ::= <Any *non-empty* sequence of octets not including SPACE
314 or NUL or CR or LF, the first of which may not be ':'>
315 <trailing> ::= <Any, possibly *empty*, sequence of octets not including NUL or CR or LF>
318 if(buf
[0] == ':') { /* check prefix */
319 if (!(p
= strchr(buf
, ' '))) return;
321 for(++p
; *p
== ' '; p
++);
323 argv
[TOK_NICKSRV
] = &buf
[1];
324 if((p
= strchr(buf
, '!'))) {
326 argv
[TOK_USER
] = ++p
;
332 for(p
= cmd
; p
&& *p
!= 0; p
++)
333 if(*p
== '\r' || *p
== '\n')
336 if((p
= strchr(cmd
, ':'))) {
338 argv
[TOK_TEXT
] = ++p
;
341 tokenize(&argv
[TOK_CMD
], TOK_LAST
- TOK_CMD
, cmd
, ' ');
343 if(!argv
[TOK_CMD
] || !strncmp("PONG", argv
[TOK_CMD
], 5)) {
345 } else if(!strncmp("PING", argv
[TOK_CMD
], 5)) {
346 snprintf(message
, PIPE_BUF
, "PONG %s\r\n", argv
[TOK_TEXT
]);
347 write(irc
, message
, strlen(message
));
349 } else if(!argv
[TOK_NICKSRV
] || !argv
[TOK_USER
]) { /* server command */
350 snprintf(message
, PIPE_BUF
, "%s%s", argv
[TOK_ARG
] ? argv
[TOK_ARG
] : "", argv
[TOK_TEXT
] ? argv
[TOK_TEXT
] : "");
351 print_out(0, message
);
353 } else if(!strncmp("ERROR", argv
[TOK_CMD
], 6))
354 snprintf(message
, PIPE_BUF
, "-!- error %s", argv
[TOK_TEXT
] ? argv
[TOK_TEXT
] : "unknown");
355 else if(!strncmp("JOIN", argv
[TOK_CMD
], 5)) {
356 if(argv
[TOK_TEXT
] != NULL
){
357 p
= strchr(argv
[TOK_TEXT
], ' ');
361 argv
[TOK_CHAN
] = argv
[TOK_TEXT
];
362 snprintf(message
, PIPE_BUF
, "-!- %s(%s) has joined %s", argv
[TOK_NICKSRV
], argv
[TOK_USER
], argv
[TOK_TEXT
]);
363 } else if(!strncmp("PART", argv
[TOK_CMD
], 5)) {
364 snprintf(message
, PIPE_BUF
, "-!- %s(%s) has left %s", argv
[TOK_NICKSRV
], argv
[TOK_USER
], argv
[TOK_CHAN
]);
365 } else if(!strncmp("MODE", argv
[TOK_CMD
], 5))
366 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] : "");
367 else if(!strncmp("QUIT", argv
[TOK_CMD
], 5))
368 snprintf(message
, PIPE_BUF
, "-!- %s(%s) has quit \"%s\"", argv
[TOK_NICKSRV
], argv
[TOK_USER
], argv
[TOK_TEXT
] ? argv
[TOK_TEXT
] : "");
369 else if(!strncmp("NICK", argv
[TOK_CMD
], 5))
370 snprintf(message
, PIPE_BUF
, "-!- %s changed nick to %s", argv
[TOK_NICKSRV
], argv
[TOK_TEXT
]);
371 else if(!strncmp("TOPIC", argv
[TOK_CMD
], 6))
372 snprintf(message
, PIPE_BUF
, "-!- %s changed topic to \"%s\"", argv
[TOK_NICKSRV
], argv
[TOK_TEXT
] ? argv
[TOK_TEXT
] : "");
373 else if(!strncmp("KICK", argv
[TOK_CMD
], 5))
374 snprintf(message
, PIPE_BUF
, "-!- %s kicked %s (\"%s\")", argv
[TOK_NICKSRV
], argv
[TOK_ARG
], argv
[TOK_TEXT
] ? argv
[TOK_TEXT
] : "");
375 else if(!strncmp("NOTICE", argv
[TOK_CMD
], 7))
376 snprintf(message
, PIPE_BUF
, "-!- \"%s\")", argv
[TOK_TEXT
] ? argv
[TOK_TEXT
] : "");
377 else if(!strncmp("PRIVMSG", argv
[TOK_CMD
], 8))
378 snprintf(message
, PIPE_BUF
, "<%s> %s", argv
[TOK_NICKSRV
], argv
[TOK_TEXT
] ? argv
[TOK_TEXT
] : "");
379 if(!argv
[TOK_CHAN
] || !strncmp(argv
[TOK_CHAN
], nick
, strlen(nick
)))
380 print_out(argv
[TOK_NICKSRV
], message
);
382 print_out(argv
[TOK_CHAN
], message
);
385 static int read_line(int fd
, size_t res_len
, char *buf
) {
389 if(read(fd
, &c
, sizeof(char)) != sizeof(char))
393 while(c
!= '\n' && i
< res_len
);
394 buf
[i
- 1] = 0; /* eliminates '\n' */
398 static void handle_channels_input(Channel
*c
) {
399 static char buf
[PIPE_BUF
];
400 if(read_line(c
->fd
, PIPE_BUF
, buf
) == -1) {
402 int fd
= open_channel(c
->name
);
409 proc_channels_input(c
, buf
);
412 static void handle_server_output() {
413 static char buf
[PIPE_BUF
];
414 if(read_line(irc
, PIPE_BUF
, buf
) == -1) {
415 perror("ii: remote host closed connection");
418 proc_server_cmd(buf
);
426 char ping_msg
[17] = "PING localhost\r\n";
432 for(c
= channels
; c
; c
= c
->next
) {
440 r
= select(maxfd
+ 1, &rd
, 0, 0, &tv
);
444 perror("ii: error on select()");
447 if(time(NULL
) - last_response
>= PING_TIMEOUT
) {
448 print_out(NULL
, "-!- ii shutting down: ping timeout");
451 write(irc
, ping_msg
, strlen(ping_msg
));
454 if(FD_ISSET(irc
, &rd
)) {
455 handle_server_output();
456 last_response
= time(NULL
);
458 for(c
= channels
; c
; c
= c
->next
)
459 if(FD_ISSET(c
->fd
, &rd
))
460 handle_channels_input(c
);
464 int main(int argc
, char *argv
[]) {
466 unsigned short port
= SERVER_PORT
;
467 char *key
= NULL
, *fullname
= NULL
, *dir
= NULL
;
468 char prefix
[_POSIX_PATH_MAX
] = "irc";
470 if (argc
<= 1 || (argc
== 2 && argv
[1][0] == '-' && argv
[1][1] == 'h')) usage();
472 for(i
= 1; (i
+ 1 < argc
) && (argv
[i
][0] == '-'); i
++) {
473 switch (argv
[i
][1]) {
474 case 'i': snprintf(prefix
,sizeof(prefix
),"%s", argv
[++i
]); break;
475 case 's': host
= argv
[++i
]; break;
476 case 'p': port
= strtol(argv
[++i
], NULL
, 10); break;
477 case 'n': snprintf(nick
,sizeof(nick
),"%s", argv
[++i
]); break;
478 case 'k': key
= argv
[++i
]; break;
479 case 'f': fullname
= argv
[++i
]; break;
480 case 'd': dir
= argv
[++i
]; break;
481 default: usage(); break;
485 if(!snprintf(path
, sizeof(path
), "%s/%s", prefix
, dir
? dir
: host
)) {
486 fprintf(stderr
, "%s", "ii: path to irc directory too long\n");
489 create_dirtree(path
);
491 add_channel(""); /* master channel */
492 login(key
, fullname
);