先決條件
- SFML 2.0 Release Candidate: [ 下載 | 文件 | 教程 ]
- 模板 [ 教程 ]
- std::vector [ 文件 ]
- std::stringstream [ 文件 ]
- C++11 基於範圍的 for 迴圈 [ 維基百科 ]
簡介
埠掃描器是一個程式,它透過逐個埠嘗試連線伺服器來探測伺服器的開放埠。該程式通常會報告哪些埠是開放的,哪些是關閉的。更復雜的埠掃描器,如 Nmap,還可以探測其他資訊。埠掃描通常由系統管理員執行以驗證網路安全,或由攻擊者尋找開放埠以破壞伺服器安全。開放埠可能構成安全漏洞,因為它們允許遠端計算機連線。意外開放的埠可能表明惡意軟體正在監聽指令。
我的第一個埠掃描器
和之前一樣,掃描埠就像嘗試連線到地址和埠一樣簡單。如果連線嘗試成功,則埠必須是開放的。否則,則假定埠是關閉的。下面是一個使用 SFML 網路模組 (
文件) 來檢查埠是否開放的示例函式。
1 2 3 4 5 6 7
|
bool port_is_open(const std::string& address, int port)
{
sf::TcpSocket socket;
bool open = (socket.connect(sf::IpAddress(address), port) == sf::Socket::Done);
socket.disconnect();
return open;
}
|
細分
- 首先,我們建立一個 sf::TcpSocket 例項 (文件),它允許我們連線到遠端套接字。
- 然後,我們連線套接字。透過呼叫 sf::IpAddress 的建構函式,我們將字串 'address' 轉換為 sf::IpAddress (文件) 例項。如果省略了顯式的建構函式呼叫,編譯器也會隱式執行。在嘗試連線後,我們透過將 sf::TcpSocket::connect 函式 (文件) 的返回值與列舉值 sf::Socket::Done (文件) 進行比較來檢查連線是否成功。如果兩者相等,則表示連線成功,埠是開放的。在這種情況下,變數 'open' 被設定為 true。
- 接下來,我們使用 sf::TcpSocket::disconnect 函式 (文件) 斷開套接字連線。如果我們省略了顯式呼叫,解構函式會自動完成此操作。
- 最後,我們將 'open' 的值返回給呼叫函式。
這實際上是我們唯一需要編寫的網路程式碼。我們也可以將其簡化為一行。
1 2 3 4
|
static bool port_is_open(const std::string& address, int port)
{
return (sf::TcpSocket().connect(address, port) == sf::Socket::Done);
}
|
這裡我們建立了一個新的 sf::TcpSocket,連線到地址和埠,然後根據連線是否成功返回 true 或 false。我們去掉了不必要的顯式 sf::IpAddress 建構函式呼叫以及對 sf::TcpSocket::disconnect() 的呼叫。我們可以像這樣在程式中使用該函式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
#include <iostream>
#include <SFML/Network.hpp>
#include <string>
static bool port_is_open(const std::string& address, int port)
{
return (sf::TcpSocket().connect(address, port) == sf::Socket::Done);
}
int main()
{
std::cout << "Port 80 : ";
if (port_is_open("localhost", 80))
std::cout << "OPEN" << std::endl;
else
std::cout << "CLOSED" << std::endl;
return 0;
}
|
Port 80 : OPEN |
嘗試編譯並執行此程式。它將測試您計算機上的 80 埠是否開放。請注意,“localhost”表示本地計算機;您也可以使用 IP 地址 127.0.0.1 或 ::1(IPv6 版本,儘管 SFML 尚不支援 IPv6)來實現相同目的。您可以將“localhost”更改為另一個網站的 IP 地址或 Web 地址(省略“http://”和任何路徑資訊),但請小心 - **未經許可掃描網站可能會在某些國家/地區給您帶來麻煩**,因為它可能被視為駭客行為。幸運的是,埠掃描器 Nmap 的網站有一個專門用於測試埠掃描器的頁面。嘗試將“localhost”更改為“scanme.nmap.org”。只是不要掃描太多次(頁面上說“每天幾次”)。
改進的埠掃描器
既然我們已經成功開發了一個可以掃描地址上埠的程式,我們就可以修改我們的程式,讓使用者指定要掃描的埠和地址。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
|
#include <iostream>
#include <SFML/Network.hpp>
#include <string>
static bool port_is_open(const std::string& address, int port)
{
return (sf::TcpSocket().connect(address, port) == sf::Socket::Done);
}
int main()
{
std::string address;
int port;
// Get the address.
std::cout << "Address: " << std::flush;
std::getline(std::cin, address);
// Get the port.
std::cout << "Port: " << std::flush;
std::cin >> port;
// Scan!
std::cout << "Scanning " << address << "...\n" << "Port " << port << " : ";
if (port_is_open(address, port))
std::cout << "OPEN" << std::endl;
else
std::cout << "CLOSED" << std::endl;
return 0;
}
|
Address: 127.0.0.1
Port: 80
Scanning 127.0.0.1...
Port 80 : OPEN |
請記住,127.0.0.1 等同於 localhost。另外,您的計算機上的 80 埠可能不開放。它只在我這裡開放,因為我執行的是 Apache HTTP 伺服器(80 埠是通常用於 HTTP,即網站的主要埠;另一個常用的 HTTP 埠是 8080)。
此外,我們也可以輕鬆地在 Nmap 的“ScanMe”頁面上測試此程式碼。
Address: scanme.nmap.org
Port: 80
Scanning scanme.nmap.org...
Port 80 : OPEN |
這次可能需要一些時間,因為您不是掃描自己的計算機,而是透過 Internet 連線到另一臺計算機。
埠如雨後春筍
一次掃描一個埠很乏味,我們希望讓使用者掃描很多埠。一種方法是讓使用者輸入任意數量的埠,然後一次性掃描所有這些埠。這樣做的問題是使用者可能想掃描很多埠,而不得不一一輸入。我們也可以讓使用者指定一個埠範圍,例如 0-100,但那樣他們就無法指定該範圍之外的值。我們將做得更好,讓他們同時做到這兩點——在列表中指定範圍和單獨的埠,例如:“80,8080”;一個範圍,例如:“20-80”;或者一個包含範圍的列表:“20-80,8080”。此程式碼由於使用了模板、std::vector、std::stringstream 和 C++11 基於範圍的 for 迴圈而變得相當複雜,因此,如果您跳過了“先決條件”部分且不知道如何使用它們,請回到本文的頂部進行學習。否則,請繼續閱讀。
首先我們需要一個分割字串的函式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
// Splits a string into tokens arround a delimiter (default: space),
// optionally allowing empty tokens.
static std::vector<std::string> split(const std::string& string,
char delimiter = ' ',
bool allow_empty = false)
{
std::vector<std::string> tokens;
std::stringstream sstream(string);
std::string token;
while (std::getline(sstream, token, delimiter)) {
if (allow_empty || token.size() > 0)
tokens.push_back(token);
}
return tokens;
}
|
埠的資料型別將是字串,但我們想要整數,所以我們還需要一個將字串轉換為整數的函式。
1 2 3 4 5 6 7 8
|
// Converts a string to an integer.
static int string_to_int(const std::string& string)
{
std::stringstream sstream(string);
int i;
sstream >> i;
return i;
}
|
如果我們有一個埠範圍,我們將需要一個函式來生成該範圍內的所有值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
// Swaps two values.
template <typename T>
static void swap(T& a, T& b)
{
T c = a;
a = b;
b = c;
}
// Generates a vector containing a range of values.
template <typename T>
static std::vector<T> range(T min, T max)
{
if (min > max)
swap(min, max);
if (min == max)
return std::vector<T>(1, min);
std::vector<T> values;
for (; min <= max; ++min)
values.push_back(min);
return values;
}
|
最後,我們需要一個函式來使用上述函式實際解析埠列表。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|
// Parses a list of ports containing numbers and ranges
static std::vector<int> parse_ports_list(const std::string& list)
{
std::vector<int> ports;
// Split list items.
for (const std::string& token : split(list, ',')) {
// Split ranges.
std::vector<std::string> strrange = split(token, '-');
switch (strrange.size()) {
// Only one value (add to end of 'ports').
case 0: ports.push_back(string_to_int(token)); break;
case 1: ports.push_back(string_to_int(strrange[0])); break;
// Two values (range - add everything in that range).
case 2:
{
int min = string_to_int(strrange[0]),
max = string_to_int(strrange[1]);
for (int port : range(min, max))
ports.push_back(port);
break;
}
default:
break;
}
}
return ports;
}
|
我們的最終程式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
|
#include <iostream>
#include <SFML/Network.hpp>
#include <sstream>
#include <string>
#include <vector>
static bool port_is_open(const std::string& address, int port)
{
return (sf::TcpSocket().connect(address, port) == sf::Socket::Done);
}
static std::vector<std::string> split(const std::string& string,
char delimiter = ' ',
bool allow_empty = false)
{
std::vector<std::string> tokens;
std::stringstream sstream(string);
std::string token;
while (std::getline(sstream, token, delimiter)) {
if (allow_empty || token.size() > 0)
tokens.push_back(token);
}
return tokens;
}
static int string_to_int(const std::string& string)
{
std::stringstream sstream(string);
int i;
sstream >> i;
return i;
}
template <typename T>
static void swap(T& a, T& b)
{
T c = a;
a = b;
b = c;
}
template <typename T>
static std::vector<T> range(T min, T max)
{
if (min > max)
swap(min, max);
if (min == max)
return std::vector<T>(1, min);
std::vector<T> values;
for (; min <= max; ++min)
values.push_back(min);
return values;
}
static std::vector<int> parse_ports_list(const std::string& list)
{
std::vector<int> ports;
for (const std::string& token : split(list, ',')) {
std::vector<std::string> strrange = split(token, '-');
switch (strrange.size()) {
case 0: ports.push_back(string_to_int(token)); break;
case 1: ports.push_back(string_to_int(strrange[0])); break;
case 2:
{
int min = string_to_int(strrange[0]),
max = string_to_int(strrange[1]);
for (int port : range(min, max))
ports.push_back(port);
break;
}
default:
break;
}
}
return ports;
}
int main()
{
std::string address;
std::string port_list;
std::vector<int> ports;
std::cout << "Address: " << std::flush;
std::getline(std::cin, address);
std::cout << "Port: " << std::flush;
std::getline(std::cin, port_list);
ports = parse_ports_list(port_list);
std::cout << "Scanning " << address << "...\n";
for (int port : ports) {
std::cout << "Port " << port << " : ";
if (port_is_open(address, port))
std::cout << "OPEN\n";
else
std::cout << "CLOSED\n";
}
std::cout << std::flush;
return 0;
}
|
Address: 127.0.0.1
Port: 20-80,8080
Scanning 127.0.0.1...
Port 20 : CLOSED
Port 21 : CLOSED
Port 22 : OPEN
Port 23 : CLOSED
Port 24 : CLOSED
Port 25 : CLOSED
Port 26 : CLOSED
Port 27 : CLOSED
Port 28 : CLOSED
Port 29 : CLOSED
Port 30 : CLOSED
Port 31 : CLOSED
Port 32 : CLOSED
Port 33 : CLOSED
Port 34 : CLOSED
Port 35 : CLOSED
Port 36 : CLOSED
Port 37 : CLOSED
Port 38 : CLOSED
Port 39 : CLOSED
Port 40 : CLOSED
Port 41 : CLOSED
Port 42 : CLOSED
Port 43 : CLOSED
Port 44 : CLOSED
Port 45 : CLOSED
Port 46 : CLOSED
Port 47 : CLOSED
Port 48 : CLOSED
Port 49 : CLOSED
Port 50 : CLOSED
Port 51 : CLOSED
Port 52 : CLOSED
Port 53 : OPEN
Port 54 : CLOSED
Port 55 : CLOSED
Port 56 : CLOSED
Port 57 : CLOSED
Port 58 : CLOSED
Port 59 : CLOSED
Port 60 : CLOSED
Port 61 : CLOSED
Port 62 : CLOSED
Port 63 : CLOSED
Port 64 : CLOSED
Port 65 : CLOSED
Port 66 : CLOSED
Port 67 : CLOSED
Port 68 : CLOSED
Port 69 : CLOSED
Port 70 : CLOSED
Port 71 : CLOSED
Port 72 : CLOSED
Port 73 : CLOSED
Port 74 : CLOSED
Port 75 : CLOSED
Port 76 : CLOSED
Port 77 : CLOSED
Port 78 : CLOSED
Port 79 : CLOSED
Port 80 : OPEN
Port 8080 : CLOSED |
就這樣!我們現在有了一個成功的埠掃描器,它可以掃描使用者所需的任意數量的埠。
再掃描幾個埠
我們可以透過更改以下程式碼來修改我們的程式,使其不顯示關閉的埠:
89 90 91 92 93 94 95 96
|
std::cout << "Scanning " << address << "...\n";
for (int port : ports) {
std::cout << "Port " << port << " : ";
if (port_is_open(address, port))
std::cout << "OPEN\n";
else
std::cout << "CLOSED\n";
}
|
為
89 90 91 92 93
|
std::cout << "Showing open ports on " << address << "...\n";
for (int port : ports) {
if (port_is_open(address, port))
std::cout << "Port " << port << " : OPEN\n";
}
|
這使得輸出更加清晰。掃描我計算機上的所有埠。
Address: localhost
Port: 0-65535
Showing open ports on localhost...
Port 22 : OPEN
Port 53 : OPEN
Port 80 : OPEN
Port 139 : OPEN
Port 445 : OPEN
Port 631 : OPEN
Port 3306 : OPEN
Port 17500 : OPEN |
請注意,共有 65535 個埠。如果我們使用先前版本的埠掃描器掃描所有埠,它將生成 65,535 行輸出,這將使得獲取所需資訊非常困難。
我們可以透過在檔案頂部新增
#include <iomanip>
,在主函式中的 for 迴圈之前新增
size_t width = digits(maximum(ports));
,然後更改顯示埠狀態的行以
std::cout << "Port " << std::setw(width) << port << " : OPEN\n";
來漂亮地格式化我們的輸出。您還需要在 main 函式之前新增這兩個函式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
|
// Gets the maximum value in a vector.
template <typename T>
static T maximum(const std::vector<T>& values)
{
T max = values[0];
for (T value : values) {
if (value > max)
max = value;
}
return max;
}
// Counts the digits in a number.
template <typename T>
static size_t digits(T value)
{
size_t count = (value < 0) ? 1 : 0;
if (value == 0)
return 0;
while (value) {
value /= 10;
++count;
};
return count;
}
|
現在輸出將得到很好的格式化。
Address: localhost
Port: 0-65535
Showing open ports on localhost...
Port 22 : OPEN
Port 53 : OPEN
Port 80 : OPEN
Port 139 : OPEN
Port 445 : OPEN
Port 631 : OPEN
Port 3306 : OPEN
Port 17500 : OPEN
Port 35723 : OPEN
Port 43351 : OPEN |
這是我們改進的埠掃描器的程式碼。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
|
#include <cstdlib>
#include <iomanip>
#include <iostream>
#include <SFML/Network.hpp>
#include <sstream>
#include <string>
#include <vector>
static bool port_is_open(const std::string& address, int port)
{
return (sf::SocketTCP().connect(address, port) == sf::Socket::Done);
}
static std::vector<std::string> split(const std::string& string,
char delimiter = ' ',
bool allow_empty = false)
{
std::vector<std::string> tokens;
std::stringstream sstream(string);
std::string token;
while (std::getline(sstream, token, delimiter)) {
if (allow_empty || token.size() > 0)
tokens.push_back(token);
}
return tokens;
}
static int string_to_int(const std::string& string)
{
std::stringstream sstream(string);
int i;
sstream >> i;
return i;
}
template <typename T>
static void swap(T& a, T& b)
{
T c = a;
a = b;
b = c;
}
template <typename T>
static std::vector<T> range(T min, T max)
{
if (min > max)
swap(min, max);
if (min == max)
return std::vector<T>(1, min);
std::vector<T> values;
for (; min <= max; ++min)
values.push_back(min);
return values;
}
static std::vector<int> parse_ports_list(const std::string& list)
{
std::vector<int> ports;
for (const std::string& token : split(list, ',')) {
std::vector<std::string> strrange = split(token, '-');
switch (strrange.size()) {
case 0: ports.push_back(string_to_int(token)); break;
case 1: ports.push_back(string_to_int(strrange[0])); break;
case 2:
{
int min = string_to_int(strrange[0]),
max = string_to_int(strrange[1]);
for (int port : range(min, max))
ports.push_back(port);
break;
}
default:
break;
}
}
return ports;
}
template <typename T>
static T maximum(const std::vector<T>& values)
{
T max = values[0];
for (T value : values) {
if (value > max)
max = value;
}
return max;
}
template <typename T>
static size_t digits(T value)
{
size_t count = (value < 0) ? 1 : 0;
if (value == 0)
return 0;
while (value) {
value /= 10;
++count;
};
return count;
}
int main()
{
std::string address;
std::string port_list;
std::vector<int> ports;
std::cout << "Address: " << std::flush;
std::getline(std::cin, address);
std::cout << "Port: " << std::flush;
std::getline(std::cin, port_list);
ports = parse_ports_list(port_list);
std::cout << "Showing open ports on " << address << "...\n";
size_t width = digits(maximum(ports));
for (int port : ports) {
if (port_is_open(address, port))
std::cout << "Port " << std::setw(width) << port << " : OPEN\n";
}
std::cout << std::flush;
return 0;
}
|
您可能希望能夠輕鬆地在指令碼中使用該程式,並從其他程式輕鬆執行它,在這種情況下,您可以修改它以使用命令列來獲取地址和埠資訊。下面是一個修改後的 main 函式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
int main(int argc, char* argv[])
{
if (argc != 3) {
std::cerr << "Usage: " << argv[0] << " address port(s)\n"
<< "Examples:\n"
<< "\t" << argv[0] << " 127.0.0.1 80\n"
<< "\t" << argv[0] << " localhost 80,8080\n"
<< "\t" << argv[0] << " 192.0.43.10 0-65535\n"
<< "\t" << argv[0] << " example.com 0-21,80,8080"
<< std::endl;
std::exit(EXIT_FAILURE);
}
std::string address = argv[1];
std::vector<int> ports = parse_ports_list(std::string(argv[2]));
std::cout << "Showing open ports on " << address << "...\n";
size_t width = digits(maximum(ports));
for (int port : ports) {
if (port_is_open(address, port))
std::cout << "Port " << std::setw(width) << port << " : OPEN\";
}
std::cout << std::endl;
return 0;
}
|
下面是一個允許兩者同時使用的版本。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
int main(int argc, char* argv[])
{
std::string address;
std::vector<int> ports;
if (argc == 3) {
address = argv[1];
ports = parse_ports_list(std::string(argv[2]));
} else {
std::string port_list;
std::cout << "Address: " << std::flush;
std::getline(std::cin, address);
std::cout << "Port: " << std::flush;
std::getline(std::cin, port_list);
ports = parse_ports_list(port_list);
}
std::cout << "Showing open ports on " << address << "...\n";
size_t width = digits(maximum(ports));
for (int port : ports) {
if (port_is_open(address, port))
std::cout << "Port " << std::setw(width) << port << " : OPEN\n";
}
std::cout << std::flush;
return 0;
}
|
這是埠掃描器的最終(目前)版本,它允許命令列和互動式呼叫。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
|
#include <cstdlib>
#include <iomanip>
#include <iostream>
#include <SFML/Network.hpp>
#include <sstream>
#include <string>
#include <vector>
static bool port_is_open(const std::string& address, int port)
{
return (sf::SocketTCP().connect(address, port) == sf::Socket::Done);
}
static std::vector<std::string> split(const std::string& string,
char delimiter = ' ',
bool allow_empty = false)
{
std::vector<std::string> tokens;
std::stringstream sstream(string);
std::string token;
while (std::getline(sstream, token, delimiter)) {
if (allow_empty || token.size() > 0)
tokens.push_back(token);
}
return tokens;
}
static int string_to_int(const std::string& string)
{
std::stringstream sstream(string);
int i;
sstream >> i;
return i;
}
template <typename T>
static void swap(T& a, T& b)
{
T c = a;
a = b;
b = c;
}
template <typename T>
static std::vector<T> range(T min, T max)
{
if (min > max)
swap(min, max);
if (min == max)
return std::vector<T>(1, min);
std::vector<T> values;
for (; min <= max; ++min)
values.push_back(min);
return values;
}
static std::vector<int> parse_ports_list(const std::string& list)
{
std::vector<int> ports;
for (const std::string& token : split(list, ',')) {
std::vector<std::string> strrange = split(token, '-');
switch (strrange.size()) {
case 0: ports.push_back(string_to_int(token)); break;
case 1: ports.push_back(string_to_int(strrange[0])); break;
case 2:
{
int min = string_to_int(strrange[0]),
max = string_to_int(strrange[1]);
for (int port : range(min, max))
ports.push_back(port);
break;
}
default:
break;
}
}
return ports;
}
template <typename T>
static T maximum(const std::vector<T>& values)
{
T max = values[0];
for (T value : values) {
if (value > max)
max = value;
}
return max;
}
template <typename T>
static size_t digits(T value)
{
size_t count = (value < 0) ? 1 : 0;
if (value == 0)
return 0;
while (value) {
value /= 10;
++count;
};
return count;
}
int main(int argc, char* argv[])
{
std::string address;
std::vector<int> ports;
if (argc == 3) {
address = argv[1];
ports = parse_ports_list(std::string(argv[2]));
} else {
std::string port_list;
std::cout << "Address: " << std::flush;
std::getline(std::cin, address);
std::cout << "Port: " << std::flush;
std::getline(std::cin, port_list);
ports = parse_ports_list(port_list);
}
std::cout << "Showing open ports on " << address << "...\n";
size_t width = digits(maximum(ports));
for (int port : ports) {
if (port_is_open(address, port))
std::cout << "Port " << std::setw(width) << port << " : OPEN\n";
}
std::cout << std::flush;
return 0;
}
|
隨意出於任何目的使用或修改以上所有程式碼(免責宣告:對於使用我的程式碼執行的任何惡意行為,我概不負責)。
您可以找到我編寫的原始程式的原始碼,該程式在編寫本文時演變成了上述程式碼。該程式是在 FreeBSD 許可下分發的。
待辦事項
附件:[cppscan.cpp] [cppscan.h] [main.cpp]