From 439da00e6acdc3fdb5515a8742a0551efdba05a4 Mon Sep 17 00:00:00 2001 From: Pantelis Roditis Date: Mon, 6 May 2024 16:48:19 +0300 Subject: [PATCH 1/8] add mail operation to the player models --- backend/modules/frontend/models/Player.php | 19 ++++++++--- frontend/models/Player.php | 39 ++++++++++++++++++++++ 2 files changed, 54 insertions(+), 4 deletions(-) diff --git a/backend/modules/frontend/models/Player.php b/backend/modules/frontend/models/Player.php index 2234c93d1..3444e26cd 100644 --- a/backend/modules/frontend/models/Player.php +++ b/backend/modules/frontend/models/Player.php @@ -68,17 +68,28 @@ public function saveWithSsl() return false; } - public function mail($content, $subject) + /** + * Send mail to player with + * @param string $subject + * @param string $html + * @param string $txt + * @param array $headers + * @return bool + */ + public function mail($subject, $html, $txt,$headers=[]) { // Get mailer try { - \Yii::$app->mailer->compose() + $message=\Yii::$app->mailer->compose() ->setFrom([\app\modules\settings\models\Sysconfig::findOne('mail_from')->val => \app\modules\settings\models\Sysconfig::findOne('mail_fromName')->val]) ->setTo([$this->email=>$this->username]) ->setSubject($subject) - ->setTextBody($content) - ->send(); + ->setTextBody($txt) + ->setHtmlBody($html); + foreach($headers as $entry) + $message->addHeader($entry[0],$entry[1]); + $message->send(); if (Yii::$app instanceof \yii\web\Application) \Yii::$app->session->setFlash('success', Yii::t('app',"The user has been mailed.")); else { diff --git a/frontend/models/Player.php b/frontend/models/Player.php index b31c03019..ca6db65fa 100644 --- a/frontend/models/Player.php +++ b/frontend/models/Player.php @@ -397,4 +397,43 @@ public function getAcademicIcon() } } + /** + * Send mail to player with + * @param string $subject + * @param string $html + * @param string $txt + * @param array $headers + * @return bool + */ + public function mail($subject, $html, $txt,$headers=[]) + { + // Get mailer + try + { + $message=\Yii::$app->mailer->compose() + ->setFrom([\app\modules\settings\models\Sysconfig::findOne('mail_from')->val => \app\modules\settings\models\Sysconfig::findOne('mail_fromName')->val]) + ->setTo([$this->email=>$this->username]) + ->setSubject($subject) + ->setTextBody($txt) + ->setHtmlBody($html); + foreach($headers as $entry) + $message->addHeader($entry[0],$entry[1]); + $message->send(); + if (Yii::$app instanceof \yii\web\Application) + \Yii::$app->session->setFlash('success', Yii::t('app',"The user has been mailed.")); + else { + echo Yii::t('app',"The user has been mailed.\n"); + } + } + catch(\Exception $e) + { + if (Yii::$app instanceof \yii\web\Application) + \Yii::$app->session->setFlash('notice', Yii::t('app',"Failed to send mail to user.")); + else + echo Yii::t('app',"Failed to send mail to user.\n"); + return false; + } + return true; + } + } From ec7b3c72fe811fce2163f3c74161aee69ec2760a Mon Sep 17 00:00:00 2001 From: Pantelis Roditis Date: Mon, 6 May 2024 16:48:49 +0300 Subject: [PATCH 2/8] make renderPhpContent static so that we can use it from anywhere --- backend/components/BaseController.php | 2 +- frontend/components/echoCTFView.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/components/BaseController.php b/backend/components/BaseController.php index dee6870ae..18296b29c 100644 --- a/backend/components/BaseController.php +++ b/backend/components/BaseController.php @@ -53,7 +53,7 @@ public function init() parent::init(); } - public function renderPhpContent($_content_, $_params_ = []) + public static function renderPhpContent($_content_, $_params_ = []) { $_obInitialLevel_ = ob_get_level(); ob_start(); diff --git a/frontend/components/echoCTFView.php b/frontend/components/echoCTFView.php index a828cd9bc..421467760 100644 --- a/frontend/components/echoCTFView.php +++ b/frontend/components/echoCTFView.php @@ -124,7 +124,7 @@ public function getTwitter_image_height() return ['name'=>'twitter:image:height', 'content'=>$this->_image_height]; } - public function renderPhpContent($_content_, $_params_ = []) + public static function renderPhpContent($_content_, $_params_ = []) { $_obInitialLevel_ = ob_get_level(); ob_start(); From c213126bf372f0072046b9d3593cff9f282383a0 Mon Sep 17 00:00:00 2001 From: Pantelis Roditis Date: Mon, 6 May 2024 17:25:19 +0300 Subject: [PATCH 3/8] change it so that we use the database mail templates --- backend/commands/PlayerController.php | 16 ++---- .../frontend/actions/player/MailAction.php | 52 +++++++++---------- 2 files changed, 30 insertions(+), 38 deletions(-) diff --git a/backend/commands/PlayerController.php b/backend/commands/PlayerController.php index 15987c496..37ce5b601 100644 --- a/backend/commands/PlayerController.php +++ b/backend/commands/PlayerController.php @@ -165,24 +165,18 @@ public function actionMail($active=false, $email=false,$status=9) $this->stdout("Mailing Registered users:\n", Console::BOLD); } $event_name=Sysconfig::findOne('event_name')->val; + $emailtpl=\app\modules\content\models\EmailTemplate::findOne(['name' => 'emailVerify']); + $subject=Yii::t('app', '{event_name} Account approved', ['event_name' => trim(Yii::$app->sys->event_name)]); foreach($players as $player) { // Generate activation URL $activationURL=sprintf("https://%s/verify-email?token=%s",\Yii::$app->sys->offense_domain, $player->verification_token); + $contentHtml = \app\components\BaseController::renderPhpContent("?>" . $emailtpl->html, ['user' => $player,'verifyLink'=>$activationURL]); + $contentTxt = \app\components\BaseController::renderPhpContent("?>" . $emailtpl->txt, ['user' => $player,'verifyLink'=>$activationURL]); $this->stdout($player->email); - $numSend=Yii::$app - ->mailer - ->compose( - ['html' => 'emailVerify-html', 'text' => 'emailVerify-text'], - ['user' => $player,'verifyLink'=>$activationURL] - ) - ->setFrom([Yii::$app->sys->mail_from => Yii::$app->sys->mail_fromName]) - ->setBcc([Yii::$app->sys->mail_from => Yii::$app->sys->mail_fromName]) - ->setTo([$player->email => $player->fullname]) - ->setSubject(trim(Yii::$app->sys->event_name). ' Account approved') - ->send(); + $numSend=intval($player->mail($subject,$contentHtml,$contentTxt)); $this->stdout($numSend ? " Ok\n" : "Not Ok\n"); } } diff --git a/backend/modules/frontend/actions/player/MailAction.php b/backend/modules/frontend/actions/player/MailAction.php index 00d61a407..584886733 100644 --- a/backend/modules/frontend/actions/player/MailAction.php +++ b/backend/modules/frontend/actions/player/MailAction.php @@ -53,13 +53,21 @@ public function run(int $id, $baseURL = "https://echoctf.red/activate/") private function rejectionMail($player) { try { - $this->sendMail( - $player, - 'rejectVerify-html', - 'rejectVerify-text', - Yii::t('app', '{event_name} Account rejected', ['event_name' => trim(Yii::$app->sys->event_name)]) - ); - \Yii::$app->getSession()->setFlash('success', Yii::t('app', 'Player rejection mail send.')); + $emailtpl=\app\modules\content\models\EmailTemplate::findOne(['name' => 'rejectVerify']); + $contentHtml = $this->controller->renderPhpContent("?>" . $emailtpl->html, ['user' => $player]); + $contentTxt = $this->controller->renderPhpContent("?>" . $emailtpl->txt, ['user' => $player]); + $subject=Yii::t('app', '{event_name} Account rejected', ['event_name' => trim(Yii::$app->sys->event_name)]); + if(!$player->mail($subject,$contentHtml,$contentTxt)) + { + throw new \Exception('Could not send mail'); + } + + if (Yii::$app->sys->player_require_approval === true && $player->approval == 3) { + $player->updateAttributes(['approval' => 4]); + \Yii::$app->getSession()->setFlash('success', Yii::t('app', 'Player rejection mail send and approval status updated.')); + } else { + \Yii::$app->getSession()->setFlash('success', Yii::t('app', 'Player rejection mail send.')); + } } catch (\Exception $e) { \Yii::$app->getSession()->setFlash('error', Yii::t('app', 'Failed to mail rejection to player. {exception}', ['exception' => Html::encode($e->getMessage())])); } @@ -72,12 +80,16 @@ private function approvalMail($player) { try { $activationURL = sprintf("https://%s/verify-email?token=%s", \Yii::$app->sys->offense_domain, $player->verification_token); - $this->sendMail( - $player, - 'emailVerify-html', - 'emailVerify-text', - Yii::t('app', '{event_name} Account approved', ['event_name' => trim(Yii::$app->sys->event_name)]) - ); + $emailtpl=\app\modules\content\models\EmailTemplate::findOne(['name' => 'emailVerify']); + $contentHtml = $this->controller->renderPhpContent("?>" . $emailtpl->html, ['user' => $player,'verifyLink'=>$activationURL]); + $contentTxt = $this->controller->renderPhpContent("?>" . $emailtpl->txt, ['user' => $player,'verifyLink'=>$activationURL]); + $subject=Yii::t('app', '{event_name} Account approved', ['event_name' => trim(Yii::$app->sys->event_name)]); + + if(!$player->mail($subject,$contentHtml,$contentTxt)) + { + throw new \Exception('Could not send mail'); + } + if (Yii::$app->sys->player_require_approval === true && $player->approval == 1) { $player->updateAttributes(['approval' => 2]); \Yii::$app->getSession()->setFlash('success', Yii::t('app', 'Player activation mail send and approval status updated.')); @@ -88,18 +100,4 @@ private function approvalMail($player) \Yii::$app->getSession()->setFlash('error', Yii::t('app', 'Failed to mail player. {exception}', ['exception' => Html::encode($e->getMessage())])); } } - - private function sendMail($player, $html, $text, $subject) - { - Yii::$app - ->mailer - ->compose( - ['html' => $html, 'text' => $text], - ['user' => $player] - ) - ->setFrom([Yii::$app->sys->mail_from => Yii::$app->sys->mail_fromName . ' robot']) - ->setTo([$player->email => $player->fullname]) - ->setSubject($subject) - ->send(); - } } From f954e3920812d6323f0b20ed1f222cdec1cf047c Mon Sep 17 00:00:00 2001 From: Pantelis Roditis Date: Mon, 6 May 2024 17:26:11 +0300 Subject: [PATCH 4/8] bring mail to frontend as well --- frontend/models/Player.php | 708 ++++++++++++++++++------------------- 1 file changed, 349 insertions(+), 359 deletions(-) diff --git a/frontend/models/Player.php b/frontend/models/Player.php index ca6db65fa..b9120697d 100644 --- a/frontend/models/Player.php +++ b/frontend/models/Player.php @@ -1,4 +1,5 @@ on(self::NEW_PLAYER, ['\app\components\PlayerEvents', 'addToRank']); - $this->on(self::NEW_PLAYER, ['\app\components\PlayerEvents', 'giveInitialHint']); - $this->on(self::NEW_PLAYER, ['\app\components\PlayerEvents', 'sendInitialNotification']); - $this->on(self::NEW_PLAYER, ['\app\components\PlayerEvents', 'addStream']); - } + const NEW_PLAYER = 'new-player'; + const SCENARIO_SETTINGS = 'settings'; + public $new_password; + public $confirm_password; - public function behaviors() - { - return [ - 'typecast' => [ - 'class' => AttributeTypecastBehavior::class, - 'attributeTypes' => [ - 'id' => AttributeTypecastBehavior::TYPE_INTEGER, - 'status' => AttributeTypecastBehavior::TYPE_INTEGER, - 'active' => AttributeTypecastBehavior::TYPE_INTEGER, - 'academic' => AttributeTypecastBehavior::TYPE_INTEGER, - ], - 'typecastAfterValidate' => true, - 'typecastBeforeSave' => true, - 'typecastAfterFind' => true, - ], - [ - 'class' => TimestampBehavior::class, - 'createdAtAttribute' => 'created', - 'updatedAtAttribute' => 'ts', - 'value' => new Expression('NOW()'), - ], - ]; - } + public function init() + { + parent::init(); + $this->on(self::NEW_PLAYER, ['\app\components\PlayerEvents', 'addToRank']); + $this->on(self::NEW_PLAYER, ['\app\components\PlayerEvents', 'giveInitialHint']); + $this->on(self::NEW_PLAYER, ['\app\components\PlayerEvents', 'sendInitialNotification']); + $this->on(self::NEW_PLAYER, ['\app\components\PlayerEvents', 'addStream']); + } - public function scenarios() - { - return [ - 'default' => ['id','username', 'email', 'password','fullname','active','status','new_password','confirm_password','created','ts'], - self::SCENARIO_SETTINGS => ['username', 'email', 'fullname','new_password','confirm_password'], - ]; - } + public function behaviors() + { + return [ + 'typecast' => [ + 'class' => AttributeTypecastBehavior::class, + 'attributeTypes' => [ + 'id' => AttributeTypecastBehavior::TYPE_INTEGER, + 'status' => AttributeTypecastBehavior::TYPE_INTEGER, + 'active' => AttributeTypecastBehavior::TYPE_INTEGER, + 'academic' => AttributeTypecastBehavior::TYPE_INTEGER, + ], + 'typecastAfterValidate' => true, + 'typecastBeforeSave' => true, + 'typecastAfterFind' => true, + ], + [ + 'class' => TimestampBehavior::class, + 'createdAtAttribute' => 'created', + 'updatedAtAttribute' => 'ts', + 'value' => new Expression('NOW()'), + ], + ]; + } - /** - * {@inheritdoc} - */ - public static function findIdentity($id) - { - return static::findOne(['player.id' => $id, 'player.status' => self::STATUS_ACTIVE]); - } + public function scenarios() + { + return [ + 'default' => ['id', 'username', 'email', 'password', 'fullname', 'active', 'status', 'new_password', 'confirm_password', 'created', 'ts'], + self::SCENARIO_SETTINGS => ['username', 'email', 'fullname', 'new_password', 'confirm_password'], + ]; + } - /** - * {@inheritdoc} - */ - public static function findIdentityByAccessToken($token, $type = null) - { - throw new NotSupportedException('"findIdentityByAccessToken" is not implemented.'); - } + /** + * {@inheritdoc} + */ + public static function findIdentity($id) + { + return static::findOne(['player.id' => $id, 'player.status' => self::STATUS_ACTIVE]); + } - /** - * Finds player by username - * - * @param string $username - * @return static|null - */ - public static function findByUsername($username) - { - return static::findOne(['username' => $username, 'active'=>1,'status' => self::STATUS_ACTIVE]); - } + /** + * {@inheritdoc} + */ + public static function findIdentityByAccessToken($token, $type = null) + { + throw new NotSupportedException('"findIdentityByAccessToken" is not implemented.'); + } - /** - * Finds player by email - * - * @param string $email - * @return static|null - */ - public static function findByEmail($email) - { - return static::findOne(['email' => $email, 'active'=>1,'status' => self::STATUS_ACTIVE]); - } + /** + * Finds player by username + * + * @param string $username + * @return static|null + */ + public static function findByUsername($username) + { + return static::findOne(['username' => $username, 'active' => 1, 'status' => self::STATUS_ACTIVE]); + } - /** - * Finds player by verification email token - * - * @param string $token verify email token - * @return static|null - */ - public static function findByVerificationToken($token) { - return static::findOne([ - 'verification_token' => $token, - 'status' => [self::STATUS_INACTIVE, self::STATUS_UNVERIFIED] - ]); - } + /** + * Finds player by email + * + * @param string $email + * @return static|null + */ + public static function findByEmail($email) + { + return static::findOne(['email' => $email, 'active' => 1, 'status' => self::STATUS_ACTIVE]); + } - /** - * {@inheritdoc} - */ - public function getId() - { - return $this->id; - } + /** + * Finds player by verification email token + * + * @param string $token verify email token + * @return static|null + */ + public static function findByVerificationToken($token) + { + return static::findOne([ + 'verification_token' => $token, + 'status' => [self::STATUS_INACTIVE, self::STATUS_UNVERIFIED] + ]); + } - /** - * {@inheritdoc} - */ - public function getAuthKey() - { - return $this->auth_key; - } + /** + * {@inheritdoc} + */ + public function getId() + { + return $this->id; + } - /** - * {@inheritdoc} - */ - public function validateAuthKey($authKey) - { - return $this->getAuthKey() === $authKey && $this->status===self::STATUS_ACTIVE && $this->active===1; - } + /** + * {@inheritdoc} + */ + public function getAuthKey() + { + return $this->auth_key; + } - /** - * Validates password - * - * @param string $password password to validate - * @return bool if password provided is valid for current player - */ - public function validatePassword($password) - { - return Yii::$app->security->validatePassword($password, $this->password); - } + /** + * {@inheritdoc} + */ + public function validateAuthKey($authKey) + { + return $this->getAuthKey() === $authKey && $this->status === self::STATUS_ACTIVE && $this->active === 1; + } - /** - * Generates password hash from password and sets it to the model - * - * @param string $password - */ - public function setPassword($password) - { - $this->password_hash=Yii::$app->security->generatePasswordHash($password); - $this->password=Yii::$app->security->generatePasswordHash($password); - } + /** + * Validates password + * + * @param string $password password to validate + * @return bool if password provided is valid for current player + */ + public function validatePassword($password) + { + return Yii::$app->security->validatePassword($password, $this->password); + } - /** - * Generates "remember me" authentication key - */ - public function generateAuthKey() - { - $this->auth_key=Yii::$app->security->generateRandomString(); - } + /** + * Generates password hash from password and sets it to the model + * + * @param string $password + */ + public function setPassword($password) + { + $this->password_hash = Yii::$app->security->generatePasswordHash($password); + $this->password = Yii::$app->security->generatePasswordHash($password); + } - /** - * Generates new password reset token - */ - public function generatePasswordResetToken() - { - $this->password_reset_token=str_replace('_','-',Yii::$app->security->generateRandomString().'-'.time()); - } + /** + * Generates "remember me" authentication key + */ + public function generateAuthKey() + { + $this->auth_key = Yii::$app->security->generateRandomString(); + } - public function generateEmailVerificationToken() - { - $this->verification_token=str_replace('_','-',Yii::$app->security->generateRandomString().'-'.time()); - } + /** + * Generates new password reset token + */ + public function generatePasswordResetToken() + { + $this->password_reset_token = str_replace('_', '-', Yii::$app->security->generateRandomString() . '-' . time()); + } - /** - * Removes password reset token - */ - public function removePasswordResetToken() - { - $this->password_reset_token=null; - } + public function generateEmailVerificationToken() + { + $this->verification_token = str_replace('_', '-', Yii::$app->security->generateRandomString() . '-' . time()); + } - /** - * Get status Label - */ - public function getStatusLabel() - { - switch($this->status) { - case self::STATUS_ACTIVE: - return 'ACTIVE'; - case self::STATUS_INACTIVE: - return 'INACTIVE'; - case self::STATUS_DELETED: - return 'DELETED'; - } - } + /** + * Removes password reset token + */ + public function removePasswordResetToken() + { + $this->password_reset_token = null; + } - public function getSpins() - { - $command=Yii::$app->db->createCommand('select counter from player_spin WHERE player_id=:player_id'); - return (int) $command->bindValue(':player_id', $this->id)->queryScalar(); + /** + * Get status Label + */ + public function getStatusLabel() + { + switch ($this->status) { + case self::STATUS_ACTIVE: + return 'ACTIVE'; + case self::STATUS_INACTIVE: + return 'INACTIVE'; + case self::STATUS_DELETED: + return 'DELETED'; } + } + public function getSpins() + { + $command = Yii::$app->db->createCommand('select counter from player_spin WHERE player_id=:player_id'); + return (int) $command->bindValue(':player_id', $this->id)->queryScalar(); + } - public function getHeadshotsCount() - { - return $this->hasMany(Headshot::class, ['player_id' => 'id'])->count(); - } - public function getOnVPN():bool - { - return Yii::$app->cache->memcache->get('ovpn:'.$this->id)!==false; - } + public function getHeadshotsCount() + { + return $this->hasMany(Headshot::class, ['player_id' => 'id'])->count(); + } - public function getVpnIP() - { - return Yii::$app->cache->memcache->get('ovpn:'.$this->id); - } + public function getOnVPN(): bool + { + return Yii::$app->cache->memcache->get('ovpn:' . $this->id) !== false; + } - public function getIsAdmin():bool - { - $admin_ids=\Yii::$app->params['admin_ids']; - $memc_admin_ids=[]; - if(Yii::$app->sys->admin_ids!==false) - $memc_admin_ids=\yii\helpers\ArrayHelper::getColumn(explode(",", Yii::$app->sys->admin_ids), function ($element) { - return intval($element); - }); - if(Yii::$app->sys->{'admin_player:'.$this->id}!==false) - return true; - return !(array_search(intval($this->id), \yii\helpers\ArrayHelper::merge($admin_ids,$memc_admin_ids),true) === false);// error is here - } + public function getVpnIP() + { + return Yii::$app->cache->memcache->get('ovpn:' . $this->id); + } + + public function getIsAdmin(): bool + { + $admin_ids = \Yii::$app->params['admin_ids']; + $memc_admin_ids = []; + if (Yii::$app->sys->admin_ids !== false) + $memc_admin_ids = \yii\helpers\ArrayHelper::getColumn(explode(",", Yii::$app->sys->admin_ids), function ($element) { + return intval($element); + }); + if (Yii::$app->sys->{'admin_player:' . $this->id} !== false) + return true; + return !(array_search(intval($this->id), \yii\helpers\ArrayHelper::merge($admin_ids, $memc_admin_ids), true) === false); // error is here + } /** * Check if the user is considered a VIP * * @return bool */ - public function getIsVip():bool + public function getIsVip(): bool { - if($this->isAdmin || Yii::$app->sys->all_players_vip===true || ($this->subscription!==null && $this->subscription->active>0)) + if ($this->isAdmin || Yii::$app->sys->all_players_vip === true || ($this->subscription !== null && $this->subscription->active > 0)) return true; return false; } - public static function find() - { - return new PlayerQuery(get_called_class()); - } + public static function find() + { + return new PlayerQuery(get_called_class()); + } - /** - * Finds out if password reset token is valid - * - * @param string $token password reset token - * @return bool - */ - public static function isPasswordResetTokenValid($token, $expire = 86400): bool - { - if(empty($token) || trim($token)==="") - { - return false; - } - return true; - $timestamp=(int) substr($token, strrpos($token, '_') + 1); - return $timestamp + $expire >= time(); + /** + * Finds out if password reset token is valid + * + * @param string $token password reset token + * @return bool + */ + public static function isPasswordResetTokenValid($token, $expire = 86400): bool + { + if (empty($token) || trim($token) === "") { + return false; } - /** - * Finds user by password reset token - * - * @param string $token password reset token - * @return static|null - */ - public static function findByPasswordResetToken($token) - { - if(!static::isPasswordResetTokenValid($token)) - { - return null; - } - - return static::findOne([ - 'password_reset_token' => $token, - 'status' => self::STATUS_ACTIVE, - ]); + return true; + $timestamp = (int) substr($token, strrpos($token, '_') + 1); + return $timestamp + $expire >= time(); + } + /** + * Finds user by password reset token + * + * @param string $token password reset token + * @return static|null + */ + public static function findByPasswordResetToken($token) + { + if (!static::isPasswordResetTokenValid($token)) { + return null; } - public function saveWithSsl($validation=true) - { - - if(!$this->save($validation)) - return false; - - if($this->active==1 && $this->status==10 && $this->sSL===null) - { - $playerSsl=new PlayerSsl(); - $playerSsl->player_id=$this->id; - $playerSsl->generate(); - if($playerSsl->save()) - { - return $playerSsl->refresh(); - } - return false; - } - return true; - } + return static::findOne([ + 'password_reset_token' => $token, + 'status' => self::STATUS_ACTIVE, + ]); + } - public function saveNewPlayer($validation=true) - { - if(!$this->saveWithSsl($validation)) - return false; + public function saveWithSsl($validation = true) + { + + if (!$this->save($validation)) + return false; - if(($profile=$this->profile)==null) - { - $profile=new Profile(); - $profile->player_id=$this->id; + if ($this->active == 1 && $this->status == 10 && $this->sSL === null) { + $playerSsl = new PlayerSsl(); + $playerSsl->player_id = $this->id; + $playerSsl->generate(); + if ($playerSsl->save()) { + return $playerSsl->refresh(); } + return false; + } + return true; + } - $profile->scenario='signup'; - $profile->visibility=\Yii::$app->sys->profile_visibility!==false ? Yii::$app->sys->profile_visibility : 'ingame'; - $profile->gdpr=true; - $profile->terms_and_conditions=true; - if(!$profile->save()) - return false; - return $profile; + public function saveNewPlayer($validation = true) + { + if (!$this->saveWithSsl($validation)) + return false; + + if (($profile = $this->profile) == null) { + $profile = new Profile(); + $profile->player_id = $this->id; } - public function genAvatar() - { - $_pID=$this->profile->id; - $avatarsDIR=\Yii::getAlias('@app/web/images/avatars/'); - $avatarPNG=\Yii::getAlias('@app/web/images/avatars/'.$_pID.'.png'); - if(is_writable($avatarsDIR)===false || (file_exists($avatarPNG) && is_writable($avatarPNG)===false)) - { - \Yii::error('The avatars folder or avatar file is not writeable. correct the permissions for the avatars to be generated.'); - return ; - } - if(file_exists($avatarPNG)) - return; - $robohash=new \app\models\Robohash($_pID,'set1'); - $image=$robohash->generate_image(); - if((gettype($image) === "object" && get_class($image) === "GdImage")||((int) phpversion() === 7 && gettype($image)==='resource')) - { - imagepng($image,$avatarPNG); - imagedestroy($image); - $this->profile->avatar=$_pID.'.png'; - $this->profile->save(false); - } + $profile->scenario = 'signup'; + $profile->visibility = \Yii::$app->sys->profile_visibility !== false ? Yii::$app->sys->profile_visibility : 'ingame'; + $profile->gdpr = true; + $profile->terms_and_conditions = true; + if (!$profile->save()) + return false; + return $profile; + } + + public function genAvatar() + { + $_pID = $this->profile->id; + $avatarsDIR = \Yii::getAlias('@app/web/images/avatars/'); + $avatarPNG = \Yii::getAlias('@app/web/images/avatars/' . $_pID . '.png'); + if (is_writable($avatarsDIR) === false || (file_exists($avatarPNG) && is_writable($avatarPNG) === false)) { + \Yii::error('The avatars folder or avatar file is not writeable. correct the permissions for the avatars to be generated.'); + return; + } + if (file_exists($avatarPNG)) + return; + $robohash = new \app\models\Robohash($_pID, 'set1'); + $image = $robohash->generate_image(); + if ((gettype($image) === "object" && get_class($image) === "GdImage") || ((int) phpversion() === 7 && gettype($image) === 'resource')) { + imagepng($image, $avatarPNG); + imagedestroy($image); + $this->profile->avatar = $_pID . '.png'; + $this->profile->save(false); } + } - public function getAcademicWord() - { - switch($this->academic) - { - case 0: - return ".gov"; - case 1: - return ".edu"; - default: - return ".pro"; - } + public function getAcademicWord() + { + switch ($this->academic) { + case 0: + return ".gov"; + case 1: + return ".edu"; + default: + return ".pro"; } + } - public function getAcademicShort() - { - switch($this->academic) - { - case 0: - return ".gov"; - case 1: - return ".edu"; - default: - return ".pro"; - } + public function getAcademicShort() + { + switch ($this->academic) { + case 0: + return ".gov"; + case 1: + return ".edu"; + default: + return ".pro"; } - public function getAcademicIcon() - { - switch($this->academic) - { - case 0: - return "government.svg"; - case 1: - return "education.svg"; - default: - return "professional.svg"; - } + } + public function getAcademicIcon() + { + switch ($this->academic) { + case 0: + return "government.svg"; + case 1: + return "education.svg"; + default: + return "professional.svg"; } + } - /** - * Send mail to player with - * @param string $subject - * @param string $html - * @param string $txt - * @param array $headers - * @return bool - */ - public function mail($subject, $html, $txt,$headers=[]) - { - // Get mailer - try - { - $message=\Yii::$app->mailer->compose() - ->setFrom([\app\modules\settings\models\Sysconfig::findOne('mail_from')->val => \app\modules\settings\models\Sysconfig::findOne('mail_fromName')->val]) - ->setTo([$this->email=>$this->username]) - ->setSubject($subject) - ->setTextBody($txt) - ->setHtmlBody($html); - foreach($headers as $entry) - $message->addHeader($entry[0],$entry[1]); - $message->send(); - if (Yii::$app instanceof \yii\web\Application) - \Yii::$app->session->setFlash('success', Yii::t('app',"The user has been mailed.")); - else { - echo Yii::t('app',"The user has been mailed.\n"); - } + /** + * Send mail to player with + * @param string $subject + * @param string $html + * @param string $txt + * @param array $headers + * @return bool + */ + public function mail($subject, $html, $txt, $headers = []) + { + // Get mailer + try { + $message = \Yii::$app->mailer->compose() + ->setFrom([\Yii::$app->sys->mail_from => \Yii::$app->sys->mail_fromName]) + ->setTo([$this->email => $this->username]) + ->setSubject($subject) + ->setTextBody($txt) + ->setHtmlBody($html); + + foreach ($headers as $entry) { + $message->addHeader($entry[0], $entry[1]); } - catch(\Exception $e) - { - if (Yii::$app instanceof \yii\web\Application) - \Yii::$app->session->setFlash('notice', Yii::t('app',"Failed to send mail to user.")); - else - echo Yii::t('app',"Failed to send mail to user.\n"); - return false; + $message->send(); + if (Yii::$app instanceof \yii\web\Application) + \Yii::$app->session->setFlash('success', Yii::t('app', "The user has been mailed.")); + else { + echo Yii::t('app', "The user has been mailed.\n"); } - return true; + } catch (\Exception $e) { + if (Yii::$app instanceof \yii\web\Application) + \Yii::$app->session->setFlash('notice', Yii::t('app', "Failed to send mail to user.")); + else + echo Yii::t('app', "Failed to send mail to user.\n"); + return false; + } + return true; } - } From 6506804f5ac9eb74609a18e44024d608713316a2 Mon Sep 17 00:00:00 2001 From: Pantelis Roditis Date: Mon, 6 May 2024 17:30:25 +0300 Subject: [PATCH 5/8] switch to using the templates --- .../models/forms/PasswordResetRequestForm.php | 22 +++++++++---------- .../forms/ResendVerificationEmailForm.php | 16 +++++--------- frontend/models/forms/SettingsForm.php | 17 ++++++-------- frontend/models/forms/SignupForm.php | 17 ++++++-------- 4 files changed, 30 insertions(+), 42 deletions(-) diff --git a/frontend/models/forms/PasswordResetRequestForm.php b/frontend/models/forms/PasswordResetRequestForm.php index aeadebfdb..c88dc35e9 100644 --- a/frontend/models/forms/PasswordResetRequestForm.php +++ b/frontend/models/forms/PasswordResetRequestForm.php @@ -68,17 +68,15 @@ public function sendEmail() $password_reset_email++; Yii::$app->cache->memcache->set('password_reset_ip:'.\Yii::$app->request->userIp,$password_reset_ip, \Yii::$app->sys->password_reset_ip_timeout); Yii::$app->cache->memcache->set('password_reset_email:'.$this->email,$password_reset_email, \Yii::$app->sys->password_reset_email_timeout); - $message=Yii::$app - ->mailer - ->compose( - ['html' => 'passwordResetToken-html', 'text' => 'passwordResetToken-text'], - ['user' => $player] - ) - ->setFrom([Yii::$app->sys->mail_from => Yii::$app->sys->mail_fromName.' robot']) - ->setTo([$player->email => $player->fullname]) - ->setSubject(\Yii::t('app','Password reset request for {event_name}',['event_name'=>trim(Yii::$app->sys->event_name)])); - $message->addHeader('X-tag', 'password-reset'); - $message->addHeader('X-Metadata-Requestor-IP', \Yii::$app->request->userIp); - return $message->send(); + $emailtpl=\app\modelscli\EmailTemplate::findOne(['name' => 'passwordResetToken']); + $subject=\Yii::t('app','Password reset request for {event_name}',['event_name'=>trim(Yii::$app->sys->event_name)]); + $resetLink=\Yii::$app->urlManager->createAbsoluteUrl(['site/reset-password', 'token' => $player->password_reset_token]); + $contentHtml = \app\components\echoCTFView::renderPhpContent("?>" . $emailtpl->html, ['user' => $player,'resetLink'=>$resetLink]); + $contentTxt = \app\components\echoCTFView::renderPhpContent("?>" . $emailtpl->txt, ['user' => $player,'resetLink'=>$resetLink]); + $mailHeaders=[ + ['X-tag','password-reset'], + ['X-Metadata-Requestor-IP',\Yii::$app->request->userIp], + ]; + return $player->mail($subject,$contentHtml,$contentTxt,$mailHeaders); } } diff --git a/frontend/models/forms/ResendVerificationEmailForm.php b/frontend/models/forms/ResendVerificationEmailForm.php index 9eee0ae41..a01bfbfe0 100644 --- a/frontend/models/forms/ResendVerificationEmailForm.php +++ b/frontend/models/forms/ResendVerificationEmailForm.php @@ -56,15 +56,11 @@ public function sendEmail() $verification_resend_email++; Yii::$app->cache->memcache->set('verification_resend_ip:'.\Yii::$app->request->userIp,$verification_resend_ip, Yii::$app->sys->verification_resend_ip_timeout); Yii::$app->cache->memcache->set('verification_resend_email:'.$this->email,$verification_resend_email, Yii::$app->sys->verification_resend_email_timeout); - return Yii::$app - ->mailer - ->compose( - ['html' => 'emailVerify-html', 'text' => 'emailVerify-text'], - ['user' => $player] - ) - ->setFrom([Yii::$app->sys->mail_from => Yii::$app->sys->mail_fromName.' robot']) - ->setTo([$player->email => $player->fullname]) - ->setSubject(\Yii::t('app','Account registration for {event_name}', ['event_name'=>trim(Yii::$app->sys->event_name)])) - ->send(); + $emailtpl=\app\modelscli\EmailTemplate::findOne(['name' => 'emailVerify']); + $subject=\Yii::t('app','Account registration for {event_name}', ['event_name'=>trim(Yii::$app->sys->event_name)]); + $verifyLink=\Yii::$app->urlManager->createAbsoluteUrl(['site/verify-email', 'token' => $player->verification_token]); + $contentHtml = \app\components\echoCTFView::renderPhpContent("?>" . $emailtpl->html, ['user' => $player,'verifyLink'=>$verifyLink]); + $contentTxt = \app\components\echoCTFView::renderPhpContent("?>" . $emailtpl->txt, ['user' => $player,'verifyLink'=>$verifyLink]); + return $player->mail($subject,$contentHtml,$contentTxt); } } diff --git a/frontend/models/forms/SettingsForm.php b/frontend/models/forms/SettingsForm.php index 47b2fe5ae..8ebdd62ae 100644 --- a/frontend/models/forms/SettingsForm.php +++ b/frontend/models/forms/SettingsForm.php @@ -229,16 +229,13 @@ public function setPlayer($value) */ protected function sendEmail() { - return Yii::$app - ->mailer - ->compose( - ['html' => 'emailChangeVerify-html', 'text' => 'emailChangeVerify-text'], - ['user' => $this->player] - ) - ->setFrom([Yii::$app->sys->mail_from => Yii::$app->sys->mail_fromName.' robot']) - ->setTo([$this->player->email => $this->player->fullname]) - ->setSubject(\Yii::t('app','Verify your email for {event_name}',['event_name'=>trim(Yii::$app->sys->event_name)])) - ->send(); + $emailtpl=\app\modelscli\EmailTemplate::findOne(['name' => 'emailChangeVerify']); + $subject=\Yii::t('app','Verify your email for {event_name}',['event_name'=>trim(Yii::$app->sys->event_name)]); + $verifyLink=\Yii::$app->urlManager->createAbsoluteUrl(['site/verify-email', 'token' => $this->_player->verification_token]); + $contentHtml = \app\components\echoCTFView::renderPhpContent("?>" . $emailtpl->html, ['user' => $this->_player,'verifyLink'=>$verifyLink]); + $contentTxt = \app\components\echoCTFView::renderPhpContent("?>" . $emailtpl->txt, ['user' => $this->_player,'verifyLink'=>$verifyLink]); + + return $this->_player->mail($subject,$contentHtml,$contentTxt); } public function save() diff --git a/frontend/models/forms/SignupForm.php b/frontend/models/forms/SignupForm.php index 1d62c233c..c627f2531 100644 --- a/frontend/models/forms/SignupForm.php +++ b/frontend/models/forms/SignupForm.php @@ -142,15 +142,12 @@ public function attributeLabels() */ protected function sendEmail($player) { - return Yii::$app - ->mailer - ->compose( - ['html' => 'emailVerify-html', 'text' => 'emailVerify-text'], - ['user' => $player] - ) - ->setFrom([Yii::$app->sys->mail_from => Yii::$app->sys->mail_fromName.' robot']) - ->setTo([$player->email => $player->fullname]) - ->setSubject('Account registration at '.trim(Yii::$app->sys->event_name)) - ->send(); + $emailtpl=\app\modelscli\EmailTemplate::findOne(['name' => 'emailVerify']); + $subject=\Yii::t('app','Account registration for {event_name}', ['event_name'=>trim(Yii::$app->sys->event_name)]); + $verifyLink=\Yii::$app->urlManager->createAbsoluteUrl(['site/verify-email', 'token' => $player->verification_token]); + + $contentHtml = \app\components\echoCTFView::renderPhpContent("?>" . $emailtpl->html, ['user' => $player,'verifyLink'=>$verifyLink]); + $contentTxt = \app\components\echoCTFView::renderPhpContent("?>" . $emailtpl->txt, ['user' => $player,'verifyLink'=>$verifyLink]); + return $player->mail($subject,$contentHtml,$contentTxt); } } From d7594daeda8ecc9521742d3609163564cf80ce69 Mon Sep 17 00:00:00 2001 From: Pantelis Roditis Date: Mon, 6 May 2024 21:06:47 +0300 Subject: [PATCH 6/8] simplify mailer and use DSN instead of the complex configuration we had. Transparently support both though --- backend/components/Mailer.php | 55 +++++++++++----------------------- frontend/components/Mailer.php | 51 +++++++++---------------------- 2 files changed, 31 insertions(+), 75 deletions(-) diff --git a/backend/components/Mailer.php b/backend/components/Mailer.php index 592da4970..f3a1ba6da 100644 --- a/backend/components/Mailer.php +++ b/backend/components/Mailer.php @@ -1,49 +1,28 @@ useFileTransport=Yii::$app->sys->mail_useFileTransport; - - $config['scheme']='smtp'; - $config['options']['local_domain']=\Yii::$app->sys->offense_domain; - - if(Yii::$app->sys->mail_host !== false) - { - $config['host']=Yii::$app->sys->mail_host; - } - - if(Yii::$app->sys->mail_port !== false) - { - $config['port']=intval(Yii::$app->sys->mail_port); - } - - if(Yii::$app->sys->mail_username !== false) - { - $config['username']=Yii::$app->sys->mail_username; - } - - if(Yii::$app->sys->mail_password !== false) - { - $config['password']=Yii::$app->sys->mail_password; - } - - if(Yii::$app->sys->mail_encryption !== false && trim(Yii::$app->sys->mail_encryption)!='') - { - $config['scheme']=Yii::$app->sys->mail_encryption; - } - - if(Yii::$app->sys->mail_verify_peer !== false) - { - $this->ssl['verify_peer']=Yii::$app->sys->mail_verify_peer; - } - if(Yii::$app->sys->mail_verify_peer_name !== false) - { - $this->ssl['verify_peer_name']=Yii::$app->sys->mail_verify_peer_name; + $this->useFileTransport = (bool)Yii::$app->sys->mail_useFileTransport; + if (trim(\Yii::$app->sys->dsn)!=="") { + $config['dsn']=trim(\Yii::$app->sys->dsn); + } else { + $config['dsn'] = Yii::t('app', '{mail_encryption}://{mail_userpass}{mail_host}:{mail_port}?verify_peer={verify_peer}&local_domain={local_domain}&verify_peer_name={verify_peer_name}', [ + 'mail_encryption' => \Yii::$app->sys->mail_encryption ?? "smtp", + 'mail_userpass' => (trim(\Yii::$app->sys->mail_username) != "" && trim(\Yii::$app->sys->mail_password) != "") ? trim(\Yii::$app->sys->mail_username) . ':' . trim(\Yii::$app->sys->mail_password) . '@' : '', + 'mail_password' => \Yii::$app->sys->mail_password, + 'mail_host' => \Yii::$app->sys->mail_host, + 'mail_port' => \Yii::$app->sys->mail_port ?? "25", + 'verify_peer' => intval(\Yii::$app->sys->verify_peer), + 'local_domain' => \Yii::$app->sys->offense_domain, + 'verify_peer_name' => intval(\Yii::$app->sys->verify_peer_name), + ]); } $this->setTransport($config); } diff --git a/frontend/components/Mailer.php b/frontend/components/Mailer.php index e9e71a6e9..ac391f246 100644 --- a/frontend/components/Mailer.php +++ b/frontend/components/Mailer.php @@ -6,43 +6,20 @@ class Mailer extends \yii\symfonymailer\Mailer public function init() { parent::init(); - - $this->useFileTransport=Yii::$app->sys->mail_useFileTransport; - - $config['scheme']='smtp'; - $config['options']['local_domain']=\Yii::$app->sys->offense_domain; - if(Yii::$app->sys->mail_host !== false) - { - $config['host']=Yii::$app->sys->mail_host; - } - - if(Yii::$app->sys->mail_port !== false) - { - $config['port']=intval(Yii::$app->sys->mail_port); - } - - if(Yii::$app->sys->mail_username !== false) - { - $config['username']=Yii::$app->sys->mail_username; - } - - if(Yii::$app->sys->mail_password !== false) - { - $config['password']=Yii::$app->sys->mail_password; - } - - if(Yii::$app->sys->mail_encryption !== false && trim(Yii::$app->sys->mail_encryption)!='') - { - $config['scheme']=Yii::$app->sys->mail_encryption; - } - - if(Yii::$app->sys->mail_verify_peer !== false) - { - $this->ssl['verify_peer']=Yii::$app->sys->mail_verify_peer; - } - if(Yii::$app->sys->mail_verify_peer_name !== false) - { - $this->ssl['verify_peer_name']=Yii::$app->sys->mail_verify_peer_name; + $this->useFileTransport = (bool)Yii::$app->sys->mail_useFileTransport; + if (\Yii::$app->sys->dsn !== false) { + $config['dsn']=trim(\Yii::$app->sys->dsn); + } else { + $config['dsn'] = Yii::t('app', '{mail_encryption}://{mail_userpass}{mail_host}:{mail_port}?verify_peer={verify_peer}&local_domain={local_domain}&verify_peer_name={verify_peer_name}', [ + 'mail_encryption' => \Yii::$app->sys->mail_encryption ?? "smtp", + 'mail_userpass' => (trim(\Yii::$app->sys->mail_username) != "" && trim(\Yii::$app->sys->mail_password) != "") ? trim(\Yii::$app->sys->mail_username) . ':' . trim(\Yii::$app->sys->mail_password) . '@' : '', + 'mail_password' => \Yii::$app->sys->mail_password, + 'mail_host' => \Yii::$app->sys->mail_host, + 'mail_port' => \Yii::$app->sys->mail_port ?? "25", + 'verify_peer' => intval(\Yii::$app->sys->verify_peer), + 'local_domain' => \Yii::$app->sys->offense_domain, + 'verify_peer_name' => intval(\Yii::$app->sys->verify_peer_name), + ]); } $this->setTransport($config); } From e2b74b3e64091a5fcef4a80cf442219483269884 Mon Sep 17 00:00:00 2001 From: Pantelis Roditis Date: Mon, 6 May 2024 21:10:26 +0300 Subject: [PATCH 7/8] update configure form and keys to support DSN --- backend/modules/settings/models/ConfigureForm.php | 9 +++++++-- .../modules/settings/views/sysconfig/configure.php | 11 +++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/backend/modules/settings/models/ConfigureForm.php b/backend/modules/settings/models/ConfigureForm.php index 444e69bd7..79359c82c 100644 --- a/backend/modules/settings/models/ConfigureForm.php +++ b/backend/modules/settings/models/ConfigureForm.php @@ -81,6 +81,7 @@ class ConfigureForm extends Model public $profile_twitch; public $profile_youtube; public $guest_visible_leaderboards; + public $dsn; public $dn_countryName; public $dn_stateOrProvinceName; @@ -164,7 +165,8 @@ class ConfigureForm extends Model 'profile_htb', 'profile_twitch', 'profile_youtube', - 'guest_visible_leaderboards' + 'guest_visible_leaderboards', + 'dsn' ]; /** @@ -203,6 +205,7 @@ public function rules() 'stripe_apiKey', 'stripe_publicApiKey', 'stripe_webhookSecret', + 'dsn' ], 'string'], [['offense_registered_tag', 'defense_registered_tag', @@ -233,7 +236,8 @@ public function rules() 'dn_localityName', 'dn_organizationName', 'dn_organizationalUnitName', - 'pf_state_limits' + 'pf_state_limits', + 'dsn' ], 'trim'], // required fields [['teams', @@ -380,6 +384,7 @@ public function attributeLabels() 'profile_twitch'=>'Twitch', 'profile_youtube'=>'Youtube', 'guest_visible_leaderboards'=>'Guest visible leaderboards', + 'dsn'=>'Mail DSN' ]; } diff --git a/backend/modules/settings/views/sysconfig/configure.php b/backend/modules/settings/views/sysconfig/configure.php index 002ebf680..61c16a099 100644 --- a/backend/modules/settings/views/sysconfig/configure.php +++ b/backend/modules/settings/views/sysconfig/configure.php @@ -114,13 +114,7 @@
field($model, 'mail_useFileTransport')->checkbox()->hint('Activate the use of file transport (save mails in files)?') ?>
field($model, 'mail_from')->textInput(['maxlength' => true])->hint('Mail From (eg. dontreply@echoctf.red)') ?>
field($model, 'mail_fromName')->textInput(['maxlength' => true])->hint('Mail From Name (eg. echoCTF RED)') ?>
-
field($model, 'mail_host')->textInput(['maxlength' => true])->hint('Mail host (eg. smtp-relay.gmail.com)') ?>
-
field($model, 'mail_port')->textInput(['maxlength' => true])->hint('Mail port (eg. 25)') ?>
-
field($model, 'mail_username')->textInput(['maxlength' => true])->hint('Mail server username') ?>
-
field($model, 'mail_password')->textInput(['maxlength' => true])->hint('Mail server password') ?>
-
field($model, 'mail_encryption')->textInput(['maxlength' => true])->hint('Mail server encryption (ssl,tls,none)') ?>
-
field($model, 'mail_verify_peer')->checkbox()->hint('Verify peer sertificate?') ?>
-
field($model, 'mail_verify_peer_name')->checkbox()->hint('Verify peer name from certificate?') ?>
+
field($model, 'dsn')->textInput(['maxlength' => true,'placeholder'=>'smtp://username:password@mail.example.com:25?local_domain=blah'])->hint('Mail DSN see '.Html::a('Symphony Mailer','https://symfony.com/doc/current/mailer.html',['title'=>'Symphony Mailer Reference','target'=>'_blank'])) ?>

@@ -150,7 +144,8 @@
-

VPN Certificate Settings If you change these values you will have to regenerate your ca keys and player certificates again.

+

VPN Certificate Settings

+ If you change these values you will have to regenerate your CA keys and player certificates.
field($model, 'dn_countryName')->textInput(['maxlength' => true])->input('text', ['placeholder' => ""])->hint('') ?>
From 8fd423ccce2ba15c04f8fd05d3dc5d1e60fcdcbc Mon Sep 17 00:00:00 2001 From: Pantelis Roditis Date: Mon, 6 May 2024 21:10:40 +0300 Subject: [PATCH 8/8] Document the sysconfig --- docs/Sysconfig-Keys.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/Sysconfig-Keys.md b/docs/Sysconfig-Keys.md index dceebede9..1ddaed99a 100644 --- a/docs/Sysconfig-Keys.md +++ b/docs/Sysconfig-Keys.md @@ -70,10 +70,16 @@ ## mail configuration * `mail_from` Email address used to send registration and password reset mails from * `mail_fromName` The name appeared on the email send for registration and password resets +* `dsn` A symphony mailer compatible DSN + +Or instead if not DSN then use the following * `mail_host` The mail server host to send mails through * `mail_port` The mail server port to connect * `mail_username` The username to authenticate to the mail server * `mail_password` The password to authenticate to the mail server +* `local_domain` Set the EHLO mail used when sending mail +* `verify_peer_name` Verify the SSL peer name of the remote server when sending email +* `verify_peer` Verify the remote peer certificate when sending mail ## VPN specific keys * `CA.csr` The CA CSR