1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /*
3 * Pan - A Newsreader for Gtk+
4 * Copyright (C) 2002-2006 Charles Kerr <charles@rebelbase.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; version 2 of the License.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, see <http://www.gnu.org/licenses/>.
17 *
18 */
19
20 #define DEBUG_SOCKET_IO
21
22 /******
23 *******
24 ******/
25
26 #include <config.h>
27 #include <iostream>
28 #include <string>
29 #include <cerrno>
30 #include <cstring>
31
32 extern "C" {
33 #include <unistd.h>
34 #include <glib/gi18n.h>
35 }
36
37 #include <pan/general/file-util.h>
38 #include <pan/general/log.h>
39 #include <pan/general/macros.h>
40 #include <pan/general/worker-pool.h>
41
42 #ifdef G_OS_WIN32
43 // this #define is necessary for mingw
44 #undef _WIN32_WINNT
45 #define _WIN32_WINNT 0x0501
46 #include <ws2tcpip.h>
47 #undef gai_strerror
48 /*
49 #define gai_strerror(i) gai_strerror_does_not_link (i)
50 static const char*
51 gai_strerror_does_not_link (int errval)
52 {
53 static char buf[32];
54 g_snprintf (buf, sizeof(buf), "Winsock error %d", errval);
55 return buf;
56 }
57 */
58 static const char*
get_last_error(int err)59 get_last_error (int err)
60 {
61 const char * msg = 0;
62 switch(err) {
63 case WSANOTINITIALISED: msg = "No successful WSAStartup call yet."; break;
64 case WSAENETDOWN: msg = "The network subsystem has failed."; break;
65 case WSAEADDRINUSE: msg = "Fully qualified address already bound"; break;
66 case WSAEADDRNOTAVAIL: msg = "The specified address is not a valid address for this computer."; break;
67 case WSAEFAULT: msg = "Error in socket address"; break;
68 case WSAEINPROGRESS: msg = "A call is already in progress"; break;
69 case WSAEINVAL: msg = "The socket is already bound to an address."; break;
70 case WSAENOBUFS: msg = "Not enough buffers available, too many connections."; break;
71 case WSAENOTSOCK: msg = "The descriptor is not a socket."; break;
72 case 11001: msg = "Host not found"; break;
73 default: msg = "Connect failed";
74 }
75 return msg;
76 }
77
78 #else
79 #include <signal.h>
80 #include <sys/types.h>
81 #include <sys/socket.h>
82 #include <netinet/in.h>
83 #include <netdb.h>
84 #include <arpa/inet.h>
85 #define closesocket(fd) close(fd)
86 #endif
87
88 #include <pan/general/debug.h>
89 #include <pan/general/log.h>
90 #include <pan/general/string-view.h>
91 #include <pan/usenet-utils/gnksa.h>
92 #include "socket-impl-gio.h"
93 #include "socket-impl-main.h"
94
95 using namespace pan;
96
97 #ifndef G_OS_WIN32
98 extern t_getaddrinfo p_getaddrinfo;
99 extern t_freeaddrinfo p_freeaddrinfo;
100 #endif
101
102 namespace
103 {
104
105 GIOChannel *
create_channel(const StringView & host_in,int port,std::string & setme_err)106 create_channel (const StringView& host_in, int port, std::string& setme_err)
107 {
108 int err;
109 int sockfd;
110
111 #ifndef G_OS_WIN32
112 signal (SIGPIPE, SIG_IGN);
113 #endif
114
115 // get an addrinfo for the host
116 const std::string host (host_in.str, host_in.len);
117 char portbuf[32], hpbuf[255];
118 g_snprintf (portbuf, sizeof(portbuf), "%d", port);
119 g_snprintf (hpbuf,sizeof(hpbuf),"%s:%s",host_in.str,portbuf);
120
121 #ifdef G_OS_WIN32 // windows might not have getaddrinfo...
122 if (!p_getaddrinfo)
123 {
124 struct hostent * ans = isalpha (host[0])
125 ? gethostbyname (host.c_str())
126 : gethostbyaddr (host.c_str(), host.size(), AF_INET);
127
128 err = WSAGetLastError();
129 if (err || !ans) {
130 setme_err = get_last_error (err);
131 return 0;
132 }
133
134 // try opening the socket
135 sockfd = socket (AF_INET, SOCK_STREAM, 0 /*IPPROTO_TCP*/);
136 if (sockfd < 0)
137 return 0;
138
139 // Try connecting
140 int i = 0;
141 err = -1;
142 struct sockaddr_in server;
143 memset (&server, 0, sizeof(struct sockaddr_in));
144 while (err && ans->h_addr_list[i])
145 {
146 char *addr = ans->h_addr_list[i];
147 memcpy (&server.sin_addr, addr, ans->h_length);
148 server.sin_family = AF_INET;
149 server.sin_port = htons(port);
150 ++i;
151 err = connect (sockfd,(struct sockaddr*)&server, sizeof(server));
152 }
153
154 if (err) {
155 closesocket (sockfd);
156 setme_err = get_last_error (err);
157 return 0;
158 }
159 }
160 else
161 #endif // #ifdef G_OS_WIN32 ...
162 {
163 errno = 0;
164 struct addrinfo hints;
165 memset (&hints, 0, sizeof(struct addrinfo));
166 hints.ai_flags = 0;
167 hints.ai_family = 0;
168 hints.ai_socktype = SOCK_STREAM;
169 struct addrinfo * ans;
170 err = ::getaddrinfo (host.c_str(), portbuf, &hints, &ans);
171 if (err != 0) {
172 char buf[512];
173 snprintf (buf, sizeof(buf), _("Error connecting to “%s”"), hpbuf);
174 setme_err = buf;
175 if (errno) {
176 setme_err += " (";
177 setme_err += file :: pan_strerror (errno);
178 setme_err += ")";
179 }
180 return 0;
181 }
182
183 // try to open a socket on any ipv4 or ipv6 addresses we found
184 errno = 0;
185 sockfd = -1;
186 for (struct addrinfo * walk(ans); walk && sockfd<0; walk=walk->ai_next)
187 {
188 // only use ipv4 or ipv6 addresses
189 if ((walk->ai_family!=PF_INET) && (walk->ai_family!=PF_INET6))
190 continue;
191
192 // try to create a socket...
193 sockfd = ::socket (walk->ai_family, walk->ai_socktype, walk->ai_protocol);
194 if (sockfd < 0)
195 continue;
196
197 // and make a connection
198 if (::connect (sockfd, walk->ai_addr, walk->ai_addrlen) < 0) {
199 closesocket (sockfd);
200 sockfd = -1;
201 }
202 }
203
204 // cleanup
205 ::freeaddrinfo (ans);
206 }
207
208 // create the giochannel...
209 if (sockfd < 0) {
210 char buf[512];
211 snprintf (buf, sizeof(buf), _("Error connecting to “%s”"), hpbuf);
212 setme_err = buf;
213 if (errno) {
214 setme_err += " (";
215 setme_err += file :: pan_strerror (errno);
216 setme_err += ")";
217 }
218 return 0;
219 }
220
221 GIOChannel * channel (0);
222 #ifndef G_OS_WIN32
223 channel = g_io_channel_unix_new (sockfd);
224 g_io_channel_set_flags (channel, G_IO_FLAG_NONBLOCK, NULL);
225 #else
226 channel = g_io_channel_win32_new_socket (sockfd);
227 #endif
228 g_io_channel_set_encoding (channel, NULL, NULL);
229 g_io_channel_set_buffered (channel, true);
230 g_io_channel_set_line_term (channel, "\n", 1);
231 return channel;
232 }
233 }
234
235 /****
236 *****
237 *****
238 *****
239 ****/
240
GIOChannelSocket()241 GIOChannelSocket :: GIOChannelSocket ():
242 _channel (0),
243 _tag_watch (0),
244 _tag_timeout (0),
245 _listener (0),
246 _out_buf (g_string_new (NULL)),
247 _in_buf (g_string_new (NULL)),
248 _io_performed (false)
249 {
250 debug ("GIOChannelSocket ctor " << (void*)this);
251 }
252
253 namespace
254 {
remove_source(guint & tag)255 void remove_source (guint& tag) {
256 if (tag) {
257 g_source_remove (tag);
258 tag = 0;
259 }
260 }
261 }
262
~GIOChannelSocket()263 GIOChannelSocket :: ~GIOChannelSocket ()
264 {
265 debug(" destroying GIO socket "<<this);
266
267 remove_source (_tag_watch);
268 remove_source (_tag_timeout);
269
270 if (_channel)
271 {
272 g_io_channel_shutdown (_channel, TRUE, NULL);
273 g_io_channel_unref (_channel);
274 _channel = 0;
275 }
276
277 g_string_free (_out_buf, TRUE);
278 _out_buf = 0;
279
280 g_string_free (_in_buf, TRUE);
281 _in_buf = 0;
282 }
283
284 bool
open(const StringView & address,int port,std::string & setme_err)285 GIOChannelSocket :: open (const StringView& address, int port, std::string& setme_err)
286 {
287 _host.assign (address.str, address.len);
288 _channel = create_channel (address, port, setme_err);
289 if (_channel)
290 {
291 #ifdef G_OS_WIN32
292 _id = g_io_channel_win32_get_fd(_channel);
293 #else
294 _id = g_io_channel_unix_get_fd(_channel);
295 #endif // G_OS_WIN32
296 }
297 return _channel != 0;
298 }
299
300 void
get_host(std::string & setme) const301 GIOChannelSocket :: get_host (std::string& setme) const
302 {
303 setme = _host;
304 }
305
306 void
write_command(const StringView & command,Listener * l)307 GIOChannelSocket :: write_command (const StringView& command, Listener * l)
308 {
309 _partial_read.clear ();
310 _listener = l;
311
312 g_string_truncate (_out_buf, 0);
313 if (!command.empty())
314 g_string_append_len (_out_buf, command.str, command.len);
315
316 set_watch_mode (WRITE_NOW);
317 }
318
319 /***
320 ****
321 ***/
322
323 GIOChannelSocket :: DoResult
do_read()324 GIOChannelSocket :: do_read ()
325 {
326 g_assert (!_out_buf->len);
327
328 GError * err (0);
329 GString * g (_in_buf);
330
331 bool more (true);
332 while (more && !_abort_flag)
333 {
334 _io_performed = true;
335 const GIOStatus status (g_io_channel_read_line_string (_channel, g, NULL, &err));
336
337 if (status == G_IO_STATUS_NORMAL)
338 {
339 g_string_prepend_len (g, _partial_read.c_str(), _partial_read.size());
340 //TODO validate!
341 _partial_read.clear ();
342
343 debug_v ("read [" << g->str << "]"); // verbose debug, if --debug --debug was on the command-line
344 increment_xfer_byte_count (g->len);
345
346 more = _listener->on_socket_response (this, StringView (g->str, g->len));
347 }
348 else if (status == G_IO_STATUS_AGAIN)
349 {
350 // see if we've got a partial line buffered up
351 if (_channel->read_buf) {
352 _partial_read.append (_channel->read_buf->str, _channel->read_buf->len);
353 g_string_set_size (_channel->read_buf, 0);
354 }
355 return IO_READ;
356 }
357 else
358 {
359 const char * msg (err ? err->message : _("Unknown Error"));
360 Log::add_err_va (_("Error reading from %s: %s"), _host.c_str(), msg);
361 if (err != NULL)
362 g_clear_error (&err);
363 return IO_ERR;
364 }
365 }
366
367 return IO_DONE;
368 }
369
370
371 GIOChannelSocket :: DoResult
do_write()372 GIOChannelSocket :: do_write ()
373 {
374 g_assert (_partial_read.empty());
375
376 GString * g = _out_buf;
377
378 #if 0
379 // #ifdef DEBUG_SOCKET_IO
380 // -2 to trim out trailing \r\n
381 std::cerr << LINE_ID << " channel " << _channel
382 << " writing ["<<StringView(g->str,g->len>=2?g->len-2:g->len)<< "]\n";
383 #endif
384
385 _io_performed = true;
386 GError * err = 0;
387 gsize out = 0;
388 GIOStatus status = g->len
389 ? g_io_channel_write_chars (_channel, g->str, g->len, &out, &err)
390 : G_IO_STATUS_NORMAL;
391 debug ("socket " << this << " channel " << _channel
392 << " maybe wrote [" << g->str << "]; status was " << status);
393
394 if (status == G_IO_STATUS_NORMAL)
395 status = g_io_channel_flush (_channel, &err);
396
397 if (err) {
398 Log::add_err (err->message);
399 g_clear_error (&err);
400 return IO_ERR;
401 }
402
403 if (out > 0) {
404 increment_xfer_byte_count (out);
405 g_string_erase (g, 0, out);
406 }
407
408 const bool finished = (!g->len) && (status==G_IO_STATUS_NORMAL);
409 if (!finished) return IO_WRITE; // not done writing.
410 if (_listener) return IO_READ; // listener wants to read the server's response
411 return IO_DONE; // done writing and not listening to response.
412 }
413
414 gboolean
timeout_func(gpointer sock_gp)415 GIOChannelSocket :: timeout_func (gpointer sock_gp)
416 {
417 GIOChannelSocket * self (static_cast<GIOChannelSocket*>(sock_gp));
418
419 if (!self->_io_performed)
420 {
421 debug ("error: channel " << self->_channel << " not responding.");
422 gio_func (self->_channel, G_IO_ERR, sock_gp);
423 return false;
424 }
425
426 // wait another TIMEOUT_SECS and check again.
427 self->_io_performed = false;
428 return true;
429 }
430
431 gboolean
gio_func(GIOChannel * channel,GIOCondition cond,gpointer sock_gp)432 GIOChannelSocket :: gio_func (GIOChannel * channel,
433 GIOCondition cond,
434 gpointer sock_gp)
435 {
436 return static_cast<GIOChannelSocket*>(sock_gp)->gio_func (channel, cond);
437 }
438
439 gboolean
gio_func(GIOChannel * channel,GIOCondition cond)440 GIOChannelSocket :: gio_func (GIOChannel * channel,
441 GIOCondition cond)
442 {
443 debug ("gio_func: sock " << this << ", channel " << channel << ", cond " << cond);
444
445 set_watch_mode (IGNORE_NOW);
446
447 if (_abort_flag)
448 {
449 _listener->on_socket_abort (this);
450 }
451 else if (!(cond & (G_IO_IN | G_IO_OUT)))
452 {
453 _listener->on_socket_error (this);
454 }
455 else // G_IO_IN or G_IO_OUT
456 {
457 const DoResult result = (cond & G_IO_IN) ? do_read () : do_write ();
458 if (result == IO_ERR) _listener->on_socket_error (this);
459 else if (result == IO_READ) set_watch_mode (READ_NOW);
460 else if (result == IO_WRITE) set_watch_mode (WRITE_NOW);
461 }
462
463 return false; // set_watch_now(IGNORE) cleared the tag that called this func
464 }
465
466 namespace
467 {
468 const unsigned int TIMEOUT_SECS (30);
469 }
470
471 void
set_watch_mode(WatchMode mode)472 GIOChannelSocket :: set_watch_mode (WatchMode mode)
473 {
474 debug ("socket " << this << " calling set_watch_mode " << mode << "; _channel is " << _channel);
475 remove_source (_tag_watch);
476 remove_source (_tag_timeout);
477
478 guint cond;
479 switch (mode)
480 {
481 case IGNORE_NOW:
482 // don't add any watches
483 debug("channel " << _channel << " setting mode **IGNORE**");
484 break;
485
486 case READ_NOW:
487 debug("channel " << _channel << " setting mode read");
488 cond = (int)G_IO_IN | (int)G_IO_ERR | (int)G_IO_HUP | (int)G_IO_NVAL;
489 _tag_watch = g_io_add_watch (_channel, (GIOCondition)cond, gio_func, this);
490 _tag_timeout = g_timeout_add (TIMEOUT_SECS*1000, timeout_func, this);
491 _io_performed = false;
492 break;
493
494 case WRITE_NOW:
495 debug("channel " << _channel << " setting mode write");
496 cond = (int)G_IO_OUT | (int)G_IO_ERR | (int)G_IO_HUP | (int)G_IO_NVAL;
497 _tag_watch = g_io_add_watch (_channel, (GIOCondition)cond, gio_func, this);
498 _tag_timeout = g_timeout_add (TIMEOUT_SECS*1000, timeout_func, this);
499 _io_performed = false;
500 break;
501 }
502
503 debug ("set_watch_mode " << mode << ": _tag_watch is now " << _tag_watch);
504 }
505