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