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