]> git.armaanb.net Git - sic.git/blob - sic.c
initial commit
[sic.git] / sic.c
1 /*
2  * (C)opyright MMV-MMVI Anselm R. Garbe <garbeam at gmail dot com>
3  * (C)opyright MMV-MMVI Nico Golde <nico at ngolde dot de>
4  * See LICENSE file for license details.
5  */
6
7 #include <errno.h>
8 #include <netdb.h>
9 #include <sys/types.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 <sys/stat.h>
19 #include <sys/socket.h>
20 #include <ctype.h>
21 #include <time.h>
22 #include <unistd.h>
23
24 #define PING_TIMEOUT 300
25 #define MAXMSG 4096
26
27 enum { TOK_NICKSRV = 0, TOK_USER, TOK_CMD, TOK_CHAN, TOK_ARG, TOK_TEXT, TOK_LAST };
28
29 static int irc;
30 static time_t last_response;
31 static char nick[32];                   /* might change while running */
32 static char message[MAXMSG]; /* message buf used for communication */
33 static char *host = NULL;
34
35 static int
36 tcpopen(char *address)
37 {
38         int fd = 0;
39         char *port;
40         struct sockaddr_in addr = { 0 };
41         struct hostent *hp;
42         unsigned int prt;
43         
44         if((host = strchr(address, '!')))
45                 *(host++) = 0;
46
47         if(!(port = strrchr(host, '!')))
48                 return -1;
49         *port = 0;
50         port++;
51         if(sscanf(port, "%d", &prt) != 1)
52                 return -1;
53
54         /* init */
55         if((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
56                 return -1;
57         hp = gethostbyname(host);
58         addr.sin_family = AF_INET;
59         addr.sin_port = htons(prt);
60         bcopy(hp->h_addr, &addr.sin_addr, hp->h_length);
61
62         if(connect(fd, (struct sockaddr *) &addr,
63                                 sizeof(struct sockaddr_in))) {
64                 close(fd);
65                 return -1;
66         }
67         return fd;
68 }
69
70 unsigned int
71 tokenize(char **result, unsigned int reslen, char *str, char delim)
72 {
73         char *p, *n;
74         unsigned int i = 0;
75
76         if(!str)
77                 return 0;
78         for(n = str; *n == delim; n++);
79         p = n;
80         for(i = 0; *n != 0;) {
81                 if(i == reslen)
82                         return i;
83                 if(*n == delim) {
84                         *n = 0;
85                         if(strlen(p))
86                                 result[i++] = p;
87                         p = ++n;
88                 } else
89                         n++;
90         }
91         if((i < reslen) && (p < n) && strlen(p))
92                 result[i++] = p;
93         return i;       /* number of tokens */
94 }
95
96 static void
97 print_out(char *channel, char *buf)
98 {
99         static char buft[18];
100         time_t t = time(0);
101
102         strftime(buft, sizeof(buft), "%F %R", localtime(&t));
103         fprintf(stdout, "%s: %s %s\n", channel, buft, buf);
104 }
105
106 static void
107 proc_channels_privmsg(char *channel, char *buf)
108 {
109         snprintf(message, MAXMSG, "<%s> %s", nick, buf);
110         print_out(channel, message);
111         snprintf(message, MAXMSG, "PRIVMSG %s :%s\r\n", channel, buf);
112         write(irc, message, strlen(message));
113 }
114
115 static void
116 proc_channels_input(char *buf)
117 {
118         char *p;
119
120         if((p = strchr(buf, ' ')))
121                 *(p++) = 0;
122         if(buf[0] != '/' && buf[0] != 0) {
123                 proc_channels_privmsg(buf, p);
124                 return;
125         }
126         if((p = strchr(&buf[3], ' ')))
127                 *(p++) = 0;
128         switch (buf[1]) {
129         case 'j':
130                 if(buf[3] == '#')
131                         snprintf(message, MAXMSG, "JOIN %s\r\n", &buf[3]);
132                 else if(p) {
133                         proc_channels_privmsg(&buf[3], p + 1);
134                         return;
135                 }
136                 break;
137         case 't':
138                 snprintf(message, MAXMSG, "TOPIC %s :%s\r\n", &buf[3], p);
139                 break;
140         case 'l':
141                 if(p)
142                         snprintf(message, MAXMSG, "PART %s :%s\r\n", &buf[3], p);
143                 else
144                         snprintf(message, MAXMSG, "PART %s :sic - 300 SLOC are too much\r\n", &buf[3]);
145                 write(irc, message, strlen(message));
146                 return;
147                 break;
148         default:
149                 snprintf(message, MAXMSG, "%s\r\n", &buf[1]);
150                 break;
151         }
152         write(irc, message, strlen(message));
153 }
154
155 static void
156 proc_server_cmd(char *buf)
157 {
158         char *argv[TOK_LAST], *cmd, *p;
159         int i;
160         if(!buf || *buf=='\0')
161                 return;
162
163         for(i = 0; i < TOK_LAST; i++)
164                 argv[i] = NULL;
165
166         /*
167            <message>  ::= [':' <prefix> <SPACE> ] <command> <params> <crlf>
168            <prefix>   ::= <servername> | <nick> [ '!' <user> ] [ '@' <host> ]
169            <command>  ::= <letter> { <letter> } | <number> <number> <number>
170            <SPACE>    ::= ' ' { ' ' }
171            <params>   ::= <SPACE> [ ':' <trailing> | <middle> <params> ]
172            <middle>   ::= <Any *non-empty* sequence of octets not including SPACE
173            or NUL or CR or LF, the first of which may not be ':'>
174            <trailing> ::= <Any, possibly *empty*, sequence of octets not including NUL or CR or LF>
175            <crlf>     ::= CR LF
176          */
177         if(buf[0] == ':') { /* check prefix */
178                 p = strchr(buf, ' ');
179                 *p = 0;
180                 for(++p; *p == ' '; p++);
181                 cmd = p;
182                 argv[TOK_NICKSRV] = &buf[1];
183                 if((p = strchr(buf, '!'))) {
184                         *p = 0;
185                         argv[TOK_USER] = ++p;
186                 }
187         } else
188                 cmd = buf;
189
190         /* remove CRLFs */
191         for(p = cmd; p && *p != 0; p++)
192                 if(*p == '\r' || *p == '\n')
193                         *p = 0;
194
195         if((p = strchr(cmd, ':'))) {
196                 *p = 0;
197                 argv[TOK_TEXT] = ++p;
198         }
199         tokenize(&argv[TOK_CMD], TOK_LAST - TOK_CMD, cmd, ' ');
200
201         if(!strncmp("PONG", argv[TOK_CMD], 5)) {
202                 return;
203         } else if(!strncmp("PING", argv[TOK_CMD], 5)) {
204                 snprintf(message, MAXMSG, "PONG %s\r\n", argv[TOK_TEXT]);
205                 write(irc, message, strlen(message));
206                 return;
207         } else if(!argv[TOK_NICKSRV] || !argv[TOK_USER]) {      /* server command */
208                 snprintf(message, MAXMSG, "%s", argv[TOK_TEXT] ? argv[TOK_TEXT] : "");
209                 print_out(0, message);
210                 return;
211         } else if(!strncmp("ERROR", argv[TOK_CMD], 6))
212                 snprintf(message, MAXMSG, "-!- error %s",
213                                 argv[TOK_TEXT] ? argv[TOK_TEXT] : "unknown");
214         else if(!strncmp("JOIN", argv[TOK_CMD], 5)) {
215                 if(argv[TOK_TEXT]!=NULL){
216                         p = strchr(argv[TOK_TEXT], ' ');
217                 if(p)
218                         *p = 0;
219                 }
220                 argv[TOK_CHAN] = argv[TOK_TEXT];
221                 snprintf(message, MAXMSG, "-!- %s(%s) has joined %s",
222                                 argv[TOK_NICKSRV], argv[TOK_USER], argv[TOK_TEXT]);
223         } else if(!strncmp("PART", argv[TOK_CMD], 5)) {
224                 snprintf(message, MAXMSG, "-!- %s(%s) has left %s",
225                                 argv[TOK_NICKSRV], argv[TOK_USER], argv[TOK_CHAN]);
226         } else if(!strncmp("MODE", argv[TOK_CMD], 5))
227                 snprintf(message, MAXMSG, "-!- %s changed mode/%s -> %s %s",
228                                 argv[TOK_NICKSRV], argv[TOK_CMD + 1],
229                                 argv[TOK_CMD + 2], argv[TOK_CMD + 3]);
230         else if(!strncmp("QUIT", argv[TOK_CMD], 5))
231                 snprintf(message, MAXMSG, "-!- %s(%s) has quit \"%s\"",
232                                 argv[TOK_NICKSRV], argv[TOK_USER],
233                                 argv[TOK_TEXT] ? argv[TOK_TEXT] : "");
234         else if(!strncmp("NICK", argv[TOK_CMD], 5))
235                 snprintf(message, MAXMSG, "-!- %s changed nick to %s",
236                                 argv[TOK_NICKSRV], argv[TOK_TEXT]);
237         else if(!strncmp("TOPIC", argv[TOK_CMD], 6))
238                 snprintf(message, MAXMSG, "-!- %s changed topic to \"%s\"",
239                                 argv[TOK_NICKSRV], argv[TOK_TEXT] ? argv[TOK_TEXT] : "");
240         else if(!strncmp("KICK", argv[TOK_CMD], 5))
241                 snprintf(message, MAXMSG, "-!- %s kicked %s (\"%s\")",
242                                 argv[TOK_NICKSRV], argv[TOK_ARG],
243                                 argv[TOK_TEXT] ? argv[TOK_TEXT] : "");
244         else if(!strncmp("NOTICE", argv[TOK_CMD], 7))
245                 snprintf(message, MAXMSG, "-!- \"%s\")",
246                                 argv[TOK_TEXT] ? argv[TOK_TEXT] : "");
247         else if(!strncmp("PRIVMSG", argv[TOK_CMD], 8))
248                 snprintf(message, MAXMSG, "<%s> %s",
249                                 argv[TOK_NICKSRV], argv[TOK_TEXT] ? argv[TOK_TEXT] : "");
250         if(!argv[TOK_CHAN] || !strncmp(argv[TOK_CHAN], nick, strlen(nick)))
251                 print_out(argv[TOK_NICKSRV], message);
252         else
253                 print_out(argv[TOK_CHAN], message);
254 }
255
256 static int
257 read_line(int fd, unsigned int res_len, char *buf)
258 {
259         unsigned int i = 0;
260         char c;
261         do {
262                 if(read(fd, &c, sizeof(char)) != sizeof(char))
263                         return -1;
264                 buf[i++] = c;
265         }
266         while(c != '\n' && i < res_len);
267         buf[i - 1] = 0;                 /* eliminates '\n' */
268         return 0;
269 }
270
271 static void
272 handle_server_output()
273 {
274         static char buf[MAXMSG];
275         if(read_line(irc, MAXMSG, buf) == -1) {
276                 perror("sic: remote host closed connection");
277                 exit(EXIT_FAILURE);
278         }
279         proc_server_cmd(buf);
280 }
281
282 int
283 main(int argc, char *argv[])
284 {
285         char address[256];
286         char *password = NULL;
287         char *fullname = NULL;
288         char ping_msg[512], buf[MAXMSG];
289         int i, n, r, maxfd;
290         struct passwd *spw = getpwuid(getuid());
291         struct timeval tv;
292         fd_set rd;
293
294         if(!spw) {
295                 fprintf(stderr,"sic: getpwuid() failed\n"); 
296                 exit(EXIT_FAILURE);
297         }
298         snprintf(nick, sizeof(nick), "%s", spw->pw_name);
299
300         if(argc == 2 && argv[1][0] == '-' && argv[1][1] == 'h')
301                 goto Usage;
302
303         address[0] = 0;
304         for(i = 1; (i + 1 < argc) && (argv[i][0] == '-'); i++) {
305                 switch (argv[i][1]) {
306                 default:
307 Usage:
308                         fputs("usage: sic -a address [-n nick] [-f fullname] [-p password] [-v]\n",
309                                         stdout);
310                         exit(EXIT_FAILURE);
311                         break;
312                 case 'a':
313                         strncpy(address, argv[++i], sizeof(address));
314                         break;
315                 case 'n':
316                         snprintf(nick, sizeof(nick), "%s", argv[++i]);
317                         break;
318                 case 'p':
319                         password = argv[++i];
320                         break;
321                 case 'f':
322                         fullname = argv[++i];
323                         break;
324                 }
325         }
326
327         if(!address[0])
328                 goto Usage;
329
330         if((irc = tcpopen(address)) == -1) {
331                 fprintf(stderr, "sic: cannot connect server '%s'\n", address);
332                 exit(EXIT_FAILURE);
333         }
334         /* login */
335         if(password)
336                 snprintf(message, MAXMSG,
337                                 "PASS %s\r\nNICK %s\r\nUSER %s localhost %s :%s\r\n",
338                                 password, nick, nick, host, fullname ? fullname : nick);
339         else
340                 snprintf(message, MAXMSG, "NICK %s\r\nUSER %s localhost %s :%s\r\n",
341                                  nick, nick, host, fullname ? fullname : nick);
342         write(irc, message, strlen(message));
343
344         snprintf(ping_msg, sizeof(ping_msg), "PING %s\r\n", host);
345         for(;;) {
346                 FD_ZERO(&rd);
347                 maxfd = irc;
348                 FD_SET(0, &rd);
349                 FD_SET(irc, &rd);
350                 tv.tv_sec = 120;
351                 tv.tv_usec = 0;
352                 r = select(maxfd + 1, &rd, 0, 0, &tv);
353                 if(r < 0) {
354                         if(errno == EINTR)
355                                 continue;
356                         perror("sic: error on select()");
357                         exit(EXIT_FAILURE);
358                 } else if(r == 0) {
359                         if(time(NULL) - last_response >= PING_TIMEOUT) {
360                                 print_out(NULL, "-!- sic shutting down: ping timeout");
361                                 exit(EXIT_FAILURE);
362                         }
363                         write(irc, ping_msg, strlen(ping_msg));
364                         continue;
365                 }
366                 if(FD_ISSET(irc, &rd)) {
367                         handle_server_output();
368                         last_response = time(NULL);
369                 }
370                 if(FD_ISSET(0, &rd)) {
371                         i = n = 0;
372                         for(;;) {
373                                 if((i = getchar()) == EOF) {
374                                         perror("sic: broken pipe");
375                                         exit(EXIT_FAILURE);
376                                 }
377                                 if(i == '\n' || n >= sizeof(buf) - 1)
378                                         break;
379                                 buf[n++] = i;
380                         }
381                         buf[n] = 0;
382                         proc_channels_input(buf);
383                 }
384         }
385
386         return 0;
387 }