Documentation

Generated on Thu Aug 31 00:02:28 2006

 

Configuration.cpp

Go to the documentation of this file.
00001 
00002 //  $Id: Configuration.cpp,v 1.101 2005/08/23 19:34:02 klas Exp $
00003 //
00004 //  Usage: Functions and stuff for Configure class, holding and
00005 //           reading config stuff.
00006 //
00007 //  A part of babyftpd, licensed under the GNU GPL, see the file 
00008 //    LICENSE for complete information.                                         
00010 
00011 #define CONFIGURATION_CPP
00012 #define BABY_IO
00013 #include "Configuration.h"
00014 
00015 class Configuration *config;
00016 
00017 Configuration::Configuration()
00018 {
00019   conf_file = "babyftpd.conf";
00020   parsed = false;
00021 
00022   general.connection_limit = INT_MAX; // ain't this a bit high?
00023   general.port = 21; // default port
00024   general.data_port = 20; // default outgoing data port
00025   general.no_daemon = false; // daemon mode or not?
00026   general.os = "UNIX"; // what OS is this?
00027   general.admin_email = ""; // admin email?
00028   general.lifebeat_tick = 300; // run the lifebeat() function every this
00029   char tmp[20];
00030   gethostname(tmp, 20);
00031   general.welcome_msg = "BabyFTPd ";
00032   general.welcome_msg += VERSION;
00033   general.welcome_msg += " Server [";
00034   general.welcome_msg += tmp;
00035   general.welcome_msg += "]";
00036   general.user = 0; // by default when run all as root
00037   general.group = 0;
00038   general.readonly = false;
00039   general.anonymous = A_DISABLE;
00040 #ifdef USE_TLS
00041   general.tls_key = "/etc/babyftpd/tls_key.pem";
00042   general.tls_cert = "/etc/babyftpd/tls_cert.pem";
00043   general.tls_ca = "/etc/babyftpd/tls_ca.pem";
00044   general.tls_crl = "/etc/babyftpd/tls_crl.pem";
00045   general.dh_regenerate = 3600; // seconds between diffie-hellman regenerations
00046   general.verify_client = "/etc/babyftpd/verify_client.pl";
00047 
00048   general_user.encryption = ALLOW;
00049 #endif
00050   logger.log_level = 3; // 3 is probably a sensible default?
00051   logger.xferlog = true; // by default all transfers are logged
00052   logger.xferlog_file = "/var/log/xferlog"; // here
00053   logger.thread_id = false; // we don't want thread ids all over the logs
00054   logger.time_format = "%b %e %H:%M:%S "; // time format
00055 
00056   // these options end up as default for all users, that is,
00057   //  those that don't occur in the conf file get these values
00058   general_user.id = "user_general";
00059   general_user.sim_data = INT_MAX;
00060   general_user.idle = 900; // idle timeout after how long?
00061   general_user.noftp = false; // can this user login with ftp or not?
00062   general_user.only_home = false; // false chroot in homedir?
00063   general_user.only_passive = false;
00064   general_user.user_limit = INT_MAX; // maximum control connections
00065 }
00066 
00067 Configuration::~Configuration()
00068 {
00069 }
00070 
00071 bool Configuration::parse_config_file()
00072 {
00073   vector<string> file_buff;
00074   if(!fill_buff(file_buff, conf_file))
00075     return(false);
00076 
00077   set_structs(get_variables(file_buff, "general", 1), GENERAL, &general);
00078   set_structs(get_variables(file_buff, "general", 2), USER, &general_user);
00079   set_structs(get_variables(file_buff, "log", 1), LOG, &logger);
00080 
00081   string user_names;
00082   num_users = count_users(file_buff, user_names) - 1; // first user is 0.
00083 
00084   class StringTokenizer *names = new StringTokenizer(user_names, ",", false);
00085   names->nextToken(); // not nice, but nicer.
00086 
00087   if(!users.empty()) // that is, on SIGHUP
00088     users.clear();
00089 
00090   users.reserve(num_users + 1);
00091   for(int i = 0; i < num_users; i++)
00092   {
00093     struct users_stru user_tmp = general_user;
00094     set_structs(get_variables(file_buff, names->nextToken().c_str(), 2),
00095         USER, &user_tmp);
00096     users.push_back(user_tmp);
00097   }
00098 
00099   parsed = true;
00100   delete names;
00101   return true;
00102 }
00103 
00104 bool Configuration::set_structs(string variables, enum sect section,
00105     void *this_struct)
00106 {
00107   struct users_stru *usr_tmp = NULL;
00108   struct general_stru *gen_tmp = NULL;
00109   struct log_stru *log_tmp = NULL;
00110   vector<config_entry> this_entry;
00111   string for_log = "Configuration for ";
00112 
00113   switch(section)
00114   {
00115     case USER:
00116       {
00117         usr_tmp = (static_cast<struct users_stru*>(this_struct));
00118 
00119         this_entry.push_back(config_entry(
00120               "id", CONF_STRING, &usr_tmp->id));
00121         this_entry.push_back(config_entry(
00122               "sim_data", CONF_INT, &usr_tmp->sim_data));
00123         this_entry.push_back(config_entry(
00124               "idle", CONF_TIME, &usr_tmp->idle));
00125         this_entry.push_back(config_entry(
00126               "noftp", CONF_BOOL, &usr_tmp->noftp));
00127         this_entry.push_back(config_entry(
00128               "only_home", CONF_BOOL, &usr_tmp->only_home));
00129         this_entry.push_back(config_entry(
00130               "only_passive", CONF_BOOL, &usr_tmp->only_passive));
00131         this_entry.push_back(config_entry(
00132               "user_limit", CONF_INT, &usr_tmp->user_limit));
00133 #ifdef USE_TLS
00134         this_entry.push_back(config_entry(
00135               "encryption", CONF_ENC, &usr_tmp->encryption));
00136 #endif // USE_TLS
00137         for_log += "user section ";
00138       }
00139       break;
00140 
00141     case LOG:
00142 
00143       {
00144         log_tmp = (static_cast<struct log_stru*>(this_struct));
00145 
00146         this_entry.push_back(config_entry(
00147               "log_level", CONF_INT, &log_tmp->log_level));
00148         this_entry.push_back(config_entry(
00149               "xferlog", CONF_BOOL, &log_tmp->xferlog));
00150         this_entry.push_back(config_entry(
00151               "xferlog_file", CONF_LOGF, &log_tmp->xferlog_file));
00152         this_entry.push_back(config_entry(
00153               "authlog", CONF_LOGF, &log_tmp->authlog));
00154         this_entry.push_back(config_entry(
00155               "infolog", CONF_LOGF, &log_tmp->infolog));
00156         this_entry.push_back(config_entry(
00157               "debuglog", CONF_LOGF, &log_tmp->debuglog));
00158         this_entry.push_back(config_entry(
00159               "thread_id", CONF_BOOL, &log_tmp->thread_id));
00160         this_entry.push_back(config_entry(
00161               "time_format", CONF_TIMEF, &log_tmp->time_format));
00162         for_log += "log section ";
00163       }
00164       break;
00165 
00166     case GENERAL:
00167       {
00168         gen_tmp = (static_cast<struct general_stru*>(this_struct));
00169 
00170         this_entry.push_back(config_entry(
00171               "connection_limit", CONF_INT, &gen_tmp->connection_limit));
00172         this_entry.push_back(config_entry(
00173               "port", CONF_INT, &gen_tmp->port));
00174         this_entry.push_back(config_entry(
00175               "data_port", CONF_INT, &gen_tmp->data_port));
00176         this_entry.push_back(config_entry(
00177               "no_daemon", CONF_BOOL, &gen_tmp->no_daemon));
00178         this_entry.push_back(config_entry(
00179               "admin_email", CONF_STRING, &gen_tmp->admin_email));
00180         this_entry.push_back(config_entry(
00181               "welcome_msg", CONF_STRING, &gen_tmp->welcome_msg));
00182         this_entry.push_back(config_entry(
00183               "lifebeat_tick", CONF_TIME, &gen_tmp->lifebeat_tick));
00184         this_entry.push_back(config_entry(
00185               "user", CONF_USER, NULL));
00186         this_entry.push_back(config_entry(
00187               "pid_file", CONF_STRING, &gen_tmp->pid_file));
00188         this_entry.push_back(config_entry(
00189               "readonly", CONF_BOOL, &gen_tmp->readonly));
00190         this_entry.push_back(config_entry(
00191               "anonymous", CONF_ANON, &gen_tmp->anonymous));
00192 #ifdef USE_TLS
00193         this_entry.push_back(config_entry(
00194               "tls_key", CONF_STRING, &gen_tmp->tls_key));
00195         this_entry.push_back(config_entry(
00196               "tls_cert", CONF_STRING, &gen_tmp->tls_cert));
00197         this_entry.push_back(config_entry(
00198               "tls_ca", CONF_STRING, &gen_tmp->tls_ca));
00199         this_entry.push_back(config_entry(
00200               "tls_crl", CONF_STRING, &gen_tmp->tls_crl));
00201         this_entry.push_back(config_entry(
00202               "dh_regenerate", CONF_TIME, &gen_tmp->dh_regenerate));
00203         this_entry.push_back(config_entry(
00204               "verify_client", CONF_STRING, &gen_tmp->verify_client));
00205 #endif // USE_TLS
00206         for_log += "general section ";
00207       }
00208       break;
00209   }
00210   class StringTokenizer *var = new StringTokenizer(variables, "\n", false);
00211   string id = var->nextToken();
00212   if(section == USER)
00213   {
00214     *(string*)this_entry[0].variabel = id;
00215     for_log += "for " + id + " ";
00216   }
00217 
00218   while(var->hasMoreTokens())
00219   {
00220     string log_me, str = var->nextToken();
00221     class StringTokenizer *st = new StringTokenizer(str, " ", false);
00222     string op = st->nextToken();
00223     string arg = st->restTokens();
00224     vector<config_entry>::iterator this_it = this_entry.begin();
00225 
00226     for(; this_it != this_entry.end(); this_it++)
00227       if(op == (*this_it).name)
00228         break;
00229 
00230     if(this_it == this_entry.end())
00231       log_me = "Parameter " + op + " doesn't exist, check your spelling.";
00232     else
00233     {
00234       switch((*this_it).type)
00235       {
00236         case CONF_TIME:
00237         case CONF_ANON:
00238 #ifdef USE_TLS
00239         case CONF_ENC:
00240           if((*this_it).type == CONF_ENC)
00241           {
00242             if(arg == "allow")
00243               arg = util.itos(ALLOW);
00244             else if(arg == "deny")
00245               arg = util.itos(DENY);
00246             else if(arg == "force")
00247               arg = util.itos(FORCE);
00248             else if(arg == "force_cert")
00249               arg = util.itos(FORCE_CERT);
00250             else
00251               log_me = "only allow, force, foce_cert and deny are legal "
00252                 "parameters to encryption, " + arg + " isn't one of them.";
00253           }
00254           else
00255 #endif // USE_TLS
00256             if((*this_it).type == CONF_TIME)
00257             {
00258               int multi = 1;
00259               string::iterator st_it = arg.begin();
00260               for(; st_it != arg.end(); st_it++)
00261               {
00262                 if(isdigit(*st_it) || isspace(*st_it))
00263                   continue;
00264                 else
00265                   break;
00266               }
00267               if(st_it != arg.end())
00268               {
00269                 switch(*st_it)
00270                 {
00271                   case 's':
00272                     multi = 1;
00273                     break;
00274                   case 'm':
00275                     multi = 60;
00276                     break;
00277                   case 'h':
00278                     multi = 3600;
00279                     break;
00280                   case 'd':
00281                     multi = 86400;
00282                     break;
00283                   default:
00284                     log_me = "I don't understand your time unit \'";
00285                     log_me += *st_it;
00286                     log_me += "\' to paramater " + op + " I will ignore it.";
00287                 }
00288                 if(!log_me.empty())
00289                   break;
00290 
00291                 arg.erase(st_it, arg.end());
00292                 int temp_num = atoi(arg.c_str()) * multi;
00293                 arg = util.itos(temp_num);
00294               }
00295             }
00296             else if((*this_it).type == CONF_ANON)
00297             {
00298               if(arg == "enable")
00299                 arg = util.itos(A_ENABLE);
00300               else if(arg == "disable")
00301                 arg = util.itos(A_DISABLE);
00302               else if(arg == "only")
00303                 arg = util.itos(A_ONLY);
00304               else
00305                 log_me = "only enable, disable and only are legal "
00306                   "parameters to anonymous, " + arg + " isn't one of them.";
00307             }
00308         case CONF_INT:
00309           {
00310             int i_value = 0;
00311             bool error = false;
00312             for(string::iterator it = arg.begin(); it != arg.end(); it++)
00313               if(!isdigit(*it))
00314               {
00315                 error = true;
00316                 break;
00317               }
00318             if(!error)
00319               i_value = atoi(arg.c_str());
00320             else
00321               if(log_me.empty())
00322                 log_me = "Non int value supplied to " + op +
00323                   " value supplied was " + arg + ".";
00324 
00325             if(log_me.empty())
00326             {
00327               *(static_cast<int*>((*this_it).variabel)) = i_value;
00328               for_log += (*this_it).name + "=" + util.itos(i_value) + "; ";
00329             }
00330           }
00331           break;
00332 
00333         case CONF_BOOL:
00334           {
00335             bool b_value = true;
00336             if(arg == "true" || arg == "t" || arg == "1" || arg == "y")
00337               b_value = true;
00338             else if(arg == "false" || arg == "f" || arg == "0" || arg == "n")
00339               b_value = false;
00340             else
00341               log_me = arg + " not recognised as a boolean value supplied to "
00342                 + op + ".";
00343 
00344             if(log_me.empty())
00345             {
00346               *(static_cast<bool*>((*this_it).variabel)) = b_value;
00347               for_log += (*this_it).name + (b_value ? "=true; " : "=false; ");
00348             }
00349           }
00350           break;
00351 
00352         case CONF_USER:
00353           {
00354             struct passwd pwd, *pwd_ptr;
00355             struct group grp, *grp_ptr;
00356             char pwdata[PWBUF_SIZE], grpdata[PWBUF_SIZE];
00357             int delim = arg.find_first_of(':');
00358             getpwnam_r(arg.substr(0, delim).c_str(),
00359                 &pwd, pwdata, sizeof(pwdata), &pwd_ptr);
00360             getgrnam_r(arg.substr(delim + 1).c_str(),
00361                 &grp, grpdata, sizeof(grpdata), &grp_ptr);
00362             if(pwd_ptr == NULL)
00363             {
00364               log_me = "Tried to use nonexistent user " + arg.substr(0,
00365                   delim) + " to run the daemon under. using root instead.";
00366               pwd.pw_uid = 0;
00367               grp.gr_gid = 0;
00368             }
00369             else if(grp_ptr == NULL)
00370             {
00371               log_me = "Tried to use nonexistent group " + arg.substr(delim +1)
00372                 + " to run the daemon under. using root instead.";
00373               grp.gr_gid = 0;
00374             }
00375             general.user = pwd.pw_uid;
00376             general.group = grp.gr_gid;
00377             for_log += (*this_it).name + "=" +
00378               util.itos(pwd.pw_uid) + ":" + util.itos(grp.gr_gid) + "; ";
00379           }
00380           break;
00381 
00382         case CONF_LOGF:
00383           if(arg == "syslog")
00384             arg.clear();
00385         case CONF_TIMEF:
00386           if((*this_it).type == CONF_TIMEF)
00387             arg += " ";
00388         case CONF_STRING:
00389           *(static_cast<string*>((*this_it).variabel)) = arg;
00390           for_log += (*this_it).name + "=" + arg + "; ";
00391           break;
00392 
00393         default:
00394           log_me = "Parameter " + op + " doesn't exist, check your spelling.";
00395           break;
00396       }
00397     }
00398     delete st;
00399     if(!log_me.empty())
00400       logging->log_this(2, TYPE_INFO, log_me);
00401   }
00402 
00403   if(this->general.anonymous >= A_ENABLE && this->general.user == 0)
00404   {
00405     this->general.anonymous = A_DISABLE;
00406     logging->log_this(2, TYPE_INFO,
00407         "you enabled anonymous logins but you don't have a configured user."
00408         " anonymous logins disabled.");
00409   }
00410 
00411   logging->log_this(3, TYPE_INFO, for_log);
00412   delete var;
00413   return true;
00414 }
00415 
00416 struct users_stru &Configuration::get_user(string name)
00417 {
00418   for(vector<users_stru>::iterator use_it = users.begin();
00419       use_it != users.end(); use_it++)
00420     if((*use_it).id == name && name != "")
00421       return(*use_it);
00422   return general_user;
00423 }
00424 
00425 int Configuration::count_users(vector<string> buff, string &users)
00426 {
00427   int ant = 0;
00428   int deep = 1, curr_deep = 0;
00429   string name = "user", section = "";
00430   for(vector<string>::iterator buff_it = buff.begin(); buff_it != buff.end();
00431       buff_it++)
00432   {
00433     if(*buff_it == "{")
00434     {
00435       if(curr_deep == deep && section == name)
00436       {
00437         ant++;
00438         users += *(buff_it - 1) + ",";
00439       }
00440       curr_deep++;
00441       if(curr_deep == deep)
00442         section = *(buff_it - 1);
00443     }
00444     else if(*buff_it == "}")
00445     {
00446       if(curr_deep == deep && section == name)
00447         return ant;
00448       curr_deep--;
00449     }
00450   }
00451   return ant;
00452 }
00453 
00454 
00455 string Configuration::get_variables(vector<string> &buff, const char *key,
00456     int deep)
00457 {
00458   int curr_deep = 0;
00459   string section, str;
00460   for(vector<string>::iterator buff_it = buff.begin(); buff_it != buff.end();
00461       buff_it++)
00462   {
00463     if((*buff_it).empty())
00464       continue;
00465     size_t comment = (*buff_it).find_first_of('#');
00466     if(comment != string::npos)
00467       (*buff_it).erase(comment);
00468     if(*buff_it == "{")
00469     {
00470       section = *(buff_it - 1);
00471       curr_deep++;
00472       if(curr_deep == deep)
00473         str = section + "\n";
00474     }
00475     else if(*buff_it == "}")
00476     {
00477       if(curr_deep == deep && section == key)
00478         break;
00479       curr_deep--;
00480     }
00481     else if(curr_deep == deep && section == key)
00482       str += *buff_it + "\n";
00483   }
00484   return(str);
00485 }
00486 
00487 bool Configuration::fill_buff(vector<string> &buff, string filename)
00488 {
00489   ifstream fin;
00490   fin.open(filename.c_str(), ifstream::in);
00491 
00492   while(fin.good())
00493   {
00494     string buffer;
00495     getline(fin, buffer);
00496     util.clean_string(buffer);
00497     if(!fin.good() || buffer.empty())
00498       continue;
00499     buff.push_back(buffer);
00500   }
00501 
00502   fin.close();
00503   if(buff.empty())
00504     return(false);
00505   else
00506     return(true);
00507 }
00508 
00509 
00510 bool Configuration::parse_command_line(int argc, char **argv)
00511 {
00512   int opt = 0;
00513   const char *cmdopts = "hc:nVl:p:P:";
00514 
00515   // struct for the long options... neat, huh?
00516   struct option longopts[] =
00517   {
00518     { "help",      0, 0, 'h' },
00519     { "conf-file", 1, 0, 'c' },
00520     { "no-daemon", 0, 0, 'd' },
00521     { "version",   0, 0, 'V' },
00522     { "log-level", 1, 0, 'l' },
00523     { "port",      1, 0, 'p' },
00524     { "pid_file",  1, 0, 'P' },
00525     { 0,           0, 0, 0   }
00526   };
00527 
00528   while((opt = getopt_long(argc, argv, cmdopts, longopts, NULL)) != -1)
00529   {
00530     switch(opt)
00531     {
00532       case 'h':
00533         cout << "Usage: " << argv[0] << " [OPTION]...\n\n"
00534           << "  -h, --help            display this help and exit\n"
00535           << "  -c, --conf-file=FILE  supply another conf file instead "
00536           << "of the default\n"
00537           << "  -n, --no-daemon       do not fork into the background\n"
00538           << "  -V, --version         display version information and exit\n"
00539           << "  -l, --log-level=LEVEL set the log level, 1-10, default is 5\n"
00540           << "  -p, --port=PORT       specify an alternate port\n"
00541           << "  -P, --pid_file=FILE   a file to write our pid in\n\n"
00542           << "Please, report bugs, either directly to the developers\n"
00543           << "or via sourceforge (http://www.sf.net/projects/babyftpd).\n";
00544 
00545           exit_baby(0, EXIT_EARLY);
00546 
00547       case 'c':
00548         config->conf_file = optarg;
00549         break;
00550 
00551       case 'n':
00552         config->general.no_daemon = true;
00553         break;
00554 
00555       case 'V':
00556         cout << "BabyFTPd version " << VERSION << "\nBuilt on " << __DATE__
00557           << " " << " " << __TIME__ << endl;
00558         exit_baby(0, EXIT_EARLY);
00559 
00560       case 'l':
00561         // Log level code...
00562         if(atoi(optarg) > 10)
00563           logger.log_level = 10;
00564         else if(atoi(optarg) < 0)
00565           logger.log_level = 0;
00566         else
00567           logger.log_level = atoi(optarg);
00568         break;
00569 
00570       case 'p':
00571         // alternate port...
00572         config->general.port = atoi(optarg);
00573         break;
00574 
00575       case 'P':
00576         // pid_file
00577         config->general.pid_file = optarg;
00578     }
00579   }
00580   return true;
00581 }
00582