Can you specify TLS v1.3 only in phpmailer? - phpmailer

Is it possible to specify the encryption protocol version used in PHPMailer?
Trying to build a small web tool for testing SMTP configurations. I used to be able to specify protocol version in .NET, but now that I am using apache, I am trying to do it in a PHP page with phpmailer. So I need to have it try ONLY a single encryption version like TLS 1.3.
I know I can set the smtpautoTLS to FALSE. But Can I specify TLS 1.3 or SSL v3 in something like the SMTPOptions Array? I haven't seemed to be able to find this in documentation/examples/google.
Thanks!
Edit for updated code and confirm this code only works for SMTPS/implicit style, but NOT STARTTLS
<?php
require_once 'PHPMailer/vendor/autoload.php';
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;
$from = $_POST['from'];
$to = $_POST['to'];
$subj = $_POST['subj'];
$body = $_POST['body'];
$server = $_POST['addr'];
$port = $_POST['port'];
$auth = $_POST['auth'];
$enctype = $_POST['enctype'];
$encver = $_POST['encver'];
$authuser = $_POST['authuser'];
$authpw = $_POST['authpw'];
$mail = new PHPMailer(true);
$mail->IsSMTP();
//$mail->SMTPDebug = 2; //2 for debugging with server responses. 0-4 as choices
$smtpopts = array();
if($encver == "auto")
{
$mail->SMTPAutoTLS = true;
}
else
{
$mail->SMTPAutoTLS = false;
}
if($auth == 1)
{
$mail->SMTPAuth = true;
$mail->Username = $authuser;
$mail->Password = $authpw;
switch($enctype)
{
case "implicit":
$mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS;
break;
case "explicit":
$mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
break;
}
switch($encver)
{
case "ssl3_0":
$smtpopts['ssl'] = array('crypto_method' => STREAM_CRYPTO_METHOD_SSLv3_CLIENT);
break;
case "tls1_0":
$smtpopts['ssl'] = array('crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT);
break;
case "tls1_1":
$smtpopts['ssl'] = array('crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT);
break;
case "tls1_2":
$smtpopts['ssl'] = array('crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT);
break;
case "tls1_3":
$smtpopts['ssl'] = array('crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT);
break;
}
$mail->SMTPOptions = $smtpopts;
}
else
{
$mail->SMTPAuth = false;
}
$mail->Host = $server;
$mail->Port = $port;
$mail->SetFrom($from);
$mail->AddAddress($to);
$mail->Subject = $subj;
$mail->MsgHTML($body);
$response = array();
try
{
header('Content-Type: application/json');
$mail->Send();
$response['success'] = 1;
$response['msg'] = "Success";
echo json_encode($response);
}
catch (Exception $e)
{
$response['success'] = 0;
$response['msg'] = $e->errorMessage();
error_log($e->errorMessage());
echo json_encode($response);
}
catch (\Exception $e)
{
$response['success'] = -99;
$response['msg'] = $e->getMessage();
error_log($e->getMessage());
echo json_encode($response);
}
?>

The critical constant for TLSv1.3 support is STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT, which was introduced in PHP 7.4.0, but appears to be undocumented.
PHPMailer uses the TLS constant STREAM_CRYPTO_METHOD_TLS_CLIENT in the SMTP class when encryption is initialised via STARTTLS. This constant combines TLS 1.0, 1.1, 1.2, and 1.3, as you can see in the PHP source.
So, for your particular situation, you want to substitute the STREAM_CRYPTO_METHOD_TLS_CLIENT default for STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT. You can achieve what you ask (at least for testing purposes) by subclassing the SMTP class and overriding the startTLS method so that you can control the TLS constant.
However, that approach will only work for SMTP+STARTTLS, not SMTPS. For SMTPS (ssl mode in PHPMailer) you can use a different approach that may work for both. Looking at this test case shows that you can specify a connection type constant in the options passed to stream_context_create – and this is the exact same array that PHPMailer gives you access to, so try this:
$mail->SMTPOptions = [
'ssl' => ['crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT]
];

Related

Resend specific order state emails e.g. order delivery shipped in shopware 6

I'm wondering if there is any simpler solution in sending specific order state emails.
Currently i use the following code to send e.g. the "order delivery shipped" email again.
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('orderNumber', $orderNumber));
$criteria->addAssociation('orderCustomer.salutation');
$criteria->addAssociation('orderCustomer.customer');
$criteria->addAssociation('stateMachineState');
$criteria->addAssociation('deliveries.shippingMethod');
$criteria->addAssociation('deliveries.shippingOrderAddress.country');
$criteria->addAssociation('deliveries.shippingOrderAddress.countryState');
$criteria->addAssociation('salesChannel');
$criteria->addAssociation('language.locale');
$criteria->addAssociation('transactions.paymentMethod');
$criteria->addAssociation('lineItems');
$criteria->addAssociation('currency');
$criteria->addAssociation('addresses.country');
$criteria->addAssociation('addresses.countryState');
/** #var OrderEntity|null $order */
$order = $this->orderRepository->search($criteria, $context)->first();
$context = new Context(new SystemSource());
$salesChannelContext = $this->orderConverter->assembleSalesChannelContext($order, $context)->getContext();
$salesChannelContext->addExtension(
MailSendSubscriber::MAIL_CONFIG_EXTENSION,
new MailSendSubscriberConfig(false)
);
$event = new OrderStateMachineStateChangeEvent(
'state_enter.order_delivery.state.shipped',
$order,
$salesChannelContext
);
$flowEvent = new FlowEvent('action.mail.send', new FlowState($event), [
'recipient' => [
'data' => [],
'type' => 'default',
],
'mailTemplateId' => $mailTemplateId,
]);
$this->sendMailAction->handle($flowEvent);
Given that you don't actually want to change the state, this looks like a good solution.
If you actually needed to programmatically change the state you could use the StateMachineRegistry service.
$transition = new Transition(OrderDeliveryDefinition::ENTITY_NAME, $orderDeliveryId, StateMachineTransitionActions::ACTION_SHIP, 'stateId');
$this->stateMachineRegistry->transition($transition, Context::createDefaultContext());
However if the current state is equal to the state you want to transition to, then an UnnecessaryTransitionException is expected to be thrown. You'd have to change the state back to a previous one before doing this and at that point your code is probably less trouble, if all you want to do is to repeat sending that mail.
Do you want to resend the mail or regenerate it? If you want to resend the mail you could use this out-of-the-box solution from FriendsOfShopware -> https://store.shopware.com/en/frosh97876597450f/mail-archive.html
You can build and send the mail with the Shopware\Core\Content\Mail\Service\MailService by yourself. But is way more code then just triggering it with a state change.
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('mailTemplateTypeId', self::ORDER_CONFIRMATION_MAIL_TYPE_ID));
$criteria->addAssociation('media.media');
$criteria->setLimit(1);
if ($order->getSalesChannelId()) {
$criteria->addFilter(new EqualsFilter('mail_template.salesChannels.salesChannel.id', $order->getSalesChannelId()));
/** #var MailTemplateEntity|null $mailTemplate */
$mailTemplate = $this->mailTemplateRepository->search($criteria, $this->context)->first();
// Fallback if no template for the saleschannel is found
if (null === $mailTemplate) {
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('mailTemplateTypeId', self::ORDER_CONFIRMATION_MAIL_TYPE_ID));
$criteria->addAssociation('media.media');
$criteria->setLimit(1);
/** #var MailTemplateEntity|null $mailTemplate */
$mailTemplate = $this->mailTemplateRepository->search($criteria, $this->context)->first();
}
} else {
/** #var MailTemplateEntity|null $mailTemplate */
$mailTemplate = $this->mailTemplateRepository->search($criteria, $this->context)->first();
}
if (null === $mailTemplate) {
return;
}
$data = new DataBag();
$data->set('recipients', [
$order->getOrderCustomer()->getEmail() => $order->getOrderCustomer()->getFirstName() . ' ' . $order->getOrderCustomer()->getLastName(),
]);
$data->set('senderName', $mailTemplate->getTranslation('senderName'));
$data->set('salesChannelId', $order->getSalesChannelId());
$data->set('templateId', $mailTemplate->getId());
$data->set('customFields', $mailTemplate->getCustomFields());
$data->set('contentHtml', $mailTemplate->getTranslation('contentHtml'));
$data->set('contentPlain', $mailTemplate->getTranslation('contentPlain'));
$data->set('subject', $mailTemplate->getTranslation('subject'));
$data->set('mediaIds', []);
$attachments = [];
if (null !== $mailTemplate->getMedia()) {
foreach ($mailTemplate->getMedia() as $mailTemplateMedia) {
if (null === $mailTemplateMedia->getMedia()) {
continue;
}
if (null !== $mailTemplateMedia->getLanguageId() && $mailTemplateMedia->getLanguageId() !== $order->getLanguageId()) {
continue;
}
$attachments[] = $this->mediaService->getAttachment(
$mailTemplateMedia->getMedia(),
$this->context
);
}
}
if (!empty($attachments)) {
$data->set('binAttachments', $attachments);
}
try {
if (
null === $this->mailService->send(
$data->all(),
$this->context,
$this->getTemplateData($order)
)
) {
throw new \Exception('Could not render mail template');
}
$this->orderMailRepository->upsert([[
'orderId' => $order->getId(),
'confirmationSend' => true,
]], $this->context);
} catch (\Exception $e) {
$this->logger->error(sprintf(
'Could not send mail for order %s: %s',
$order->getOrderNumber(),
$e->getMessage()
));
}
}

Why I can not disable SSLv3?

I have disabled sslv3 in server side like this :
char certPass[] = "***";
char certAliaMainPass[] = "***";;
KeyStore keyStore = KeyStore.getInstance("JKS");
keyStore.load(new FileInputStream(certPath), certPass);
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
keyManagerFactory.init(keyStore, certAliaMainPass);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagerFactory.getKeyManagers(), null, null);
SSLServerSocketFactory sslServerSocketFactory = sslContext.getServerSocketFactory();
sslServerSocket = (SSLServerSocket) sslServerSocketFactory.createServerSocket(iPort);
String[] protocols = sslServerSocket.getEnabledProtocols();
Set<String> set = new HashSet<String>();
for (String s : protocols) {
if (s.equals("SSLv3")) {
continue;
}
set.add(s);
}
sslServerSocket.setEnabledProtocols(set.toArray(new String[0]));
but client which used "SSLv3" still can connect to server, how can I do for this issue?
Go to Java installation folder.
Open {JRE_HOME}\lib\security\java.security -file in text editor.
Go to the last line.
Delete or comment out the following line jdk.tls.disabledAlgorithms=SSLv3

Wrap conditional into a function or not represent it at all in a sequence diagram?

I've this PHP controller class (belongs to Symfony2 bundle):
class ReptoolController extends PageController
{
// ...
private function _get($request, $action, $case)
{
$app_id = $this->getRequested('app_id');
if( ( $repappConfig = $this->getDoctrine()->getRepository('PDOneBundle:RepappConfig')->findOneBy(array("app_id"=>$app_id))) )
{
$current_timestamp = new \DateTime(date('Y-m-d'));
if($repappConfig->getExpireDate())
$expire_date = $repappConfig->getExpireDate()->getTimestamp();
else
{
$temp = $current_timestamp;
$temp->modify("+7 day");
$temp->format("Y-m-d");
$expire_date = $temp->getTimestamp();
}
if($expire_date < $current_timestamp->getTimestamp())
{
$response = new \stdClass();
$response->status = FormUtilities::RESPONSE_STATUS_BAD;
$controller_response = new Response( json_encode($response) );
$controller_response->headers->set('Content-Type', 'application/json; charset=utf-8');
return $controller_response;
}
}
switch($case)
{
// ...
case FormUtilities::CASE_BRAND_CUSTOM_MESSAGES:
return $this->getBrandCustomMessages($request, $action, $case);
break;
// ...
default:
$response = new \stdClass();
$response->status = FormUtilities::RESPONSE_STATUS_BAD;
$controller_response = new Response( json_encode($response) );
$controller_response->headers->set('Content-Type', 'application/json; charset=utf-8');
return $controller_response;
break;
}
}
// ...
private function getBrandCustomMessages($request, $action, $case)
{
$id = $this->getRequested('app_id');
$reptool_records = $this->getRequestedSync();
$response = new \stdClass();
$response->status = FormUtilities::RESPONSE_STATUS_BAD;
$repappConfig = new RepappConfig();
$repappConfig = $this->getDoctrine()->getRepository('PDOneBundle:RepappConfig')->findOneBy(array("app_id"=>$id));
$project_id = $repappConfig->getProjectId();
$brand_table = $this->getDoctrine()->getRepository('PDOneBundle:Brand')->findBy(array("project"=>$project_id));
if($brand_table)
{
foreach($brand_table as $bt)
{
$brand_id = $bt->getID();
$brandCustomMessages = new BrandCustomMessages();
if( $brandCustomMessages = $this->getDoctrine()->getRepository('PDOneBundle:BrandCustomMessages')->findBy(array("brand_id"=>$brand_id) ))
{
$sync = array();
foreach ($brandCustomMessages as $brandCustomMessage)
{
$response->status = FormUtilities::RESPONSE_STATUS_VALID;
$brandCustMess = new PDOneResponse(
$brandCustomMessage->getID(),
strtotime($brandCustomMessage->getModifiedAt()->format("Y-m-d H:i:s"))
);
$brandCustMess->id = $brandCustomMessage->getID();
$brandCustMess->message_text = $brandCustomMessage->getMessageText();
$brandCustMess->message_code = $brandCustomMessage->getMessageCode();
$brandCustMess->brand_id = (int)$brandCustomMessage->getBrandId();
$reptool_records = $brandCustMess->setRecordStatus($reptool_records);
// ADD BACKEND RECORD TO SYNC
if($brandCustMess->status != FormUtilities::RESPONSE_STATUS_OK ) $sync[] = $brandCustMess;
}
// COMPOSITE SYNC WITH REPTOOL RECORDS
$sync = PDOneResponse::compositeSync($sync, $reptool_records);
$response->sync = $sync;
}
}
}
$controller_response = new Response( json_encode($response) );
$controller_response->headers->set('Content-Type', 'application/json; charset=utf-8');
return $controller_response;
}
// ...
I need to build a sequence diagram (SD) for the flow from the actor PDOneApp (which is an iPad application making get|set request to that controller). This is what I have done in the SD Version1, SD Version 2:
Version 1
Version 2
About the diagrams shown above I have the following doubts and taking as example the code shown above also:
Which diagram is the right one?
The calls (meaning the representation at diagram) for the entities: RepappConfig and Brand are right? In the code this calls are made from within the method getBrandCustomMessages() and I have them directly from the controller ReptoolController which makes me think that's wrong. If this is the case how they should be represented?
I know that SD is not mean to be for represent code as it is but, how do you represent conditionals on the function? Perhaps I can wrap the conditional in a function and call that function from within the getBrandCustomMessages() is this one the right way? What did you recommend me on this doubt?
As you will see on the function the last return is a Response object, is that part right on the diagram? Or the line should be dashed with return label?
Can any give some help for finish this diagram and understand the conditional part for UML SD?
Your 2nd diagram shows the internal call to getBrandCustomMessages correctly.
If you really want to show if then use fragments (http://www.uml-diagrams.org/sequence-diagrams-combined-fragment.html). You can divide fragments into single partitions (if/else or case; whatever fits).
The last response should not go to an entity but as return message (from after the internal call) to the actor. That's likely what you intend to show.

Windows + SetCommState how to set RTS?

I have an application that has been ported up from VC++ 6 where it worked fine. The code in question uses the WinAPI for a serial device driver. When ported to VS2012, the same code behaves rather differently.
Create a DCB, set SetCommState and go. CTS was set high, RTS was set high and you were on your way.
Since ported up to VS2012 Pro MFC, I am finding that it sets up SetCommState the same with or without hardware flow control:
memset(&dcb, 0x00, sizeof(dcb));
dcb.DCBlength = sizeof(DCB);
// Must be TRUE, only binary mode in Windows
dcb.fBinary = TRUE;
dcb.fParity = FALSE;
// XOn/XOff disabled
dcb.fTXContinueOnXoff = TRUE;
dcb.fOutX = FALSE;
dcb.fInX = FALSE;
dcb.XonLim = 0;
dcb.XoffLim = 0;
dcb.XonChar = 0;
dcb.XoffChar = 0;
// Misc Stuff
dcb.EofChar = 0;
dcb.EvtChar = 0;
dcb.ErrorChar = 0;
dcb.fErrorChar = FALSE;
dcb.fNull = FALSE;
dcb.fAbortOnError = FALSE;
// 8N1 Setup
dcb.ByteSize = 8;
dcb.Parity = NOPARITY;
dcb.StopBits = ONESTOPBIT;
// Baud Rate
if (dwBaudRate == BAUD_115200)
{
dcb.BaudRate = CBR_115200;
}
else
{
dcb.BaudRate = CBR_38400;
}
// setup hardware flow control
if (bHardware == eYesHardwareFlowControl)
{
// ================ FLOW CONTROL ON ================
switch (bIgnoreCTS)
{
case eIgnoreCTS:
dcb.fOutxCtsFlow = FALSE;
break;
case eEnableCTS:
dcb.fOutxCtsFlow = TRUE;
break;
default:
case eCTSDecideLater:
dcb.fOutxCtsFlow = TRUE;
break;
}
// DSR Flow Control
dcb.fDsrSensitivity = FALSE;
dcb.fOutxDsrFlow = FALSE;
// <<Hardware flow control On(TRUE) Off(FALSE)>>
dcb.fDtrControl = DTR_CONTROL_ENABLE;
// <<Hardware flow control On(_HANDSHAKE) Off(_ENBLE)>>
dcb.fRtsControl = RTS_CONTROL_HANDSHAKE;
}
else
{
// ================ FLOW CONTROL OFF ================
switch (bIgnoreCTS)
{
case eIgnoreCTS:
dcb.fOutxCtsFlow = FALSE;
break;
case eEnableCTS:
dcb.fOutxCtsFlow = TRUE;
break;
default:
case eCTSDecideLater:
dcb.fOutxCtsFlow = FALSE;
break;
}
// DSR Flow Control
dcb.fDsrSensitivity = FALSE;
dcb.fOutxDsrFlow = FALSE;
dcb.fDtrControl = DTR_CONTROL_ENABLE;
dcb.fRtsControl = RTS_CONTROL_ENABLE;
}
if (SetCommState(m_hIdComDev, &dcb) == WINDOWS_API_ZERO_IS_BAD)
{
dwLastError = GetLastError();
}
At this point, I have set up the DCB, cleared it. I don't read in the previous state is I don't want to trust any garbage of anyone that previously used the port. Then I set up every field with Baud, Flow Control and CTS Ignore being the only optional items.
So, what I've noticed is that I can create situation where the Device and the PC don't communicate. Now, mind you, they always did before and they always work with Hyperterminal, ProComm, TeraTerm and so on. What I can see is that when these comm programs start (and the old VC++ 6 App), when the device is created and set up, RTS is immediately set high.
Now, my App, once the DCB is set up, SetCommState called; RTS is always LOW. And when this happens, communications is toast.
I want to FORCE RTS to be high and thought I could do it like this:
if (EscapeCommFunction(m_hIdComDev, CLRRTS) == WINDOWS_API_ZERO_IS_BAD)
{
dwLastError = GetLastError();
}
if (EscapeCommFunction(m_hIdComDev, SETRTS) == WINDOWS_API_ZERO_IS_BAD)
{
dwLastError = GetLastError();
}
But this fails, it gives an error 87 (parameter error). And I cannot quite figure it out. Even if I just SETRTS high only, it fails.
Any ideas on how I can force Windows to set RTS high after I setup the comm parameters in the DCB?
Well it turned out to be a device problem and not a windows problem at all.

Check if MOSS resource exists generating unexpected 401's

I have a webdav function listed below:
The behavior is completely unexpected....
When I first run the function and pass a URL to a resource (folder in sharepoint) that does not exist, I get a 404 which is expected. I then use another function to create the resource using THE SAME credentials as in this method. No problems yet...
However on 2nd run, after the resource has been created - when I check if resource exists, now I get a 401.
Whats important to note here is that the same credentials are used to check for 401 and create folder, so clearly the credentials are fine...
So it must be something else.... All I want to do is check if a resource exists in SharePoint.... any ideas how to improve this function? Or any theory as to why its giving this 401...
private bool MossResourceExists(string url)
{
var request = (HttpWebRequest)WebRequest.Create(url);
request.Method = "HEAD";
// Create a new CredentialCache object and fill it with the network
// credentials required to access the server.
var myCredentialCache = new CredentialCache();
if (!string.IsNullOrEmpty(this.Domain ))
{
myCredentialCache.Add(new Uri(url),
"NTLM",
new NetworkCredential(this.Username , this.Password , this.Domain )
);
}
else
{
myCredentialCache.Add(new Uri(url),
"NTLM",
new NetworkCredential(this.Username , this.Password )
);
}
request.Credentials = myCredentialCache;
try
{
request.GetResponse();
return true;
}
catch (WebException ex)
{
var errorResponse = ex.Response as HttpWebResponse;
if (errorResponse != null)
if (errorResponse.StatusCode == HttpStatusCode.NotFound)
{
return false;
}
else
{
throw new Exception("Error checking if URL exists:" + url + ";Status Code:" + errorResponse.StatusCode + ";Error Message:" + ex.Message ) ;
}
}
return true;
}
The only clue I have is that when using http://mysite.com/mydoclib/mytoplevelfolder it works.... any sub folders automatically give 401's....
The thing is that you can't pass the whole url that includes folders to the CredentialCache.Add() method.
For example:
http://MyHost/DocumentLibrary/folder1/folder2 will not work as an Uri to the Add() method, but
http://MyHost/DocumentLibrary/ will work.
I would guess that the lack of permissioning capabilities on folder level in SharePoint is the reason for this. Or the way that otherwise SharePoint handles folders.
What you can do is to separate the parameters in your method to accept a base url (including document libraries / lists) and a folder name parameter.
The CredentialCache gets the base url and the request object gets the full url.
Another way is to use the
request.Credentials = System.Net.CredentialCache.DefaultCredentials;
credentials instead. And, if necessary, do an impersonation if you want to use another account than the executing one.
A third variation is to try with authentication type set to Kerberos instead of NTLM.
Here is my test code. I am able to reproduce the problem if I replace the problem with your code, and this code works for me.
static void Main(string[] args)
{
bool result = MossResourceExists("http://intranet/subtest/content_documents/", "testfolder/testfolder2");
}
private static bool MossResourceExists(string baseUrl, string folder)
{
string completeUrl = baseUrl + folder;
var request = (HttpWebRequest)WebRequest.Create(completeUrl);
request.Method = "HEAD";
// Create a new CredentialCache object and fill it with the network
// credentials required to access the server.
var myCredentialCache = new CredentialCache();
if (!string.IsNullOrEmpty(Domain))
{
myCredentialCache.Add(new Uri(baseUrl),
"NTLM",
new NetworkCredential(Username, Password, Domain)
);
}
else
{
myCredentialCache.Add(new Uri(baseUrl),
"NTLM",
new NetworkCredential(Username, Password)
);
}
request.Credentials = myCredentialCache;
//request.Credentials = System.Net.CredentialCache.DefaultCredentials;
try
{
WebResponse response = request.GetResponse();
return true;
}
catch (WebException ex)
{
var errorResponse = ex.Response as HttpWebResponse;
if (errorResponse != null)
if (errorResponse.StatusCode == HttpStatusCode.NotFound)
{
return false;
}
else
{
throw new Exception("Error checking if URL exists:" + completeUrl + ";Status Code:" + errorResponse.StatusCode + ";Error Message:" + ex.Message);
}
}
return true;
}
Hope this helps.

Resources