Compare commits

273 Commits
bbs ... master

Author SHA1 Message Date
673ed6aaed Ensure our outgoing packet to/from/subject are char limited according to the spec
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 43s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m21s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2025-07-02 11:55:55 +08:00
22ba6fe35c Dont need yield page-scripts here, it is already yield by the include layouts.partials.scripts
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 30s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m45s
Create Docker Image / Final Docker Image Manifest (push) Successful in 8s
2025-05-03 20:59:28 +10:00
201a466ae3 Framework update 2025-04-20 22:19:35 +10:00
8edc45fbd5 Change our $casts array to a casts() function for our updated laravel, fix incoming TICs to not export to our system 2025-04-20 22:19:05 +10:00
e2cd09ac98 Fix nodelist segment generation, include all our addresses
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 33s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m38s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2025-04-18 22:36:10 +10:00
c6458219bf Additional fixes for 14616471
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 30s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m36s
Create Docker Image / Final Docker Image Manifest (push) Successful in 8s
2025-04-17 20:02:01 +10:00
99dc13b297 Initial implementation of API access to clrghouz with a whoami endpoint
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 58s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 2m51s
Create Docker Image / Final Docker Image Manifest (push) Successful in 8s
2025-04-17 09:41:01 +10:00
6284795e3b Logging some @todos to do 2025-04-17 09:36:39 +10:00
1461647159 Improvements to mail send job and to reduce the number of failed polls going to the failed job table and improvements to determining downlinks 2025-04-17 09:33:32 +10:00
3f0e17e20b Optimize the SQL queries that finds unsent echomail,netmail and files 2025-04-17 09:31:02 +10:00
128e333deb Fix BBS registration link, we need the udpated_at value when calculating the code
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 32s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m36s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2025-04-16 09:02:56 +10:00
6f6b5fe2c4 We need to include our hub_id so we can correctly identify our parent
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 32s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m36s
Create Docker Image / Final Docker Image Manifest (push) Successful in 8s
2025-04-15 17:58:11 +10:00
3ca16f0d87 Fix toggling validated on page 2 of our AKAs, we need to use jquery delegated targets
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 31s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m32s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2025-04-15 17:11:01 +10:00
c49daadc5f Updates now that we have updated our_address() to differentiate public/mailer advertised addresses with all our addresses
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 33s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m35s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2025-04-15 09:35:35 +10:00
b59317871a Added tool to list node addresses and the address we use,
Fix address edit which reactivated the address,
Fix correct icons when using address merge,
We only advertise validated addresses and use validated addresses for routing,
Show our address on known AKA screen
2025-04-15 09:32:50 +10:00
1656087ded When clearing mail for a node, also clear local netmails 2025-04-13 22:13:19 +10:00
9bc880520f Fix setting next_at date for dynamics items, when the item hasnt been sent in the last run
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 34s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m41s
Create Docker Image / Final Docker Image Manifest (push) Successful in 8s
2025-03-24 11:20:11 +11:00
2458ca2408 Fix nodelist segment being sent from queue where gethostname() is only a container name, added ability to control filename when sending nodelist segment
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 38s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m38s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2025-03-18 17:26:17 +11:00
3797f3c7c5 Update robots.txt 2025-03-18 17:26:17 +11:00
bbc013a7a0 Fix Delist warning date on subject line of messages 2025-03-18 17:26:17 +11:00
db34b2b641 Highlight packet configuration is not mailer configuration in system/addedit 2025-02-07 13:47:36 +11:00
b1eaec3271 Autodetect Telnet IAC modes, and if in binary mode esc 0xff chars
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 37s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m34s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2025-01-31 21:11:02 +11:00
52961e2403 Allow admins to reduce the size of pkt_msgs 2025-01-29 21:15:55 +11:00
c9eee48c6a Enable packet passwords to be optional, configured by the admin in setup
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 42s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m43s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2025-01-29 13:40:54 +11:00
8cc561ea2b Fix for EMSI aborting with $rc undefined, Fix for idle nodes updates failing on updated_at column and change text used when nodes have never polled 2025-01-29 13:40:54 +11:00
35c5b3da8d Our traffic graph now has a dynamic y axis
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 44s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m36s
Create Docker Image / Final Docker Image Manifest (push) Successful in 11s
2025-01-08 08:04:55 +11:00
3e202e34d2 Fix for messages originating from a point, where PATH: is empty
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 40s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m31s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2025-01-07 21:02:32 +11:00
29cbc80982 Fix adding FMPT/TOPT kludges for outgoing netmail
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 38s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m31s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2025-01-07 18:15:36 +11:00
bf446e1186 Fix message_path when the source is not in the DB
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 43s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m37s
Create Docker Image / Final Docker Image Manifest (push) Successful in 11s
2025-01-07 16:46:07 +11:00
aeea49abe7 Fix filefix FILELIST command when area not provided
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 37s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m32s
Create Docker Image / Final Docker Image Manifest (push) Successful in 11s
2025-01-07 15:58:23 +11:00
2c1ab88bbd Improvements to working out region for new addresses, Bounce netmails to a invalid address (that we would host)
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 40s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m36s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2025-01-07 14:55:57 +11:00
058b4ac4b9 Fix for 4bbb826 command syntax for Comm* commands was displaying incorrectly
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 2m46s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 3m50s
Create Docker Image / Final Docker Image Manifest (push) Successful in 11s
2025-01-07 11:13:31 +11:00
b27d983fe6 Order dashboard systems by name 2024-12-20 16:06:38 +07:00
5fe486b93c Areafix/Filefix Scan/Rescan commands cannot use +/- in front of area name
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 45s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m35s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-12-19 22:15:38 +07:00
010f34f4f2 Update our welcome page with feature updates and more information
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 40s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m38s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-12-16 11:17:22 +07:00
aa55d05ccb Change our_nodes() to include only active zones/domains also. Updates to heartbeat
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 39s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m35s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-12-09 12:03:38 +11:00
ae44732848 When showing items sent, also incluce tftn_id in case netmails get routed
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 36s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m34s
Create Docker Image / Final Docker Image Manifest (push) Successful in 11s
2024-12-09 10:50:28 +11:00
bcae8693a7 Fix address merge where netmails sent_id is used
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 35s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m36s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-12-08 22:00:18 +11:00
46cf488337 NodesNew also now sends an Echomail
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 37s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m34s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-12-08 21:38:10 +11:00
f4ee2e1a51 Update NodesNew report
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 40s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m37s
Create Docker Image / Final Docker Image Manifest (push) Successful in 11s
2024-12-08 18:47:25 +11:00
ec139b79a2 Change all notes inputs to use notes component 2024-12-06 21:30:44 +11:00
e9b5783945 Complete implement of select2 component, and replaced existing use of select2. Also deprecated CustomBlade definitions of select2/simplemde
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 43s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m34s
Create Docker Image / Final Docker Image Manifest (push) Successful in 11s
2024-12-06 14:44:19 +11:00
e3c45dfd3d Consistent instantiation of <form> 2024-12-06 11:16:38 +11:00
082b70e072 Return input when linking a BBS, some consistent instantiation of <form>, more work on select2 transition 2024-12-06 11:11:50 +11:00
70f3a049f6 Change address promot/demote to only apply to normal nodes and hubs 2024-12-06 08:45:29 +11:00
e7336a942b More changes to use form.select component. Re-engineered user BBS registration 2024-12-06 08:33:24 +11:00
fd780d1756 Minor fixes to select component to render HTML helpers 2024-12-04 22:24:34 +11:00
8c8283503f Default form-label for the label css on select components 2024-12-04 20:28:46 +11:00
b239f52719 Updates to bootstrap/select2 2024-12-04 20:28:46 +11:00
c69cbe8746 Add select component, and start of switch to use @pa instead of @js/@css 2024-12-04 20:28:46 +11:00
1c35f71a4b Make autofocus default for select2 dropdowns 2024-12-04 20:28:46 +11:00
a65e4b8bc3 Additional syntax validation for areafix/filefix commands
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 44s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m33s
Create Docker Image / Final Docker Image Manifest (push) Successful in 11s
2024-12-04 20:28:32 +11:00
6dde4cf910 Change MessagePath to not render the Origin in full
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 44s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m38s
Create Docker Image / Final Docker Image Manifest (push) Successful in 11s
2024-12-04 16:10:23 +11:00
5dbef2d9d1 Fix Carbon not found for system/addedit
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 38s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m33s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-12-03 11:58:06 +11:00
3bc98047c3 Update meta keywords
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 40s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m35s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-12-03 09:26:37 +11:00
37549a90b7 Change rendering of user's home, taking into account systems that have been marked inactive
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 35s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m30s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-12-02 20:03:01 +11:00
09a0139839 Dont display inactive systems in the sidebar
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 42s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m32s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-12-02 19:44:08 +11:00
e9fde81bfb Fix sorting of FTN zones in table Admin->Domain table 2024-12-02 19:39:11 +11:00
941f7b480e Fix creation of Region Address AKA for a system
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 38s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m31s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-12-01 21:52:37 +11:00
aa1a460836 Debug dynamic item, use address in db row
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 37s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m34s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-12-01 21:35:13 +11:00
6743e5bf73 Fix, nodelist segment doubling up when generating segment
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 37s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m33s
Create Docker Image / Final Docker Image Manifest (push) Successful in 11s
2024-12-01 21:17:22 +11:00
e1ed446f3e Ensure children() returns addresses in FTN order
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 37s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m36s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-12-01 16:31:07 +11:00
445dd48c81 When re-enabling dynamic items, ensure the next_at date is from today, not from when it was last run 2024-12-01 16:15:07 +11:00
33a97ce5b3 Fix showing 'View Packet' when the to zone is not in the same domain as the from domain
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 46s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m38s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-12-01 12:38:08 +11:00
2ea8bbd0b8 Implemented filefix %RESEND 2024-12-01 12:38:08 +11:00
ce19985c2d Implemented filefix %SCAN/%RESCAN, and some cosmetic cleanup 2024-12-01 12:38:08 +11:00
0e1086c99f Fixes for Zmodem missed in bf3fce 2024-12-01 12:38:08 +11:00
fc11700601 Remove/reduce usage of QueryCache 2024-12-01 12:38:08 +11:00
119f2cb6b9 Fix bug with HAPROXY v2 processing when implementing v1 in d2d109 2024-12-01 12:38:08 +11:00
d9817e1c2e Added filefix %FILELIST 2024-12-01 12:38:08 +11:00
08375f4995 Respond to filefix commands with Filefix/CommandsProcessed 2024-12-01 12:38:08 +11:00
d58ed8842b Added filefix %AREA 2024-12-01 12:38:08 +11:00
39034dbbb0 Batch up files when sending to a remote node 2024-12-01 12:38:08 +11:00
1296e3be40 Remove Address::downstream() for Address::downlinks() 2024-12-01 12:38:08 +11:00
9c828d65e6 Update TestNodeHierarchy to include a fuller FTN setup for testing. Update testing. 2024-12-01 12:38:08 +11:00
810e620526 SocketClient change has.. can.. functions to return boolean and other minor cosmetic changes 2024-12-01 12:38:08 +11:00
6c36f7f9aa Fix UDP services (ie: DNS) 2024-12-01 12:38:07 +11:00
38c68982ec Exclude points when choosing what to remove from the net during nodelist processing 2024-12-01 12:38:07 +11:00
a161b8fc5e Added filefix %LIST 2024-12-01 12:38:07 +11:00
b67ae28b98 Split out areafix command processing, implemented start of filefix 2024-11-26 15:13:35 +11:00
521a9b0679 Deprecate singleOrNew(), we use firstOrNew() instead 2024-11-26 15:13:35 +11:00
24144de193 Change NodelistImport display when using job:list 2024-11-26 15:13:35 +11:00
aabc8b5a65 Added ansitex font 2024-11-26 15:13:35 +11:00
1aabd323bb Remove unused config files 2024-11-26 15:13:35 +11:00
bf3fce252d Remove CommProtocolReceive commands, Remove protocol onConnect() functions, pass Setup::class to protocols 2024-11-26 15:13:35 +11:00
72ad1307c5 Deprecate singleOrFail() in favour of sole() 2024-11-26 15:13:35 +11:00
f0f2d74a14 Reduce the need for Mailer::class in protocols 2024-11-26 15:13:35 +11:00
ff4ecddb76 Drop updated_at from system_log 2024-11-23 23:26:05 +11:00
6c9f4facc6 Additional debugging for messages that fail quick validation during processing 2024-11-20 16:20:49 +09:30
67dad76bd1 Process netmails from unlisted systems
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 31s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m30s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2024-11-20 16:01:11 +09:30
016c1fb1b0 Show message type in packet:info
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 27s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m31s
Create Docker Image / Final Docker Image Manifest (push) Successful in 8s
2024-11-19 21:56:35 +09:30
733f4621a1 Add domain to path/seenby for 4D addresses in tic files
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 29s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m30s
Create Docker Image / Final Docker Image Manifest (push) Successful in 8s
2024-11-19 21:23:33 +09:30
bf4c282282 Optionally, also find TIC sender address in tic sending node's list of addresses
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 27s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m30s
Create Docker Image / Final Docker Image Manifest (push) Successful in 8s
2024-11-19 20:47:24 +09:30
dacd8be4c2 Process TIC files even if they omit the size attribute
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 34s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m34s
Create Docker Image / Final Docker Image Manifest (push) Successful in 8s
2024-11-19 16:37:29 +09:30
2316663dac Show unsent echomails in cyan not red
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 1m12s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m38s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2024-11-17 21:39:14 +11:00
59af68dca8 Validation to make sure netmails have an INTL kludge, needed to determine address details. Update Packet View to not trigger validation netmails 2024-11-17 21:38:59 +11:00
e802fe5030 Echoarea tag can have a period
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 33s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m33s
Create Docker Image / Final Docker Image Manifest (push) Successful in 8s
2024-11-08 18:40:57 +11:00
996b8cf99b Arealist should be AreaList
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 27s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m29s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2024-11-07 12:50:15 +11:00
9762d95cef Some debugging to catch unprocessed areafix commands
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 33s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m30s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2024-11-07 12:31:35 +11:00
21d3ff5918 Limit address idle only to addresses where we are the parent
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 29s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m31s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2024-11-06 22:44:21 +11:00
1075cc0de4 Fix auth controllers for laravel 11
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 30s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m31s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2024-11-06 18:43:35 +11:00
c2197ecf7c Fix resume for files being sent, and dont throw exception if we cannot open a file to send
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 27s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m34s
Create Docker Image / Final Docker Image Manifest (push) Successful in 8s
2024-11-06 00:18:29 +11:00
b5d2479098 Better catching failures when Stream::read doesnt returns null
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 26s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m31s
Create Docker Image / Final Docker Image Manifest (push) Successful in 8s
2024-11-05 21:51:48 +11:00
296740aead Change build to remove unnessary files in the resulting container
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 31s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m30s
Create Docker Image / Final Docker Image Manifest (push) Successful in 8s
2024-11-05 20:39:13 +11:00
e7a94d906f Fix for protocols not showing system sysop name and location 2024-11-05 20:39:13 +11:00
60ea0afc98 Minor optimisations when editing and viewing domains 2024-11-05 20:39:13 +11:00
dc2e84386f More updates for laravel 11 2024-11-05 20:39:13 +11:00
0bd2f6e82c Continue to show all common addresses in Items Waiting tab, Add Address Clear Queue job to delete anything in the queue for an address 2024-11-04 23:00:17 +11:00
80fa3e840b Fix for 3b7ce4b, change where setup is defined for our protocols 2024-11-04 23:00:17 +11:00
242f4013b1 Added New Nodes Report 2024-11-04 23:00:17 +11:00
3aeeed1686 Consistency and SQL Query optimisations - focused around the UI 2024-11-04 23:00:17 +11:00
f03533b62a Ensure our jobs are storing objects without relations 2024-11-04 09:06:28 +11:00
3b7ce4b9ce Optimise the setting of our configuration via Setup::class, optimise the calculation of our_addresses() 2024-11-02 23:30:42 +11:00
1b228a58c9 Attempt to work out a packets domain from a nodes addresses for looking at a default
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 26s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m31s
Create Docker Image / Final Docker Image Manifest (push) Successful in 8s
2024-11-02 17:01:31 +11:00
0c51b17e6c Fix test logic when evaluating a message is a *fix message
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 31s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m31s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2024-11-02 14:15:03 +11:00
f6134c0a98 Added areafix command %LIST
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 32s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m35s
Create Docker Image / Final Docker Image Manifest (push) Successful in 8s
2024-11-02 00:10:38 +11:00
a310f4190c MATRIX_SERVER configuration item is now a URL
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 29s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m32s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2024-11-01 13:40:18 +11:00
6342fe28b2 Revert "Force posting to matrix to use IPv4 resolution"
This might not be required afterall
This reverts commit 881449a170.
2024-11-01 13:16:31 +11:00
881449a170 Force posting to matrix to use IPv4 resolution
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 28s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m31s
Create Docker Image / Final Docker Image Manifest (push) Successful in 8s
2024-11-01 13:01:55 +11:00
1002309614 Separatly handle areafix/filefix
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 30s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m30s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2024-11-01 12:35:46 +11:00
2598203edb Logging message changes for areafix, no functional changes 2024-11-01 10:28:22 +11:00
7c70c1f12d Added %SCAN command, to send unsent mail from an area
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 30s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m34s
Create Docker Image / Final Docker Image Manifest (push) Successful in 8s
2024-11-01 10:04:12 +11:00
247d30505e Show %AREA in area commands back to user 2024-11-01 08:15:32 +11:00
1128bddcee Added areafix %HELP
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 33s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m34s
Create Docker Image / Final Docker Image Manifest (push) Successful in 8s
2024-10-31 23:53:43 +11:00
4796dd9a6e Implementation of areafix processing, first subscribe/unsubscribe with scan 2024-10-31 22:40:58 +11:00
d792bf8fe3 Us our const as our product name presented to remote mailers 2024-10-29 21:41:29 +11:00
28b48e5bef Set default example mail server to smtp
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 34s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m47s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2024-10-26 13:12:31 +11:00
d2d1094c1a Update HAPROXY process to understand v1 2024-10-26 13:12:31 +11:00
271bf937cf Add configuration items to determine DNS records returned and sort order 2024-10-26 13:12:31 +11:00
179233385b Cache our echo and file area stats for page rendering performance reasons
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 35s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m49s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2024-10-24 17:00:14 +11:00
4b309871ec Sometimes files come in without descriptions
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 35s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m48s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-10-24 15:37:41 +11:00
afb8ad9d17 Fix some UTF8 output when doing a search. Convert filename desc to CP437 when displaying files in a file area
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 38s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m49s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-10-24 11:31:20 +11:00
2132b4564c Limit what we show users for echo/file areas
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 35s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m46s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2024-10-23 22:21:42 +11:00
48d329e503 List files in a file area
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 32s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m46s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2024-10-23 22:09:04 +11:00
d20a62ad48 Add filearea stats 2024-10-23 22:09:04 +11:00
9c7d7bf932 Performance updates for domain/view 2024-10-23 20:46:49 +11:00
a32e2e504a Add a maintenance page
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 35s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m50s
Create Docker Image / Final Docker Image Manifest (push) Successful in 8s
2024-10-23 12:56:50 +11:00
7e87481e05 Increase number of attempts when trying to poll a system 2024-10-23 12:56:50 +11:00
670450387c Improvements to handle rogue 2D adresses 2024-10-23 12:56:50 +11:00
967153c70a Only admins can change the heartbeat below 12 2024-10-22 19:33:24 +11:00
7da09a1a9e Better handling of badly addressed packets for netmails
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 33s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m46s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2024-10-22 19:17:03 +11:00
86a15872b8 Better catching bad TZUTC in messages, continue parsing mail bundles in an archive if a packet has an error
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 36s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m48s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-10-22 17:08:48 +11:00
eb61c5ea6e Change wording of forwarding netmail, no functional changes 2024-10-22 15:29:41 +11:00
a6e911fb48 Improve netmail sent/receive information
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 35s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m47s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2024-10-21 17:46:33 +11:00
53c5488df7 When rescanning, choose the echomails date, not the date it was imported. By default dont re-export sent echomail, unless using -R
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 34s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m47s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2024-10-21 11:21:48 +11:00
944053972b Fix for 4bbb82 Job::dispatch doesnt return an int, so cannot be used for success status 2024-10-21 08:47:54 +11:00
6bba18fc5f Add hold when populating address with system data
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 36s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m51s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2024-10-21 08:40:47 +11:00
d2d2f31664 Fix updating systems sometimes items
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 37s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m50s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2024-10-20 21:44:56 +11:00
8396866280 Fix addressing when forwarding netmail, as well as fix content being prefixed with forward notice.
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 36s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m47s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2024-10-20 17:25:45 +11:00
ac687efe51 When receiving a netmail without a MSGID, include that in the log
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 35s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m49s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2024-10-20 11:54:52 +11:00
676ba4a40b Presentation fixes, no functional changes
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 34s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m49s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2024-10-20 11:49:52 +11:00
432a9bbf2b Fix working out our hostname when we are an RC 2024-10-20 11:49:34 +11:00
7ba037b76d Fix dynamic processing
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 34s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m51s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2024-10-20 11:08:42 +11:00
a2cc1b6b8e For address:idle show the correct days since the system was last seen, not the configuration
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 40s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m51s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2024-10-20 09:39:14 +11:00
3852e69fcc When originating a connection, ensure we dont do any HAPROXY stuff
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 37s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m52s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2024-10-19 18:34:50 +11:00
bf5d5bcd74 Fix FSX-48 packets from points
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 35s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m51s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2024-10-19 14:57:33 +11:00
4bbb82690c Enable comm:* commands to be run interactively
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 1m10s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m52s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-10-19 13:38:55 +11:00
03c6cadbf9 Improvements for deterining an Address for a 2D FTN
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 36s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m50s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2024-10-18 11:26:06 +11:00
b1560015ae Allow users to update their auto_hold status
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 34s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m51s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2024-10-17 16:25:49 +11:00
a3719b9186 Echoarea NOT EXIST messages should go to the Sysop of the system, not to the user
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 33s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m51s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2024-10-16 11:46:24 +11:00
f4f8e9fa94 Fix some user permissions, when viewing netmails and other systems
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 37s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m50s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2024-10-16 08:44:46 +11:00
38fd1539a6 Dont let users increase pkt_msgs above 100 2024-10-16 08:19:36 +11:00
32d31cea90 Fix counts of items waiting for node idle notifications
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 33s
Create Docker Image / Build Docker Image (arm64) (push) Successful in -17h44m39s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-10-09 15:41:08 +11:00
5e7bf0554a Fixes for address idle, to properly catch when role is NULL
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 33s
Create Docker Image / Build Docker Image (arm64) (push) Successful in -17h41m59s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2024-10-09 15:06:45 +11:00
c76995fa00 Revert 693e88 since unsuccessfully sent mail shouldnt be marked as sent 2024-10-09 14:11:11 +11:00
caac9c21a8 If we are given a zero byte file, we SKIP it
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 50s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m49s
Create Docker Image / Final Docker Image Manifest (push) Successful in 12s
2024-09-30 09:12:21 +10:00
43c573f641 Address list log is the config, not the actual node time since seen 2024-09-29 13:31:43 +10:00
b8f0b11c05 Handle delisting when last_seen is blank (node hasnt polled)
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 51s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 2m0s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-09-29 13:16:23 +10:00
494cb317d1 Fix for parsing a netmail path that is 5D but without a .0 for a boss
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 45s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m50s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-09-26 09:54:44 +10:00
bfab8c7d26 If we get a file we cannot parse, then send back a SKIP and keep going
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 44s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m49s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-09-25 00:10:50 +10:00
333e7e773d More work on finding idle nodes
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 46s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m53s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-09-22 18:00:46 +10:00
4501443a43 Improvements to finding idle nodes, last_session actually only shows the last time the remote polled us
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 47s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m53s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-09-15 22:37:35 +10:00
832b496b0b Dont use count(*) but rather count(column) so that indexes can help us
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 42s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m51s
Create Docker Image / Final Docker Image Manifest (push) Successful in 11s
2024-09-10 14:52:35 +10:00
2d75c92afb Some query optimisations when rendering status, dashboard, about and FTN list pages
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 45s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m51s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-09-10 13:05:43 +10:00
67f0e3007e Reduce some logging of connect/disconnect
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 44s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m52s
Create Docker Image / Final Docker Image Manifest (push) Successful in 11s
2024-09-09 23:49:04 +10:00
6974b9b885 Log PID created on a connection to help match the source address in logging
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 50s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m52s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-09-09 23:34:42 +10:00
5d9d8ed72a Strip from the last tearline when posting to matrix
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 47s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m55s
Create Docker Image / Final Docker Image Manifest (push) Successful in 11s
2024-09-09 20:29:17 +10:00
eafd203021 Improve node idle detection, including adding NODE_KEEP 2024-09-09 20:16:14 +10:00
9d06b422e0 Improve match of messages that come via matrix, so they dont get posted back 2024-09-09 19:44:14 +10:00
693e88bc01 Abort on M_SKIP since it incorreclty marks items as sent, and thus they are not sent again
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 47s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m50s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-09-09 14:20:55 +10:00
2456402246 Implement M_SKIP in binkp protocol
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 46s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m59s
Create Docker Image / Final Docker Image Manifest (push) Successful in 11s
2024-09-09 14:02:51 +10:00
967aadf21d Add first message date to domain echoarea list
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 41s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 2m2s
Create Docker Image / Final Docker Image Manifest (push) Successful in 11s
2024-09-09 13:30:45 +10:00
75f5424d4f Change our mail packet name to be the youngest ID in the packet, not the youngest date
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 43s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m52s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-09-09 11:56:12 +10:00
e6c0511b33 Fix page rendering when a leftbox is rendered
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 50s
Create Docker Image / Build Docker Image (arm64) (push) Successful in -16h54m16s
Create Docker Image / Final Docker Image Manifest (push) Successful in 12s
2024-09-09 08:23:40 +10:00
7f95305d75 Fixes for DE-LIST notifications 2024-09-09 08:23:40 +10:00
9d7455233b Improvements for finding intransit netmails to upstream and peer hubs 2024-09-09 08:23:40 +10:00
c70222fef8 When posting to matrix, encode in triple backticks to create a mono-spaced message 2024-09-09 08:23:40 +10:00
2c4bf83250 Minor HTML fixes 2024-09-08 14:16:15 +10:00
84d9d271dc Fix for not detecting duplicate messages, when messages had a TZUTC 2024-09-08 14:14:37 +10:00
61dfadba5a Post matrix messages with UTF-8 4 kludge
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 37s
Create Docker Image / Build Docker Image (arm64) (push) Successful in -27h17m57s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2024-08-01 18:27:26 +10:00
616393edcc Add origin to replied messages, fix address in BadMessage notification
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 39s
Create Docker Image / Build Docker Image (arm64) (push) Successful in -23h34m54s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2024-07-30 20:54:47 +10:00
62a9139d14 Fix packet processing issue - we now find recent deleted address when creatingFTN, fix netmail processing with points, fix processing badly address netmails
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 35s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m52s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2024-07-12 12:47:50 +10:00
e10ca8f18a Updated Dockerfile to be consistent with other implementations 2024-07-11 22:03:19 +10:00
c1e6d436e5 We had the contacted notification inversed, also dont alert on points. 2024-06-30 11:40:53 +10:00
617901f7df Add AKA to email notification
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 38s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m50s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2024-06-30 11:25:04 +10:00
4d7af7c7e3 Store datetime in UTC format now, and fix presentation of TZUTC. Also standardise message summaries on Notifications
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 42s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m51s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2024-06-28 23:27:06 +10:00
fc930ba6c2 Revert "Fix date being used in mail packets, timezone was effectively being to a timestamp with the timezone already"
This function is used to get the true date of a message (taking into account the TZ), and needed when creating the TZUTC

This reverts commit ad0ad73b0c.
2024-06-28 18:30:34 +10:00
7f4540f5ec For some reason addresses.nodes_hub is passed a blank object, thus not retrieving hub's nodes 2024-06-28 18:30:34 +10:00
d25dae2427 Make debug logging of binkd consistent with other logging statements 2024-06-28 15:31:30 +10:00
cfb408766f Fix for bug introduced in 7c23971 that was affecting transfers with binkd nodes
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 38s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m46s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-06-28 05:28:35 +00:00
b9603e4bb8 Changing File/Send debugging to make it clear it is content read/open, not a network read/open
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 36s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m46s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2024-06-27 15:27:16 +10:00
173446edbe Fix for showing FTN list
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 36s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m43s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2024-06-27 10:57:01 +10:00
3333679fc6 Make it clear that phone on system is for the BBS phone number
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 42s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m48s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2024-06-27 10:40:19 +10:00
dcf34d9be2 Need to include pkg_msgs when lazyloading relations
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 40s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m47s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2024-06-27 10:06:11 +10:00
df07d9978e Fix for when Address::createFTN refuses to create an address
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 41s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m46s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2024-06-27 09:54:03 +10:00
f006d829f4 Fix for originating calls and not recognising that the remote accepted our password
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 37s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m45s
Create Docker Image / Final Docker Image Manifest (push) Successful in 11s
2024-06-27 09:09:47 +10:00
6374725e8e Added debugging for our read buffer, for problem debugging
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 37s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m46s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-06-27 08:57:00 +10:00
e2321f3acd Including size in debugging messages for items being sent
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 35s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m48s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2024-06-27 08:36:14 +10:00
d8c68bcaa2 Change DNS logging, especially around missing details
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 37s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m50s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2024-06-27 08:20:52 +10:00
a82927c1f6 Reduce DNS query debugging
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 36s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m46s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2024-06-26 23:56:30 +10:00
5e162a3427 Return more system attributes with Address::findFTN() 2024-06-26 23:55:10 +10:00
b62f18cf3d Remove protocol checking for validated, presented akas are now by definition valid
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 38s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m47s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2024-06-26 23:42:25 +10:00
b88046d57e Record AKA we are sending to, not system name
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 35s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m48s
Create Docker Image / Final Docker Image Manifest (push) Successful in 8s
2024-06-26 23:11:08 +10:00
d3c8bc844a Fix calls to deprecated getDateAttribute() for mail packets
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 39s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m47s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2024-06-26 22:44:31 +10:00
b891abb611 Netmail waiting query optimisation, increase logging on what we have for a node
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 43s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m53s
Create Docker Image / Final Docker Image Manifest (push) Successful in 8s
2024-06-26 22:37:38 +10:00
5f3db71451 Only show active AKAs for our systems 2024-06-26 22:29:05 +10:00
9ed7171c9b Add FTN to mail:list for netmails 2024-06-26 22:21:52 +10:00
298dfe6703 Fix message text. 2024-06-26 17:15:15 +10:00
d3a9e36bb7 Debugging when msgid's are auto added
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 39s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m51s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2024-06-26 16:49:29 +10:00
2f55de2ddf We need to reload the system address for notifications 2024-06-26 16:49:11 +10:00
b34b046d3c Fix polling for messages sent via uplinks, where those systems which had a status of HOLD 2024-06-26 16:47:10 +10:00
7c23971e58 Recognise all AKAs for a system when we are polling it with one of it's AKAs in a domain 2024-06-26 16:03:58 +10:00
91d4cd0b2f Lower the severity of a message when an AKA is not in our domain 2024-06-26 16:02:26 +10:00
db8475053c Attempt to catch Sending we got an EXCEPTION when in maintenance mode
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 40s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m50s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-06-26 12:18:06 +10:00
176ecb16a8 Exit gracefully if we dont generate a packet for debug:packet:dump 2024-06-26 11:22:33 +10:00
ad0ad73b0c Fix date being used in mail packets, timezone was effectively being to a timestamp with the timezone already
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 37s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m49s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-06-25 13:09:48 +10:00
11b7dc4229 Fix presenting PATH/SEENBY when point_id is null
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 36s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m47s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2024-06-25 11:20:08 +10:00
8b4e2cb9f0 Add system_id when listing AKAs, so that we can reference the system relation. 2024-06-25 11:20:08 +10:00
7bd192980f Fix file sending, as a result of moving $size into Send::class when optimising mail sending 2024-06-25 11:20:08 +10:00
0a5374c743 Fix filefix rescan, argument used is file not days 2024-06-21 11:45:31 +10:00
1b2358b5a9 Mail bundling and processing performance improvements
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 48s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m57s
Create Docker Image / Final Docker Image Manifest (push) Successful in 12s
2024-06-21 09:09:50 +10:00
c9700fbd0c Fix date for graph, problem introduced in 3f5668 2024-06-21 09:09:50 +10:00
ef2e208fde Fix for test failing introduced in df28732
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 42s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m54s
Create Docker Image / Final Docker Image Manifest (push) Successful in 11s
2024-06-18 18:01:56 +09:30
c28392b2b6 Remove deprecated methods
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 38s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m48s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-06-15 18:29:55 +10:00
180c620168 Support nodelist archives with more than 1 file in it 2024-06-15 18:29:55 +10:00
941117b342 Nodelist import update 2024-06-15 18:29:55 +10:00
df2873287c Abstract address session() details 2024-06-15 18:29:55 +10:00
0304967e80 Missed removing methods as a result of removing EncodeUTF8 in 29710c37 2024-06-13 20:34:42 +10:00
90c65fd5e1 Dont attempt to process System::default systems when processing address:idle
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 46s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m55s
Create Docker Image / Final Docker Image Manifest (push) Successful in 11s
2024-06-12 23:12:51 +10:00
a46ce7ff9e Posting messages to matrix
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 42s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m55s
Create Docker Image / Final Docker Image Manifest (push) Successful in 11s
2024-06-11 17:23:59 +10:00
86995b03a8 Receiving messages from matrix 2024-06-11 14:20:54 +10:00
61cfb773e2 Fix comments for Echomail Notifications 2024-06-11 14:18:37 +10:00
ba0f643dca Improvements to e692de7, which wasnt picking up netmail alerts
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 41s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m51s
Create Docker Image / Final Docker Image Manifest (push) Successful in 11s
2024-06-09 21:14:27 +10:00
d6779e6e01 Fix, when moving address and deleting the source system, also delete any system_logs 2024-06-08 21:27:42 +10:00
829fe1d7a9 Dont accept connections when application down
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 39s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m46s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-06-07 18:07:34 +10:00
b23d9351b5 Make $setup available to all blades
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 37s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m46s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-06-07 15:08:50 +10:00
153e4dc12b Only show system setting for packet msgs, not the resulting calculated value
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 40s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m45s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2024-06-07 14:29:30 +10:00
7c34a9f6c3 Explain why there are no echoareas/fileareas for the system
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 38s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m47s
Create Docker Image / Final Docker Image Manifest (push) Successful in 11s
2024-06-07 14:06:53 +10:00
368198bc77 Add link to netmail/echomail when showing packet contents 2024-06-07 13:30:58 +10:00
67377a2012 Clear messages from sent packet
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 38s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m46s
Create Docker Image / Final Docker Image Manifest (push) Successful in 11s
2024-06-07 11:12:06 +10:00
e692de7d7f When limiting the number of messages in a packet, only retrieve that number from the DB
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 36s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m44s
Create Docker Image / Final Docker Image Manifest (push) Successful in 11s
2024-06-07 10:51:28 +10:00
95b6058020 Fixes for Idle notifications
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 38s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m47s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-06-07 09:30:40 +10:00
a0b6df31f0 Move processed TIC files into Y/M/D heirarchy, consistent with packets
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 39s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m47s
Create Docker Image / Final Docker Image Manifest (push) Successful in 11s
2024-06-07 09:13:18 +10:00
06c29d8750 Updates to Message, Remove updated_at/mid from echomails/netmails, not used
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 35s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m47s
Create Docker Image / Final Docker Image Manifest (push) Successful in 11s
2024-06-07 08:54:49 +10:00
614d5b877b Make Autohold button consistent with other buttons, sold=active, outline=inactive
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 40s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m48s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-06-06 19:30:01 +10:00
742f0cd015 More UTF8 message processing fixes, specifically related to tagline/tearline/origin processing
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 39s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m46s
Create Docker Image / Final Docker Image Manifest (push) Successful in 12s
2024-06-05 21:57:16 +10:00
6b1cb8cd78 Fix finding echomail origin AKA by including the domain name
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 39s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m53s
Create Docker Image / Final Docker Image Manifest (push) Successful in 12s
2024-06-05 21:26:09 +10:00
2509aa99e1 Update bootstrap icons to 1.11.3
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 41s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m49s
Create Docker Image / Final Docker Image Manifest (push) Successful in 11s
2024-06-05 19:23:58 +10:00
be8aec4751 Make it easier to identify systems that have been marked not active 2024-06-05 19:23:58 +10:00
efdb6b96ea Fix for user creation 2024-06-05 19:23:58 +10:00
1753111f8f Prefix is an optional argument, so default it to blank 2024-06-05 19:23:58 +10:00
27cdb02b06 Fixes for saving a new system
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 41s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 2m8s
Create Docker Image / Final Docker Image Manifest (push) Successful in 11s
2024-06-03 22:31:38 +10:00
434226f8bc Netmail forwarding shouldnt be required
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 41s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m49s
Create Docker Image / Final Docker Image Manifest (push) Successful in 14s
2024-06-03 21:11:02 +10:00
8fb3a21fcd Normalise tagline/tearline/origin
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 39s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m51s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-06-03 19:09:09 +10:00
1d354da6e3 Enable per system configuration of messages per packet 2024-06-03 19:09:09 +10:00
0bc3d4cf60 Downgrade DNS query errors, since they are handled 2024-06-03 19:09:09 +10:00
38795b83bf Move HAproxy exceptions into their own class, and downgrade HAproxy errors since they are handled 2024-06-03 19:09:09 +10:00
73cf421739 Remove EncodeUTF8 infavour of using attribute casting only. The implementation of EncodeUTF8 was not correct, essentially removing any previous casting causing issues when saving a record. 2024-06-03 19:09:09 +10:00
b5047c52f0 Fix for processing inbound ping netmail messages
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 40s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m51s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-05-31 21:57:21 +10:00
27fe3cd223 Fix message processing when tag/tear lines dont start with double \r
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 41s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m54s
Create Docker Image / Final Docker Image Manifest (push) Successful in 11s
2024-05-30 20:14:50 +10:00
2590c2de71 Remove deprecated Domain::managed()
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 37s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m49s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-05-29 19:45:08 +10:00
31770241ec Fix for when no zones/echoareas have been created yet in a domain 2024-05-29 19:45:00 +10:00
c414ebd053 Created a 'zc' role to be used when a user is a ZC of a domain, or a site admin
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 41s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m56s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-05-28 23:21:54 +10:00
302 changed files with 11358 additions and 6772 deletions

View File

@@ -1,55 +1,25 @@
APP_NAME="Clearing Houz"
APP_ENV=production
APP_KEY=
APP_DEBUG=false
APP_URL=http://clrghouz
APP_MAINTENANCE_DRIVER=cache
APP_MAINTENANCE_STORE=memcached
APP_TIMEZONE=
APP_URL=
LOG_CHANNEL=stack
LOG_LEVEL=info
AUTH_PASSWORD_RESET_TOKEN_TABLE=password_resets
CACHE_STORE=memcached
MEMCACHED_HOST=memcached
DB_CONNECTION=pgsql
DB_HOST=postgres
DB_PORT=5432
DB_DATABASE=clrghouz
DB_USERNAME=clrghouz
DB_PASSWORD=
#DB_SSLMODE=prefer
#DB_SSLROOTCERT=/var/www/html/config/ssl/ca.crt
#DB_SSLCERT=/var/www/html/config/ssl/client.crt
#DB_SSLKEY=/var/www/html/config/ssl/client.key
BROADCAST_DRIVER=log
MEMCACHED_HOST=memcached
CACHE_DRIVER=memcached
QUEUE_CONNECTION=database
SESSION_DRIVER=file
SESSION_LIFETIME=120
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=
REDIS_PORT=6379
MAIL_DRIVER=smtp
MAIL_HOST=mail.dege.lan
MAIL_PORT=25
MAIL_USERNAME=
MAIL_PASSWORD=
MAIL_ENCRYPTION=
MAIL_AUTO_EMBED_METHOD=base64
PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_APP_CLUSTER=mt1
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
FIDO_DIR=fido
FIDO_PACKET_KEEP=
FIDO_STRICT=false
FILESYSTEM_DISK=s3
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
@@ -57,3 +27,24 @@ AWS_BUCKET=
AWS_ENDPOINT=
AWS_DEFAULT_REGION=home
AWS_USE_PATH_STYLE_ENDPOINT=true
LOG_CHANNEL=daily
LOG_LEVEL=info
LOG_DAILY_DAYS=93
MAIL_MAILER=smtp
MAIL_HOST=smtp
MAIL_PORT=25
MAIL_USERNAME=
MAIL_PASSWORD=
MAIL_ENCRYPTION=
MAIL_AUTO_EMBED_METHOD=base64
SESSION_DRIVER=file
# Clrghouz configuration
FIDO_DNS_NS=
MATRIX_SERVER=
MATRIX_AS_TOKEN=
MATRIX_HS_TOKEN=

View File

@@ -5,6 +5,8 @@ APP_DEBUG=true
APP_URL=http://clrghouz
APP_TIMEZONE=Australia/Melbourne
CACHE_STORE=array
LOG_CHANNEL=stderr
LOG_LEVEL=debug
@@ -13,7 +15,7 @@ DB_HOST=postgres-test
DB_PORT=5432
DB_DATABASE=test
DB_USERNAME=test
DB_PASSWORD=test
DB_PASSWORD=password
BROADCAST_DRIVER=log
CACHE_DRIVER=file

View File

@@ -74,8 +74,7 @@ jobs:
( dockerd --host=tcp://0.0.0.0:2375 --tls=false & ) && sleep 3
## Some debugging info
# docker info && docker version
env|sort
echo "PRT: ${{ secrets.PKG_WRITE_TOKEN }}"
# env|sort
- name: Registry FQDN Setup
id: registry
@@ -93,12 +92,10 @@ jobs:
- name: Code Checkout
uses: actions/checkout@v4
- name: Record version
- name: Record version and Delete Unnecessary files
run: |
pwd
ls -al
echo ${GITHUB_SHA::8} > VERSION
cat VERSION
rm -rf .git* tests/ storage/app/test/
- name: Build and Push Docker Image
uses: docker/build-push-action@v5

View File

@@ -9,6 +9,7 @@ use Illuminate\Support\Facades\Log;
use App\Classes\Dynamic;
use App\Models\Address;
use App\Traits\HubStats as HubStatsTrait;
/**
* This method will generate the hub status for an upstream Host/RC/ZC
@@ -18,6 +19,8 @@ use App\Models\Address;
*/
class HubStats extends Dynamic
{
use HubStatsTrait;
private const LOGKEY = 'DHS';
private string $name = '';
@@ -32,41 +35,8 @@ class HubStats extends Dynamic
{
$date = Carbon::now()->yesterday()->endOfday();
$r = Address::select([
'a.id',
'addresses.system_id',
'addresses.zone_id',
'addresses.region_id',
'addresses.host_id',
'addresses.node_id',
'addresses.point_id',
'addresses.hub_id',
'addresses.role',
DB::raw('sum(a.uncollected_echomail) as uncollected_echomail'),
DB::raw('sum(a.uncollected_netmail) as uncollected_netmail'),
DB::raw('sum(a.uncollected_files) as uncollected_files')
])
->from(
Address::UncollectedEchomailTotal()
->where('echomails.created_at','<',$date)
->union(Address::UncollectedNetmailTotal()
->where('netmails.created_at','<',$date)
)
->union(Address::UncollectedFilesTotal()
->where('files.created_at','<',$date)
),'a')
->where('systems.active',true)
->where('addresses.active',TRUE)
->where('zones.active',TRUE)
->where('domains.active',TRUE)
->where('zones.id',$this->ao->zone_id)
->join('addresses',['addresses.id'=>'a.id'])
->join('systems',['systems.id'=>'addresses.system_id'])
->join('zones',['zones.id'=>'addresses.zone_id'])
->join('domains',['domains.id'=>'zones.domain_id'])
->ftnOrder()
->groupBy('addresses.system_id','a.id','addresses.zone_id','addresses.region_id','addresses.host_id','addresses.node_id','addresses.point_id','addresses.hub_id','addresses.role')
->with(['system','zone.domain']);
$r = $this->HubStats($date)
->where('zones.id',$this->ao->zone_id);
$header = "| %-12s | %4d | %3d | %3d | %16s | %5s | %5s |\r\n";
@@ -100,7 +70,7 @@ class HubStats extends Dynamic
$o->uncollected_echomail ?? 0,
$o->uncollected_netmail ?? 0,
$o->uncollected_files ?? 0,
$o->system->last_session?->format('Y-m-d H:i'),
$o->system->last_seen?->format('Y-m-d H:i') ?: '-',
is_null($o->system->pollmode) ? 'HOLD' : ($o->system->pollmode ? 'CRASH' : 'DAILY'),
$o->system->autohold ? 'YES' : 'NO');
}

View File

@@ -18,20 +18,16 @@ class NodelistSegment extends Dynamic
{
private const LOGKEY = 'DNL';
private string $name = '';
private ?string $name;
private Address $our_address;
private Carbon $now;
public function __construct(Address $ao,Collection $arg)
{
$this->our_address = our_address($ao->zone->domain)->first();
$this->our_address = our_address($ao->zone->domain,FALSE)->first();
$this->now = Carbon::now();
$this->name = sprintf('z%dn%d.%d',
$this->our_address->zone->zone_id,
$this->our_address->host_id,
$this->now->format('z'),
);
$this->name = $arg->get('name','');
Log::debug(sprintf('%s:- Generating Nodelist for [%s] from [%s] as [%s] with arguments',self::LOGKEY,$ao->ftn,$this->our_address->ftn,$this->our_address->role_name),['args'=>$arg]);
}
@@ -59,7 +55,7 @@ class NodelistSegment extends Dynamic
$result->push('CM');
if ($ao->system->address) {
$result->push(sprintf('INA:%s',$ao->system->address));
$result->push(sprintf('INA:%s',our_address($ao->domain)->contains($ao->id) ? our_hostname($ao) : $ao->system->address));
if (($x=$ao->system->mailers->pluck('name')->search('BINKP')) !== FALSE)
$result->push(sprintf('IBN%s',(($y=$ao->system->mailers->get($x)->pivot->port) !== 24554) ? ':'.$y : ''));
@@ -149,7 +145,7 @@ class NodelistSegment extends Dynamic
if ($oo->system_id == $so->id)
continue;
$result->push($this->generate($oo) ?: $this->entry($oo));
$result->push($this->entry($oo));
}
return $result->join("\n");
@@ -157,6 +153,8 @@ class NodelistSegment extends Dynamic
public function getName(): string
{
return $this->name;
return ($this->name ?: sprintf('z%dn%d',
$this->our_address->zone->zone_id,
$this->our_address->host_id)).'.'.sprintf('%03d',$this->now->format('z'));
}
}

View File

@@ -17,7 +17,7 @@ abstract class FTN
$this->fn,
$this->ff,
$this->fp,
).($this->zone ? sprintf('@%s',$this->zone->domain->name) : '');
).((isset($this->zone) && $this->zone) ? sprintf('@%s',$this->zone->domain->name) : '');
case 'tftn_t':
return sprintf('%d:%d/%d.%d',
@@ -25,7 +25,7 @@ abstract class FTN
$this->tn,
$this->tf,
$this->tp,
).($this->zone ? sprintf('@%s',$this->zone->domain->name) : '');
).((isset($this->zone) && $this->zone) ? sprintf('@%s',$this->zone->domain->name) : '');
case 'fftn':
return Address::findFTN($this->fftn_t);

View File

@@ -8,6 +8,7 @@ use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
use Illuminate\Validation\Validator as ValidatorResult;
use App\Classes\FTN as FTNBase;
@@ -21,7 +22,31 @@ use App\Traits\ObjectIssetFix;
* Represents the structure of a message in a packet
*
* @note FTN packed echomail messages are ZONE agnostic.
* @package App\Classes
* @note FTN packed netmails may not have an INTL kludge
*
* We work out addresses using the following approach/priority:
* = By definition we should know the author node, because it's either ours or (will be) in the nodelist (but it might not be there yet)
* = The target node may not be in the nodelist (anymore)
*
* + Echomail - only has source addresses (MUST have an AREA: tag, otherwise its netmail)
* a Origin Line " * Origin: <some text> (z:f/n.p)
* b MSGID Kludge "MSGID: z:f/n.p<@domain> <sometext>
* c net/node from msg headers (dst should be to hub to be processed)
* d domain address from packet (2.2 only) (dst should be to hub to be processed)
* e point from packet (2+/2e/2.2) (dst should be to hub to be processed)
* f zone from (2/2+/2e/2.2) (dst should be to hub to be processed)
*
* RULES:
* + if a exists, c, e, f must match
* + if b exists, c, d (if present), e, f must match
*
* + Netmail
* a INTL kludge (may not exist)
* b FMPT/TOPT (points only)
* c src & dst net/node from msg headers
* d src domain address from packet (2.2 only) (dst is to next hop, not final destination)
* e src point from packet (2+/2e/2.2) (dst is to next hop, not final destination)
* f src zone from (2/2+/2e/2.2) (dst is to next hop, not final destination)
*/
class Message extends FTNBase
{
@@ -89,13 +114,10 @@ class Message extends FTNBase
public const AREATAG_LEN = 35; //
private array $header; // Message Header
private int $tzutc = 0; // TZUTC that needs to be converted to be used by Carbon @see self::kludges
private Collection $kludges; // TZUTC that needs to be converted to be used by Carbon @see self::kludges
private Echomail|Netmail $mo; // The object storing this packet message
private Address $us; // Our address for this message
/** @deprecated Not sure why this is needed? */
public bool $packed = FALSE; // Has the message been packed successfully
// Convert characters into printable chars
// https://int10h.org/oldschool-pc-fonts/readme/#437_charset
private const CP437 = [
@@ -144,7 +166,8 @@ class Message extends FTNBase
public static function header_len(): int
{
return collect(static::HEADER)->sum(function($item) { return Arr::get($item,2); });
return collect(static::HEADER)
->sum(fn($item)=>Arr::get($item,2));
}
/**
@@ -236,7 +259,7 @@ class Message extends FTNBase
$o->mo->from = $o->header['user_from'];
$o->mo->subject = $o->header['subject'];
$o->mo->datetime = $o->datetime;
$o->mo->datetime = $o->datetime->clone()->utc();
$o->mo->tzoffset = $o->datetime->utcOffset();
$o->mo->flags = $o->header['flags'];
$o->mo->cost = $o->header['cost'];
@@ -297,6 +320,7 @@ class Message extends FTNBase
public function __construct(Zone $zone)
{
$this->zone = $zone;
$this->kludges = collect();
}
public function __get($key)
@@ -306,10 +330,11 @@ class Message extends FTNBase
switch ($key) {
// From Addresses
// @todo $this->src no longer appears to be defined
case 'fz': return (int)Arr::get($this->src,'z');
case 'fn': return (int)($x=$this->src) ? Arr::get($x,'n') : Arr::get($this->header,'onet');
case 'ff': return (int)($x=$this->src) ? Arr::get($x,'f') : Arr::get($this->header,'onode');
case 'fp': return (int)$this->mo->kludges->get('FMPT:') ?: Arr::get($this->src,'p',Arr::get($this->header,'opoint',0));
case 'fp': return (int)$this->mo->kludges->get('FMPT') ?: Arr::get($this->src,'p',Arr::get($this->header,'opoint',0));
case 'fd': return Arr::get($this->src,'d');
case 'fzone':
@@ -326,6 +351,7 @@ class Message extends FTNBase
return Zone::where('zone_id',$this->fz)
->where('default',TRUE)
->single();
case 'fdomain':
// We'll use the zone's domain if this method class was called with a zone
if ($this->zone && (($this->zone->domain->name === Arr::get($this->src,'d')) || ! Arr::get($this->src,'d')))
@@ -343,7 +369,7 @@ class Message extends FTNBase
case 'tz': return (int)Arr::get($this->isEchomail() ? $this->src : $this->dst,'z');
case 'tn': return (int)Arr::get($this->header,'dnet');
case 'tf': return (int)Arr::get($this->header,'dnode');
case 'tp': return (int)$this->mo->kludges->get('TOPT:') ?: Arr::get($this->header,'dpoint',0);
case 'tp': return (int)$this->mo->kludges->get('TOPT') ?: Arr::get($this->header,'dpoint',0);
case 'tzone':
// Use the zone if this class was called with it.
@@ -373,23 +399,6 @@ class Message extends FTNBase
// Otherwise we'll assume the same as the source domain
return $this->fdomain ?: NULL;
case 'fftn_t':
case 'fftn':
case 'tftn_t':
case 'tftn':
return parent::__get($key);
// For 5D we need to include the domain
/* @deprecated - is this required? */
case 'fboss':
return sprintf('%d:%d/%d',$this->fz,$this->fn,$this->ff).(($x=$this->fdomain) ? '@'.$x->name : '');
case 'tboss':
return sprintf('%d:%d/%d',$this->tz,$this->tn,$this->tf).(($x=$this->tdomain) ? '@'.$x->name : '');
case 'fboss_o':
return Address::findFTN($this->fboss);
case 'tboss_o':
return Address::findFTN($this->tboss);
// Convert our message (header[datetime]) with our TZUTC into a Carbon date
case 'datetime':
try {
@@ -489,19 +498,23 @@ class Message extends FTNBase
break;
case 'tzutc':
return $this->kludges->get($key);
default:
throw new \Exception('Unknown key: '.$key);
return parent::__get($key);
}
}
/**
* When we serialise this object, we'll need to utf8_encode some values
*
* @return array
*/
public function __serialize(): array
public function __set(string $key,mixed $value): void
{
return $this->encode();
switch ($key) {
case 'tzutc':
if (! is_numeric($value))
throw new InvalidPacketException('TZUTC is not numeric '.$value);
$this->kludges->put($key,$value);
}
}
/**
@@ -512,8 +525,6 @@ class Message extends FTNBase
*/
public function __toString(): string
{
$s = Setup::findOrFail(config('app.id'));
$return = pack(collect(self::HEADER)->pluck(1)->join(''),
$this->mo->fftn->node_id, // Originating Node
$this->mo->tftn->node_id, // Destination Node
@@ -524,15 +535,12 @@ class Message extends FTNBase
$this->mo->date->format('d M y H:i:s'),
);
$return .= $this->mo->to."\00";
$return .= $this->mo->from."\00";
$return .= $this->mo->subject."\00";
if (($this->mo instanceof Netmail) && $this->mo->isFlagSet(self::FLAG_LOCAL)) {
// If there isnt an INTL kludge, we'll add it
if (! $this->mo->kludges->has('INTL'))
$this->mo->kludges->put('INTL',sprintf('%s %s',$this->mo->tftn->ftn3d,$this->mo->fftn->ftn3d));
$return .= Str::limit($this->mo->to,self::USER_TO_LEN,'')."\00";
$return .= Str::limit($this->mo->from,self::USER_FROM_LEN,'')."\00";
$return .= Str::limit($this->mo->subject,self::SUBJECT_LEN-3)."\00";
// Add our FMPT/TOPT kludges for netmails to a point
if ($this->mo instanceof Netmail) {
if ((! $this->mo->kludges->has('FMPT')) && $this->mo->fftn->point_id)
$this->mo->kludges->put('FMPT',$this->mo->fftn->point_id);
@@ -540,12 +548,16 @@ class Message extends FTNBase
$this->mo->kludges->put('TOPT',$this->mo->tftn->point_id);
}
$this->mo->kludges->put($this->mo->isFlagSet(self::FLAG_LOCAL) ? 'PID:' : 'TID:',sprintf('%s %s',Setup::PRODUCT_NAME_SHORT,$s->version));
$this->mo->kludges->put($this->mo->isFlagSet(self::FLAG_LOCAL) ? 'PID:' : 'TID:',sprintf('%s %s',Setup::PRODUCT_NAME_SHORT,Setup::version()));
$this->mo->kludges->put('DBID:',$this->mo->id);
if ($this->mo instanceof Echomail)
$return .= sprintf("AREA:%s\r",strtoupper($this->mo->echoarea->name));
// Rebuild the INTL kludge line
elseif ($this->mo instanceof Netmail)
$this->mo->kludges->put('INTL',sprintf('%s %s',$this->mo->tftn->ftn3d,$this->mo->fftn->ftn3d));
// Add some kludges
$return .= sprintf("\01TZUTC: %s\r",str_replace('+','',$this->mo->date->getOffsetString('')));
@@ -568,14 +580,19 @@ class Message extends FTNBase
$return .= sprintf("\x01Via %s @%s.UTC %s %s\r",
$this->us->ftn3d,
Carbon::now()->format('Ymd.His'),
Setup::PRODUCT_NAME_SHORT,$s->version);
Setup::PRODUCT_NAME_SHORT,Setup::version());
} else {
// FTS-0004.001/FSC-0068.001 The message SEEN-BY lines
// FTS-0004.001/FSC-0068.001 The message PATH lines
// @todo This unique() function here shouldnt be required, but is while system generated messages are storing path/seenby
$path = $this->mo->path->push($this->us)->unique('ftn')->filter(fn($item)=>($item->point_id === 0));
$path = $this
->mo
->path
->push($this->us)
->unique('ftn')
->filter(fn($item)=>is_null($item->point_id) || ($item->point_id === 0));
// Create our rogue seenby objects
$seenby = $this->mo->seenby;
@@ -589,7 +606,7 @@ class Message extends FTNBase
$seenby = $seenby
->push($this->us)
->filter(fn($item)=>($item->point_id === 0))
->filter(fn($item)=>is_null($item->point_id) || ($item->point_id === 0))
->unique('ftn')
->sortBy(function($item) { return sprintf('%05d%05d',$item->host_id,$item->node_id);});
@@ -602,16 +619,6 @@ class Message extends FTNBase
return $return;
}
/**
* When we unserialize, we'll restore (utf8_decode) some values
*
* @param array $values
*/
public function __unserialize(array $values): void
{
$this->decode($values);
}
/**
* Reduce our PATH/SEEN-BY for messages as per FSC-0068
*
@@ -672,8 +679,9 @@ class Message extends FTNBase
* @param Echomail|Netmail $o
* @return Echomail|Netmail
* @throws InvalidPacketException
* @todo Remove parsing $o as second object, make this private, and use $this->... instead of $o->...
*/
private function unpackMessage(string $message,Echomail|Netmail $o): Echomail|Netmail
public function unpackMessage(string $message,Echomail|Netmail $o): Echomail|Netmail
{
// Remove DOS \n\r
$message = preg_replace("/\n\r/","\r",$message);
@@ -682,136 +690,159 @@ class Message extends FTNBase
// First find our kludge lines
$ptr_start = 0;
while (substr($message,$ptr_start,1) === "\x01") {
$ptr_end = strpos($message,"\r",$ptr_start);
try {
while (substr($message,$ptr_start,1) === "\x01") {
$ptr_end = strpos($message,"\r",$ptr_start);
$m = [];
$kludge = ($x=substr($message,$ptr_start+1,$ptr_end-$ptr_start-1));
preg_match('/^([^\s]+:?)+\s+(.*)$/',$kludge,$m);
$m = [];
$kludge = ($x=substr($message,$ptr_start+1,$ptr_end-$ptr_start-1));
preg_match('/^([^\s]+:?)+\s+(.*)$/',$kludge,$m);
$ptr_start = $ptr_end+1;
if (! $m) {
Log::alert(sprintf('%s:! Invalid Kluge Line [%s]',self::LOGKEY,$x));
continue;
}
// Catch any kludges we need to process here
if (array_key_exists($m[1],self::kludges)) {
// Some earlier mystic message had a blank value for TZUTC
if ((($m[1]) === 'TZUTC:') && (! $m[2]))
$m[2] = '0000';
$this->{self::kludges[$m[1]]} = $m[2];
} else
$o->kludges = [$m[1],$m[2]];
}
// Next our message content ends with '\r * Origin: ... \r' or <soh>...
// FTS-0004.001
if ($ptr_end=strrpos($message,"\r * Origin: ",$ptr_start)) {
// Find the <cr>
$ptr_end = strpos($message,"\r",$ptr_end+1);
// If there is no ptr_end, then this is not an origin
if (! $ptr_end)
throw new InvalidPacketException('Couldnt find the end of the origin');
} elseif (! $ptr_end=strpos($message,"\r\x01",$ptr_start)) {
$ptr_end = strlen($message);
}
$remaining = substr($message,$ptr_end+1);
// At this point, the remaining part of the message should start with \x01, PATH or SEEN-BY
if ((substr($remaining,0,9) !== 'SEEN-BY: ') && (substr($remaining,0,5) !== 'PATH:') && ($x=strpos($remaining,"\x01")) !== 0) {
if ($x)
$ptr_end += $x;
else
$ptr_end += strlen($remaining);
}
// Process the message content
if ($content=substr($message,$ptr_start,$ptr_end-$ptr_start)) {
$o->msg_src = $content;
$o->msg_crc = md5($content);
$ptr_content_start = 0;
// See if we have a tagline
if ($ptr_content_end=strrpos($content,"\r... ",$ptr_content_start)) {
$o->msg = substr($content,$ptr_content_start,$ptr_content_end+1);
$ptr_content_start = $ptr_content_end+5;
$ptr_content_end = strpos($content,"\r",$ptr_content_start);
// If there is no terminating "\r", then that's it
if (! $ptr_content_end) {
$o->set_tagline = substr($content,$ptr_content_start);
$ptr_content_start = strlen($content);
} else {
$o->set_tagline = substr($content,$ptr_content_start,$ptr_content_end-$ptr_content_start);
$ptr_content_start = $ptr_content_end;
}
}
// See if we have a tearline
if ($ptr_content_end=strrpos($content,"\r--- ",$ptr_content_start)) {
if (! $ptr_content_start)
$o->msg = substr($content,$ptr_content_start,$ptr_content_end+1);
$ptr_content_start = $ptr_content_end+5;
$ptr_content_end = strpos($content,"\r",$ptr_content_start);
// If there is no terminating "\r", then that's it
if (! $ptr_content_end) {
$o->set_tearline = substr($content,$ptr_content_start);
$ptr_content_start = strlen($content);
} else {
$o->set_tearline = substr($content,$ptr_content_start,$ptr_content_end-$ptr_content_start);
$ptr_content_start = $ptr_content_end;
}
}
// See if we have an origin
if ($ptr_content_end=strrpos($content,"\r * Origin: ",$ptr_content_start)) {
if (! $ptr_content_start)
$o->msg = substr($content,$ptr_content_start,$ptr_content_end);
$ptr_content_start = $ptr_content_end+12;
$ptr_content_end = strpos($content,"\r",$ptr_content_start);
// If there is no terminating "\r", then that's it
if (! $ptr_content_end) {
$o->set_origin = substr($content,$ptr_content_start);
$ptr_content_start = strlen($content);
} else {
$o->set_origin = substr($content,$ptr_content_start,$ptr_content_end-$ptr_content_start);
$ptr_content_start = $ptr_content_end+1;
}
}
// If there wasnt any tagline/tearline/origin, then the whole content is the message
if (! $ptr_content_start) {
$o->msg = $content;
$ptr_content_start = $ptr_end-$ptr_start;
}
// Trim any right \r from the message
$o->msg = rtrim($o->msg,"\r");
// Quick validation that we are done
if ($ptr_content_start !== strlen($content)) {
Log::alert(sprintf('%s:! We failed parsing the message start [%d] content [%d]',self::LOGKEY,$ptr_content_start,strlen($content)));
$o->msg = substr($message,0,$ptr_end);
}
}
$ptr_start = $ptr_end+1;
if (! $m) {
Log::alert(sprintf('%s:! Invalid Kluge Line [%s]',self::LOGKEY,$x));
continue;
// Finally work out control kludges
foreach (collect(explode("\r",substr($message,$ptr_start)))->filter() as $line) {
// If the line starts with <soh> ignore it
if (substr($line,0,1) === "\x01")
$line = ltrim($line,"\x01");
$m = [];
preg_match('/^([^\s]+:?)+\s+(.*)$/',$line,$m);
// Messages that originate from a point dont have anything in a PATH
if (count($m) === 2)
$o->kludges = [$m[1],$m[2]];
}
// Catch any kludges we need to process here
if (array_key_exists($m[1],self::kludges))
$this->{self::kludges[$m[1]]} = $m[2];
else
$o->kludges = [$m[1],$m[2]];
}
} catch (\Exception $e) {
Log::error(sprintf('%s:! Error parsing message, now at offset [0x%02x] (%s)',
self::LOGKEY,
$ptr_start,
$e->getMessage()),['dump'=>hex_dump($message),'line'=>$e->getLine(),'file'=>$e->getFile()]);
// Next our message content ends with '\r * Origin: ... \r' or <soh>...
// FTS-0004.001
if ($ptr_end=strrpos($message,"\r * Origin: ",$ptr_start)) {
// Find the <cr>
$ptr_end = strpos($message,"\r",$ptr_end+1);
// If there is no ptr_end, then this is not an origin
if (! $ptr_end)
throw new InvalidPacketException('Couldnt find the end of the origin');
} elseif (! $ptr_end=strpos($message,"\r\x01",$ptr_start)) {
throw new InvalidPacketException('Couldnt parse the end of the content');
}
$remaining = substr($message,$ptr_end+1);
// At this point, the remaining part of the message should start with \x01, PATH or SEEN-BY
if ((substr($remaining,0,9) !== 'SEEN-BY: ') && (substr($remaining,0,5) !== 'PATH:') && ($x=strpos($remaining,"\x01")) !== 0) {
if ($x)
$ptr_end += $x;
else
$ptr_end += strlen($remaining);
}
// Process the message content
if ($content=substr($message,$ptr_start,$ptr_end-$ptr_start)) {
$o->msg_src = $content;
$o->msg_crc = md5($content);
$ptr_content_start = 0;
// See if we have a tagline
if ($ptr_content_end=strrpos($content,"\r\r... ",$ptr_content_start)) {
$o->msg = substr($content,$ptr_content_start,$ptr_content_end);
$ptr_content_start = $ptr_content_end+6;
$ptr_content_end = strpos($content,"\r",$ptr_content_start);
// If there is no terminating "\r", then that's it
if (! $ptr_content_end) {
$o->set_tagline = substr($content,$ptr_content_start);
$ptr_content_start = strlen($content);
} else {
$o->set_tagline = substr($content,$ptr_content_start,$ptr_content_end-$ptr_content_start);
$ptr_content_start = $ptr_content_end;
}
}
// See if we have a tearline
if ($ptr_content_end=strrpos($content,"\r\r--- ",$ptr_content_start)) {
if (! $ptr_content_start)
$o->msg = substr($content,$ptr_content_start,$ptr_content_end);
$ptr_content_start = $ptr_content_end+6;
$ptr_content_end = strpos($content,"\r",$ptr_content_start);
// If there is no terminating "\r", then that's it
if (! $ptr_content_end) {
$o->set_tearline = substr($content,$ptr_content_start);
$ptr_content_start = strlen($content);
} else {
$o->set_tearline = substr($content,$ptr_content_start,$ptr_content_end-$ptr_content_start);
$ptr_content_start = $ptr_content_end;
}
}
// See if we have an origin
if ($ptr_content_end=strrpos($content,"\r * Origin: ",$ptr_content_start)) {
if (! $ptr_content_start)
$o->msg = substr($content,$ptr_content_start,$ptr_content_end);
$ptr_content_start = $ptr_content_end+12;
$ptr_content_end = strpos($content,"\r",$ptr_content_start);
// If there is no terminating "\r", then that's it
if (! $ptr_content_end) {
$o->set_origin = substr($content,$ptr_content_start);
$ptr_content_start = strlen($content);
} else {
$o->set_origin = substr($content,$ptr_content_start,$ptr_content_end-$ptr_content_start);
$ptr_content_start = $ptr_content_end+1;
}
}
// If there wasnt any tagline/tearline/origin, then the whole content is the message
if (! $ptr_content_start) {
$o->msg = $content;
$ptr_content_start = $ptr_end-$ptr_start;
}
// Quick validation that we are done
if ($ptr_content_start !== strlen($content))
throw new InvalidPacketException('There is more data in the message content?');
}
$ptr_start = $ptr_end+1;
// Finally work out control kludges
foreach (collect(explode("\r",substr($message,$ptr_start)))->filter() as $line) {
// If the line starts with <soh> ignore it
if (substr($line,0,1) === "\x01")
$line = ltrim($line,"\x01");
$m = [];
preg_match('/^([^\s]+:?)+\s+(.*)$/',$line,$m);
$o->kludges = [$m[1],$m[2]];
throw new InvalidPacketException('Error parsing message');
}
return $o;
@@ -847,9 +878,6 @@ class Message extends FTNBase
'replyid' => 'sometimes|min:1',
'msg' => 'required|min:1', // @todo max message length?
'msg_crc' => 'required|size:32',
'tagline' => 'sometimes|min:1|max:255',
'tearline' => 'sometimes|min:1|max:255',
'origin' => 'sometimes|min:1|max:255',
'local' => 'sometimes|boolean',
'fftn_id' => 'required|exists:App\Models\Address,id',
'tftn_id' => $this->isNetmail() ? 'required|exists:App\Models\Address,id' : 'prohibited',
@@ -870,13 +898,26 @@ class Message extends FTNBase
);
$validator->after(function($validator) {
// @todo If the message has an INTL kludge, we send the message to our ZC, and if we are the ZC onto the dst ZC
// @todo So validate we can send it on
// @todo This validation below is incorrect - netmails only need to have an INTL if they are traversing zones
// @todo Without an INTL it is affecting our determination of a source zone/dst zone
if (($this->mo instanceof Netmail) && (! $this->mo->kludges->has('INTL')))
$validator->errors()->add('no-intl','Netmail message is missing INTL KLUDGE.');
if ($this->zone->domain->flatten) {
if (! $this->zone->domain->zones->pluck('zone_id')->contains($this->fz))
$validator->errors()->add('invalid-zone',sprintf('Message zone [%d] doesnt match any zone in domain for packet zone [%d].',$this->fz,$this->zone->zone_id));
$validator->errors()->add('invalid-zone',sprintf('Message from zone [%d] doesnt match any zone in domain for packet zone [%d].',$this->fz,$this->zone->zone_id));
if (! $this->zone->domain->zones->pluck('zone_id')->contains($this->tz))
$validator->errors()->add('invalid-zone',sprintf('Message to zone [%d] doesnt match any zone in domain for packet zone [%d].',$this->fz,$this->zone->zone_id));
} else {
if ($this->zone->zone_id !== $this->fz)
$validator->errors()->add('invalid-zone',sprintf('Message zone [%d] doesnt match packet zone [%d].',$this->fz,$this->zone->zone_id));
$validator->errors()->add('invalid-zone',sprintf('Message from zone [%d] doesnt match packet zone [%d].',$this->fz,$this->zone->zone_id));
if ($this->zone->zone_id !== $this->tz)
$validator->errors()->add('invalid-zone',sprintf('Message to zone [%d] doesnt match packet zone [%d].',$this->tz,$this->zone->zone_id));
}
if (! $this->fftn)

View File

@@ -3,6 +3,7 @@
namespace App\Classes\FTN;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
@@ -11,8 +12,8 @@ use Symfony\Component\HttpFoundation\File\File;
use App\Classes\FTN as FTNBase;
use App\Exceptions\InvalidPacketException;
use App\Models\{Address,Domain,Echomail,Netmail,Software,System,Zone};
use App\Notifications\Netmails\EchomailBadAddress;
use App\Models\{Address,Echomail,Netmail,Software,System,Zone};
use App\Notifications\Netmails\{EchomailBadAddress,NetmailBadAddress};
/**
* Represents a Fidonet Packet, that contains an array of messages.
@@ -51,9 +52,10 @@ abstract class Packet extends FTNBase implements \Iterator, \Countable
protected Address $fftn_p; // Address the packet is from (when packing messages)
protected Address $tftn_p; // Address the packet is to (when packing messages)
protected Collection $messages; // Messages in the Packet
protected string $content; // Outgoing packet data
public Collection $errors; // Messages that fail validation
protected int $index; // Our array index
protected $pass_p = NULL; // Overwrite the packet password (when packing messages)
protected $pass_p = NULL; // Overwrite the packet password (when packing messages)
/* ABSTRACT */
@@ -66,7 +68,7 @@ abstract class Packet extends FTNBase implements \Iterator, \Countable
* @return bool
*/
abstract public static function is_type(string $header): bool;
abstract protected function header(): string;
abstract protected function header(Collection $msgs): string;
/* STATIC */
@@ -86,11 +88,12 @@ abstract class Packet extends FTNBase implements \Iterator, \Countable
* @param mixed $f File handler returning packet data
* @param string $name
* @param int $size
* @param Domain|null $domain
* @param System|null $so - The system that sent us the packet, used to figure out domains if the packet is for a different zone
* @param bool $process
* @return Packet
* @throws InvalidPacketException
*/
public static function process(mixed $f,string $name,int $size,Domain $domain=NULL): self
public static function process(mixed $f,string $name,int $size,System $so=NULL,bool $process=TRUE): self
{
Log::debug(sprintf('%s:+ Opening Packet [%s] with size [%d]',self::LOGKEY,$name,$size));
@@ -137,26 +140,41 @@ abstract class Packet extends FTNBase implements \Iterator, \Countable
} else
throw new InvalidPacketException('Not a valid packet, not EOP or SOM:'.bin2hex($x));
Log::info(sprintf('%s:- Packet [%s] is a [%s] packet, dated [%s]',self::LOGKEY,$o->name,get_class($o),$o->date));
Log::info(sprintf('%s:- Packet [%s] is a [%s] packet',self::LOGKEY,$o->name,get_class($o)));
// Work out the packet zone
if ($o->fz && ($o->fd || $domain)) {
$o->zone = Zone::select('zones.*')
if ($o->fz && $o->fd) {
$o->zone = Zone::where('zone_id',$o->fz)
->join('domains',['domains.id'=>'zones.domain_id'])
->where('zone_id',$o->fz)
->where('name',$o->fd ?: $domain->name)
->where('name',$o->fd)
->single();
} elseif ($o->fz && $so) {
Log::alert(sprintf('%s:! No domain in the packet, work it out from the system [%d] for zone [%d]',self::LOGKEY,$so->name,$o->fz));
if (($x=$so->zones->where('zone_id',$o->fz)->unique('domain_id'))->count() === 1) {
$o->zone = $x->pop();
} else {
Log::alert(sprintf('%s:! Node [%s] has two zones with [%d]',self::LOGKEY,$so->name,$o->fz));
}
}
// If zone is not set, then we need to use a default zone - the messages may not be from this zone.
if (empty($o->zone)) {
Log::alert(sprintf('%s:! We couldnt work out the packet zone, so we have fallen back to the default for [%d]',self::LOGKEY,$o->fz));
$o->zone = Zone::where('zone_id',$o->fz)
->where('default',TRUE)
->singleOrFail();
try {
$o->zone = Zone::where('zone_id',$o->fz)
->where('default',TRUE)
->sole();
} catch (ModelNotFoundException $e) {
throw new InvalidPacketException(sprintf('%s:! We couldnt work out the packet zone, and there isnt a default for [%d]',self::LOGKEY,$o->fz));
}
}
Log::info(sprintf('%s:- Packet Dated [%s] from [%s] to [%s]',self::LOGKEY,$o->date,$o->fftn_t,$o->tftn_t));
$message = ''; // Current message we are building
$msgbuf = '';
$leader = Message::header_len()+strlen(self::PACKED_MSG_LEAD);
@@ -171,18 +189,19 @@ abstract class Packet extends FTNBase implements \Iterator, \Countable
|| (($end=strpos($msgbuf,"\x00".self::PACKED_END,$leader)) !== FALSE))
{
// Parse our message
$o->parseMessage(substr($msgbuf,0,$end));
Log::debug(sprintf('%s:- Message at offset [%d] in [%s]',self::LOGKEY,$read_ptr-strlen($readbuf),$name));
$o->parseMessage(substr($msgbuf,0,$end),$process);
$msgbuf = substr($msgbuf,$end+3);
continue;
// If we have more to read
// If we have more to read
} elseif ($read_ptr < $size) {
continue;
}
// If we get here
throw new InvalidPacketException(sprintf('Cannot determine END of message/packet: %s|%s',get_class($o),hex_dump($message)));;
throw new InvalidPacketException(sprintf('Cannot determine END of message/packet: %s|%s',get_class($o),hex_dump($message)));
}
if ($msgbuf)
@@ -250,7 +269,7 @@ abstract class Packet extends FTNBase implements \Iterator, \Countable
case 'software':
Software::unguard();
$o = Software::singleOrNew(['code'=>$this->product,'type'=>Software::SOFTWARE_TOSSER]);
$o = Software::firstOrNew(['code'=>$this->product,'type'=>Software::SOFTWARE_TOSSER]);
Software::reguard();
return $o;
@@ -286,20 +305,7 @@ abstract class Packet extends FTNBase implements \Iterator, \Countable
*/
public function __toString(): string
{
if (empty($this->messages))
throw new InvalidPacketException('Refusing to make an empty packet');
if (empty($this->tftn_p) || empty($this->fftn_p))
throw new InvalidPacketException('Cannot generate a packet without a destination address');
$return = $this->header();
foreach ($this->messages as $o)
$return .= self::PACKED_MSG_LEAD.$o->packet($this->tftn_p);
$return .= "\00\00";
return $return;
return $this->content;
}
/* INTERFACE */
@@ -339,53 +345,6 @@ abstract class Packet extends FTNBase implements \Iterator, \Countable
/* METHODS */
/**
* When creating a new packet, set the header.
*
* @param Address $oo
* @param Address $o
* @param string|null $passwd Override the password used in the packet
* @deprecated Use Packet::generate(), which should generate a packet of the right type
*/
public function addressHeader(Address $oo,Address $o,string $passwd=NULL): void
{
Log::debug(sprintf('%s:+ Creating packet for [%s]',self::LOGKEY,$o->ftn));
$date = Carbon::now();
// Create Header
$this->header = [
'ozone' => $oo->zone->zone_id, // Orig Zone
'dzone' => $o->zone->zone_id, // Dest Zone
'onet' => $oo->host_id ?: $oo->region_id, // Orig Net
'dnet' => $o->host_id ?: $o->region_id, // Dest Net
'onode' => $oo->node_id, // Orig Node
'dnode' => $o->node_id, // Dest Node
'opoint' => $oo->point_id, // Orig Point
'dpoint' => $o->point_id, // Dest Point
'odomain' => $oo->zone->domain->name, // Orig Domain
'ddomain' => $o->zone->domain->name, // Dest Domain
'y' => $date->format('Y'), // Year
'm' => $date->format('m')-1, // Month
'd' => $date->format('d'), // Day
'H' => $date->format('H'), // Hour
'M' => $date->format('i'), // Minute
'S' => $date->format('s'), // Second
'password' => strtoupper((! is_null($passwd)) ? $passwd : $o->session('pktpass')), // Packet Password
];
}
/**
* Add a message to this packet
*
* @param Message $o
* @deprecated No longer used when Address::class is updated
*/
public function addMail(Message $o): void
{
$this->messages->push($o);
}
public function for(Address $ao): self
{
$this->tftn_p = $ao;
@@ -406,7 +365,20 @@ abstract class Packet extends FTNBase implements \Iterator, \Countable
public function mail(Collection $msgs): self
{
$this->messages = $msgs;
if (! $msgs->count())
throw new InvalidPacketException('Refusing to make an empty packet');
if (empty($this->tftn_p) || empty($this->fftn_p))
throw new InvalidPacketException('Cannot generate a packet without a destination address');
$this->content = $this->header($msgs);
foreach ($msgs as $o)
$this->content .= self::PACKED_MSG_LEAD.$o->packet($this->tftn_p);
$this->content .= "\00\00";
$this->messages = $msgs->map(fn($item)=>$item->only(['id','datetime']));
return $this;
}
@@ -415,25 +387,25 @@ abstract class Packet extends FTNBase implements \Iterator, \Countable
* Parse a message in a mail packet
*
* @param string $message
* @throws InvalidPacketException|\Exception
* @param bool $process
* @throws \Exception
*/
private function parseMessage(string $message): void
private function parseMessage(string $message,bool $process): void
{
Log::info(sprintf('%s:+ Processing packet message [%d] bytes',self::LOGKEY,strlen($message)));
$msg = Message::parseMessage($message,$this->zone);
// If the message is invalid, we'll ignore it
if ($msg->errors->count()) {
if ($process && $msg->errors->count()) {
Log::info(sprintf('%s:- Message [%s] has [%d] errors',self::LOGKEY,$msg->msgid ?: 'No ID',$msg->errors->count()));
// If the messages is not for the right zone, we'll ignore it
if ($msg->errors->has('invalid-zone')) {
Log::alert(sprintf('%s:! Message [%s] is from an invalid zone [%s], packet is from [%s] - ignoring it',self::LOGKEY,$msg->msgid,$msg->fftn->zone->zone_id,$this->fftn->zone->zone_id));
Log::alert(sprintf('%s:! Message [%s] is from|to an invalid zone [%s|%s], packet is from [%s] - ignoring it',self::LOGKEY,$msg->msgid,$msg->get_fftn,$msg->get_tftn,$this->fz));
if (! $msg->kludges->get('RESCANNED'))
Notification::route('netmail',$this->fftn)->notify(new EchomailBadAddress($msg));
Notification::route('netmail',$this->fftn)->notify(($msg instanceof Echomail) ? new EchomailBadAddress($msg) : new NetmailBadAddress($msg));
return;
}
@@ -442,7 +414,7 @@ abstract class Packet extends FTNBase implements \Iterator, \Countable
if ($msg->errors->has('from') && $this->fftn && $this->fftn->zone_id) {
Log::debug(sprintf('%s:^ From address [%s] doesnt exist, it needs to be created',self::LOGKEY,$msg->set->get('set_fftn')));
$ao = Address::findFTN($msg->set->get('set_fftn'),TRUE);
$ao = Address::findFTN($msg->set->get('set_fftn'),TRUE,TRUE);
if ($ao?->exists && ($ao->zone?->domain_id !== $this->fftn->zone->domain_id)) {
Log::alert(sprintf('%s:! From address [%s] domain [%d] doesnt match packet domain [%d]?',self::LOGKEY,$msg->set->get('set_fftn'),$ao->zone?->domain_id,$this->fftn->zone->domain_id));
@@ -453,17 +425,37 @@ abstract class Packet extends FTNBase implements \Iterator, \Countable
if (! $ao) {
$so = System::createUnknownSystem();
$ao = Address::createFTN($msg->set->get('set_fftn'),$so);
Log::alert(sprintf('%s:- From FTN [%s] is not defined, created new entry for (%d)',self::LOGKEY,$msg->set->get('set_fftn'),$ao->id));
}
$msg->fftn_id = $ao->id;
Log::alert(sprintf('%s:- From FTN [%s] is not defined, created new entry for (%d)',self::LOGKEY,$msg->set->get('set_fftn'),$ao->id));
$msg->errors->forget('from');
$msg->errors->forget('fftn_id');
}
// If the $msg->tftn doesnt exist, we'll need to create it
if ($msg->errors->has('to') && $this->tftn && $this->tftn->zone_id) {
Log::debug(sprintf('%s:^ To address [%s] doesnt exist, it needs to be created',self::LOGKEY,$msg->set->get('set_tftn')));
$ao = Address::findFTN($msg->set->get('set_tftn'),TRUE,TRUE);
$ao = Address::findFTN($msg->set->get('set_tftn'),TRUE);
// If this is a netmail message, to a non existant address, we need to bounce it
if (($msg instanceof Netmail)) {
if ((! $ao) && our_address()->contains(Address::newFTN($msg->set_tftn)?->parent())) {
Log::alert(sprintf('%s:^ To address [%s] doesnt exist, netmail will be bounced',self::LOGKEY,$msg->set->get('set_tftn')));
$this->messages->push($msg);
return;
// If this is a netmail message, to a non existant address, we need to bounce it
} elseif ($ao && (! $ao->active) && our_address()->contains($ao->parent())) {
Log::alert(sprintf('%s:^ To address [%s] isnt active, netmail will be bounced',self::LOGKEY,$msg->set->get('set_tftn')));
$this->messages->push($msg);
return;
}
}
Log::debug(sprintf('%s:^ To address [%s] doesnt exist, it needs to be created',self::LOGKEY,$msg->set->get('set_tftn')));
if ($ao?->exists && ($ao->zone?->domain_id !== $this->tftn->zone->domain_id)) {
Log::alert(sprintf('%s:! To address [%s] domain [%d] doesnt match packet domain [%d]?',self::LOGKEY,$msg->set->get('set_tftn'),$ao->zone?->domain_id,$this->fftn->zone->domain_id));
@@ -473,11 +465,14 @@ abstract class Packet extends FTNBase implements \Iterator, \Countable
if (! $ao) {
$so = System::createUnknownSystem();
$ao = Address::createFTN($msg->set->get('set_fftn'),$so);
$ao = Address::createFTN($msg->set->get('set_tftn'),$so);
Log::alert(sprintf('%s:- To FTN [%s] is not defined, created new entry for (%d)',self::LOGKEY,$msg->set->get('set_tftn'),$ao->id));
}
$msg->tftn_id = $ao->id;
Log::alert(sprintf('%s:- To FTN [%s] is not defined, created new entry for (%d)',self::LOGKEY,$msg->set->get('set_tftn'),$ao->id));
$msg->errors->forget('to');
$msg->errors->forget('tftn_id');
}
// If there is no fftn, then its from a system that we dont know about
@@ -490,7 +485,7 @@ abstract class Packet extends FTNBase implements \Iterator, \Countable
}
// @todo If the message from domain (eg: $msg->fftn->zone->domain) is different to the packet address domain ($pkt->fftn->zone->domain), we'll skip this message
Log::debug(sprintf('%s:^ Message [%s] - Packet from domain [%d], Message domain [%d]',self::LOGKEY,$msg->msgid,$this->fftn->zone->domain_id,$msg->fftn->zone->domain_id));
//Log::debug(sprintf('%s:^ Message [%s] - Packet from domain [%d], Message domain [%d]',self::LOGKEY,$msg->msgid,$this->fftn->zone->domain_id,$msg->fftn->zone->domain_id));
$this->messages->push($msg);
}
@@ -504,15 +499,8 @@ abstract class Packet extends FTNBase implements \Iterator, \Countable
public function password(string $password=NULL): self
{
if ($password && (strlen($password) < 9))
$this->pass_p = $password;
$this->pass_p = strtoupper($password);
return $this;
}
/** @deprecated Is this used? */
public function pluck(string $key): Collection
{
throw new \Exception(sprintf('%s:! This function is deprecated - [%s]',self::LOGKEY,$key));
return $this->messages->pluck($key);
}
}

View File

@@ -3,6 +3,7 @@
namespace App\Classes\FTN\Packet;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use App\Classes\FTN\Packet;
use App\Models\Setup;
@@ -62,44 +63,39 @@ final class FSC39 extends Packet
/**
* Create our message packet header
*/
protected function header(): string
protected function header(Collection $msgs): string
{
$oldest = $this->messages->sortBy('datetime')->last();
$oldest = $this->messages->sortBy('date')->last();
try {
return pack(collect(self::HEADER)->pluck(1)->join(''),
$this->fftn_p->node_id, // Orig Node
$this->tftn_p->node_id, // Dest Node
$oldest->datetime->format('Y'), // Year
$oldest->datetime->format('m')-1, // Month
$oldest->datetime->format('d'), // Day
$oldest->datetime->format('H'), // Hour
$oldest->datetime->format('i'), // Minute
$oldest->datetime->format('s'), // Second
0, // Baud
2, // Packet Version (should be 2)
$this->fftn_p->host_id, // Orig Net
$this->tftn_p->host_id, // Dest Net
(Setup::PRODUCT_ID & 0xff), // Product Code Lo
Setup::PRODUCT_VERSION_MAJ, // Product Version Major
$this->pass_p ?: $this->tftn_p->session('pktpass'), // Packet Password
$this->fftn_p->zone->zone_id, // Orig Zone
$this->tftn_p->zone->zone_id, // Dest Zone
'', // Reserved
static::VERS, // fsc-0039.004 (copy of 0x2c)
((Setup::PRODUCT_ID >> 8) & 0xff), // Product Code Hi
Setup::PRODUCT_VERSION_MIN, // Product Version Minor
static::VERS, // Capability Word
$this->fftn_p->zone->zone_id, // Orig Zone
$this->tftn_p->zone->zone_id, // Dest Zone
$this->fftn_p->point_id, // Orig Point
$this->tftn_p->point_id, // Dest Point
strtoupper(hexstr(Setup::PRODUCT_ID)), // ProdData
);
} catch (\Exception $e) {
return $e->getMessage();
}
return pack(collect(self::HEADER)->pluck(1)->join(''),
$this->fftn_p->node_id, // Orig Node
$this->tftn_p->node_id, // Dest Node
$oldest->datetime->format('Y'), // Year
$oldest->datetime->format('m')-1, // Month
$oldest->datetime->format('d'), // Day
$oldest->datetime->format('H'), // Hour
$oldest->datetime->format('i'), // Minute
$oldest->datetime->format('s'), // Second
0, // Baud
2, // Packet Version (should be 2)
$this->fftn_p->host_id, // Orig Net
$this->tftn_p->host_id, // Dest Net
(Setup::PRODUCT_ID & 0xff), // Product Code Lo
Setup::PRODUCT_VERSION_MAJ, // Product Version Major
$this->pass_p ?: $this->tftn_p->pass_packet, // Packet Password
$this->fftn_p->zone->zone_id, // Orig Zone
$this->tftn_p->zone->zone_id, // Dest Zone
'', // Reserved
static::VERS, // fsc-0039.004 (copy of 0x2c)
((Setup::PRODUCT_ID >> 8) & 0xff), // Product Code Hi
Setup::PRODUCT_VERSION_MIN, // Product Version Minor
static::VERS, // Capability Word
$this->fftn_p->zone->zone_id, // Orig Zone
$this->tftn_p->zone->zone_id, // Dest Zone
$this->fftn_p->point_id, // Orig Point
$this->tftn_p->point_id, // Dest Point
strtoupper(hexstr(Setup::PRODUCT_ID)), // ProdData
);
}
/**

View File

@@ -3,6 +3,7 @@
namespace App\Classes\FTN\Packet;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use App\Classes\FTN\Packet;
use App\Models\Setup;
@@ -54,32 +55,27 @@ final class FSC45 extends Packet
/**
* Create our message packet header
*/
protected function header(): string
protected function header(Collection $msgs): string
{
try {
return pack(collect(self::HEADER)->pluck(1)->join(''),
$this->fftn_p->node_id, // Orig Node
$this->tftn_p->node_id, // Dest Node
$this->fftn_p->point_id, // Orig Point
$this->tftn_p->point_id, // Dest Point
'', // Reserved
2, // Sub Version (should be 2)
2, // Packet Version (should be 2)
$this->fftn_p->host_id, // Orig Net
$this->tftn_p->host_id, // Dest Net
(Setup::PRODUCT_ID & 0xff), // Product Code
Setup::PRODUCT_VERSION_MAJ, // Product Version
$this->pass_p ?: $this->tftn_p->session('pktpass'), // Packet Password
$this->fftn_p->zone->zone_id, // Orig Zone
$this->tftn_p->zone->zone_id, // Dest Zone
$this->fftn_p->zone->domain->name, // Orig Domain
$this->tftn_p->zone->domain->name, // Dest Domain
strtoupper(hexstr(Setup::PRODUCT_ID)), // ProdData
);
} catch (\Exception $e) {
return $e->getMessage();
}
return pack(collect(self::HEADER)->pluck(1)->join(''),
$this->fftn_p->node_id, // Orig Node
$this->tftn_p->node_id, // Dest Node
$this->fftn_p->point_id, // Orig Point
$this->tftn_p->point_id, // Dest Point
'', // Reserved
2, // Sub Version (should be 2)
2, // Packet Version (should be 2)
$this->fftn_p->host_id, // Orig Net
$this->tftn_p->host_id, // Dest Net
(Setup::PRODUCT_ID & 0xff), // Product Code
Setup::PRODUCT_VERSION_MAJ, // Product Version
$this->pass_p ?: $this->tftn_p->pass_packet, // Packet Password
$this->fftn_p->zone->zone_id, // Orig Zone
$this->tftn_p->zone->zone_id, // Dest Zone
$this->fftn_p->zone->domain->name, // Orig Domain
$this->tftn_p->zone->domain->name, // Dest Domain
strtoupper(hexstr(Setup::PRODUCT_ID)), // ProdData
);
}
/**

View File

@@ -3,6 +3,7 @@
namespace App\Classes\FTN\Packet;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use App\Classes\FTN\Packet;
use App\Models\Setup;
@@ -54,6 +55,10 @@ final class FSC48 extends Packet
case 'capability':
return sprintf('%016b',Arr::get($this->header,'capword'));
case 'fn':
// If the packet is from a point, then onet will be 0xffff
return ($x=Arr::get($this->header,'onet')) === 0xffff ? Arr::get($this->header,'auxnet') : $x;
default:
return parent::__get($key);
}
@@ -62,44 +67,39 @@ final class FSC48 extends Packet
/**
* Create our message packet header
*/
protected function header(): string
protected function header(Collection $msgs): string
{
$oldest = $this->messages->sortBy('datetime')->last();
$oldest = $msgs->sortBy('date')->last();
try {
return pack(collect(self::HEADER)->pluck(1)->join(''),
$this->fftn_p->node_id, // Orig Node
$this->tftn_p->node_id, // Dest Node
$oldest->datetime->format('Y'), // Year
$oldest->datetime->format('m')-1, // Month
$oldest->datetime->format('d'), // Day
$oldest->datetime->format('H'), // Hour
$oldest->datetime->format('i'), // Minute
$oldest->datetime->format('s'), // Second
0, // Baud
2, // Packet Version (should be 2)
$this->fftn_p->point_id ? 0xffff : $this->fftn_p->host_id, // Orig Net (0xFFFF when OrigPoint != 0)
$this->tftn_p->host_id, // Dest Net
(Setup::PRODUCT_ID & 0xff), // Product Code Lo
Setup::PRODUCT_VERSION_MAJ, // Product Version Major
$this->pass_p ?: $this->tftn_p->session('pktpass'), // Packet Password
$this->fftn_p->zone->zone_id, // Orig Zone
$this->tftn_p->zone->zone_id, // Dest Zone
$this->fftn_p->point_id ? $this->fftn_p->host_id : 0x00, // Aux Net
static::VERS, // fsc-0039.004 (copy of 0x2c)
((Setup::PRODUCT_ID >> 8) & 0xff), // Product Code Hi
Setup::PRODUCT_VERSION_MIN, // Product Version Minor
static::VERS, // Capability Word
$this->fftn_p->zone->zone_id, // Orig Zone
$this->tftn_p->zone->zone_id, // Dest Zone
$this->fftn_p->point_id, // Orig Point
$this->tftn_p->point_id, // Dest Point
strtoupper(hexstr(Setup::PRODUCT_ID)), // ProdData
);
} catch (\Exception $e) {
return $e->getMessage();
}
return pack(collect(self::HEADER)->pluck(1)->join(''),
$this->fftn_p->node_id, // Orig Node
$this->tftn_p->node_id, // Dest Node
$oldest->datetime->format('Y'), // Year
$oldest->datetime->format('m')-1, // Month
$oldest->datetime->format('d'), // Day
$oldest->datetime->format('H'), // Hour
$oldest->datetime->format('i'), // Minute
$oldest->datetime->format('s'), // Second
0, // Baud
2, // Packet Version (should be 2)
$this->fftn_p->point_id ? 0xffff : $this->fftn_p->host_id, // Orig Net (0xFFFF when OrigPoint != 0)
$this->tftn_p->host_id, // Dest Net
(Setup::PRODUCT_ID & 0xff), // Product Code Lo
Setup::PRODUCT_VERSION_MAJ, // Product Version Major
$this->pass_p ?: $this->tftn_p->pass_packet, // Packet Password
$this->fftn_p->zone->zone_id, // Orig Zone
$this->tftn_p->zone->zone_id, // Dest Zone
$this->fftn_p->point_id ? $this->fftn_p->host_id : 0x00, // Aux Net
static::VERS, // fsc-0039.004 (copy of 0x2c)
((Setup::PRODUCT_ID >> 8) & 0xff), // Product Code Hi
Setup::PRODUCT_VERSION_MIN, // Product Version Minor
static::VERS, // Capability Word
$this->fftn_p->zone->zone_id, // Orig Zone
$this->tftn_p->zone->zone_id, // Dest Zone
$this->fftn_p->point_id, // Orig Point
$this->tftn_p->point_id, // Dest Point
strtoupper(hexstr(Setup::PRODUCT_ID)), // ProdData
);
}
/**

View File

@@ -3,6 +3,7 @@
namespace App\Classes\FTN\Packet;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use App\Classes\FTN\Packet;
use App\Models\Setup;
@@ -55,35 +56,30 @@ final class FTS1 extends Packet
/**
* Create our message packet header
*/
protected function header(): string
protected function header(Collection $msgs): string
{
$oldest = $this->messages->sortBy('datetime')->last();
try {
return pack(collect(self::HEADER)->pluck(1)->join(''),
$this->fftn_p->node_id, // Orig Node
$this->tftn_p->node_id, // Dest Node
$oldest->datetime->format('Y'), // Year
$oldest->datetime->format('m')-1, // Month
$oldest->datetime->format('d'), // Day
$oldest->datetime->format('H'), // Hour
$oldest->datetime->format('i'), // Minute
$oldest->datetime->format('s'), // Second
0, // Baud
2, // Packet Version (should be 2)
$this->fftn_p->host_id, // Orig Net
$this->tftn_p->host_id, // Dest Net
(Setup::PRODUCT_ID & 0xff), // Product Code Lo
Setup::PRODUCT_VERSION_MAJ, // Product Version Major
$this->pass_p ?: $this->tftn_p->session('pktpass'), // Packet Password
$this->fftn_p->zone->zone_id, // Orig Zone
$this->tftn_p->zone->zone_id, // Dest Zone
'', // Reserved
);
} catch (\Exception $e) {
return $e->getMessage();
}
return pack(collect(self::HEADER)->pluck(1)->join(''),
$this->fftn_p->node_id, // Orig Node
$this->tftn_p->node_id, // Dest Node
$oldest->datetime->format('Y'), // Year
$oldest->datetime->format('m')-1, // Month
$oldest->datetime->format('d'), // Day
$oldest->datetime->format('H'), // Hour
$oldest->datetime->format('i'), // Minute
$oldest->datetime->format('s'), // Second
0, // Baud
2, // Packet Version (should be 2)
$this->fftn_p->host_id, // Orig Net
$this->tftn_p->host_id, // Dest Net
(Setup::PRODUCT_ID & 0xff), // Product Code Lo
Setup::PRODUCT_VERSION_MAJ, // Product Version Major
$this->pass_p ?: $this->tftn_p->pass_packet, // Packet Password
$this->fftn_p->zone->zone_id, // Orig Zone
$this->tftn_p->zone->zone_id, // Dest Zone
'', // Reserved
);
}
/**

View File

@@ -11,7 +11,7 @@ abstract class Process
{
public static function canProcess(Echoarea $eao): bool
{
return $eao->automsgs ? TRUE : FALSE;
return (bool)$eao->automsgs;
}
/**

View File

@@ -1,37 +0,0 @@
<?php
namespace App\Classes\FTN\Process\Netmail;
use Illuminate\Support\Facades\Notification;
use Illuminate\Support\Facades\Log;
use App\Classes\FTN\Process;
use App\Models\{Echomail,Netmail};
use App\Notifications\Netmails\Areafix as AreafixNotification;
use App\Notifications\Netmails\Areafix\NotConfiguredHere as AreafixNotConfiguredHereNotification;
/**
* Process messages to Ping
*
* @package App\Classes\FTN\Process
*/
final class Areafix extends Process
{
private const LOGKEY = 'RP-';
public static function handle(Echomail|Netmail $mo): bool
{
if (strtolower($mo->to) !== 'areafix')
return FALSE;
Log::info(sprintf('%s:- Processing AREAFIX message from (%s) [%s]',self::LOGKEY,$mo->from,$mo->fftn));
// If this is not a node we manage, then respond with a sorry can help you
if ($mo->fftn->system->sessions->count())
Notification::route('netmail',$mo->fftn)->notify(new AreafixNotification($mo));
else
Notification::route('netmail',$mo->fftn)->notify(new AreafixNotConfiguredHereNotification($mo));
return TRUE;
}
}

View File

@@ -0,0 +1,50 @@
<?php
namespace App\Classes\FTN\Process\Netmail;
use Illuminate\Support\Facades\Notification;
use Illuminate\Support\Facades\Log;
use App\Classes\FTN\Process;
use App\Models\{Echomail,Netmail};
use App\Notifications\Netmails\Areafix\{InvalidPassword,NotConfiguredHere};
/**
* Process messages to Ping
*
* @package App\Classes\FTN\Process
*/
abstract class Robot extends Process
{
private const LOGKEY = 'RPR';
public static function handle(Echomail|Netmail $mo): bool
{
if (((strtolower($mo->to) !== 'areafix') && (strtolower($mo->to) !== 'filefix')) || (! ($mo instanceof Netmail)))
return FALSE;
Log::info(sprintf('%s:- Processing *FIX [%s] message from (%s) [%s]',self::LOGKEY,$mo->to,$mo->from,$mo->fftn->ftn));
// If this is not a node we manage, then respond with a sorry can help you
if (! $mo->fftn->system->sessions->count()) {
Notification::route('netmail',$mo->fftn)->notify(new NotConfiguredHere($mo));
return TRUE;
}
// If this nodes password is not correct
if ($mo->fftn->pass_fix !== strtoupper($mo->subject)) {
Notification::route('netmail',$mo->fftn)->notify(new InvalidPassword($mo));
return TRUE;
}
if ((strtolower($mo->to) === 'areafix'))
return static::areafix($mo);
if ((strtolower($mo->to) === 'filefix'))
return static::filefix($mo);
return FALSE;
}
}

View File

@@ -0,0 +1,104 @@
<?php
namespace App\Classes\FTN\Process\Netmail\Robot;
use Illuminate\Support\Facades\Notification;
use Illuminate\Support\Facades\Log;
use App\Classes\FTN\Process\Netmail\Robot;
use App\Models\{Echomail,Netmail};
use App\Notifications\Netmails\Areafix\CommandsProcessed;
/**
* Process messages to Ping
*
* @package App\Classes\FTN\Process
*/
final class Areafix extends Robot
{
private const LOGKEY = 'RPA';
public const commands = 'App\\Classes\\FTN\\Process\\Netmail\\Robot\\Areafix\\';
public static function handle(Echomail|Netmail $mo): bool
{
if ((strtolower($mo->to) !== 'areafix') || (! ($mo instanceof Netmail)))
return FALSE;
Log::info(sprintf('%s:- Processing AREAFIX [%s] message from (%s) [%s]',self::LOGKEY,$mo->to,$mo->from,$mo->fftn->ftn));
return parent::handle($mo);
}
public static function areafix(Netmail $mo): bool
{
$result = collect();
$result->push('--> BEGIN <--');
foreach ($mo->body_lines as $command) {
Log::debug(sprintf('%s:* Processing command [%s]',self::LOGKEY,$command));
// Skip empty lines
if (! $command || preg_match('/^\s+$/',$command))
continue;
$command = explode(' ',strtoupper(rtrim($command)));
Log::debug(sprintf('%s:* Processing command',self::LOGKEY),['command'=>$command]);
// If command starts with '...' or '---', its a tear/tag line, and we have reached the end
if (str_starts_with($command[0],'...') || str_starts_with($command[0],'---')) {
Log::info(sprintf('%s:= We got a tearline/tagline, end of processing',self::LOGKEY));
$result->push('--> END OF PROCESSING - TEARLINE/TAGLINE <--');
break;
// Lines starting with a space, we'll abort
} elseif (! $command[0]) {
Log::info(sprintf('%s:= Got a new line with a space, end of processing',self::LOGKEY));
$result->push('--> END OF PROCESSING - SPACE DETECTED <--');
break;
// If command doesnt start with %, its an area
} elseif (! str_starts_with($command[0],'%')) {
Log::info(sprintf('%s:= Assuming command [%s] is an AREA command',self::LOGKEY,$command[0]));
array_unshift($command,'%AREA');
}
// Some commands are reserved words
switch ($x=strtolower(substr($command[0],1))) {
case 'list':
$class = self::commands.'AreaList';
break;
default:
// Parse the message body and pluck out the commands on each line
$class = self::commands.ucfirst($x);
}
if (! class_exists($class)) {
$result->push(sprintf('%-25s <-- **COMMAND UNKNOWN**',join(' ',$command)));
Log::info(sprintf('%s:! Command UNKNOWN [%s] ',self::LOGKEY,join('|',$command)),['class'=>$class]);
continue;
}
// Drop the command from the array, the rest are arguments
array_shift($command);
// Refresh our echoareas
$mo->fftn->load('echoareas');
$o = new $class($mo,$command);
$result->push($o->process());
}
// Reply with a confirmation of what commands were processed
Notification::route('netmail',$mo->fftn)->notify(new CommandsProcessed($mo,$result));
return TRUE;
}
}

View File

@@ -0,0 +1,147 @@
<?php
namespace App\Classes\FTN\Process\Netmail\Robot\Areafix;
use Carbon\Carbon;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use App\Jobs\AreafixRescan;
// Echoarea Processing Command
class Area extends Base
{
private const LOGKEY = 'AFA';
private const command = '%AREA';
public static function help(): array
{
return [
self::command.' [-|+]<ECHOAREA> [R|D=<DAYS>]',
' Use the area command to subscribe (+) or unsubscribe (-) to an ECHOAREA',
' Arguments:',
' - ECHOAREA (required) name of area to subscribe or unsubscribe',
' - D=DAYS (optional) number of days to resend mail from this area that you',
' havent already received (useful if you are resubscribing to an area and',
' have received mail in the past)',
' - R=DAYS (optional) number of days to resend mail from this area (even if',
' it was sent to you previously)',
' Notes:',
' * "+" is optional, and is implied if "-" is not used',
' * "R" and "D" options only apply to subscribing',
];
}
public function process(): string
{
$command = self::command.' '.join(' ',$this->arguments);
if (! ($area=Arr::get($this->arguments,0,NULL)))
return sprintf('%-25s <-- INVALID, AN AREA IS REQUIRED',$command);
// If command starts with '-', its to unsubscribe
if (str_starts_with($area,'-')) {
$sub = FALSE;
$area = substr($area,1);
} elseif (str_starts_with($area,'+')) {
$sub = TRUE;
$area = substr($area,1);
} else {
$sub = TRUE;
$area = $area;
}
Log::debug(sprintf('%s:- Processing [%s] for [%s]',self::LOGKEY,$sub ? 'ADD' : 'REMOVE',$area));
// Drop the area from the arguments, the rest are options
array_shift($this->arguments);
// Area exists
if ($ea=$this->mo->fftn->domain->echoareas->where('name',$area)->pop()) {
// If already subscribed
if ($nea=$this->mo->fftn->echoareas->where('name',$area)->pop()) {
// requesting to subscribe "You already are since..., arguments ignored
if ($sub) {
Log::debug(sprintf('%s:- FTN [%s] ALREADY subscribed to [%s] since [%s]',self::LOGKEY,$this->mo->fftn->ftn,$area,$nea->pivot->subscribed));
return sprintf('%-25s <-- ALREADY subscribed since %s',$command,$nea->pivot->subscribed);
// requesting to unsubscribe
} else {
$this->mo->fftn->echoareas()->detach($ea->id);
// Remove sub, clear queue
$x = DB::table('echomail_seenby')
->where('address_id',$this->mo->fftn->id)
->join('echomails',['echomails.id'=>'echomail_seenby.echomail_id'])
->where('echoarea_id',$nea->id)
->whereNotNull('export_at')
->whereNull('sent_at')
->orderBy('echomails.datetime')
->skip($this->mo->fftn->system->pkt_msgs) // Might already being sent in this session
->delete();
Log::debug(sprintf('%s:- FTN [%s] UNSUBSCRIBED from [%s] clearing [%s]',self::LOGKEY,$this->mo->fftn->ftn,$area,$x));
return sprintf('%-25s <-- UNSUBSCRIBED, CLEARED [%d] MSGS from queue',$command,$x);
}
// If not subscribed
} else {
// requesting to subscribe, subsubsribe and rescan if arguments
if ($sub) {
$this->mo->fftn->echoareas()->attach([$ea->id=>['subscribed'=>Carbon::now()]]);
// If we have arguments, they are to rescan
if (count($this->arguments) === 1) {
$m = [];
if (preg_match('/^([DR])=([0-9]+)$/',$this->arguments[0],$m)) {
switch ($m[1]) {
// Scan
case 'D':
AreafixRescan::dispatch($this->mo->fftn,$ea,$m[2])
->onQueue('mail');
return sprintf('%-25s <-- SUBSCRIBED, RESCAN [%d] DAYS queued',$command,$m[2]);
// Scan
case 'R':
AreafixRescan::dispatch($this->mo->fftn,$ea,$m[2],TRUE)
->onQueue('mail');
return sprintf('%-25s <-- SUBSCRIBED, FORCE RESCAN [%d] DAYS queued',$command,$m[2]);
}
}
return sprintf('%-25s <-- SUBSCRIBED, INVALID OPTIONS',$command);
} elseif (count($this->arguments) > 1) {
Log::debug(sprintf('%s:- FTN [%s] subscribed to [%s], extra commands [%s] ignored',self::LOGKEY,$this->mo->fftn->ftn,$area,join('|',$this->arguments)));
return sprintf('%-25s <-- SUBSCRIBED, OPTIONS IGNORED',$command);
} else {
Log::debug(sprintf('%s:- FTN [%s] subscribed to [%s]',self::LOGKEY,$this->mo->fftn->ftn,$area));
return sprintf('%-25s <-- SUBSCRIBED',$command);
}
// If not subscribed, "you arent subscribed, arguments ignored"
} else {
Log::debug(sprintf('%s:- FTN [%s] is NOT subscribed to [%s], NO ACTION taken',self::LOGKEY,$this->mo->fftn->ftn,$area));
return sprintf('%-25s <-- NOT subscribed, NO ACTION taken',$command);
}
}
} else {
Log::debug(sprintf('%s:- FTN [%s] area UNKNOWN [%s], NO ACTION taken',self::LOGKEY,$this->mo->fftn->ftn,$area));
return sprintf('%-25s <-- AREA UNKNOWN, NO ACTION TAKEN',$command);
}
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace App\Classes\FTN\Process\Netmail\Robot\Areafix;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
use App\Notifications\Netmails\Areafix\AreaList as AreaListNotification;
// LIST - List echoareas in a domain
class AreaList extends Base
{
private const LOGKEY = 'AFS';
private const command = '%LIST';
public static function help(): array
{
return [
self::command,
' List the available echoareas in this network',
];
}
public function process(): string
{
Log::debug(sprintf('%s:- Areafix [%s] for [%s] for [%s]',self::LOGKEY,self::command,$this->mo->fftn->ftn,join('|',$this->arguments)));
if (count($this->arguments) > 1)
return sprintf('%-25s <-- INVALID COMMAND',self::command);
else {
Notification::route('netmail',$this->mo->fftn)
->notify(new AreaListNotification($this->mo));
return sprintf('%-25s <-- COMMAND PROCESSED',self::command);
}
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace App\Classes\FTN\Process\Netmail\Robot\Areafix;
use Illuminate\Support\Facades\Log;
use App\Models\Netmail;
// Our base areafix commands class
abstract class Base
{
private const LOGKEY = 'AB-';
protected Netmail $mo;
protected array $arguments;
public function __construct(Netmail $mo,array $arguments) {
Log::debug(sprintf('%s:- Areafix [%s] command with arguments [%s] for [%s]',self::LOGKEY,get_class($this),implode('|',$arguments),$mo->fftn->ftn));
$this->mo = $mo;
$this->arguments = $arguments;
}
abstract public static function help(): array;
abstract public function process(): string;
}

View File

@@ -0,0 +1,48 @@
<?php
namespace App\Classes\FTN\Process\Netmail\Robot\Areafix;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
use App\Classes\FTN\Process\Netmail\Robot\Areafix;
use App\Notifications\Netmails\Areafix\Help as HelpNotification;
// A Help Index Command
class Help extends Base
{
private const LOGKEY = 'AFH';
private const areafix_classes = 'app/Classes/FTN/Process/Netmail/Robot/Areafix';
private const command = '%HELP';
public static function help(): array
{
return [
self::command,
' This message!',
];
}
public function process(): string
{
Log::debug(sprintf('%s:- Processing [%s] for [%s]',self::LOGKEY,self::command,$this->mo->fftn->ftn));
$result = collect();
foreach (preg_grep('/^([^.])/',scandir(self::areafix_classes)) as $file) {
if (($file === 'Base.php') || (! str_ends_with(strtolower($file),'.php')))
continue;
$class = Areafix::commands.preg_replace('/\.php$/','',$file);
if ($result->count())
$result->push('');
$result = $result
->merge($class::help());
}
Notification::route('netmail',$this->mo->fftn)->notify(new HelpNotification($this->mo,$result));
return sprintf('%-25s <-- COMMAND PROCESSED',self::command);
}
}

View File

@@ -0,0 +1,65 @@
<?php
namespace App\Classes\FTN\Process\Netmail\Robot\Areafix;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Log;
use App\Jobs\AreafixRescan;
// RESCAN - Resend echomail
class Rescan extends Base
{
private const LOGKEY = 'AFR';
private const command = '%RESCAN';
public static function help(): array
{
return [
self::command.' <ECHOAREA> [<DAYS>]',
' Use the rescan command to resend mail from an echoarea.',
' This is will resend mail again, even if you have received it in the past.',
' Arguments:',
' - ECHOAREA (required) name of area to subscribe or unsubscribe',
' - DAYS (optional) number of days to resend mail from this area that you',
' If DAYS is omitted, the default is 30.',
];
}
public function process(): string
{
Log::debug(sprintf('%s:- Areafix [%s] for [%s] for [%s]',self::LOGKEY,self::command,$this->mo->fftn->ftn,join('|',$this->arguments)));
$command = self::command.' '.join(' ',$this->arguments);
if (! ($area=Arr::get($this->arguments,0)))
return sprintf('%-25s <-- INVALID, AN AREA IS REQUIRED',$command);
if (! is_numeric($days=Arr::get($this->arguments,1,30)))
return sprintf('%-25s <-- INVALID, DAYS [%s] NOT NUMERIC',$command,$this->arguments[1]);
// Area exists
if ($ea=$this->mo->fftn->domain->echoareas->where('name',$area)->pop()) {
// If already subscribed
if ($this->mo->fftn->echoareas->pluck('name')->contains($area)) {
AreafixRescan::dispatch($this->mo->fftn,$ea,$days,TRUE)
->onQueue('mail');
Log::debug(sprintf('%s:- FTN [%s] RESCAN [%s] DAYS [%d]',self::LOGKEY,$this->mo->fftn->ftn,$area,$days));
return sprintf('%-25s <-- RESCAN [%d] DAYS queued',$command,$days);
// If not subscribed
} else {
Log::debug(sprintf('%s:- FTN [%s] is NOT subscribed to [%s], NO ACTION taken',self::LOGKEY,$this->mo->fftn->ftn,$area));
return sprintf('%-25s <-- NOT subscribed, NO ACTION taken',$command);
}
} else {
Log::debug(sprintf('%s:- FTN [%s] area UNKNOWN [%s], NO ACTION taken',self::LOGKEY,$this->mo->fftn->ftn,$area));
return sprintf('%-25s <-- AREA UNKNOWN, NO ACTION TAKEN',$command);
}
}
}

View File

@@ -0,0 +1,66 @@
<?php
namespace App\Classes\FTN\Process\Netmail\Robot\Areafix;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Log;
use App\Jobs\AreafixRescan;
// SCAN - Send unsent echomail
class Scan extends Base
{
private const LOGKEY = 'AFS';
private const command = '%SCAN';
public static function help(): array
{
return [
self::command.' <ECHOAREA> [<DAYS>]',
' Use the scan command to resend mail that you havent received yet from an',
' echoarea. This is useful if you are rejoining an echoarea, and only want',
' to get mail that you dont already have.',
' Arguments:',
' - ECHOAREA (required) name of area to subscribe or unsubscribe',
' - DAYS (optional) number of days to resend mail from this area that you',
' If DAYS is omitted, the default is 30.',
];
}
public function process(): string
{
Log::debug(sprintf('%s:- Areafix [%s] for [%s] for [%s]',self::LOGKEY,self::command,$this->mo->fftn->ftn,join('|',$this->arguments)));
$command = self::command.' '.join(' ',$this->arguments);
if (! ($area=Arr::get($this->arguments,0)))
return sprintf('%-25s <-- INVALID, AN AREA IS REQUIRED',$command);
if (! is_numeric($days=Arr::get($this->arguments,1,30)))
return sprintf('%-25s <-- INVALID, DAYS [%s] NOT NUMERIC',$command,$this->arguments[1]);
// Area exists
if ($ea=$this->mo->fftn->domain->echoareas->where('name',$area)->pop()) {
// If already subscribed
if ($this->mo->fftn->echoareas->pluck('name')->contains($area)) {
AreafixRescan::dispatch($this->mo->fftn,$ea,$days)
->onQueue('mail');
Log::debug(sprintf('%s:- FTN [%s] SCAN [%s] DAYS [%d]',self::LOGKEY,$this->mo->fftn->ftn,$area,$days));
return sprintf('%-25s <-- SCAN [%d] DAYS queued',$command,$days);
// If not subscribed
} else {
Log::debug(sprintf('%s:- FTN [%s] is NOT subscribed to [%s], NO ACTION taken',self::LOGKEY,$this->mo->fftn->ftn,$area));
return sprintf('%-25s <-- NOT subscribed, NO ACTION taken',$command);
}
} else {
Log::debug(sprintf('%s:- FTN [%s] area UNKNOWN [%s], NO ACTION taken',self::LOGKEY,$this->mo->fftn->ftn,$area));
return sprintf('%-25s <-- AREA UNKNOWN, NO ACTION TAKEN',$command);
}
}
}

View File

@@ -0,0 +1,104 @@
<?php
namespace App\Classes\FTN\Process\Netmail\Robot;
use Illuminate\Support\Facades\Notification;
use Illuminate\Support\Facades\Log;
use App\Classes\FTN\Process\Netmail\Robot;
use App\Models\{Echomail,Netmail};
use App\Notifications\Netmails\Filefix\CommandsProcessed;
/**
* Process messages to Ping
*
* @package App\Classes\FTN\Process
*/
final class Filefix extends Robot
{
private const LOGKEY = 'RPF';
public const commands = 'App\\Classes\\FTN\\Process\\Netmail\\Robot\\Filefix\\';
public static function handle(Echomail|Netmail $mo): bool
{
if ((strtolower($mo->to) !== 'filefix') || (! ($mo instanceof Netmail)))
return FALSE;
Log::info(sprintf('%s:- Processing FILEFIX [%s] message from (%s) [%s]',self::LOGKEY,$mo->to,$mo->from,$mo->fftn->ftn));
return parent::handle($mo);
}
public static function filefix(Netmail $mo): bool
{
$result = collect();
$result->push('--> BEGIN <--');
foreach ($mo->body_lines as $command) {
Log::debug(sprintf('%s:* Processing command [%s]',self::LOGKEY,$command));
// Skip empty lines
if (! $command || preg_match('/^\s+$/',$command))
continue;
$command = explode(' ',strtoupper(rtrim($command)));
Log::debug(sprintf('%s:* Processing command',self::LOGKEY),['command'=>$command]);
// If command starts with '...' or '---', its a tear/tag line, and we have reached the end
if (str_starts_with($command[0],'...') || str_starts_with($command[0],'---')) {
Log::info(sprintf('%s:= We got a tearline/tagline, end of processing',self::LOGKEY));
$result->push('--> END OF PROCESSING - TEARLINE/TAGLINE <--');
break;
// Lines starting with a space, we'll abort
} elseif (! $command[0]) {
Log::info(sprintf('%s:= Got a new line with a space, end of processing',self::LOGKEY));
$result->push('--> END OF PROCESSING - SPACE DETECTED <--');
break;
// If command doesnt start with %, its an area
} elseif (! str_starts_with($command[0],'%')) {
Log::info(sprintf('%s:= Assuming command [%s] is an AREA command',self::LOGKEY,$command[0]));
array_unshift($command,'%AREA');
}
// Some commands are reserved words
switch ($x=strtolower(substr($command[0],1))) {
case 'list':
$class = self::commands.'AreaList';
break;
default:
// Parse the message body and pluck out the commands on each line
$class = self::commands.ucfirst($x);
}
if (! class_exists($class)) {
$result->push(sprintf('%-25s <-- **COMMAND UNKNOWN**',join(' ',$command)));
Log::info(sprintf('%s:! Command UNKNOWN [%s] ',self::LOGKEY,join('|',$command)),['class'=>$class]);
continue;
}
// Drop the command from the array, the rest are arguments
array_shift($command);
// Refresh our echoareas
$mo->fftn->load('fileareas');
$o = new $class($mo,$command);
$result->push($o->process());
}
// Reply with a confirmation of what commands were processed
Notification::route('netmail',$mo->fftn)->notify(new CommandsProcessed($mo,$result));
return TRUE;
}
}

View File

@@ -0,0 +1,148 @@
<?php
namespace App\Classes\FTN\Process\Netmail\Robot\Filefix;
use Carbon\Carbon;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use App\Classes\FTN\Process\Netmail\Robot\Areafix\Base;
use App\Jobs\FilefixRescan;
// Filearea Processing Command
class Area extends Base
{
private const LOGKEY = 'FFA';
private const command = '%AREA';
public static function help(): array
{
return [
self::command.' [-|+]<FILEAREA> [R|D=<DAYS>]',
' Use the area command to subscribe (+) or unsubscribe (-) to a FILEAREA',
' Arguments:',
' - FILEAREA (required) name of area to subscribe or unsubscribe',
' - D=DAYS (optional) number of days to resend files from this area that you',
' havent already received (useful if you are resubscribing to an area and',
' have received files in the past)',
' - R=DAYS (optional) number of days to resend files from this area (even if',
' it was sent to you previously)',
' Notes:',
' * "+" is optional, and is implied if "-" is not used',
' * "R" and "D" options only apply to subscribing',
];
}
public function process(): string
{
$command = self::command.' '.join(' ',$this->arguments);
if (! ($area=Arr::get($this->arguments,0,NULL)))
return sprintf('%-25s <-- INVALID, AN AREA IS REQUIRED',$command);
// If command starts with '-', its to unsubscribe
if (str_starts_with($area,'-')) {
$sub = FALSE;
$area = substr($area,1);
} elseif (str_starts_with($area,'+')) {
$sub = TRUE;
$area = substr($area,1);
} else {
$sub = TRUE;
$area = $area;
}
Log::debug(sprintf('%s:- Processing [%s] for [%s]',self::LOGKEY,$sub ? 'ADD' : 'REMOVE',$area));
// Drop the area from the arguments, the rest are options
array_shift($this->arguments);
// Area exists
if ($fa=$this->mo->fftn->domain->fileareas->where('name',$area)->pop()) {
// If already subscribed
if ($nea=$this->mo->fftn->fileareas->where('name',$area)->pop()) {
// requesting to subscribe "You already are since..., arguments ignored
if ($sub) {
Log::debug(sprintf('%s:- FTN [%s] ALREADY subscribed to [%s] since [%s]',self::LOGKEY,$this->mo->fftn->ftn,$area,$nea->pivot->subscribed));
return sprintf('%-25s <-- ALREADY subscribed since %s',$command,$nea->pivot->subscribed);
// requesting to unsubscribe
} else {
$this->mo->fftn->fileareas()->detach($fa->id);
// Remove sub, clear queue
$x = DB::table('file_seenby')
->where('address_id',$this->mo->fftn->id)
->join('files',['files.id'=>'file_seenby.file_id'])
->where('filearea_id',$nea->id)
->whereNotNull('export_at')
->whereNull('sent_at')
->orderBy('files.datetime')
->skip(1) // Might already being sent in this session
->delete();
Log::debug(sprintf('%s:- FTN [%s] UNSUBSCRIBED from [%s] clearing [%s]',self::LOGKEY,$this->mo->fftn->ftn,$area,$x));
return sprintf('%-25s <-- UNSUBSCRIBED, CLEARED [%d] FILES from queue',$command,$x);
}
// If not subscribed
} else {
// requesting to subscribe, subsubsribe and rescan if arguments
if ($sub) {
$this->mo->fftn->fileareas()->attach([$fa->id=>['subscribed'=>Carbon::now()]]);
// If we have arguments, they are to rescan
if (count($this->arguments) === 1) {
$m = [];
if (preg_match('/^([DR])=([0-9]+)$/',$this->arguments[0],$m)) {
switch ($m[1]) {
// Scan
case 'D':
FilefixRescan::dispatch($this->mo->fftn,$fa,$m[2])
->onQueue('mail');
return sprintf('%-25s <-- SUBSCRIBED, RESCAN [%d] DAYS queued',$command,$m[2]);
// Scan
case 'R':
FilefixRescan::dispatch($this->mo->fftn,$fa,$m[2],TRUE)
->onQueue('mail');
return sprintf('%-25s <-- SUBSCRIBED, FORCE RESCAN [%d] DAYS queued',$command,$m[2]);
}
}
return sprintf('%-25s <-- SUBSCRIBED, INVALID OPTIONS',$command);
} elseif (count($this->arguments) > 1) {
Log::debug(sprintf('%s:- FTN [%s] subscribed to [%s], extra commands [%s] ignored',self::LOGKEY,$this->mo->fftn->ftn,$area,join('|',$this->arguments)));
return sprintf('%-25s <-- SUBSCRIBED, OPTIONS IGNORED',$command);
} else {
Log::debug(sprintf('%s:- FTN [%s] subscribed to [%s]',self::LOGKEY,$this->mo->fftn->ftn,$area));
return sprintf('%-25s <-- SUBSCRIBED',$command);
}
// If not subscribed, "you arent subscribed, arguments ignored"
} else {
Log::debug(sprintf('%s:- FTN [%s] is NOT subscribed to [%s], NO ACTION taken',self::LOGKEY,$this->mo->fftn->ftn,$area));
return sprintf('%-25s <-- NOT subscribed, NO ACTION taken',$command);
}
}
} else {
Log::debug(sprintf('%s:- FILE [%s] area UNKNOWN [%s], NO ACTION taken',self::LOGKEY,$this->mo->fftn->ftn,$area));
return sprintf('%-25s <-- AREA UNKNOWN, NO ACTION TAKEN',$command);
}
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace App\Classes\FTN\Process\Netmail\Robot\Filefix;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
use App\Classes\FTN\Process\Netmail\Robot\Areafix\Base;
use App\Notifications\Netmails\Filefix\AreaList as AreaListNotification;
// LIST - List fileareas in a domain
class AreaList extends Base
{
private const LOGKEY = 'AFS';
private const command = '%LIST';
public static function help(): array
{
return [
self::command,
' List the available fileareas in this network',
];
}
public function process(): string
{
Log::debug(sprintf('%s:- Filefix [%s] for [%s] for [%s]',self::LOGKEY,self::command,$this->mo->fftn->ftn,join('|',$this->arguments)));
if (count($this->arguments) > 1)
return sprintf('%-25s <-- INVALID COMMAND',self::command);
else {
Notification::route('netmail',$this->mo->fftn)
->notify(new AreaListNotification($this->mo));
return sprintf('%-25s <-- COMMAND PROCESSED',self::command);
}
}
}

View File

@@ -0,0 +1,71 @@
<?php
namespace App\Classes\FTN\Process\Netmail\Robot\Filefix;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
use App\Classes\FTN\Process\Netmail\Robot\Areafix\Base;
use App\Notifications\Netmails\Filefix\Filelist as FilelistNotification;
// FILELIST - List files in an area
class Filelist extends Base
{
private const LOGKEY = 'AFR';
private const command = '%FILELIST';
public static function help(): array
{
return [
self::command.' [-|+]<FILEAREA> [<DAYS>]',
' Use the filelist command to list files from an filearea.',
' This is will resend files again, even if you have received them in the',
' past.',
' Arguments:',
' - FILEAREA (required) name of area',
' - DAYS (optional) number of days to resend mail from this area that you',
' If DAYS is omitted, the default is 30. The maximum is 365.',
];
}
public function process(): string
{
Log::debug(sprintf('%s:- Filefix [%s] for [%s] for [%s]',self::LOGKEY,self::command,$this->mo->fftn->ftn,join('|',$this->arguments)));
$command = self::command.' '.join(' ',$this->arguments);
if (! is_numeric($days=Arr::get($this->arguments,1,30)))
return sprintf('%-25s <-- INVALID, DAYS [%s] NOT NUMERIC',$command,$this->arguments[1]);
if ($days > 365)
$days = 365;
if (! ($area=Arr::get($this->arguments,0)))
return sprintf('%-25s <-- INVALID, AN AREA IS REQUIRED',$command);
// Area exists
if ($fa=$this->mo->fftn->domain->fileareas->where('name',$area)->pop()) {
// If already subscribed
if ($this->mo->fftn->fileareas->pluck('name')->contains($area)) {
Notification::route('netmail',$this->mo->fftn)
->notify(new FileListNotification($this->mo,$fa,$days));
Log::debug(sprintf('%s:- FTN [%s] FILELIST [%s] DAYS [%d]',self::LOGKEY,$this->mo->fftn->ftn,$area,$days));
return sprintf('%-25s <-- FILELIST [%d] DAYS',$command,$days);
// If not subscribed
} else {
Log::debug(sprintf('%s:- FTN [%s] is NOT subscribed to [%s], NO ACTION taken',self::LOGKEY,$this->mo->fftn->ftn,$area));
return sprintf('%-25s <-- NOT subscribed, NO ACTION taken',$command);
}
} else {
Log::debug(sprintf('%s:- FILE [%s] area UNKNOWN [%s], NO ACTION taken',self::LOGKEY,$this->mo->fftn->ftn,$area));
return sprintf('%-25s <-- AREA UNKNOWN, NO ACTION TAKEN',$command);
}
}
}

View File

@@ -0,0 +1,50 @@
<?php
namespace App\Classes\FTN\Process\Netmail\Robot\Filefix;
use App\Classes\FTN\Process\Netmail\Robot\Areafix\Base;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
use App\Classes\FTN\Process\Netmail\Robot\Filefix;
use App\Notifications\Netmails\Filefix\Help as HelpNotification;
// A Help Index Command
class Help extends Base
{
private const LOGKEY = 'FFH';
private const filefix_classes = 'app/Classes/FTN/Process/Netmail/Robot/Filefix';
private const command = '%HELP';
public static function help(): array
{
return [
self::command,
' This message!',
];
}
public function process(): string
{
Log::debug(sprintf('%s:- Processing [%s] for [%s]',self::LOGKEY,self::command,$this->mo->fftn->ftn));
$result = collect();
foreach (preg_grep('/^([^.])/',scandir(self::filefix_classes)) as $file) {
if (($file === 'Base.php') || (! str_ends_with(strtolower($file),'.php')))
continue;
$class = Filefix::commands.preg_replace('/\.php$/','',$file);
if ($result->count())
$result->push('');
$result = $result
->merge($class::help());
}
Notification::route('netmail',$this->mo->fftn)->notify(new HelpNotification($this->mo,$result));
return sprintf('%-25s <-- COMMAND PROCESSED',self::command);
}
}

View File

@@ -0,0 +1,70 @@
<?php
namespace App\Classes\FTN\Process\Netmail\Robot\Filefix;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Log;
use App\Classes\FTN\Process\Netmail\Robot\Areafix\Base;
use App\Jobs\FilefixRescan;
// RESCAN - Resend echomail
class Rescan extends Base
{
private const LOGKEY = 'AFR';
private const command = '%RESCAN';
public static function help(): array
{
return [
self::command.' <FILEAREA> [<DAYS>]',
' Use the rescan command to resend files from a filearea.',
' This is will resend files again, even if you have received them in the',
' past.',
' Arguments:',
' - FILEAREA (required) name of area to subscribe or unsubscribe',
' - DAYS (optional) number of days to resend mail from this area that you',
' If DAYS is omitted, the default is 30. The maximum is 365.',
];
}
public function process(): string
{
Log::debug(sprintf('%s:- Filefix [%s] for [%s] for [%s]',self::LOGKEY,self::command,$this->mo->fftn->ftn,join('|',$this->arguments)));
$command = self::command.' '.join(' ',$this->arguments);
if (! ($area=Arr::get($this->arguments,0)))
return sprintf('%-25s <-- INVALID, AN AREA IS REQUIRED',$command);
if (! is_numeric($days=Arr::get($this->arguments,1,30)))
return sprintf('%-25s <-- INVALID, DAYS [%s] NOT NUMERIC',$command,$this->arguments[1]);
if ($days > 365)
$days = 365;
// Area exists
if ($fa=$this->mo->fftn->domain->fileareas->where('name',$area)->pop()) {
// If already subscribed
if ($this->mo->fftn->fileareas->pluck('name')->contains($area)) {
FilefixRescan::dispatch($this->mo->fftn,$fa,$days,TRUE)
->onQueue('mail');
Log::debug(sprintf('%s:- FTN [%s] RESCAN [%s] DAYS [%d]',self::LOGKEY,$this->mo->fftn->ftn,$area,$days));
return sprintf('%-25s <-- RESCAN [%d] DAYS queued',$command,$days);
// If not subscribed
} else {
Log::debug(sprintf('%s:- FTN [%s] is NOT subscribed to [%s], NO ACTION taken',self::LOGKEY,$this->mo->fftn->ftn,$area));
return sprintf('%-25s <-- NOT subscribed, NO ACTION taken',$command);
}
} else {
Log::debug(sprintf('%s:- FILE [%s] area UNKNOWN [%s], NO ACTION taken',self::LOGKEY,$this->mo->fftn->ftn,$area));
return sprintf('%-25s <-- AREA UNKNOWN, NO ACTION TAKEN',$command);
}
}
}

View File

@@ -0,0 +1,79 @@
<?php
namespace App\Classes\FTN\Process\Netmail\Robot\Filefix;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use App\Classes\FTN\Process\Netmail\Robot\Areafix\Base;
// Resend a file
class Resend extends Base
{
private const LOGKEY = 'FFA';
private const command = '%RESEND';
public static function help(): array
{
return [
self::command.' <FILEAREA> <FILENAME>',
' Resend a file from a file area',
' Arguments:',
' - FILEAREA (required) name of area to subscribe or unsubscribe',
' - FILENAME (required) number of file to resend',
' Notes:',
' * You can obtain a list of files in an area with %FILELIST <FILEAREA>',
];
}
public function process(): string
{
$command = self::command.' '.join(' ',$this->arguments);
if (count($this->arguments) < 2)
return sprintf('%-25s <-- INVALID, NOT ENOUGH ARGUMENTS',$command);
elseif (count($this->arguments) > 2)
return sprintf('%-25s <-- INVALID, TOO MANY ARGUMENTS',$command);
Log::debug(sprintf('%s:- Resending [%s] from [%s] to [%s]',self::LOGKEY,$this->arguments[1],$this->arguments[0],$this->mo->fftn->ftn));
// Area exists
if ($fa=$this->mo->fftn->domain->fileareas->where('name',$this->arguments[0])->pop()) {
// If already subscribed
if ($nea=$this->mo->fftn->fileareas->where('name',$fa->name)->pop()) {
// Check the file is in the area
if ($fo=$nea->files()->where('name','ilike',$this->arguments[1])->single()) {
// File hasnt been exported before
if (! $fo->seenby->where('id',$this->mo->fftn_id)->count()) {
Log::info(sprintf('Exported [%d] FILE (%s) dated (%s) to [%s]',$fo->id,$fo->name,$fo->datetime->format('Y-m-d H:i:s'),$this->mo->fftn->ftn3d));
$fo->seenby()->attach($this->mo->fftn_id,['export_at'=>Carbon::now()]);
} else {
Log::debug(sprintf('Re-exported [%d] FILE (%s) dated (%s) to [%s]',$fo->id,$fo->name,$fo->datetime,$this->mo->fftn->ftn3d));
$fo->seenby()->updateExistingPivot($this->mo->fftn_id,['export_at'=>Carbon::now(),'sent_at'=>NULL]);
}
return sprintf('%-25s <-- FILE QUEUED TO RESEND',$command);
// No file in area
} else {
Log::debug(sprintf('%s:- FTN [%s] doesnt have a file [%s], NO ACTION taken',self::LOGKEY,$this->mo->fftn->ftn,$this->arguments[1]));
return sprintf('%-25s <-- FILE NOT FOUND, NO ACTION taken',$command);
}
// If not subscribed
} else {
Log::debug(sprintf('%s:- FTN [%s] is NOT subscribed to [%s], NO ACTION taken',self::LOGKEY,$this->mo->fftn->ftn,$this->arguments[0]));
return sprintf('%-25s <-- NOT subscribed, NO ACTION taken',$command);
}
} else {
Log::debug(sprintf('%s:- FILE [%s] area UNKNOWN [%s], NO ACTION taken',self::LOGKEY,$this->mo->fftn->ftn,$this->arguments[0]));
return sprintf('%-25s <-- AREA UNKNOWN, NO ACTION TAKEN',$command);
}
}
}

View File

@@ -0,0 +1,70 @@
<?php
namespace App\Classes\FTN\Process\Netmail\Robot\Filefix;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Log;
use App\Classes\FTN\Process\Netmail\Robot\Areafix\Base;
use App\Jobs\FilefixRescan;
// SCAN - Send unsent echomail
class Scan extends Base
{
private const LOGKEY = 'AFS';
private const command = '%SCAN';
public static function help(): array
{
return [
self::command.' <FILEAREA> [<DAYS>]',
' Use the scan command to resend files that you havent received yet from an',
' filearea. This is useful if you are rejoining an filearea, and only want',
' to get files that you dont already have.',
' Arguments:',
' - FILEAREA (required) name of area to subscribe or unsubscribe',
' - DAYS (optional) number of days to resend mail from this area that you',
' If DAYS is omitted, the default is 30. The maximum is 365.',
];
}
public function process(): string
{
Log::debug(sprintf('%s:- Filefix [%s] for [%s] for [%s]',self::LOGKEY,self::command,$this->mo->fftn->ftn,join('|',$this->arguments)));
$command = self::command.' '.join(' ',$this->arguments);
if (! ($area=Arr::get($this->arguments,0)))
return sprintf('%-25s <-- INVALID, AN AREA IS REQUIRED',$command);
if (! is_numeric($days=Arr::get($this->arguments,1,30)))
return sprintf('%-25s <-- INVALID, DAYS [%s] NOT NUMERIC',$command,$this->arguments[1]);
if ($days > 365)
$days = 365;
// Area exists
if ($fa=$this->mo->fftn->domain->fileareas->where('name',$area)->pop()) {
// If already subscribed
if ($this->mo->fftn->fileareas->pluck('name')->contains($area)) {
FilefixRescan::dispatch($this->mo->fftn,$fa,$days)
->onQueue('mail');
Log::debug(sprintf('%s:- FTN [%s] SCAN [%s] DAYS [%d]',self::LOGKEY,$this->mo->fftn->ftn,$area,$days));
return sprintf('%-25s <-- SCAN [%d] DAYS queued',$command,$days);
// If not subscribed
} else {
Log::debug(sprintf('%s:- FTN [%s] is NOT subscribed to [%s], NO ACTION taken',self::LOGKEY,$this->mo->fftn->ftn,$area));
return sprintf('%-25s <-- NOT subscribed, NO ACTION taken',$command);
}
} else {
Log::debug(sprintf('%s:- FILE [%s] area UNKNOWN [%s], NO ACTION taken',self::LOGKEY,$this->mo->fftn->ftn,$area));
return sprintf('%-25s <-- AREA UNKNOWN, NO ACTION TAKEN',$command);
}
}
}

View File

@@ -106,7 +106,7 @@ class Tic extends FTNBase
$result->put('REPLACES',$this->file->replaces);
$result->put('AREA',$this->file->filearea->name);
$result->put('AREADESC',$this->file->filearea->description);
if ($x=strtoupper($this->to->session('ticpass')))
if ($x=$this->to->pass_tic)
$result->put('PW',$x);
$result->put('CRC',sprintf("%X",$this->file->crc));
@@ -135,7 +135,7 @@ class Tic extends FTNBase
*/
public function isNodelist(): bool
{
Log::critical(sprintf('%s:D fo_nodelist_file_area [%d], fo_filearea_domain_filearea_id [%d], regex [%s] name [%s]',
Log::info(sprintf('%s:D fo_nodelist_file_area [%d], fo_filearea_domain_filearea_id [%d], regex [%s] name [%s]',
self::LOGKEY,
$this->file->nodelist_filearea_id,
$this->file->filearea->domain->filearea_id,
@@ -219,6 +219,8 @@ class Tic extends FTNBase
case 'from':
if (($ao=Address::findFTN($m[2])) && ((! $aid) || ($ao->zone->domain_id === Address::findOrFail(hexdec($aid))->zone->domain_id)))
$this->file->fftn_id = $ao->id;
elseif ($aid && ($x=Address::findOrFail(hexdec($aid))) && (($y=$x->system->akas->search(fn($item)=>str_starts_with($item->ftn,$m[2]))) !== FALSE))
$this->file->fftn_id = $x->system->akas->get($y)->id;
else
throw new ModelNotFoundException(sprintf('FTN Address [%s] not found or sender mismatch',$m[2]));
@@ -347,11 +349,11 @@ class Tic extends FTNBase
// @todo Add notification back to the system if no replaces line and the file already exists
// Validate Size
if ($this->file->size !== ($y=$fs->size($file)))
if ((! is_null($this->file->size)) && ($this->file->size !== ($y=$fs->size($file))))
throw new SizeMismatchException(sprintf('TIC file size [%d] doesnt match file [%s] (%d)',$this->file->size,$fs->path($rel_path_name),$y));
// Validate Password
if (strtoupper($pw) !== ($y=strtoupper($this->file->fftn->session('ticpass'))))
if (strtoupper($pw) !== ($y=$this->file->fftn->pass_tic))
throw new InvalidPasswordException(sprintf('TIC file PASSWORD [%s] doesnt match system [%s] (%s)',$pw,$this->file->fftn->ftn,$y));
// Validate Sender is linked
@@ -367,6 +369,10 @@ class Tic extends FTNBase
if (! $this->file->datetime)
$this->file->datetime = Carbon::createFromTimestamp($fs->lastModified($file));
// If the file size was omitted, we'll use the file's size
if (is_null($this->file->size))
$this->file->size = $fs->size($file);
$this->file->src_file = $file;
$this->file->recv_tic = $filename;

View File

@@ -22,24 +22,28 @@ class File extends FileBase implements \Iterator
{
parent::__construct($path,$checkPath);
switch($x=$this->guessExtension()) {
case 'zip':
$this->canHandle = TRUE;
$this->isArchive = TRUE;
$this->z = new \ZipArchive;
$this->z->open($this->getRealPath());
break;
if ($this->getExtension() === 'pkt')
$this->canHandle = TRUE;
case NULL:
case 'bin':
if ($this->isPacket() || ($path instanceof UploadedFile && (strcasecmp($path->getClientOriginalExtension(),'pkt') === 0))) {
else
switch ($x=$this->guessExtension()) {
case 'zip':
$this->canHandle = TRUE;
$this->isArchive = TRUE;
$this->z = new \ZipArchive;
$this->z->open($this->getRealPath());
break;
}
default:
Log::alert(sprintf('%s:? Unknown file received: %s (%s) [%s]',self::LOGKEY,$x,$this->getExtension(),$path instanceof UploadedFile ? $path->getClientOriginalExtension() : '-'));
}
case NULL:
case 'bin':
if ($this->isPacket() || ($path instanceof UploadedFile && (strcasecmp($path->getClientOriginalExtension(),'pkt') === 0))) {
$this->canHandle = TRUE;
break;
}
default:
Log::alert(sprintf('%s:? Unknown file received: %s (%s) [%s]',self::LOGKEY,$x,$this->getExtension(),$path instanceof UploadedFile ? $path->getClientOriginalExtension() : '-'));
}
}
/* ITERATOR */
@@ -83,6 +87,11 @@ class File extends FileBase implements \Iterator
/* METHODS */
public function isArchive(): bool
{
return $this->isArchive;
}
/**
* Determine if the file is a mail packet
*

View File

@@ -11,7 +11,7 @@ use Illuminate\Support\Collection;
* When sending, we can queue up a list of items, and mark one active (the one being sent) at a time.
*
* + Netmail/Echomail/TIC
* + name is dynamically calculated, based on timew() of the youngest item in the mail bundle
* + name is the hex ID of the youngest item in the mail bundle
* + size is dynamically calculated based on the size of the bundle
* + mtime is dynamically calculated, based on the age of the youngest item
* + sendas (nameas) is name + [.pkt|.tic]

View File

@@ -41,7 +41,6 @@ final class File extends Send
return $this->f->datetime->timestamp;
case 'name':
case 'size':
return $this->f->{$key};
case 'type':
@@ -84,22 +83,21 @@ final class File extends Send
*/
public function open(string $compress=''): bool
{
$this->size = $this->f->size;
// If sending file is a File::class, then our file is s3
if ($this->nameas && $this->f instanceof FileModel) {
$this->fd = Storage::readStream($this->f->rel_name);
$this->fd = ($this->nameas && $this->f instanceof FileModel)
? Storage::readStream($this->f->rel_name)
: fopen($this->full_name,'rb');
} else {
$this->fd = fopen($this->full_name,'rb');
if (! $this->fd) {
Log::error(sprintf('%s:! Unable to open file [%s] for reading',self::LOGKEY,$this->full_name));
if (! $this->fd) {
Log::error(sprintf('%s:! Unable to open file [%s] for reading',self::LOGKEY,$this->full_name));
return FALSE;
}
Log::info(sprintf('%s:= File [%s] opened with size [%d]',self::LOGKEY,$this->full_name,$this->size));
return FALSE;
}
Log::info(sprintf('%s:= File [%s] opened with size [%d]',self::LOGKEY,$this->full_name,$this->size));
return TRUE;
}
@@ -110,6 +108,6 @@ final class File extends Send
public function seek(int $pos): bool
{
return (fseek($this->f,$pos,SEEK_SET) === 0);
return (fseek($this->fd,$pos,SEEK_SET) === 0);
}
}

View File

@@ -4,6 +4,7 @@ namespace App\Classes\File;
use Carbon\Carbon;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
@@ -36,16 +37,13 @@ final class Mail extends Send
return $this->f->messages->pluck('id');
case 'name':
return sprintf('%08x',timew($this->youngest()));
return sprintf('%08x',$this->youngest_id());
case 'nameas':
return sprintf('%s.pkt',$this->name);
case 'mtime':
return $this->youngest()->timestamp;
case 'size':
return strlen($this->f);
return $this->youngest_date()->timestamp;
case 'type':
return ($this->ftype&0xff00)>>8;
@@ -60,7 +58,7 @@ final class Mail extends Send
if ($successful) {
$this->complete = TRUE;
Log::debug(sprintf('%s:- Successful close for [%d] - updating [%d] records.',self::LOGKEY,$this->type,$this->dbids->count()),['dbids'=>$this->dbids,'authd'=>$node->aka_remote_authed->pluck('id')]);
Log::info(sprintf('%s:- Successful close for [%d] - updating [%d] records.',self::LOGKEY,$this->type,$this->dbids->count()),['dbids'=>$this->dbids,'authd'=>$node->aka_remote_authed->pluck('id')]);
// Update netmail table
if (($this->type === Send::T_NETMAIL)
@@ -87,6 +85,7 @@ final class Mail extends Send
]);
$this->content = NULL;
$this->f = NULL;
}
}
@@ -98,6 +97,7 @@ final class Mail extends Send
public function open(string $compress=''): bool
{
$this->content = (string)$this->f;
$this->size = strlen($this->content);
return TRUE;
}
@@ -115,8 +115,18 @@ final class Mail extends Send
return TRUE;
}
private function youngest(): Carbon
private function youngest(): array
{
return $this->f->messages->pluck('date')->sort()->last();
return $this->f->messages->sortBy(fn($item)=>Arr::get($item,'datetime'))->first();
}
private function youngest_id(): int
{
return Arr::get($this->youngest(),'id',0);
}
private function youngest_date(): Carbon
{
return Arr::get($this->youngest(),'datetime',Carbon::now());
}
}

View File

@@ -128,9 +128,9 @@ class Receive extends Base
// If packet is greater than a size, lets queue it
if ($this->queue || ($this->receiving->size > config('fido.queue_size',0))) {
Log::info(sprintf('%s:- Packet [%s] will be sent to the queue for processing because its [%d] size, or queue forced',self::LOGKEY,$this->receiving->full_name,$this->receiving->size));
PacketProcess::dispatch($this->receiving->rel_name,$this->ao->zone->domain,FALSE,$rcvd_time);
PacketProcess::dispatch($this->receiving->rel_name,$this->ao->system,FALSE,$rcvd_time);
} else
PacketProcess::dispatchSync($this->receiving->rel_name,$this->ao->zone->domain,TRUE,$rcvd_time);
PacketProcess::dispatchSync($this->receiving->rel_name,$this->ao->system,TRUE,$rcvd_time);
} catch (\Exception $e) {
Log::error(sprintf('%s:! Got error dispatching packet [%s] (%d:%s-%s).',self::LOGKEY,$this->receiving->rel_name,$e->getLine(),$e->getFile(),$e->getMessage()));

View File

@@ -34,6 +34,7 @@ class Send extends Base
public const T_ECHOMAIL = (1<<2);
private string $comp_data;
protected int $size = 0;
public function __construct()
{
@@ -124,6 +125,9 @@ class Send extends Base
if ($successful) {
$end = time()-$this->start;
Log::info(sprintf('%s:- Closing [%s], sent in [%d] with [%s] items',self::LOGKEY,$this->sending->nameas,$end,$this->sending->dbids->count()));
} else {
Log::alert(sprintf('%s:- Closing [%s], file NOT SENT successfully',self::LOGKEY,$this->sending->nameas));
}
$this->sending->close($successful,$node);
@@ -199,8 +203,8 @@ class Send extends Base
}
// Files
if (($x=$ao->filesWaiting())->count()) {
Log::debug(sprintf('%s:- [%d] Files(s) added for sending to [%s]',self::LOGKEY,$x->count(),$ao->ftn));
if (($x=$ao->getFiles())->count()) {
Log::info(sprintf('%s:- [%d] Files(s) added for sending to [%s]',self::LOGKEY,$x->count(),$ao->ftn));
// Add Files
foreach ($x as $fo) {
@@ -223,12 +227,12 @@ class Send extends Base
*/
public function open(string $compress=''): bool
{
Log::debug(sprintf('%s:+ Opening file to send',self::LOGKEY));
Log::debug(sprintf('%s:+ File Open to send',self::LOGKEY));
if ((($this->index=$this->list->search(function($item) { return $item->complete === FALSE; })) !== FALSE)
&& $this->sending->open())
{
Log::info(sprintf('%s:- Sending item [%d] (%s)',self::LOGKEY,$this->index,$this->sending->nameas));
Log::info(sprintf('%s:- Content Send item [#%d] (%s) with size [%d]',self::LOGKEY,$this->index,$this->sending->nameas,$this->sending->size));
$this->pos = 0;
$this->start = time();
@@ -241,7 +245,10 @@ class Send extends Base
return TRUE;
} else {
throw new Exception('No files to open');
Log::error(sprintf('%s:- No files to open',self::LOGKEY));
$this->index = NULL;
return FALSE;
}
}
@@ -302,7 +309,7 @@ class Send extends Base
$this->pos += strlen($data);
Log::debug(sprintf('%s:- Read [%d] bytes, file pos now [%d]',self::LOGKEY,strlen($data),$this->pos));
Log::debug(sprintf('%s:- Content Read [%d] bytes, pos now [%d]',self::LOGKEY,strlen($data),$this->pos));
return $data;
}
@@ -322,7 +329,7 @@ class Send extends Base
if ($this->sending->seek($pos)) {
$this->pos = $pos;
Log::debug(sprintf('%s:= Seeked to [%d]',self::LOGKEY,$this->pos));
Log::debug(sprintf('%s:= Content Seek to [%d]',self::LOGKEY,$this->pos));
return TRUE;

View File

@@ -3,6 +3,7 @@
namespace App\Classes\File\Send;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use App\Classes\File\Send;
use App\Classes\Node;
@@ -47,7 +48,7 @@ final class Dynamic extends Send
return $this->sent->timestamp;
case 'size':
return strlen($this->buffer);
return $this->{$key};
default:
return NULL;
@@ -56,6 +57,8 @@ final class Dynamic extends Send
public function close(bool $successful,Node $node): void
{
Log::debug(sprintf('%s:- Close [%s] - %s',self::LOGKEY,$this->nameas,$successful ? 'SUCCESSFUL' : 'FAILED'));
if ($successful) {
$this->complete = TRUE;
@@ -74,24 +77,35 @@ final class Dynamic extends Send
$this->do->next_at = $next_at
->addDay();
while ($this->do->next_at->isPast())
$this->do->next_at = $this->do->next_at->addDay();
break;
case 'WEEKLY':
$this->do->next_at = $next_at
->addWeek();
while ($this->do->next_at->isPast())
$this->do->next_at = $this->do->next_at->addWeek();
break;
case 'MONTHLY':
$this->do->next_at = $next_at
->addMonth();
while ($this->do->next_at->isPast())
$this->do->next_at = $this->do->next_at->addMonth();
break;
default:
throw new \Exception(sprintf('%s:! Unknown frequency [%s] for [%d]',self::LOGKEY,$this->do->frequency,$this->do->id));
}
Log::debug(sprintf('%s: - Frequency [%s], UPDATE next_at [%s]',self::LOGKEY,$this->do->frequency,$next_at->format('Y-m-d H:i:s')));
$this->do->save();
}
}
@@ -104,6 +118,7 @@ final class Dynamic extends Send
public function open(string $compress=''): bool
{
$this->buffer = (string)$this->item;
$this->size = strlen($this->buffer);
return TRUE;
}

View File

@@ -43,9 +43,6 @@ final class Tic extends Send
case 'mtime':
return $this->f->datetime->timestamp;
case 'size':
return strlen($this->tic);
case 'type':
return ($this->ftype&0xff00)>>8;
@@ -67,6 +64,8 @@ final class Tic extends Send
public function open(string $compress=''): bool
{
$this->size = strlen($this->tic);
return TRUE;
}

View File

@@ -0,0 +1,201 @@
<?php
namespace App\Classes\Fonts;
use App\Classes\Font;
final class Ansitex extends Font
{
protected const FONT = [
'a' => [
[0xda,0xc4,0xdc],
[0xb3,0xdf,0xdb],
[0x20,0x20,0x20],
],
'b' => [
[0xb3,0xc4,0xdc],
[0xb3,0x5f,0xdb],
[0x20,0x20,0x20],
],
'c' => [
[0xda,0xc4,0xdc],
[0xb3,0x5f,0xdc],
[0x20,0x20,0x20],
],
'd' => [
[0xda,0xc4,0xdb],
[0xb3,0x5f,0xdb],
[0x20,0x20,0x20],
],
'e' => [
[0xda,0xc4,0xdc],
[0xc3,0x5f,0xdc],
[0x20,0x20,0x20],
],
'f' => [
[0xda,0xc4,0xdc],
[0xc3,0x20,0x20],
[0x20,0x20,0x20],
],
'g' => [
[0xda,0xc4,0xdc],
[0xb3,0x5f,0xdb],
[0x20,0xc4,0xdf],
],
'h' => [
[0xde,0xc4,0xdc],
[0xde,0x20,0xdb],
[0x20,0x20,0x20],
],
'i' => [
[0x20,0xdf],
[0xde,0xdb],
[0x20,0x20],
],
'j' => [
[0x20,0xdf],
[0xde,0xdb],
[0xc4,0xdf],
],
'k' => [
[0xb3,0x20,0xdc],
[0xb3,0x60,0xdc],
[0x20,0x20,0x20],
],
'l' => [
[0xb3,0x20],
[0xb3,0xdc],
[0x20,0x20],
],
'm' => [
[0xda,0xda,0xdc],
[0xb3,0xb3,0xdb],
[0x20,0x20,0x20],
],
'n' => [
[0xda,0xc4,0xdc],
[0xb3,0x20,0xdb],
[0x20,0x20,0x20],
],
'o' => [
[0xda,0xc4,0xdc],
[0xb3,0x5f,0xdb],
[0x20,0x20,0x20],
],
'p' => [
[0xda,0xc4,0xdc],
[0xb3,0x5f,0xdb],
[0xc0,0x20,0x20],
],
'q' => [
[0xda,0xc4,0xdc],
[0xb3,0x5f,0xdb],
[0x20,0x20,0xdf],
],
'r' => [
[0xda,0xc4,0xdc],
[0xb3,0xc1,0x5c],
[0x20,0x20,0x20],
],
's' => [
[0xda,0xc4,0xdc],
[0x2e,0x5c,0xdc],
[0x20,0x20,0x20],
],
't' => [
[0xda,0xc2,0xdc],
[0x20,0xb3,0x20],
[0x20,0x20,0x20],
],
'u' => [
[0xda,0x20,0xdc],
[0xb3,0x5f,0xdb],
[0x20,0x20,0x20],
],
'v' => [
[0xda,0x20,0xdc],
[0x60,0x5c,0xdb],
[0x20,0x20,0x20],
],
'w' => [
[0xda,0xda,0xdb],
[0x60,0x5c,0xdb],
[0x20,0x20,0x20],
],
'x' => [
[0xda,0x20,0xdc],
[0xda,0xdf,0xdc],
[0x20,0x20,0x20],
],
'y' => [
[0xda,0x20,0xdc],
[0xb3,0x5f,0xdb],
[0x20,0xc4,0xdf],
],
'z' => [
[0xda,0xc4,0xdc],
[0x2e,0x2f,0xdc],
[0x20,0x20,0x20],
],
'1' => [
[0xc4,0xdc],
[0x20,0xdb],
[0x20,0x20],
],
'2' => [
[0xfe,0xc4,0xdc],
[0xda,0x2f,0xdc],
[0x20,0x20,0x20],
],
'3' => [
[0xda,0xc4,0xdc],
[0xda,0xf0,0xdb],
[0x20,0x20,0x20],
],
'4' => [
[0xde,0x20,0xdc],
[0xc0,0xc4,0xdb],
[0x20,0x20,0x20],
],
'5' => [
[0xda,0xc4,0xdc],
[0x2e,0x2d,0xdc],
[0x20,0x20,0x20],
],
'6' => [
[0xde,0xc4,0xbf],
[0xb3,0x5f,0xdb],
[0x20,0x20,0x20],
],
'7' => [
[0xfe,0x2d,0xbf],
[0x20,0xde,0x20],
[0x20,0x20,0x20],
],
'8' => [
[0xda,0xc4,0xdc],
[0xc3,0xf0,0xdb],
[0x20,0x20,0x20],
],
'9' => [
[0xda,0xc4,0xdc],
[0xd4,0xc4,0xdb],
[0x20,0x20,0x20],
],
'0' => [
[0xda,0xc4,0xdc],
[0xb3,0x5f,0xdb],
[0x20,0x20,0x20],
],
'#' => [
[0xdc,0xba,0xdc],
[0xfe,0xba,0xfe],
[0x20,0x20,0x20],
],
'!' => [
[0xdb],
[0xfe],
[0x20],
],
];
}

View File

@@ -38,6 +38,7 @@ class Node
private Collection $ftns_authed; // The FTNs we have validated
private Collection $ftns_other; // Other FTN addresses presented
private bool $authed; // Have we authenticated the remote.
private Address $originate; // When we originate a call, this is who we are after
private int $options; // This nodes capabilities/options
@@ -84,11 +85,15 @@ class Node
// The nodes password
case 'password':
// If we are originating a session, we'll use that password.
if (isset($this->originate))
return $this->originate->pass_session;
// If we have already authed, we'll use that password.
if ($this->ftns_authed->count())
return $this->ftns_authed->first()->session('sespass');
return $this->ftns_authed->first()->pass_session;
else
return ($this->ftns->count() && ($x=$this->ftns->first()->session('sespass'))) ? $x : '-';
return ($this->ftns->count() && ($x=$this->ftns->first()->pass_session)) ? $x : '-';
// Return how long our session has been connected
case 'session_time':
@@ -194,7 +199,9 @@ class Node
throw new Exception('Already authed');
foreach ($this->ftns as $o) {
if (! $sespass=$o->session('sespass'))
Log::debug(sprintf('%s:- Attempting to authenticate [%s] with [%s]',self::LOGKEY,$o->ftn,$o->pass_session));
if (! $sespass=$o->pass_session)
continue;
// If we have challenge, then we are doing MD5
@@ -269,7 +276,8 @@ class Node
*/
public function originate(Address $o): void
{
$this->ftns_authed->push($o);
$this->originate = $o;
$this->ftns_authed = $o->system->match($o->zone,Address::NODE_ALL);
}
/**
@@ -283,19 +291,11 @@ class Node
if ($this->authed)
return TRUE;
if ($this->ftns_authed->count() !== 1 || ! $this->ftns->count())
return FALSE;
Log::debug(sprintf('%s:- Making sure we called [%s] from [%s]',self::LOGKEY,$this->originate->ftn,$this->ftns->pluck('ftn')->join(',')));
$ftn = $this->ftns_authed->first()->ftn;
$this->authed = $this->ftns->pluck('ftn')->contains($this->originate->ftn);
return $this->ftns->search(function($item) use ($ftn) {
if ($item->ftn === $ftn) {
$item->system->last_session = Carbon::now();
$item->system->save();
$this->authed = TRUE;
return TRUE;
}
}) !== FALSE;
return $this->authed;
}
public function optionClear(int $key): void

View File

@@ -332,13 +332,13 @@ class Page
$subtext = substr($this->text,$current_pos,$space_pos);
}
// If the reset of the string will fit on the current line
} elseif ($text_length-$current_pos < static::MSG_WIDTH-$buffer) {
// If the rest of the string will fit on the current line
} elseif ($text_length-$current_pos < static::MSG_WIDTH-$this->x-$buffer) {
$subtext = substr($this->text,$current_pos);
// Get the next lines worth of chars, breaking on a space
} else {
$subtext = $this->text_substr(substr($this->text,$current_pos),static::MSG_WIDTH-$buffer);
$subtext = $this->text_substr(substr($this->text,$current_pos),static::MSG_WIDTH-$this->x-$buffer);
// Include the text up to the last space
if (substr($this->text,$current_pos+strlen($subtext),1) !== ' ')

View File

@@ -6,9 +6,9 @@ use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use App\Classes\File\{Receive,Send};
use App\Classes\Protocol\EMSI;
use App\Classes\Protocol\{Binkp,DNS,EMSI,Zmodem};
use App\Classes\Sock\Exception\SocketException;
use App\Classes\Sock\SocketClient;
use App\Classes\Sock\SocketException;
use App\Models\{Address,Mailer,Setup,System,SystemLog};
// @todo after receiving a mail packet/file, dont acknowledge it until we can validate that we can read it properly.
@@ -97,17 +97,17 @@ abstract class Protocol
// File transfer status
public const FOP_OK = 0;
public const FOP_CONT = 1;
public const FOP_SKIP = 2;
public const FOP_ERROR = 3;
public const FOP_OK = 0;
public const FOP_CONT = 1;
public const FOP_SKIP = 2;
public const FOP_ERROR = 3;
public const FOP_SUSPEND = 4;
public const FOP_GOT = 5;
public const TCP_SPEED = 115200;
protected SocketClient $client; /* Our socket details */
protected ?Setup $setup; /* Our setup */
protected Setup $setup; /* Our setup */
protected Node $node; /* The node we are communicating with */
/** The list of files we are sending */
protected Send $send;
@@ -122,7 +122,12 @@ abstract class Protocol
protected array $capability; // @todo make private
/** @var bool Are we originating a connection */
protected bool $originate;
/** Our comms details */
/** @var bool Is the application down for maintenance */
protected bool $down = FALSE;
/** @var int Our mailer ID for logging purposes */
private int $mailer_id;
private array $comms;
@@ -132,12 +137,31 @@ abstract class Protocol
abstract protected function protocol_session(bool $force_queue=FALSE): int;
public function __construct(Setup $o=NULL)
/**
* @param Setup $setup
* @throws \Exception
*/
public function __construct(Setup $setup)
{
if ($o && ! $o->system->akas->count())
throw new \Exception('We dont have any ACTIVE FTN addresses assigned');
$this->setup = $setup;
$this->setup = $o;
// Some initialisation details
switch (get_class($this)) {
case Binkp::class:
$this->mailer_id = Mailer::where('name','BINKP')->sole()->id;
break;
case DNS::class:
case Zmodem::class:
break;
case EMSI::class:
$this->mailer_id = Mailer::where('name','EMSI')->sole()->id;
break;
default:
throw new \Exception('not handled'.get_class($this));
}
}
/**
@@ -246,20 +270,29 @@ abstract class Protocol
* Incoming Protocol session
*
* @param SocketClient $client
* @return int|null
* @return int
* @throws SocketException
*/
public function onConnect(SocketClient $client): ?int
public function onConnect(SocketClient $client): int
{
$pid = pcntl_fork();
if ($pid === -1)
throw new SocketException(SocketException::CANT_ACCEPT,'Could not fork process');
// If our parent returns a PID, we've forked
if ($pid)
Log::info(sprintf('%s:+ New connection, thread [%d] created',self::LOGKEY,$pid));
Log::info(sprintf('%s:+ New connection from [%s], thread [%d] created',self::LOGKEY,$client->address_remote,$pid));
// This is the new thread
else {
Log::withContext(['pid'=>getmypid()]);
Log::debug(sprintf('%s:* Client session starting',self::LOGKEY));
$this->session($client,(new Address));
}
// Parent return ready for next connection
return $pid;
}
@@ -302,6 +335,7 @@ abstract class Protocol
* Our addresses to send to the remote
*
* @return Collection
* @throws \Exception
*/
protected function our_addresses(): Collection
{
@@ -316,7 +350,7 @@ abstract class Protocol
Log::debug(sprintf('%s:- Presenting limited AKAs [%s]',self::LOGKEY,$addresses->pluck('ftn')->join(',')));
} else {
$addresses = $this->setup->system->akas;
$addresses = our_address();
Log::debug(sprintf('%s:- Presenting ALL our AKAs [%s]',self::LOGKEY,$addresses->pluck('ftn')->join(',')));
}
@@ -325,15 +359,14 @@ abstract class Protocol
}
/**
* Initialise our Session
* Setup a session with a remote client
*
* @param Mailer $mo
* @param SocketClient $client
* @param Address|null $o
* @param SocketClient $client Socket details of session
* @param Address $o If we have an address, we originated a session to this Address
* @return int
* @throws \Exception
*/
public function session(Mailer $mo,SocketClient $client,Address $o=NULL): int
public function session(SocketClient $client,Address $o): int
{
if ($o->exists)
Log::withContext(['ftn'=>$o->ftn]);
@@ -364,13 +397,17 @@ abstract class Protocol
}
// We are an IP node
$this->optionSet(self::O_TCP);
$this->client = $client;
switch ($mo->name) {
case 'EMSI':
$this->client = $client;
// @todo This appears to be a bug in laravel? Need to call app()->isDownForMaintenance() twice?
app()->isDownForMaintenance();
$this->down = app()->isDownForMaintenance();
switch (get_class($this)) {
case EMSI::class:
Log::debug(sprintf('%s:- Starting EMSI',self::LOGKEY));
$this->optionSet(self::O_TCP);
$rc = $this->protocol_init();
if ($rc < 0) {
Log::error(sprintf('%s:! Unable to start EMSI [%d]',self::LOGKEY,$rc));
@@ -382,21 +419,23 @@ abstract class Protocol
break;
case 'BINKP':
case Binkp::class:
Log::debug(sprintf('%s:- Starting BINKP',self::LOGKEY));
$this->optionSet(self::O_TCP);
$rc = $this->protocol_session($this->originate);
break;
case DNS::class:
return $this->protocol_session();
default:
Log::error(sprintf('%s:! Unsupported session type [%d]',self::LOGKEY,$mo->id));
Log::error(sprintf('%s:! Unsupported session type [%s]',self::LOGKEY,get_class($this)));
return self::S_FAILURE;
}
// @todo Unlock outbounds
// @todo These flags determine when we connect to the remote.
// If the remote indicated that they dont support file requests (NRQ) or temporarily hold them (HRQ)
if (($this->node->optionGet(self::O_NRQ) && (! $this->setup->optionGet(EMSI::F_IGNORE_NRQ,'emsi_options'))) || $this->node->optionGet(self::O_HRQ))
@@ -426,8 +465,9 @@ abstract class Protocol
if ($so && $so->exists) {
foreach ($this->node->aka_other as $aka)
if (! Address::findFTN($aka)) {
$oo = Address::createFTN($aka,$so);
// @todo For disabled zones, we shouldnt refuse to record the address
// @todo If the system hasnt presented an address for a configured period (eg: 30 days) assume it no longer carries it
if ((! Address::findFTN($aka)) && ($oo=Address::createFTN($aka,$so))) {
$oo->validated = TRUE;
$oo->save();
}
@@ -438,7 +478,7 @@ abstract class Protocol
$slo->items_sent_size = $this->send->total_sent_bytes;
$slo->items_recv = $this->recv->total_recv;
$slo->items_recv_size = $this->recv->total_recv_bytes;
$slo->mailer_id = $mo->id;
$slo->mailer_id = $this->mailer_id;
$slo->sessiontime = $this->node->session_time;
$slo->result = ($rc & self::S_MASK);
$slo->originate = $this->originate;
@@ -452,12 +492,6 @@ abstract class Protocol
}
}
// @todo Optional after session execution event
// if ($this->node->start_time && $this->setup->cfg('CFG_AFTERSESSION')) {}
// @todo Optional after session includes mail event
// if ($this->node->start_time && $this->setup->cfg('CFG_AFTERMAIL')) {}
return $rc;
}

View File

@@ -11,10 +11,9 @@ use League\Flysystem\UnreadableFileEncountered;
use App\Classes\Crypt;
use App\Classes\Node;
use App\Classes\Protocol as BaseProtocol;
use App\Classes\Sock\SocketClient;
use App\Classes\Sock\SocketException;
use App\Classes\Sock\Exception\SocketException;
use App\Exceptions\{FileGrewException,InvalidFTNException};
use App\Models\{Address,Mailer};
use App\Models\{Address,Setup};
final class Binkp extends BaseProtocol
{
@@ -142,36 +141,26 @@ final class Binkp extends BaseProtocol
*/
private Crypt $crypt_out;
/**
* Incoming BINKP session
*
* @param SocketClient $client
* @return int|null
* @throws SocketException
*/
public function onConnect(SocketClient $client): ?int
{
// If our parent returns a PID, we've forked
if (! parent::onConnect($client)) {
Log::withContext(['pid'=>getmypid()]);
$this->session(Mailer::where('name','BINKP')->singleOrFail(),$client,(new Address));
$this->client->close();
exit(0);
}
return NULL;
}
/**
* BINKD handshake
*
* @throws \Exception
*/
private function binkp_hs(): void
private function binkp_hs(): bool
{
Log::debug(sprintf('%s:+ Starting BINKP handshake',self::LOGKEY));
if (! $this->originate && $this->down) {
Log::info(sprintf('%s:! System down for maintenance',self::LOGKEY));
$this->msgs(self::BPM_BSY,'RETRY 0600: Down for maintenance, back soon...');
// @note Sometimes the remote drops the connection when we send the busy
while (($this->tx_left || $this->mqueue->count()) && $this->binkp_send()) {}
return FALSE;
}
if (! $this->originate && $this->capGet(self::F_MD,self::O_WANT)) {
$random_key = random_bytes(8);
$this->md_challenge = md5($random_key,TRUE);
@@ -184,7 +173,7 @@ final class Binkp extends BaseProtocol
$this->msgs(self::BPM_NUL,sprintf('NDL %d,TCP,BINKP',$this->client->speed));
$this->msgs(self::BPM_NUL,sprintf('TIME %s',Carbon::now()->toRfc2822String()));
$this->msgs(self::BPM_NUL,
sprintf('VER %s-%s %s/%s',config('app.name'),$this->setup->version,self::PROT,self::VERSION));
sprintf('VER %s/%s %s/%s',Setup::PRODUCT_NAME_SHORT,Setup::version(),self::PROT,self::VERSION));
if ($this->originate) {
$opt = $this->capGet(self::F_NOREL,self::O_WANT) ? ' NR' : '';
@@ -206,6 +195,8 @@ final class Binkp extends BaseProtocol
$this->msgs(self::BPM_ADR,$addresses->pluck('ftn')->join(' '));
}
return TRUE;
}
/**
@@ -380,11 +371,13 @@ final class Binkp extends BaseProtocol
if ($this->capGet(self::F_CRYPT,self::O_YES)) {
Log::debug(sprintf('%s:%% Decrypting data from remote.',self::LOGKEY));
$this->rx_buf .= $this->crypt_in->decrypt($rx_buf);
$this->rx_buf .= ($x=$this->crypt_in->decrypt($rx_buf));
} else {
$this->rx_buf .= $rx_buf;
$this->rx_buf .= ($x=$rx_buf);
}
Log::debug(sprintf('%s:- We read [%d] chars from remote',self::LOGKEY,strlen($x)),['rx_buf'=>hex_dump($x)]);
}
Log::debug(sprintf('%s:- Read buffer has [%d] chars to process.',self::LOGKEY,strlen($this->rx_buf)));
@@ -421,7 +414,7 @@ final class Binkp extends BaseProtocol
}
if (static::DEBUG)
Log::debug(sprintf('%s: - binkp_recv BUFFER [%d]',self::LOGKEY,strlen($this->rx_buf)));
Log::debug(sprintf('%s:- rx_buf size [%d]',self::LOGKEY,strlen($this->rx_buf)));
$msg = ord(substr($this->rx_buf,0,1));
@@ -474,6 +467,11 @@ final class Binkp extends BaseProtocol
$rc = $this->M_get($data);
break;
case self::BPM_SKIP:
Log::debug(sprintf('%s:- SKIP:Remote requested to skip file [%s]',self::LOGKEY,$data));
$rc = $this->M_skip($data);
break;
case self::BPM_GOTSKIP:
Log::debug(sprintf('%s:- GOT:Remote received, or already has a file [%s]',self::LOGKEY,$data));
$rc = $this->M_gotskip($data);
@@ -643,7 +641,7 @@ final class Binkp extends BaseProtocol
$offs = (int)$this->strsep($str,' ');
$flags = $this->strsep($str,' ');
if ($name && $size && $time) {
if ($name && is_numeric($size) && $time) {
return [
'file'=>['name'=>$name,'size'=>$size,'mtime'=>$time],
'offs'=>$offs,
@@ -698,7 +696,7 @@ final class Binkp extends BaseProtocol
// If we only present limited AKAs dont validate password against akas outside of the domains we present
} elseif (is_null(our_address($o))) {
Log::alert(sprintf('%s:/ AKA domain [%s] is not in our domain(s) [%s] - ignoring',self::LOGKEY,$o->zone->domain->name,our_address()->pluck('zone.domain.name')->unique()->join(',')));
Log::debug(sprintf('%s:/ AKA domain [%s] is not in our domain(s) [%s] - ignoring',self::LOGKEY,$o->zone->domain->name,our_address()->pluck('zone.domain.name')->unique()->join(',')));
$this->node->ftn_other = $rem_aka;
continue;
@@ -713,6 +711,7 @@ final class Binkp extends BaseProtocol
Log::info(sprintf('%s:- Got AKA [%s]',self::LOGKEY,$rem_aka));
// We'll update this address status
// @todo this shouldnt be here, since we havent authenticated the node
$o->validated = TRUE;
$o->role &= ~(Address::NODE_HOLD|Address::NODE_DOWN);
$o->save();
@@ -873,16 +872,13 @@ final class Binkp extends BaseProtocol
//if ($this->recv->fd)
// $this->recv->close();
// If we cannot understand the file, we'll send back a SKIP
if (! ($file=$this->file_parse($buf))) {
Log::error(sprintf('%s:! UNPARSABLE file info [%s]',self::LOGKEY,$buf));
$this->msgs(self::BPM_ERR,sprintf('M_FILE: unparsable file info: "%s", what are you on?',$buf));
$this->msgs(self::BPM_SKIP,$buf);
if ($this->sessionGet(self::SE_SENDFILE))
$this->send->close(FALSE,$this->node);
$this->rc = self::S_FAILURE;
return FALSE;
return TRUE;
}
// In NR mode, when we got -1 for the file offsite, the reply to our get will confirm our requested offset.
@@ -900,6 +896,18 @@ final class Binkp extends BaseProtocol
$this->recv->new($file['file'],$this->node->address,$this->force_queue);
// If the file is zero byte size, we'll skip it
if ($this->recv->recvsize === 0) {
Log::alert(sprintf('%s:! SKIPPING zero byte file info [%s]',self::LOGKEY,$this->recv->nameas));
$this->msgs(self::BPM_SKIP,$this->recv->name_size_time);
// Close the file, since we are skipping it.
$this->recv->close();
return TRUE;
}
try {
switch ($this->recv->open($file['offs']<0,$file['flags'])) {
case self::FOP_ERROR:
@@ -993,7 +1001,46 @@ final class Binkp extends BaseProtocol
}
/**
* M_GOT/M_SKIP commands
* M_SKIP commands
*
* @param string $buf
* @return bool
* @throws \Exception
* @todo We need to not add more files this session if a node skips a file
*/
private function M_skip(string $buf): bool
{
Log::alert(sprintf('%s:+ Remote request to skip the file for now [%s]',self::LOGKEY,$buf));
if ($file = $this->file_parse($buf)) {
if ($this->send->nameas
&& ! strncasecmp(Arr::get($file,'file.name'),$this->send->nameas,self::MAX_PATH)
&& $this->send->mtime === Arr::get($file,'file.mtime')
&& $this->send->size === Arr::get($file,'file.size'))
{
if ((! $this->sessionGet(self::SE_SENDFILE)) && (! $this->sessionGet(self::SE_WAITGOT))) {
Log::error(sprintf('%s:! M_skip for unknown file [%s]',self::LOGKEY,$buf));
} else {
Log::info(sprintf('%s:= Packet/File [%s], type [%d] skipped.',self::LOGKEY,$this->send->nameas,$this->send->type));
$this->sessionClear(self::SE_WAITGOT|self::SE_SENDFILE);
$this->send->close(FALSE,$this->node);
}
} else {
Log::error(sprintf('%s:! M_skip not for our file? [%s]',self::LOGKEY,$buf));
}
} else {
Log::error(sprintf('%s:! UNPARSABLE file info [%s]',self::LOGKEY,$buf));
}
return TRUE;
}
/**
* M_GOTSKIP command
*
* @param string $buf
* @return bool
@@ -1217,9 +1264,13 @@ final class Binkp extends BaseProtocol
}
}
if ($this->optionGet(self::O_PWD))
if ($this->optionGet(self::O_PWD)) {
Log::info(sprintf('%s:- SECURE',self::LOGKEY));
// @todo Since we have connected, if the node was marked down/hold reset that
// Notification::route('netmail',$ao->system->aka_unknown()->first()->withoutRelations())->notify(new NodeMarkedDownNetmail($ao->withoutRelations()));
}
return $this->binkp_hsdone();
}
@@ -1315,6 +1366,9 @@ final class Binkp extends BaseProtocol
if ($this->node->aka_authed) {
$this->msgs(self::BPM_OK,sprintf('%ssecure',$have_pwd ? '' : 'non-'));
// @todo Since we have connected, if the node was marked down/hold reset that
// Notification::route('netmail',$ao->system->aka_unknown()->first()->withoutRelations())->notify(new NodeMarkedDownNetmail($ao->withoutRelations()));
} else {
$this->msgs(self::OK,'non-secure');
}
@@ -1341,7 +1395,8 @@ final class Binkp extends BaseProtocol
return self::S_FAILURE;
$this->force_queue = $force_queue;
$this->binkp_hs();
if (! $this->binkp_hs())
return self::S_FAILURE;
while (TRUE) {
if ((! $this->sessionGet(self::SE_INIT))
@@ -1496,12 +1551,7 @@ final class Binkp extends BaseProtocol
Log::info(sprintf('%s:- We have authed these AKAs [%s]',self::LOGKEY,$node->aka_remote_authed->pluck('ftn')->join(',')));
foreach ($node->aka_remote_authed as $ao) {
Log::debug(sprintf('%s:- Checking for any new mail and files to [%s]',self::LOGKEY,$ao->ftn));
if (! $ao->validated) {
Log::alert(sprintf('%s:! Address [%s] is not validated, so we wont bundle mail for it',self::LOGKEY,$ao->ftn));
continue;
}
Log::info(sprintf('%s:- Checking for any new mail and files to [%s]',self::LOGKEY,$ao->ftn));
$this->send->mail($ao);
$this->send->files($ao);
@@ -1518,33 +1568,14 @@ final class Binkp extends BaseProtocol
*/
}
Log::info(sprintf('%s:- We have [%d] items to send to [%s]',self::LOGKEY,$this->send->togo_count,$ao->system->name));
Log::info(sprintf('%s:- We have [%d] items to send to [%s]',self::LOGKEY,$this->send->togo_count,$ao->ftn));
} else {
// @todo We should only send netmail if unauthenticated - netmail that is direct to this node (no routing)
Log::debug(sprintf('%s:- Not AUTHed so not looking for mail, but we know these akas [%s]',self::LOGKEY,$node->aka_remote->pluck('ftn')->join(',')));
Log::alert(sprintf('%s:- Not AUTHed so not looking for mail, but we know these akas [%s]',self::LOGKEY,$node->aka_remote->pluck('ftn')->join(',')));
}
}
/**
* Strip blanks at the beginning of a string
*
* @param string $str
* @return string
* @throws \Exception
* @deprecated - use ltrim instead
*/
private function skip_blanks(string $str): string
{
$c = 0;
if ($str != NULL)
while ($this->isSpace(substr($str,$c,1)))
$c++;
return substr($str,$c);
}
/**
* Return the string delimited by char and shorten the input to the remaining characters
*
@@ -1565,20 +1596,4 @@ final class Binkp extends BaseProtocol
return $return;
}
/**
* Check if the string is a space
*
* @param string $str
* @return bool
* @throws \Exception
* @deprecated No longer required since we are using ltrim
*/
private function isSpace(string $str):bool
{
if (strlen($str) > 1)
throw new \Exception('String is more than 1 char');
return $str && in_array($str,[' ',"\n","\r","\v","\f","\t"]);
}
}

View File

@@ -6,7 +6,6 @@ use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use App\Classes\Protocol as BaseProtocol;
use App\Classes\Sock\SocketClient;
use App\Models\{Address,Domain,Mailer};
/**
@@ -64,22 +63,6 @@ final class DNS extends BaseProtocol
public const DNS_TYPE_OPT = 41; // OPT Records
public const DNS_TYPE_DS = 43; // DS Records (Delegation signer RFC 4034)
public function onConnect(SocketClient $client): ?int
{
// If our parent returns a PID, we've forked
if (! parent::onConnect($client)) {
Log::withContext(['pid'=>getmypid()]);
$this->client = $client;
$this->protocol_session();
Log::info(sprintf('%s:= onConnect - Connection closed [%s]',self::LOGKEY,$client->address_remote));
exit(0);
}
return NULL;
}
protected function protocol_init(): int
{
// N/A
@@ -110,7 +93,7 @@ final class DNS extends BaseProtocol
$this->query = new BaseProtocol\DNS\Query($this->client->read(0,512));
} catch (\Exception $e) {
Log::error(sprintf('%s:! Ignoring bad DNS query (%s)',self::LOGKEY,$e->getMessage()));
Log::notice(sprintf('%s:! Ignoring bad DNS query (%s)',self::LOGKEY,$e->getMessage()));
return FALSE;
}
@@ -119,7 +102,7 @@ final class DNS extends BaseProtocol
// If the wrong class
if ($this->query->class !== self::DNS_QUERY_IN) {
Log::error(sprintf('%s:! We only service Internet queries [%d]',self::LOGKEY,$this->query->class));
Log::notice(sprintf('%s:! We only service Internet queries [%d]',self::LOGKEY,$this->query->class));
return $this->reply(self::DNS_NOTIMPLEMENTED,[],$this->soa());
}
@@ -167,7 +150,7 @@ final class DNS extends BaseProtocol
case self::DNS_TYPE_AAAA:
case self::DNS_TYPE_SRV:
case self::DNS_TYPE_TXT:
Log::info(sprintf('%s:= Looking for record [%s] for [%s]',self::LOGKEY,$this->query->type,$this->query->domain));
Log::debug(sprintf('%s:= Looking for record [%s] for [%s]',self::LOGKEY,$this->query->type,$this->query->domain));
$labels = clone($this->query->labels);
$mailer = '';
@@ -179,11 +162,11 @@ final class DNS extends BaseProtocol
switch ($labels->first()) {
case '_binkp':
$mailer = Mailer::where('name','BINKP')->singleOrFail();
$mailer = Mailer::where('name','BINKP')->sole();
break;
case '_ifcico':
$mailer = Mailer::where('name','EMSI')->singleOrFail();
$mailer = Mailer::where('name','EMSI')->sole();
break;
default:
@@ -232,14 +215,15 @@ final class DNS extends BaseProtocol
// Check we have the right record
if ((! $ao) || (($rootdn !== self::TLD) && ((! $ao->zone->domain->dnsdomain) || ($ao->zone->domain->dnsdomain !== $rootdn)))) {
Log::alert(sprintf('%s:= No DNS record for [%d:%d/%d.%d@%s]',self::LOGKEY,$z,$n,$f,$p,$d));
return $this->nameerr();
}
switch ($this->query->type) {
case self::DNS_TYPE_SRV:
Log::info(sprintf('%s:= Returning [%s] for DNS query [%s]',self::LOGKEY,$ao->system->address,$ao->ftn));
if (($ao->system->address) && ($xx=$ao->system->mailers->where('id',$mailer->id)->pop())) {
Log::info(sprintf('%s:= Returning [%s] for DNS query [%s]',self::LOGKEY,$ao->system->address,$ao->ftn));
return $this->reply(
self::DNS_NOERROR,
[serialize([
@@ -250,6 +234,8 @@ final class DNS extends BaseProtocol
]) => self::DNS_TYPE_SRV]);
} else {
Log::alert(sprintf('%s:! No/incomplete hostname/port details for [%d] for DNS query [%s]',self::LOGKEY,$ao->system->id,$ao->ftn));
return $this->nodata();
}
@@ -261,7 +247,7 @@ final class DNS extends BaseProtocol
[serialize($ao->system->name) => self::DNS_TYPE_TXT]);
default:
Log::info(sprintf('%s:= Returning [%s] for DNS query [%s]',self::LOGKEY,$ao->system->address,$ao->ftn));
Log::info(sprintf('%s:= Returning [%s] for DNS query [%s]',self::LOGKEY,$ao->system->address ?: 'NO ADDRESS',$ao->ftn));
return (! $ao->system->address)
? $this->nodata()
@@ -272,7 +258,7 @@ final class DNS extends BaseProtocol
// Other attributes return NOTIMPL
default:
Log::error(sprintf('%s:! We dont support DNS query types [%d]',self::LOGKEY,$this->query->type));
Log::notice(sprintf('%s:! We dont support DNS query types [%d]',self::LOGKEY,$this->query->type));
return $this->reply(self::DNS_NOTIMPLEMENTED,[],$this->soa());
}
@@ -309,14 +295,14 @@ final class DNS extends BaseProtocol
private function nameerr(): int
{
Log::error(sprintf('%s:! DNS query for a resource we dont manage [%s]',self::LOGKEY,$this->query->domain));
Log::notice(sprintf('%s:! DNS query for a resource we dont manage [%s]',self::LOGKEY,$this->query->domain));
return $this->reply(self::DNS_NAMEERR,[],$this->soa());
}
private function nodata(): int
{
Log::error(sprintf('%s:! DNS query for a resource we dont manage [%s] in our zone(s)',self::LOGKEY,$this->query->domain));
Log::notice(sprintf('%s:! DNS query for a resource we dont manage [%s] in our zone(s)',self::LOGKEY,$this->query->domain));
return $this->reply(self::DNS_NOERROR,[],$this->soa());
}

View File

@@ -6,12 +6,11 @@ use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use App\Classes\Protocol as BaseProtocol;
use App\Classes\Sock\SocketClient;
use App\Classes\Sock\SocketException;
use App\Classes\Sock\Exception\SocketException;
use App\Exceptions\InvalidFTNException;
use App\Models\{Address,Mailer,Setup};
use App\Interfaces\CRC as CRCInterface;
use App\Interfaces\Zmodem as ZmodemInterface;
use App\Models\{Address,Setup};
use App\Traits\CRC as CRCTrait;
// http://ftsc.org/docs/fsc-0056.001
@@ -82,27 +81,6 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface
'1'=>self::P_ZMODEM,
];
/**
* Incoming EMSI session
*
* @param SocketClient $client
* @return int|null
* @throws SocketException
*/
public function onConnect(SocketClient $client): ?int
{
// If our parent returns a PID, we've forked
if (! parent::onConnect($client)) {
Log::withContext(['pid'=>getmypid()]);
$this->session(Mailer::where('name','EMSI')->singleOrFail(),$client,(new Address));
$this->client->close();
exit(0);
}
return NULL;
}
/**
* Send our welcome banner
*
@@ -207,8 +185,8 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface
// Mailer Details
$makedata .= sprintf('{%s}{%s}{%s}{%s}',
Setup::product_id(),
config('app.name'),
$this->setup->version,
Setup::PRODUCT_NAME_SHORT,
Setup::version(),
'#000000' // Serial Numbers
);
@@ -930,16 +908,135 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface
$this->client->rx_purge();
$this->client->tx_purge();
if ($this->down) {
Log::info(sprintf('%s:! System down for maintenance',self::LOGKEY));
$this->client->buffer_add(self::EMSI_NAK.'Sorry down for maintenance, call back again after a few minutes'.self::CR.self::CR);
$this->client->buffer_flush(5);
return -1;
}
$this->emsi_banner();
$t1 = $this->client->timer_set(self::EMSI_HSTIMEOUT);
$t2 = $this->client->timer_set(self::EMSI_RESEND_TO);
$c = 0;
while (! $this->client->timer_expired($t1)) {
$ch = $this->client->read_ch(max( 1,min($this->client->timer_rest($t1),$this->client->timer_rest($t2))));
try {
$ch = $this->client->read_ch(max( 1,min($this->client->timer_rest($t1),$this->client->timer_rest($t2))));
} catch (SocketException $e) {
if ($c++ > 2)
return self::TIMEOUT;
else
$ch = -2;
}
if (static::DEBUG)
Log::debug(sprintf('%s:- Got [%x] (%c)',self::LOGKEY,$ch,$ch));
// Look for Telnet IAC, if binary mode we'll need to handle IAC IAC => IAC
if ($ch === 0xff) {
Log::info(sprintf('%s:- TELNET IAC',self::LOGKEY));
$iaccmd = NULL;
// Peek for the next chars
do {
try {
$iac = $this->client->read(1,1,MSG_PEEK);
if (static::DEBUG)
Log::debug(sprintf('%s: - IAC LOOP',self::LOGKEY),['iac'=>ord($iac),'cmd'=>$iaccmd]);
switch (ord($iac)) {
// Binary Mode
case 0x00:
if ($iaccmd === 0xfb) {
Log::debug(sprintf('%s: - IAC WILL BINARY [%02x]',self::LOGKEY,ord($iac)));
// Config with DO
$this->client->send(chr(0xff).chr(0xfd).$iac,10);
} elseif ($iaccmd === 0xfd) {
Log::debug(sprintf('%s: - IAC DO BINARY [%02x]',self::LOGKEY,ord($iac)));
// Config with WILL
if (! $this->client->iac_bin) {
$this->client->send(chr(0xff).chr(0xfb).$iac,10);
$this->client->iac_bin = true;
}
}
$iaccmd = NULL;
break;
// Suppress Go Ahead
case 0x03:
if ($iaccmd === 0xfb) {
Log::debug(sprintf('%s: - IAC WILL SUPPRESS-GO-AHEAD [%02x]',self::LOGKEY,ord($iac)));
// Config with DO
$this->client->send(chr(0xff).chr(0xfd).$iac,10);
} elseif ($iaccmd === 0xfd) {
Log::debug(sprintf('%s: - IAC DO SUPPRESS-GO-AHEAD [%02x]',self::LOGKEY,ord($iac)));
// Config with WILL
$this->client->send(chr(0xff).chr(0xfb).$iac,10);
}
$iaccmd = NULL;
break;
// Will
case 0xfb:
if (static::DEBUG)
Log::debug(sprintf('%s: - IAC WILL [%02x]',self::LOGKEY,ord($iac)));
$iaccmd = ord($iac);
break;
// Do
case 0xfd:
if (static::DEBUG)
Log::debug(sprintf('%s: - IAC DO [%02x]',self::LOGKEY,ord($iac)));
$iaccmd = ord($iac);
break;
// IAC
case 0xff:
if (static::DEBUG)
Log::debug(sprintf('%s: - IAC [%02x]',self::LOGKEY,ord($iac)));
$iaccmd = ord($iac);
break;
default:
Log::alert(sprintf('%s: - IAC Unhandled [%02x]',self::LOGKEY,ord($iac)),['iac'=>$iac,'iaccmd'=>$iaccmd,'ch'=>ord($iac)]);
$ch = ord($iac);
$iac = NULL;
}
if ($iaccmd) {
$iac = ord($this->client->read_ch(10));
$ch = NULL;
} elseif (is_null($ch)) {
$ch = ord($this->client->read_ch(10));
}
} catch (SocketException $e) {
Log::debug(sprintf('%s:! SocketException: %s',self::LOGKEY,$e->getMessage()),['class'=>get_class($e),'code'=>$e->getCode()]);
$iac = NULL;
}
} while (! is_null($iac));
Log::debug(sprintf('%s:- Leaving IAC with [%02x]',self::LOGKEY,$ch),['ch'=>serialize($ch)]);
}
if (($ch != self::TIMEOUT) && ($ch < 0))
return $ch;
@@ -1053,6 +1150,9 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface
return (self::S_REDIAL|self::S_ADDTRY);
}
// @todo Since we have connected, if the node was marked down/hold reset that
// Notification::route('netmail',$ao->system->aka_unknown()->first()->withoutRelations())->notify(new NodeMarkedDownNetmail($ao->withoutRelations()));
// @todo Lock Node AKAs
Log::info(sprintf('%s:- We have [%lu%s] mail, [%lu%s] files',self::LOGKEY,$this->send->mail_size,'b',$this->send->files_size,'b'));
@@ -1204,7 +1304,7 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface
Log::debug(sprintf('%s:+ Start WAZOO Receive',self::LOGKEY));
// @todo If the node is not defined in the DB node->address is NULL. Need to figure out how to handle those nodes.
$rc = (new Zmodem)->zmodem_receive($this->client,$zap,$this->recv,$this->node->address,$this->force_queue);
$rc = (new Zmodem($this->setup))->zmodem_receive($this->client,$zap,$this->recv,$this->node->address,$this->force_queue);
return ($rc === self::RCDO || $rc === self::ERROR);
}
@@ -1226,14 +1326,9 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface
// Add our mail to the queue if we have authenticated
if ($this->node->aka_authed)
foreach ($this->node->aka_remote_authed as $ao) {
if (! $ao->validated) {
Log::alert(sprintf('%s:! Address [%s] is not validated, so we wont bundle mail for it',self::LOGKEY,$ao->ftn));
continue;
}
// Send mail
while ($this->send->mail($ao)) {
$z = new Zmodem;
$z = new Zmodem($this->setup);
if (! $z->zmodem_sendinit($this->client,$zap) && $this->send->togo_count)
$z->zmodem_sendfile($this->send,$this->node);
@@ -1241,7 +1336,7 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface
// Send files
while ($this->send->files($ao)) {
$z = new Zmodem;
$z = new Zmodem($this->setup);
if (! $z->zmodem_sendinit($this->client,$zap) && $this->send->togo_count)
$z->zmodem_sendfile($this->send,$this->node);

View File

@@ -5,12 +5,12 @@ namespace App\Classes\Protocol;
use Illuminate\Support\Facades\Log;
use App\Classes\{Node,Protocol};
use App\Classes\Protocol\Zmodem as ZmodemClass;
use App\Classes\File\{Receive,Send};
use App\Classes\Sock\{SocketClient,SocketException};
use App\Classes\Sock\Exception\SocketException;
use App\Classes\Sock\SocketClient;
use App\Interfaces\CRC as CRCInterface;
use App\Interfaces\Zmodem as ZmodemInterface;
use App\Models\{Address,Mailer};
use App\Models\Address;
use App\Traits\CRC as CRCTrait;
/**
@@ -202,27 +202,6 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
private string $rxbuf = '';
private string $txbuf = '';
/**
* @param SocketClient $client
* @return null
* @throws SocketException
*/
public function onConnect(SocketClient $client): ?int
{
// If our parent returns a PID, we've forked
if (! parent::onConnect($client)) {
Log::withContext(['pid'=>getmypid()]);
$this->session(Mailer::where('name','ZMODEM')->singleOrFail(),$client);
$this->client->close();
Log::info(sprintf('%s:= onConnect - Connection closed [%s]',self::LOGKEY,$client->address_remote));
exit(0);
}
return NULL;
}
/**
* Initialise our session
*/
@@ -510,7 +489,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
* @param Send $send
* @return int
*/
public function zmodem_sendfile(Send $send,Node $node): int
public function zmodem_sendfile(Send $send,Node $node): void
{
Log::debug(sprintf('%s:+ zmodem_sendfile',self::LOGKEY));
@@ -534,14 +513,16 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
break;
}
return $rc;
return;
} catch (\Exception $e) {
Log::error(sprintf('%s:! Error [%s]',self::LOGKEY,$e->getMessage()));
Log::error(sprintf('%s:! Error [%s]',self::LOGKEY,$e->getMessage()),['rc'=>$rc ?? '-UNDEFINED-']);
return;
}
}
return self::OK;
return;
}
/**
@@ -1166,7 +1147,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
private function ls_zrecvdata32(string &$data,int &$len,int $timeout): int
{
if (static::DEBUG)
Log::debug(sprintf('%s:+ ls_zrecvdata32',self::LOGKEY),['d'=>$data]);
Log::debug(sprintf('%s:+ ls_zrecvdata32',self::LOGKEY),['d'=>$data,'len'=>$len,'timeout'=>$timeout]);
$got = 0; /* Bytes total got */
$crc = 0; /* Received CRC */
@@ -1184,6 +1165,9 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
return self::LSZ_BADCRC;
} else {
if (static::DEBUG)
Log::debug(sprintf('%s:- ls_zrecvdata32 c>32 [%x] (%c)',self::LOGKEY,$c,($c<31 ? 32 : $c)),['c'=>serialize($c)]);
switch ($c) {
case self::LSZ_CRCE:
case self::LSZ_CRCG:
@@ -1296,6 +1280,8 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
break;
case self::LSZ_BADCRC:
$this->rxbuf = '';
case self::TIMEOUT:
if ($this->ls_rxAttnStr) {
$this->client->buffer_add($this->ls_rxAttnStr);
@@ -1324,6 +1310,9 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
$needzdata = 1;
}
if (static::DEBUG)
Log::debug(sprintf('%s:- ls_zrecvfile RC [%s]',self::LOGKEY,$rc),['needzdata'=>$needzdata]);
/* We need new position -- ZDATA (and may be ZEOF) */
} else {
Log::debug(sprintf('%s:- ls_zrecvfile Want ZDATA/ZEOF at [%d]',self::LOGKEY,$rxpos));
@@ -1354,7 +1343,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
return self::OK;
}
Log::debug(sprintf('%s:- ls_zrecvfile ZDATA',self::LOGKEY));
Log::debug(sprintf('%s:- ls_zrecvfile ZDATA',self::LOGKEY),['newpos'=>$newpos]);
$needzdata = 0;
}
}
@@ -1947,6 +1936,9 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
$this->ls_zsendhhdr(self::ZNAK,$this->ls_storelong(0));
}
// sleep between tries
sleep(5);
} while (++$trys < 10);
Log::error(sprintf('%s:? ls_zrecvnewpos Something strange or timeout [%d]',self::LOGKEY,$rc));

View File

@@ -0,0 +1,6 @@
<?php
namespace App\Classes\Sock\Exception;
final class HAproxyException extends \Exception {
}

View File

@@ -1,6 +1,6 @@
<?php
namespace App\Classes\Sock;
namespace App\Classes\Sock\Exception;
// @todo Can we change this to use socket_strerr() && socket_last_error()
final class SocketException extends \Exception {
@@ -11,6 +11,7 @@ final class SocketException extends \Exception {
public const CANT_CONNECT = 5;
public const SOCKET_ERROR = 6;
public const SOCKET_EAGAIN = 11;
public const SOCKET_TIMEOUT = 15;
public const SOCKET_READ = 22;
public const CONNECTION_RESET = 104;
@@ -22,6 +23,7 @@ final class SocketException extends \Exception {
self::CANT_CONNECT => 'Can\'t connect: "%s"',
self::SOCKET_ERROR => 'Socket Error: "%s"',
self::SOCKET_EAGAIN => 'Socket Resource Temporarily Unavailable - Try again',
self::SOCKET_TIMEOUT => 'Timeout reached "%d"',
self::SOCKET_READ => 'Unable to read from socket',
self::CONNECTION_RESET => 'Connection reset by peer',
];

View File

@@ -6,6 +6,8 @@ use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use App\Classes\Sock\Exception\{HAproxyException,SocketException};
/**
* Class SocketClient
*
@@ -46,7 +48,8 @@ final class SocketClient {
/** @var string Data in the RX buffer */
private string $rx_buf = '';
public function __construct (\Socket $connection) {
public function __construct (\Socket $connection,bool $originate=FALSE)
{
$this->connection = $connection;
if ($this->type === SOCK_STREAM) {
@@ -54,133 +57,163 @@ final class SocketClient {
socket_getpeername($connection,$this->address_remote,$this->port_remote);
// If HAPROXY is used, work get the clients address
if (config('fido.haproxy')) {
if ((! $originate) && config('fido.haproxy')) {
Log::debug(sprintf('%s:+ HAPROXY connection host [%s] on port [%d] (%s)',self::LOGKEY,$this->address_remote,$this->port_remote,$this->type));
if ($this->read(5,12) !== "\x0d\x0a\x0d\x0a\x00\x0d\x0aQUIT\x0a") {
Log::error(sprintf('%s:! Failed to initialise HAPROXY connection',self::LOGKEY));
throw new SocketException(SocketException::CANT_CONNECT,'Failed to initialise HAPROXY connection');
}
if (($x=$this->read(5,6)) === 'PROXY ')
$vers = 1;
// Version/Command
$vc = $this->read_ch(5);
elseif (($x === "\x0d\x0a\x0d\x0a\x00\x0d") && ($this->read(5,6) === "\x0aQUIT\x0a"))
$vers = 2;
if (($x=($vc>>4)&0x7) !== 2) {
Log::error(sprintf('%s:! HAPROXY version [%d] is not handled',self::LOGKEY,$x));
else
throw new HAproxyException('Failed to initialise HAPROXY connection');
throw new SocketException(SocketException::CANT_CONNECT,'Unknown HAPROXY version');
}
switch ($x=($vc&0x7)) {
// HAPROXY internal
case 0:
Log::debug(sprintf('%s:! HAPROXY internal health-check',self::LOGKEY));
throw new SocketException(SocketException::CANT_CONNECT,'Healthcheck');
// PROXY connection
switch ($vers) {
case 1:
// Protocol/Address Family
switch ($x=$this->read(5,5)) {
case 'TCP4 ':
$p = 4;
break;
case 'TCP6 ':
$p = 6;
break;
default:
throw new HAproxyException(sprintf('HAPROXY protocol [%d] is not handled',$x));
}
$read = $this->read(5,104-11);
// IPv4
if (($p === 4) || ($p === 6)) {
$parse = collect(sscanf($read,'%s %s %s %s'));
$src = Arr::get($parse,0);
$dst = Arr::get($parse,1);
$src_port = (int)Arr::get($parse,2);
$dst_port = (int)Arr::get($parse,3);
$len = $parse->map(fn($item)=>strlen($item))->sum()+3;
// The last 2 chars should be "\r\n"
if (($x=substr($read,$len)) !== "\r\n")
throw new HAproxyException(sprintf('HAPROXY parsing failed for version [%d] [%s] (%s)',$p,$read,hex_dump($x)));
} else {
throw new HAproxyException(sprintf('HAPROXY version [%d] is not handled [%s]',$p,$read));
}
$this->port_remote = $src_port;
break;
case 2:
// Version/Command
$vc = $this->read_ch(5);
if (($x=($vc>>4)&0x7) !== 2)
throw new HAproxyException(sprintf('Unknown HAPROXY version [%d]',$x));
switch ($x=($vc&0x7)) {
// HAPROXY internal
case 0:
throw new HAproxyException('HAPROXY internal health-check');
// PROXY connection
case 1:
break;
default:
throw new HAproxyException(sprintf('HAPROXY command [%d] is not handled',$x));
}
// Protocol/Address Family
$pa = $this->read_ch(5);
switch ($x=($pa>>4)&0x7) {
case 1: // AF_INET
$p = 4;
break;
case 2: // AF_INET6
$p = 6;
break;
}
switch ($x=($pa&0x7)) {
case 1: // STREAM
break;
default:
throw new HAproxyException(sprintf('HAPROXY address family [%d] is not handled',$x));
}
$len = Arr::get(unpack('n',$this->read(5,2)),1);
// IPv4
if (($p === 4) && ($len === 12)) {
$src = inet_ntop($this->read(5,4));
$dst = inet_ntop($this->read(5,4));
} elseif (($p === 6) && ($len === 36)) {
$src = inet_ntop($this->read(5,16));
$dst = inet_ntop($this->read(5,16));
} else {
throw new HAproxyException(sprintf('HAPROXY address len [%d:%d] is not handled',$p,$len));
}
$src_port = unpack('n',$this->read(5,2));
$dst_port = Arr::get(unpack('n',$this->read(5,2)),1);
$this->port_remote = Arr::get($src_port,1);
break;
default:
Log::error(sprintf('%s:! HAPROXY command [%d] is not handled',self::LOGKEY,$x));
throw new SocketException(SocketException::CANT_CONNECT,'Unknown HAPROXY command');
throw new HAproxyException('Failed to initialise HAPROXY connection');
}
// Protocol/Address Family
$pa = $this->read_ch(5);
$p = NULL;
switch ($x=($pa>>4)&0x7) {
case 1: // AF_INET
$p = 4;
break;
case 2: // AF_INET6
$p = 6;
break;
default:
Log::error(sprintf('%s:! HAPROXY protocol [%d] is not handled',self::LOGKEY,$x));
throw new SocketException(SocketException::CANT_CONNECT,'Unknown HAPROXY protocol');
}
switch ($x=($pa&0x7)) {
case 1: // STREAM
break;
default:
Log::error(sprintf('%s:! HAPROXY address family [%d] is not handled',self::LOGKEY,$x));
throw new SocketException(SocketException::CANT_CONNECT,'Unknown HAPROXY address family');
}
$len = Arr::get(unpack('n',$this->read(5,2)),1);
// IPv4
if (($p === 4) && ($len === 12)) {
$src = inet_ntop($this->read(5,4));
$dst = inet_ntop($this->read(5,4));
} elseif (($p === 6) && ($len === 36)) {
$src = inet_ntop($this->read(5,16));
$dst = inet_ntop($this->read(5,16));
} else {
Log::error(sprintf('%s:! HAPROXY address len [%d:%d] is not handled',self::LOGKEY,$p,$len));
throw new SocketException(SocketException::CANT_CONNECT,'Unknown HAPROXY address length');
}
$src_port = unpack('n',$this->read(5,2));
$dst_port = unpack('n',$this->read(5,2));
$this->address_remote = $src;
$this->port_remote = Arr::get($src_port,1);
Log::info(sprintf('%s:! HAPROXY src [%s:%d] dst [%s:%d]',
Log::debug(sprintf('%s:- HAPROXY src [%s:%d] dst [%s:%d]',
self::LOGKEY,
$this->address_remote,
$this->port_remote,
$dst,
Arr::get($dst_port,1),
$dst_port,
));
}
Log::info(sprintf('%s:+ Connection host [%s] on port [%d] (%s)',self::LOGKEY,$this->address_remote,$this->port_remote,$this->type));
Log::debug(sprintf('%s:+ Connection host [%s] on port [%d] (%s)',self::LOGKEY,$this->address_remote,$this->port_remote,$this->type));
}
}
public function __get($key) {
switch ($key) {
case 'address_remote':
case 'port_remote':
return $this->{$key};
case 'cps':
case 'speed':
return Arr::get($this->session,$key);
case 'rx_free':
return self::RX_BUF_SIZE-$this->rx_left;
case 'rx_left':
return strlen($this->rx_buf);
case 'tx_free':
return self::TX_BUF_SIZE-strlen($this->tx_buf);
case 'type':
return socket_get_option($this->connection,SOL_SOCKET,SO_TYPE);
default:
throw new \Exception(sprintf('%s:! Unknown key [%s]:',self::LOGKEY,$key));
}
public function __get(string $key): mixed
{
return match ($key) {
'address_remote', 'port_remote' => $this->{$key},
'cps', 'speed' => Arr::get($this->session,$key),
'iac_bin' => Arr::get($this->session,$key),
'rx_free' => self::RX_BUF_SIZE-$this->rx_left,
'rx_left' => strlen($this->rx_buf),
'tx_free' => self::TX_BUF_SIZE-strlen($this->tx_buf),
'type' => socket_get_option($this->connection,SOL_SOCKET,SO_TYPE),
default => throw new \Exception(sprintf('%s:! Unknown key [%s]:',self::LOGKEY, $key)),
};
}
public function __set($key,$value) {
public function __set(string $key,mixed $value): void
{
switch ($key) {
case 'cps':
case 'speed':
return $this->session[$key] = $value;
case 'iac_bin':
$this->session[$key] = $value;
break;
default:
throw new \Exception(sprintf('%s:! Unknown key [%s]:',self::LOGKEY,$key));
@@ -193,13 +226,14 @@ final class SocketClient {
* @param string $address
* @param int $port
* @return static
* @throws SocketException
* @throws SocketException|HAproxyException
*/
public static function create(string $address,int $port): self
{
Log::info(sprintf('%s:+ Creating connection to [%s:%d]',self::LOGKEY,$address,$port));
$sort = collect(['AAAA','A']);
$type = collect(config('fido.ip'))
->filter(fn($item)=>$item['enabled']);
if (filter_var($address,FILTER_VALIDATE_IP))
$resolved = collect([[
@@ -208,14 +242,15 @@ final class SocketClient {
]]);
else
// We only look at AAAA/A records
$resolved = collect(dns_get_record($address,DNS_AAAA|DNS_A))
->filter(function($item) use ($sort) { return $sort->search(Arr::get($item,'type')) !== FALSE; })
->sort(function($item) use ($sort) { return $sort->search(Arr::get($item,'type')); });
$resolved = collect(dns_get_record($address,$type->map(fn($item)=>$item['type'])->sum()))
->filter(fn($item)=>$type->has(Arr::get($item,'type')))
->sort(fn($a,$b)=>$type->get(Arr::get($a,'type'))['order'] < $type->get(Arr::get($b,'type'))['order']);
if (! $resolved->count())
throw new SocketException(SocketException::CANT_CONNECT,sprintf('%s doesnt resolved to an IPv4/IPv6 address',$address));
$result = FALSE;
$socket = NULL;
foreach ($resolved as $address) {
try {
@@ -245,7 +280,7 @@ final class SocketClient {
if ($result === FALSE)
throw new SocketException(SocketException::CANT_CONNECT,socket_strerror(socket_last_error($socket)));
return new self($socket);
return new self($socket,TRUE);
}
/**
@@ -306,7 +341,7 @@ final class SocketClient {
while (strlen($this->tx_buf)) {
$tv = $this->timer_rest($tm);
if (($rc=$this->canSend($tv)) > 0) {
if ($rc=$this->canSend($tv)) {
if (self::DEBUG)
Log::debug(sprintf('%s:- Chars to send [%d]',self::LOGKEY,strlen($this->tx_buf)));
@@ -334,14 +369,14 @@ final class SocketClient {
/**
* @param int $timeout
* @return int
* @return bool
* @throws \Exception
*/
public function canSend(int $timeout): int
public function canSend(int $timeout): bool
{
$write = [$this->connection];
return $this->socketSelect(NULL,$write,NULL,$timeout);
return $this->socketSelect(NULL,$write,NULL,$timeout) > 0;
}
/**
@@ -361,21 +396,21 @@ final class SocketClient {
Log::error(sprintf('%s:! Closing socket [%s]',self::LOGKEY,$e->getMessage()));
}
Log::info(sprintf('%s:= Connection closed with [%s]',self::LOGKEY,$this->address_remote));
Log::debug(sprintf('%s:= Connection closed with [%s]',self::LOGKEY,$this->address_remote));
}
/**
* We have data in the buffer or on the socket
*
* @param int $timeout
* @return int
* @return bool
* @throws \Exception
*/
public function hasData(int $timeout): int
public function hasData(int $timeout): bool
{
$read = [$this->connection];
return $this->rx_left ?: $this->socketSelect($read,NULL,NULL,$timeout);
return ($this->rx_left ?: $this->socketSelect($read,NULL,NULL,$timeout)) > 0;
}
/**
@@ -383,10 +418,11 @@ final class SocketClient {
*
* @param int $timeout How long to wait for data
* @param int $len The amount of data we want
* @param int $flags
* @return string|null
* @throws SocketException
*/
public function read(int $timeout,int $len=1024): ?string
public function read(int $timeout,int $len=1024,int $flags=MSG_DONTWAIT): ?string
{
// We have data in our buffer
if ($this->rx_left >= $len) {
@@ -394,24 +430,58 @@ final class SocketClient {
Log::debug(sprintf('%s:- Returning [%d] chars from the RX buffer',self::LOGKEY,$len));
$result = substr($this->rx_buf,0,$len);
$this->rx_buf = substr($this->rx_buf,strlen($result));
if ($flags !== MSG_PEEK)
$this->rx_buf = substr($this->rx_buf,strlen($result));
return $result;
// In case we are in Telnet Binary Mode
if ($this->iac_bin) {
if (self::DEBUG)
Log::debug(sprintf('%s:- Telnet IAC Binary Mode, looking for ff ff',self::LOGKEY),['result'=>hex_dump($result)]);
// if the last char is ff, we need to get the next char
if (str_ends_with($result,"\xff")) {
if (self::DEBUG)
Log::debug(sprintf('%s: - We have a hit',self::LOGKEY));
// If we have it in our buffer, just get it
if ($this->rx_left) {
$result .= substr($this->rx_buf,0,1);
$this->rx_buf = substr($this->rx_buf,1);
// Else put everything back into rx_buf, and increase len by 1
} else {
$this->rx_buf = $result;
$len++;
$result = '';
}
}
if (strlen($result) > 1)
$result = str_replace("\xff\xff","\xff",$result);
if (strlen($result))
return $result;
} else
return $result;
}
if ($timeout AND ($this->hasData($timeout) === 0))
return NULL;
if (self::DEBUG)
Log::debug(sprintf('%s:- Buffer doesnt have [%d] chars, it only has [%d], or it ends with 0xff',self::LOGKEY,$len,strlen($this->rx_buf)),['rx_buf'=>hex_dump($this->rx_buf)]);
if ($timeout && (! $this->hasData($timeout)))
throw new SocketException(SocketException::SOCKET_TIMEOUT,$timeout);
$buf = '';
try {
switch ($this->type) {
case SOCK_STREAM:
$recv = socket_recv($this->connection,$buf,self::RX_SIZE,MSG_DONTWAIT);
$recv = socket_recv($this->connection,$buf,self::RX_SIZE,$flags);
break;
case SOCK_DGRAM:
$recv = socket_recvfrom($this->connection,$buf,self::RX_SIZE,MSG_DONTWAIT,$this->address_remote,$this->port_remote);
$recv = socket_recvfrom($this->connection,$buf,self::RX_SIZE,$flags,$this->address_remote,$this->port_remote);
break;
default:
@@ -452,13 +522,19 @@ final class SocketClient {
}
}
if ($flags === MSG_PEEK) {
Log::debug(sprintf('%s:- Returning [%d] chars as a result of a PEEK operation, buffer would have [%d], but still has [%d]',self::LOGKEY,$len,strlen($this->rx_buf.$buf),strlen($this->rx_buf)),['rx_buf'=>hex_dump($this->rx_buf),'buf'=>hex_dump($buf)]);
return substr($this->rx_buf.$buf,0,$len);
}
$this->rx_buf .= $buf;
if (self::DEBUG)
Log::debug(sprintf('%s:- Added [%d] chars to the RX buffer',self::LOGKEY,strlen($buf)),['rx_buf'=>hex_dump($this->rx_buf)]);
// Loop again and return the data, now that it is in the RX buffer
return $this->read($timeout,$len);
return $this->read($timeout,$len,$flags);
}
/**
@@ -471,12 +547,14 @@ final class SocketClient {
*/
public function read_ch(int $timeout): int
{
if ($this->hasData($timeout) > 0) {
if ($this->hasData($timeout))
$ch = $this->read($timeout,1);
} else {
return self::TIMEOUT;
}
else
throw new SocketException(SocketException::SOCKET_TIMEOUT,$timeout);
if (self::DEBUG)
Log::debug(sprintf('%s:+ read_ch [%c] (%x)',self::LOGKEY,$ch,ord($ch)));
return ord($ch);
}
@@ -505,17 +583,22 @@ final class SocketClient {
*
* @param string $message
* @param int $timeout
* @return int|false
* @return int|bool
* @throws \Exception
*/
public function send(string $message,int $timeout): int|false
public function send(string $message,int $timeout): int|bool
{
if ($timeout AND (! $rc=$this->canSend($timeout)))
if ($timeout && (! $rc=$this->canSend($timeout)))
return $rc;
if (self::DEBUG)
Log::debug(sprintf('%s:- Sending [%d] chars [%s]',self::LOGKEY,strlen($message),Str::limit($message,15)));
if ($this->iac_bin) {
Log::debug(sprintf('%s:- IAC_BIN mode, looking for 0xff',self::LOGKEY));
$message = str_replace("\xff","\xff\xff",$message);
}
switch ($this->type) {
case SOCK_STREAM:
return socket_write($this->connection,$message,strlen($message));

View File

@@ -4,6 +4,8 @@ namespace App\Classes\Sock;
use Illuminate\Support\Facades\Log;
use App\Classes\Sock\Exception\{HAproxyException,SocketException};
final class SocketServer {
private const LOGKEY = 'SS-';
@@ -125,16 +127,27 @@ final class SocketServer {
if (($accept = socket_accept($this->server)) === FALSE)
throw new SocketException(SocketException::CANT_ACCEPT,socket_strerror(socket_last_error($this->server)));
Log::debug(sprintf('%s:* TCP Loop Start',self::LOGKEY));
try {
$r = new SocketClient($accept);
} catch (HAproxyException $e) {
Log::notice(sprintf('%s:! HAPROXY Exception [%s]',self::LOGKEY,$e->getMessage()));
socket_close($accept);
continue;
} catch (\Exception $e) {
Log::error(sprintf('%s:! Creating Socket client failed? [%s]',self::LOGKEY,$e->getMessage()));
Log::notice(sprintf('%s:! Creating Socket client failed? [%s]',self::LOGKEY,$e->getMessage()));
socket_close($accept);
continue;
}
$this->handler[0]->{$this->handler[1]}($r);
// If the handler returns a value, then that is the main thread
if (! $this->handler[0]->{$this->handler[1]}($r)) {
$r->close();
exit(0);
}
}
}
@@ -144,7 +157,8 @@ final class SocketServer {
$r = new SocketClient($this->server);
if ($r->hasData(30)) {
$this->handler[0]->{$this->handler[1]}($r);
if (! ($this->handler[0]->{$this->handler[1]}($r)))
exit(0);
// Sleep so our thread has a chance to pick up the data from our connection
usleep(50000);

View File

@@ -0,0 +1,42 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Jobs\AddressClearQueue as Job;
use App\Models\Address;
class AddressClearQueue extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'address:clear:queue'
.' {ftn : FTN}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Clear up anything queued for an FTN';
/**
* Execute the console command.
*/
public function handle(): int
{
$ao = Address::findFTN($this->argument('ftn'),TRUE,TRUE);
if (! $ao) {
$this->error('FTN not found: '.$this->argument('ftn'));
return self::FAILURE;
}
return Job::dispatchSync($ao);
}
}

View File

@@ -30,7 +30,7 @@ class AddressIdle extends Command
*/
public function handle(): int
{
$do = Domain::where('name',$this->argument('domain'))->singleOrFail();
$do = Domain::where('name',$this->argument('domain'))->sole();
return Job::dispatchSync($do,$this->option('ftn') ? Address::findFTN($this->option('ftn')) : NULL);
}

View File

@@ -2,10 +2,10 @@
namespace App\Console\Commands\Areafix;
use Carbon\Carbon;
use Illuminate\Console\Command;
use App\Models\{Address,Echoarea,Echomail};
use App\Jobs\AreafixRescan;
use App\Models\{Address,Echoarea};
class Rescan extends Command
{
@@ -14,7 +14,13 @@ class Rescan extends Command
*
* @var string
*/
protected $signature = 'areafix:rescan {ftn} {area} {days?}';
protected $signature = 'areafix:rescan'
.' {ftn : FTN Address}'
.' {area : Echoarea Tag}'
.' {days? : Limit to messages authored days ago}'
.' {--j|queue : Queue the Job}'
.' {--Q|queuename=default : Queue on queue}'
.' {--R|export : Re-export previously sent messages}';
/**
* The console command description.
@@ -43,50 +49,15 @@ class Rescan extends Command
if (! $this->argument('area'))
throw new \Exception('Areaname is required');
$eao = Echoarea::where('name',$this->argument('area'))->singleOrFail();
if ($eao->domain_id !== $ao->zone->domain_id)
throw new \Exception(sprintf('Echo area [%s] is not in domain [%s] for FTN [%s]',$eao->name,$ao->zone->domain->name,$ao->ftn));
$eo = Echoarea::where('name',$this->argument('area'))->sole();
// Check that the user is subscribed
if (! $ao->echoareas->contains($eao->id))
throw new \Exception(sprintf('FTN [%s] is not subscribed to [%s]',$ao->ftn,$eao->name));
if ($eo->domain_id !== $ao->zone->domain_id)
throw new \Exception(sprintf('Echo area [%s] is not in domain [%s] for FTN [%s]',$eo->name,$ao->zone->domain->name,$ao->ftn));
// Check that an FTN can read the area
if (! $eao->can_read($ao->security))
throw new \Exception(sprintf('FTN [%s] doesnt have permission to receive [%s]',$ao->ftn,$eao->name));
foreach (Echomail::select('id')
->where('echoarea_id',$eao->id)
->when($this->argument('days'),function($query) {
return $query->where('created_at','>=',Carbon::now()->subDays($this->argument('days'))->startOfDay());
})
->orderBy('datetime')
->cursor() as $eo) {
// Echomail hasnt been exported before
if (! $eo->seenby->count()) {
$eo->seenby()->attach($ao->id,['export_at'=>Carbon::now()]);
$this->info(sprintf('Exported [%d] to [%s]',$eo->id,$ao->ftn3d));
} else {
$export = $eo->seenby->where('id',$ao->id)->pop();
// Echomail is pending export
if ($export && $export->pivot->export_at && is_null($export->pivot->sent_at) && is_null($export->pivot->sent_pkt)) {
$this->warn(sprintf('Not exporting [%d] already queued for [%s]',$eo->id,$ao->ftn3d));
// Echomail has been exported
} elseif ($export) {
$eo->seenby()->updateExistingPivot($ao,['export_at'=>Carbon::now(),'sent_at'=>NULL,'sent_pkt'=>NULL]);
$this->info(sprintf('Re-exported [%d] to [%s]',$eo->id,$ao->ftn3d));
// Echomail has not been exported
} else {
$eo->seenby()->attach($ao,['export_at'=>Carbon::now(),'sent_at'=>NULL,'sent_pkt'=>NULL]);
$this->info(sprintf('Exported [%d] to [%s]',$eo->id,$ao->ftn3d));
}
}
}
if ($this->option('queue'))
AreafixRescan::dispatch($ao,$eo,$this->argument('days'))->onQueue($this->option('queuename'));
else
AreafixRescan::dispatchSync($ao,$eo,$this->argument('days'));
return self::SUCCESS;
}

View File

@@ -1,53 +0,0 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
use App\Classes\Protocol\Binkp;
use App\Classes\Sock\SocketException;
use App\Classes\Sock\SocketServer;
use App\Models\Setup;
class CommBinkpReceive extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'comm:binkp:receive';
/**
* The console command description.
*
* @var string
*/
protected $description = 'BINKP receive';
/**
* Execute the console command.
*
* @return mixed
* @throws SocketException
*/
public function handle()
{
Log::info('Listening for BINKP connections...');
$o = Setup::findOrFail(config('app.id'));
$server = new SocketServer($o->binkp_port,$o->binkp_bind);
$server->handler = [new Binkp($o),'onConnect'];
try {
$server->listen();
} catch (SocketException $e) {
if ($e->getMessage() === 'Can\'t accept connections: "Success"')
Log::debug('Server Terminated');
else
Log::emergency('Uncaught Message: '.$e->getMessage());
}
}
}

View File

@@ -16,7 +16,9 @@ class CommBinkpSend extends Command
*
* @var string
*/
protected $signature = 'comm:binkp:send {ftn : FTN to Send to}';
protected $signature = 'comm:binkp:send'
.' {--N|now : Dont queue}'
.' {ftn : FTN to Send to}';
/**
* The console command description.
@@ -25,14 +27,12 @@ class CommBinkpSend extends Command
*/
protected $description = 'BINKP send';
private const ID = 'BINKP';
/**
* Execute the console command.
*
* @throws \Exception
*/
public function handle(): void
public function handle()
{
$ao = Address::findFTN($this->argument('ftn'));
if (! $ao)
@@ -40,8 +40,13 @@ class CommBinkpSend extends Command
Log::info(sprintf('CBS:- Call BINKP send for %s',$ao->ftn));
$mo = Mailer::where('name',self::ID)->singleOrFail();
$mo = Mailer::where('name','BINKP')->sole();
Job::dispatch($ao,$mo);
if ($this->option('now'))
Job::dispatchSync($ao,$mo);
else
Job::dispatch($ao,$mo);
return self::SUCCESS;
}
}

View File

@@ -1,53 +0,0 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
use App\Classes\Protocol\EMSI;
use App\Classes\Sock\SocketException;
use App\Classes\Sock\SocketServer;
use App\Models\Setup;
class CommEMSIReceive extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'comm:emsi:receive';
/**
* The console command description.
*
* @var string
*/
protected $description = 'EMSI receive';
/**
* Execute the console command.
*
* @return mixed
* @throws \Exception
*/
public function handle()
{
Log::info('Listening for EMSI connections...');
$o = Setup::findOrFail(config('app.id'));
$server = new SocketServer($o->emsi_port,$o->emsi_bind);
$server->handler = [new EMSI($o),'onConnect'];
try {
$server->listen();
} catch (SocketException $e) {
if ($e->getMessage() === 'Can\'t accept connections: "Success"')
Log::debug('Server Terminated');
else
Log::emergency('Uncaught Message: '.$e->getMessage());
}
}
}

View File

@@ -16,7 +16,9 @@ class CommEMSISend extends Command
*
* @var string
*/
protected $signature = 'comm:emsi:send {ftn : FTN to Send to}';
protected $signature = 'comm:emsi:send'
.' {--N|now : Dont queue}'
.' {ftn : FTN to Send to}';
/**
* The console command description.
@@ -25,14 +27,12 @@ class CommEMSISend extends Command
*/
protected $description = 'EMSI send';
private const ID = 'EMSI';
/**
* Execute the console command.
*
* @throws \Exception
*/
public function handle(): void
public function handle()
{
$ao = Address::findFTN($this->argument('ftn'));
if (! $ao)
@@ -40,8 +40,13 @@ class CommEMSISend extends Command
Log::info(sprintf('CES:- Call EMSI send for %s',$ao->ftn));
$mo = Mailer::where('name',self::ID)->singleOrFail();
$mo = Mailer::where('name','EMSI')->sole();
Job::dispatch($ao,$mo);
if ($this->option('now'))
Job::dispatchSync($ao,$mo);
else
Job::dispatch($ao,$mo);
return self::SUCCESS;
}
}

View File

@@ -24,7 +24,7 @@ class AddressCheck extends Command
$this->info(sprintf('Address: %s (%s)',$o->ftn,$o->role_name));
$this->info(sprintf("Children: \n- %s",$o->children()->pluck('ftn4d')->join("\n- ")));
$this->info(sprintf("Downstream: \n- %s",$o->downstream()->pluck('ftn4d')->join("\n- ")));
$this->info(sprintf("Downlinks: \n- %s",$o->downlinks()->pluck('ftn4d')->join("\n- ")));
$this->info(sprintf('Uplink: %s (Parent: %s)',$o->uplink()?->ftn,$o->parent()?->ftn));
$this->info(sprintf('Our Address: %s',our_address($o)?->ftn));
$this->info(sprintf('- Domain Addresses: %s',our_address($o->zone->domain)->pluck('ftn4d')->join(',')));

View File

@@ -0,0 +1,64 @@
<?php
namespace App\Console\Commands\Debug;
use Illuminate\Console\Command;
use App\Models\Address;
class AddressCheckNode extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'debug:address:check:nodes'
.' {ftn? : FTN}'
.' {--N|node : Node Order}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Check all addresses we use for nodes';
/**
* Execute the console command.
*
* @return int
* @throws \Exception
*/
public function handle(): int
{
$ao = NULL;
if ($this->argument('ftn')) {
$ao = Address::findFTN($this->argument('ftn'));
if (! $ao) {
$this->error('FTN not found: ' .$this->argument('ftn'));
return self::FAILURE;
}
$this->info('our address:'.our_address($ao)->ftn);
return self::SUCCESS;
}
$this->table(['System','Node','Ours'],
our_nodes($ao ? $ao->domain : NULL)
->sortBy(fn($item)=>$this->option('node')
? sprintf('%s:%s',$item->system->name,$item->domain->name)
: sprintf('%s',$item->domain->name))
->map(fn($item)=>
[
'System'=>$item->system->name,
'Node'=>$item->ftn.' '.($item->echoareas->count() ? '^' : '').($item->fileareas->count() ? '*' : ''),
'Ours'=>our_address($item)?->ftn,
]));
return self::SUCCESS;
}
}

View File

@@ -12,8 +12,7 @@ use App\Models\Dynamic as DynamicModel;
class DynamicItem extends Command
{
protected $signature = 'debug:dynamic:item'
.' {name : Dynamic Item}'
.' {ftn : FTN Address}';
.' {name : Dynamic Item}';
protected $description = 'Generate a dynamic item';
@@ -24,12 +23,12 @@ class DynamicItem extends Command
if (! $do)
throw new \Exception(sprintf('Dynamic Item [%s] doesnt exist?',$this->argument('name')));
$ao = Address::findFTN($this->argument('ftn'));
$d = new Dynamic($do,$ao,Send::T_FILE);
$d = new Dynamic($do,$do->address,Send::T_FILE);
$d->open();
echo $d->read($d->size);
echo $d->read($d->size)."\n";
$this->alert('File sent as:'.$d->nameas);
return self::SUCCESS;
}

View File

@@ -16,8 +16,7 @@ class PacketDump extends Command
protected $signature = 'debug:packet:dump'.
' {type : Type of packet, netmail|echomail }'.
' {ftn : FTN}'.
' {file? : filename}'.
' {--dump : Dump packet}';
' {file? : filename}';
/**
* The console command description.
@@ -50,11 +49,16 @@ class PacketDump extends Command
throw new \Exception('Unknown type: '.$this->argument('type'));
}
if ($this->option('dump')) {
if (is_null($pkt)) {
$this->info(sprintf('No packet for [%s] of type [%s]',$this->argument('ftn'),$this->argument('type')));
return self::SUCCESS;
}
if (! $this->argument('file')) {
$this->info('Item Name:'.$pkt->name);
$this->info('Item Type:'.get_class($pkt));
$this->info('Dump:');
echo hex_dump($pkt);
echo hex_dump((string)$pkt);
} else {
$f = fopen($this->argument('file'),'w+');

View File

@@ -16,7 +16,7 @@ class ZoneCheck extends Command
public function handle(): int
{
$do = Domain::where('name',$this->argument('domain'))->singleOrFail();
$do = Domain::where('name',$this->argument('domain'))->sole();
foreach ($do->zones->sortby('zone_id') as $zo) {
if ($this->option('zone') && ($this->option('zone') != $zo->zone_id))
@@ -25,21 +25,22 @@ class ZoneCheck extends Command
$this->warn('Zone: '.$zo->zone_id);
$this->info(sprintf('- Our address(es): %s',our_address($do)->pluck('ftn4d')->join(',')));
$this->table(['id','ftn','role','parent','children','downlinks','uplink','send from','region_id','system','notes'],$zo->addresses()->FTNorder()->active()->with(['system'])->dontCache()->get()->transform(function($item) {
return [
'id'=>$item->id,
'ftn'=>$item->ftn4d,
'role'=>$item->role_name,
'parent'=>$item->parent()?->ftn4d,
'children'=>$item->children()->count(),
'downlinks'=>$item->downlinks()->count(),
'uplink'=>($x=$item->uplink())?->ftn4d,
'send from'=>$x ? our_address($item->uplink())?->ftn4d : '',
'region_id'=>$item->region_id,
'system'=>$item->system->name,
'notes'=>$item->isRoleOverride() ? 'Role Override' : '',
];
}));
$this->table(['id','region_id','ftn','role','parent','children','downlinks','uplink','send from','system','notes'],
$zo->addresses()->FTN()->active()->with(['system','nodes_hub'])->get()->transform(function($item) {
return [
'id'=>$item->id,
'region_id'=>$item->region_id,
'ftn'=>$item->ftn4d,
'role'=>$item->role_name,
'parent'=>$item->parent()?->ftn4d,
'children'=>$item->children()->count(),
'downlinks'=>$item->downlinks()->count(),
'uplink'=>($x=$item->uplink())?->ftn4d,
'send from'=>$x ? our_address($item->uplink())?->ftn4d : '',
'system'=>$item->system->name,
'notes'=>$item->isRoleOverride() ? 'Role Override' : '',
];
}));
}
return self::SUCCESS;

View File

@@ -34,7 +34,8 @@ class EchoareaImport extends Command
*/
public function handle(): int
{
$do = Domain::where('name',strtolower($this->argument('domain')))->singleOrFail();
return Job::dispatchSync($this->argument('file'),$do,$this->option('prefix'),$this->option('unlink'));
$do = Domain::where('name',strtolower($this->argument('domain')))->single();
return Job::dispatchSync($this->argument('file'),$do,$this->option('prefix') ?: '',$this->option('unlink'));
}
}

View File

@@ -34,7 +34,8 @@ class FileareaImport extends Command
*/
public function handle(): int
{
$do = Domain::where('name',strtolower($this->argument('domain')))->singleOrFail();
return Job::dispatchSync($this->argument('file'),$do,$this->option('prefix'),$this->option('unlink'));
$do = Domain::where('name',strtolower($this->argument('domain')))->sole();
return Job::dispatchSync($this->argument('file'),$do,$this->option('prefix') ?: '',$this->option('unlink'));
}
}

View File

@@ -2,10 +2,10 @@
namespace App\Console\Commands\Filefix;
use Carbon\Carbon;
use Illuminate\Console\Command;
use App\Models\{Address,Filearea,File};
use App\Jobs\FilefixRescan;
use App\Models\{Address,Filearea};
class Rescan extends Command
{
@@ -14,7 +14,13 @@ class Rescan extends Command
*
* @var string
*/
protected $signature = 'filefix:rescan {ftn} {area} {file?}';
protected $signature = 'filefix:rescan'
.' {ftn : FTN Address}'
.' {area : Echoarea Tag}'
.' {days? : Limit to files received days ago}'
.' {--j|queue : Queue the Job}'
.' {--Q|queuename=default : Queue on queue}'
.' {--R|export : Re-export previously sent files}';
/**
* The console command description.
@@ -31,6 +37,9 @@ class Rescan extends Command
*/
public function handle(): int
{
if (($this->argument('days')) && (! is_numeric($this->argument('days'))))
throw new \Exception('Days must be numeric: '.$this->argument('days'));
$ao = Address::findFtn($this->argument('ftn'));
if (! $ao)
@@ -40,50 +49,15 @@ class Rescan extends Command
if (! $this->argument('area'))
throw new \Exception('Areaname is required');
$fao = Filearea::where('name',$this->argument('area'))->singleOrFail();
$fao = Filearea::where('name',$this->argument('area'))->sole();
if ($fao->domain_id !== $ao->zone->domain_id)
throw new \Exception(sprintf('File area [%s] is not in domain [%s] for FTN [%s]',$fao->name,$ao->zone->domain->name,$ao->ftn));
// Check that the user is subscribed
if (! $ao->fileareas->contains($fao->id))
throw new \Exception(sprintf('FTN [%s] is not subscribed to [%s]',$ao->ftn,$fao->name));
// Check that an FTN can read the area
if (! $fao->can_read($ao->security))
throw new \Exception(sprintf('FTN [%s] doesnt have permission to receive [%s]',$ao->ftn,$fao->name));
foreach (File::select('id')
->where('filearea_id',$fao->id)
->when($this->argument('file'),function($query) {
return $query->where('name','=',$this->argument('days'));
})
->orderBy('datetime')
->cursor() as $fo) {
// File hasnt been exported before
if (! $fo->seenby->count()) {
$fo->seenby()->attach($ao->id,['export_at'=>Carbon::now()]);
$this->info(sprintf('Exported [%d] to [%s]',$fo->id,$ao->ftn3d));
} else {
$export = $fo->seenby->where('id',$ao->id)->pop();
// File is pending export
if ($export && $export->pivot->export_at && is_null($export->pivot->sent_at) && is_null($export->pivot->sent_pkt)) {
$this->warn(sprintf('Not exporting [%d] already queued for [%s]',$fo->id,$ao->ftn3d));
// File has been exported
} elseif ($export) {
$fo->seenby()->updateExistingPivot($ao,['export_at'=>Carbon::now(),'sent_at'=>NULL]);
$this->info(sprintf('Re-exported [%d] to [%s]',$fo->id,$ao->ftn3d));
// File has not been exported
} else {
$fo->seenby()->attach($ao,['export_at'=>Carbon::now(),'sent_at'=>NULL]);
$this->info(sprintf('Exported [%d] to [%s]',$fo->id,$ao->ftn3d));
}
}
}
if ($this->option('queue'))
FilefixRescan::dispatch($ao,$fao,$this->argument('days'))->onQueue($this->option('queuename'));
else
FilefixRescan::dispatchSync($ao,$fao,$this->argument('days'));
return self::SUCCESS;
}

View File

@@ -45,12 +45,12 @@ class MailList extends Command
'from' => 'FROM',
'to' => 'TO',
'subject' => 'SUBJECT',
],$ao->netmailWaiting()->map(function($item) {
],$ao->netmailWaiting()->get()->map(function($item) {
return [
'id'=>$item->id,
'msgid'=>$item->msgid,
'from'=>$item->from,
'to'=>$item->to,
'from'=>sprintf('%s (%s)',$item->from,$item->fftn->ftn3d),
'to'=>sprintf('%s (%s)',$item->to,$item->tftn->ftn3d),
'subject'=>$item->subject,
];
}));
@@ -63,7 +63,7 @@ class MailList extends Command
'to' => 'TO',
'subject' => 'SUBJECT',
'area' => 'AREA',
],$ao->echomailWaiting()->map(function($item) {
],$ao->echomailWaiting()->get()->map(function($item) {
return [
'id'=>$item->id,
'msgid'=>$item->msgid,

View File

@@ -36,22 +36,15 @@ class NodelistImport extends Command
*/
public function handle():int
{
try {
return Job::dispatchSync(
is_numeric($x=$this->argument('file'))
? File::findOrFail($x)
: sprintf('%s/%s',config('fido.dir'),$this->argument('file')),
$this->argument('domain'),
$this->option('delete'),
$this->option('unlink'),
$this->option('test'),
$this->option('ignorecrc'),
);
} catch (\Exception $e) {
$this->error($e->getMessage());
return self::FAILURE;
}
return Job::dispatchSync(
is_numeric($x=$this->argument('file'))
? File::findOrFail($x)
: sprintf('%s/%s',config('fido.dir'),$this->argument('file')),
$this->argument('domain'),
$this->option('delete'),
$this->option('unlink'),
$this->option('test'),
$this->option('ignorecrc'),
);
}
}

View File

@@ -0,0 +1,50 @@
<?php
namespace App\Console\Commands;
use App\Models\Address;
use Carbon\Carbon;
use Illuminate\Console\Command;
use App\Jobs\NodesNew as Job;
use App\Models\Domain;
class NodesNew extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'nodes:new'
.' {domain : Domain}'
.' {--date= : From a specific date (default 1 since last Saturday)}'
.' {--netmail= : Send a Netmail to FTN}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'List new nodes since last Saturday (or a specific date)';
/**
* Execute the console command.
*/
public function handle(): int
{
$do = Domain::where('name',$this->argument('domain'))->sole();
$ao = NULL;
if ($this->option('netmail')) {
$ao = Address::findFTN($this->option('netmail'));
if (! $ao) {
$this->error('Address not found: '.$this->option('netmail'));
return self::FAILURE;
}
}
return Job::dispatchSync($do,$this->option('date') ? Carbon::parse($this->option('date')) : Carbon::parse('last saturday'),$ao);
}
}

View File

@@ -50,7 +50,7 @@ class PacketInfo extends Command
}
foreach ($f as $packet) {
$pkt = Packet::process($packet,$x=$f->itemName(),$f->itemSize(),$a?->zone->domain);
$pkt = Packet::process($packet,$x=$f->itemName(),$f->itemSize(),$a?->system);
$this->alert(sprintf('File Name: %s',$x));
@@ -68,7 +68,8 @@ class PacketInfo extends Command
echo "\n";
try {
$this->warn(sprintf('- Date : %s (%s)',$msg->datetime,$msg->datetime->tz->toOffsetName()));
$this->warn(sprintf('- TYPE : %s',get_class($msg)));
$this->warn(sprintf(' - Date : %s (%s)',$msg->date,$msg->date->tz->toOffsetName()));
$this->warn(sprintf(' - Errors : %s',$msg->errors->count() ? 'YES' : 'No'));
$this->warn(sprintf(' - Flags : %s',$msg->flags()->keys()->join(', ')));
$this->warn(sprintf(' - Cost : %d',$msg->cost));
@@ -76,7 +77,7 @@ class PacketInfo extends Command
if ($msg instanceof Echomail)
$this->warn(sprintf(' - To : %s',$msg->to));
else
$this->warn(sprintf(' - To : %s (%s)',$msg->to,$msg->tftn->ftn));
$this->warn(sprintf(' - To : %s (%s)',$msg->to,$msg->tftn?->ftn ?: $msg->set_tftn));
$this->warn(sprintf(' - Subject: %s',$msg->subject));
if ($msg instanceof Echomail)
$this->warn(sprintf(' - Area : %s',$msg->echoarea->name));
@@ -97,7 +98,7 @@ class PacketInfo extends Command
}
foreach ($pkt->errors as $msg) {
$this->error(sprintf('- Date: %s',$msg->date));
$this->error(sprintf('- Date: %s',$msg->datetime));
$this->error(sprintf(' - FLAGS: %s',$msg->flags()->filter()->keys()->join(', ')));
$this->error(sprintf(' - From: %s (%s)',$msg->from,$msg->fftn));
$this->error(sprintf(' - To: %s (%s)',$msg->to,$msg->tftn));

View File

@@ -34,6 +34,8 @@ use App\Models\Address;
* - To areafix (processed)
* - To ping (respond)
* - With trace turned on (respond)
*
* @todo Enable force processing packets when the password is wrong
*/
class PacketProcess extends Command
{
@@ -78,7 +80,7 @@ class PacketProcess extends Command
return self::FAILURE;
}
Job::dispatchSync($rel_name,$ao->zone->domain,$this->option('dontqueue'));
Job::dispatchSync($rel_name,$ao->system,$this->option('dontqueue'));
return self::SUCCESS;
}

View File

@@ -3,11 +3,12 @@
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Log;
use App\Classes\Protocol\{Binkp,DNS,EMSI};
use App\Classes\Sock\{SocketException,SocketServer};
use App\Models\Setup;
use App\Classes\Sock\Exception\SocketException;
use App\Classes\Sock\SocketServer;
class ServerStart extends Command
{
@@ -36,7 +37,11 @@ class ServerStart extends Command
public function handle(): int
{
Log::info(sprintf('%s:+ Server Starting (%d)',self::LOGKEY,getmypid()));
$o = Setup::findOrFail(config('app.id'));
if (! our_address()->count())
throw new \Exception('We dont have any ACTIVE FTN addresses assigned');
$o = Config::get('setup');
$start = collect();
@@ -61,7 +66,7 @@ class ServerStart extends Command
'address'=>$o->dns_bind,
'port'=>$o->dns_port,
'proto'=>SOCK_DGRAM,
'class'=>new DNS(),
'class'=>new DNS($o),
]);
$children = collect();

View File

@@ -25,7 +25,7 @@ class UserCodeSend extends Command
public function handle(): int
{
$ao = Address::findFTN($this->argument('ftn'));
$uo = User::where('email',$this->argument('email'))->singleOrFail();
$uo = User::where('email',$this->argument('email'))->sole();
Notification::route('netmail',$ao->uplink())->notify(new AddressLink($uo));

View File

@@ -1,46 +0,0 @@
<?php
namespace App\Console;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
use App\Jobs\{AddressIdleDomain,MailSend,SystemHeartbeat};
class Kernel extends ConsoleKernel
{
/**
* The Artisan commands provided by your application.
*
* @var array
*/
protected $commands = [
//
];
/**
* Define the application's command schedule.
*
* @param \Illuminate\Console\Scheduling\Schedule $schedule
* @return void
*/
protected function schedule(Schedule $schedule)
{
$schedule->job(new MailSend(TRUE))->everyMinute()->withoutOverlapping();
$schedule->job(new MailSend(FALSE))->twiceDaily(1,13);
$schedule->job(new SystemHeartbeat)->hourly();
$schedule->job(new AddressIdleDomain)->weeklyOn(0,'01:00');
}
/**
* Register the commands for the application.
*
* @return void
*/
protected function commands()
{
$this->load(__DIR__.'/Commands');
require base_path('routes/console.php');
}
}

21
app/Events/Echomail.php Normal file
View File

@@ -0,0 +1,21 @@
<?php
namespace App\Events;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use App\Models\Echomail as EchomailModel;
class Echomail
{
use Dispatchable, SerializesModels;
/**
* Create a new event instance.
*/
public function __construct(public EchomailModel $eo)
{
//
}
}

View File

@@ -0,0 +1,64 @@
<?php
namespace App\Events\Matrix;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use App\Models\Echoarea;
abstract class Base
{
protected $_data = [];
public function __construct(array $request)
{
Log::info(sprintf('EMb:- Event Initialised [%s]',get_class($this)));
$this->_data = json_decode(json_encode($request));
}
/**
* Enable getting values for keys in the response
*
* @note: This method is limited to certain values to ensure integrity reasons
* @note: Classes should return:
* + channel_id,
* + team_id,
* + ts,
* + user_id
* @param string $key
* @return mixed|object
* @throws ConnectionException
*/
public function __get(string $key)
{
switch ($key) {
case 'echoarea':
$rooms = collect(config('matrix.rooms'));
return Echoarea::where('name',$rooms->get($this->room_id))->single();
case 'room':
$room_alias = Http::withToken(config('matrix.as_token'))
->get(sprintf('%s/_matrix/client/v3/rooms/%s/state/m.room.canonical_alias',config('matrix.server'),$this->room_id));
return $room_alias->json('alias',$this->room_id);
case 'topic':
$subject = Http::withToken(config('matrix.as_token'))
->get(sprintf('%s/_matrix/client/v3/rooms/%s/state/m.room.topic',config('matrix.server'),$this->room_id));
return $subject->json('topic','Message from Matrix');
case 'ts':
return object_get($this->_data,'origin_server_ts');
case 'event_id':
case 'room_id':
default:
return object_get($this->_data,$key);
}
}
}

View File

@@ -0,0 +1,41 @@
<?php
namespace App\Events\Matrix;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Log;
class Factory {
private const LOGKEY = 'EMf';
/**
* @var array event type to event class mapping
*/
public const map = [
'm.room.message' => Message::class,
];
/**
* Returns new event instance
*
* @param string $type
* @param array $request
* @return Base
*/
public static function create(string $type,array $request): Base
{
$class = Arr::get(self::map,$type,Unknown::class);
Log::debug(sprintf('%s:- Working out Event Class for [%s] as [%s]',static::LOGKEY,$type,$class));
if (App::environment() == 'local')
file_put_contents('/tmp/event.'.$type,print_r($request,TRUE));
return new $class($request);
}
public static function make(array $request): Base
{
return self::create(Arr::get($request,'type','unknown'),$request);
}
}

View File

@@ -0,0 +1,49 @@
<?php
namespace App\Events\Matrix;
/**
* A matrix message event
*
* Array
* (
* [age] => 37
* [content] => Array
* (
* [body] => This is my text
* [m.mentions] => Array
* (
* )
*
* [msgtype] => m.text
* )
*
* [event_id] => $fkpvy3qDkAGlB55nvqcH8mUfSxzELtaJ9TKJs6GP9us
* [origin_server_ts] => 1717917709298
* [room_id] => !bbXofZepRYOhKjihLH:matrix.dege.au
* [sender] => @deon:matrix.dege.au
* [type] => m.room.message
* [unsigned] => Array
* (
* [age] => 37
* )
*
* [user_id] => @deon:matrix.dege.au
* )
*/
class Message extends Base
{
public function __get($key)
{
switch ($key) {
case 'message':
return object_get($this->_data,'content.body');
case 'sender':
return object_get($this->_data,$key);
default:
return parent::__get($key);
}
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace App\Events\Matrix;
use Illuminate\Support\Facades\Log;
/**
* Catch all unknown events that we havent specifically programmed for.
*
* @package Slack\Event
*/
class Unknown extends Base
{
public function __construct(array $request)
{
Log::notice(sprintf('EMU:? UNKNOWN Event received [%s]',get_class($this)));
parent::__construct($request);
}
}

View File

@@ -1,40 +0,0 @@
<?php
namespace App\Exceptions;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Throwable;
class Handler extends ExceptionHandler
{
/**
* A list of the exception types that are not reported.
*
* @var array
*/
protected $dontReport = [
//
];
/**
* A list of the inputs that are never flashed for validation exceptions.
*
* @var array
*/
protected $dontFlash = [
'password',
'password_confirmation',
];
/**
* Register the exception handling callbacks for the application.
*
* @return void
*/
public function register()
{
$this->reportable(function (Throwable $e) {
//
});
}
}

212
app/Helpers/PageAssets.php Normal file
View File

@@ -0,0 +1,212 @@
<?php
namespace App\Helpers;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
/**
* Useful tools (js/css) used when rendering pages
*/
class PageAssets
{
// Types that we can handle
private const array types = [
'css',
'js',
];
public const array assets = [
'datatables' => [
'base' => [
'css' => [
'//cdn.datatables.net/1.10.25/css/dataTables.bootstrap5.css',
//'//cdn.datatables.net/2.1.2/css/dataTables.dataTables.min.css',
'/plugin/dataTables/dataTables.bootstrap5.css',
],
'js' => [
'//cdn.datatables.net/1.10.25/js/jquery.dataTables.min.js',
'//cdn.datatables.net/1.10.25/js/dataTables.bootstrap5.min.js',
],
],
'buttons' => [
'css' => [
'//cdn.datatables.net/buttons/3.1.0/css/buttons.bootstrap4.min.css',
//'//cdn.datatables.net/buttons/3.1.0/css/buttons.dataTables.min.css',
],
'js' => [
'//cdn.datatables.net/buttons/3.1.0/js/dataTables.buttons.min.js',
//'//cdn.datatables.net/buttons/3.1.0/js/buttons.dataTables.min.js',
'//cdn.datatables.net/buttons/3.1.0/js/buttons.bootstrap4.min.js',
'//cdnjs.cloudflare.com/ajax/libs/jszip/3.2.0/jszip.min.js',
],
],
'conditionalpaging' => [
'js' => [
//'//cdn.datatables.net/plug-ins/2.0.5/features/conditionalPaging/dataTables.conditionalPaging.min.js',
'/plugin/dataTables/dataTables.conditionalPaging.js',
],
],
'fixedheader' => [
'css' => [
'//cdn.datatables.net/fixedheader/4.0.1/css/fixedHeader.bootstrap4.min.css',
//'//cdn.datatables.net/fixedheader/4.0.1/css/fixedHeader.dataTables.min.css',
],
'js' => [
'//cdn.datatables.net/fixedheader/4.0.1/js/dataTables.fixedHeader.min.js',
//'//cdn.datatables.net/fixedheader/4.0.1/js/fixedHeader.dataTables.min.js',
'//cdn.datatables.net/fixedheader/4.0.1/js/fixedHeader.bootstrap4.min.js',
]
],
'responsive' => [
'css' => [
'//cdn.datatables.net/responsive/3.0.2/css/responsive.bootstrap4.min.css',
//'//cdn.datatables.net/responsive/3.0.2/css/responsive.dataTables.min.css',
],
'js' => [
'//cdn.datatables.net/responsive/3.0.2/js/dataTables.responsive.min.js',
//'//cdn.datatables.net/responsive/3.0.2/js/responsive.bootstrap.min.js',
'//cdn.datatables.net/responsive/3.0.2/js/responsive.bootstrap4.min.js',
]
],
'rowgroup' => [
'css' => [
'//cdn.datatables.net/rowgroup/1.1.2/css/rowGroup.bootstrap4.min.css',
//'//cdn.datatables.net/rowgroup/1.5.0/css/rowGroup.dataTables.min.css',
],
'js' => [
'//cdn.datatables.net/rowgroup/1.1.2/js/dataTables.rowGroup.min.js',
//'//cdn.datatables.net/rowgroup/1.5.0/js/rowGroup.dataTables.min.js',
'//cdn.datatables.net/rowgroup/1.1.2/js/rowGroup.bootstrap5.min.js',
],
],
'searchpanes' => [
'css' => [
'//cdn.datatables.net/searchpanes/2.3.1/css/searchPanes.bootstrap4.min.css',
//'//cdn.datatables.net/searchpanes/2.3.1/css/searchPanes.dataTables.min.css',
],
'js' => [
'//cdn.datatables.net/searchpanes/2.3.1/js/dataTables.searchPanes.min.js',
//'//cdn.datatables.net/searchpanes/2.3.1/js/searchPanes.dataTables.min.js',
'//cdn.datatables.net/searchpanes/2.3.1/js/searchPanes.bootstrap4.min.js',
],
],
'searchpanes-left' => [
'css' => [
'/plugin/dataTables/leftSearchPanes.css',
],
],
'select' => [
'css' => [
'//cdn.datatables.net/select/2.0.3/css/select.bootstrap4.min.css',
//'//cdn.datatables.net/select/2.0.3/css/select.dataTables.min.css',
],
'js' => [
'//cdn.datatables.net/select/2.0.3/js/dataTables.select.min.js',
//'//cdn.datatables.net/select/2.0.3/js/select.dataTables.min.js',
'//cdn.datatables.net/select/2.0.3/js/select.bootstrap4.min.js',
]
],
],
'select2' => [
'base' => [
'css' => [
'//cdn.jsdelivr.net/npm/select2@4.0.13/dist/css/select2.min.css',
'//cdn.jsdelivr.net/npm/select2-bootstrap-5-theme@1.3.0/dist/select2-bootstrap-5-theme.min.css',
],
'js' => [
'//cdn.jsdelivr.net/npm/select2@4.0.13/dist/js/select2.full.min.js',
'/plugin/select2/fix-autofocus.js',
],
],
],
'simplemde' => [
'base' => [
'css' => [
'//cdn.jsdelivr.net/simplemde/latest/simplemde.min.css',
],
'js' => [
'//cdn.jsdelivr.net/simplemde/latest/simplemde.min.js',
],
],
],
];
// Items to manage
public static Collection $items;
// Add an item to the list
public static function add(string $type,Collection|array|string $asset): void
{
if (! in_array($type,self::types))
throw new \Exception('Invalid type: '.$type);
if (! isset(self::$items))
self::init();
if (is_string($asset))
self::$items
->get($type)
->push($asset)
->unique();
else
self::$items->put($type,
self::$items
->get($type)
->merge($asset->values())
->unique());
}
// Add a predefined asset
public static function asset(string $id): void
{
if (! isset(self::$items))
self::init();
if (str_contains($id,',')) {
[$item,$arguments] = explode(',',$id,2);
$arguments = collect(explode('|',$arguments));
} else {
$item = $id;
$arguments = collect();
}
$arguments = $arguments->prepend('base');
$asset = collect(Arr::get(self::assets,$item))->only($arguments);
foreach (self::types as $type)
if ($x=$asset->pluck($type)->filter()->flatten())
self::add($type,$x);
}
// Render the CSS items
public static function css(): string
{
return isset(self::$items)
? self::$items
->get('css')
->map(fn($item)=>sprintf('<link rel="stylesheet" href="%s">',$item))
->join('')
: '';
}
public static function init(): void
{
self::$items = collect([
'js' => collect(),
'css' => collect(),
]);
}
// Render the JS items
public static function js(): string
{
return isset(self::$items)
? self::$items
->get('js')
->map(fn($item)=>sprintf('<script type="text/javascript" src="%s"></script>',$item))
->join('')
: '';
}
}

View File

@@ -2,10 +2,9 @@
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Auth\ConfirmsPasswords;
use App\Http\Controllers\Controller;
class ConfirmPasswordController extends Controller
{
/*
@@ -26,7 +25,7 @@ class ConfirmPasswordController extends Controller
*
* @var string
*/
protected $redirectTo = RouteServiceProvider::HOME;
protected $redirectTo = '/';
/**
* Create a new controller instance.

View File

@@ -2,11 +2,12 @@
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Password;
use App\Http\Controllers\Controller;
class ForgotPasswordController extends Controller
{
/*

View File

@@ -8,7 +8,6 @@ use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
class LoginController extends Controller
{
@@ -30,7 +29,7 @@ class LoginController extends Controller
*
* @var string
*/
protected $redirectTo = RouteServiceProvider::HOME;
protected $redirectTo = '/';
/**
* Create a new controller instance.
@@ -39,40 +38,41 @@ class LoginController extends Controller
*/
public function __construct()
{
$this->middleware('guest')
->except('logout');
$this->middleware('guest')->except('logout');
$this->middleware('auth')->only('logout');
}
public function login(Request $request)
{
$this->validateLogin($request);
public function login(Request $request)
{
$this->validateLogin($request);
if (Auth::attempt(array_merge($this->credentials($request),['active'=>TRUE]),TRUE)) {
$request->session()->regenerate();
if (Auth::attempt(array_merge($this->credentials($request),['active'=>TRUE]),TRUE)) {
$request->session()->regenerate();
return $this->sendLoginResponse($request);
}
return $this->sendLoginResponse($request);
}
return $this->sendFailedLoginResponse($request);
}
return $this->sendFailedLoginResponse($request);
}
protected function authenticated(Request $request, $user)
{
$user->last_on = Carbon::now();
$user->save();
}
// Record our last logged in time
protected function authenticated(Request $request, $user)
{
$user->last_on = Carbon::now();
$user->save();
}
/**
* Show our themed login page
*/
public function showLoginForm()
{
$login_note = '';
/**
* Show our themed login page
*/
public function showLoginForm()
{
$login_note = '';
if (file_exists('login_note.txt'))
$login_note = file_get_contents('login_note.txt');
if (file_exists('login_note.txt'))
$login_note = file_get_contents('login_note.txt');
return view('auth.login')
->with('login_note',$login_note);
}
return view('auth.login')
->with('login_note',$login_note);
}
}

View File

@@ -7,7 +7,6 @@ use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use App\Models\User;
class RegisterController extends Controller
@@ -30,7 +29,7 @@ class RegisterController extends Controller
*
* @var string
*/
protected $redirectTo = RouteServiceProvider::HOME;
protected $redirectTo = '/';
/**
* Create a new controller instance.

View File

@@ -2,10 +2,10 @@
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Auth\ResetsPasswords;
use App\Http\Controllers\Controller;
class ResetPasswordController extends Controller
{
/*
@@ -26,15 +26,5 @@ class ResetPasswordController extends Controller
*
* @var string
*/
protected $redirectTo = RouteServiceProvider::HOME;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest');
}
protected $redirectTo = '/';
}

View File

@@ -2,10 +2,10 @@
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Auth\VerifiesEmails;
use App\Http\Controllers\Controller;
class VerificationController extends Controller
{
/*
@@ -26,7 +26,7 @@ class VerificationController extends Controller
*
* @var string
*/
protected $redirectTo = RouteServiceProvider::HOME;
protected $redirectTo = '/';
/**
* Create a new controller instance.

View File

@@ -2,12 +2,10 @@
namespace App\Http\Controllers;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Validation\ValidatesRequests;
class Controller extends BaseController
abstract class Controller extends \Illuminate\Routing\Controller
{
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
use AuthorizesRequests,ValidatesRequests;
}

View File

@@ -2,6 +2,7 @@
namespace App\Http\Controllers;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Gate;
@@ -14,7 +15,7 @@ class DomainController extends Controller
/**
* Daily stats as shown on the about page
*
* @param Domain $o
* @param Request $request
* @return Collection
*/
public function api_daily_stats(Request $request): Collection
@@ -25,7 +26,7 @@ class DomainController extends Controller
->sortBy('date')
->groupBy('date')
->transform(function($item,$key) { return [
'x'=>\Carbon\Carbon::createFromFormat('Y-m-d',$key)->timestamp,
'x'=>Carbon::createFromFormat('Y-m-d',$key)->timestamp*1000,
'y'=>$item->sum('count')]; } )
->values();
}

View File

@@ -18,7 +18,7 @@ class EchoareaController extends Controller
$request->validate([
'domain_id' => 'required|exists:domains,id',
'name' => 'required|min:4|max:35|regex:/^[a-zA-Z0-9\-_~]{4,}$/|unique:echoareas,name,'.($o->exists ? $o->id : 0),
'name' => 'required|min:4|max:35|regex:/^[a-zA-Z0-9\-_~.]{4,}$/|unique:echoareas,name,'.($o->exists ? $o->id : 0),
'description' => 'required',
'active' => 'required|boolean',
'show' => 'required|boolean',

View File

@@ -10,13 +10,16 @@ use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Gate;
use App\Classes\File;
use App\Classes\FTN\Packet;
use App\Classes\FTN\{Message,Packet};
use App\Http\Requests\SetupRequest;
use App\Models\File as FileModel;
use App\Models\{Address,Echomail,Netmail,Setup,System};
use App\Traits\HubStats;
class HomeController extends Controller
{
use HubStats;
public function home()
{
return redirect(Auth::check() ? 'dashboard' : 'about');
@@ -104,7 +107,7 @@ class HomeController extends Controller
$f = new File($file);
foreach ($f as $packet)
$pkt->push([$f->itemName()=>Packet::process($packet,$f->itemName(),$f->itemSize())]);
$pkt->push([$f->itemName()=>Packet::process($packet,$f->itemName(),$f->itemSize(),NULL,FALSE)]);
} catch (\Exception $e) {
return redirect()->back()->withErrors(sprintf('%s (%s:%d)',$e->getMessage(),$e->getFile(),$e->getLine()));
@@ -169,7 +172,7 @@ class HomeController extends Controller
->orWhere('replyid','like','%'.$request->query('term').'%')
->get() as $o)
{
$result->push(['id'=>$o->id,'name'=>sprintf('%s (%s)',$o->from,$o->fftn->ftn3d),'value'=>url('echomail/view',[$o->id]),'category'=>'Echomail']);
$result->push(['id'=>$o->id,'name'=>sprintf('%s (%s)',Message::tr($o->from),$o->fftn->ftn3d),'value'=>url('echomail/view',[$o->id]),'category'=>'Echomail']);
}
// Look for Netmail
@@ -224,6 +227,7 @@ class HomeController extends Controller
$options->put('options',collect($request->post('options'))->sum());
$options->put('msgs_pkt',$request->post('msgs_pkt'));
$options->put('pkt_passwds',$request->post('pkt_passwds') === "1");
$o->servers = $servers;
$o->options = $options;
@@ -245,49 +249,8 @@ class HomeController extends Controller
{
$date = Carbon::now()->yesterday()->endOfday();
$r = Address::select([
'a.id',
'a.system_id',
'a.zone_id',
'addresses.region_id',
'a.host_id',
'a.node_id',
'a.point_id',
'addresses.hub_id',
'addresses.role',
DB::raw('sum(a.uncollected_echomail) as uncollected_echomail'),
DB::raw('sum(a.uncollected_netmail) as uncollected_netmail'),
DB::raw('sum(a.uncollected_files) as uncollected_files')
])
->from(
Address::UncollectedEchomailTotal()
->where('echomails.created_at','<',$this->yesterdayEOD())
->union(Address::UncollectedNetmailTotal()
->where('netmails.created_at','<',$this->yesterdayEOD())
)
->union(Address::UncollectedFilesTotal()
->where('files.created_at','<',$this->yesterdayEOD())
),'a')
->where('systems.active',TRUE)
->where('addresses.active',TRUE)
->where('zones.active',TRUE)
->where('domains.active',TRUE)
->when(! ($x=Auth::user()) || (! $x->isAdmin()),function($query) { return $query->where('domains.public',TRUE); })
->join('addresses',['addresses.id'=>'a.id'])
->join('systems',['systems.id'=>'a.system_id'])
->join('zones',['zones.id'=>'addresses.zone_id'])
->join('domains',['domains.id'=>'zones.domain_id'])
->ftnOrder()
->groupBy('a.system_id','a.id','a.zone_id','addresses.region_id','a.host_id','a.node_id','a.point_id','addresses.hub_id','addresses.role')
->with(['system','zone.domain']);
return view('status')
->with('date',$date)
->with('uncollected',$r->get());
}
private function yesterdayEOD(): Carbon
{
return Carbon::now()->yesterday()->endOfday();
->with('uncollected',$this->HubStats($date)->get());
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Log;
use App\Events\Matrix\Factory as MatrixEventFactory;
use App\Events\Matrix\Message;
final class MatrixController extends Controller
{
private const LOGKEY = 'CMC';
public function webhook(Request $request): mixed
{
$event = MatrixEventFactory::make(Arr::get($request->events,0,[]));
// Catch our messages that we've posted
if (($event instanceof Message) && preg_match('#^.*\^[0-9]+_[0-9]+/[0-9]+(\.[0-9]+)?:#',$event->sender)) {
Log::info(sprintf('%s:- Ignoring Matrix Message event, probably from us [%s]',static::LOGKEY,$event->sender));
return response(['result'=>'OK']);
}
Log::info(sprintf('%s:- Dispatching Matrix Event [%s]',static::LOGKEY,get_class($event)));
event($event);
return response(['result'=>'OK']);
}
}

View File

@@ -13,7 +13,6 @@ use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
use Illuminate\Support\ViewErrorBag;
use App\Classes\FTN\Message;
use App\Http\Requests\{AddressAdd,AddressMerge,AreafixRequest,SystemEchoareaRequest,SystemRegisterRequest,SystemSessionRequest};
@@ -31,12 +30,12 @@ class SystemController extends Controller
public function add_edit(SystemRegisterRequest $request, System $o)
{
if ($request->validated()) {
foreach (['name','location','phone','address','port','active','method','pkt_type'] as $key)
foreach (['name','location','phone','address','port','active','method','pkt_msgs','pkt_type'] as $key)
$o->{$key} = $request->validated($key);
// Sometimes items
foreach (['sysop','hold','notes','zt_id','heartbeat'] as $key)
if ($request->validated($key))
if ($request->has($key))
$o->{$key} = $request->validated($key);
switch ($request->validated('pollmode')) {
@@ -45,9 +44,13 @@ class SystemController extends Controller
default: $o->pollmode = NULL;
}
$o->active = (! is_null($x=$request->validated('active'))) && $x;
$o->autohold = FALSE;
$o->save();
if ($o->wasRecentlyCreated)
$o->users()->attach(Auth::id());
$mailers = collect($request->post('mailer_details'))
->filter(function($item) { return $item['port']; })
->transform(function($item) { $item['active'] = Arr::get($item,'active',FALSE); return $item; });
@@ -60,10 +63,22 @@ class SystemController extends Controller
$o->users()->detach();
}
return redirect()->to('system');
return redirect()
->to('system/addedit/'.$o->id)
->with('saved',TRUE);
}
$o->load(['addresses.zone.domain','addresses.nodes_hub','addresses.system','sessions.domain','sessions.systems']);
$o->loadMissing([
'zcs',
// For 'ftn' to work
'addresses.zone:id,zone_id,domain_id,active',
'addresses.zone.domain:id,name,active',
// For 'role'
'addresses.system:id,address',
// For system addedit
'sessions.domain:id,name,active',
'sessions.systems:id',
]);
return view('system.addedit')
->with('action',$o->exists ? 'update_nn' : 'create')
@@ -83,7 +98,10 @@ class SystemController extends Controller
$oo = Address::findOrNew($request->validated('submit'));
$oo->zone_id = $request->validated('zone_id');
$oo->security = $request->validated('security');
$oo->active = TRUE;
// Only change to active if it is new
if (! $oo->exists)
$oo->active = TRUE;
switch ($request->validated('action')) {
case 'region':
@@ -157,7 +175,9 @@ class SystemController extends Controller
// Make sure that no other system has this address active.
if ($o->role_id === Address::NODE_NN)
return redirect()->back()->withErrors(['address'=>sprintf('%s cannot be demoted any more',$o->ftn3D)]);
return redirect()
->back()
->withErrors(['address'=>sprintf('%s cannot be demoted any more',$o->ftn3D)]);
$off = $o->role_id;
$o->role &= ~$off;
@@ -165,7 +185,8 @@ class SystemController extends Controller
$o->save();
return redirect()->to(sprintf('system/addedit/%d',$o->system_id));
return redirect()
->to(sprintf('system/addedit/%d',$o->system_id));
}
public function address_merge(AddressMerge $request,int $id)
@@ -207,6 +228,9 @@ class SystemController extends Controller
// 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 sent_id=? where sent_id=?',[$request->dst,$request->src]);
// Find all netmails
$x = DB::update('update netmails set tftn_id=? where tftn_id=?',[$request->dst,$request->src]);
@@ -305,6 +329,7 @@ class SystemController extends Controller
if (Arr::get($validated,'remove')) {
$so->sessions()->detach($o->zone);
$so->logs()->delete();
$so->mailers()->detach();
$so->users()->detach();
$so->delete();
@@ -334,8 +359,10 @@ class SystemController extends Controller
session()->flash('accordion','address');
// Make sure that no other system has this address active.
if ($o->role_id === Address::NODE_NC)
return redirect()->back()->withErrors(['address'=>sprintf('%s cannot be promoted any more',$o->ftn3D)]);
if ($o->role_id === Address::NODE_HC)
return redirect()
->back()
->withErrors(['address'=>sprintf('%s cannot be promoted any more',$o->ftn3D)]);
$off = $o->role_id;
$o->role &= ~$off;
@@ -343,7 +370,8 @@ class SystemController extends Controller
$o->save();
return redirect()->to(sprintf('system/addedit/%d',$o->system_id));
return redirect()
->to(sprintf('system/addedit/%d',$o->system_id));
}
/**
@@ -484,10 +512,16 @@ class SystemController extends Controller
public function api_autohold_toggle(Request $request,string $state): array
{
$o = System::findOrFail($request->id);
$o->autohold = $state === 'off' ? FALSE : TRUE;
$o->save();
Log::debug(sprintf('%s:- Autohold set to [%s]',self::LOGKEY,$o->autohold ? 'ON' : 'OFF'));
if ($request->user()->can('update_nn',$o)) {
$o->autohold = !($state === 'off');
$o->save();
Log::debug(sprintf('%s:- Autohold set to [%s]',self::LOGKEY,$o->autohold ? 'ON' : 'OFF'));
} else {
abort(403);
}
return ['autohold'=>$o->autohold];
}
@@ -507,7 +541,7 @@ class SystemController extends Controller
$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->set_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));
@@ -571,7 +605,7 @@ class SystemController extends Controller
session()->flash('accordion','filearea');
// Ensure we have session details for this address.
if (! $ao->session('sespass'))
if (! $ao->pass_session)
return redirect()->back()->withErrors('System doesnt belong to this network');
$ao->fileareas()->syncWithPivotValues($request->get('id',[]),['subscribed'=>Carbon::now()]);
@@ -601,58 +635,19 @@ class SystemController extends Controller
*/
public function register(SystemRegisterRequest $request)
{
// Step 1, show the user a form to select an existing defined system
if ($request->isMethod('GET'))
return view('user.system.register');
// During the BBS linking process, if the user selected a system will link to confirm it
if ($request->action === 'register' && $request->system_id && is_numeric($request->system_id))
return redirect()
->to(sprintf('user/system/register_confirm/%d',$request->system_id));
if ($request->action === 'register' && $request->name && is_numeric($request->name))
return view('user.system.widget.register_confirm')
->with('o',System::findOrFail($request->name));
// Re-flash our previously input data, we must be creating a new system
session()->flashInput([
'name'=>$request->system_id,
'sysop'=>Auth::user()->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.system')
->with('action',$request->action)
->with('o',$o)
->with('errors',new ViewErrorBag);
return redirect()
->to('system/addedit');
}
/**
@@ -694,9 +689,16 @@ class SystemController extends Controller
$this->authorize('update_nn',$o);
session()->flash('accordion','session');
// Remove the subscription to file/echo areas for each address affected
foreach ($o->akas->where('zone_id',$zo->id) as $ao) {
$ao->echoareas()->detach();
$ao->fileareas()->detach();
}
$o->sessions()->detach($zo);
return redirect()->to(sprintf('system/addedit/%d',$o->id));
return redirect()
->to(sprintf('system/addedit/%d',$o->id));
}
// @todo Can this be consolidated with system_register()

View File

@@ -64,10 +64,17 @@ class UserController extends Controller
'code', 'Invalid Code!'
);
return back()->withErrors($validator);
return back()
->withInput()
->withErrors($validator);
}
}
return view('user.link');
}
public function whoami(): User
{
return Auth::user();
}
}

View File

@@ -10,7 +10,7 @@ use App\Models\{Address,Zone};
class ZoneController extends Controller
{
/**
* Add or edit a node
* Add or edit a zone
*/
public function add_edit(Request $request,Zone $o)
{
@@ -69,7 +69,7 @@ class ZoneController extends Controller
$o->save();
$zo = Zone::where('zone_id',$request->zone_id)
->where('domain_id',$request->domain_id)
->singleOrFail();
->sole();
// Find the zones 0/0 address, and assign it to this host.
$ao = Address::where('zone_id',$zo->id)

View File

@@ -1,83 +0,0 @@
<?php
namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
/**
* The application's global HTTP middleware stack.
*
* These middleware are run during every request to your application.
*
* @var array
*/
protected $middleware = [
\App\Http\Middleware\CheckForMaintenanceMode::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
\App\Http\Middleware\TrustProxies::class,
];
/**
* The application's route middleware groups.
*
* @var array
*/
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
// \Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\AddUserToView::class,
],
'api' => [
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
\Illuminate\Routing\Middleware\ThrottleRequests::class.':api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
];
/**
* The application's route middleware.
*
* These middleware may be assigned to groups or used individually.
*
* @var array
*/
protected $routeMiddleware = [
'activeuser' => \App\Http\Middleware\ActiveUser::class,
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
];
/**
* The priority-sorted list of middleware.
*
* This forces the listed middleware to always be in the given order.
*
* @var array
*/
protected $middlewarePriority = [
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\Authenticate::class,
\Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\Illuminate\Auth\Middleware\Authorize::class,
];
}

View File

@@ -5,6 +5,9 @@ namespace App\Http\Middleware;
use Closure;
use Illuminate\Contracts\View\Factory;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Support\Facades\Config;
use App\Models\Setup;
class AddUserToView
{
@@ -43,7 +46,10 @@ class AddUserToView
*/
public function handle($request, Closure $next)
{
Config::set('setup',$x=Setup::find(config('app.id'))->load('system'));
$this->factory->share('user',$this->user);
$this->factory->share('setup',$x);
return $next($request);
}

View File

@@ -1,21 +0,0 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Auth\Middleware\Authenticate as Middleware;
class Authenticate extends Middleware
{
/**
* Get the path the user should be redirected to when they are not authenticated.
*
* @param \Illuminate\Http\Request $request
* @return string
*/
protected function redirectTo($request)
{
if (! $request->expectsJson()) {
return route('login');
}
}
}

View File

@@ -1,17 +0,0 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode as Middleware;
class CheckForMaintenanceMode extends Middleware
{
/**
* The URIs that should be reachable while maintenance mode is enabled.
*
* @var array
*/
protected $except = [
//
];
}

View File

@@ -1,17 +0,0 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Cookie\Middleware\EncryptCookies as Middleware;
class EncryptCookies extends Middleware
{
/**
* The names of the cookies that should not be encrypted.
*
* @var array
*/
protected $except = [
//
];
}

Some files were not shown because too many files have changed in this diff Show More