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