TFCweb  1.0.4 $Rev: 483 $
TFC Primavera 2012: Nucli d'un servidor web
Configuracio.cc
Veure la documentació d'aquest fitxer.
1 
8 /*
9  * Copyright (c) 2012 Toni Corvera
10  *
11  * This file is part of TFCWeb.
12  *
13  * TFCWeb is free software: you can redistribute it and/or modify
14  * it under the terms of the GNU General Public License as published by
15  * the Free Software Foundation, either version 3 of the License, or
16  * (at your option) any later version.
17  *
18  * TFCWeb is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with TFCWeb. If not, see <http://www.gnu.org/licenses/>.
25  */
26 
27 #include "Configuracio.h"
28 
29 #include <cassert>
30 #include <iostream>
31 #include <sstream>
32 #include <boost/bind.hpp>
33 #include <boost/function.hpp>
34 #include <boost/program_options.hpp>
35 
36 using namespace std;
37 namespace fs = boost::filesystem;
38 namespace po = boost::program_options;
39 
40 namespace {
41 
42 // Valors per defecte
43 const unsigned short PORT_PER_DEFECTE = 8000;
44 const string ARREL_PER_DEFECTE("."); // fs::path produeix error en Windows ¿¿¿???
45 const char * CONFIG_PER_DEFECTE = "tfcweb.conf";
46 const char * INDEX_PER_DEFECTE = "index.html";
47 const string REALME_PER_DEFECTE(PACKAGE_NAME);
49 enum {
51 };
52 
54  "L'arrel ha de ser un directori existent i llegible";
55 
60 template<typename TipusExcepcio>
61 void valida_arrel(const fs::path & candidata) {
62  if (!fs::exists(candidata) || !fs::is_directory(candidata)) {
63  throw TipusExcepcio(MISSATGE_ERROR_ARREL_INCORRECTA);
64  }
65  if (!tfc::utils::es_llegible(candidata)) {
66  throw TipusExcepcio(MISSATGE_ERROR_ARREL_INCORRECTA);
67  }
68 }
69 
74 typedef vector< std::pair<string, FlagsPermis> > permisos_preparats_t;
89 void prepara_permisos(permisos_preparats_t & permisos_preparats,
90  const vector<string> rutes,
91  FlagsPermis permisos)
92 {
93  // No serveix: Cal afegir la ruta base
94  // boost::bind(&Cerber::defineix_regla, &cerber, _1, permisos)
95  //std::for_each(rutes.begin(), rutes.end(),
96  // boost::bind(&Cerber::defineix_regla, &cerber, ruta_absoluta, permisos));
97  for (auto it=rutes.begin(); it != rutes.end(); ++it) {
98  permisos_preparats.push_back( make_pair(*it, permisos) );
99  }
100 }
101 
103 inline bool config_llegible(const fs::path & cfg) {
104  return fs::exists(cfg) && fs::is_regular_file(cfg) && utils::es_llegible(cfg);
105 }
106 
107 } // ns anònim
108 
109 namespace tfc {
110 
111 Configuracio::Configuracio(int argc, char *argv[]) throw (ErrorOpcions,AjudaDemanada,VersioDemanada)
112  : argc_(argc), argv_(argv),
113  arrel_(ARREL_PER_DEFECTE),
114  index_(INDEX_PER_DEFECTE),
115  port_(PORT_PER_DEFECTE),
116  config_(CONFIG_PER_DEFECTE),
117  auth_user_(), auth_pass_(), auth_realme_(REALME_PER_DEFECTE),
118  cerber_(PERMIS_PER_DEFECTE)
119 {
120  // Contindrà la relació de rutes i permisos configurats
121  // XXX: S'ha optat per fer què qualsevol permís diferent de PRIVAT impliqui el
122  // permís de lectura.
123  permisos_preparats_t permisos_preparats;
124 
125  // TODO: rutes_publiques per permetre re-escriure permisos més restrictius en subdirectoris
126 
127  // Opcions només de línia de comandes
128  po::options_description clidesc("Opcions de línia de comandes"), cfgdesc("Configuració");
129  clidesc.add_options()
130  ("help", "mostra aquest missatge d'ajuda")
131  ("config",
132  po::value<fs::path>(&config_)->default_value(CONFIG_PER_DEFECTE),
133  "fitxer de configuració")
134  ("version", "mostra informació de l'entorn de compilació")
135  ;
136  // Opcions de l'arxiu de configuració (i la línia de comandes)
137  // po::value(...)->composing() permet combinar valors de la línia
138  // de comandes i la configuració. Si no s'especifica, el valor
139  // que es defineix primer (línia de comandes) serà l'utilitzat
140  cfgdesc.add_options()
141  ("port,p",
142  po::value<unsigned short>(&port_)->default_value(PORT_PER_DEFECTE),
143  "especifica el port en què escoltar")
144  ("arrel,r",
145  po::value<fs::path>(&arrel_)->default_value(ARREL_PER_DEFECTE),
146  "directori arrel dels documents del servidor")
147  ("index", // TODO: Utilitzar
148  po::value<string>(&index_)->default_value(INDEX_PER_DEFECTE),
149  "fitxer d'índex per defecte")
150  ("usuari,U",
151  po::value<string>(&auth_user_)->default_value(""),
152  "nom d'usuari per directoris d'accés restringit")
153  ("contrasenya,P",
154  po::value<string>(&auth_pass_)->default_value(""),
155  "contrasenya per directoris d'accés restringit")
156  ("fils,t",
157  po::value<size_t>(&fils_)->default_value(FILS_PER_DEFECTE),
158  "nombre de fils simultanis")
159  ("realme,R",
160  po::value<string>(&auth_realme_)->default_value(REALME_PER_DEFECTE),
161  "realme d'autenticació")
162  ("privat,n",
163  po::value< vector<string> >()
164  ->composing()
165  ->notifier(bind(&prepara_permisos, boost::ref(permisos_preparats), _1, PRIVAT)),
166  "ruta privada (error 403 en accedir-hi); es poden definir múltiples")
167  ("protegit,c",
168  po::value< vector<string> >()
169  ->composing()
170  ->notifier(bind(&prepara_permisos, boost::ref(permisos_preparats), _1, LECTURA|PROTEGIT)),
171  "ruta protegida (requereix usuari/contrasenya); es poden definir múltiples")
172  ("cgi,x",
173  po::value< vector<string> >()
174  ->composing()
175  ->notifier(bind(&prepara_permisos, boost::ref(permisos_preparats), _1, LECTURA|EXECUCIO)),
176  "ruta en què es permet executar CGIs; es poden definir múltiples")
177  ("escribible,e",
178  po::value< vector<string> >()
179  ->composing()
180  ->notifier(bind(&prepara_permisos, boost::ref(permisos_preparats), _1, LECTURA|ESCRIPTURA)),
181  "ruta en què es permeten mètodes d'escriptura (PUT i DELETE); "
182  "es poden definir múltiples")
183  ("llegible,l",
184  po::value< vector<string> >()
185  ->composing()
186  ->notifier(bind(&prepara_permisos, boost::ref(permisos_preparats), _1, LECTURA)),
187  "ruta en què es permet accés de lectura (GET, HEAD, ...); "
188  "permet definir permisos més laxos dintre de rutes restringrides; "
189  "es poden definir múltiples ")
190 
191  // Per composar valors a partir de diferents orígens s'utilitza
192  // value(...)->composing()
193  // http://www.boost.org/doc/libs/1_49_0/doc/html/program_options/tutorial.html#id2500303
194  ;
195  po::options_description desc("Opcions");
196  desc.add(clidesc);
197  desc.add(cfgdesc);
198 
199  // Primer la línia de comandes, ja que els valors sense "composing" es quedaran
200  // amb el primer valor que se'ls assigni.
201  po::variables_map varmap;
202  try {
203  po::store(po::parse_command_line(argc_, argv_, desc), varmap);
204  valida_arrel<ErrorLiniaComandes>(varmap["arrel"].as<fs::path>());
205 
206  // Per a assignar realment els valors passats a les variables associades
207  // cal cridar program_options::notify(), però això també crida els callbacks
208  // associats amb notifier(), per tant és millor fer una única crida a notify.
209  // Sense notificadores pròpies es crida notify() després de cada store()
210  // Per a comprovar el valor d'una opció abans de cridar notify() cal extreure'l
211  // manualment de varmap
212  const fs::path cfg = varmap["config"].as<fs::path>(); // és un boost::any, cal fer un cast
213 
214  if (config_llegible(cfg))
215  {
216  try {
217  // parse_config_file té menys prioritat que parse_command_line, si quelcom
218  // es defineix en la línia de comandes no es sobreescriu des del fitxer de
219  // configuració
220  po::store(po::parse_config_file<char>(cfg.string().c_str(), cfgdesc), varmap);
221  valida_arrel<ErrorConfiguracio>(varmap["arrel"].as<fs::path>());
222  }
223  catch (const po::error & e) {
224  throw ErrorConfiguracio(string("Error de configuració: ") + e.what());
225  }
226  }
227  }
228  catch (const po::error & e) {
229  throw ErrorLiniaComandes(string("Error en la línia de comandes: ") + e.what());
230  }
231 
232  po::notify(varmap); // "promocionem" els valors, cridem els callbacks
233  if (config_llegible(config_)) { // Després de notify()!
234  config_ = fs::canonical(config_);
235  }
236 
237  if (varmap.count("help")) {
238  ostringstream oss;
239  desc.print(oss);
240  throw AjudaDemanada(oss.str());
241  }
242  if (varmap.count("version")) {
243  throw VersioDemanada();
244  }
245  arrel_ = fs::canonical(arrel_);
246 
247  // Sanejament d'índex
248  index_ = fs::path(index_).filename().string();
249 
250  // Permisos per defecte
251  cerber_.defineix_regla(arrel_, LECTURA);
252  cerber_.defineix_regla(arrel_.parent_path(), PRIVAT);
253  cerber_.defineix_regla(Cerber::ruta_t("/"), PRIVAT); // En realitat innecessari
254  // Permisos configurats. XXX: L'ordre de definició no es respecta!
255  // No serveix: Cal afegir la ruta base
256  // boost::bind(&Cerber::defineix_regla, &cerber_, _1, PRIVAT)
257  // Functor: Afegeix ruta arrel a rutes relatives
258  struct CreaRuta {
259  typedef fs::path result_type; // Permet a boost::bind() obtenir el tipus retornat
260  fs::path operator()(const string & s, const fs::path & arrel) {
261  fs::path tmp(s);
262  if (!tmp.has_root_directory()) {
263  tmp = arrel / tmp;
264  }
265  // canonicalitzar?
266  if (fs::exists(tmp)) {
267  tmp = fs::canonical(tmp);
268  }
269  return tmp;
270  }
271  };
272  // En no respectar l'ordre cal utilitzar un callback de pre-processament
273  //std::for_each(rutes_privades.begin(), rutes_privades.end(),
274  // boost::bind(&Cerber::defineix_regla, &cerber_, crea_ruta, PRIVAT));
275 
276  // Processa els permisos preparats, afegint-los al Cèrber:
277  // XXX: GCC 4.4 no és capaç de resoldre la crida a bind
278  // auto ruta_absoluta = boost::bind(CreaRuta(), _1, arrel_);
279  CreaRuta ruta_absoluta;
280  for (auto it=permisos_preparats.begin(); it!=permisos_preparats.end(); ++it) {
281  cerr << "Permís configurat: " << it->first << ": " << nom_permis(it->second) << endl;
282  //cerber_.defineix_regla(ruta_absoluta(it->first), it->second);
283  cerber_.defineix_regla(ruta_absoluta(it->first, arrel_), it->second);
284  }
285 
286  // INVARIANTS
287  assert( fs::exists(arrel_) );
288  assert( fs::is_directory(arrel_) );
289  assert( arrel_ == fs::canonical(arrel_) );
290 }
291 
292 // friend Configuracio
293 ostream& operator<<(ostream & os, const Configuracio & c) {
294  const bool config_existeix = fs::exists(c.config());
295  // INVARIANTS
296  assert( utils::es_canonic(c.arrel()) );
297  assert( !config_existeix || utils::es_canonic(c.config()) );
298 
299  return os << "Configuració {"
300  << "\n\tPort: " << c.port()
301  << "\n\tArrel: " << c.arrel()
302  << "\n\tNombre de fils: " << c.fils()
303  << "\n\tIndex: " << c.index()
304  << "\n\tConfiguració: " << c.config()
305  << " [" << (config_existeix ? "" : "no ") << "existeix]"
306  << "\n\tNom d'usuari: " << c.usuari()
307  << "\n\tContrasenya: " << c.contrasenya()
308  << "\n\tRealme: " << c.realme()
309  << "\n}"
310 #if 0
311  "\n"
312  << c.cerber_
313 #endif
314  ;
315 }
316 
317 } // ns tfc
318 
319 // vim:set ts=4 et ai: //