* new class for IP utils
* method to check telegram support out of config * getters in Config.cpp * lots of reformatings and cdoc
This commit is contained in:
parent
b133ab6961
commit
ba4af36df0
@ -146,7 +146,8 @@ add_library(logger ${LIB_METHOD}
|
|||||||
SET(SOURCE
|
SET(SOURCE
|
||||||
src/main.cpp
|
src/main.cpp
|
||||||
src/IPRefresher.cpp
|
src/IPRefresher.cpp
|
||||||
src/Config.cpp)
|
src/Config.cpp
|
||||||
|
src/IpHelper.cpp)
|
||||||
|
|
||||||
add_executable(iprefresher ${SOURCE})
|
add_executable(iprefresher ${SOURCE})
|
||||||
|
|
||||||
|
69
inc/Config.h
69
inc/Config.h
@ -6,22 +6,17 @@
|
|||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A static class to manage the configuration file, read/write parameters to it.
|
||||||
|
*/
|
||||||
class Config {
|
class Config {
|
||||||
public:
|
public:
|
||||||
static std::string dynuapikey;
|
|
||||||
|
|
||||||
static std::string domainid; //id of the dynu domain
|
|
||||||
static std::string domainname;
|
|
||||||
|
|
||||||
static std::string telegramApiKey;
|
|
||||||
static std::string chatId;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* read configuration out of config file
|
* read configuration out of config file
|
||||||
*
|
*
|
||||||
* @return success of config read
|
* @return success of config read
|
||||||
*/
|
*/
|
||||||
static bool readCredentials();
|
static bool readConfig();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* validate config file
|
* validate config file
|
||||||
@ -30,5 +25,61 @@ public:
|
|||||||
*/
|
*/
|
||||||
static bool validateConfig();
|
static bool validateConfig();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* check if telegram credentials in config are set
|
||||||
|
* @return is supported?
|
||||||
|
*/
|
||||||
|
static bool isTelegramSupported();
|
||||||
|
|
||||||
|
/** Getters **/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* encapsulated getter for DynuApiKey
|
||||||
|
* @return api key
|
||||||
|
*/
|
||||||
|
static const std::string &getDynuapikey();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* encapsulated getter for DomainId
|
||||||
|
* @return DomainId
|
||||||
|
*/
|
||||||
|
static const std::string &getDomainid();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* encapsulated getter for Domainname
|
||||||
|
* @return Domainname
|
||||||
|
*/
|
||||||
|
static const std::string &getDomainname();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* encapsulated getter for TelegramApiKey
|
||||||
|
* @return TelegramApiKey
|
||||||
|
*/
|
||||||
|
static const std::string &getTelegramApiKey();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* encapsulated getter for ChatId
|
||||||
|
* @return ChatId
|
||||||
|
*/
|
||||||
|
static const std::string &getChatId();
|
||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
/**
|
||||||
|
* private constructor --> don't allow instance of this class
|
||||||
|
*/
|
||||||
|
Config();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* helper variable for managing telegram Support
|
||||||
|
*/
|
||||||
|
static bool telegramSupport;
|
||||||
|
|
||||||
|
static std::string dynuapikey;
|
||||||
|
|
||||||
|
static std::string domainid; //id of the dynu domain
|
||||||
|
static std::string domainname;
|
||||||
|
|
||||||
|
static std::string telegramApiKey;
|
||||||
|
static std::string chatId;
|
||||||
};
|
};
|
@ -14,7 +14,7 @@ public:
|
|||||||
/**
|
/**
|
||||||
* default constructor
|
* default constructor
|
||||||
*/
|
*/
|
||||||
IPRefresher();
|
IPRefresher() = default;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* start the service in loop mode
|
* start the service in loop mode
|
||||||
|
22
inc/IpHelper.h
Normal file
22
inc/IpHelper.h
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
//
|
||||||
|
// Created by lukas on 07.05.20.
|
||||||
|
//
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* General helper class for IP actions
|
||||||
|
*/
|
||||||
|
class IpHelper {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* check if ip is valid
|
||||||
|
* @param ip ip address to test
|
||||||
|
* @return validity
|
||||||
|
*/
|
||||||
|
static bool isIpValid(std::string ip);
|
||||||
|
|
||||||
|
private:
|
||||||
|
};
|
@ -25,7 +25,10 @@ public:
|
|||||||
* @param headers header fields
|
* @param headers header fields
|
||||||
* @return return string of server
|
* @return return string of server
|
||||||
*/
|
*/
|
||||||
std::string request(std::string myurl, bool post, Hashmap<std::string, std::string> &map, std::vector<std::string> &headers);
|
std::string request(std::string myurl,
|
||||||
|
bool post,
|
||||||
|
Hashmap<std::string, std::string> &map,
|
||||||
|
std::vector<std::string> &headers);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static size_t write_data(void *buffer, size_t size, size_t buffersize, FILE *stream);
|
static size_t write_data(void *buffer, size_t size, size_t buffersize, FILE *stream);
|
||||||
|
@ -6,14 +6,14 @@
|
|||||||
|
|
||||||
#include "API.h"
|
#include "API.h"
|
||||||
|
|
||||||
class DynuAPI : API{
|
class DynuAPI : API {
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
* refresh the ip of domain on Dynu server
|
* refresh the ip of domain on Dynu server
|
||||||
* @param ip new ip
|
* @param ip new ip
|
||||||
* @return request status
|
* @return request status
|
||||||
*/
|
*/
|
||||||
int refreshIp(std::string ip);
|
bool refreshIp(std::string ip);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* init Telegram api with apikey and chatid
|
* init Telegram api with apikey and chatid
|
||||||
@ -21,7 +21,8 @@ public:
|
|||||||
* @param domainId ID of domain received by Dynu
|
* @param domainId ID of domain received by Dynu
|
||||||
* @param domainName domainname to refresh
|
* @param domainName domainname to refresh
|
||||||
*/
|
*/
|
||||||
void init(const std::string& dynuApiKey, const std::string& domainId, const std::string& domainName);
|
void init(const std::string &dynuApiKey, const std::string &domainId, const std::string &domainName);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string dynuapikey; // Dynu API key
|
std::string dynuapikey; // Dynu API key
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
class IPAPI : API{
|
class IPAPI : API {
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
* get global ip of current internet connection
|
* get global ip of current internet connection
|
||||||
|
@ -14,7 +14,7 @@ public:
|
|||||||
* send telegram Message to predefined destination
|
* send telegram Message to predefined destination
|
||||||
* @param text message
|
* @param text message
|
||||||
*/
|
*/
|
||||||
int sendMessage(const std::string& text);
|
int sendMessage(const std::string &text);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* init Telegram api with apikey and chatid
|
* init Telegram api with apikey and chatid
|
||||||
|
@ -18,10 +18,11 @@ std::string Config::domainname;
|
|||||||
std::string Config::telegramApiKey;
|
std::string Config::telegramApiKey;
|
||||||
std::string Config::chatId;
|
std::string Config::chatId;
|
||||||
|
|
||||||
bool Config::readCredentials() {
|
bool Config::telegramSupport;
|
||||||
|
|
||||||
|
bool Config::readConfig() {
|
||||||
libconfig::Config cfg;
|
libconfig::Config cfg;
|
||||||
try {
|
try {
|
||||||
// todo make dynamic here
|
|
||||||
cfg.readFile(Version::ConfigDir.c_str());
|
cfg.readFile(Version::ConfigDir.c_str());
|
||||||
}
|
}
|
||||||
catch (const libconfig::FileIOException &fioex) {
|
catch (const libconfig::FileIOException &fioex) {
|
||||||
@ -33,7 +34,7 @@ bool Config::readCredentials() {
|
|||||||
myfile << Version::SAMPLECONFIG;
|
myfile << Version::SAMPLECONFIG;
|
||||||
myfile.close();
|
myfile.close();
|
||||||
} else {
|
} else {
|
||||||
std::cout << "error creating file" << std::endl;
|
Logger::error("error creating file");
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@ -52,11 +53,15 @@ bool Config::readCredentials() {
|
|||||||
// optional parameters
|
// optional parameters
|
||||||
telegramApiKey = (std::string) cfg.lookup("telegramApiKey");
|
telegramApiKey = (std::string) cfg.lookup("telegramApiKey");
|
||||||
chatId = (std::string) cfg.lookup("chatId");
|
chatId = (std::string) cfg.lookup("chatId");
|
||||||
|
telegramSupport = true;
|
||||||
}
|
}
|
||||||
catch (const libconfig::SettingNotFoundException &nfex) {
|
catch (const libconfig::SettingNotFoundException &nfex) {
|
||||||
// triggered if setting is missing in config
|
// triggered if setting is missing in config
|
||||||
if (!(std::strcmp("telegramApiKey", nfex.getPath()) == 0 || std::strcmp("chatId", nfex.getPath()) == 0)) {
|
if (!(std::strcmp("telegramApiKey", nfex.getPath()) == 0 || std::strcmp("chatId", nfex.getPath()) == 0)) {
|
||||||
std::cerr << "No '" << nfex.getPath() << "' setting in configuration file." << std::endl;
|
std::cerr << "No '" << nfex.getPath() << "' setting in configuration file." << std::endl;
|
||||||
|
} else {
|
||||||
|
Logger::message("no Telegram support - fields in config not set");
|
||||||
|
telegramSupport = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// check if needed values aren't empty
|
// check if needed values aren't empty
|
||||||
@ -83,27 +88,57 @@ bool Config::validateConfig() {
|
|||||||
try {
|
try {
|
||||||
// needed parameters
|
// needed parameters
|
||||||
if (((std::string) cfg.lookup("dynuapikey")).empty()) {
|
if (((std::string) cfg.lookup("dynuapikey")).empty()) {
|
||||||
Logger::warning("required parameter dynuapikey seems to be empty.");
|
Logger::warning("required parameter \"dynuapikey\" seems to be empty.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (((std::string) cfg.lookup("domainid")).empty()) {
|
if (((std::string) cfg.lookup("domainid")).empty()) {
|
||||||
Logger::warning("required parameter domainid seems to be empty.");
|
Logger::warning("required parameter \"domainid\" seems to be empty.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (((std::string) cfg.lookup("domainname")).empty()) {
|
if (((std::string) cfg.lookup("domainname")).empty()) {
|
||||||
Logger::warning("required parameter domainname seems to be empty.");
|
Logger::warning("required parameter \"domainname\" seems to be empty.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// optional parameters
|
// optional parameters
|
||||||
cfg.lookup("telegramApiKey");
|
cfg.lookup("telegramApiKey");
|
||||||
cfg.lookup("chatId");
|
cfg.lookup("chatId");
|
||||||
|
telegramSupport = true;
|
||||||
}
|
}
|
||||||
catch (const libconfig::SettingNotFoundException &nfex) {
|
catch (const libconfig::SettingNotFoundException &nfex) {
|
||||||
// triggered if setting is missing in config
|
// triggered if setting is missing in config
|
||||||
if (!(std::strcmp("telegramApiKey", nfex.getPath()) == 0 || std::strcmp("chatId", nfex.getPath()) == 0)) {
|
if (!(std::strcmp("telegramApiKey", nfex.getPath()) == 0 || std::strcmp("chatId", nfex.getPath()) == 0)) {
|
||||||
std::cerr << "No '" << nfex.getPath() << "' setting in configuration file." << std::endl;
|
std::cerr << "No '" << nfex.getPath() << "' setting in configuration file." << std::endl;
|
||||||
return false;
|
return false;
|
||||||
|
} else {
|
||||||
|
Logger::message("no Telegram support - fields in config not set");
|
||||||
|
telegramSupport = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Config::isTelegramSupported() {
|
||||||
|
return telegramSupport;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string &Config::getDynuapikey() {
|
||||||
|
return dynuapikey;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string &Config::getDomainid() {
|
||||||
|
return domainid;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string &Config::getDomainname() {
|
||||||
|
return domainname;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string &Config::getTelegramApiKey() {
|
||||||
|
return telegramApiKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string &Config::getChatId() {
|
||||||
|
return chatId;
|
||||||
|
}
|
||||||
|
|
||||||
|
Config::Config() = default;
|
||||||
|
@ -3,10 +3,10 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
#include "FileLogger.h"
|
#include "FileLogger.h"
|
||||||
|
#include "IpHelper.h"
|
||||||
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <climits>
|
|
||||||
|
|
||||||
void FileLogger::safeip(std::string ip) {
|
void FileLogger::safeip(std::string ip) {
|
||||||
std::ofstream out;
|
std::ofstream out;
|
||||||
@ -25,8 +25,8 @@ std::string FileLogger::readip() {
|
|||||||
|
|
||||||
in >> ip;
|
in >> ip;
|
||||||
|
|
||||||
// when received ip has no : return 0.0.0.0
|
// when received ip has no . return 0.0.0.0
|
||||||
if (ip.find(':') == ULONG_MAX)
|
if (!IpHelper::isIpValid(ip))
|
||||||
return "0.0.0.0";
|
return "0.0.0.0";
|
||||||
else
|
else
|
||||||
return ip;
|
return ip;
|
||||||
|
@ -9,12 +9,11 @@
|
|||||||
#include "api/TelegramAPI.h"
|
#include "api/TelegramAPI.h"
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
#include "Version.h"
|
#include "Version.h"
|
||||||
|
#include "IpHelper.h"
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <Logger.h>
|
#include <Logger.h>
|
||||||
#include <climits>
|
|
||||||
|
|
||||||
void IPRefresher::checkIPAdress(bool force) {
|
void IPRefresher::checkIPAdress(bool force) {
|
||||||
FileLogger logger;
|
FileLogger logger;
|
||||||
@ -25,7 +24,7 @@ void IPRefresher::checkIPAdress(bool force) {
|
|||||||
if (ip.empty()) {
|
if (ip.empty()) {
|
||||||
//no internet connection (or other error)
|
//no internet connection (or other error)
|
||||||
Logger::warning("no internet connection");
|
Logger::warning("no internet connection");
|
||||||
} else if (ip.find(':') == ULONG_MAX) {
|
} else if (!IpHelper::isIpValid(ip)) {
|
||||||
// error when ip doesn't contain a :
|
// error when ip doesn't contain a :
|
||||||
Logger::warning("an error occured when getting the global ip");
|
Logger::warning("an error occured when getting the global ip");
|
||||||
} else {
|
} else {
|
||||||
@ -37,13 +36,15 @@ void IPRefresher::checkIPAdress(bool force) {
|
|||||||
Logger::message("ip changed! -- from :" + oldip + "to: " + ip);
|
Logger::message("ip changed! -- from :" + oldip + "to: " + ip);
|
||||||
|
|
||||||
DynuAPI dynu;
|
DynuAPI dynu;
|
||||||
dynu.init(Config::dynuapikey, Config::domainid, Config::domainname);
|
dynu.init(Config::getDynuapikey(), Config::getDomainid(), Config::getDomainname());
|
||||||
|
// actual refresh of IP in api - here
|
||||||
|
bool result = dynu.refreshIp(ip);
|
||||||
|
|
||||||
if (dynu.refreshIp(ip)) {
|
if (result && Config::isTelegramSupported()) {
|
||||||
TelegramAPI tele;
|
TelegramAPI tele;
|
||||||
tele.init(Config::telegramApiKey, Config::chatId);
|
tele.init(Config::getTelegramApiKey(), Config::getChatId());
|
||||||
tele.sendMessage(oldip + " moved to " + ip);
|
tele.sendMessage(oldip + " moved to " + ip);
|
||||||
} else {
|
} else if (!result) {
|
||||||
//error
|
//error
|
||||||
Logger::error("failed to write ip to dynu api!");
|
Logger::error("failed to write ip to dynu api!");
|
||||||
}
|
}
|
||||||
@ -53,13 +54,11 @@ void IPRefresher::checkIPAdress(bool force) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
IPRefresher::IPRefresher() = default;
|
|
||||||
|
|
||||||
IPRefresher::IPRefresher(bool loop) {
|
IPRefresher::IPRefresher(bool loop) {
|
||||||
if (loop) {
|
if (loop) {
|
||||||
Logger::message("startup of service");
|
Logger::message("startup of service");
|
||||||
Logger::message("Version: " + Version::VERSION);
|
Logger::message("Version: " + Version::VERSION);
|
||||||
if (Config::readCredentials()) {
|
if (Config::readConfig()) {
|
||||||
while (true) {
|
while (true) {
|
||||||
Logger::message("starting check");
|
Logger::message("starting check");
|
||||||
checkIPAdress(false);
|
checkIPAdress(false);
|
||||||
|
11
src/IpHelper.cpp
Normal file
11
src/IpHelper.cpp
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
//
|
||||||
|
// Created by lukas on 07.05.20.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "IpHelper.h"
|
||||||
|
|
||||||
|
#include <climits>
|
||||||
|
|
||||||
|
bool IpHelper::isIpValid(std::string ip) {
|
||||||
|
return (ip.find('.') != ULONG_MAX);
|
||||||
|
}
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
#include "api/DynuAPI.h"
|
#include "api/DynuAPI.h"
|
||||||
|
|
||||||
int DynuAPI::refreshIp(std:: string ip) {
|
bool DynuAPI::refreshIp(std::string ip) {
|
||||||
Hashmap<std::string, std::string> args;
|
Hashmap<std::string, std::string> args;
|
||||||
args.add("name", domainname);
|
args.add("name", domainname);
|
||||||
args.add("ipv4Address", ip);
|
args.add("ipv4Address", ip);
|
||||||
@ -16,15 +16,11 @@ int DynuAPI::refreshIp(std:: string ip) {
|
|||||||
|
|
||||||
const std::string dynurepl = request("https://api.dynu.com/v2/dns/" + domainid, true, args, headers);
|
const std::string dynurepl = request("https://api.dynu.com/v2/dns/" + domainid, true, args, headers);
|
||||||
|
|
||||||
if (dynurepl != "{\"statusCode\":200}") {
|
return (dynurepl == "{\"statusCode\":200}");
|
||||||
return -1;
|
|
||||||
} else {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DynuAPI::init(const std::string& dynuApiKey, const std::string& domainId, const std::string& domainName) {
|
void DynuAPI::init(const std::string &dynuApiKey, const std::string &domainId, const std::string &domainName) {
|
||||||
this->dynuapikey=dynuApiKey;
|
this->dynuapikey = dynuApiKey;
|
||||||
this->domainid=domainId;
|
this->domainid = domainId;
|
||||||
this->domainname=domainName;
|
this->domainname = domainName;
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
#include <climits>
|
#include <climits>
|
||||||
|
|
||||||
int TelegramAPI::sendMessage(const std::string& text) {
|
int TelegramAPI::sendMessage(const std::string &text) {
|
||||||
Hashmap<std::string, std::string> args;
|
Hashmap<std::string, std::string> args;
|
||||||
args.add("chat_id", chatid);
|
args.add("chat_id", chatid);
|
||||||
args.add("text", text);
|
args.add("text", text);
|
||||||
|
@ -8,8 +8,6 @@
|
|||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
#include "api/IPAPI.h"
|
#include "api/IPAPI.h"
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* application entry point
|
* application entry point
|
||||||
*/
|
*/
|
||||||
@ -28,7 +26,7 @@ int main(int argc, char *argv[]) {
|
|||||||
std::cout << "Version " << Version::VERSION << std::endl;
|
std::cout << "Version " << Version::VERSION << std::endl;
|
||||||
} else if (firstarg == "-f" || firstarg == "--force") {
|
} else if (firstarg == "-f" || firstarg == "--force") {
|
||||||
IPRefresher ipr;
|
IPRefresher ipr;
|
||||||
if (Config::readCredentials()) {
|
if (Config::readConfig()) {
|
||||||
ipr.checkIPAdress(true);
|
ipr.checkIPAdress(true);
|
||||||
} else {
|
} else {
|
||||||
std::cout << "incorrect credentials!" << std::endl;
|
std::cout << "incorrect credentials!" << std::endl;
|
||||||
@ -40,7 +38,7 @@ int main(int argc, char *argv[]) {
|
|||||||
if (Config::validateConfig()) {
|
if (Config::validateConfig()) {
|
||||||
Logger::message("Config file is OK");
|
Logger::message("Config file is OK");
|
||||||
} else {
|
} else {
|
||||||
Logger::warning("There are errors in config file!");
|
Logger::error("There are errors in config file!");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
} else if (firstarg == "-ip" || firstarg == "--currentip") {
|
} else if (firstarg == "-ip" || firstarg == "--currentip") {
|
||||||
@ -52,7 +50,7 @@ int main(int argc, char *argv[]) {
|
|||||||
} else {
|
} else {
|
||||||
IPRefresher ipr;
|
IPRefresher ipr;
|
||||||
Logger::message("starting check");
|
Logger::message("starting check");
|
||||||
if (Config::readCredentials()) {
|
if (Config::readConfig()) {
|
||||||
ipr.checkIPAdress(false);
|
ipr.checkIPAdress(false);
|
||||||
} else {
|
} else {
|
||||||
std::cout << "incorrect credentials!" << std::endl;
|
std::cout << "incorrect credentials!" << std::endl;
|
||||||
|
Loading…
Reference in New Issue
Block a user