AWS EC2 Apache User Cannot run php exec - php

I am using the following PHP script to send an email from my server. I need to send an email to the admin when a new record is created in the DB. So within the same php script that updates the DB, I want to trigger the other script that sends the email.
Problem is no matter what I do, apache will not execute the email script when requested from the web/api.
However, when I run php sendemail.php from the command line it works. Also when I run php updatedb.php which includes the exec('php sendemail.php') also works from the command line (these are all executed with root "ec2-user").
Things I tried:
I checked the disabled functions in php.ini and it is empty, nothing is disabled.
I tried changing file permissions to include 'x' for the apache group still no go.
I tried replacing exec with shell_exec, and include, no luck.
Here is 'sendemail.php':
<?php
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;
require 'vendor/autoload.php';
use Aws\Ses\SesClient;
use Aws\Exception\AwsException;
$SesClient = new SesClient([
'profile' => 'default',
'version' => '2010-12-01',
'region' => 'us-west-2'
]);
$sender_email = 'sender#example.com';
$recipient_emails = ['recipient1#example.com','recipient2#example.com'];
$configuration_set = 'ConfigSet';
$subject = 'Amazon SES test (AWS SDK for PHP)';
$plaintext_body = 'This email was sent with Amazon SES using the AWS SDK for PHP.' ;
$html_body = '<h1>AWS Amazon Simple Email Service Test Email</h1>'.
'<p>This email was sent with <a href="https://aws.amazon.com/ses/">'.
'Amazon SES</a> using the <a href="https://aws.amazon.com/sdk-for-php/">'.
'AWS SDK for PHP</a>.</p>';
$char_set = 'UTF-8';
try {
$result = $SesClient->sendEmail([
'Destination' => [
'ToAddresses' => $recipient_emails,
],
'ReplyToAddresses' => [$sender_email],
'Source' => $sender_email,
'Message' => [
'Body' => [
'Html' => [
'Charset' => $char_set,
'Data' => $html_body,
],
'Text' => [
'Charset' => $char_set,
'Data' => $plaintext_body,
],
],
'Subject' => [
'Charset' => $char_set,
'Data' => $subject,
],
],
'ConfigurationSetName' => $configuration_set,
]);
$messageId = $result['MessageId'];
echo("Email sent! Message ID: $messageId"."\n");
} catch (AwsException $e) {
// output error message if fails
echo $e->getMessage();
echo("The email was not sent. Error message: ".$e->getAwsErrorMessage()."\n");
echo "\n";
}
a short version of the updatedb.php file, omitting all transactions:
<?php
exec('php send_email.php', $sendEmail);
require_once 'response.php';
$response = new response();
$response->setHttpStatusCode(201);
$response->setSuccess(true);
$response->addMessage('DB Record Inserted successfully ::: ');
$response->setData($sendEmail);
$response->send();
?>
in the updatedb.php file, if I change the first line to echo exec('whoami') and hit it from the web it works. Which is what I am looking for exactly except that I want to work for php sendemail.php
Environment: AWS EC2 Amazon Linux 2 AMI. PHP 7.2.34
I hope it is clear. I am beginner with linux. Please help. Thanks in advance to all.

Thank you so much #Riz your tip about sudo -u apache php sendemail.php saved my day! I was able to debug on the command line and it turns out that my mistake was with the line require 'vendor/autoload.php'; in my original sendemail.php script I was requiring the file from a directory that did not belong to the apache group. So, once I moved the vendor folder to the same folder as the sendemail.php script file everything worked great!
Lesson learned: Make sure all required/included files belong to the apache group. There was no need to grant apache any execution permissions on any file.

Related

SSH into dynamic remote server and run a command - Laravel 5.8

I'm using L 5.8 and I have a form with 3 inputs
Right now, I can SSH into a specific server with this package laravelcollective/remote": "~5.8. I required to pre configured IP, UN, PW in the config/remote.php like so :
'connections' => [
'production' => [
'host' => '1.1.1.1',
'username' => 'code',
'password' => '8888',
'key' => '',
'keytext' => '',
'keyphrase' => '',
'agent' => '',
'timeout' => 10,
],
],
Then, I can easily run any command(s) or even chain them
$commands = ['cd /home/code && ./runMe.sh'];
SSH::run($commands, function($line)
{
echo $line.PHP_EOL;
});
Result
My portal will connect to that server and run that command successfully, and I've verified it.
Now
I need to read it from form inputs. Is it possible to use the same plugin and dynamically setting that file remote.php ?
or
Should I start looking into something else because this is a dead end ?
Please advise,
You can use the config() helper here:
example config:
config([
'remote.connections.production.host' => $request->input('hostname'),
'remote.connections.production.username' => $request->input('username'),
'remote.connections.production.password' => $request->input('password')
]);
then call:
$commands = ['cd /home/code && ./runMe.sh'];
SSH::run($commands, function($line)
{
echo $line.PHP_EOL;
});
This example require the package php-ssh2, and provide many ways to retrieve the remote shell STDOUT as a stream. It's also great to control the current machine shell and get responses.
sudo apt install php-ssh2
Or by version:
sudo apt install php7.4-ssh2
Example usage of ssh2_exec() and ssh2_fetch_stream():
<?php
$ssh = ssh2_connect('192.162.68.212', 22); // Remote address
ssh2_auth_password($ssh, 'root', 'XQA8DCamdEm'); // Credentials
$shell = ssh2_shell($ssh, 'xterm'); // Choose remote shell
$stream = ssh2_exec($ssh, 'cd /home/code && ./runMe.sh'); // Remote command
stream_set_blocking($stream, true); // Wait for multiline output, till EOF
$stream_out = ssh2_fetch_stream($stream, SSH2_STREAM_STDIO); // SSH2_STREAM_STDERR
echo stream_get_contents($stream_out); // Print in real-time
It is possible to retrieve only the errors, this is great for monitoring only without parsing the whole output, by using the flag SSH2_STREAM_STDERR.
To write strings only to STDERR from the remote PHP, we can use:
fwrite(STDERR, "Some logs, from machine (...)\n");
Or we can pipe the errors directly in the remote shell command:
cd /home/code && ./runMe.sh 2>
https://www.php.net/manual/en/ref.ssh2.php

how to send message remotely to kafka

I am very new to kafka. I am trying to send a message from my local machine producer to kafka server. I am not able to figure out the issue or what am doing worng.
$config = \Kafka\ProducerConfig::getInstance();
$config->setMetadataRefreshIntervalMs(10000);
$config->setMetadataBrokerList('localhost:9092');
$config->setBrokerVersion('1.0.0');
$config->setRequiredAck(1);
$config->setIsAsyn(false);
$config->setProduceInterval(500);
$producer = new \Kafka\Producer(
function() {
return [
[
'topic' => 'test',
'value' => 'test....message.',
'key' => 'testkey',
],
];
}
);
// $producer->setLogger($logger);
$producer->success(function($result) {
print_r($result);
});
$producer->error(function($errorCode) {
var_dump($errorCode);
});
$producer->send(true);
Output:-
Fatal error: Uncaught exception 'Kafka\Exception' with message 'Not has broker can connection metadataBrokerList' in C:\xampp\htdocs\vendor\nmred\kafka-php\src\Kafka\Producer\Process.php on line 193
So you're trying to produce from your local machine to a different server? If that's the case you'll need to update the line
$config->setMetadataBrokerList('localhost:9092');
to point to that server's domain name and not localhost:9092
This is the library https://github.com/weiboad/kafka-php I was using in codeigniter for the producer.
This library works fine.
The issue was that on server port number was changed, it was 29092 actually by default the port is 9092 that why the connection was failing.

Why does Amazon SES fail half the time when called using the PHP SDK?

I'm using the AWS SDK for PHP (version 3.52.33, PHP version 7.2.19) and trying to send emails using the Simple Email Service (SES). I have SES configured, and can run the example code successfully. To make my life easier, I wrote a function to send emails (send_email.php):
<?php
// Path to autoloader for AWS SDK
define('REQUIRED_FILE', "/path/to/vendor/autoload.php");
// Region:
define('REGION','us-west-2');
// Charset
define('CHARSET','UTF-8');
// Specify Sender
define('SENDER', 'sender#xxxx.com');
require REQUIRED_FILE;
use Aws\Ses\SesClient;
use Aws\Ses\Exception\SesException;
function send_email($htmlBody,$textBody,$subject,$recipient) {
$access_key = 'accessKey';
$secret_key = 'secretKey';
$ret_array = array('success' => false,
'message' => 'No Email Sent'
);
$client = SesClient::factory(array(
'version' => 'latest',
'region' => REGION,
'credentials' => array(
'key' => $access_key,
'secret' => $secret_key
)
));
try {
$result = $client->sendEmail([
'Destination' => [
'ToAddresses' => [
$recipient,
],
],
'Message' => [
'Body' => [
'Html' => [
'Charset' => CHARSET,
'Data' => $htmlBody,
],
'Text' => [
'Charset' => CHARSET,
'Data' => $textBody,
],
],
'Subject' => [
'Charset' => CHARSET,
'Data' => $subject,
],
],
'Source' => SENDER,
]);
$messageId = $result->get('MessageId');
$ret_array['success'] = true;
$ret_array['message'] = $messageId;
echo("Email sent! Message ID: $messageId" . "\n");
} catch (SesException $error) {
echo("The email was not sent. Error message: " . $error->getAwsErrorMessage() . "\n");
$ret_array['message'] = $error->getAwsErrorMessage();
}
return $ret_array;
}
This works when called from a simple testing script (test.php) in a terminal:
<?php
ini_set('display_errors','On');
error_reporting(E_ALL | E_STRICT);
require_once './send_email.php';
$email = 'test#email.com';
$htmlbody = 'test';
$txtbody = 'test';
$subject = 'test email';
$success = send_email($htmlbody,$txtbody,$subject,$email);
I get output like:
[~]$ php test.php
Email sent! Message ID: 0101016c8d665369-027be596-f8da-4410-8f09-ff8d7f87181b-000000
which is great. However, I'm doing this to send automated emails from a website (new user registration, password resets, ...) and when I try to use send_email from within a larger script I get a ~%50 success rate (when using a constant email address). Either it works and everything is fine, or it fails without an error message:
The email was not sent. Error message:
I know that an exception is being thrown, as I'm ending up in the catch statement, but I don't know how to get more information about what went wrong since there isn't a message associated with the exception. I've tried expanding what I look for in the catch block:
<snip>
catch (SesException $error) {
echo("The email was not sent. Error message: " . $error->getAwsErrorMessage() . "\n");
$ret_array['message'] = $error->getAwsErrorMessage();
$ret_array['errorCode'] = $error->getAwsErrorCode();
$ret_array['type'] = $error->getAwsErrorType();
$ret_array['response'] = $error->getResponse();
$ret_array['statusCode'] = $error->getStatusCode();
$ret_array['isConnectionError'] = $error->isConnectionError();
}
but when it fails everything is NULL except isConnectionError = false. Anecdotally, it is totally random -- I haven't been able to discern a pattern at all as to when it works and when it fails.
One other potentially relevant note: if I loop the email sending so a new user gets 10 emails, either they all succeed or they all fail.
So, does anyone have any suggestions as to what might be going wrong, or other steps I could take to help diagnose why this is happening?
For anyone running in to a similar issue in the future, I eventually came up with two solutions:
Initially, I gave up hope on the AWS SDK and switched to using PHPMailer which avoided the issue (though I believe at the cost of a loss in performance).
The real issue (I'm fairly certain now) was a mismatch in the versions of PHP between my CLI and what the webserver was providing. This question got me thinking about how that might be the issue. By updating the version of cURL and PHP that the webserver was using, I resolved the issue.

PHP Sending Large Amounts of Email to Amazon SES

I have an online software that sends emails to Amazon SES. Currently I have a cron job that sends the emails via the SMTP with phpmailer to send the messages. Currently I have to max the send limit to around 300 every minute to make sure my server doesn't time out. We see growth and eventually I'd like to send out to 10,000 or more.
Is there a better way to send to Amazon SES, or is this what everyone else does, but with just more servers running the workload?
Thanks in advance!
You can try using the AWS SDK for PHP. You can send emails through the SES API, and the SDK allows you to send multiple emails in parallel. Here is a code sample (untested and only partially complete) to get you started.
<?php
require 'vendor/autoload.php';
use Aws\Ses\SesClient;
use Guzzle\Service\Exception\CommandTransferException;
$ses = SesClient::factory(/* ...credentials... */);
$emails = array();
// #TODO SOME SORT OF LOGIC THAT POPULATES THE ABOVE ARRAY
$emailBatch = new SplQueue();
$emailBatch->setIteratorMode(SplQueue::IT_MODE_DELETE);
while ($emails) {
// Generate SendEmail commands to batch
foreach ($emails as $email) {
$emailCommand = $ses->getCommand('SendEmail', array(
// GENERATE COMMAND PARAMS FROM THE $email DATA
));
$emailBatch->enqueue($emailCommand);
}
try {
// Send the batch
$successfulCommands = $ses->execute(iterator_to_array($emailBatch));
} catch (CommandTransferException $e) {
$successfulCommands = $e->getSuccessfulCommands();
// Requeue failed commands
foreach ($e->getFailedCommands() as $failedCommand) {
$emailBatch->enqueue($failedCommand);
}
}
foreach ($successfulCommands as $command) {
echo 'Sent message: ' . $command->getResult()->get('MessageId') . "\n";
}
}
// Also Licensed under version 2.0 of the Apache License.
You could also look into using the Guzzle BatchBuilder and friends to make it more robust.
There are a lot of things you will need to fine tune with this code, but you may be able to achieve higher throughput of emails.
If anyone is looking for this answer, its outdated and you can find the new documentation here: https://docs.aws.amazon.com/aws-sdk-php/v3/guide/guide/commands.html
use Aws\S3\S3Client;
use Aws\CommandPool;
// Create the client.
$client = new S3Client([
'region' => 'us-standard',
'version' => '2006-03-01'
]);
$bucket = 'example';
$commands = [
$client->getCommand('HeadObject', ['Bucket' => $bucket, 'Key' => 'a']),
$client->getCommand('HeadObject', ['Bucket' => $bucket, 'Key' => 'b']),
$client->getCommand('HeadObject', ['Bucket' => $bucket, 'Key' => 'c'])
];
$pool = new CommandPool($client, $commands);
// Initiate the pool transfers
$promise = $pool->promise();
// Force the pool to complete synchronously
$promise->wait();
Same thing can be done for SES commands
Thank you for your answer. It was a good starting point. #Jeremy Lindblom
My problem is now that i can't get the Error-Handling to work.
The catch()-Block works fine and inside of it
$successfulCommands
returns all the succeed Responses with Status-Codes, but only if an error occurs. For example "unverified address" in Sandbox-Mode. Like a catch() should work. :)
The $successfulCommands inside the try-Block only returns:
SplQueue Object
(
[flags:SplDoublyLinkedList:private] => 1
[dllist:SplDoublyLinkedList:private] => Array
(
)
)
I can't figure it out how to get the real Response from Amazon with Status-Codes etc.

Trigger ISPConfig vhost folders creation

I make an script that fill up all ISPConfig tables by itself and now i only need to raise some script to create the needed vhost and the rest of the symblink needed for apache to work.
My Script is working like a charm since i can view all the data correctly using the ISPConfig frontend.
Digging into the ISPConfig panel i see a RaiseEvent function triggered everytime a record is created but i can't trace where it ends and how it perform the creation of the symblink.
Maybe calling some function or cron it can work.
I'm using Apache 2 + PHP 5.3 + MySQL + ISPConfig 3 on Ubuntu Server 10.4
Ok I respond myself.
Since version 3 ISPConfig came with a simple API that let you performe some operation like Adding FTP Users, Websites and Databases.
I left here an example of how to create a database:
$params_db = array(
'server_id' => '1',
'system_user' => "web10",
'system_group' => 'client0',
'active' => 'y',
'type' => 'mysql',
'database_name' => $NAME,
'database_user' => $NAME,
'database_password' => '123456',
'database_charset' => 'utf8',
'remote_access' => 'n',
);
Next we have to create on the ISPConfig panel a "remote user" that allow to comunicate using the webservice.
$soap_username = 'whatever';
$soap_password = 'h4ck3m3';
$soap_location = 'http://localhost:8080/remote/index.php';
$soap_uri = 'http://localhost:8080/remote/';
$client = new SoapClient(null, array('location' => $soap_location, 'uri' => $soap_uri));
So, what's next?
Next we call the webserver function like this:
try
{
//* Login to the remote server
if( $session_id = $client->login($soap_username,$soap_password))
{
echo 'Logged into remote server sucessfully. The SessionID is '.$session_id. "\n";
$client->sites_database_add($session_id, $client_id, $params_db);
//* Logout
if($client->logout($session_id))
{
echo "DB Created\n";
}
}
}
catch (SoapFault $e)
{
die('SOAP Error: '.$e->getMessage());
}
For more information check out this link of howtogeek website: http://www.howtoforge.com/how-to-create-remote-api-scripts-for-ispconfig-3

Categories