diff --git a/app/Classes/Frame.php b/app/Classes/Frame.php index 9b4ed63..05ee756 100644 --- a/app/Classes/Frame.php +++ b/app/Classes/Frame.php @@ -48,6 +48,11 @@ abstract class Frame protected $cost_unit = 'u'; */ + const FRAMETYPE_INFO = 'i'; + const FRAMETYPE_ACTION = 'a'; + const FRAMETYPE_LOGIN = 'l'; + const FRAMETYPE_TERMINATE = 't'; + public $fields = NULL; // The fields in this frame. // Magic Fields that are pre-filled @@ -58,9 +63,9 @@ abstract class Frame // Fields that are editable private $fieldoptions = [ - 'a'=>['edit'=>TRUE], // Address - 'p'=>['edit'=>TRUE], // Password - 'u'=>['edit'=>TRUE], // User + 'p'=>['edit'=>TRUE,'mask'=>'*'], // Password + 'u'=>['edit'=>TRUE], // User + 't'=>['edit'=>TRUE], // Text ]; // @todo Move this to the database @@ -78,7 +83,7 @@ abstract class Frame $startline = 0; - if (! $this->hasFlag('ip')) { + if (! $this->hasFlag('ip') AND (! $this->isCUG(0) OR $this->type() !== self::FRAMETYPE_LOGIN)) { // Set the page header: CUG/Site Name | Page # | Cost $this->output .= $this->render_header($this->header). $this->render_page($this->frame->frame,$this->frame->index). @@ -113,6 +118,7 @@ abstract class Frame ->where('index',$this->index()) ->where('id','<>',$this->frame->id) ->where('mode_id',$o->id) + ->where('access',1) ->limit(9); } @@ -130,7 +136,7 @@ abstract class Frame * * @param int $startline */ - abstract public function fields($startline=0,$fieldchar='.'); + abstract public function fields($startline=0); /** * Returns the current frame. @@ -191,6 +197,11 @@ abstract class Frame }); } + public function getFieldOptions(int $id) + { + return array_get($this->fieldoptions,$this->getField($id)->type); + } + /** * Return the flag for this page * @@ -258,6 +269,11 @@ abstract class Frame return array_get(array_get($this->fieldoptions,$field),'edit',FALSE); } + public function isFieldMasked(string $field) + { + return array_get(array_get($this->fieldoptions,$field),'mask',FALSE); + } + /** * Is this frame Public * @@ -354,8 +370,15 @@ abstract class Frame */ public function route(string $read) { - // @todo - return FALSE; + if (! preg_match('/^[0-9]$/',$read)) + throw new \Exception('Routes are single digit'); + + // If we dont have a route record... + if (! $this->frame->route) + return '*'; + + $key = 'r'.$read; + return $this->frame->route->{$key}; } /** diff --git a/app/Classes/Frame/Action.php b/app/Classes/Frame/Action.php new file mode 100644 index 0000000..56f34ba --- /dev/null +++ b/app/Classes/Frame/Action.php @@ -0,0 +1,38 @@ +so = $so; + $this->uo = $uo; + $this->action = $action; + $this->mode = $mode; + } + + public static function factory(string $class,Server $so,User $uo,int $action,int $mode) + { + $c = self::prefix.$class; + $o = class_exists($c) ? new $c($so,$uo,$action,$mode) : FALSE; + + $so->log('debug',sprintf(($o ? 'Executing: %s' : 'Class doesnt exist: %s'),$c)); + + return $o; + } + + abstract public function handle(array $fielddata); +} \ No newline at end of file diff --git a/app/Classes/Frame/Action/Login.php b/app/Classes/Frame/Action/Login.php new file mode 100644 index 0000000..c7d5561 --- /dev/null +++ b/app/Classes/Frame/Action/Login.php @@ -0,0 +1,56 @@ +mode = 2; // MODE_FIELD + // $this->action = 2; // ACTION_GOTO + + $this->so->sendBaseline($this->so->client(),RED.'INVALID DETAILS, TRY AGAIN *00'); + + return FALSE; + } + + try { + $this->uo = User::where('login',array_get($fielddata,0))->firstOrFail(); + + } catch (ModelNotFoundException $e) { + $this->so->sendBaseline($this->so->client(),RED.'USER NOT FOUND, TRY AGAIN *00'); + + return FALSE; + } + + if ($this->uo->password != array_get($fielddata,1)) + { + $this->uo = new User; + $this->so->sendBaseline($this->so->client(),RED.'INVALID PASSWORD, TRY AGAIN *00'); + + return FALSE; + } + + $this->page = ['frame'=>1,'index'=>'a']; // @todo Get from DB. + + $this->action = 2; // ACTION_GOTO + $this->mode = FALSE; + + return TRUE; + } +} \ No newline at end of file diff --git a/app/Classes/Frame/Ansi.php b/app/Classes/Frame/Ansi.php index 62774a1..014f17d 100644 --- a/app/Classes/Frame/Ansi.php +++ b/app/Classes/Frame/Ansi.php @@ -16,7 +16,9 @@ class Ansi extends AbstractFrame public static $cost_length = 7; public static $cost_unit = 'u'; - public function fields($startline=0,$fieldchar='.') + public static $if_filler = '.'; + + public function fields($startline=0) { $this->output .= str_replace(LF,CR.LF,$this->frame->content); } diff --git a/app/Classes/Frame/Videotex.php b/app/Classes/Frame/Videotex.php index 71f3757..479e798 100644 --- a/app/Classes/Frame/Videotex.php +++ b/app/Classes/Frame/Videotex.php @@ -17,7 +17,9 @@ class Videotex extends AbstractFrame public static $cost_length = 7; public static $cost_unit = 'u'; - public function fields($startline=0,$fieldchar='.') + public static $if_filler = '.'; + + public function fields($startline=0) { $infield = FALSE; // In a field $fieldtype = NULL; // Type of field @@ -46,13 +48,13 @@ class Videotex extends AbstractFrame $infield = TRUE; $fieldlength = 1; $fieldtype = ord(substr($this->frame->content,$posn+1,1))%128; - $this->output .= $fieldchar; + $this->output .= static::$if_filler; } else { if ($infield) { if ($byte == $fieldtype) { $fieldlength++; - $byte = ord($fieldchar); // Replace field with $fieldchar. + $byte = ord(static::$if_filler); // Replace field with static::$if_filler. if ($fieldx === FALSE) { $fieldx = $x; @@ -79,7 +81,7 @@ class Videotex extends AbstractFrame // Drop the last dot and replace it. if ($fieldlength == 2) { $datetime = date('D d M H:ia'); - $this->output = rtrim($this->output,$fieldchar); + $this->output = rtrim($this->output,static::$if_filler); $this->output .= $datetime{0}; } diff --git a/app/Classes/Server.php b/app/Classes/Server.php index 2e5e43c..9ec65ec 100644 --- a/app/Classes/Server.php +++ b/app/Classes/Server.php @@ -14,6 +14,7 @@ use App\Models\Mode; abstract class Server { private $mo = NULL; // Our Mode object + private $co = NULL; protected $blp = 0; // Size of Bottom Line Pollution protected $pid = NULL; // Client PID @@ -21,49 +22,46 @@ abstract class Server { { $this->mo = $o; - define('MODE_BL',1); // Typing a * command on the baseline - define('MODE_FIELD',2); // typing into an imput field - define('MODE_WARPTO',3); // awaiting selection of a timewarp - define('MODE_COMPLETE',4); // Entry of data is complete .. - define('MODE_SUBMITRF',5); // asking if should send or not. - define('MODE_RFSENT',6); - define('MODE_RFERROR',7); - define('MODE_RFNOTSENT',8); + define('MODE_BL', 1); // Typing a * command on the baseline + define('MODE_FIELD', 2); // typing into an imput field + define('MODE_WARPTO', 3); // awaiting selection of a timewarp + define('MODE_COMPLETE', 4); // Entry of data is complete .. + define('MODE_SUBMITRF', 5); // asking if should send or not. + define('MODE_RFSENT', 6); + define('MODE_RFERROR', 7); + define('MODE_RFNOTSENT', 8); - define('ACTION_RELOAD',1); - define('ACTION_GOTO',2); - define('ACTION_BACKUP',3); - define('ACTION_NEXT',4); - define('ACTION_INFO',5); - define('ACTION_TERMINATE',6); - define('ACTION_SUBMITRF',7); // Offer to submit a response frame - define('ACTION_STAR',8); - - define('FRAMETYPE_INFO','i'); - define('FRAMETYPE_ACTION','a'); - define('FRAMETYPE_LOGIN','l'); + define('ACTION_RELOAD', 1); + define('ACTION_GOTO', 2); + define('ACTION_BACKUP', 3); + define('ACTION_NEXT', 4); + define('ACTION_INFO', 5); + define('ACTION_TERMINATE', 6); + define('ACTION_SUBMITRF', 7); // Offer to submit a response frame + define('ACTION_STAR', 8); // Keyboard presses - define('KEY_LEFT',chr(136)); - define('KEY_RIGHT',chr(137)); - define('KEY_DOWN',chr(138)); - define('KEY_UP',chr(139)); + define('KEY_DELETE', chr(8)); + define('KEY_LEFT', chr(136)); + define('KEY_RIGHT', chr(137)); + define('KEY_DOWN', chr(138)); + define('KEY_UP', chr(139)); - define('TCP_IAC',chr(255)); - define('TCP_DONT',chr(254)); - define('TCP_DO',chr(253)); - define('TCP_WONT',chr(252)); - define('TCP_WILL',chr(251)); - define('TCP_SB',chr(250)); - define('TCP_AYT',chr(246)); - define('TCP_SE',chr(240)); + define('TCP_IAC', chr(255)); + define('TCP_DONT', chr(254)); + define('TCP_DO', chr(253)); + define('TCP_WONT', chr(252)); + define('TCP_WILL', chr(251)); + define('TCP_SB', chr(250)); + define('TCP_AYT', chr(246)); + define('TCP_SE', chr(240)); - define('TCP_BINARY',chr(0)); - define('TCP_OPT_ECHO',chr(1)); - define('TCP_OPT_SUP_GOAHEAD',chr(3)); - define('TCP_OPT_TERMTYPE',chr(24)); - define('TCP_OPT_WINDOWSIZE',chr(31)); - define('TCP_OPT_LINEMODE',chr(34)); + define('TCP_BINARY', chr(0)); + define('TCP_OPT_ECHO', chr(1)); + define('TCP_OPT_SUP_GOAHEAD', chr(3)); + define('TCP_OPT_TERMTYPE', chr(24)); + define('TCP_OPT_WINDOWSIZE', chr(31)); + define('TCP_OPT_LINEMODE', chr(34)); define('MSG_SENDORNOT', GREEN.'KEY 1 TO SEND, 2 NOT TO SEND'); define('MSG_SENT', GREEN.'MESSAGE SENT - KEY _ TO CONTINUE'); @@ -82,6 +80,11 @@ abstract class Server { define('MSG_TIMEWARP', WHITE.'OTHER VERSIONS EXIST'.GREEN.'KEY *02 TO VIEW'); } + public function client() + { + return $this->co; + } + public function log(string $mode,string $message,array $data=[]) { Log::$mode(sprintf('%s: %s',$this->pid,$message),$data); @@ -104,6 +107,8 @@ abstract class Server { elseif ($pid) return; + $fo = NULL; + $this->co = $client; $this->pid = getmypid(); $this->log('info','Connection from: ',['client'=>$client->getAddress(),'server'=>$this->mo->name]); @@ -128,7 +133,7 @@ abstract class Server { $action = ACTION_GOTO; // Initial action. $cmd = ''; // Current *command being typed in $mode = FALSE; // Current mode. - $user = User::find(1); // The logged in user + $user = new User; // The logged in user $current = []; // Attributes about the current page // field/fieldnum indexes are for fields on the active page @@ -142,7 +147,7 @@ abstract class Server { } else if (!empty($service['start_page'])) { $page = ['frame'=>$service['start_page'],'index'=>'a']; } else { - $page = ['frame'=>'98','index'=>'a']; // next page + $page = ['frame'=>'980','index'=>'a']; // next page } while ($action != ACTION_TERMINATE) { @@ -155,8 +160,6 @@ abstract class Server { $read = NULL; if ($read != '') { - dump(sprintf('Mode: [%s] CMD: [%s] frame: [%s] Received [%s (%s)]', $mode, $cmd, $page['frame'].$page['index'], $read, ord($read))); - // Client initiation input // TELNET http://pcmicro.com/netfoss/telnet.html if ($read == TCP_IAC OR $session_init OR $session_option) { @@ -241,15 +244,29 @@ abstract class Server { switch ($mode) { // Key presses during field input. case MODE_FIELD: - dump(sprintf('** Processing Keypress in MODE_FIELD [%s (%s)]. Last POS [%s]',$read,ord($read),$current['fieldpos'])); $cmd = ''; $action = FALSE; switch ($fo->type()) { // Login frame. - case 'l': + case Frame::FRAMETYPE_LOGIN: + switch ($read) { + case HASH: + // If we are the main login screen, see if it is a new user + if ($fo->isCUG(0)) + { + if ($current['field']->type == 'u' AND array_get($fielddata,$current['fieldnum']) == 'NEW') + { + $action = ACTION_GOTO; + $page = ['frame'=>'981','index'=>'a']; // @todo This should be in the DB. + } + } + + break; + } + // Response frame. - case 'a': + case Frame::FRAMETYPE_ACTION: switch ($read) { // End of field entry. case HASH: @@ -286,9 +303,21 @@ abstract class Server { break; + case KEY_DELETE: + if ($current['fieldpos']) + { + $current['fieldpos']--; + $client->send(LEFT.$fo::$if_filler.LEFT); + $fielddata[$current['fieldnum']] = substr($fielddata[$current['fieldnum']],0,-1); + } + + break; + case KEY_LEFT: - if ($current['fieldpos']--) + if ($current['fieldpos']) { + $current['fieldpos']--; $client->send(LEFT); + } break; @@ -338,7 +367,8 @@ abstract class Server { $fielddata[$current['fieldnum']]{$current['fieldpos']} = $read; $current['fieldpos']++; - $client->send($read); + + $client->send($fo->isFieldMasked($current['field']->type) ?: $read); } } @@ -349,7 +379,6 @@ abstract class Server { $client->close(); throw new \Exception('Shouldnt get here', 500); - } break; @@ -359,15 +388,26 @@ abstract class Server { switch ($read) { // @todo Input received, process it. case '1': - // dump(['line'=>__LINE__,'f' => $fielddata]); - // @todo if send successful or not - if (TRUE) { - $this->sendBaseline($client,MSG_SENT); + $route = $fo->route(1); + + if ($route == '*' OR is_numeric($route)) { + $this->sendBaseline($client, RED . 'NO action performed'); $mode = MODE_RFSENT; + } elseif ($ao = FrameClass\Action::factory($fo->route(1),$this,$user,$action,$mode)) { + + $ao->handle($fielddata); + $mode = $ao->mode; + $action = $ao->action; + $user = $ao->uo; + + if ($ao->page) + $page = $ao->page; + } else { - $this->sendBaseline($client,ERR_NOTSENT); - $mode = MODE_RFERROR; + $this->sendBaseline($client, RED . 'NO method exists...'); + + $mode = MODE_RFSENT; } break; @@ -570,7 +610,7 @@ abstract class Server { $mode = $current['prevmode']; $current['prevmode'] = FALSE; $client->send($this->outputPosition($current['field']->x,$current['field']->y).CON); - $client->send(str_repeat('.', $current['field']->length)); + $client->send(str_repeat($fo::$if_filler, $current['field']->length)); $current['fieldreset'] = TRUE; } else { @@ -586,10 +626,14 @@ abstract class Server { $timewarpalt = FALSE; // Nothing typed between * and # + // *# means go back if ($cmd === '') { $action = ACTION_BACKUP; - // *# means go back + } elseif ($cmd === '0') { + $page = $user->exists ? ['frame'=>1,'index'=>'a'] : ['frame'=>980,'index'=>'a']; // @todo Get from DB. + $action = ACTION_GOTO; + } else { $page['frame'] = $cmd; $page['index'] = 'a'; @@ -653,13 +697,15 @@ abstract class Server { // Look for requested page case ACTION_GOTO: + $current['frame'] = $fo; + // If we wanted a "Searching..." message, this is where to put it. try { $fo = $timewarpalt ? $this->mo->frame(FrameModel::findOrFail($timewarpalt)) : $this->mo->frameLoad($page['frame'],$page['index'],$this); - $this->log('debug',sprintf('Fetched frame: %s',$fo->id())); + $this->log('debug',sprintf('Fetched frame: %s (%s)',$fo->id(),$fo->page())); } catch (ModelNotFoundException $e) { $this->sendBaseline($client,ERR_PAGE); @@ -676,10 +722,13 @@ abstract class Server { { if ($fo->isFramePublic() AND $fo->isAccessible()) { - if ($fo->type() == FRAMETYPE_LOGIN AND $user->isMemberCUG($fo->getCUG())) + if ($fo->type() == Frame::FRAMETYPE_LOGIN AND $user->isMemberCUG($fo->getCUG())) { $this->sendBaseline($client,ERR_USER_ALREADYMEMBER); + $fo = $current['frame']; + $page = $history->last(); $mode = $action = FALSE; + $this->log('debug',sprintf('Frame Denied - Already Member: %s (%s)',$fo->id(),$fo->page())); break; } @@ -692,7 +741,10 @@ abstract class Server { if (! $fo->isAccessible()) { $this->sendBaseline($client,ERR_PAGE); + $fo = $current['frame']; + $page = $history->last(); $mode = $action = FALSE; + $this->log('debug',sprintf('Frame Denied - In Accessible: %s (%s)',$fo->id(),$fo->page())); break; } @@ -700,7 +752,10 @@ abstract class Server { if (! $user->isMemberCUG($fo->getCUG())) { $this->sendBaseline($client, ERR_PRIVATE); + $fo = $current['frame']; + $page = $history->last(); $mode = $action = FALSE; + $this->log('debug',sprintf('Frame Denied - Not in CUG [%s]: %s (%s)',$fo->getCUG()->id,$fo->id(),$fo->page())); break; } @@ -735,8 +790,7 @@ abstract class Server { $output = CLS; } else { - $output = HOME - ; + $output = HOME; // Clear the baseline. $this->sendBaseline($client,''); } @@ -750,16 +804,16 @@ abstract class Server { switch ($fo->type()) { default: // Standard Frame - case 'i': + case Frame::FRAMETYPE_INFO: $client->send($output); $mode = $action = false; break; // Login Frame. - case 'l': + case Frame::FRAMETYPE_LOGIN: // Active Frame. Prestel uses this for a Response Frame. - case 'a': + case Frame::FRAMETYPE_ACTION: $client->send($output); // holds data entered by user. $fielddata = []; @@ -788,7 +842,7 @@ abstract class Server { break; // Terminate Frame - case 't': + case Frame::FRAMETYPE_TERMINATE: $client->send($output); $action = ACTION_TERMINATE; @@ -808,7 +862,7 @@ abstract class Server { //$output .= $this->outputPosition(0, $y++) . WHITE . NEWBG . BLUE . 'Varient : ' . substr($varient['varient_name'] . str_repeat(' ', 27), 0, 27); $output .= $this->outputPosition(0, $y++) . WHITE . NEWBG . BLUE . 'Dated : ' .substr(($fo->created() ? $fo->created()->format('j F Y') : 'Unknown').str_repeat(' ', 27), 0, 27); - $alts = $fo->alts()->get(); + $alts = $fo->alts($this->mo)->get(); if (count($alts)) { $n = 1; diff --git a/app/Console/Commands/FrameImport.php b/app/Console/Commands/FrameImport.php index be43c4c..efaa4bb 100644 --- a/app/Console/Commands/FrameImport.php +++ b/app/Console/Commands/FrameImport.php @@ -14,6 +14,8 @@ class FrameImport extends Command * @var string */ protected $signature = 'frame:import {frame} {index} {file} '. + '{--access=0 : Is frame accessible }'. + '{--closed=1 : Is frame limited to CUG }'. '{--cost=0 : Frame Cost }'. '{--mode=1 : Frame Emulation Mode }'. '{--replace : Replace existing frame}'. @@ -43,8 +45,8 @@ class FrameImport extends Command * @return mixed * @throws \Exception */ - public function handle() - { + public function handle() + { if (! is_numeric($this->argument('frame'))) throw new \Exception('Frame is not numeric: '.$this->argument('frame')); @@ -75,10 +77,12 @@ class FrameImport extends Command ? substr(file_get_contents($this->argument('file')),40) : file_get_contents($this->argument('file')); + $o->access = $this->option('access'); + $o->closed = $this->option('closed'); $o->cost = $this->option('cost'); $o->mode_id = $this->option('mode'); $o->type = $this->option('type'); $o->save(); - } -} \ No newline at end of file + } +} diff --git a/app/Models/Frame.php b/app/Models/Frame.php index 5127eb3..7571cbf 100644 --- a/app/Models/Frame.php +++ b/app/Models/Frame.php @@ -12,6 +12,11 @@ class Frame extends Model return $this->belongsTo(CUG::class); } + public function route() + { + return $this->hasOne(FrameMeta::class); + } + protected static function boot() { parent::boot(); diff --git a/app/Models/FrameMeta.php b/app/Models/FrameMeta.php new file mode 100644 index 0000000..5adc5e9 --- /dev/null +++ b/app/Models/FrameMeta.php @@ -0,0 +1,10 @@ +down(); Schema::create('framemeta', function (Blueprint $table) { - $table->integer('frame_id')->primary(); - $table->string('r0'); - $table->string('r1'); - $table->string('r2'); - $table->string('r3'); - $table->string('r4'); - $table->string('r5'); - $table->string('r6'); - $table->string('r7'); - $table->string('r8'); - $table->string('r9'); + $table->integer('frame_id')->primary(); + $table->string('r0')->default('*'); + $table->string('r1')->default('*'); + $table->string('r2')->default('*'); + $table->string('r3')->default('*'); + $table->string('r4')->default('*'); + $table->string('r5')->default('*'); + $table->string('r6')->default('*'); + $table->string('r7')->default('*'); + $table->string('r8')->default('*'); + $table->string('r9')->default('*'); $table->foreign('frame_id')->references('id')->on('frames'); });