sic.c - sic - simple irc client | |
git clone git://git.suckless.org/sic | |
Log | |
Files | |
Refs | |
README | |
LICENSE | |
--- | |
sic.c (4393B) | |
--- | |
1 /* See LICENSE file for license details. */ | |
2 #include <sys/select.h> | |
3 | |
4 #include <ctype.h> | |
5 #include <errno.h> | |
6 #include <stdarg.h> | |
7 #include <stdio.h> | |
8 #include <stdlib.h> | |
9 #include <string.h> | |
10 #include <time.h> | |
11 #include <unistd.h> | |
12 | |
13 #include "arg.h" | |
14 #include "config.h" | |
15 | |
16 char *argv0; | |
17 static char *host = DEFAULT_HOST; | |
18 static char *port = DEFAULT_PORT; | |
19 static char *password; | |
20 static char nick[32]; | |
21 static char bufin[4096]; | |
22 static char bufout[4096]; | |
23 static char channel[256]; | |
24 static time_t trespond; | |
25 static FILE *srv; | |
26 | |
27 #undef strlcpy | |
28 #include "strlcpy.c" | |
29 #include "util.c" | |
30 | |
31 static void | |
32 pout(char *channel, char *fmt, ...) { | |
33 static char timestr[80]; | |
34 time_t t; | |
35 va_list ap; | |
36 | |
37 va_start(ap, fmt); | |
38 vsnprintf(bufout, sizeof bufout, fmt, ap); | |
39 va_end(ap); | |
40 t = time(NULL); | |
41 strftime(timestr, sizeof timestr, TIMESTAMP_FORMAT, localtime(&t… | |
42 fprintf(stdout, "%-12s: %s %s\n", channel, timestr, bufout); | |
43 } | |
44 | |
45 static void | |
46 sout(char *fmt, ...) { | |
47 va_list ap; | |
48 | |
49 va_start(ap, fmt); | |
50 vsnprintf(bufout, sizeof bufout, fmt, ap); | |
51 va_end(ap); | |
52 fprintf(srv, "%s\r\n", bufout); | |
53 } | |
54 | |
55 static void | |
56 privmsg(char *channel, char *msg) { | |
57 if(channel[0] == '\0') { | |
58 pout("", "No channel to send to"); | |
59 return; | |
60 } | |
61 pout(channel, "<%s> %s", nick, msg); | |
62 sout("PRIVMSG %s :%s", channel, msg); | |
63 } | |
64 | |
65 static void | |
66 parsein(char *s) { | |
67 char c, *p; | |
68 | |
69 if(s[0] == '\0') | |
70 return; | |
71 skip(s, '\n'); | |
72 if(s[0] != COMMAND_PREFIX_CHARACTER) { | |
73 privmsg(channel, s); | |
74 return; | |
75 } | |
76 c = *++s; | |
77 if(c != '\0' && isspace((unsigned char)s[1])) { | |
78 p = s + 2; | |
79 switch(c) { | |
80 case 'j': | |
81 sout("JOIN %s", p); | |
82 if(channel[0] == '\0') | |
83 strlcpy(channel, p, sizeof channel); | |
84 return; | |
85 case 'l': | |
86 s = eat(p, isspace, 1); | |
87 p = eat(s, isspace, 0); | |
88 if(!*s) | |
89 s = channel; | |
90 if(*p) | |
91 *p++ = '\0'; | |
92 if(!*p) | |
93 p = DEFAULT_PARTING_MESSAGE; | |
94 sout("PART %s :%s", s, p); | |
95 return; | |
96 case 'm': | |
97 s = eat(p, isspace, 1); | |
98 p = eat(s, isspace, 0); | |
99 if(*p) | |
100 *p++ = '\0'; | |
101 privmsg(s, p); | |
102 return; | |
103 case 's': | |
104 strlcpy(channel, p, sizeof channel); | |
105 return; | |
106 } | |
107 } | |
108 sout("%s", s); | |
109 } | |
110 | |
111 static void | |
112 parsesrv(char *cmd) { | |
113 char *usr, *par, *txt; | |
114 | |
115 usr = host; | |
116 if(!cmd || !*cmd) | |
117 return; | |
118 if(cmd[0] == ':') { | |
119 usr = cmd + 1; | |
120 cmd = skip(usr, ' '); | |
121 if(cmd[0] == '\0') | |
122 return; | |
123 skip(usr, '!'); | |
124 } | |
125 skip(cmd, '\r'); | |
126 par = skip(cmd, ' '); | |
127 txt = skip(par, ':'); | |
128 trim(par); | |
129 if(!strcmp("PONG", cmd)) | |
130 return; | |
131 if(!strcmp("PRIVMSG", cmd)) | |
132 pout(par, "<%s> %s", usr, txt); | |
133 else if(!strcmp("PING", cmd)) | |
134 sout("PONG %s", txt); | |
135 else { | |
136 pout(usr, ">< %s (%s): %s", cmd, par, txt); | |
137 if(!strcmp("NICK", cmd) && !strcmp(usr, nick)) | |
138 strlcpy(nick, txt, sizeof nick); | |
139 } | |
140 } | |
141 | |
142 | |
143 static void | |
144 usage(void) { | |
145 eprint("usage: sic [-h host] [-p port] [-n nick] [-k keyword] [-… | |
146 } | |
147 | |
148 int | |
149 main(int argc, char *argv[]) { | |
150 struct timeval tv; | |
151 const char *user = getenv("USER"); | |
152 int n; | |
153 fd_set rd; | |
154 | |
155 strlcpy(nick, user ? user : "unknown", sizeof nick); | |
156 ARGBEGIN { | |
157 case 'h': | |
158 host = EARGF(usage()); | |
159 break; | |
160 case 'p': | |
161 port = EARGF(usage()); | |
162 break; | |
163 case 'n': | |
164 strlcpy(nick, EARGF(usage()), sizeof nick); | |
165 break; | |
166 case 'k': | |
167 password = EARGF(usage()); | |
168 break; | |
169 case 'v': | |
170 eprint("sic-"VERSION", © 2005-2014 Kris Maglione, Ansel… | |
171 break; | |
172 default: | |
173 usage(); | |
174 } ARGEND; | |
175 | |
176 /* init */ | |
177 srv = fdopen(dial(host, port), "r+"); | |
178 if (!srv) | |
179 eprint("fdopen:"); | |
180 /* login */ | |
181 if(password) | |
182 sout("PASS %s", password); | |
183 sout("NICK %s", nick); | |
184 sout("USER %s localhost %s :%s", nick, host, nick); | |
185 fflush(srv); | |
186 setbuf(stdout, NULL); | |
187 setbuf(srv, NULL); | |
188 setbuf(stdin, NULL); | |
189 #ifdef __OpenBSD__ | |
190 if (pledge("stdio", NULL) == -1) | |
191 eprint("error: pledge:"); | |
192 #endif | |
193 for(;;) { /* main loop */ | |
194 FD_ZERO(&rd); | |
195 FD_SET(0, &rd); | |
196 FD_SET(fileno(srv), &rd); | |
197 tv.tv_sec = 120; | |
198 tv.tv_usec = 0; | |
199 n = select(fileno(srv) + 1, &rd, 0, 0, &tv); | |
200 if(n < 0) { | |
201 if(errno == EINTR) | |
202 continue; | |
203 eprint("sic: error on select():"); | |
204 } | |
205 else if(n == 0) { | |
206 if(time(NULL) - trespond >= 300) | |
207 eprint("sic shutting down: parse timeout… | |
208 sout("PING %s", host); | |
209 continue; | |
210 } | |
211 if(FD_ISSET(fileno(srv), &rd)) { | |
212 if(fgets(bufin, sizeof bufin, srv) == NULL) | |
213 eprint("sic: remote host closed connecti… | |
214 parsesrv(bufin); | |
215 trespond = time(NULL); | |
216 } | |
217 if(FD_ISSET(0, &rd)) { | |
218 if(fgets(bufin, sizeof bufin, stdin) == NULL) | |
219 eprint("sic: broken pipe\n"); | |
220 parsein(bufin); | |
221 } | |
222 } | |
223 return 0; | |
224 } |