]>
git.gir.st - ircpipe.git/blob - ircpipe.c
1 #define _POSIX_C_SOURCE 200809L /* getopt(>=2), dprintf(>=200809L), strtok_r(*), getaddrinfo(>=200112L) */
9 #include <sys/socket.h>
13 #include <openssl/ssl.h>
14 #include <openssl/err.h>
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"
23 #define POLL_TIMEOUT 100
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);}
42 void irc_help(const char *exe
, const int code
) {
43 fprintf(stderr
, "Usage: %s [-n NICK] [-j CHAN] HOST [PORT]\n", exe
);
47 int irc_connect(const char *host
, const char *port
) {
49 struct addrinfo
*results
, *r
;
51 fprintf(stderr
, "%s : %s\n", host
,port
);
52 int err
= getaddrinfo(host
, port
, NULL
, &results
); OR_DIE_gai(err
); /*unable to resolve*/
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 */
58 if (connect(sockfd
, r
->ai_addr
, r
->ai_addrlen
) == 0)
59 break; /* successfully connected */
61 close(sockfd
); /* failed, try next addr */
65 /* all failed; abort. */
68 /* connection established. */
70 /* connect timeout here */
73 freeaddrinfo(results
);
77 enum { /* requested command: */
82 int irc_answer(const int sockfd
, char *buf
, const unsigned int command
) {
83 unsigned int seen
= 0;
85 char *line
= strtok_r(buf
, "\n", &saveptr
);
87 /* skip over prefix (servername): */
89 while (*line
++ != ' ');
91 printf("\033[92m>>>%s<<<\033[0m\n", line
);
92 /* look for command responses, if any: */
94 case PING
: seen
|= PING
* (strncmp(line
, "PONG ", 5)==0); break;
95 case NICK
: seen
|= NICK
* (strncmp(line
, "001 " , 4)==0); break;
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);
104 } while (line
= strtok_r(NULL
, "\n", &saveptr
));
106 printf("\033[91mseen=%d\033[0m\n", seen
);
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]:'=';
125 int irc_setup(const int sockfd
, const int outfd
, const char *nick
, const char *pass
, int pass_type
, const char *chan
) {
128 struct pollfd fds
[1];
130 fds
[0].events
= POLLIN
;
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
);
138 dprintf(sockfd
, "NICK %s\r\n", nick
);
139 dprintf(sockfd
, "USER %s 0.0.0.0 %s :%s\r\n", nick
, nick
, nick
);
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");
154 /* block until we get a RPL_WELCOME: */
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;
164 dprintf(sockfd
, "JOIN %s\r\n", chan
);
170 int irc_poll(const int sockfd
, const int infd
, const int outfd
) {
174 struct pollfd fds
[2];
175 fds
[IRC
].fd
= sockfd
;
176 fds
[IRC
].events
= POLLIN
;
178 fds
[CLI
].events
= POLLIN
;
181 poll(fds
, 2, POLL_TIMEOUT
) OR_DIE
;
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 */
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
);
199 dprintf(sockfd, "PING\r\n");
201 if (irc_answer(sockfd, buf, NICK) & NICK) break;
206 void irc_cleanup(const int sockfd
) {
207 write(sockfd
, "QUIT :ircpipe\r\n", 15);
208 shutdown(sockfd
, SHUT_RDWR
);
212 int main(int argc
, char **argv
) {
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
;
228 pass
= getenv("IRC_PASSWD");
230 while ((opt
= getopt(argc
, argv
, "n:j:pPsSkh")) != -1) {
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');
244 host
= argv
[optind
++];
246 /* too few positional arguments */
247 irc_help(argv
[0], 1);
250 port
= argv
[optind
++];
252 port
= (tls
== NO_TLS
)
257 /* too many positional arguments */
258 irc_help(argv
[0], 1);
261 if (pass_type
!= NO_PASSWD
&& pass
== NULL
) {
262 fprintf(stderr
, "must set IRC_PASSWD envvar to use -p/-P\n");
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);