diff --git a/backend/commands/VpnController.php b/backend/commands/VpnController.php
index 08f658813..dcb60313b 100644
--- a/backend/commands/VpnController.php
+++ b/backend/commands/VpnController.php
@@ -11,7 +11,9 @@
use yii\helpers\Console;
use yii\console\Controller;
use app\modules\frontend\models\Player;
+use app\modules\activity\models\PlayerLast;
use app\components\OpenVPN;
+use yii\console\ExitCode;
/**
* Manages VPN specific operations.
*
@@ -65,7 +67,15 @@ public function actionKill($player)
throw new ConsoleException(Yii::t('app', 'Player not found with id or username of [{values}]', ['values' => $player]));
}
printf("Killing %d with last local IP [%s]\n",$pM->id,$pM->last->vpn_local_address_octet);
- OpenVPN::kill($pM->id,intval($pM->last->vpn_local_address));
+ try {
+ OpenVPN::kill($pM->id,intval($pM->last->vpn_local_address));
+ }
+ catch(\Exception $e)
+ {
+ echo "Error: ",$e->getMessage(),"\n";
+ return ExitCode::UNSPECIFIED_ERROR;
+ }
+
}
/**
@@ -74,14 +84,25 @@ public function actionKill($player)
public function actionKillall()
{
- foreach(Player::find()->where(['status'=>10])->all() as $pM)
+ foreach(PlayerLast::find()->all() as $pM)
{
- printf("Logging out %d\n",$pM->id);
- OpenVPN::logout($pM->id);
+ try {
+ if($pM->vpn_local_address!==null)
+ {
+ printf("Killing %s => %d\n",$pM->player->username,$pM->id);
+ OpenVPN::kill($pM->id,intval($pM->vpn_local_address));
+ }
+ }
+ catch(\Exception $e)
+ {
+ echo "Error: ",$e->getMessage(),"\n";
+ }
}
+ Yii::$app->db->createCommand("UPDATE player_last SET vpn_local_address=NULL, vpn_remote_address=NULL")->execute();
+
}
/**
- * Logoutall stall connections from OpenVPN.
+ * Logout all stall connections from all player_last entries
*/
public function actionLogoutall()
{
@@ -89,43 +110,98 @@ public function actionLogoutall()
}
/**
- * Load configuration from filesystem
+ * Load OpenVPN configuration from filesystem and store to the database.
+ * Parses the configuration file for management interface and ranges.
+ * @param string $filename The filename to read and store to the database
*/
public function actionLoad($filepath)
{
$file=basename($filepath);
+ $conf=file_get_contents($filepath);
try{
- $contents=file_get_contents($filepath);
- Yii::$app->db->createCommand("UPDATE openvpn SET conf=:config WHERE name=:filename",[':config'=>$contents,':filename'=>$file])->execute();
+ if(preg_match('/server (.*) (.*)/',$conf,$matches) && count($matches)>1)
+ {
+ $ovpnModel=\app\modules\settings\models\Openvpn::find()->where(['name'=>$file,'net'=>ip2long($matches[1])]);
+ }
+ if(($ovpn=$ovpnModel->one())===null)
+ {
+ $ovpn=new \app\modules\settings\models\Openvpn;
+ }
+ $ovpn->conf=file_get_contents($filepath);
+ $ovpn->name=$file;
+ $ovpn->server=gethostbyaddr(gethostbyname(gethostname()));
+ if(preg_match('/status (.*)/',$conf,$matches) && count($matches)>1)
+ {
+ $ovpn->status_log=trim($matches[1]);
+ }
+ if(preg_match('/management (.*) (.*) (.*)/',$conf,$matches) && count($matches)>1)
+ {
+ $ovpn->management_ip_octet=$matches[1];
+ $ovpn->management_port=$matches[2];
+ if(str_starts_with($matches[3], '/'))
+ {
+ if(file_exists($matches[3]))
+ $ovpn->management_passwd=file_get_contents($matches[3]);
+ else
+ echo "WARNING: The provided config uses a file as a management password\n\t but the file [",$matches[3], "] does not exist!\n";
+ }
+ }
+ if(preg_match('/server (.*) (.*)/',$conf,$matches) && count($matches)>1)
+ {
+ $ovpn->net_octet=$matches[1];
+ $ovpn->mask_octet=$matches[2];
+ }
+ if($ovpn->save())
+ {
+ echo $ovpn->isNewRecord ? "Record created successfully!\n" : "Record updated successfully!\n";
+ }
+ else
+ {
+ echo "Failed to save record: ",$ovpn->getErrorSummary(true),"\n";
+ }
}
catch (\Exception $e)
{
- printf("Error: ",$e->getMessage());
+ printf("Error: %s",$e->getMessage());
}
-
}
/**
- * Save configuration to filesystem
+ * Save OpenVPN configuration to filesystem.
+ * Uses the provided file basename and current system hostname to find the actual entry.
+ * @param string $filepath The full path to store the config contents.
*/
public function actionSave($filepath)
{
try{
$file=basename($filepath);
- $contents=Yii::$app->db->createCommand("SELECT conf FROM openvpn WHERE name=:filename",[':filename'=>$file])->queryScalar();
- file_put_contents($filepath,$contents);
+ $ovpnModel=\app\modules\settings\models\Openvpn::find()->where(['server'=>gethostname(),'name'=>$file]);
+ if(($ovpn=$ovpnModel->one())===null)
+ {
+ echo "No record found for the given file and server!\n";
+ return ExitCode::CANTCREAT;
+ }
+ if(file_put_contents($filepath,$ovpn->conf))
+ {
+ echo "File saved at ",$filepath,"\n";
+ }
+ else
+ {
+ echo "Failed to save ",$filepath,"\n";
+ return ExitCode::UNSPECIFIED_ERROR;
+ }
}
catch (\Exception $e)
{
- printf("Error: ",$e->getMessage());
+ printf("Error: %s",$e->getMessage());
}
}
/**
* Display openvpn status enriched with database details
*/
- public function actionStatus($provider_id=null)
+ public function actionStatus()
{
- $q=\app\modules\settings\models\Openvpn::find()->select('status_log')->andFilterWhere(['LIKE','provider_id',$provider_id]);
+ $q=\app\modules\settings\models\Openvpn::find()->select('status_log')->andFilterWhere(['server'=>gethostname()]);
$status['routing_table']=[];
$status['client_list']=[];
foreach($q->all() as $entry)
@@ -139,11 +215,11 @@ public function actionStatus($provider_id=null)
}
catch (\Exception $e)
{
-
+ printf("Error: %s",$e->getMessage());
}
unset($entry);
}
- $this->stdout( sprintf("%-5s %-10s %-10s %-18s %-10s %-10s\n", 'ID', 'Username','Local IP','Remote IP', 'Received', 'Send'), Console::BOLD);
+ $this->stdout(sprintf("%-5s %-10s %-10s %-18s %-10s %-10s\n", 'ID', 'Username','Local IP','Remote IP', 'Received', 'Send'), Console::BOLD);
foreach($status['client_list'] as $entry)
{
$p=\app\modules\frontend\models\Player::findOne($entry->player_id);
diff --git a/backend/components/OpenVPN.php b/backend/components/OpenVPN.php
index 59772c794..90ade6c2e 100644
--- a/backend/components/OpenVPN.php
+++ b/backend/components/OpenVPN.php
@@ -49,9 +49,9 @@ static public function kill(int $player_id,int $player_ip)
{
echo "connected to {$creds->management_ip_octet}\n";
fwrite($fp, "$creds->management_passwd\n");
- echo "send {$creds->management_ip_octet}\n";
+ echo "sending to ",$creds->management_ip_octet,"\n";
usleep(250000);
- fwrite($fp, "kill ${player_id}\n");
+ fwrite($fp, "kill $player_id\n");
usleep(250000);
fwrite($fp, "exit\n");
usleep(250000);
@@ -77,6 +77,10 @@ static public function determineServerByAddr(int $player_ip)
static public function parseStatus(string $location)
{
+ if(!file_exists($location))
+ {
+ throw new yii\base\UserException("Status file does not exist");
+ }
$statusLines=explode("\n",file_get_contents($location));
if(count($statusLines)==0)
return new stdClass;
diff --git a/backend/components/validators/VerifymailValidator.php b/backend/components/validators/VerifymailValidator.php
new file mode 100644
index 000000000..bccd0c047
--- /dev/null
+++ b/backend/components/validators/VerifymailValidator.php
@@ -0,0 +1,112 @@
+sys->signup_ValidatemailValidator===false)
+ return;
+
+ $data = http_build_query(['key'=>\Yii::$app->sys->verifymail_key]);
+
+ $domain=$value;
+
+ if(str_contains($value,'@'))
+ {
+ $parts=explode("@",$value);
+ if(count($parts)<2)
+ {
+ return ['Verifymail.io: Failed to parse email!', [
+ 'email' => $value,
+ ]];
+ }
+ $domain=end($parts);
+ }
+
+ $req=$this->url.$domain.'?'.$data;
+ $ch = curl_init($req);
+
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($ch, CURLOPT_TIMEOUT, 40);
+
+ try
+ {
+ $result = curl_exec($ch);
+ curl_close($ch);
+ $retData=json_decode($result);
+ if($retData != null && $retData->disposable===true)
+ {
+ return [$this->message, [
+ 'email' => $value,
+ ]];
+ }
+ }
+ catch(\Exception $e)
+ {
+ if(curl_errno($ch)===0)
+ return [$this->message, ['email' => $value]];
+ }
+ }
+
+ public function validateAttribute($model, $attribute)
+ {
+ if(\Yii::$app->sys->signup_ValidatemailValidator===false)
+ return;
+ $value = $model->$attribute;
+
+ $data = http_build_query(['key'=>\Yii::$app->sys->verifymail_key]);
+
+ if(str_contains($value,'@'))
+ {
+ $parts=explode("@",$value);
+ if(count($parts)<2)
+ {
+ return ['Verifymail.io: Failed to parse email!', [
+ 'email' => $value,
+ ]];
+ }
+ $domain=end($parts);
+ }
+
+ $req=$this->url.$domain.'?'.$data;
+ $ch = curl_init($req);
+
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($ch, CURLOPT_TIMEOUT, 40);
+
+ try
+ {
+ $result = curl_exec($ch);
+ curl_close($ch);
+ $retData=json_decode($result);
+ if($retData && $retData->message)
+ {
+ \Yii::error("Verifymail.io return message: ".$retData->message);
+ }
+ if($retData != null && $retData->disposable===true)
+ {
+ return [$this->message, ['email' => $value]];
+ }
+ }
+ catch(\Exception $e)
+ {
+ if(curl_errno($ch)===0)
+ return [$this->message, ['email' => $value]];
+ }
+ }
+}
diff --git a/backend/migrations/m240320_235210_add_server_column_to_openvpn_table.php b/backend/migrations/m240320_235210_add_server_column_to_openvpn_table.php
new file mode 100644
index 000000000..39b8ee733
--- /dev/null
+++ b/backend/migrations/m240320_235210_add_server_column_to_openvpn_table.php
@@ -0,0 +1,25 @@
+addColumn('{{%openvpn}}', 'server', $this->string()->after('id'));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function safeDown()
+ {
+ $this->dropColumn('{{%openvpn}}', 'server');
+ }
+}
diff --git a/backend/migrations/m240320_235211_alter_openvpn_table_unique_keys.php b/backend/migrations/m240320_235211_alter_openvpn_table_unique_keys.php
new file mode 100644
index 000000000..4027d158d
--- /dev/null
+++ b/backend/migrations/m240320_235211_alter_openvpn_table_unique_keys.php
@@ -0,0 +1,31 @@
+dropIndex('name', 'openvpn');
+ $this->dropIndex('net', 'openvpn');
+ $this->createIndex('server_name_net', 'openvpn', ['server','name','net'], true );
+
+
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function safeDown()
+ {
+ $this->dropIndex('server_name_net', 'openvpn');
+ $this->createIndex('name', 'openvpn', ['name'], true );
+ $this->createIndex('net', 'openvpn', ['name'], true );
+ }
+}
diff --git a/backend/modules/activity/models/PlayerLast.php b/backend/modules/activity/models/PlayerLast.php
index 2a1976dc1..84c4ffe63 100644
--- a/backend/modules/activity/models/PlayerLast.php
+++ b/backend/modules/activity/models/PlayerLast.php
@@ -56,6 +56,10 @@ public function attributeLabels()
'signup_ip' => 'Signup IP',
];
}
+ public function resetVPN() {
+ return $this->updateAttributes(['vpn_remote_address' => null,'vpn_local_address'=>null]);
+ }
+
public function afterFind() {
parent::afterFind();
$this->signin_ipoctet=long2ip($this->signin_ip);
diff --git a/backend/modules/frontend/controllers/PlayerController.php b/backend/modules/frontend/controllers/PlayerController.php
index 44650a81f..d751b803f 100644
--- a/backend/modules/frontend/controllers/PlayerController.php
+++ b/backend/modules/frontend/controllers/PlayerController.php
@@ -36,6 +36,7 @@ public function behaviors()
'actions' => [
'set-deleted' => ['POST'],
'clear-verification-token' => ['POST'],
+ 'clear-vpn' => ['POST'],
'delete' => ['POST'],
'ban' => ['POST'],
'ban-filtered' => ['POST'],
@@ -407,6 +408,37 @@ public function actionDeleteFiltered()
}
return $this->redirect(['index']);
}
+
+ /**
+ * Clear VPN related data for a given user
+ * @param integer $id The player ID
+ * @throws NotFoundHttpException if the player id cannot be found
+ */
+ public function actionClearVpn($id)
+ {
+ $player=$this->findModel($id);
+ $ip=Yii::$app->cache->Memcache->get("ovpn:".$player->id);
+ if($ip!==false)
+ {
+ Yii::$app->cache->Memcache->delete("ovpn:".$player->id);
+ $memid=Yii::$app->cache->Memcache->get("ovpn:".long2ip($ip));
+ if($memid!==false && intval($memid)===intval($id))
+ {
+ Yii::$app->cache->Memcache->delete("ovpn:".long2ip($ip));
+ }
+ }
+ if($player->last->resetVPN())
+ {
+ \Yii::$app->session->addFlash('success', \Yii::t('app', "Player VPN details cleared"));
+ }
+ else
+ {
+ \Yii::$app->session->addFlash('error', \Yii::t('app', "Player VPN details failed to clear"));
+ }
+
+ return $this->redirect(['index']);
+ }
+
/**
* Finds the Player model based on its primary key value.
* If the model is not found, a 404 HTTP exception will be thrown.
diff --git a/backend/modules/frontend/models/PlayerAR.php b/backend/modules/frontend/models/PlayerAR.php
index dd794f6b0..680d5befd 100644
--- a/backend/modules/frontend/models/PlayerAR.php
+++ b/backend/modules/frontend/models/PlayerAR.php
@@ -131,8 +131,9 @@ public function rules()
$this->addError($attribute, '{attribute} must be empty when player active.');
},'on'=>'validator'],
//['email', 'email','checkDNS'=>true,'on'=>'validator','message'=>'This domain does not resolve.'],
+ //['email', '\app\components\validators\VerifymailValidator', 'when' => function($model) { return (bool)Yii::$app->sys->signup_ValidatemailValidator;}],
//['email', '\app\components\validators\StopForumSpamValidator', 'max'=>Yii::$app->sys->signup_StopForumSpamValidator,'when' => function($model) { return Yii::$app->sys->signup_StopForumSpamValidator!==false;},'on'=>'validator'],
- ['email', '\app\components\validators\MXServersValidator', 'mxonly'=>false, 'when' => function($model) { return Yii::$app->sys->signup_MXServersValidator!==false;},'on'=>'validator'],
+ //['email', '\app\components\validators\MXServersValidator', 'mxonly'=>false, 'when' => function($model) { return Yii::$app->sys->signup_MXServersValidator!==false;},'on'=>'validator'],
];
}
diff --git a/backend/modules/frontend/views/player/index.php b/backend/modules/frontend/views/player/index.php
index 0a771305f..f7069ad55 100644
--- a/backend/modules/frontend/views/player/index.php
+++ b/backend/modules/frontend/views/player/index.php
@@ -59,6 +59,8 @@
],
[
'attribute' => 'avatar',
+ 'label'=>false,
+ 'headerOptions' => ['style' => 'width:3.5em'],
'format' => ['image', ['width' => '40px', 'class' => 'img-thumbnail']],
'value' => function ($data) {
return '//' . Yii::$app->sys->offense_domain . '/images/avatars/' . $data->profile->avatar;
@@ -88,7 +90,11 @@
return Html::encode($model->affiliation);
},
],
- 'email:email',
+ [
+ 'attribute'=>'email',
+ 'format'=>'email',
+ 'contentOptions' => ['class' => 'small'],
+ ],
[
'attribute' => 'vpn_local_address',
'label' => 'VPN IP',
@@ -101,22 +107,26 @@
[
'attribute' => 'academic',
'value' => 'academicShort',
+ 'contentOptions' => ['class' => 'small'],
'filter' => [0 => Yii::$app->sys->academic_0short, 1 => Yii::$app->sys->academic_1short, 2 => Yii::$app->sys->academic_2short],
],
[
'attribute' => 'status',
'format' => 'playerStatus',
'filter' => [10 => 'Enabled', 9 => 'Inactive', 8 => "Change", 0 => "Deleted",],
+ 'contentOptions' => ['class' => 'small'],
+
],
[
'attribute' => 'type',
- 'filter' => ["offense"=>"offense","defense"=>"defense"]
+ 'filter' => ["offense"=>"offense","defense"=>"defense"],
+ 'contentOptions' => ['class' => 'small'],
],
[
'attribute'=>'created',
- 'contentOptions' => ['class' => 'text-small']
+ 'contentOptions' => ['class' => 'small']
],
//'ts',
[
@@ -124,11 +134,11 @@
'visibleButtons'=>[
'generate-ssl'=>function($model){ if ($model->status==10 || $model->active==1) return true; return false;},
'mail'=>function($model){ if ($model->status==10 || $model->active==1) return false; return true;},
- 'kill-vpn'=>function($model){ if ($model->last->vpn_local_address!==null) return true; return false;},
+ 'clear-vpn'=>function($model){ if ($model->last->vpn_local_address!==null) return true; return false;},
'delete'=>function($model){ if (\Yii::$app->user->identity->isAdmin) return true; return false;},
'reset-activkey'=>function($model){ if ($model->active && trim($model->activkey)!=="") return true; return false;},
],
- 'template' => '{player-view-full} {kill-vpn} {view} {generate-ssl} {set-deleted} ' . '{update} {delete} {ban} {mail} {reset-activkey}',
+ 'template' => '{player-view-full} {clear-vpn} {view} {generate-ssl} {set-deleted} ' . '{update} {delete} {ban} {mail} {reset-activkey}',
'header' => Html::a(
'',
['ban-filtered'],
@@ -167,12 +177,12 @@
],
]);
},
- 'kill-vpn' => function($url, $model) {
- return Html::a('', ['kill-vpn', 'id' => $model->id], [
+ 'clear-vpn' => function($url, $model) {
+ return Html::a('', ['clear-vpn', 'id' => $model->id], [
'class' => '',
- 'title'=>'Kill VPN Session',
+ 'title'=>'Clear VPN Session',
'data' => [
- 'confirm' => 'Are you absolutely sure you want to kill the vpn session for ['.Html::encode($model->username).'] ?',
+ 'confirm' => 'Are you absolutely sure you want to clear the vpn session for ['.Html::encode($model->username).'] ?',
'method' => 'post',
],
]);
diff --git a/backend/modules/settings/models/ConfigureForm.php b/backend/modules/settings/models/ConfigureForm.php
index 31d8b3d3f..636faf9bc 100644
--- a/backend/modules/settings/models/ConfigureForm.php
+++ b/backend/modules/settings/models/ConfigureForm.php
@@ -55,8 +55,8 @@ class ConfigureForm extends Model
public $leaderboard_visible_after_event_end;
public $leaderboard_show_zero;
public $time_zone;
- public $target_days_new;
- public $target_days_updated;
+ public $target_days_new=2;
+ public $target_days_updated=1;
public $discord_invite_url;
public $discord_news_webhook;
public $pf_state_limits;
@@ -219,8 +219,6 @@ public function rules()
'mail_fromName',
'approved_avatar',
'team_manage_members',
- 'target_days_new',
- 'target_days_updated',
], 'required'],
['challenge_home','default','value'=>'@web/uploads'],
['challenge_root','default','value'=>'/uploads/'],
@@ -233,6 +231,8 @@ public function rules()
[['online_timeout', 'spins_per_day','members_per_team','target_days_new','target_days_updated'], 'integer'],
[['online_timeout'], 'default', 'value'=>900],
[['spins_per_day'], 'default', 'value'=> 2],
+ ['target_days_new','default','value'=>1],
+ ['target_days_updated','default','value'=>2],
[['event_start','event_end','registrations_start','registrations_end'], 'datetime', 'format' => 'php:Y-m-d H:i:s'],
[[
'dashboard_is_home',
diff --git a/backend/modules/settings/models/Openvpn.php b/backend/modules/settings/models/Openvpn.php
index 60254971f..7c827ef45 100644
--- a/backend/modules/settings/models/Openvpn.php
+++ b/backend/modules/settings/models/Openvpn.php
@@ -9,6 +9,7 @@
*
* @property int $id
* @property string|null $provider_id
+ * @property string|null $server
* @property string|null $name
* @property int|null $net
* @property int|null $mask
@@ -41,10 +42,9 @@ public function rules()
[['net', 'mask', 'management_ip', 'management_port'], 'integer'],
[['conf'], 'string'],
[['created_at', 'updated_at'], 'safe'],
- [['provider_id', 'name', 'management_passwd','status_log'], 'string', 'max' => 255],
+ [['provider_id', 'name', 'management_passwd','status_log','server'], 'string', 'max' => 255],
[['net_octet','mask_octet','management_ip_octet'], 'ip'],
- [['name'], 'unique'],
- [['net'], 'unique'],
+ [['server','name','net'], 'unique','targetAttribute'=>['server','name','net']],
];
}
@@ -56,6 +56,7 @@ public function attributeLabels()
return [
'id' => Yii::t('app', 'ID'),
'provider_id' => Yii::t('app', 'Provider ID'),
+ 'server' => Yii::t('app', 'Server'),
'name' => Yii::t('app', 'Name'),
'net' => Yii::t('app', 'Net'),
'mask' => Yii::t('app', 'Mask'),
@@ -87,10 +88,12 @@ public function beforeSave($insert)
{
return false;
}
-
- $this->net=ip2long($this->net_octet);
- $this->mask=ip2long($this->mask_octet);
- $this->management_ip=ip2long($this->management_ip_octet);
+ if(empty($this->net))
+ $this->net=ip2long($this->net_octet);
+ if(empty($this->mask))
+ $this->mask=ip2long($this->mask_octet);
+ if(empty($this->management_ip))
+ $this->management_ip=ip2long($this->management_ip_octet);
return true;
}
diff --git a/backend/modules/settings/views/openvpn/_form.php b/backend/modules/settings/views/openvpn/_form.php
index 72cf59613..6cd4c4f9f 100644
--- a/backend/modules/settings/views/openvpn/_form.php
+++ b/backend/modules/settings/views/openvpn/_form.php
@@ -14,7 +14,9 @@
= $form->field($model, 'provider_id')->textInput(['maxlength' => true,'placeholder'=>'vpn01-eu01.example.com'])->hint("An string to help you distinguish the server instance that this entry refers to (eg. vpn01-eu01.example.com)") ?>
- = $form->field($model, 'name')->textInput(['maxlength' => true,'placeholder'=>'openvpn_tun0.conf'])->hint("A unique name to distinguish this instance from others on the same server, this can be the configuration file name (eg. server_tun0.conf)") ?>
+ = $form->field($model, 'server')->textInput(['maxlength' => true,'placeholder'=>'vpn.example.com'])->hint("The server name for this entry)") ?>
+
+ = $form->field($model, 'name')->textInput(['maxlength' => true,'placeholder'=>'openvpn_tun0.conf'])->hint("The configuration file name for this instance of openvpn (eg. server_tun0.conf)") ?>
= $form->field($model, 'net_octet')->textInput(['maxlength' => true,'placeholder'=>'10.10.0.0'])->hint("Network address that this OpenVPN instance serves. This must reflect your server block ie server 10.10.0.0 255.255.0.0
") ?>
diff --git a/backend/modules/settings/views/openvpn/index.php b/backend/modules/settings/views/openvpn/index.php
index dc051f7de..3fc3c49d0 100644
--- a/backend/modules/settings/views/openvpn/index.php
+++ b/backend/modules/settings/views/openvpn/index.php
@@ -33,6 +33,7 @@
'columns' => [
'id',
'provider_id',
+ 'server',
'name',
'net_octet',
'mask_octet',
diff --git a/backend/modules/settings/views/openvpn/view.php b/backend/modules/settings/views/openvpn/view.php
index fc6462c05..3333c21ea 100644
--- a/backend/modules/settings/views/openvpn/view.php
+++ b/backend/modules/settings/views/openvpn/view.php
@@ -31,6 +31,7 @@
'attributes' => [
'id',
'provider_id',
+ 'server',
'name',
'net_octet',
'mask_octet',
diff --git a/contrib/Dockerfile-backend b/contrib/Dockerfile-backend
index f09cdeddd..24be11053 100644
--- a/contrib/Dockerfile-backend
+++ b/contrib/Dockerfile-backend
@@ -33,11 +33,6 @@ RUN set -ex \
&& chmod a+rwx /var/www/echoCTF.RED/${RED_APP}/web/uploads \
&& cd ${RED_APP} \
&& composer validate \
-# [ -z "${GITHUB_OAUTH_TOKEN}" ] || git config --global url."https://".insteadOf "git://" ; \
-# [ -z "${GITHUB_OAUTH_TOKEN}" ] || composer config -g github-oauth.github.com "${GITHUB_OAUTH_TOKEN}"; \
-# [ -z "${GITHUB_OAUTH_TOKEN}" ] || composer config --global github-protocols https; \
-# [ -z "${GITHUB_OAUTH_TOKEN}" ] || composer install --no-dev --prefer-dist --no-progress --no-suggest ; \
-# [ -z "${GITHUB_OAUTH_TOKEN}" ] || composer clearcache; \
&& cd .. \
&& mv /var/www/html /var/www/html.old \
&& ln -s /var/www/echoCTF.RED/${RED_APP}/web /var/www/html \
diff --git a/contrib/Dockerfile-vpn b/contrib/Dockerfile-vpn
index 251c89cb4..9e0a79ffc 100644
--- a/contrib/Dockerfile-vpn
+++ b/contrib/Dockerfile-vpn
@@ -28,10 +28,6 @@ RUN set -ex \
touch /sbin/pfctl && chmod +x /sbin/pfctl; \
ln -s /var/www/echoCTF.RED/${RED_APP}/yii /usr/local/sbin/backend; \
git config --global url."https://".insteadOf "git://" ; \
-# [ -z "${GITHUB_OAUTH_TOKEN}" ] || composer config -g github-oauth.github.com "${GITHUB_OAUTH_TOKEN}"; \
-# [ -z "${GITHUB_OAUTH_TOKEN}" ] || composer config --global github-protocols https; \
-# [ -z "${GITHUB_OAUTH_TOKEN}" ] || composer install --no-dev --prefer-dist --no-progress --no-suggest; \
-# [ -z "${GITHUB_OAUTH_TOKEN}" ] || composer clearcache; \
chmod +x /entrypoint.sh; useradd _openvpn; \
touch /var/log/openvpn/openvpn.log; \
rm -rf /root/.composer /usr/src/* /var/lib/apt/lists/*
diff --git a/contrib/entrypoint-vpn.sh b/contrib/entrypoint-vpn.sh
index fb7e8fb54..7a3ef6b44 100644
--- a/contrib/entrypoint-vpn.sh
+++ b/contrib/entrypoint-vpn.sh
@@ -24,6 +24,7 @@ if [ ! -f /etc/openvpn/.configured ]; then
/var/www/echoCTF.RED/backend/yii ssl/create-ca
/var/www/echoCTF.RED/backend/yii ssl/get-ca 1
/var/www/echoCTF.RED/backend/yii ssl/create-cert "VPN Server"
+ /var/www/echoCTF.RED/backend/yii vpn/load /etc/openvpn/openvpn_tun0.conf
mv echoCTF-OVPN-CA.crt /etc/openvpn/private/echoCTF-OVPN-CA.crt
mv echoCTF-OVPN-CA.key /etc/openvpn/private/echoCTF-OVPN-CA.key
mv VPN\ Server.crt /etc/openvpn/private/VPN\ Server.crt
diff --git a/docs/Sysconfig-Keys.md b/docs/Sysconfig-Keys.md
index 837229fd0..27a7ce04c 100644
--- a/docs/Sysconfig-Keys.md
+++ b/docs/Sysconfig-Keys.md
@@ -22,6 +22,8 @@
* `players_require_approval` If player activation requires moderator approval first
* `disable_registration` Whether online registrations are allowed
* `team_visible_instances` Whether or not player instances are visible to the rest of the team by default otherwise the per-instance field `team_allowed` takes priority
+* `guest_visible_leaderboards` Whether or not the leaderboards will be visible to guest users (this still respects the event start/end restrictions)
+* `hide_timezone` Whether or not the Timezone information should be visible
* `profile_discord`: Whether the field will be visible under the player profile page. This is different than `profile_settings_fields`
* `profile_echoctf`: Whether the field will be visible under the player profile page. This is different than `profile_settings_fields`
@@ -117,6 +119,8 @@ backend/yii sysconfig/set academic_1short "anothersite"
* `signup_TotalRegistrationsValidator` Number of total registrations allowed per single IP overall on the platform. `0` Disables the check completely
* `signup_HourRegistrationValidator` Number of total registrations per IP allowed. `0` Disables the check completely
* `signup_StopForumSpamValidator` Percentage of confidence required before we mark an email offensive from StopForumSpam (eg `80`). `0` Disables the check completely
+* `signup_VerifymailValidator` Enable or disable verifymail.io validator
+* `verifymail_key` The API key for verifymail.io
* `signup_MXServersValidator` Enable/Disable validating `MX` and `IN A` DNS records for given domains. `0` Disables the check completely
* `failed_login_ip` A number of failed logins are allowed per IP. `0` Disables the check completely
* `failed_login_ip_timeout` timeout of failed login ip counter expires
diff --git a/docs/console-commands/Vpn.md b/docs/console-commands/Vpn.md
index 68d8362c3..61732aa2a 100644
--- a/docs/console-commands/Vpn.md
+++ b/docs/console-commands/Vpn.md
@@ -2,25 +2,57 @@
General purpose VPN related commands.
## is-online
-Check if a given player username or id is currently online
+Check if a given player `username` or `id` is currently online
-## kill
-Connect to an OpenVPN management port and issue a kill command.
+Usage: `./backend/yii vpn/is-online `
+
+## kill
+Kill the OpenVPN connection of a given player `username` or `id`.
+
+Usage: `./backend/yii vpn/kill `
## killall
-Kill all connections and set players offline.
+Kill all (not just online ones) player connections from OpenVPN and set players offline.
+
+Usage: `./backend/yii vpn/killall`
+
+
+This command connects to the OpenVPN management port and issues a `kill` command for each active player which currently has an IP assigned to them. Once the OpenVPN operations are complete a cleanup is performed to reset the online status of all players.
## load
Load an OpenVPN instance configuration file into the database
+Usage: `./backend/yii vpn/load `
+
+The command extracts the following details from the provided configuration file:
+* Gets the hostname from the running host
+* Player allocated Network and Netmask
+* OpenVPN Management IP, port and password to authenticate
+* OpenVPN status file location
+
## logout
-Logout a given user ignoring OpenVPN sessions
+Logout a given player `username` or `id`, from the database, ignoring OpenVPN sessions.
+
+Usage: `./backend/yii vpn/load `
+
## logoutall
-Logout all users ignoring OpenVPN sessions
+Logout all players from the database. It **does NOT** issue an OpenVPN kill command for connected players.
+
+**NOTE**: _Only issue this command when you are certain there are no users connected to OpenVPN._
## save
Save an OpenVPN instance configuration file with data from the database
+Usage: `./backend/yii vpn/save `
+
+The command looks for a record with the following criteria:
+* uses the current system `hostname` as a `server`
+* uses the filename (basename) from the openvpn configuration file provided as cli argument
+
## status
A small wrapper for OpenVPN status files. The details are merged with data from the database.
+
+Usage: `./backend/yii vpn/status`
+
+The command uses the status file defined in the Backend Settings=>OpenVPN entries matching the current server.
diff --git a/frontend/commands/ValidatorController.php b/frontend/commands/ValidatorController.php
index fce5de705..0c0db6e8d 100644
--- a/frontend/commands/ValidatorController.php
+++ b/frontend/commands/ValidatorController.php
@@ -12,7 +12,6 @@
use app\models\Stream;
class ValidatorController extends Controller {
-
public function actionIndex()
{
$this->stdout("The Validator provides the following actions: \n", Console::BOLD);
diff --git a/frontend/components/validators/VerifymailValidator.php b/frontend/components/validators/VerifymailValidator.php
new file mode 100644
index 000000000..bccd0c047
--- /dev/null
+++ b/frontend/components/validators/VerifymailValidator.php
@@ -0,0 +1,112 @@
+sys->signup_ValidatemailValidator===false)
+ return;
+
+ $data = http_build_query(['key'=>\Yii::$app->sys->verifymail_key]);
+
+ $domain=$value;
+
+ if(str_contains($value,'@'))
+ {
+ $parts=explode("@",$value);
+ if(count($parts)<2)
+ {
+ return ['Verifymail.io: Failed to parse email!', [
+ 'email' => $value,
+ ]];
+ }
+ $domain=end($parts);
+ }
+
+ $req=$this->url.$domain.'?'.$data;
+ $ch = curl_init($req);
+
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($ch, CURLOPT_TIMEOUT, 40);
+
+ try
+ {
+ $result = curl_exec($ch);
+ curl_close($ch);
+ $retData=json_decode($result);
+ if($retData != null && $retData->disposable===true)
+ {
+ return [$this->message, [
+ 'email' => $value,
+ ]];
+ }
+ }
+ catch(\Exception $e)
+ {
+ if(curl_errno($ch)===0)
+ return [$this->message, ['email' => $value]];
+ }
+ }
+
+ public function validateAttribute($model, $attribute)
+ {
+ if(\Yii::$app->sys->signup_ValidatemailValidator===false)
+ return;
+ $value = $model->$attribute;
+
+ $data = http_build_query(['key'=>\Yii::$app->sys->verifymail_key]);
+
+ if(str_contains($value,'@'))
+ {
+ $parts=explode("@",$value);
+ if(count($parts)<2)
+ {
+ return ['Verifymail.io: Failed to parse email!', [
+ 'email' => $value,
+ ]];
+ }
+ $domain=end($parts);
+ }
+
+ $req=$this->url.$domain.'?'.$data;
+ $ch = curl_init($req);
+
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($ch, CURLOPT_TIMEOUT, 40);
+
+ try
+ {
+ $result = curl_exec($ch);
+ curl_close($ch);
+ $retData=json_decode($result);
+ if($retData && $retData->message)
+ {
+ \Yii::error("Verifymail.io return message: ".$retData->message);
+ }
+ if($retData != null && $retData->disposable===true)
+ {
+ return [$this->message, ['email' => $value]];
+ }
+ }
+ catch(\Exception $e)
+ {
+ if(curl_errno($ch)===0)
+ return [$this->message, ['email' => $value]];
+ }
+ }
+}
diff --git a/frontend/models/forms/SignupForm.php b/frontend/models/forms/SignupForm.php
index 13fd2b838..1d62c233c 100644
--- a/frontend/models/forms/SignupForm.php
+++ b/frontend/models/forms/SignupForm.php
@@ -54,6 +54,7 @@ public function rules()
}],
['username', '\app\components\validators\HourRegistrationValidator', 'client_ip'=>\Yii::$app->request->userIp, 'max'=>Yii::$app->sys->signup_HourRegistrationValidator,'when' => function($model) { return Yii::$app->sys->signup_HourRegistrationValidator!==false;}],
['username', '\app\components\validators\TotalRegistrationsValidator', 'client_ip'=>\Yii::$app->request->userIp, 'max'=>Yii::$app->sys->signup_TotalRegistrationsValidator,'when' => function($model) { return Yii::$app->sys->signup_TotalRegistrationsValidator!==false;}],
+ ['email', '\app\components\validators\VerifymailValidator', 'when' => function($model) { return (bool)Yii::$app->sys->signup_ValidatemailValidator;}],
['email', '\app\components\validators\StopForumSpamValidator', 'max'=>Yii::$app->sys->signup_StopForumSpamValidator,'when' => function($model) { return Yii::$app->sys->signup_StopForumSpamValidator!==false;}],
['email', '\app\components\validators\MXServersValidator', 'mxonly'=>true, 'when' => function($model) { return Yii::$app->sys->signup_MXServersValidator!==false;}],
//['email', '\app\components\validators\WhoisValidator', ],
diff --git a/frontend/modules/game/controllers/LeaderboardsController.php b/frontend/modules/game/controllers/LeaderboardsController.php
index f66051f89..8f75111f4 100644
--- a/frontend/modules/game/controllers/LeaderboardsController.php
+++ b/frontend/modules/game/controllers/LeaderboardsController.php
@@ -56,6 +56,14 @@ public function behaviors()
'allow' => true,
'roles' => ['@']
],
+ [
+ 'allow' => true,
+ 'roles' => ['?'],
+ 'matchCallback' => function () {
+ return \Yii::$app->sys->guest_visible_leaderboards;
+ },
+ ],
+
],
]
]);
diff --git a/frontend/themes/material/layouts/left.php b/frontend/themes/material/layouts/left.php
index e0fc55911..9b9c736f8 100644
--- a/frontend/themes/material/layouts/left.php
+++ b/frontend/themes/material/layouts/left.php
@@ -20,7 +20,7 @@
" style="font-size: 0.75em"> =Html::encode(Yii::$app->user->identity->username)?> (=number_format(Yii::$app->user->identity->profile->score->points)?> pts
)
- =\Yii::t('app','Server time:')?> =date('H:i');?> =date_default_timezone_get()?>
+ =\Yii::t('app','Server time:')?> =date('H:i');?>sys->hide_timezone):?> =date_default_timezone_get()?>