authorize('update',$o); if ($request->post()) { foreach (['name','location','sysop','hold','phone','address','port','active','method','notes','zt_id','pkt_type','heartbeat'] as $key) $o->{$key} = $request->post($key); switch ($request->post('pollmode')) { case 1: $o->pollmode = FALSE; break; case 2: $o->pollmode = TRUE; break; default: $o->pollmode = NULL; } $o->autohold = FALSE; $o->save(); $mailers = collect($request->post('mailer_details')) ->filter(function($item) { return $item['port']; }) ->transform(function($item) { $item['active'] = Arr::get($item,'active',FALSE); return $item; }); $o->mailers()->sync($mailers); return redirect()->to('system'); } $o->load(['addresses.zone.domain','addresses.system','sessions.domain','sessions.systems']); return view('system.addedit') ->with('action',$o->exists ? 'update' : 'create') ->with('o',$o); } /** * Add an address to a system * * @param Request $request * @param System $o * @return \Illuminate\Http\RedirectResponse * @throws \Illuminate\Auth\Access\AuthorizationException */ public function address_add(Request $request,System $o) { // @todo a point address is failing validation // @todo This should be admin of the zone $this->authorize('admin',$o); session()->flash('accordion','address'); $request->validate([ 'action' => 'required|in:region,host,node,update', 'zone_id' => 'required|exists:zones,id', ]); switch ($request->post('action')) { case 'region': $request->validate([ 'region_id_new' => [ 'required', new TwoByteInteger, function ($attribute,$value,$fail) { // Check that the region doesnt already exist $o = Address::where(function($query) use ($value) { return $query->where('region_id',$value) ->where('host_id',0) ->where('node_id',0) ->where('point_id',0) ->where('role',Address::NODE_RC); }) // Check that a host doesnt already exist ->orWhere(function($query) use ($value) { return $query->where('host_id',$value) ->where('point_id',0) ->where('role',Address::NODE_NC); }); if ($o->count()) { $fail('Region or host already exists'); } }, ], ]); $oo = new Address; $oo->zone_id = $request->post('zone_id'); $oo->region_id = $request->post('region_id_new'); $oo->host_id = 0; $oo->node_id = 0; $oo->point_id = 0; $oo->role = Address::NODE_RC; $oo->active = TRUE; $o->addresses()->save($oo); break; case 'host': $request->validate([ 'region_id' => ['required',new FidoInteger], 'host_id_new' => [ 'required', new TwoByteInteger, function ($attribute,$value,$fail) use ($request) { // Check that the region doesnt already exist $o = Address::where(function($query) use ($value) { return $query->where(function($query) use ($value) { return $query->where('region_id',$value) ->where('role',Address::NODE_RC); }) // Check that a host doesnt already exist ->orWhere(function($query) use ($value) { return $query->where('host_id',$value) ->where('role',Address::NODE_NC); }); }) ->where('zone_id',$request->post('zone_id')) ->where('point_id',0) ->where('active',TRUE); if ($o->count()) { $fail('Region or host already exists'); } }, ], 'node_id_new' => [ 'required', new TwoByteInteger, function ($attribute,$value,$fail) use ($request) { // Check that the region doesnt already exist $o = Address::where(function($query) use ($request,$value) { return $query ->where('host_id',$request->post('host_id_new')) ->where('node_id',$value) ->where('point_id',0) ->where('role',Address::NODE_RC); }); if ($o->count()) { $fail('Host already exists'); } }, ] ]); // Find the Hub address // Find the zones /0 address, and assign it to this host. $oo = Address::where('zone_id',$request->zone_id) ->where('region_id',$request->region_id) ->where('host_id',$request->host_id_new) ->where('node_id',0) ->where('point_id',0) ->single(); // Its not defined, so we'll create it. if (! $oo) { $oo = new Address; $oo->forceFill([ 'zone_id'=>$request->zone_id, 'region_id'=>$request->region_id, 'host_id'=>$request->host_id_new, 'node_id'=>0, 'point_id'=>0, 'role'=>Address::NODE_NC, ]); } $oo->system_id = $request->system_id; $oo->active = TRUE; $o->addresses()->save($oo); $oo = new Address; $oo->zone_id = $request->post('zone_id'); $oo->region_id = $request->post('region_id'); $oo->host_id = $request->post('host_id_new'); $oo->node_id = $request->post('node_id_new'); $oo->point_id = 0; $oo->role = Address::NODE_ACTIVE; $oo->active = TRUE; $o->addresses()->save($oo); break; case 'update': case 'node': $request->validate([ 'region_id' => ['required',new FidoInteger], 'host_id' => ['required',new FidoInteger], 'node_id' => [ 'required', new TwoByteInteger, function ($attribute,$value,$fail) use ($request) { if ($request->point_id === 0) { // Check that the host doesnt already exist $o = Address::where(function($query) use ($request,$value) { return $query ->where('zone_id',$request->post('zone_id')) ->where('host_id',$request->post('host_id')) ->where('node_id',$value) ->where('point_id',0) ->where('id','<>',$request->post('submit')); }); if ($o->count()) { $fail(sprintf('Host already exists: %s',$o->get()->pluck('ftn')->join(','))); } } }, ], 'point_id' => [ 'required', function($attribute,$value,$fail) use ($request) { if (! is_numeric($value) || $value > DomainController::NUMBER_MAX) $fail(sprintf('Point numbers must be between 0 and %d',DomainController::NUMBER_MAX)); // Check that the host doesnt already exist $o = Address::where(function($query) use ($request,$value) { return $query ->where('zone_id',$request->post('zone_id')) ->where('host_id',$request->post('host_id')) ->where('node_id',$request->post('node_id')) ->where('point_id',$value) ->where('id','<>',$request->post('submit')); }); if ($o->count()) { $fail(sprintf('Point already exists: %s',$o->get()->pluck('ftn')->join(','))); } } ], 'hub' => 'required|boolean', 'hub_id' => 'nullable|exists:addresses,id', 'security' => 'required|integer|min:0|max:7', ]); $oo = Address::findOrNew($request->post('submit')); $oo->zone_id = $request->post('zone_id'); $oo->region_id = $request->post('region_id'); $oo->host_id = $request->post('host_id'); $oo->node_id = $request->post('node_id'); $oo->point_id = $request->post('point_id'); $oo->hub_id = $request->post('hub_id') > 0 ? $request->post('hub_id') : NULL; if (is_null($oo->role)) $oo->role = ((! $oo->point_id) && $request->post('hub')) ? Address::NODE_HC : ($request->post('point_id') ? Address::NODE_POINT : Address::NODE_ACTIVE); $oo->security = $request->post('security'); $oo->active = TRUE; $o->addresses()->save($oo); break; default: return redirect()->back()->withErrors(['action'=>'Unknown action: '.$request->post('action')]); } return redirect()->to(sprintf('system/addedit/%d',$o->id)); } /** * Delete address assigned to a host * * @param Address $o * @return \Illuminate\Http\RedirectResponse * @throws \Illuminate\Auth\Access\AuthorizationException */ public function address_del(Address $o) { // @todo This should be admin of the zone $this->authorize('admin',$o); session()->flash('accordion','address'); $sid = $o->system_id; $o->active = FALSE; $o->save(); $o->delete(); return redirect()->to(sprintf('system/addedit/%d',$sid)); } /** * Demote an address NC -> node * * @param Address $o * @return \Illuminate\Http\RedirectResponse * @throws \Illuminate\Auth\Access\AuthorizationException */ public function address_dem(Address $o) { // @todo This should be admin of the zone $this->authorize('admin',$o); session()->flash('accordion','address'); // Make sure that no other system has this address active. if ($o->role === Address::NODE_ACTIVE) return redirect()->back()->withErrors(['demaddress'=>sprintf('%s cannot be demoted any more',$o->ftn3D)]); $o->role = ($o->role << 1); $o->save(); return redirect()->to(sprintf('system/addedit/%d',$o->system_id)); } public function address_merge(AddressMerge $request,int $id) { if ($request->validated()) { DB::beginTransaction(); // Find all echomail seenbys $x = DB::update('update echomail_seenby set address_id=? where address_id=?',[$request->dst,$request->src]); // Find all echomail paths $x = DB::update('update echomail_path set address_id=? where address_id=?',[$request->dst,$request->src]); // Find all echomails $x = DB::update('update echomails set fftn_id=? where fftn_id=?',[$request->dst,$request->src]); // Find all netmails $x = DB::update('update netmails set fftn_id=? where fftn_id=?',[$request->dst,$request->src]); // Find all netmails $x = DB::update('update netmails set tftn_id=? where tftn_id=?',[$request->dst,$request->src]); // Find all nodelist $x = DB::update('update address_nodelist set address_id=? where address_id=?',[$request->dst,$request->src]); // Find all file seenbys $x = DB::update('update file_seenby set address_id=? where address_id=?',[$request->dst,$request->src]); // Find all files $x = DB::update('update files set fftn_id=? where fftn_id=?',[$request->dst,$request->src]); $src = Address::withTrashed()->findOrFail($request->src); // Resubscribe echoareas try { $x = DB::update('update address_echoarea set address_id=? where address_id=?',[$request->dst,$request->src]); } catch (QueryException $e) { DB::rollback(); return back()->withInput()->withErrors('error',sprintf('You may need to remove %s:%s (%d) from echoareas',$src->ftn,$src->system->name,$src->id)); } // Resubscribe fileareas try { $x = DB::update('update address_filearea set address_id=? where address_id=?',[$request->dst,$request->src]); } catch (QueryException $e) { DB::rollback(); return back()->withInput()->withErrors('error',sprintf('You may need to remove %s:%s (%d) from fileareas',$src->ftn,$src->system->name,$src->id)); } if ($src->forceDelete()) { DB::commit(); return redirect()->to('address/merge/'.$request->dst); } else { return back()->withInput()->withErrors('error',sprintf('Address [%s] didnt delete?',$src->ftn)); DB::rollBack(); } } $o = Address::withTrashed() ->findOrFail($id); $oo = Address::withTrashed() ->where('zone_id',$o->zone_id) ->where('host_id',$o->host_id) ->where('node_id',$o->node_id) ->where('point_id',$o->point_id) ->get(); if ($o->zone->domain->flatten) $oo = $oo->merge(Address::withTrashed() ->whereIn('zone_id',$o->zone->domain->zones->pluck('id')) ->where('host_id',$o->host_id) ->where('node_id',$o->node_id) ->where('point_id',$o->point_id) ->get() ); return view('system/address-merge') ->with('o',$o) ->with('oo',$oo); } /** * Move address to another system * * @param Request $request * @param System $so * @param Address $o * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|\Illuminate\Http\RedirectResponse * @throws \Illuminate\Auth\Access\AuthorizationException */ public function address_mov(Request $request,System $so,Address $o) { session()->flash('accordion','address'); // Quick check that this address belongs to this system if ($so->addresses->search(function($item) use ($o) { return $item->id === $o->id; }) === FALSE) abort(404); if ($request->post()) { $this->authorize('admin',$o); $validated = $request->validate([ 'system_id' => 'required|exists:systems,id', 'remove' => 'nullable|boolean', 'remsess' => 'nullable|boolean|exclude_if:remove,1', ]); $o->system_id = $validated['system_id']; $o->save(); if (Arr::get($validated,'remove')) { $so->sessions()->detach($o->zone); $so->mailers()->detach(); $so->users()->detach(); $so->delete(); } elseif (Arr::get($validated,'remsess')) { $so->sessions()->detach($o->zone); } return redirect()->to('system/addedit/'.$validated['system_id']); } return view('system.moveaddr') ->with('o',$o); } /** * Promote an address node -> NC * * @param Address $o * @return \Illuminate\Http\RedirectResponse * @throws \Illuminate\Auth\Access\AuthorizationException */ public function address_pro(Address $o) { // @todo This should be admin of the zone $this->authorize('admin',$o); session()->flash('accordion','address'); // Make sure that no other system has this address active. if ($o->role === Address::NODE_NC) return redirect()->back()->withErrors(['proaddress'=>sprintf('%s cannot be promoted any more',$o->ftn3D)]); $o->role = ($o->role >> 1); $o->save(); return redirect()->to(sprintf('system/addedit/%d',$o->system_id)); } /** * Recover a deleted address * * @param int $id * @return \Illuminate\Http\RedirectResponse * @throws \Illuminate\Auth\Access\AuthorizationException */ public function address_pur(int $id) { $o = Address::onlyTrashed()->findOrFail($id); // @todo This should be admin of the zone $this->authorize('admin',$o); session()->flash('accordion','address'); $o->forceDelete(); return redirect()->to(sprintf('system/addedit/%d',$o->system_id)); } /** * Recover a deleted address * * @param int $id * @return \Illuminate\Http\RedirectResponse * @throws \Illuminate\Auth\Access\AuthorizationException */ public function address_rec(int $id) { $o = Address::onlyTrashed()->findOrFail($id); // @todo This should be admin of the zone $this->authorize('admin',$o); session()->flash('accordion','address'); $o->restore(); return redirect()->to(sprintf('system/addedit/%d',$o->system_id)); } /** * Suspend address assigned to a host * * @param Address $o * @return \Illuminate\Http\RedirectResponse * @throws \Illuminate\Auth\Access\AuthorizationException */ public function address_sus(Address $o) { // @todo This should be admin of the zone $this->authorize('admin',$o); session()->flash('accordion','address'); // Make sure that no other system has this address active. if (! $o->active && ($x=Address::where([ 'zone_id'=>$o->zone_id, 'host_id'=>$o->host_id, 'node_id'=>$o->node_id, 'point_id'=>$o->point_id, 'active'=>TRUE, ])->single())) { return redirect()->back()->withErrors(['susaddress'=>sprintf('%s is already active on system [%s]',$o->ftn,url('system/addedit',$x->system_id),$x->system->name)]); } $o->active = (! $o->active); $o->save(); return redirect()->to(sprintf('system/addedit/%d',$o->system_id)); } public function api_address(Request $request,System $o): Collection { return Address::select(['addresses.id','addresses.zone_id','region_id','host_id','node_id','point_id']) ->leftjoin('zones',['zones.id'=>'addresses.zone_id']) ->where('addresses.system_id',$o->id) ->where('zones.domain_id',$request->domain_id) ->withTrashed() ->FTNorder() ->get() ->map(function($item) { return ['id'=>(string)$item->id,'value'=>$item->ftn4d]; }); } public function api_address_get(Address $o) { return $o; } /** * Identify all the addresses from systems that are not owned by a user * * @param Request $request * @return Collection */ public function api_address_orphan(Request $request): Collection { $result = collect(); list($zone_id,$host_id,$node_id,$point_id,$domain) = sscanf($request->query('term'),'%d:%d/%d.%d@%s'); # Look for Systems foreach (Address::select(['addresses.id','systems.name',DB::raw('systems.id AS system_id'),'zones.zone_id','region_id','host_id','node_id','point_id','addresses.zone_id']) ->join('zones',['zones.id'=>'addresses.zone_id']) ->rightjoin('systems',['systems.id'=>'addresses.system_id']) ->when($zone_id || $host_id || $node_id,function($query) use ($zone_id,$host_id,$node_id) { return $query ->when($zone_id,function($q,$zone_id) { return $q->where('zones.zone_id',$zone_id); }) ->where(function($q) use ($host_id) { return $q ->when($host_id,function($q,$host_id) { return $q->where('region_id',$host_id); }) ->when($host_id,function($q,$host_id) { return $q->orWhere('host_id',$host_id); }); }) ->when($node_id,function($q,$node_id) { return $q->where('node_id',$node_id); }); }) ->orWhere('systems.name','ilike','%'.$request->query('term').'%') ->orderBy('systems.name') ->get() as $o) { $result->push(['id'=>$o->id,'name'=>sprintf('%s (%s)',$o->ftn3d,$o->name),'category'=>'Systems']); } return $result; } public function areafix(AreafixRequest $request,System $o,Zone $zo) { if ($request->post()) { $no = new Netmail; foreach ($request->safe() as $item => $value) $no->{$item} = $value; $no->from = auth::user()->name; $no->msg .= "\r"; $no->datetime = Carbon::now(); $no->tzoffset = $no->datetime->utcOffset(); $no->flags = (Message::FLAG_LOCAL|Message::FLAG_PRIVATE|Message::FLAG_CRASH); $no->cost = 0; $no->tearline = sprintf('%s (%04X)',Setup::PRODUCT_NAME,Setup::PRODUCT_ID); $no->save(); Log::info(sprintf('%s:= Areafix to [%s], scheduling a poll',self::LOGKEY,$no->tftn->ftn)); AddressPoll::dispatch($no->tftn); return redirect()->back()->with('success','Areafix/Filefix sent'); } return view('system.areafix') ->with('zo',$zo) ->with('ao',$o->match($zo)->first()) ->with('o',$o) ->with('setup',Setup::findOrFail(config('app.id'))); } /** * Update the systems echoareas * * @param Request $request * @param System $o * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|\Illuminate\Http\RedirectResponse */ public function echoareas(Request $request,System $o) { $ao = $o->addresses->firstWhere('id',$request->address_id); if (($request->method() === 'POST') && $request->post()) { session()->flash('accordion','echoarea'); if ($ao->trashed() && collect($request->get('id'))->diff($ao->echoareas->pluck('id'))->count()) return redirect()->back()->withErrors(sprintf('Address [%s] has been deleted, cannot add additional echos',$ao->ftn3d)); // Ensure we have session details for this address. if (! $ao->session('sespass')) return redirect()->back()->withErrors('System doesnt belong to this network'); $ao->echoareas()->syncWithPivotValues($request->get('id',[]),['subscribed'=>Carbon::now()]); return redirect()->back()->with('success','Echoareas updated'); } // @todo Allow a NC/RC/ZC to override $eo = Echoarea::active() ->where('domain_id',$ao->zone->domain_id) ->where(function($query) use ($ao) { return $query ->whereRaw(sprintf('(security&7) <= %d',$ao->security)) // write ->orWhereRaw(sprintf('((security>>3)&7) <= %d',$ao->security)); // read }) ->orderBy('name') ->get(); return view('system.widget.echoarea') ->with('o',$o) ->with('ao',$ao) ->with('echoareas',$eo); } /** * Update the systems fileareas * * @param Request $request * @param System $o * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|\Illuminate\Http\RedirectResponse */ public function fileareas(Request $request,System $o) { $ao = $o->addresses->firstWhere('id',$request->address_id); if (($request->method() === 'POST') && $request->post()) { session()->flash('accordion','filearea'); // Ensure we have session details for this address. if (! $ao->session('sespass')) return redirect()->back()->withErrors('System doesnt belong to this network'); $ao->fileareas()->syncWithPivotValues($request->get('id',[]),['subscribed'=>Carbon::now()]); return redirect()->back()->with('success','Fileareas updated'); } // @todo Allow a NC/RC/ZC to override $fo = Filearea::active() ->where('domain_id',$ao->zone->domain_id) ->where(function($query) use ($ao) { return $query ->whereRaw(sprintf('(security&7) <= %d',$ao->security)) // write ->orWhereRaw(sprintf('((security>>3)&7) <= %d',$ao->security)); // read }) ->orderBy('name') ->get(); return view('system.widget.filearea') ->with('o',$o) ->with('ao',$ao) ->with('fileareas',$fo); } /** * Register a system, or link to an existing system */ public function register(SystemRegister $request) { // Step 1, show the user a form to select an existing defined system if ($request->isMethod('GET')) return view('user.system.register'); if ($request->action === 'register' && $request->name && is_numeric($request->name)) return view('user.system.widget.register_confirm') ->with('o',System::findOrFail($request->name)); $o = System::findOrNew(is_numeric($request->system_id) ? $request->system_id : NULL); // If the system exists, and we are 'register', we'll start the address claim process if ($o->exists && $request->action === 'Link') { $validate = Setup::findOrFail(config('app.id'))->system->inMyZones($o->addresses); // If we have addresses, we'll trigger the routed netmail if ($validate->count()) { Notification::route('netmail',$x=$validate->first())->notify(new AddressLink(Auth::user())); AddressPoll::dispatch($x)->delay(15); } return view('user.system.widget.register_send') ->with('validate',$validate) ->with('o',$o); } // If the system doesnt exist, we'll create it if (! $o->exist) { $o->sysop = Auth::user()->name; foreach (['name','zt_id','location','phone','method','address','port'] as $item) if ($request->{$item}) $o->{$item} = $request->{$item}; $o->active = TRUE; } if ($request->post('submit')) { Auth::user()->systems()->save($o); // @todo if the system already exists and part of one of our networks, we'll need to send the registration email to confirm the address. // @todo mark the system (or addresses) as "pending" at this stage until it is confirmed return redirect()->to(url('system/addedit',$o->id)); } // Re-flash our previously input data if ($request->old) session()->flashInput($request->old); return view('system.widget.form-system') ->with('action',$request->action) ->with('o',$o) ->with('errors',new ViewErrorBag); } /** * Add Session details * * @param Request $request * @param System $o * @return \Illuminate\Http\RedirectResponse * @throws \Illuminate\Auth\Access\AuthorizationException */ public function session_add(Request $request,System $o) { // @todo This should be admin of the zone $this->authorize('update',$o); session()->flash('accordion','session'); $validate = $request->validate([ 'zone_id' => 'required|exists:zones,id', 'sespass' => 'required|string|min:4', 'pktpass' => 'required|string|min:4|max:8', 'ticpass' => 'required|string|min:4', 'fixpass' => 'required|string|min:4', ]); $zo = Zone::findOrFail($validate['zone_id']); // If this session is for the ZC, it now becomes the default. if ($o->match($zo,Address::NODE_ZC)->count()) { SystemZone::where('default',TRUE)->update(['default'=>FALSE]); $validate['default'] = TRUE; } $o->sessions()->attach($zo,$validate); return redirect()->to(sprintf('system/addedit/%d',$o->id)); } /** * Delete address assigned to a host * * @param Address $o * @return \Illuminate\Http\RedirectResponse * @throws \Illuminate\Auth\Access\AuthorizationException */ public function session_del(System $o,Zone $zo) { $this->authorize('admin',$zo); session()->flash('accordion','session'); $o->sessions()->detach($zo); return redirect()->to(sprintf('system/addedit/%d',$o->id)); } // @todo Can this be consolidated with system_register() public function system_link(Request $request) { if (! $request->system_id) return redirect('user/system/register'); $so = System::findOrFail($request->system_id); $ca = NULL; $la = NULL; foreach (our_address() as $ao) { if (($ca=$so->match($ao->zone))->count()) break; } if ($ca->count() && $la=$ca->pop()) { Notification::route('netmail',$la)->notify(new AddressLink(Auth::user())); AddressPoll::dispatch($la)->delay(15); } return view('user.system.register_send') ->with('la',$la) ->with('o',$so); } public function view(System $o) { $o->load(['addresses']); return view('system.view') ->with('o',$o); } }