You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

699 lines
14 KiB

/*
* main.cpp
*
* The MIT License (MIT) {{{
*
* Copyright (c) 2015 Dominic Reich
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE. }}}
*
*/
/*
* SOME SPECIAL NOTES FOR THIS PROGRAM
* 1. When compiling, use -lboost_system in 'link-libraries'
* 2. When compiling for other hosts, use -static in 'linker-options'
* (doesn't work always)
*/
#include <iostream>
#include <fstream>
#include <unistd.h> // get the current user-id
//#include <boost/system/error_code.hpp> // ip-address check
#include <boost/asio/ip/address.hpp> // ip-address check
#include <vector>
//#include <algorithm> // sorting the vector
#include <boost/range/algorithm/sort.hpp> // sort in vector
#include <boost/range/algorithm/unique.hpp> // get duplicates in vector
#include <boost/range/algorithm_ext/erase.hpp> // remove duplicates in vectora
#include <boost/algorithm/string/predicate.hpp> // find ip in file (string compare)
/** \brief Some definitions
* \param DEFAULT_FILENAME Sets the default filename for the blacklist file
* \param IPTABLES iptables' default location
*
* \info We might change this later so we can include those defines
* \info at compile time like:
* \info ./configure --iptables=[new-location] --default-file=[new-location]
*
*/
#define STATUS "RCx [nod.oe7drt.com]"
#define COPYRIGHT "Copyright (C)2015-2023 Dominic Reich"
#ifndef VERSION
#define VERSION "0.0-noversion (check Makefile)"
#endif
#define DEFAULT_FILENAME "/etc/blacklist-ip"
#define TMP_FILENAME "/tmp/blacklist"
#define IPTABLES "/sbin/iptables"
using namespace std;
inline bool checkRoot();
inline bool fileExists(const string &name);
inline bool checkIptables();
void printHelp(bool printAll);
////inline bool checkBlacklistFile(const char *filename);
//const char *setFilename(char *tmpName);
inline bool listFile(const char *filename);
inline bool checkFile(const char *filename);
//inline bool compare(string a, string b);
int loadIptables(const char *filename);
int flushIptables();
int listIptables();
int addIpToIptables(string ip);
int countIptablesAdresses();
int checkIp(string ip);
inline bool addIpToFile(const char *filename, string ip);
inline bool removeIpFromFile(const char *filename, string ip);
inline bool countIpAdresses(const char *filename);
inline bool findIpInFile(const char *filename, string ip);
/** \brief main function (startup function)
*
* \param argc int
* \param argv char**
* \return int
*
*/
int main(int argc, char **argv)
{
if(checkIptables() == false)
return 1;
//if(argc < 2 || argc > 3)
if(argc < 2)
{
printHelp(false);
return 0;
}
string Choice = argv[1];
int cmd = 0;
const char *Filename = DEFAULT_FILENAME;
/// add ip-address to file (check for root)
if(Choice == "-a")
{
if(checkRoot() == false)
return 1;
if(argc > 3)
{
// many args, iterate over them
for(int i = 2; i < argc; i++)
{
// cout << i << ": " << argv[i] << endl;
cmd = addIpToFile(Filename, argv[i]);
if(cmd == false)
continue;
}
return 0;
}
if(argc != 3)
return 1;
cmd = addIpToFile(Filename, argv[2]);
if(cmd == false)
return 1;
/// remove ip-address from file (check for root)
} else if(Choice == "-d")
{
if(checkRoot() == false)
return 1;
if(argc != 3)
return 1;
cmd = removeIpFromFile(Filename, argv[2]);
if(cmd == false)
return 1;
/// list ip-addresses from file on screen (no root required)
} else if(Choice == "-l")
{
if(!listFile(Filename))
return 1;
/// find an ip-address in file
} else if(Choice == "-f")
{
if(argc != 3)
{
printHelp(false);
return 1;
}
cmd = checkIp(argv[2]);
if(cmd != 0)
return 1;
if(!findIpInFile(Filename, argv[2]))
return 1;
/// show how many ips are stored in file
} else if(Choice == "-C")
{
if(!countIpAdresses(Filename))
return 1;
/// show count of ips in iptables
} else if(Choice == "-CL")
{
if(checkRoot() == false)
return 1;
if(!countIptablesAdresses())
return 1;
/// sort and check file for duplicates (check for root)
} else if(Choice == "-c")
{
if(checkRoot() == false)
return 1;
if(!checkFile(Filename))
return 1;
/// reload iptables chain BLACKLIST (check for root)
/// (check file, flush iptables chain, re-load ips from file to chain)
} else if(Choice == "-r")
{
if(checkRoot() == false)
return 1;
// flush chain
cmd = flushIptables();
if(cmd != 0)
return 1;
// check file
if(!checkFile(Filename))
return 1;
// re-load ips
cmd = loadIptables(Filename);
if(cmd != 0)
return 1;
/// print iptables content to screen (check for root, iptables needs root)
} else if(Choice == "-L")
{
if(checkRoot() == false)
return 1;
cmd = listIptables();
if(cmd != 0)
return 1;
/// flush iptables (check for root, iptables needs root)
} else if(Choice == "-F")
{
if(checkRoot() == false)
return 1;
cmd = flushIptables();
if(cmd != 0)
return 1;
/// print help screen (no root required)
} else if(Choice == "-h")
{
printHelp(true);
} else
{
printHelp(false);
}
/// exit program with exit code 0
return 0;
}
/** \brief checks for superuser
*
* \return bool
*
*/
inline bool checkRoot()
{
if(getuid() != 0)
{
cout << "you must be root for this operation" << endl;
return false;
}
return true;
}
/** \brief checks for iptables installation
*
* \return bool
*
*/
inline bool checkIptables()
{
if(!fileExists(IPTABLES))
{
cout << "could not find " << IPTABLES << endl;
return false;
}
return true;
}
/** \brief checks if a file exists
* Source: http://stackoverflow.com/a/12774387
*
* \param name const string&
* \return bool
*
*/
inline bool fileExists(const string &name) {
struct stat buffer;
return (stat (name.c_str(), &buffer) == 0);
}
/** \brief prints a help text on screen (short or long)
*
* \param printAll bool
* \return void
*
*/
void printHelp(bool printAll)
{
cout << "blacklist " << VERSION << ", ";
cout << STATUS << endl;
cout << COPYRIGHT << endl;
2 years ago
cout << "Usage: blacklist [options] <ip-address>" << endl;
if(printAll == true)
{
2 years ago
cout << endl;
cout << "The blacklist file is located at: " << DEFAULT_FILENAME << endl << endl;
cout << "Options: -a add ip-address(es) (to file)" << endl;
cout << " -d delete ip-address (from file) (still only 1!)" << endl;
cout << " -l list ip-addresses (from file)" << endl;
cout << " -f find ip-address in file" << endl;
cout << " -c check file (sort and remove dulicates)" << endl;
cout << " -r reload (check file, flush iptables, load ips from file)" << endl;
cout << " -C count ip-addresses (from file)" << endl;
cout << " -CL count ip-addresses (from iptables)" << endl;
cout << " -L list ip-addresses (from iptables)" << endl;
2 years ago
cout << " -F flush (iptables)" << endl;
}
}
/** \brief checks file for duplicates and sorts it
*
* \param filename const char*
* \return bool
*
*/
inline bool checkFile(const char *filename)
{
fstream File;
File.open(filename, ios::in);
if(!File.is_open())
{
return false;
}
string line = "";
vector<string> ips;
while(getline(File, line))
{
ips.push_back(line.c_str());
}
boost::erase(ips, boost::unique<boost::return_found_end>(boost::sort(ips)));
File.close();
File.open(filename, ios::out | ios::trunc);
if(!File.is_open())
{
return false;
}
for(size_t i = 0; i < ips.size(); i++)
{
File << ips[i] << endl;
}
File.close();
return true;
}
/** \brief prints content of file to screen
*
* \param filename const char*
* \return bool
*
*/
inline bool listFile(const char *filename)
{
ifstream inFile(filename);
if(!inFile.is_open())
{
//cout << "could not open file " << filename << endl;
return false;
}
string line = "";
while(getline(inFile, line))
{
cout << line << endl;
}
inFile.close();
return true;
}
/** \brief sets iptables chain BLACKLIST up with ips from file
*
* \param filename const char*
* \return int
*
*/
int loadIptables(const char *filename)
{
ifstream File(filename);
if(!File.is_open())
{
//cout << "could not open file " << filename << endl;
return 1;
}
int cmd = 0;
string line = "";
while(getline(File, line))
{
cmd = addIpToIptables(line);
if(cmd != 0)
{
return cmd;
}
}
File.close();
return 0;
}
/** \brief flushes iptables chain BLACKLIST
*
* \return int
*
*/
int flushIptables()
{
string cmd_iptables = IPTABLES;
int cmd = 0;
cmd = system((cmd_iptables+" -F BLACKLIST").c_str());
return cmd;
}
/** \brief prints iptables content to screen
*
* \return int
*
*/
int listIptables()
{
string cmd_iptables = IPTABLES;
int cmd = 0;
cmd = system((cmd_iptables+" -L BLACKLIST -vn").c_str());
return cmd;
}
/** \brief adds ip to iptables chain BLACKLIST
*
* \param ip string
* \return int
*
*/
int addIpToIptables(string ip)
{
string cmd_iptables = IPTABLES;
int cmd = 0;
cmd = checkIp(ip);
if(cmd != 0)
{
return cmd;
}
// for now we don't add a destination rule as we normally won't send
// something if the host is unable to talk to us
//cmd = system((cmd_iptables+" -A BLACKLIST -d "+ip+"/32 -j DROP").c_str());
//if(cmd != 0)
//{
// return cmd;
//}
// original line
//cmd = system((cmd_iptables+" -A BLACKLIST -s "+ip+"/32 -j DROP").c_str());
cmd = system((cmd_iptables+" -A BLACKLIST -s "+ip+" -j DROP").c_str());
if(cmd != 0)
{
return cmd;
}
return 0;
}
/** \brief counts the content of BLACKLIST chain
*
* \param /nothing
* \return int number of ips
*
*/
// iptables -nL BLACKLIST|tail -n +3|wc -l
int countIptablesAdresses()
{
string cmd_iptables = IPTABLES;
int cmd = 0;
cmd = system((cmd_iptables+" -nL BLACKLIST | tail -n +3 | wc -l").c_str());
return cmd;
}
/** \brief checks for a valid ip-address
*
* \param ip string
* \return int
*
*/
int checkIp(string ip)
{
boost::system::error_code ec;
boost::asio::ip::address::from_string(ip, ec);
if(ec)
{
cerr << ec.message() << ": " << ip;
return ec.value();
}
return 0;
}
/** \brief adds ip-address to file
*
* \param filename const char*
* \param ip string
* \return bool
*
*/
inline bool addIpToFile(const char *filename, string ip)
{
int cmd = 0;
cmd = checkIp(ip);
if(cmd != 0)
{
return false;
}
ofstream File(filename, ios::app);
if(!File.is_open())
{
return false;
}
File << ip << endl;
File.close();
// disable auto-check for now
checkFile(filename);
return true;
}
/** \brief removes ip-address from file
*
* \param filename const char*
* \param ip string
* \return bool
*
*/
inline bool removeIpFromFile(const char *filename, string ip)
{
int cmd = 0;
cmd = checkIp(ip);
if(cmd != 0)
{
// input is not an IP
return false;
}
if(!findIpInFile(filename, ip))
{
// IP not found in blacklist file
return false;
}
const char *tmpFile = TMP_FILENAME;
ifstream inFile(filename);
if(!inFile.is_open())
{
// can't open file for reading
return false;
}
ofstream outFile(tmpFile, ios::out);
if(!outFile.is_open())
{
// can't open tmp file for writing
return false;
}
string line = "";
while(getline(inFile, line))
{
if(boost::iequals(ip, line) == false)
{
outFile << line << endl;
}
}
inFile.close();
outFile.close();
ifstream a(tmpFile);
if(!a.is_open())
{
// now we can't open previosly created tmp file oO`
return false;
}
ofstream b(filename, ios::out);
if(!b.is_open())
{
// can't open blacklist file for ordered/checked write
return false;
}
b << a.rdbuf();
a.close();
b.close();
remove(tmpFile);
// TODO: Read file, load every line into an array
// search ip in that array and remove that array element
// write array elements into file (without appending, just create new one)
return true;
}
/** \brief Counts ip-addresses in file
*
* \param filename const char*
* \return bool
*
*/
inline bool countIpAdresses(const char *filename)
{
int Count = 0;
ifstream File(filename);
if(!File.is_open())
{
//cout << "could not open file " << filename << endl;
return false;
}
string line = "";
while(getline(File, line))
{
Count++;
}
File.close();
//cout << "There are currently " << Count << "IP Adresses blacklisted." << endl;
// as when we want to use that number to calculate something, only print the count
cout << Count << endl;
return true;
}
/** \brief Finds an ip-address in file
*
* \param filename const char*
* \param ip string
* \return bool
*
*/
inline bool findIpInFile(const char *filename, string ip)
{
ifstream File(filename);
if(!File.is_open())
{
cout << "error while opening file" << endl;
return false;
}
string line = "";
int iFound = 0;
while(getline(File, line))
{
if(boost::iequals(ip, line) == true)
{
//cout << ip << " found in " << filename << endl;
cerr << "Found: " << ip << endl;
iFound = 1;
return true;
}
}
if(iFound == 0)
{
cerr << "Not found: " << ip << endl;
//cout << ip << " not found in " << filename << endl;
}
File.close();
return false;
}
/* vim: set ts=2 sw=2 tw=0 et :*/