]>
git.gir.st - ircpipe.git/blob - ircpipe.c
1 #define _POSIX_C_SOURCE 200809L /* getopt(>=2), gethostbyname(>=200809L), dprintf(>=200809L), strtok_r(*) */
2 #define _DEFAULT_SOURCE /* herror(*) */
10 #include <sys/socket.h>
12 #include <openssl/ssl.h>
13 #include <openssl/err.h>
16 #define DEFAULT_PORT 6667
17 #define DEFAULT_PING 60000 /*ms*/
18 #define DEFAULT_TIMEOUT 2000 /*ms*/
19 #define DEFAULT_TLS 0 /*off*/
21 #define POLL_TIMEOUT 100
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)
28 void irc_help(const char *exe
, const int code
) {
29 fprintf(stderr
, "Usage: %s [-n NICK] [-j CHAN] HOST [PORT]\n", exe
);
33 int irc_connect(const char *host
, const unsigned short port
) {
35 struct sockaddr_in addr
= {0};
36 struct hostent
*host_e
;
38 host_e
= gethostbyname(host
); host_e OR_DIE_h
;
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]);
44 sockfd
= socket(AF_INET
, SOCK_STREAM
, 0); sockfd OR_DIE
;
46 /* connect timeout here */
47 connect(sockfd
, (struct sockaddr
*)&addr
, sizeof(addr
)) OR_DIE
;
51 enum { /* requested command: */
56 int irc_answer(const int sockfd
, char *buf
, const unsigned int command
) {
57 unsigned int seen
= 0;
59 char *line
= strtok_r(buf
, "\n", &saveptr
);
61 /* skip over prefix (servername): */
63 while (*line
++ != ' ');
65 printf("\033[92m>>>%s<<<\033[0m\n", line
);
66 /* look for command responses, if any: */
68 case PING
: seen
|= PING
* (strncmp(line
, "PONG ", 5)==0); break;
69 case NICK
: seen
|= NICK
* (strncmp(line
, "001 " , 4)==0); break;
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);
78 } while (line
= strtok_r(NULL
, "\n", &saveptr
));
80 printf("\033[91mseen=%d\033[0m\n", seen
);
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]:'=';
99 int irc_setup(const int sockfd
, const int outfd
, const char *nick
, const char *pass
, const char *chan
) {
102 struct pollfd fds
[1];
104 fds
[0].events
= POLLIN
;
106 if (pass
) { /* SASL as per IRCv3 */
107 dprintf(sockfd
, "CAP REQ :sasl\r\n");
110 dprintf(sockfd
, "NICK %s\r\n", nick
);
111 dprintf(sockfd
, "USER %s 0.0.0.0 %s :%s\r\n", nick
, nick
, nick
);
113 /* SASL-PLAIN, IRCv3 */
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");
127 /* block until we get a RPL_WELCOME: */
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;
137 dprintf(sockfd
, "JOIN %s\r\n", chan
);
143 int irc_poll(const int sockfd
, const int infd
, const int outfd
) {
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
;
154 poll(fds
, 2, POLL_TIMEOUT
) OR_DIE
;
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 */
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
);
172 dprintf(sockfd, "PING\r\n");
174 if (irc_answer(sockfd, buf, NICK) & NICK) break;
179 void irc_cleanup(const int sockfd
) {
180 write(sockfd
, "QUIT :ircpipe\r\n", 15);
181 shutdown(sockfd
, SHUT_RDWR
);
185 int main(int argc
, char **argv
) {
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
;
199 while ((opt
= getopt(argc
, argv
, "n:j:psh")) != -1) {
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');
211 host
= argv
[optind
++];
213 /* too few positional arguments */
214 irc_help(argv
[0], 1);
217 port
= atoi(argv
[optind
++]);
220 /* too many positional arguments */
221 irc_help(argv
[0], 1);
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);