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