Im trying to get my c++ app to authenticate user/pass against a web database. To do so, I have a simple html form for user/pass which triggers the authentication php script.
Im having a hard time understanding cURL since im a complete noob on it.
Right now, I'm able to send data to the html form, but i dont even know if im doing properly.
Ideally, I would like you to teach me how to do it, and how to read a response. I mean, if the login goes right, how do i get c++ to know it?
All the code i have:
HTML
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<form method="post" action="check.php">
<input type="text" name="uname"/>
<input type="password" name="password"/>
<input type="submit"/>
</form>
</html>
PHP
<?php
$fileDir = '/var/www/html/forums/';
require($fileDir . '/src/XF.php');
XF::start($fileDir);
$app = \XF::setupAPP('XF\App');
$username = $_POST['uname']; $password = $_POST['password'];
$ip = $app->request->getIp();
$loginService = $app->service('XF:User\Login', $username, $ip);
$userValidate = $loginService->validate($password, $error);
if(!$userValidate)
{
//Not good pass / user
$data = ['validated' => false];
}
else $data = ['validated' => true];
header('Content-type: application/json');
echo json_encode($data);
?>
C++
#include "stdafx.h"
#include <iostream>
#include <stdio.h>
#include <curl/curl.h>
using namespace std;
int main()
{
char username[20];
char password[25];
cout << "Username: ";
cin >> username;
cout << "Password: ";
cin >> password;
/*------------------------------------------*/
CURL *curl;
CURLcode res;
curl_global_init(CURL_GLOBAL_ALL);
curl = curl_easy_init();
if (curl) {
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt(curl, CURLOPT_URL, "https://localhost/index.hmtl");
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "uname=?&password=?", username, password);
res = curl_easy_perform(curl);
if (res != CURLE_OK) {
fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
}
curl_easy_cleanup(curl);
}
curl_global_cleanup();
}
EDIT: WORKING SOLUTION
#include "stdafx.h"
#include <iostream>
#include <stdio.h>
#include <string>
#include <curl/curl.h>
using namespace std;
string urlencode(const string &str) {
char *escaped = curl_escape(str.c_str(), str.length());
if (escaped == NULL) throw runtime_error("curl_escape failed!");
string ret = escaped;
curl_free(escaped);
return ret;
}
size_t my_write_function(const void * indata, const size_t size, const size_t count, void *out) {
(*(string*)out).append((const char*)indata, size*count);
return size * count;
}
int main()
{
string username;
string password;
cout << "Username: ";
getline(cin, username);
cout << "Password: ";
getline(cin, password);
/*------------------------------------------*/
CURL *curl;
CURLcode res;
curl_global_init(CURL_GLOBAL_ALL);
curl = curl_easy_init();
if (curl) {
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt(curl, CURLOPT_URL, "https://urlto/index.html");
curl_easy_setopt(curl, CURLOPT_COPYPOSTFIELDS, string("uname=" + urlencode(username) + "&password=" + urlencode(password)).c_str());
string response;
curl_easy_setopt(curl, CURLOPT_URL, "https://urlto/check.php");
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, my_write_function);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
res = curl_easy_perform(curl);
//cout << "Response from website: " << response << endl;
if (response.find("true") == string::npos) {
cout << "Failed to login";
}
else cout << "Log in successful";
if (res != CURLE_OK) {
fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
}
curl_easy_cleanup(curl);
}
curl_global_cleanup();
}
i dont even know if im doing properly. - well, there's certainly a couple of bugs in your code,
first, what do you think happens if the username is longer than 20 bytes, or the password is longer than 25 bytes? try
string username;
getline(cin, username);
instead. c++ will keep increasing the size of username as needed until you run out of ram, as it should be.
and i see you're using CURLOPT_POSTFIELDS (and wrongly), until you know what you're doing, i recommend you use CURLOPT_COPYPOSTFIELDS instead. (btw i pretty much always use COPYPOSTFIELDS myself) this line is wrong:
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "uname=?&password=?", username, password);`
because curl_easy_setopt() only accepts 3 parameters, but you're trying to give it 5. i don't think that will even compile, but even if it does, it certainly shouldn't work at runtime.
try instead something like:
string urlencode(const string& str)
{
char *escaped = curl_escape(str.c_str(), str.length());
if (unlikely(escaped==NULL))
{
throw runtime_error("curl_escape failed!");
}
string ret = escaped;
curl_free(escaped);
return ret;
}
curl_easy_setopt(curl, CURLOPT_COPYPOSTFIELDS, string("uname="+urlencode(username)+"&password="+urlencode(password)).c_str());
as for reading (and capturing) the output, there's lots of ways to do it, but how about hooking the CURLOPT_WRITEFUNCTION? that tends to work, something like:
size_t my_write_function( const void * indata, const size_t size, const size_t count, void *out){
(*(string*)out).append((const char*)indata,size*count);
return size*count;
}
then
string response;
curl_easy_setopt(curl,CURLOPT_WRITEFUNCTION,my_write_function);
curl_easy_setopt(curl,CURLOPT_WRITEDATA,&response);
res = curl_easy_perform(curl);
cout << "response from website: " << response << endl;
now you can check if you logged in or not by check checking for the existence of the string "true" in the response (because it should respond something like {validated:true} if you are), eg
if(response.find("true")==string::npos){
cout << "failed to authenticate!";
}else{
cout << "authenticated successfully!";
}
(and a warning, while it may be tempting to use a lambda callback with CURLOPT_WRITEFUNCTION, it's a trap, c++ lambdas may crash when given to curl as callbacks... been there, done that.)
Related
So I'm trying to upload a simple text file using Qt Network Mangager to a php script that I'm serving. But it's not working. I tried examples with QHttpMultiPart and with Setting raw data headers in request but none work.
Here is my Qt Code:
#include <QCoreApplication>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QDebug>
#include <QEventLoop>
#include <QObject>
#include <QVariantMap>
#include <QJsonDocument>
#include <QFile>
#include <QHttpMultiPart>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QString address = "http://localhost/api_test/";
//address = "https://dashboard.viewmind.ai/dashboard/api_test/welcome.php";
QUrl api_url = QUrl(address);
QVariantMap postDatamap;
postDatamap.insert("Name","Ariel Ñoño");
postDatamap.insert("Age",37);
QJsonDocument json = QJsonDocument::fromVariant(postDatamap);
qDebug() << "Sending the request";
QNetworkAccessManager *networkManager = new QNetworkAccessManager();
QNetworkRequest request(api_url);
QString bound = "<<<<<boundary>>>>>";
request.setRawHeader(QString("Content-Type").toUtf8(),QString("multipart/form-postData; boundary=" + bound).toUtf8());
//QByteArray postData;
QByteArray postData(QString("--" + bound + "\r\n").toUtf8());
postData.append("Content-Disposition: form-postData; name=\"action\"\r\n\r\n");
postData.append("welcome.php\r\n");
postData.append(QString("--" + bound + "\r\n").toUtf8());
postData.append("Content-Disposition: form-postData; name=\"uploaded\"; filename=\"");
postData.append("test.json");
postData.append("\"\r\n");
postData.append("Content-Type: text/xml\r\n\r\n"); //postData type
QFile file("test.json");
if (!file.open(QIODevice::ReadOnly)){
qDebug() << "QFile Error: File not found!";
delete networkManager;
return 0;
} else { qDebug() << "File found, proceed as planned"; }
postData.append(file.readAll());
postData.append("\r\n");
postData.append(QString("--" + bound + "\r\n").toUtf8());
request.setRawHeader(QString("Content-Length").toUtf8(), QString::number(postData.length()).toUtf8());
//request.setHeader(QNetworkRequest::ContentTypeHeader,"application/json; charset=utf-8");
//request.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-postData; name=\"text\""));
//qDebug() << QString(json.toJson());
//QHttpMultiPart multipart(QHttpMultiPart::FormDataType);
//QHttpPart textPart;
// QFile file("test.json");
// if (!file.open(QIODevice::ReadOnly)){
// qDebug() << "Could not open file for reading";
// delete networkManager;
// return 0;
// }
//textPart.setBodyDevice(&file);
//multipart.append(textPart);
//file.setParent(&multipart);
//QNetworkReply *reply = networkManager->post(request,json.toJson());
//QNetworkReply *reply = networkManager->post(request,file.readAll());
//QNetworkReply *reply = networkManager->post(request,&multipart);
QNetworkReply *reply = networkManager->post(request,postData);
//file.setParent(reply);
//multipart.setParent(reply);
// Using the loop to wait for the reply to finish.
QEventLoop loop;
QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
loop.exec();
qDebug() << "Reply is finished";
//file.close();
if (reply->error() != QNetworkReply::NoError){
qDebug() << "The following error ocurred";
qDebug() << reply->errorString();
return 0;
}
QString postData_returned = QString::fromUtf8(reply->readAll());
qDebug() << "DATA RETURNED";
qDebug() << postData_returned;
return 0;
}
My php code looks like this
<?php
header("Access-Control-Allow-Origin: *"); // Anyone can access
header("Content-Type: application/json; charset=UTF-8"); // Will return json data.
error_log("FILES iS");
vardump_toerror($_FILES);
?>
It is my understanding that the $_FILES super global should get filled with the file information. Am I mistaken? But the print out shows it's empty.
I am not an expert in PHP but I find it unnecessary to use the content-type application/json since multipart (submit forms) is not part of that protocol. On the other hand I can't find a reference of the vardump_toerror function so I change with var_dump so my test php is:
<?php
var_dump($_FILES);
?>
In a previous question for PyQt5 I implemented a similar logic for django that also applies in this case so I will show a translation.
#include <QCoreApplication>
#include <QFile>
#include <QHttpMultiPart>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QTextCodec>
QHttpMultiPart *buildMultpart(const QVariantMap & data, const QMap<QString, QString> filenames){
QHttpMultiPart *multipart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
QVariantMap::const_iterator i_data = data.constBegin();
while (i_data != data.constEnd()) {
QHttpPart postpart;
postpart.setHeader(QNetworkRequest::ContentDispositionHeader, QString("form-data; name=\"%1\"").arg(i_data.key()));
postpart.setBody(i_data.value().toByteArray());
multipart->append(postpart);
++i_data;
}
QMap<QString, QString>::const_iterator i_filenames = filenames.constBegin();
while (i_filenames != filenames.constEnd()) {
QFile *file = new QFile(i_filenames.value());
if(!file->open(QIODevice::ReadOnly)){
delete file;
continue;
}
QHttpPart postpart;
postpart.setHeader(QNetworkRequest::ContentDispositionHeader,
QString("form-data; name=\"%1\"; filename=\"%2\"")
.arg(i_filenames.key(), file->fileName()));
postpart.setBodyDevice(file);
multipart->append(postpart);
file->setParent(multipart);
++i_filenames;
}
return multipart;
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QUrl url("http://localhost:4000/upload.php");
QNetworkAccessManager manager;
QMap<QString, QString> filenames;
filenames["fileToUpload"] = "/path/of/data.txt";
QHttpMultiPart *multipart = buildMultpart({}, filenames);
QNetworkRequest request(url);
QNetworkReply *reply = manager.post(request, multipart);
multipart->setParent(reply);
QObject::connect(reply, &QNetworkReply::finished, QCoreApplication::quit);
a.exec();
if(reply->error() == QNetworkReply::NoError){
qDebug() << reply->readAll();
}
else{
qDebug() << reply->error() << reply->errorString();
}
delete reply;
return 0;
}
Output:
"array(1) {\n [\"fileToUpload\"]=>\n array(5) {\n [\"name\"]=>\n string(8) \"data.txt\"\n [\"type\"]=>\n string(0) \"\"\n [\"tmp_name\"]=>\n string(14) \"/tmp/phpVmOAhO\"\n [\"error\"]=>\n int(0)\n [\"size\"]=>\n int(6)\n }\n}\n"
I am trying to encrypt and decrypt a communication between a C++ library and a PHP server using OPENSSL library in both of them. I want to use the Blowfish CBC algorithm but it seems that the results are different between the C++ code and the PHP code. The C++ code is taken from here:
This is the PHP code:
<?php
function strtohex($x)
{
$s='';
foreach (str_split($x) as $c) $s.=sprintf("%02X",ord($c));
return($s);
}
$encryptedMessage = openssl_encrypt("input", "BF-CBC", "123456", 0, "initvect");
echo $encryptedMessage;
echo "\n";
echo strtohex($encryptedMessage);
The PHP output is this:
x9jDa2WMwvQ=
78396A446132574D7776513D
This is the c++ code:
bool do_encrypt(const char *in, unsigned char *out, int *outlen, unsigned char *key, unsigned char *iv)
{
int buflen, tmplen;
EVP_CIPHER_CTX ctx;
EVP_CIPHER_CTX_init(&ctx);
EVP_EncryptInit_ex(&ctx, EVP_bf_cbc(), nullptr, key, iv);
if (!EVP_EncryptUpdate(&ctx, out, &buflen, (unsigned char*)in, strlen(in)))
{
return false;
}
if (!EVP_EncryptFinal_ex(&ctx, out + buflen, &tmplen))
{
return false;
}
buflen += tmplen;
*outlen = buflen;
EVP_CIPHER_CTX_cleanup(&ctx);
return true;
}
unsigned char output[2048] = { 0 };
int outLen;
auto result = do_encrypt("input", output, &outLen, (unsigned char*)"123456", (unsigned char*)"initvect");
BIGNUM *outputStr = BN_new();
BN_bin2bn(output, outLen, outputStr);
cout << base64_encode(output, outLen) << "\n";
cout << BN_bn2hex(outputStr) << "\n";
The C++ output is this:
EfRhhWqGmSQ=
11F461856A869924
Can someone please help me understand what I'm doing wrong? Any help will be very much appreciated.
Thanks!
Edit 1:
I managed to fix the C++ code after jww's answer and it worked well. I was missing the EVP_CIPHER_CTX_set_key_length However, I couldn't make the PHP code return the same thing and eventually we decided to move to AES and it now works flawlessly. Thanks!
For your OpenSSL code, I believe you need to call EVP_CIPHER_CTX_set_key_length to tell the library the key is only 6 bytes.
Let me throw Crypto++ into the arena below. OpenSSL and Crypto++ will converge on the right answer once you add the missing EVP_CIPHER_CTX_set_key_length OpenSSL call. The right answer is 32CEBA7E046431EB (in hex).
I don't know what's going on with PHP:
x9jDa2WMwvQ=
78396A446132574D7776513D
Considering x is ASCII 0x78, 9 is ASCII 0x39, I'm guessing you hex encoded the Base64 string.
$ cat test.cxx
#include "blowfish.h"
#include "modes.h"
#include "channels.h"
#include "filters.h"
#include "base64.h"
#include "hex.h"
using namespace CryptoPP;
#include <iostream>
#include <string>
using namespace std;
int main(int argc, char* argv[])
{
const byte key[] = "123456"; // 6
const byte iv[] = "initvect"; // 8
CBC_Mode<Blowfish>::Encryption encryptor;
encryptor.SetKeyWithIV(key, 6, iv, 8);
string r1, r2;
ChannelSwitch chsw;
Base64Encoder e1(new StringSink(r1));
HexEncoder e2(new StringSink(r2));
chsw.AddDefaultRoute(e1);
chsw.AddDefaultRoute(e2);
string msg = "input";
StringSource ss(msg, true,
new StreamTransformationFilter(encryptor,
new Redirector(chsw)));
cout << r1 << endl;
cout << r2 << endl;
return 0;
}
The test program results in:
$ ./test.exe
Ms66fgRkMes=
32CEBA7E046431EB
Here's the OpenSSL portion of things. Notice EVP_EncryptInit_ex is called twice. First, EVP_EncryptInit_ex is called to set the block cipher EVP_bf_cbc. The key length is set with EVP_CIPHER_CTX_set_key_length. Then second, EVP_EncryptInit_ex is called to set the key and iv.
#include <openssl/evp.h>
#include <iostream>
#include <iomanip>
#include <stdexcept>
using namespace std;
typedef unsigned char byte;
int main()
{
EVP_CIPHER_CTX ctx;
EVP_CIPHER_CTX_init(&ctx);
int rc;
const byte key[] = "123456"; // 6
const byte iv[] = "initvect"; // 8
rc = EVP_EncryptInit_ex(&ctx, EVP_bf_cbc(), NULL, 0, 0);
if (rc != 1)
throw runtime_error("EVP_EncryptInit_ex failed");
rc = EVP_CIPHER_CTX_set_key_length(&ctx, 6);
if (rc != 1)
throw runtime_error("EVP_CIPHER_CTX_set_key_length failed");
rc = EVP_EncryptInit_ex(&ctx, NULL, NULL, key, iv);
if (rc != 1)
throw runtime_error("EVP_EncryptInit_ex failed");
const byte msg[] = "input";
byte buf[32];
int len1 = sizeof(buf), len2 = sizeof(buf);
rc = EVP_EncryptUpdate(&ctx, buf, &len1, msg, 5);
if (rc != 1)
throw runtime_error("EVP_EncryptUpdate failed");
rc = EVP_EncryptFinal_ex(&ctx, buf+len1, &len2);
if (rc != 1)
throw runtime_error("EVP_EncryptFinal_ex failed");
for(unsigned int i=0; i<len1+len2; i++)
cout << std::hex << setw(2) << setfill('0') << (int)buf[i];
cout << endl;
EVP_CIPHER_CTX_cleanup(&ctx);
return 0;
}
I am having a really hard time reading character input that is sent through a socket connection to a Qt server application. The data is sent from PHP.
I understand the principles of reading streamdata because I already asked this on stack. I also got it working using a server and client written both in Qt.
The method I use is to append the bytesize of the data i want to send before the actual data. Then when the data comes in, I first read the length parth so that I know exactly how much bytes I have to read in order to have correctly formed data.
it looks like this:
send function:
void Client::sendNewMessage(){
qDebug() << "sendNewMessage()";
QString string(messageLineEdit->text());
QByteArray block;
QDataStream out(&block, QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_4_0);
out << quint16(0);
out << string;
out.device()->seek(0);
out << (quint16)(block.size() - sizeof(quint16));
tcpSocket->write(block);
}
receive function:
QDataStream in(tcpServerConnection);
in.setVersion(QDataStream::Qt_4_0);
qDebug() << "bytes available = " << tcpServerConnection->bytesAvailable();
if (blockSize == 0) {
int size = (int) sizeof(quint16);
qDebug() << "size = " << size;
if (tcpServerConnection->bytesAvailable() < (int)sizeof(quint16)){
qDebug() << "less bytes than size...";
return;
}
qDebug() << "bytes available=" << tcpServerConnection->bytesAvailable();
in >> blockSize;
}
if (tcpServerConnection->bytesAvailable() < blockSize){
qDebug() << "less bytes available than blocksize, bytes="
<< tcpServerConnection->bytesAvailable();
return;
}
QString data;
in >> data;
qDebug() << "data = " << data;
Okay, this all works so I tried doing it with PHP but it failed
this is one of my attempts:
<?php
$addr = gethostbyname("127.0.0.1");
$client = stream_socket_client("tcp://$addr:*****", $errno, $errorMessage);
if ($client === false) {
throw new UnexpectedValueException("Failed to connect: $errorMessage");
}
$data = 'a';
$datatopost = serialize($data);
fwrite($client, strlen($data));
fwrite($client, base64_encode($data));
echo stream_get_contents($client);
fclose($client);
In Qt I have tried various combinations of quint8, 16, 32, 64, sizeof(char), sizeof(int).
in PHP I have tried serializing the data, encoding it, and also sending it without all that stuff. But i can not get it to work. I must be very close though because the data is actually sent as there are bytes available but I have no idea how to encode/decode correctly for it to work.
After asking various question concerning this topic I do feel that my understanding has gone up a lot but an important piece of information on how to actually do things is still missing for me.
So my question: What is going wrong here and what steps need to be taken to be able to read data from PHP to Qt/C++?
Details are highly apreciated as I really like to know how things work from the inside out.
side-note after sending data from the PHP script, the server sends data back aswel and that works. So the connection is made succesfuly
UPDATE
this is the working php script that actually also receives a reply back:
<?php
if(!($sock = socket_create(AF_INET, SOCK_STREAM, 0)))
{
perror("Could not create socket");
}
echo "Socket created n";
//Connect socket to remote server
if(!socket_connect($sock , '127.0.0.1' , *****))
{
perror("Could not connect");
}
echo "Connection established n";
$message = "aa";
//Send the message to the server
if( ! socket_send ( $sock , $message , strlen($message) , 0))
{
perror("Could not send data");
}
echo "Message send successfully n";
//Now receive reply from server
if(socket_recv ( $sock , $buf , 500 , MSG_WAITALL ) === FALSE)
{
perror("Could not receive data");
}
echo $buf;
///Function to print socket error message
function perror($msg)
{
$errorcode = socket_last_error();
$errormsg = socket_strerror($errorcode);
die("$msg: [$errorcode] $errormsg n");
}
The script reply when executed from browser url:
Socket created nConnection established nMessage send successfully n hello
It's not that surprising the PHP code does not integrate. As mentioned you have to be aware that QDataStream implements a custom serialization. And as also mentioned you probably want to use (read|write)RawData, or (read|write)Bytes, if your reading something not previously serialized with QDataStream in general. However, the general idea of the way your trying to write string data from PHP should be compatible with the way Qt encodes strings (length then a series of characters. That is what the manual says anyway..). But there some issues.
QString is 2Byte Unicode.
PHP Strings are byte arrays of an arbitrary kind of ASCII compatible data - PHP String details.
There is a few things wrong with this bit:
fwrite($client, strlen($data));
fwrite($client, base64_encode($data));
strlen() returns the number of bytes in the underlying storage (which is the actual byte length for a ASCII string). base64_encode() changes the number of bytes in the string. And your assuming fwrite() is writing a four byte integer. Its type casting and writing a string.
We are still guessing at how
QString data;
in >> data;
really works.
General advice is, you've got to carefully define external binary APIs.
Do you need data serialization for this task at all? Your PHP client and Qt server are probably using different formats for it.
Try to send and receive raw data.
Here is a simple QTcpServer exmaple:
class DataReceiver : public QObject
{
Q_OBJECT
public:
explicit DataReceiver(QObject *parent = 0);
public slots:
void start(quint16 port = 9090);
private slots:
void newTcpConnection();
private:
QTcpServer server;
};
DataReceiver::DataReceiver(QObject *parent) :
QObject(parent)
{
connect(&server, SIGNAL(newConnection()), this, SLOT(newTcpConnection()));
}
void DataReceiver::start(quint16 port)
{
bool isOk = server.listen(QHostAddress::Any, port);
if (isOk && server.isListening())
{
qDebug() << "QTcpServer started on port" << port;
}
else
{
qCritical() << "Failed to start QTcpServer";
}
}
void DataReceiver::newTcpConnection()
{
qDebug() << "New incoming connection";
QTcpSocket *socket = server.nextPendingConnection();
QByteArray data;
while (true)
{
QByteArray tmp = socket->readAll();
data += tmp;
if (tmp.isEmpty() && !socket->waitForReadyRead())
{
break;
}
}
socket->deleteLater();
qDebug("Data received: %s (len = %d)", data.constData(), data.length());
}
Launching server:
#include <QCoreApplication>
#include "data_receiver.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
DataReceiver d;
d.start();
return a.exec();
}
You can use a PHP client to send data to it:
<?php
$addr = gethostbyname("127.0.0.1");
$port = 9090;
$data = 'hello from php';
$client = stream_socket_client("tcp://$addr:$port", $errno, $errorMessage);
if ($client === false) {
throw new UnexpectedValueException("Failed to connect: $errorMessage");
}
fwrite($client, $data);
fclose($client);
Or you can use the nc utility:
echo -n "hello from nc" | nc 127.0.0.1 9090
Here is server output for both cases:
QTcpServer started on port 9090
New incoming connection
Data received: hello from php (len = 14)
New incoming connection
Data received: hello from nc (len = 13)
I'm trying to send data with curl to my PHP file and then I can do all the other actions like hashing password/data with salt, running database queries eco. It seems to work fine, but there's only one problem. I'm not sure how to secure it, with an authorization token for example. I want to be able to query data from my PHP file using the written application only. I can see how this would become a problem, if people had access to the link through web browser for example.
I've included my code below, if someone needs something similar.
main.cpp
#include <iostream>
#include <stdlib.h>
#include <stdio.h>
#include <curl/curl.h>
#include <sha.h>
#include <hex.h>
using namespace std;
using namespace CryptoPP;
size_t size = 0;
size_t write_to_string(void *ptr, size_t size, size_t count, void *stream) {
((string*)stream)->append((char*)ptr, 0, size*count);
return size*count;
}
template <class T>
string QueryDB(initializer_list<T> list) // Use initialize_list to query an undefined number of params
{
CURL *curl;
CURLcode res;
string submitdata = "", query_result;
int i = 1;
for (auto elem : list) // For each param append to the submitdata string
{
if (i == 1) { // If first param, we append "?"
string d = "?" + to_string(i) + "=" + elem;
submitdata.append(d);
} else if (i > 1) { // If not first param, we append "&" as it's the second, third, fourth ... param
string d = "&" + to_string(i) + "=" + elem;
submitdata.append(d);
}
i++;
}
curl_global_init(CURL_GLOBAL_ALL);
curl = curl_easy_init();
if (curl)
{
string loginurl = string("http://localhost/login.php");
curl_easy_setopt(curl, CURLOPT_USERPWD, "randomhttpuser:randomhttppassword");
curl_easy_setopt(curl, CURLOPT_URL, (loginurl + submitdata).c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_to_string);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &query_result);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 20L);
res = curl_easy_perform(curl);
if (res != CURLE_OK)
fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
curl_easy_cleanup(curl);
}
else {
query_result = "CONNECTION FAILED";
}
curl_global_cleanup();
return query_result;
}
string SHA256Hash(string input)
{
SHA256 hash;
string hashed_input;
StringSource ss(input, true, new HashFilter(hash, new HexEncoder(new StringSink(hashed_input))));
return hashed_input;
}
int main()
{
string username = "testuser";
string raw_password = "testpass";
// Hash password and send it as a query to PHP file
// query_result will hold the value of REQUEST response
auto hashed_password = SHA256Hash(raw_password);
auto query_result = QueryDB({ username, hashed_password });
cout << "=========================================== [ POST ] ===========================================" << endl;
cout << "User: " << username.c_str() << endl;
cout << "Raw Password: " << raw_password.c_str() << endl;
cout << "Hashed password: " << hashed_password.c_str() << endl;
cout << "========================================== [ REQUEST ] =========================================" << endl;
cout << query_result.c_str() << endl;
Sleep(15 * 1000);
return 0;
}
login.php
<?php
$reqparams = array();
function AddStringToArray($name,$string) {
global $reqparams;
$reqparams[$name] = $string;
}
/* Check if specified param exists in reqparams array */
function GetRequestParam($value) {
global $reqparams;
if (array_key_exists($value, $reqparams)) {
$returnvalue = $reqparams[$value];
} else {
$returnvalue = "INVALID PARAMETER";
}
return $returnvalue;
}
$authuser = "randomhttpuser";
$authpw = "randomhttppassword";
$authorized = False;
if (!isset($_SERVER['PHP_AUTH_USER'])) {
header('WWW-Authenticate: Basic realm="My Realm"');
header('HTTP/1.0 401 Unauthorized');
echo 'Failed to authorize!';
exit;
} else {
if($_SERVER['PHP_AUTH_USER'] == $authuser && $_SERVER['PHP_AUTH_PW'] == $authpw)
{
$authorized = True;
} else {
$authorized = False;
die('Failed to authorize!');
}
}
if($authorized == True)
{
/* Store each REQUEST and it's value in the $reqparams array using AddStringToArray function */
foreach ($_REQUEST as $key => $value)
{
$value = addslashes($value);
$value = strip_tags($value);
AddStringToArray($key, $value);
}
/* You should remember in which order you called the params in your REQUEST query or if you really want, you can just use:
$variable = $_REQUEST['param_name'];
However, if an undefined param is specified, it will result in an warning and ruin your output, if you manually parse it */
$user = GetRequestParam(1);
$pass = GetRequestParam(2);
/* GetRequestParam returns 'INVALID_PARAMETER' instead of a warning that an undefined param was requested */
$invalid_param = GetRequestParam(42);
/* Re-hash password with a salt that's stored in the PHP file only, before using or comparing it to the value stored in database or doing whatever else */
$salt = $user . $pass . "secretkey42";
$salt_hashed_passsword = strtoupper(hash('sha256', $salt));
echo "User: $user";
echo "\nHashed Password: $salt_hashed_passsword (Salt)";
}
?>
Edit: I could use HTTP header, but isn't it possible to reverse my application and abuse it?
Edit: I currently decided to use HTTP authentication as a temporary measure.
I stored a random generated username and password in my PHP file and compare them to the PHP_AUTH_USER/PW which are sent in the HTTP header from my cpp application using CURLOPT_USERPWD:
curl_easy_setopt(curl, CURLOPT_USERPWD, "randomhttpusername:randomhttppassword");
Hopefully, this will at least make it a bit harder for the hacker. First he will have to RE my application to get the the user/password and even after that he can only query the response if password belongs to specified user or not - since most of my queries are hard coded. You could even store the number of failed logins and temporarily ban him for x amount of time. Rest of the queries are made after login returns true.
I've also updated the code above to use the changes I've made and added some comments if you're too lazy to go over the code line-by-line. Feel free to give me some tips on how to improve the code or optimize for better use.
From what I understood, you want to implement a some sort of login system using auth tokens. In this case, OAuth can do the job. Here's a tutorial written on SitePoint that can guide you through the process.
What will be the C++ equivalemt command for below mentioned php command:
$command = shell_exec("sqlldr {$connect_string} control={$ctl_file_name} log={$log_file_name}");
So based on your comments a solution that would work would be to use popen(3):
#include <cstdio>
#include <iostream>
#include <string>
int main()
{
// Set file names based on your input etc... just using dummies below
std::string
ctrlFileName = "file1",
logFileName = "file2",
cmd = "sqlldr usr/pwd#LT45 control=" + ctrlFileName + " log=" + logFileName ;
std::cout << "Executing Command: " << cmd << std::endl ;
FILE* pipe = popen(cmd.c_str(), "r");
if (pipe == NULL)
{
return -1;
}
char buffer[128];
std::string result = "";
while(!feof(pipe))
{
if(fgets(buffer, 128, pipe) != NULL)
{
result += buffer;
}
}
std::cout << "Results: " << std::endl << result << std::endl ;
pclose(pipe);
}
Try forkpty, you get a file descriptor which you can use to read from the other pseudoterminal.