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 #include <config.h>
21 #include <ctime>
22 #include <cstdio> // snprintf
23 #include <glib/gi18n.h>
24 #include <pan/general/debug.h>
25 #include <pan/general/log.h>
26 #include <pan/general/macros.h>
27 #include "nntp-pool.h"
28 
29 using namespace pan;
30 
31 namespace
32 {
33   const int MAX_IDLE_SECS (30);
34 
35   const int HUP_IDLE_SECS (90);
36 
37   const int TOO_MANY_CONNECTIONS_LOCKOUT_SECS (120);
38 }
39 
NNTP_Pool(const Quark & server,ServerInfo & server_info,Prefs & prefs,SocketCreator * creator,CertStore & store)40 NNTP_Pool :: NNTP_Pool (const Quark        & server,
41                         ServerInfo         & server_info,
42                         Prefs              & prefs,
43                         SocketCreator      * creator,
44                         CertStore          & store):
45 
46   _server_info (server_info),
47   _prefs (prefs),
48   _server (server),
49   _socket_creator (creator),
50   _pending_connections (0),
51   _active_count (0),
52   _time_to_allow_new_connections (0),
53   _certstore(store)
54 {
55 }
56 
~NNTP_Pool()57 NNTP_Pool :: ~NNTP_Pool ()
58 {
59   foreach (pool_items_t, _pool_items, it) {
60     delete it->nntp->_socket;
61     delete it->nntp;
62   }
63 }
64 
65 /***
66 ****
67 ***/
68 
69 bool
new_connections_are_allowed() const70 NNTP_Pool :: new_connections_are_allowed () const
71 {
72   return _time_to_allow_new_connections <= time(0);
73 }
74 
75 void
disallow_new_connections_for_n_seconds(int n)76 NNTP_Pool :: disallow_new_connections_for_n_seconds (int n)
77 {
78   _time_to_allow_new_connections = time(0) + n;
79 }
80 
81 void
allow_new_connections()82 NNTP_Pool :: allow_new_connections ()
83 {
84   _time_to_allow_new_connections = 0;
85 }
86 
87 /***
88 ****
89 ***/
90 
91 void
abort_tasks()92 NNTP_Pool :: abort_tasks ()
93 {
94   foreach (pool_items_t, _pool_items, it)
95     if (!it->is_checked_in)
96       it->nntp->_socket->set_abort_flag (true);
97 }
98 
99 void
kill_tasks()100 NNTP_Pool :: kill_tasks ()
101 {
102   foreach (pool_items_t, _pool_items, it)
103     it->nntp->_socket->set_abort_flag (true);
104 }
105 
106 
107 NNTP*
check_out()108 NNTP_Pool :: check_out ()
109 {
110   NNTP * nntp (0);
111 
112   foreach (pool_items_t, _pool_items, it) {
113     debug("pool item "<<it->is_checked_in<<" "<<it->nntp);
114     if (it->is_checked_in) {
115       nntp = it->nntp;
116       it->is_checked_in = false;
117       ++_active_count;
118       debug ("nntp " << nntp << " is now checked out");
119       break;
120     }
121   }
122 
123   return nntp;
124 }
125 
126 void
check_in(NNTP * nntp,Health health)127 NNTP_Pool :: check_in (NNTP * nntp, Health health)
128 {
129   debug ("nntp " << nntp << " is being checked in, health is " << health);
130 
131   // find this nntp in _pool_items
132   pool_items_t::iterator it;
133   for (it=_pool_items.begin(); it!=_pool_items.end(); ++it)
134     if (it->nntp == nntp)
135       break;
136 
137   // process the nntp if we have a match
138   if (it != _pool_items.end())
139   {
140     const bool bad_connection = (health == ERR_NETWORK);
141     const bool nospace = (health == ERR_NOSPACE);
142     int active, idle, pending, max;
143     get_counts (active, idle, pending, max);
144     const bool too_many = (pending + active) > max;
145     const bool discard = bad_connection || too_many || nospace;
146 
147     --_active_count;
148 
149     if (discard)
150     {
151       delete it->nntp->_socket;
152       delete it->nntp;
153       _pool_items.erase (it);
154       if (bad_connection)
155         allow_new_connections (); // to make up for this one
156     }
157     else
158     {
159       it->is_checked_in = true;
160       it->last_active_time = time (NULL);
161       fire_pool_has_nntp_available ();
162     }
163   }
164 }
165 
166 /***
167 ****
168 ***/
169 
170 void
on_socket_created(const StringView & host,int port UNUSED,bool ok,Socket * socket)171 NNTP_Pool :: on_socket_created (const StringView  & host,
172                                 int                 port UNUSED,
173                                 bool                ok,
174                                 Socket            * socket)
175 {
176   std::string user;
177   gchar* pass(NULL);
178   ok = ok && _server_info.get_server_auth (_server, user, pass, _prefs.get_flag("use-password-storage", false));
179   debug("on socket created "<<host<<" "<<ok<<" "<<socket);
180   if (!ok)
181   {
182     delete socket;
183     --_pending_connections;
184   }
185   else
186   {
187     // okay, we at least established a connection.
188     // now try to handshake and pass the buck to on_nntp_done().
189     NNTP * nntp;
190     if (!_prefs.get_flag("use-password-storage", false))
191     {
192       std::string pw (pass ? pass : "");
193       if (pass) g_free(pass);
194       nntp = new NNTP (_server, user, pw, _server_info, socket);
195     }
196     else
197     {
198       nntp = new NNTP ( _server, user, pass, _server_info, socket);
199     }
200     nntp->handshake (this);
201   }
202 }
203 
204 
205 void
on_nntp_done(NNTP * nntp,Health health,const StringView & response)206 NNTP_Pool :: on_nntp_done (NNTP* nntp, Health health, const StringView& response)
207 {
208    debug ("NNTP_Pool: on_nntp_done()");
209 
210    if (health == ERR_COMMAND) // news server isn't accepting our connection!
211    {
212      std::string s (response.str, response.len);
213      foreach (std::string, s, it) *it = tolower (*it);
214 
215      // too many connections.
216      // there doesn't seem to be a reliable way to test for this:
217      // response can be 502, 400, or 451... and the error messages
218      // vary from server to server
219      if (   (s.find ("502") != s.npos)
220          || (s.find ("400") != s.npos)
221          || (s.find ("451") != s.npos)
222          || (s.find ("480") != s.npos) // http://bugzilla.gnome.org/show_bug.cgi?id=409085
223          || (s.find ("too many") != s.npos)
224          || (s.find ("limit reached") != s.npos)
225          || (s.find ("maximum number of connections") != s.npos)
226          || (s.find ("multiple") != s.npos) )
227      {
228        disallow_new_connections_for_n_seconds (TOO_MANY_CONNECTIONS_LOCKOUT_SECS);
229      }
230      else
231      {
232        const std::string addr (_server_info.get_server_address (_server));
233        std::string s;
234        char buf[4096];
235        snprintf (buf, sizeof(buf), _("Unable to connect to “%s”"), addr.c_str());
236        s = buf;
237        if (!response.empty()) {
238          s += ":\n";
239          s.append (response.str, response.len);
240        }
241        fire_pool_error (s.c_str());
242      }
243    }
244 
245    if (health != OK)
246    {
247       delete nntp->_socket;
248       delete nntp;
249       nntp = 0;
250    }
251 
252    --_pending_connections;
253 
254    // if success...
255    if (nntp != 0)
256    {
257       debug ("success with handshake to " << _server << ", nntp " << nntp);
258 
259       PoolItem i;
260       i.nntp = nntp;
261       i.is_checked_in = true;
262       i.last_active_time = time (0);
263       _pool_items.push_back (i);
264 
265       fire_pool_has_nntp_available ();
266    }
267 }
268 
269 void
get_counts(int & setme_active,int & setme_idle,int & setme_pending,int & setme_max) const270 NNTP_Pool :: get_counts (int& setme_active,
271                          int& setme_idle,
272                          int& setme_pending,
273                          int& setme_max) const
274 {
275   setme_active = _active_count;
276   setme_idle = _pool_items.size() - _active_count;
277   setme_max = _server_info.get_server_limits (_server);
278   setme_pending  = _pending_connections;
279 }
280 
281 
282 void
request_nntp(WorkerPool & threadpool)283 NNTP_Pool :: request_nntp (WorkerPool& threadpool)
284 {
285   int active, idle, pending, max;
286   get_counts (active, idle, pending, max);
287 
288 #if 0
289   std::cerr << LINE_ID << "server " << _server << ", "
290             << "active: " << active << ' '
291             << "idle: " << idle << ' '
292             << "pending: " << pending << ' '
293             << "max: " << max << ' ' << std::endl;
294 #endif
295 
296   if (!idle && ((pending+active)<max) && new_connections_are_allowed())
297   {
298     std::string address;
299     int port;
300     _server_info.get_server_addr (_server, address, port);
301     if (!_certstore.in_blacklist(_server))
302     {
303       ++_pending_connections;
304       _socket_creator->create_socket (_server_info, _server, address, port, threadpool, this);
305     }
306   }
307 }
308 
309 /**
310 ***
311 **/
312 
313 namespace
314 {
315   class NoopListener: public NNTP::Listener
316   {
317     private:
318       NNTP::Source * source;
319       const bool hang_up;
320 
321     public:
NoopListener(NNTP::Source * s,bool b)322       NoopListener (NNTP::Source * s, bool b): source(s), hang_up(b) {}
~NoopListener()323       virtual ~NoopListener() {}
on_nntp_done(NNTP * nntp,Health health,const StringView & response UNUSED)324       virtual void on_nntp_done  (NNTP * nntp,
325                                   Health health,
326                                   const StringView& response UNUSED) {
327         source->check_in (nntp, hang_up ? ERR_NETWORK : health);
328         delete this;
329       }
330   };
331 }
332 
333 void
idle_upkeep()334 NNTP_Pool :: idle_upkeep ()
335 {
336   for (;;)
337   {
338     PoolItem * item (0);
339 
340     const time_t now (time (0));
341     foreach (pool_items_t, _pool_items, it) {
342       if (it->is_checked_in && ((now - it->last_active_time) > MAX_IDLE_SECS)) {
343         item = &*it;
344         break;
345       }
346     }
347 
348     // if no old, checked-in items, then we're done
349     if (!item)
350       break;
351 
352     // send a keepalive message to the old, checked-in item we found
353     // the noop can trigger changes in _pool_items, so that must be
354     // the last thing we do with the 'item' pointer.
355     const time_t idle_time_secs = now - item->last_active_time;
356     item->is_checked_in = false;
357     ++_active_count;
358     if (idle_time_secs >= HUP_IDLE_SECS)
359       item->nntp->goodbye (new NoopListener (this, true));
360     else
361       item->nntp->noop (new NoopListener (this, false));
362     item = 0;
363   }
364 }
365 
366 #ifdef HAVE_GNUTLS
367 void
on_verify_cert_failed(gnutls_x509_crt_t cert,std::string server,int nr)368 NNTP_Pool:: on_verify_cert_failed(gnutls_x509_crt_t cert, std::string server, int nr)
369 {
370 }
371 
372 void
on_valid_cert_added(gnutls_x509_crt_t cert,std::string server)373 NNTP_Pool :: on_valid_cert_added (gnutls_x509_crt_t cert, std::string server)
374 {
375 }
376 #endif
377 
378