]>
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)
34 void irc_help(const char *exe
, const int code
) {
35 fprintf(stderr
, "Usage: %s [-n NICK] [-j CHAN] HOST [PORT]\n", exe
);
39 int irc_connect(const char *host
, const unsigned short port
) {
41 struct sockaddr_in addr
= {0};
42 struct hostent
*host_e
;
44 host_e
= gethostbyname(host
); host_e OR_DIE_h
;
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]);
50 sockfd
= socket(AF_INET
, SOCK_STREAM
, 0); sockfd OR_DIE
;
52 /* connect timeout here */
53 connect(sockfd
, (struct sockaddr
*)&addr
, sizeof(addr
)) OR_DIE
;
57 enum { /* requested command: */
62 int irc_answer(const int sockfd
, char *buf
, const unsigned int command
) {
63 unsigned int seen
= 0;
65 char *line
= strtok_r(buf
, "\n", &saveptr
);
67 /* skip over prefix (servername): */
69 while (*line
++ != ' ');
71 printf("\033[92m>>>%s<<<\033[0m\n", line
);
72 /* look for command responses, if any: */
74 case PING
: seen
|= PING
* (strncmp(line
, "PONG ", 5)==0); break;
75 case NICK
: seen
|= NICK
* (strncmp(line
, "001 " , 4)==0); break;
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);
84 } while (line
= strtok_r(NULL
, "\n", &saveptr
));
86 printf("\033[91mseen=%d\033[0m\n", seen
);
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]:'=';
105 int irc_setup(const int sockfd
, const int outfd
, const char *nick
, const char *pass
, int pass_type
, const char *chan
) {
108 struct pollfd fds
[1];
110 fds
[0].events
= POLLIN
;
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
);
118 dprintf(sockfd
, "NICK %s\r\n", nick
);
119 dprintf(sockfd
, "USER %s 0.0.0.0 %s :%s\r\n", nick
, nick
, nick
);
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");
134 /* block until we get a RPL_WELCOME: */
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;
144 dprintf(sockfd
, "JOIN %s\r\n", chan
);
150 int irc_poll(const int sockfd
, const int infd
, const int outfd
) {
154 struct pollfd fds
[2];
155 fds
[IRC
].fd
= sockfd
;
156 fds
[IRC
].events
= POLLIN
;
157 fds
[CLI
].fd
= sockfd
;
158 fds
[CLI
].events
= POLLIN
;
161 poll(fds
, 2, POLL_TIMEOUT
) OR_DIE
;
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 */
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
);
179 dprintf(sockfd, "PING\r\n");
181 if (irc_answer(sockfd, buf, NICK) & NICK) break;
186 void irc_cleanup(const int sockfd
) {
187 write(sockfd
, "QUIT :ircpipe\r\n", 15);
188 shutdown(sockfd
, SHUT_RDWR
);
192 int main(int argc
, char **argv
) {
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
;
208 pass
= getenv("IRC_PASSWD");
210 while ((opt
= getopt(argc
, argv
, "n:j:psh")) != -1) {
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');
223 host
= argv
[optind
++];
225 /* too few positional arguments */
226 irc_help(argv
[0], 1);
229 port
= atoi(argv
[optind
++]);
232 /* too many positional arguments */
233 irc_help(argv
[0], 1);
236 if (pass_type
!= NO_PASSWD
&& pass
== NULL
) {
237 fprintf(stderr
, "must set IRC_PASSWD envvar to use -p/-P\n");
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);