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