]> git.gir.st - ircpipe.git/blob - ircpipe.c
remove debug printfs
[ircpipe.git] / ircpipe.c
1 #define _POSIX_C_SOURCE 200809L /* getopt(>=2), dprintf(>=200809L), strtok_r(*), getaddrinfo(>=200112L) */
2 #include <netdb.h>
3 #include <poll.h>
4 #include <stdio.h>
5 #include <stdlib.h>
6 #include <string.h>
7 #include <unistd.h>
8 #include <sys/types.h>
9 #include <sys/socket.h>
10 #include <netdb.h>
11
12 #ifndef WITHOUT_TLS
13 #include <openssl/ssl.h>
14 #include <openssl/err.h>
15 #endif
16
17 #define DEFAULT_PING 60000 /*ms*/
18 #define DEFAULT_TIMEOUT 2000 /*ms*/
19 #define DEFAULT_TLS NO_TLS
20 #define DEFAULT_PORT_PLAIN "6667"
21 #define DEFAULT_PORT_TLS "6697"
22
23 #define POLL_TIMEOUT 100
24
25 #define STR_(x) #x
26 #define STR(x) STR_(x)
27 #define OR_DIE < 0 && (perror(__FILE__ ":" STR(__LINE__)), exit(1), 0)
28 #define OR_DIE_gai(err) if (err) {fprintf(stderr, __FILE__ ":" STR(__LINE__) ": %s\n", gai_strerror(err));exit(1);}
29
30 enum pass_type_e {
31 NO_PASSWD,
32 SERVER_PASSWD,
33 SASL_PLAIN_PASSWD,
34 };
35
36 enum tls_use_e {
37 NO_TLS,
38 USE_TLS,
39 INSECURE_TLS,
40 };
41
42 void irc_help(const char *exe, const int code) {
43 fprintf(stderr, "Usage: %s [-n NICK] [-j CHAN] HOST [PORT]\n", exe);
44 exit(code);
45 }
46
47 int irc_connect(const char *host, const char *port) {
48 int sockfd;
49 struct addrinfo *results, *r;
50
51 fprintf(stderr, "%s : %s\n", host,port);
52 int err = getaddrinfo(host, port, NULL, &results); OR_DIE_gai(err); /*unable to resolve*/
53
54 for (r = results; r != NULL; r = r->ai_next) {
55 sockfd = socket(r->ai_family, SOCK_STREAM, 0);
56 if (sockfd < 0) continue; /* try next; todo: should check errno */
57
58 if (connect(sockfd, r->ai_addr, r->ai_addrlen) == 0)
59 break; /* successfully connected */
60
61 close(sockfd); /* failed, try next addr */
62 }
63
64 if (r == NULL) {
65 /* all failed; abort. */
66 sockfd = -1;
67 } else {
68 /* connection established. */
69 /* tls here */
70 /* connect timeout here */
71 }
72
73 freeaddrinfo(results);
74 return sockfd;
75 }
76
77 enum { /* requested command: */
78 NO_CMD = 1<<0,
79 NICK = 1<<1,
80 PING = 1<<2
81 };
82 int irc_answer(const int sockfd, char *buf, const unsigned int command) {
83 unsigned int seen = 0;
84 char *saveptr;
85 char *line = strtok_r(buf, "\n", &saveptr);
86 do {
87 /* skip over prefix (servername): */
88 if (line[0] == ':')
89 while (*line++ != ' ');
90
91 /* look for command responses, if any: */
92 switch (command) {
93 case PING: seen |= PING * (strncmp(line, "PONG ", 5)==0); break;
94 case NICK: seen |= NICK * (strncmp(line, "001 " , 4)==0); break;
95 }
96
97 /* reply to pings: */
98 if (strncmp(line, "PING ", 5) == 0) {
99 line[1] = 'O'; /* PING :foo -> PONG :foo */
100 write(sockfd, line, strlen(line));
101 write(sockfd, "\r\n", 2);
102 }
103 } while (line = strtok_r(NULL, "\n", &saveptr));
104
105 return seen;
106 }
107
108 int irc_base64(char *buf, int n) {
109 const char *b = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
110 int i, o, v, l = ((n+(3-n%3)%3)/3)*4;
111 if (l >= 400) return -1; /* lazy */
112 buf[n+1] = buf[n+2] = buf[l] = '\0';
113 for (i=(n+(3-n%3)%3)-3, o=l-4; i>=0 && o>=0; i-=3, o-=4) {
114 v = buf[i+0]<<16 | buf[i+1]<<8 | buf[i+2]<<0;
115 buf[o+0] = b[v>>18 & 0x3f];
116 buf[o+1] = b[v>>12 & 0x3f];
117 buf[o+2] = (i+1<n)? b[v>>06 & 0x3f]:'=';
118 buf[o+3] = (i+2<n)? b[v>>00 & 0x3f]:'=';
119 }
120 return l;
121 }
122
123 int irc_setup(const int sockfd, const int outfd, const char *nick, const char *pass, int pass_type, const char *chan) {
124 char buf[BUFSIZ];
125 int n;
126 struct pollfd fds[1];
127 fds[0].fd = sockfd;
128 fds[0].events = POLLIN;
129
130 if (pass_type == SASL_PLAIN_PASSWD) {
131 dprintf(sockfd, "CAP REQ :sasl\r\n");
132 } else if (pass_type == SERVER_PASSWD) {
133 dprintf(sockfd, "PASS %s\r\n", pass);
134 }
135
136 dprintf(sockfd, "NICK %s\r\n", nick);
137 dprintf(sockfd, "USER %s 0.0.0.0 %s :%s\r\n", nick, nick, nick);
138
139 if (pass_type == SASL_PLAIN_PASSWD) {
140 /* should wait for 'CAP <nick|*> ACK :<...>' */
141 dprintf(sockfd, "AUTHENTICATE PLAIN\r\n");
142 /* server sends 'AUTHENTICATE +' */
143 /* split base64-output into 400 byte chunks; if last is exactly
144 400 bytes, send empty msg ('+') afterwards */
145 n = snprintf(buf, BUFSIZ, "%s%c%s%c%s", nick, 0, nick, 0, pass);
146 irc_base64(buf, n) OR_DIE;
147 dprintf(sockfd, "AUTHENTICATE %s\r\n", buf);
148 /* wait for response 900+903 (ok) or 904 (err) */
149 dprintf(sockfd, "CAP END\r\n");
150 }
151
152 /* block until we get a RPL_WELCOME: */
153 for (;;) {
154 if (poll(fds, 1, POLL_TIMEOUT)) {
155 n = read(sockfd, buf, BUFSIZ);
156 write(outfd, buf, n);
157 if (irc_answer(sockfd, buf, NICK) & NICK) break;
158 }
159 }
160
161 if (chan) {
162 dprintf(sockfd, "JOIN %s\r\n", chan);
163 }
164
165 return 0;
166 }
167
168 int irc_poll(const int sockfd, const int infd, const int outfd) {
169 int n;
170 char buf[BUFSIZ];
171 enum { IRC, CLI };
172 struct pollfd fds[2];
173 fds[IRC].fd = sockfd;
174 fds[IRC].events = POLLIN;
175 fds[CLI].fd = infd;
176 fds[CLI].events = POLLIN;
177
178 for (;;) {
179 poll(fds, 2, POLL_TIMEOUT) OR_DIE;
180
181 /* XXX: long responses don't get fully processed until user input */
182 if (fds[IRC].revents & POLLIN) {
183 n = read(sockfd, buf, BUFSIZ);
184 if (n == 0) return -1; /* server closed connection */
185 write(outfd, buf, n);
186 irc_answer(sockfd, buf, NO_CMD);
187 /* update last-msg-rcvd here */
188 }
189 if (fds[CLI].revents & POLLIN) {
190 n = read(infd, buf, BUFSIZ);
191 if (n == 0) return 0; /* we closed connection */
192 write(sockfd, buf, n);
193 }
194
195 /* send ping here */
196 /*
197 dprintf(sockfd, "PING\r\n");
198 // poll-read
199 if (irc_answer(sockfd, buf, NICK) & NICK) break;
200 */
201 }
202 }
203
204 void irc_cleanup(const int sockfd) {
205 write(sockfd, "QUIT :ircpipe\r\n", 15);
206 shutdown(sockfd, SHUT_RDWR);
207 close(sockfd);
208 }
209
210 int main(int argc, char **argv) {
211 char *host = NULL;
212 char *port = NULL;
213 char *nick = NULL;
214 char *pass = NULL;
215 char *chan = NULL;
216 size_t ping_iv = DEFAULT_PING; /* interval between outgoing pings */
217 size_t resp_to = DEFAULT_TIMEOUT; /* how long to wait for command response (connect, ping, auth, ...) */
218 int tls = DEFAULT_TLS;
219 int pass_type = NO_PASSWD;
220
221 int sockfd;
222 int rv;
223
224 int opt; opterr = 0;
225
226 pass = getenv("IRC_PASSWD");
227
228 while ((opt = getopt(argc, argv, "n:j:pPsSkh")) != -1) {
229 switch (opt) {
230 case 'n': nick = optarg; break;
231 case 'p': pass_type = SERVER_PASSWD; break;
232 case 'P': pass_type = SASL_PLAIN_PASSWD; break;
233 case 's': tls = USE_TLS; break;
234 case 'S': tls = NO_TLS; break;
235 case 'k': tls = INSECURE_TLS; break;
236 case 'j': chan = optarg; break;
237 default: irc_help(argv[0], opt != 'h');
238 }
239 }
240
241 if (optind < argc) {
242 host = argv[optind++];
243 } else {
244 /* too few positional arguments */
245 irc_help(argv[0], 1);
246 }
247 if (optind < argc) {
248 port = argv[optind++];
249 } else {
250 port = (tls == NO_TLS)
251 ? DEFAULT_PORT_PLAIN
252 : DEFAULT_PORT_TLS;
253 }
254 if (optind < argc) {
255 /* too many positional arguments */
256 irc_help(argv[0], 1);
257 }
258
259 if (pass_type != NO_PASSWD && pass == NULL) {
260 fprintf(stderr, "must set IRC_PASSWD envvar to use -p/-P\n");
261 exit(1);
262 }
263
264 sockfd = irc_connect(host, port); sockfd OR_DIE;
265 irc_setup(sockfd, 1, nick, pass, pass_type, chan) OR_DIE;
266 rv = irc_poll(sockfd, 0, 1);
267 irc_cleanup(sockfd);
268
269 return (rv < 0);
270 }
Imprint / Impressum