two question about phpMailer security when hosting my app? - php

Currently I am using phpMailer for sending emails to my Gmail account in a form submition. the code that I used for sending email is similar to the below code:
###################
/* sendeng email */
###################
use phpMailer\PHPMailer\PHPMailer;
if ($sehat === true) {
require_once "../phpMailer/PHPMailer.php";
require_once "../phpMailer/SMTP.php";
require_once "../phpMailer/Exception.php";
$mail = new PHPMailer();
//smtp settings
$mail->isSMTP();
$mail->Host = "smtp.gmail.com";
$mail->SMTPAuth = true;
$mail->Username = "myGmail#gmail.com";
$mail->Password = 'myPassword';
$mail->Port = 465;
$mail->SMTPSecure = "ssl";
//email settings
$mail->isHTML(true);
$mail->setFrom($commEmail, $commName);
$mail->addAddress("myGmail#gmail.com");
$mail->Subject = ("$commEmail ($commTopic)");
$mail->Body = "<div style='text-align:right; direction:rtl;'>" . nl2br(strip_tags($commMess)) . "</div>";
// $mail->Body = nl2br(strip_tags($commMess));
// $mail->AltBody = nl2br(strip_tags($commMess));
// $mail->Body = $commMess;
/* for other language messages */
$mail->CharSet = 'UTF-8';
if($mail->send()){
$status = "success";
$response = "Email is sent!";
}
else
{
$status = "failed";
$response = "Something is wrong: <br>" . $mail->ErrorInfo;
}
exit(json_encode(array("status" => $status, "response" => $response)));
}
I don't have any problem with sending email. But my first question is where I am using $mail->Password = 'myPassword'; in my code. Actually I am writing and debugging the code on a localhost (WAMPSERVER), and I used my real password instead of myPassword in the code. But after finishing the app I am going to host it to a real server (deploy my app). My question is that with this code, could host provider access to my Gmail password? And if so what is the solution to that? Is it a bug in phpMailer or I am wrong?
The second question is that when I want to send form data to my Gmail account, I must change the setting of my Gmail to "lower security" in this localhost version. If I deploy my app and it becomes available online, again I must do that (lower security of Gmail) or there are better ways? Because now when I return Gmail security setting to normal state the phpMailer does not send data.

My question is that with this code, could host provider access to my Gmail password?
Yes.
And if so what is the solution to that?
Don't use a host you don't trust.

In general you need to be able to trust your host, but there is one step that can help avoid all that this implies: using XOAUTH2 for authentication.
With this mechanism you do not have to store a real ID and password on your server; you need to use them in a one-off operation to obtain a token that can be limited to gmail operations. There are code examples provided with PHPMailer for this, along with a utility script you can use to obtain your auth token, and a wiki article to help you configure it (though it could use updating; contributions welcome). However, be aware that using OAuth is generally a complicated and unpleasant experience that contains many potential ways to mess things up. In this use case, it does provide a security enhancement as it means you don't have to leave your real google credentials lying around.
A scrupulous hosting provider will allow you to encrypt your VM's disk images in a way that means they can't read your data from the hypervisor, and if they don't also have SSH access to your instance, your data should be fairly well protected from them.

Related

SMTP connect() failed Office 365 OVH [duplicate]

I am attempting to set up PHPMailer so that one of our clients is able to have the automatically generated emails come from their own account. I have logged into their Office 365 account, and found that the required settings for PHPMailer are:
Host: smtp.office365.com
Port: 587
Auth: tls
I have applied these settings to PHPMailer, however no email gets sent (The function I call works fine for our own mail, which is sent from an external server (Not the server serving the web pages)).
"host" => "smtp.office365.com",
"port" => 587,
"auth" => true,
"secure" => "tls",
"username" => "clientemail#office365.com",
"password" => "clientpass",
"to" => "myemail",
"from" => "clientemail#office365.com",
"fromname" => "clientname",
"subject" => $subject,
"body" => $body,
"altbody" => $body,
"message" => "",
"debug" => false
Does anyone know what settings are required to get PHPMailer to send via smtp.office365.com?
#nitin's code was not working for me, as it was missing 'tls' in the SMTPSecure param.
Here is a working version. I've also added two commented out lines, which you can use in case something is not working.
<?php
require 'vendor/phpmailer/phpmailer/PHPMailerAutoload.php';
$mail = new PHPMailer(true);
$mail->isSMTP();
$mail->Host = 'smtp.office365.com';
$mail->Port = 587;
$mail->SMTPSecure = 'tls';
$mail->SMTPAuth = true;
$mail->Username = 'somebody#somewhere.com';
$mail->Password = 'YourPassword';
$mail->SetFrom('somebody#somewhere.com', 'FromEmail');
$mail->addAddress('recipient#domain.com', 'ToEmail');
//$mail->SMTPDebug = 3;
//$mail->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";}; //$mail->Debugoutput = 'echo';
$mail->IsHTML(true);
$mail->Subject = 'Here is the subject';
$mail->Body = 'This is the HTML message body <b>in bold!</b>';
$mail->AltBody = 'This is the body in plain text for non-HTML mail clients';
if(!$mail->send()) {
echo 'Message could not be sent.';
echo 'Mailer Error: ' . $mail->ErrorInfo;
} else {
echo 'Message has been sent';
}
UPDATE: May 2022
So i was struggling at this Problem really hard. For Business Accounts with Exchange Online and access to the Microsoft Admin Center i can provide the answer for this.
TLDR: Goto the Admin Center and select the User you want to send the Mail. Then look under settings after E-Mail and E-Mail Apps after the Setting "authenticated SMTP", simply enable it.
Still not working? I got you covered, here is how i got it fully working.
Use PHP composer, saves a lot of work actually.
Replace your Code with my Code and change it after test
<?php
//Import the PHPMailer class into the global namespace
use PHPMailer\PHPMailer\PHPMailer; //important, on php files with more php stuff move it to the top
use PHPMailer\PHPMailer\SMTP; //important, on php files with more php stuff move it to the top
//SMTP needs accurate times, and the PHP time zone MUST be set
//This should be done in your php.ini, but this is how to do it if you don't have access to that
date_default_timezone_set('Etc/UTC');
require 'path/to/vendor/autoload.php'; //important
//Enable SMTP debugging
// SMTP::DEBUG_OFF = off (for production use)
// SMTP::DEBUG_CLIENT = client messages
// SMTP::DEBUG_SERVER = client and server messages
//$mail->SMTPDebug = SMTP::DEBUG_off;
//SMTP
$mail = new PHPMailer(true); //important
$mail->CharSet = 'UTF-8'; //not important
$mail->isSMTP(); //important
$mail->Host = 'smtp.office365.com'; //important
$mail->Port = 587; //important
$mail->SMTPSecure = 'tls'; //important
$mail->SMTPAuth = true; //important, your IP get banned if not using this
//Auth
$mail->Username = 'yourname#mail.org';
$mail->Password = 'your APP password';//Steps mentioned in last are to create App password
//Set who the message is to be sent from, you need permission to that email as 'send as'
$mail->SetFrom('hosting#mail.org', 'Hosting Group Inc.'); //you need "send to" permission on that account, if dont use yourname#mail.org
//Set an alternative reply-to address
$mail->addReplyTo('no-reply#mail.com', 'First Last');
//Set who the message is to be sent to
$mail->addAddress('customer#othermail.com', 'SIMON MÜLLER');
//Set the subject line
$mail->Subject = 'PHPMailer SMTP test';
//Read an HTML message body from an external file, convert referenced images to embedded,
//convert HTML into a basic plain-text alternative body
$mail->msgHTML(file_get_contents('replace-with-file.html'), __DIR__); //you can also use $mail->Body = "</p>This is a <b>body</b> message in html</p>"
//Replace the plain text body with one created manually
$mail->AltBody = 'This is a plain-text message body';
//Attach an image file
//$mail->addAttachment('../../../images/phpmailer_mini.png');
//send the message, check for errors
if (!$mail->send()) {
echo 'Mailer Error: ' . $mail->ErrorInfo;
} else {
}
This may looks like your file, whats ok, but now comes the easy-tricky part. As like as Google, Microsoft implemented a "switch" for the SMTP stuff. Simply go to your Admin Center from your Business Account, or kindly ask someone with permission to do that part:
navigate to https://admin.microsoft.com/AdminPortal/Home#/users
select to user where you want to send the email from
under the tab "E-Mail" search for "E-Mail-Apps", click on "manage E-Mail-Apps"
here you can select "authenticated SMTP", make sure that option is checked and save the Changes
if you use MFA, then make sure you use an app password as mentioned in https://stackoverflow.com/a/61359150/14148981
Run the script
I hope this helps someone. Took me hell long to find this option on myself.
Summary of all steps to get App password and authentication ON:
With Admin Account:
Active Directory AD -> Properties -> Security Default: Turn OFF.
Active directory Portal -> conditional access -> Configure MFA Trusted IPs -> Allow user App password: Enable
Admin Page User List -> 'Multi factor Authentication' for target user: Enable then Enforce
Admin Page User List -> User details -> Mail -> 'Manage Email Apps' -> 'Authenticated SMTP': Enable
With user account:
User Account Profile -> Security -> add login method: App Password
PHP Mailer Settings:
smtp.office365.com, 587, tls, email, appPassword
UPDATE: April 2020
Using the accepted answer for sending email using Office 365 has the high chance of not working since Microsoft is pushing for their Microsoft Graph (the only supported PHP framework right now is Laravel). If fortunately you were still able to make it work in your application, email will either go to the recipient's Junk, Trash, or Spam folder, which you don't want to happen.
Common errors I encountered were:
Failed to authenticate password. // REALLY FRUSTRATED WITH THIS ERROR! WHY IS MY PASSWORD WRONG?!
or
Failed to send AUTH LOGIN command.
or
Unable to send email using PHP SMTP. Your server might not be configured to send mail using this method.
In order to still make it work with the accepted answer, we just have to change a single line, which is the Password parameter line:
$mail->Password = 'YourOffice365Password';
Instead of setting the password with the one you use when you login to your Office365 account, you have to use an App Password instead.
Create App Password
First, in order to create an App Password, the Multi-Factor Authentication of your Office 365 account should be enabled (you may have to contact your administrator for this to be enabled).
After that, login your Office 365 in your favorite browser
Go to My Account page (you will see the link to this page when you click your name's initials on the upper right)
Choose Security & Privacy then Additional security verification
At the top of the page, choose App Passwords
Choose create to get an app password
If prompted, type a name for your app password, and click Next
You will then see the password generated by Office 365 as your App Password
Copy the password
After copying the password, go back to your working code and replace the Password parameter with the copied password. Your application should now be able to properly send email using Office 365.
Reference:
Create an app password for Microsoft 365
Try this, it works fine for me, i have been using this for so long
$mail = new PHPMailer(true);
$mail->Host = "smtp.office365.com";
$mail->Port = 587;
$mail->SMTPSecure = '';
$mail->SMTPAuth = true;
$mail->Username = "email";
$mail->Password = "password";
$mail->SetFrom('email', 'Name');
$mail->addReplyTo('email', 'Name');
$mail->SMTPDebug = 2;
$mail->IsHTML(true);
$mail->MsgHTML($message);
$mail->Send();
I had the same issue when we moved from Gmail to Office365.
You MUST set up a connector first (either an open SMTP relay or Client Send). Read this and it will tell you everything you need to know about allowing Office365 to send email:
https://technet.microsoft.com/en-us/library/dn554323.aspx
Update: Dec 2020
I resolved the problem by NOT setting the App Password and MFA(Disable it!).
I did it by disabling MS security default at
Microsoft 365 admin center
Azure Active Directory admin center
Azure Active Directory
Properties
Manage Security defaults
Enable Security defaults
No
When it's set, send email with your Office365 username(Email Address) and password (Not App Password)
I was facing this error during configuration of PHPMailer 5.2 stable with outlook SMTP. I found this:
SMTP ERROR:
Password command failed, Authentication unsuccessful, SmtpClientAuthentication is disabled for the Tenant.
SMTP connect() failed.
After some research I have resolved this issue by enabling the Authenticated SMTP
Use the Microsoft 365 admin center to enable or disable SMTP AUTH on specific mailboxes
Open the Microsoft 365 admin center and go to Users > Active users.
Select the user, and in the flyout that appears, click Mail.
In the Email apps section, click Manage email apps.
Verify the Authenticated SMTP setting: unchecked = disabled, checked = enabled.
When you're finished, click Save changes.
I modified the example in the current PHPmailer version 6.5.4 to allow for Microsoft 365 non auth TLS connection (ie you do not need to use an license user account but can use a shared mailbox as the sender)
$mail = new PHPMailer(true);
try {
//Enable SMTP debugging
// $mail->SMTPDebug = 3; // connection, client and server level debug!
//SMTP
$mail->CharSet = 'UTF-8';
$mail->isSMTP();
$mail->Host = 'domain-xyz.mail.protection.outlook.com';
$mail->Port = 25;
$mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS; //Enable implicit TLS encryption
$mail->SMTPAuth = false;
// as advised OPTION #3 on this page
// https://learn.microsoft.com/en-us/exchange/mail-flow-best-practices/how-to-set-up-a-multifunction-device-or-application-to-send-email-using-microsoft-365-or-office-365
//Auth
$mail->Username = 'website#domain.xyz';
$mail->Password = 'yourpassword'; // set SMTPAuth to false though right so this is not used?
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//you need permission to that email as 'send as'
$mail->SetFrom('website#website#domain.xyz', 'Website');
$mail->addReplyTo('website#website#domain.xyz', 'Website');
// Sending to a external recipient
$mail->addAddress('external#domain.test', 'John doe');
$mail->Subject = 'PHPMailer TESTY TEST';
$mail->isHTML(true);
$mail->Body = "</p>This is a <b>body</b> message in html</p>";
//Replace the plain text body with one created manually
$mail->AltBody = 'This is a plain-text message body';
//Attach an image file
$mail->addAttachment('img/logo.png');
//send the message, check for errors
$mail->send();
echo 'Message has been sent';
} catch (Exception $e) {
echo "Message could not be sent. Mailer Error: {$mail->ErrorInfo}";
}
Update september 2021
As of September 2022 (so from now on only a year) Microsoft will deprecating and shut down what they call basic authentication. That includes, but is not limited to, SMTP. They later announced that tenant administrators will be able to re-enable SMTP_AUTH, but (in my opinion) it opens up the door for permanently disabling it in the feature.
For now, Microsoft is not disabling it, but they encourage you to authenticate on different ways. On the documentation site they placed a warning telling you this.
Today I will do some research on how to use this new methods. For my application (PHP, CodeIgniter) I found this Microsoft package. I'm going to try sending my mails using this package, I hope this will be a smooth experience, but I'm afraid it will be a hell of a ride... I'll keep you all posted.
You need an App Password and multi-factor authentication enabled to able to send mails from third party applications using office365 stmp. Google it for further information

PHPMailer Not sending Mails To Some Gmail Accounts

I'm using PHPMailer (Version 5) for user registration. (When user registers to my site the Profile activation code is sent to user to activate it).
PHPMailer works, I tested it many times (I registered Myself with other mails and with gmail too for testing purposes, I always got the activation code), but many users complain that they not getting the activation codes and then I have to send them manually...
I can't understand what is the problem (When I checked my users database there are many users that got activation codes, but also that couldn't received).. I debug PHPMailer, but there is not any error or problem...
I'm Using PHPMailer With Gmail SMTP:
$mail = new PHPMailer();
$mail->CharSet = 'UTF-8';
$mail->Host = "smtp.gmail.com";
$mail->Port = 587;
$mail->SMTPSecure = "tls";
$mail->SMTPAuth = true;
$mail->Username = "mymail#gmail.com";
$mail->Password = "MyPassword";
$mail->From = "mymail#gmail.com";
$mail->FromName = 'www.mysite.com';
$mail->AddAddress($email);
$mail->WordWrap = 80;
$mail->IsHTML(true);
$mail->Subject = 'Registration';
$mail->Body = $message;
$mail->AltBody = $message;
$mail->Send();
I also tried to use SSL-465, but the result is the same..
Please Help!
Thank you very much in advanced...
I Solved this problem from server (host). I checked mail functions / configs and found "MAX_EMAIL_PER_HOUR" was set to 30 (Really strange, by default It is 100) So I changed it to 500 and It fixed all PHPMailer Issues...
In addition:
Enabling "Allow less secure apps" will usually solve many problem for PHPMailer, and it does not really make your app significantly less secure. (When enabling Google warns you that the App is insecure).
If you Need More security, PHPMailer added support for XOAUTH2 for google, you can use it...
I haven't used this technology before, but I'll play with it as soon as possible ;)
With Best Wishes!

Google SMTP Error: Could not authenticate

The following code does not work once i put it on the server, I have similar code working on my local environment and it works perfectly, any ideas?
edit: i did set my gmail setting to "less secure"
<?php
$setid = $_POST['setid'];
$promo = $_POST['promo'];
echo "Good Sir, your set ID is ".$setid.", and you are eligible for the following deal:";
echo "<br><br>";
echo $promo;
$message= "Good Sir, your set ID is ".$setid.", and you are eligible for the following deal:"."<br><br>".$promo;
require "phpmailer/class.phpmailer.php";
// Instantiate Class
$mail = new PHPMailer();
// Set up SMTP
$mail->IsSMTP();
$mail->SMTPAuth = true;
$mail->SMTPSecure = "ssl";
$mail->Host = "smtp.gmail.com";
$mail->Port = 465;
$mail->Encoding = '7bit';
// Authentication
$mail->Username = "xxx#example.com";
$mail->Password = "mypass";
// Compose
$mail->SetFrom("jghh#ghh.ca");
$mail->AddReplyTo("ghh#ghh.ca");
$mail->Subject = "TryIt";
$mail->MsgHTML($message);
// Send To
$mail->AddAddress("receiver#hotmail.com", "Recipient Name");
$result = $mail->Send();
$message = $result ? 'Successfully Sent!' : 'Sending Failed!';
unset($mail);
?>
This is what I get in Network -> Preview:
Good Sir, your set ID is 100065, and you are eligible for the following deal:
Current Promotion: Enjoy 15% discount on your next visit!SMTP Error: Could not authenticate.
You probably need to enable less secure apps
Change account access for less secure apps
To help keep Google Apps users' accounts secure, we may block less
secure apps from accessing Google Apps accounts. As a Google Apps
user, you will see a "Password incorrect" error when trying to sign
in. If this is the case, you have two options:
Option 1: Upgrade to a more secure app that uses the most up to date
security measures. All Google products, like Gmail, use the latest
security measures.
Option 2: Change your settings to allow less secure apps to access
your account. We don't recommend this option because it might make
it easier for someone to break into your account. If you want to
allow access anyway, follow these steps:
2.1. Go to the "Less secure apps" section in My Account
2.2. Next to "Access for less secure apps," select Turn on. (Note to
Google Apps users: This setting is hidden if your administrator has locked less secure app account access.)
If you still can't sign in to your account, the "password
incorrect" error might be caused by a different reason.
SRC: https://support.google.com/accounts/answer/6010255?hl=en
UPDATE:
Add error reporting to the top of your file(s) right after your opening PHP tag for example <?php error_reporting(E_ALL); ini_set('display_errors', 1);
and enable debug on PHPMAILER
$mail->SMTPDebug = 1; // enables SMTP debug information (for testing)
// 1 = errors and messages
// 2 = messages only
to see if it yields anything.

PHPMailer: Dynamic gmail forwarding

I do not know if what I want to do is possible (but finding out that it isn't would be useful in itself).
I cannot use my company's gmail account "real.business#gmail.com" directly with PHPMailer. I can, however, use an intermediary gmail account "fake.12345.account#gmail.com" which can have "less secure apps" enabled, which permits SMTP verification.
However I do not want to have the emails be sent from this fake.12345.account#gmail.com account (wouldn't look particularly professional) - but rather the company's gmail account.
I can send the emails from the intermediary account to real.business#gmail.com; either through the editing of the PHPMailer parameters, or by automatically forwarding emails from fake.12345.account#gmail.com to the company account.
The problem lies in how real.business#gmail.com can then successfully email the email (or at least appear to be the sender), as originally intended.
The code so far
$Mail = new PHPMailer();
$Mail->IsSMTP(); // Use SMTP
$Mail->Host = "smtp.gmail.com"; // Sets SMTP server for gmail
$Mail->SMTPDebug = 0; // 2 to enable SMTP debug information
$Mail->SMTPAuth = TRUE; // enable SMTP authentication
$Mail->SMTPSecure = "tls"; //Secure conection
$Mail->Port = 587; // set the SMTP port to gmail's port
$Mail->Username = 'fake.12345.account#gmail.com'; // gmail account username
$Mail->Password = 'a_password'; // gmail account password
$Mail->Priority = 1; // Highest priority - Email priority (1 = High, 3 = Normal, 5 = low)
$Mail->CharSet = 'UTF-8';
$Mail->Encoding = '8bit';
$Mail->Subject = 'Mail test';
$Mail->ContentType = 'text/html; charset=utf-8\r\n';
$Mail->From = 'testing.num.101#gmail.com'; //Your email adress (Gmail overwrites it anyway)
$Mail->FromName = 'Testing Again';
$Mail->WordWrap = 900; // RFC 2822 Compliant for Max 998 characters per line
$Mail->addAddress($personEmail); // To: the PERSON WE WANT TO EMAIL
$Mail->isHTML( TRUE );
$Mail->Body = ' Good news '.$personName.'! The email sent correctly!';
$Mail->AltBody = 'This is a test mail';
$Mail->Send();
$Mail->SmtpClose();
if(!$Mail->send()) {
echo 'Message could not be sent.';
echo 'Mailer Error: ' . $Mail->ErrorInfo;
exit;
}
So the issue is: not having the email sent to $personEmail from fake.12345.account#gmail.com (that's trivial) but rather how to send the email from fake.12345.account#gmail.com to real.business#gmail.com such that real.business#gmail.com forwards the message to $personEmail
What you're describing is really relaying, which is usually configured in the mail server config (not the messages), but you don't have access to anything like that in gmail.
You can set allowed aliases in gmail, but I would guess that these are not allowed to overlap with existing gmail account names as that would be a major security hole. Why not enable "less secure apps" on the main account? It's not as if it is actually any less secure - if anything it's better, because the setup to use OAuth2 is so deeply complex and unpleasant...
That said, rather than trying to do all this forgery, you may be interested in this PR and associated docs. It's fairly likely the xoauth branch will get merged into master and released without any further changes as PHPMailer 5.2.11, and it would very helpful if you could give it a try.
PHPMailer is made for sending.
What you want to do is forward an email. This implies receiving the email and then sending it through.
What you need is some kind of IMAP client in php, that will allow you to read the emails on fake.12345.account#gmail.com (and maybe real.business#gmail.com). Then save their body and title and pass it to PHPMailer. You can then use PHPMailer to send the emails with real.business#gmail.com.

Email System in PHP using PHPMailer

Hi i am currently implementing an email system for a customer in php. I'm having a bit of trouble in figuring out something. Here's a sample code:
$mail = new PHPMailer(); // create a new object
$mail->IsSMTP(); // enable SMTP
$mail->SMTPDebug = 0; // debugging: 1 = errors and messages, 2 = messages only
$mail->SMTPAuth = true; // authentication enabled
$mail->SMTPSecure = 'ssl'; // secure transfer enabled REQUIRED for Gmail
$mail->Host = 'smtp.gmail.com';
$mail->Port = 465;
$mail->Username = "***missing part***";
$mail->Password = "***missing part***";
$mail->SetFrom($from, $from_name);
$mail->Subject = $subject;
$mail->Body = $body;
$mail->AddAddress($to);
My customer already created a business email account on gmail for this website. My question is should i put this business email and password in these missing parts? Anyone could help me please? Thanks.
whose username and password should i put there?
If you want to use GMail, you need to put in the username and password that belong to the GMail account you want to send the message from.
.... which is why sending E-Mail through GMail is a bit of an imperfect solution IMO - you put your personal Google login data, with which you can access everything you do on Google, into a script on a server. It's not great practice security-wise.
It might be more feasible to create a SMTP account on the client's web site, and use that. That has the additional advantage that you can use a email#clients-domain-name.com sender address.
you must post the username and password so we can help you.. LOL, just kidding. You should put there the username and password of the account that would be sending the email. In this case, probably your webpage's gmail account.
Often web hosts don't use authentication for their web servers to send email using their smtp servers, i would suggest contacting the people who host the website and ask them about using their smtp servers.
Using gmail (if you can from a script) might end up with the rather annoying reply thing that you get from gmail where the reply address is a gmail account.
e.g. from mrKoz#gmail.com on behalf of mr#koz.com which doesn't look awesome :)

Categories