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 <unistd.h>
22 #include <fstream>
23 #include <iostream>
24 #include <sstream>
25 #include <map>
26 #include <set>
27 #include <vector>
28 extern "C" {
29 #include <glib.h> // for GMarkup
30 #include <glib/gi18n.h>
31 }
32 #include <pan/general/debug.h>
33 #include <pan/general/file-util.h>
34 #include <pan/general/log.h>
35 #include <pan/general/macros.h>
36 #include <pan/general/messages.h>
37 #include "data-impl.h"
38
39 using namespace pan;
40
41 /**
42 ***
43 **/
44
45 void
delete_server(const Quark & server_in)46 DataImpl :: delete_server (const Quark& server_in)
47 {
48 const Quark server (server_in);
49
50 if (_servers.count (server))
51 {
52 const std::string newsrc_filename (_servers[server].newsrc_filename);
53 _servers.erase (server);
54 save_server_properties (*_data_io, _prefs);
55 std::remove (newsrc_filename.c_str());
56 rebuild_backend ();
57 }
58 }
59
60 Quark
add_new_server()61 DataImpl :: add_new_server ()
62 {
63 // find a server ID that's not in use
64 Quark new_server;
65 for (unsigned long i(1); ; ++i) {
66 char buf[64];
67 snprintf (buf, sizeof(buf), "%lu", i);
68 new_server = buf;
69 if (!_servers.count (new_server))
70 break;
71 }
72
73 // add it to the _servers map and give it a default filename
74 std::ostringstream o;
75 o << "newsrc-" << new_server;
76 _servers[new_server].newsrc_filename = o.str ();
77 return new_server;
78 }
79
80 Data :: Server*
find_server(const Quark & server)81 DataImpl :: find_server (const Quark& server)
82 {
83 Server * retval (0);
84
85 servers_t::iterator it (_servers.find (server));
86 if (it != _servers.end())
87 retval = &it->second;
88 return retval;
89 }
90
91 const Data :: Server*
find_server(const Quark & server) const92 DataImpl :: find_server (const Quark& server) const
93 {
94 const Server * retval (0);
95
96 servers_t::const_iterator it (_servers.find (server));
97 if (it != _servers.end())
98 retval = &it->second;
99 return retval;
100 }
101
102 bool
find_server_by_hn(const std::string & server,Quark & setme) const103 DataImpl :: find_server_by_hn (const std::string& server, Quark& setme) const
104 {
105 foreach_const(servers_t, _servers, it)
106 if (it->second.host == server) { setme = it->first; return true; }
107 return false;
108 }
109
110 void
set_server_article_expiration_age(const Quark & server,int days)111 DataImpl :: set_server_article_expiration_age (const Quark & server,
112 int days)
113 {
114 Server * s (find_server (server));
115 assert (s);
116
117 s->article_expiration_age = std::max (0, days);
118
119 }
120
121 void
set_server_auth(const Quark & server,const StringView & username,gchar * & password,bool use_gkr)122 DataImpl :: set_server_auth (const Quark & server,
123 const StringView & username,
124 gchar *&password,
125 bool use_gkr)
126 {
127 Server * s (find_server (server));
128 assert (s);
129
130 s->username = username;
131 #ifndef HAVE_GKR
132 s->password = password;
133 #else
134 if (use_gkr)
135 {
136 PasswordData pw;
137 pw.server = s->host;
138 pw.user = username;
139 pw.pw = password;
140 password_encrypt(pw);
141 }
142 else
143 {
144 s->password = password;
145 }
146 #endif
147
148 }
149
150 void
set_server_trust(const Quark & server,const int setme)151 DataImpl :: set_server_trust (const Quark & server,
152 const int setme)
153 {
154 Server * s (find_server (server));
155 assert (s);
156 s->trust = setme;
157 }
158
159 void
set_server_compression_type(const Quark & server,const int setme)160 DataImpl :: set_server_compression_type (const Quark & server,
161 const int setme)
162 {
163 Server * s (find_server (server));
164 assert (s);
165 s->compression_type = setme;
166 }
167
168 void
set_server_addr(const Quark & server,const StringView & host,int port)169 DataImpl :: set_server_addr (const Quark & server,
170 const StringView & host,
171 int port)
172 {
173 Server * s (find_server (server));
174 assert (s);
175 s->host = host;
176 s->port = port;
177
178 }
179
180
181 void
set_server_limits(const Quark & server,int max_connections)182 DataImpl :: set_server_limits (const Quark & server,
183 int max_connections)
184 {
185 Server * s (find_server (server));
186 assert (s);
187 s->max_connections = max_connections;
188
189 }
190
191 void
set_server_rank(const Quark & server,int rank)192 DataImpl :: set_server_rank (const Quark & server,
193 int rank)
194 {
195 Server * s (find_server (server));
196 assert (s);
197 s->rank = rank;
198
199 }
200
201 void
set_server_ssl_support(const Quark & server,int ssl)202 DataImpl :: set_server_ssl_support (const Quark & server,
203 int ssl)
204 {
205 Server * s (find_server (server));
206 assert (s);
207 s->ssl_support = ssl;
208
209 }
210
211 void
set_server_cert(const Quark & server,const StringView & cert)212 DataImpl :: set_server_cert (const Quark & server,
213 const StringView & cert)
214 {
215
216 Server * s (find_server (server));
217 assert (s);
218 s->cert = cert;
219
220 }
221
222 void
save_server_info(const Quark & server)223 DataImpl :: save_server_info (const Quark& server)
224 {
225 Server * s (find_server (server));
226 assert (s);
227 save_server_properties (*_data_io, _prefs);
228
229 }
230
231
232 bool
get_server_auth(const Quark & server,std::string & setme_username,gchar * & setme_password,bool use_gkr)233 DataImpl :: get_server_auth (const Quark & server,
234 std::string & setme_username,
235 gchar *&setme_password,
236 bool use_gkr)
237 {
238 Server * s (find_server (server));
239 bool found (s);
240 if (found) {
241 setme_username = s->username;
242 #ifndef HAVE_GKR
243 setme_password = g_strdup(s->password.c_str());
244 #else
245 #if GTK_CHECK_VERSION(3,0,0)
246 if (!use_gkr)
247 {
248 setme_password = g_strdup(s->password.c_str());
249 }
250 else if (s->gkr_pw)
251 {
252 setme_password = s->gkr_pw;
253 }
254 else
255 {
256 PasswordData pw;
257 pw.server = s->host;
258 pw.user = s->username;
259
260 if (password_decrypt(pw) == NULL)
261 {
262 Log::add_urgent_va (_("Received no password from libsecret for server %s."), s->host.c_str());
263 }
264 else
265 {
266 setme_password = pw.pw;
267 s->gkr_pw = pw.pw;
268 }
269 }
270 #else
271 if (!use_gkr)
272 {
273 setme_password = g_strdup(s->password.c_str());
274 }
275 else if (s->gkr_pw)
276 {
277 setme_password = s->gkr_pw;
278 }
279 else
280 {
281 PasswordData pw;
282 pw.server = s->host;
283 pw.user = s->username;
284 switch (password_decrypt(pw))
285 {
286 case GNOME_KEYRING_RESULT_NO_MATCH:
287 Log::add_info_va(_("There seems to be no password set for server %s."), s->host.c_str());
288 break;
289
290 case GNOME_KEYRING_RESULT_NO_KEYRING_DAEMON:
291 Log::add_urgent_va (_("GNOME Keyring denied access to the passwords."), s->host.c_str());
292 break;
293
294 case GNOME_KEYRING_RESULT_OK:
295 // setme_password.assign(pw.pw.str, pw.pw.len);
296 setme_password = pw.pw;
297 s->gkr_pw = pw.pw;
298 break;
299
300 default:
301 break;
302 }
303 }
304 #endif /* GTK_CHECK_VERSION(3,0,0) */
305 #endif
306 }
307
308 return found;
309
310 }
311
312 bool
get_server_trust(const Quark & server,int & setme) const313 DataImpl :: get_server_trust (const Quark & server, int& setme) const
314 {
315 const Server * s (find_server (server));
316 const bool found (s);
317 if (found) {
318 setme = s->trust;
319 }
320
321 return found;
322 }
323
324 namespace
325 {
get_compression_type(int val)326 CompressionType get_compression_type(int val)
327 {
328 CompressionType ret = HEADER_COMPRESS_NONE;
329 switch (val)
330 {
331 case 1:
332 ret = HEADER_COMPRESS_XZVER;
333 break;
334
335 case 2:
336 ret = HEADER_COMPRESS_XFEATURE;
337 break;
338
339 case 3:
340 ret = HEADER_COMPRESS_DIABLO;
341 break;
342 }
343 return ret;
344 }
345 }
346
347 bool
get_server_compression_type(const Quark & server,CompressionType & setme) const348 DataImpl :: get_server_compression_type (const Quark & server, CompressionType& setme) const
349 {
350 const Server * s (find_server (server));
351 const bool found (s);
352 if (found)
353 setme = get_compression_type(s->compression_type);
354
355 return found;
356 }
357
358 bool
get_server_addr(const Quark & server,std::string & setme_host,int & setme_port) const359 DataImpl :: get_server_addr (const Quark & server,
360 std::string & setme_host,
361 int & setme_port) const
362 {
363 const Server * s (find_server (server));
364 const bool found (s);
365 if (found) {
366 setme_host = s->host;
367 setme_port = s->port;
368 }
369
370 return found;
371
372 }
373
374 std::string
get_server_address(const Quark & server) const375 DataImpl :: get_server_address (const Quark& server) const
376 {
377 std::string str;
378 const Server * s (find_server (server));
379 if (s) {
380 std::ostringstream x(s->host,std::ios_base::ate);
381 x << ":" << s->port;
382 str = x.str();
383 }
384
385 return str;
386
387 }
388
389 bool
get_server_ssl_support(const Quark & server) const390 DataImpl :: get_server_ssl_support (const Quark & server) const
391 {
392 bool retval (false);
393 const Server * s (find_server (server));
394 if (s)
395 retval = (s->ssl_support != 0);
396
397 return retval;
398
399 }
400
401 std::string
get_server_cert(const Quark & server) const402 DataImpl :: get_server_cert (const Quark & server) const
403 {
404 std::string str;
405 const Server * s (find_server (server));
406 if (s)
407 str = s->cert;
408
409 return str;
410
411 }
412
413 int
get_server_limits(const Quark & server) const414 DataImpl :: get_server_limits (const Quark & server) const
415 {
416 int retval (2);
417 const Server * s (find_server (server));
418 if (s)
419 retval = s->max_connections;
420
421 return retval;
422
423 }
424
425 int
get_server_rank(const Quark & server) const426 DataImpl :: get_server_rank (const Quark & server) const
427 {
428 int retval (1);
429 const Server * s (find_server (server));
430 if (s)
431 retval = s->rank;
432
433 return retval;
434
435 }
436
437 int
get_server_article_expiration_age(const Quark & server) const438 DataImpl :: get_server_article_expiration_age (const Quark & server) const
439 {
440 int retval (31);
441 const Server * s (find_server (server));
442 if (s)
443 retval = s->article_expiration_age;
444
445 return retval;
446
447 }
448
449
450 /***
451 ****
452 ***/
453
454 namespace
455 {
456 typedef std::map<std::string,std::string> keyvals_t;
457 typedef std::map<std::string,keyvals_t> key_to_keyvals_t;
458
459 struct ServerParseContext
460 {
461 std::string key;
462 std::string text;
463 key_to_keyvals_t data;
464 };
465
start_element(GMarkupParseContext * context UNUSED,const gchar * element_name,const gchar ** attribute_names,const gchar ** attribute_vals,gpointer user_data,GError ** error UNUSED)466 void start_element (GMarkupParseContext *context UNUSED,
467 const gchar *element_name,
468 const gchar **attribute_names,
469 const gchar **attribute_vals,
470 gpointer user_data,
471 GError **error UNUSED)
472 {
473 ServerParseContext& mc (*static_cast<ServerParseContext*>(user_data));
474
475 if (!strcmp (element_name, "server"))
476 for (const char **k(attribute_names), **v(attribute_vals); *k; ++k, ++v)
477 if (!strcmp (*k,"id"))
478 mc.key = *v;
479 }
480
end_element(GMarkupParseContext * context UNUSED,const gchar * element_name,gpointer user_data,GError ** error UNUSED)481 void end_element (GMarkupParseContext *context UNUSED,
482 const gchar *element_name,
483 gpointer user_data,
484 GError **error UNUSED)
485 {
486 ServerParseContext& mc (*static_cast<ServerParseContext*>(user_data));
487 if (!mc.key.empty())
488 mc.data[mc.key][element_name] = mc.text;
489 }
490
text(GMarkupParseContext * context UNUSED,const gchar * text,gsize text_len,gpointer user_data,GError ** error UNUSED)491 void text (GMarkupParseContext *context UNUSED,
492 const gchar *text,
493 gsize text_len,
494 gpointer user_data,
495 GError **error UNUSED)
496 {
497 static_cast<ServerParseContext*>(user_data)->text.assign (text, text_len);
498 }
499
to_int(const std::string & s,int default_value=0)500 int to_int (const std::string& s, int default_value=0)
501 {
502 return s.empty() ? default_value : atoi(s.c_str());
503 }
504 }
505
506
507 void
load_server_properties(const DataIO & source)508 DataImpl :: load_server_properties (const DataIO& source)
509 {
510 const std::string filename (source.get_server_filename());
511
512 std::string txt;
513 file :: get_text_file_contents (filename, txt);
514
515 ServerParseContext spc;
516 GMarkupParser p;
517 p.start_element = start_element;
518 p.end_element = end_element;
519 p.text = text;
520 p.passthrough = 0;
521 p.error = 0;
522 GMarkupParseContext* c = g_markup_parse_context_new (&p, (GMarkupParseFlags)0, &spc, 0);
523 GError * gerr (0);
524 if (!txt.empty())
525 g_markup_parse_context_parse (c, txt.c_str(), txt.size(), &gerr);
526 if (gerr) {
527 Log::add_err_va (_("Error reading file “%s”: %s"), filename.c_str(), gerr->message);
528 g_clear_error (&gerr);
529 }
530 g_markup_parse_context_free (c);
531
532 // populate the servers from the info we loaded...
533 _servers.clear ();
534 foreach_const (key_to_keyvals_t, spc.data, it) {
535 Server& s (_servers[it->first]);
536 keyvals_t kv (it->second);
537 s.host = kv["host"];
538 s.username = kv["username"];
539 #ifndef HAVE_GKR
540 s.password = kv["password"];
541 #else
542 if (!_prefs.get_flag("use-password-storage", false))
543 s.password = kv["password"];
544 #endif
545 s.port = to_int (kv["port"], STD_NNTP_PORT);
546 s.max_connections = to_int (kv["connection-limit"], 2);
547 s.article_expiration_age = to_int(kv["expire-articles-n-days-old"], 31);
548 s.rank = to_int(kv["rank"], 1);
549 int ssl(to_int(kv["use-ssl"], 0));
550 s.ssl_support = ssl;
551 s.cert = kv["cert"];
552 int trust(to_int(kv["trust"], 0));
553 s.trust = trust;
554 s.compression_type = to_int(kv["compression-type"], 0); // NONE
555 s.newsrc_filename = kv["newsrc"];
556 if (s.newsrc_filename.empty()) { // set a default filename
557 std::ostringstream o;
558 o << file::get_pan_home() << G_DIR_SEPARATOR << "newsrc-" << it->first;
559 s.newsrc_filename = o.str ();
560 }
561 }
562
563 }
564
565 namespace
566 {
567 const int indent_char_len (2);
568
indent(int depth)569 std::string indent (int depth) { return std::string(depth*indent_char_len, ' '); }
570
escaped(const std::string & s)571 std::string escaped (const std::string& s)
572 {
573 char * pch = g_markup_escape_text (s.c_str(), s.size());
574 const std::string ret (pch);
575 g_free (pch);
576 return ret;
577 }
578 }
579
580 void
save_server_properties(DataIO & data_io,Prefs & prefs)581 DataImpl :: save_server_properties (DataIO& data_io, Prefs& prefs)
582 {
583 int depth (0);
584 std::ostream * out = data_io.write_server_properties ();
585
586 *out << "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n";
587
588 // sort the servers by id
589 typedef std::set<Quark,AlphabeticalQuarkOrdering> alpha_quarks_t;
590 alpha_quarks_t servers;
591 foreach_const (servers_t, _servers, it)
592 servers.insert (it->first);
593
594 // write the servers to the ostream
595 *out << indent(depth++) << "<server-properties>\n";
596 foreach_const (alpha_quarks_t, servers, it) {
597 const Server* s (find_server (*it));
598 std::string user;
599 gchar* pass(NULL);
600 get_server_auth(*it, user, pass, prefs.get_flag("use-password-storage",false));
601 *out << indent(depth++) << "<server id=\"" << escaped(it->to_string()) << "\">\n";
602 *out << indent(depth) << "<host>" << escaped(s->host) << "</host>\n"
603 << indent(depth) << "<port>" << s->port << "</port>\n"
604 << indent(depth) << "<username>" << escaped(user) << "</username>\n";
605 #ifdef HAVE_GKR
606 if (prefs.get_flag("use-password-storage", false))
607 *out << indent(depth) << "<password>" << "HANDLED_BY_PASSWORD_STORAGE" << "</password>\n";
608 else
609 *out << indent(depth) << "<password>" << escaped(pass) << "</password>\n";
610 #else
611 *out << indent(depth) << "<password>" << escaped(pass) << "</password>\n";
612 #endif
613 *out << indent(depth) << "<expire-articles-n-days-old>" << s->article_expiration_age << "</expire-articles-n-days-old>\n"
614 << indent(depth) << "<connection-limit>" << s->max_connections << "</connection-limit>\n"
615 << indent(depth) << "<newsrc>" << s->newsrc_filename << "</newsrc>\n"
616 << indent(depth) << "<rank>" << s->rank << "</rank>\n"
617 << indent(depth) << "<use-ssl>" << s->ssl_support << "</use-ssl>\n"
618 << indent(depth) << "<trust>" << s->trust << "</trust>\n"
619 << indent(depth) << "<compression-type>" << s->compression_type << "</compression-type>\n"
620 << indent(depth) << "<cert>" << s->cert << "</cert>\n";
621
622 *out << indent(--depth) << "</server>\n";
623 }
624 *out << indent(--depth) << "</server-properties>\n";
625
626 data_io.write_done (out);
627 }
628