]> git.gir.st - ircpipe.git/blob - ircpipe.c
use getaddrinfo() for transparent ipv4/6 support
[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 printf("\033[92m>>>%s<<<\033[0m\n", line);
92 /* look for command responses, if any: */
93 switch (command) {
94 case PING: seen |= PING * (strncmp(line, "PONG ", 5)==0); break;
95 case NICK: seen |= NICK * (strncmp(line, "001 " , 4)==0); break;
96 }
97
98 /* reply to pings: */
99 if (strncmp(line, "PING ", 5) == 0) {
100 line[1] = 'O'; /* PING :foo -> PONG :foo */
101 write(sockfd, line, strlen(line));
102 write(sockfd, "\r\n", 2);
103 }
104 } while (line = strtok_r(NULL, "\n", &saveptr));
105
106 printf("\033[91mseen=%d\033[0m\n", seen);
107 return seen;
108 }
109
110 int irc_base64(char *buf, int n) {
111 const char *b = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
112 int i, o, v, l = ((n+(3-n%3)%3)/3)*4;
113 if (l >= 400) return -1; /* lazy */
114 buf[n+1] = buf[n+2] = buf[l] = '\0';
115 for (i=(n+(3-n%3)%3)-3, o=l-4; i>=0 && o>=0; i-=3, o-=4) {
116 v = buf[i+0]<<16 | buf[i+1]<<8 | buf[i+2]<<0;
117 buf[o+0] = b[v>>18 & 0x3f];
118 buf[o+1] = b[v>>12 & 0x3f];
119 buf[o+2] = (i+1<n)? b[v>>06 & 0x3f]:'=';
120 buf[o+3] = (i+2<n)? b[v>>00 & 0x3f]:'=';
121 }
122 return l;
123 }
124
125 int irc_setup(const int sockfd, const int outfd, const char *nick, const char *pass, int pass_type, const char *chan) {
126 char buf[BUFSIZ];
127 int n;
128 struct pollfd fds[1];
129 fds[0].fd = sockfd;
130 fds[0].events = POLLIN;
131
132 if (pass_type == SASL_PLAIN_PASSWD) {
133 dprintf(sockfd, "CAP REQ :sasl\r\n");
134 } else if (pass_type == SERVER_PASSWD) {
135 dprintf(sockfd, "PASS %s\r\n", pass);
136 }
137
138 dprintf(sockfd, "NICK %s\r\n", nick);
139 dprintf(sockfd, "USER %s 0.0.0.0 %s :%s\r\n", nick, nick, nick);
140
141 if (pass_type == SASL_PLAIN_PASSWD) {
142 /* should wait for 'CAP <nick|*> ACK :<...>' */
143 dprintf(sockfd, "AUTHENTICATE PLAIN\r\n");
144 /* server sends 'AUTHENTICATE +' */
145 /* split base64-output into 400 byte chunks; if last is exactly
146 400 bytes, send empty msg ('+') afterwards */
147 n = snprintf(buf, BUFSIZ, "%s%c%s%c%s", nick, 0, nick, 0, pass);
148 irc_base64(buf, n) OR_DIE;
149 dprintf(sockfd, "AUTHENTICATE %s\r\n", buf);
150 /* wait for response 900+903 (ok) or 904 (err) */
151 dprintf(sockfd, "CAP END\r\n");
152 }
153
154 /* block until we get a RPL_WELCOME: */
155 for (;;) {
156 if (poll(fds, 1, POLL_TIMEOUT)) {
157 n = read(sockfd, buf, BUFSIZ);
158 write(outfd, buf, n);
159 if (irc_answer(sockfd, buf, NICK) & NICK) break;
160 }
161 }
162
163 if (chan) {
164 dprintf(sockfd, "JOIN %s\r\n", chan);
165 }
166
167 return 0;
168 }
169
170 int irc_poll(const int sockfd, const int infd, const int outfd) {
171 int n;
172 char buf[BUFSIZ];
173 enum { IRC, CLI };
174 struct pollfd fds[2];
175 fds[IRC].fd = sockfd;
176 fds[IRC].events = POLLIN;
177 fds[CLI].fd = infd;
178 fds[CLI].events = POLLIN;
179
180 for (;;) {
181 poll(fds, 2, POLL_TIMEOUT) OR_DIE;
182
183 /* XXX: long responses don't get fully processed until user input */
184 if (fds[IRC].revents & POLLIN) {
185 n = read(sockfd, buf, BUFSIZ);
186 if (n == 0) return -1; /* server closed connection */
187 write(outfd, buf, n);
188 irc_answer(sockfd, buf, NO_CMD);
189 /* update last-msg-rcvd here */
190 }
191 if (fds[CLI].revents & POLLIN) {
192 n = read(infd, buf, BUFSIZ);
193 if (n == 0) return 0; /* we closed connection */
194 write(sockfd, buf, n);
195 }
196
197 /* send ping here */
198 /*
199 dprintf(sockfd, "PING\r\n");
200 // poll-read
201 if (irc_answer(sockfd, buf, NICK) & NICK) break;
202 */
203 }
204 }
205
206 void irc_cleanup(const int sockfd) {
207 write(sockfd, "QUIT :ircpipe\r\n", 15);
208 shutdown(sockfd, SHUT_RDWR);
209 close(sockfd);
210 }
211
212 int main(int argc, char **argv) {
213 char *host = NULL;
214 char *port = NULL;
215 char *nick = NULL;
216 char *pass = NULL;
217 char *chan = NULL;
218 size_t ping_iv = DEFAULT_PING; /* interval between outgoing pings */
219 size_t resp_to = DEFAULT_TIMEOUT; /* how long to wait for command response (connect, ping, auth, ...) */
220 int tls = DEFAULT_TLS;
221 int pass_type = NO_PASSWD;
222
223 int sockfd;
224 int rv;
225
226 int opt; opterr = 0;
227
228 pass = getenv("IRC_PASSWD");
229
230 while ((opt = getopt(argc, argv, "n:j:pPsSkh")) != -1) {
231 switch (opt) {
232 case 'n': nick = optarg; break;
233 case 'p': pass_type = SERVER_PASSWD; break;
234 case 'P': pass_type = SASL_PLAIN_PASSWD; break;
235 case 's': tls = USE_TLS; break;
236 case 'S': tls = NO_TLS; break;
237 case 'k': tls = INSECURE_TLS; break;
238 case 'j': chan = optarg; break;
239 default: irc_help(argv[0], opt != 'h');
240 }
241 }
242
243 if (optind < argc) {
244 host = argv[optind++];
245 } else {
246 /* too few positional arguments */
247 irc_help(argv[0], 1);
248 }
249 if (optind < argc) {
250 port = argv[optind++];
251 } else {
252 port = (tls == NO_TLS)
253 ? DEFAULT_PORT_PLAIN
254 : DEFAULT_PORT_TLS;
255 }
256 if (optind < argc) {
257 /* too many positional arguments */
258 irc_help(argv[0], 1);
259 }
260
261 if (pass_type != NO_PASSWD && pass == NULL) {
262 fprintf(stderr, "must set IRC_PASSWD envvar to use -p/-P\n");
263 exit(1);
264 }
265
266 printf("\033[91mconnecting to %s:%hd as %s, joining %s\033[0m\n", host, port, nick, chan);
267 sockfd = irc_connect(host, port); sockfd OR_DIE;
268 printf("\033[91mirc_setup...\033[0m\n", host, port, nick, chan);
269 irc_setup(sockfd, 1, nick, pass, pass_type, chan) OR_DIE;
270 printf("\033[91mirc_poll...\033[0m\n", host, port, nick, chan);
271 rv = irc_poll(sockfd, 0, 1);
272 irc_cleanup(sockfd);
273
274 return (rv < 0);
275 }
Imprint / Impressum