Compare commits

..

270 Commits

Author SHA1 Message Date
1e617bb8be Move ANSI* commands to BBS/
Some checks failed
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 35s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m44s
Create Docker Image / Final Docker Image Manifest (push) Has been cancelled
2024-05-28 12:43:58 +10:00
b43784574c BBS ported from vbbs 2024-05-28 12:37:52 +10:00
364815e8af Setup to present different mail bundle types 2024-05-28 12:23:59 +10:00
3d43a256ba Added DynamicItem and PacketDump debug utilities 2024-05-28 10:44:33 +10:00
b460cd0196 Fix display of addresses in pkt dump 2024-05-28 09:38:55 +10:00
643f1197d6 Fix when we use newFTN, find the zone/domain if it is provided. Fix packet creation error, where Address::ftn depends on zone_id
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 1m43s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-05-27 22:22:38 +10:00
7ef9f2dbd0 Fix/optimise address creation/editing via System AKAs
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 1m46s
Create Docker Image / Final Docker Image Manifest (push) Successful in 11s
2024-05-27 21:42:03 +10:00
b102fc4d2a Fix creating a new discovered address and setting validated to true
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 1m42s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-05-27 18:04:04 +10:00
e15331ec35 No function changes. Cleanup console command cleanup
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 1m43s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-05-27 15:08:39 +10:00
3a594acc03 Fix edit of AKAs for a system. Fix css usage of label for= for the yes/no radio inputs
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 1m41s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-05-27 10:48:50 +10:00
800593d034 Optimise Zone::class to identify region/hosts/hubs 2024-05-27 10:47:42 +10:00
65b2a2d519 Fix from bug introduct by 7e0178d for echomails
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 1m44s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-05-26 22:08:39 +10:00
bf21671a1f Show system name on AbsentNodes echomail
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 1m43s
Create Docker Image / Final Docker Image Manifest (push) Successful in 12s
2024-05-26 22:01:35 +10:00
fe18968c57 Show node status with Hub Stats 2024-05-26 21:50:13 +10:00
b6639c7bfc When de-listing, remove unsent items and unsubscribe from file/echo areas.
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-05-26 21:41:02 +10:00
7e0178d183 Fix routed netmails being packed for the hub, not the destination. Added some logging for idle netmails/emails 2024-05-26 20:55:39 +10:00
03bfc9dbfc Fix path on rejected echomails, change layout of message_path quoting original message and control lines
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 1m42s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-05-26 12:35:13 +10:00
77b9bb30c4 Another fix to make sure site generated echomail is not exported to ourselfs
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 1m41s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-05-26 12:08:01 +10:00
f8cb6ccc37 Automatically mark idle nodes HOLD/DOWN/DE-LIST. Automatically validate presented addresses.
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 1m43s
Create Docker Image / Final Docker Image Manifest (push) Successful in 11s
2024-05-25 22:31:42 +10:00
0e1d5b3239 Fix text lable for Nodelist file 2024-05-25 20:03:23 +10:00
9f3b9f692a Dont show a MSGID kludge in test messages if there isnt one
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 1m44s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-05-25 14:12:04 +10:00
391e9e1e39 Fix TicProcess, save() returns a bool, not the object
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 10s
2024-05-25 11:09:34 +10:00
3555e5a91c Dont cache the mail:send query
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 1m42s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-05-25 10:33:35 +10:00
2f24e13940 Dont add a MSGID kludge if there isnt one
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 1m44s
Create Docker Image / Final Docker Image Manifest (push) Successful in 11s
2024-05-25 00:43:44 +10:00
87f495b326 Add nodelist segment creation
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 1m43s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-05-24 17:09:10 +10:00
1615b413a7 Assume f0 for DNS queries that dont pass an f in the query
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 1m40s
Create Docker Image / Final Docker Image Manifest (push) Successful in 11s
2024-05-24 12:34:28 +10:00
86c27a3f17 Show users hub connection details for their domains
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 10s
2024-05-24 11:04:25 +10:00
a687b5fd1c Change looking for mail waiting for downlinks() instead of children() 2024-05-24 09:28:17 +10:00
27956146e3 Change domain view last seen to show last time an echomail came from that system
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 1m43s
Create Docker Image / Final Docker Image Manifest (push) Successful in 11s
2024-05-23 23:28:42 +10:00
a547e29e56 Sometimes errors have URLs, so enable them to be resolved 2024-05-23 22:38:03 +10:00
de34052c3b Add constraint for hubs, which must have the same host and region for a zone
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 11s
2024-05-23 21:46:26 +10:00
cb63ec50d2 Dont cache when doing debug:zone:check 2024-05-23 21:31:17 +10:00
8b00d29db3 Update mailist to look for deleted FTNs as well
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 1m41s
Create Docker Image / Final Docker Image Manifest (push) Successful in 11s
2024-05-23 20:30:03 +10:00
f082bb0ebd Dont record us in seenby/path for local messages. Update echomail display to know if an exchomail seenby has collected the message
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-05-23 20:12:21 +10:00
4f8448563d Fix for when adding our address to path for outgoing echomails - introduced in 5fc6906
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 10s
2024-05-23 17:38:18 +10:00
710adad634 Show only validated addresses with packets for packet:system 2024-05-23 17:37:46 +10:00
5fc69067fb Fix sorting of seenby/path items in echomail/netmail. Add rogue_seenby to seenby in echomail 2024-05-23 17:37:46 +10:00
dc212d35fb Work to handle grunged packets as well as look for tearline/tagline/orgin line from the end of the content
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 1m44s
Create Docker Image / Final Docker Image Manifest (push) Successful in 11s
2024-05-22 23:24:29 +10:00
3ce6a8ed61 Record the AKAs presented 2024-05-22 22:12:38 +10:00
b398163cfd We need to use EncodeUTF our mail objects while passing them to the queue
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 1m42s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-05-22 21:37:58 +10:00
a5e9a28673 Added PRODUCT_NAME_SHORT and updated PID/TID kludge to include git hash
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 1m43s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-05-22 15:31:10 +10:00
49e40f4fb8 Generated mail from the hub wont have $model->errors defined 2024-05-22 09:21:59 +10:00
924d760c79 Fix FSC45 packets, point_id was still being validated the old way 2024-05-22 09:21:59 +10:00
58dc090c83 Use packets domain for packet validation, not remote sytems address 2024-05-22 09:21:59 +10:00
b20878e378 Kludges are not required in messages 2024-05-22 09:21:59 +10:00
b443762739 Fix processing echomail and when mail crc is calculated as it was decompressing the CompressedString 2024-05-22 09:21:59 +10:00
18f5354d0c Mail validation errors is now an object, and must be tested with ->count() 2024-05-22 09:21:59 +10:00
17e3c69f07 Fix for invalid-zone validation comparing a string with an int 2024-05-22 09:21:59 +10:00
51784df6a8 Fix missed Notification using old Message::class 2024-05-22 09:21:59 +10:00
8df6384736 Fixed checking for RESCAN kludge 2024-05-22 09:21:59 +10:00
752462d20f Update job:list, and change "subject" to "jobname" 2024-05-22 09:21:59 +10:00
72d68fa1ab Update SocketClient to support raw IP addresses 2024-05-22 09:21:59 +10:00
aaec5f8f4a Security update enabling update_nn to edit system details 2024-05-22 09:21:59 +10:00
ab2e288f06 More complete rework of packet parsing and packet generation with f279d85 - and testing passes 2024-05-22 09:21:59 +10:00
b30ab2f999 Add github hash VERSION file during build
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 1m51s
Create Docker Image / Final Docker Image Manifest (push) Successful in 11s
2024-05-19 23:30:44 +10:00
b4a42f6780 More update on security, user to edit their own system with 59ec5f5 2024-05-19 23:30:14 +10:00
f279d85b08 More complete rework of packet parsing and packet generation with 29710c 2024-05-19 23:28:45 +10:00
46f52dd56d Only auth AKAs in the same domain as us 2024-05-18 12:26:00 +10:00
59ec5f5a0c Update on security, user to edit their own system 2024-05-18 08:27:17 +10:00
29710c37c2 Complete rework of packet parsing and packet generation 2024-05-17 22:10:54 +10:00
1650d07d5c Fix recording of netmails, when they contain taglines and origin lines 2024-05-16 22:59:37 +10:00
0457b3df25 Resorting methods, no functional changes 2024-05-13 18:55:39 +10:00
731fdb0a44 When decompressing compressed messages, dont barf if we try to decompress the same attribute twice 2024-05-13 17:50:24 +10:00
6216ada5e5 Fix recording of netmails, when they contain taglines and origin lines 2024-05-13 17:50:24 +10:00
b9b5cf4214 Display msgid's smaller for Netmail/Echomail H1 headings 2024-05-13 17:06:51 +10:00
f912e81ee6 Change where processed packets are placed, if fido.packet_keep is true
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 54s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m54s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-05-12 21:56:46 +10:00
4fe5dc6ad0 Fix for session being reported negative 2024-05-12 21:56:46 +10:00
556b95c7c1 Fix when dispatching packets, and our address object has many relations already loaded, causing memory exhaustion due to recursion 2024-05-12 21:56:46 +10:00
14c505c15b Must not cache the mail waiting queries, otherwise mail/files will be resent in a query loop
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 1m42s
Create Docker Image / Final Docker Image Manifest (push) Successful in 12s
2024-05-12 14:56:18 +10:00
dd8558487c Increase default logging to 3 months 2024-05-11 21:20:05 +10:00
3ad20f969b Put back laravel-eloquent-query-cache and remove Caching from previous commit
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 1m41s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-05-11 09:10:00 +10:00
4d13199848 Some interface SQL performance improvements 2024-05-11 08:18:57 +10:00
cd2efbd1d4 Added downstream(), and fixed failing tests in RoutingTest
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 10s
2024-05-10 21:33:02 +10:00
edee0643ec Reorder functions, no functional changes 2024-05-09 21:31:50 +10:00
23159d19d5 Rework address roles, making Address::role optional, rework determining uplink/downlinks/parent/children 2024-05-09 21:22:30 +10:00
2765a27db8 Performance fix for address_merge, when there are a log of echomails to move to the new address
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 1m39s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-05-06 08:23:07 +10:00
c8ef7d065b Fix address_add validation, missing scoped to zone. Change to use shortform of $request->post() in address_add()
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 1m43s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-05-05 23:17:29 +10:00
7540ddf8f4 $o is not always defined
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 1m41s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-05-05 22:14:33 +10:00
b17fe1d2ee It seems session time is now returning a float, cast it to an int
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 1m40s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-05-05 17:41:47 +10:00
5389739920 Enable setting autohold and address validation in web UI
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 1m47s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-05-05 00:10:55 +10:00
92f964f572 Remove static cache from our_address in favour of Cache::class keyed off setup:system_id 2024-04-26 20:31:02 +10:00
9abfd88e3d Fix for AreaSecurity when presented security is null 2024-04-26 20:31:02 +10:00
e9895aee45 Added Echoarea::addresses_active() to find addresses that are connected to the area, and active 2024-04-26 20:31:02 +10:00
79b180f453 Upgrade to Laravel 11, begining of enabling network join functionality, removed QueryCacheable 2024-04-26 20:31:02 +10:00
6e376100a5 Fix System registration form presentation and validation processing
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 1m42s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-04-26 12:23:55 +10:00
1a5c1eff7b Move passkey login to a button on the login 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 1m41s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2024-04-25 20:20:43 +10:00
f42fe97902 Add user policy to manage user security
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 1m40s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-04-25 16:08:09 +10:00
ac02f37c67 We need to add git to the build for custom composer packages
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 1m40s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-04-25 15:56:08 +10:00
6a41536d57 Enable user reseting password
Some checks failed
Create Docker Image / Build Docker Image (x86_64) (push) Failing after 24s
Create Docker Image / Build Docker Image (arm64) (push) Failing after 54s
Create Docker Image / Final Docker Image Manifest (push) Has been skipped
2024-04-25 15:45:05 +10:00
527cc1d4ab Added passkey for logins 2024-04-25 15:45:05 +10:00
d90f431925 Changed layout of system/addedit 2024-04-25 15:45:05 +10:00
ceffc7ff14 Removed unnessary controller functions that just call a view, HTML/CSS consistency updates 2024-04-25 15:44:52 +10:00
001618d719 Move zone:check to debug namespace, add address:check command
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 1m37s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-04-21 22:10:12 +10:00
bba6f93fbc Code improvement to our_address(), reducing arguments 2024-04-21 21:40:55 +10:00
1c270025cf Move determination of system packet to System::class 2024-04-21 20:40:19 +10:00
8bf58f3daa Added performance indexes for echomails/echoareas
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 1m35s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-04-21 15:19:38 +10:00
3f5668292f More optimisations for users dashboard
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 10s
2024-04-21 00:28:50 +10:00
20d3776490 Revert 9299697 FIDO_STRICT to default false. Not all FTNs support sending uplinks from a *C address.
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 1m36s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-04-20 09:38:30 +10:00
7b225d8fc0 Attempt to catch dns query failures
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 1m37s
Create Docker Image / Final Docker Image Manifest (push) Successful in 11s
2024-04-16 22:01:09 +09:30
ff7ab68a54 Added filefix rescan 2024-04-16 22:00:41 +09:30
a2ff2df9f3 Move security evaluations for File/Echoareas back to model 2024-04-16 21:28:35 +09:30
9c9fd84e0a Change layout of topmenu
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 1m37s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2024-04-14 20:26:28 +10:00
5cb70da458 Optimise rendering of bbs list 2024-04-14 16:59:06 +10:00
3e561ab068 Optimise rendering of domain list 2024-04-14 16:55:52 +10:00
42cc50512f Fix topmenu dropdown rendering, Recognise POINTs in Address Type 2024-04-14 16:52:47 +10:00
bb42f418e0 Revert part of 9299697, so that our (lowest) address is selected, especially in the case for msgid creation
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 1m34s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-04-14 11:03:17 +10:00
ae0bd09a47 Add Debug command Packet:Address
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 1m35s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-04-14 10:43:07 +10:00
9299697ec1 Fix a caching pollution issue when using static
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 10s
2024-04-14 10:23:21 +10:00
bac41969a5 Optimise queries for rendering the users dashboard page
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 1m37s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2024-04-14 00:47:08 +10:00
d6e23b9a90 Optimise queries for rendering the about page
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
2024-04-13 22:41:58 +10:00
2edc41b11e Support merging addresses when both src/dst addresses are in the seenby 2024-04-13 20:54:05 +10:00
03ca4c10b1 Only add items to the queue when the queue is empty 2024-04-12 21:22:27 +10:00
1923eb429f Change wording for Internet Address for a System 2024-04-12 20:06:58 +10:00
1e08c2f6f7 Move Domain_Controller::NUMBER_MAX to Address::ADDRESS_FIELD_MAX 2024-04-12 15:29:11 +10:00
77df5746be Added gitea CI/CD configuration
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 1m35s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2024-04-10 22:34:30 +10:00
60964e27a7 Attribution to phpStorm 2024-04-10 21:28:45 +10:00
c496d131cf Fix wording on EMSI sessions 2024-03-22 08:45:44 +11:00
2ba656b1d9 Update to php 8.3, change armv7l build to arm64 2024-02-05 23:11:16 +11:00
cb09016539 Partially revert #9cf0f1e so that we create jobs by Address ID, not System ID 2024-01-10 16:58:35 +11:00
0c17391dec Updated parent containers 2024-01-10 16:56:40 +11:00
c1a1797778 If user information is not provided in a system update (for example when users update), then dont zap system_users 2023-12-20 17:18:10 +11:00
9376c6de11 We should check for subscription before checking for permissions 2023-12-20 11:20:48 +11:00
713615d8d5 Revert changes to CompressedString::class, messages were going out base64 encoded and compressed 2023-12-19 16:51:15 +11:00
01107cd3dc Added AnsiLove for rendering messages with ANSI sequences 2023-12-19 15:16:10 +11:00
0e5a04596a Disable armv7l builds 2023-12-19 12:55:27 +11:00
c9d04b64ac Enabled NetmailPolicy, users can see netmail if they are in the seen-by, a ZC or admin 2023-12-19 12:55:27 +11:00
90206f2bb5 Enable admin setting user for a system 2023-12-19 12:55:27 +11:00
ac2ee7df0c Fix for netmail notifications, fftn_id wasnt being set correctly 2023-12-19 09:13:16 +11:00
24f6af3d3b Disabling our routine to set a default on an address, it needs to be improved 2023-12-19 08:54:26 +11:00
9c8e546765 Increase some logging for binkp sessions 2023-12-18 22:44:55 +11:00
13e51724c0 Further enhancement with #9063a2a - to ensure our message addressing and content picks the right address 2023-12-18 20:43:21 +11:00
1ded66990c Enable ZC to see netmail in their zone, and point owners to see their own netmail 2023-12-18 20:43:21 +11:00
6fb7d165ae text-right should be text-end 2023-12-18 20:43:21 +11:00
fd07fb2be7 Include children in netmail for a host 2023-12-18 20:43:21 +11:00
e1c9fa12aa When rethrowing an exception, only include the message 2023-12-18 20:43:21 +11:00
7b9ab388d8 Optimise our use of items waiting and queries used. We are now using a single consistent query for each resource. 2023-12-18 20:43:21 +11:00
7af67de2a8 Fix scenario when creating a new BBS during user registration 'Attempt to read property exists on null' 2023-12-18 20:43:21 +11:00
6d9179ed37 Fix for creating a new area 'Attempt to ready property nodelist on null' 2023-12-18 20:43:21 +11:00
27c050dc38 When we have multiple addresses, add we want a specific address, return the lowest role, or if strict mode enable, return the lowest role that is higher than the target 2023-12-18 20:43:21 +11:00
301fc33d2f HubStats was not limiting the scope to the date, now that the scopeUncollected..() methods dont limit by date (changed in #5a74386) 2023-12-18 20:43:21 +11:00
5d88a5e10e our_address() now takes a domain name - missed it for hub stats 2023-12-18 20:43:21 +11:00
aae551aacf Simplify packet processing. Re-enable pkt processing tests. 2023-12-18 20:43:21 +11:00
26c80dc1c5 Move TIC testing into a file subfolder, so our test folder can have other types of resources 2023-12-18 20:43:21 +11:00
ba0d612889 Change file desc to text, since it can be larger than 255 chars 2023-12-18 20:43:21 +11:00
f6a6c13ca2 NCs should be /0 not /x 2023-12-18 20:43:21 +11:00
b9bc413b05 Fix for finding the TIC origin, it doesnt have a parent 2023-12-18 20:43:21 +11:00
caa6e629f4 Change Address::parent(),Address::children(), improved CI testing 2023-12-18 20:43:21 +11:00
541f612446 Improve our parent/children identification with points, fix our testing that was failing with NULLs and asserted out. Added zone:check so that's its easier to identify parent for FTNs 2023-12-11 18:31:38 +11:00
247cf614f3 Fix log note for PING netmails 2023-12-11 08:48:30 +11:00
ab5476d373 Remove deprecated Protocol:setClient() 2023-12-10 20:44:15 +11:00
0526500ff0 Integrate Mailer::class into System_Log::class, removed Zmodem Server/Client 2023-12-10 20:41:37 +11:00
8fc0336314 Fix for bugs implemented in #e56eca, where the message was including the 3 NULLs at the end and our header is near the end of a buffer read 2023-12-09 13:56:45 +11:00
5a74386f5a Optimisations to uncollected Items, which fix mail:send, which was only being triggered for mail unsent from yesterday 2023-12-08 15:16:49 +11:00
e56ecaa999 Fix for processing packets, where our EOM or EOP is split across buffer reads 2023-12-07 20:19:48 +11:00
ddccc44261 Coverage is now defined in CI/CD yml file 2023-12-07 12:48:26 +11:00
fa2e74eaca Changes to timew() and wtime() to leverage last 2 bits for 4 year timestamp, making msgid checking valid according to FTSC. Added a test suite for timew()/wtime(). 2023-12-07 12:07:11 +11:00
ee15274478 Enhancements to accordion displays, mainly to show chevrons indicating open status 2023-12-04 09:03:54 +11:00
1890b66dc7 Implemented Dynamic Items for data to be sent to polled systems based on data in db, like stats/nodelists 2023-12-03 18:51:46 +11:00
8f3d77b04d Implemented CLI areafix:rescan 2023-12-01 18:14:51 +11:00
049b2c7204 Change BINKP so that we send more packets in the same session, when we have more than msgs_pkt to send 2023-12-01 18:14:07 +11:00
5b7ec1a629 Add missing const to parent() and children() relations 2023-12-01 17:25:01 +11:00
535a082edd Enable overriding the DNS NS hostname 2023-11-28 19:57:47 +11:00
9cf0f1e2f4 Changed AddressPoll unique ID to be system_id, setup for memcached for all micrsoservices 2023-11-27 16:00:02 +11:00
27a3e3e24e Use ObjectIssetFix Trait instead of defining __isset() directly 2023-11-27 15:56:28 +11:00
8590bb8acc Fix the rendering of SEENBY/PATH lines in packets, which should have each new line prefixed with host 2023-11-27 09:00:32 +11:00
a19eaa3291 Enable accordion transition 2023-11-26 17:32:39 +11:00
4a0e6e67fc Added number of netmails/echomails/files processed on status page 2023-11-26 17:32:39 +11:00
1ac3583479 Implemented system heartbeat, to poll systems regularly that we havent heard from 2023-11-26 14:59:05 +11:00
6e7e09ab50 Minor changes to mail:send and job:list rendering 2023-11-26 11:32:21 +11:00
2b2482ba71 Rework crash polling, using optimised scope queries 2023-11-25 21:52:05 +11:00
b5e5decfdf Processing packets on the command line can be archives as well 2023-11-25 09:48:37 +11:00
6abf10ab0b TIC and PKT passwords are case insensitive, so convert them to uppercase when we are using them 2023-11-25 09:47:58 +11:00
4070a060c3 Use created_at not datetime on status, since datetime may be wrong by the sender 2023-11-25 09:29:08 +11:00
a13497df5f Update domain::public() to only show public domains to admins, update status to be consistent with domain::public() 2023-11-25 00:10:21 +11:00
82cee02fa8 Dont included deleted_at items for the status 2023-11-24 23:39:38 +11:00
e337a29003 Determine unsent netmails by their flags, not sent_at for the status 2023-11-24 23:39:38 +11:00
eab35d4c18 On the status page, dont show inactive addresses, zones or domains 2023-11-24 23:39:38 +11:00
4c91ed54c0 First work on a status page showing nodes with uncollected mail 2023-11-24 23:39:38 +11:00
bed5bf8acc Binkp control frames can be NULL terminated. 2023-11-24 13:15:22 +11:00
9e870858da Packet filenames can be in uppercase hex too 2023-11-24 12:18:19 +11:00
0800c48928 Use regex for received file evaluation. Fixes recording received packet names 2023-11-23 23:17:13 +11:00
455fed52ee Touch all our test files so that testing works when comparing mtimes - for TIC testing 2023-11-23 22:10:04 +11:00
19338edcb6 Optimise the queries used to display packet contents and show DBID if msgid is not available 2023-11-23 21:55:39 +11:00
b3dfca5b89 Optimise BINKP msg processing by using ltrim instead of skip_blanks. Should also address taurus mailers that add a NULL to the end of ADR messages 2023-11-23 19:11:14 +11:00
76dc90ceb3 Fixes to TIC processing that uses the Address::ftn_regex 2023-11-23 13:17:02 +11:00
a13028808a Optimise our address FTN regex 2023-11-23 12:22:39 +11:00
e5de4970d1 When originating a session, send anything received via the queue 2023-11-23 08:31:24 +11:00
7847728e52 Remove autohold on successful poll 2023-11-22 18:15:06 +11:00
b8670a5593 Change our TIC mtime comparision to compare with the actual mtime of the TIC file 2023-11-22 17:25:48 +11:00
fbcbe2c5a8 Address part of packet names is in hex 2023-11-22 16:49:14 +11:00
5f11f81be3 Fixes for TICs for nodelists 2023-11-22 16:41:14 +11:00
fcc2c23894 Our testing methods need to assert something 2023-11-22 15:58:00 +11:00
9fd8264c3f Rework TIC processing and added test cases 2023-11-22 15:58:00 +11:00
5b24ff944f Change System::match to return a single item regardless of role 2023-11-22 13:59:19 +11:00
3221d7f679 Show packet info when viewing echomail 2023-11-22 13:59:19 +11:00
f639e3ffab New attempt to making sure echomails have origin and senders path/seenby details (rework of #45d7823) 2023-11-22 13:59:19 +11:00
e8f4bf93bd Add a dontqueue option to packet::process 2023-11-22 13:59:19 +11:00
116f726885 Fix helpers checking for wtime existance 2023-11-22 10:40:28 +11:00
a74c5d5f5c Framework update 2023-11-22 10:40:28 +11:00
3a0847f13a For DNS records that we resolve for, return nodata if we dont serve that data type 2023-11-22 10:40:28 +11:00
509cdd7ea6 Fix incorrect subjects on some notifications 2023-11-22 10:40:28 +11:00
45d78233b2 Disabling adding to path - its adding to the end of the path which is not correct 2023-11-22 10:40:28 +11:00
4a870b6587 Improvements to echomail path handling, ensuring sender and pktsrc are in the path 2023-11-22 10:40:28 +11:00
67747c062a Add mtime to receiving filename, so that we dont have name clashes with systems that use the same archive name for our host 2023-11-17 16:30:19 +11:00
250e584c03 Routed Netmails should still have the netmail dest address, not the hub's address 2023-11-15 22:56:26 +11:00
7087fe9bbb Throw an exception when we cannot determine the end of the message/packet 2023-11-15 22:12:09 +11:00
3b99c409e0 When a netmail has a msgid, dont overwrite it 2023-11-15 11:19:14 +11:00
ea42a347eb As per RFC 2308, add SOA to authoriative answers with nodata, or errors 2023-11-13 07:57:01 +11:00
85243d128e Fixes for TIC processing and not identifying path/seenby correctly 2023-10-29 21:28:29 +11:00
7d82cbcf12 Since we know the zone, we know the domain name when parsing addresses 2023-10-26 11:14:54 +11:00
a886a389a8 Fix for echomail notifications, when echomails come from a point. Auto detect address when manually processing packets. 2023-10-26 11:02:36 +11:00
f9d24db9f8 Minor changes to optimise new installs 2023-10-18 20:03:23 +11:00
8ce3ce8164 Implement multiarch docker build and enable armv7l 2023-10-16 21:51:44 +11:00
a7e8cc7568 Implement HAPROXY proto support 2023-10-13 08:57:22 +11:00
c8ab8d3db3 Exported echomail should have the Hub as the OrigNet/OrigNode 2023-10-09 21:54:46 +11:00
953d3725b2 Another enhancement to the linking system 2023-10-09 17:48:26 +11:00
8332f485d1 Framework update 2023-10-09 17:47:45 +11:00
b32020e60f Nodelist import debugging to make sure we do select nodelists correctly 2023-10-07 21:09:00 +11:00
2c504c3d66 Include receive timestamp in packet:info and now using Storage::disk to find file 2023-10-07 21:09:00 +11:00
df5cc8c2d4 TIC processing fixes, was unable to find TIC file 2023-10-07 21:09:00 +11:00
4616feacda Fix some redirects now that self::class,'home' is no longer defined 2023-10-07 21:09:00 +11:00
7a9b6d5015 Change network/ to domain/view/ 2023-10-07 21:09:00 +11:00
654e7bd2aa Remove remaining ftn/ paths, no functional changes 2023-10-07 21:09:00 +11:00
b25e6f432c Rework DomainController/UserController methods and paths, no functional changes 2023-10-07 21:09:00 +11:00
27985dbf0b Rework ZoneController methods and paths, no functional changes 2023-10-07 21:09:00 +11:00
fda68bba04 Rework SystemController methods and paths, no functional changes 2023-10-07 21:09:00 +11:00
c86d8d8952 Logging to catch some hex2bin exceptions with 2/109 2023-10-06 22:52:03 +11:00
614d332fae Add ifcico to DNS query responses 2023-10-06 22:52:03 +11:00
0cabdcd3c1 Still return TXT records even if a system doesnt have an address 2023-10-06 22:52:03 +11:00
495a27cfed Enhance the system link/register selection 2023-10-06 22:52:03 +11:00
32c0088339 Rework nodelist import and ignoring addresses that we manage 2023-10-06 22:52:03 +11:00
b854cf9fe0 Better catch TIC file exceptions, enable moving TIC files if fido.packet_keep is defined 2023-10-04 22:22:01 +11:00
ce7a96ca2a Logging cosmetic changes only in Zmodem 2023-10-04 22:06:16 +11:00
28e30a05e6 Make passwords mandatory 2023-10-04 16:26:05 +11:00
e75be34afd Detach users when deleting a system 2023-10-04 15:58:46 +11:00
d82f8ac8b3 Catch bad DNS queries and reduce exception logging 2023-10-04 15:50:24 +11:00
0fcb628c11 Non functional cosmetic updates 2023-10-04 15:49:44 +11:00
c7e707c143 Attempt to catch errors creating address for dovenet mail 2023-10-04 12:17:16 +11:00
62f0c1a909 DNS server now responds to SRV and TXT records 2023-10-03 23:15:21 +11:00
073d95f605 Reduce the exception noise with queries that we dont parse correctly 2023-10-03 20:58:23 +11:00
2a50a1d795 When we dont have session() details return a blank string instead of null 2023-10-03 09:17:30 +11:00
782acad560 When processing packets on the command line, send it to the queue 2023-09-27 11:19:36 +10:00
c0c8861c08 Fix for Serialization of 'finfo' is not allowed 2023-09-24 00:01:44 +10:00
d11a2a5b8d Update nodelist import to exclude systems managed by us 2023-09-23 23:15:42 +10:00
ff04de52b5 Rework TIC processing to use Storage::disk(). Implemented handling of replaces and files that already exist 2023-09-23 22:40:17 +10:00
56544b89e1 Change background for graphics to #000000 2023-09-22 21:17:00 +10:00
2ae24b9955 Move fido configuation items into fido namespace. If keeping packets move them into a date aligned subdir 2023-09-22 15:35:08 +10:00
22c8b3df74 Respond to areafix netmails 2023-09-21 15:25:18 +10:00
2e7aecff57 Show icon to indicate address valid or not 2023-09-20 23:03:17 +10:00
b7c1c97cf7 Catch DNS Query that fail unpack() 2023-09-20 22:26:35 +10:00
612efda945 Process packet seenby/path/via lines when saving echomail/netmail 2023-09-20 21:37:18 +10:00
7fedf88d8c Hopefully a fix to stop clrghouz creating systems called Discovered System 2023-09-19 22:16:25 +10:00
11f9adf11a Fix seenby sort order, with flatten domains 2023-09-19 17:28:25 +10:00
eebe8a159d Fix address reported when scheduling a poll 2023-09-19 13:54:35 +10:00
3a35bce9e7 Changing System::match() to not include NC, some debugging updates 2023-09-19 11:29:08 +10:00
5e67be5ba1 Alert message for echomails with security violations is using wrong address 2023-09-19 11:29:08 +10:00
f315c71ca9 Fix path to public/logo 2023-09-19 11:29:08 +10:00
4343774079 Dont abort a session when there is an invalid FTN presented 2023-09-19 11:29:08 +10:00
eb40f94e37 Fix for binkd when remote present binkp protocol in brackets 2023-09-19 11:29:08 +10:00
cc04ddd7b3 More work to ensure messages from a node are valid for the domain, and fix domain flatten to check for zone if one is supplied 2023-09-17 15:54:47 +10:00
e611dcbe11 Filter available echoareas/fileareas based on security 2023-09-17 00:14:46 +10:00
073fa466d6 Added mail:list 2023-09-16 22:12:19 +10:00
708d9a9f67 More work to decommission rogue_path 2023-09-16 21:39:34 +10:00
c1d6d48a3c Dont enable rogue_path - it looses our true path for messages - instead create addresses in the path we dont know about. 2023-09-15 16:59:46 +10:00
6e133770fc An enhancement to ensure that flatten domains gets the correct FTN 2023-09-15 15:20:19 +10:00
a991db788e For AddressPoll, force using our file cache - seems sometimes the key doesnt release with memcached 2023-09-15 14:28:07 +10:00
096e37ef35 Removed packet cache, it wasnt used and not needed since we can queue large packets. Renamed to for consistent variable when using Packet::process() 2023-09-15 08:14:27 +10:00
2f878b6e64 Added filearea import 2023-09-14 23:42:25 +10:00
ec5c28a03e Added ignore_crc option to nodelist import 2023-09-14 23:06:02 +10:00
ff8c370d86 Move packet processing into a job 2023-09-13 20:58:22 +10:00
285 changed files with 17358 additions and 8002 deletions

View File

@ -2,7 +2,8 @@ APP_NAME="Clearing Houz"
APP_ENV=production
APP_KEY=
APP_DEBUG=false
APP_URL=http://localhost
APP_URL=http://clrghouz
APP_TIMEZONE=
LOG_CHANNEL=stack
LOG_LEVEL=info
@ -10,8 +11,8 @@ LOG_LEVEL=info
DB_CONNECTION=pgsql
DB_HOST=postgres
DB_PORT=5432
DB_DATABASE=laravel
DB_USERNAME=laravel
DB_DATABASE=clrghouz
DB_USERNAME=clrghouz
DB_PASSWORD=
#DB_SSLMODE=prefer
#DB_SSLROOTCERT=/var/www/html/config/ssl/ca.crt
@ -19,6 +20,7 @@ DB_PASSWORD=
#DB_SSLKEY=/var/www/html/config/ssl/client.key
BROADCAST_DRIVER=log
MEMCACHED_HOST=memcached
CACHE_DRIVER=memcached
QUEUE_CONNECTION=database
SESSION_DRIVER=file
@ -46,6 +48,7 @@ MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
FIDO_DIR=fido
FIDO_PACKET_KEEP=
FIDO_STRICT=false
FILESYSTEM_DISK=s3
AWS_ACCESS_KEY_ID=

View File

@ -2,7 +2,7 @@ APP_NAME="Clearing Houz Testing"
APP_ENV=testing
APP_KEY=base64:FiMSvv4J7jDfy6W/sHrQ9YImuUYaxynYCcXQJwp/6Tc=
APP_DEBUG=true
APP_URL=http://localhost
APP_URL=http://clrghouz
APP_TIMEZONE=Australia/Melbourne
LOG_CHANNEL=stderr
@ -41,7 +41,8 @@ PUSHER_APP_CLUSTER=mt1
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
FIDO_DIR=fido
FIDO_DIR=test
FIDO_DIR_FILES=local
FIDO_PACKET_KEEP=true
FILESYSTEM_DISK=s3

View File

@ -0,0 +1,147 @@
name: Create Docker Image
run-name: ${{ gitea.actor }} Building Docker Image 🐳
on: [push]
env:
VERSION: latest
DOCKER_HOST: tcp://127.0.0.1:2375
jobs:
# test:
# strategy:
# matrix:
# arch:
# - x86_64
# # arm64
#
# name: Test Application
# runs-on: docker-${{ matrix.arch }}
# container:
# image: gitea.dege.au/docker/php:8.3-fpm-pgsql-server-test
#
# steps:
# - name: Environment Setup
# run: |
# # If we have a proxy use it
# if [ -n "${HTTP_PROXY}" ]; then echo "HTTP PROXY [${HTTP_PROXY}]"; sed -i -e s'/https/http/' /etc/apk/repositories; fi
# # Some pre-reqs
# apk add git nodejs
# ## Some debugging info
# # env|sort
#
# - name: Code Checkout
# uses: actions/checkout@v4
#
# - name: Run Tests
# run: |
# mv .env.testing .env
# # Install Composer and project dependencies.
# mkdir -p ${COMPOSER_HOME}
# if [ -n "${{ secrets.COMPOSER_GITHUB_TOKEN }}" ]; then echo ${{ secrets.COMPOSER_GITHUB_TOKEN }} > ${COMPOSER_HOME}/auth.json; fi
# composer install
# # Generate an application key. Re-cache.
# php artisan key:generate
# php artisan migrate
# php artisan db:seed
# # run laravel tests
# touch storage/app/test/*ZIP storage/app/test/file/*
# XDEBUG_MODE=coverage php vendor/bin/phpunit --coverage-text --colors=never
build:
strategy:
matrix:
arch:
- x86_64
- arm64
# needs: [test]
name: Build Docker Image
runs-on: docker-${{ matrix.arch }}
container:
image: docker:dind
privileged: true
env:
ARCH: ${{ matrix.arch }}
VERSIONARCH: ${{ env.VERSION }}-${{ env.ARCH }}
steps:
- name: Environment Setup
run: |
# If we have a proxy use it
if [ -n "${HTTP_PROXY}" ]; then echo "HTTP PROXY [${HTTP_PROXY}]"; sed -i -e s'/https/http/' /etc/apk/repositories; fi
# Some pre-reqs
apk add git curl nodejs
# Start docker
( 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 }}"
- name: Registry FQDN Setup
id: registry
run: |
registry=${{ github.server_url }}
echo "registry=${registry##http*://}" >> "$GITHUB_OUTPUT"
- name: Container Registry Login
uses: docker/login-action@v2
with:
registry: ${{ steps.registry.outputs.registry }}
username: ${{ gitea.actor }}
password: ${{ secrets.PKG_WRITE_TOKEN }}
- name: Code Checkout
uses: actions/checkout@v4
- name: Record version
run: |
pwd
ls -al
echo ${GITHUB_SHA::8} > VERSION
cat VERSION
- name: Build and Push Docker Image
uses: docker/build-push-action@v5
with:
context: .
file: docker/Dockerfile
push: true
tags: "${{ steps.registry.outputs.registry }}/${{ env.GITHUB_REPOSITORY }}:${{ env.VERSIONARCH }}"
manifest:
name: Final Docker Image Manifest
runs-on: docker-x86_64
container:
image: docker:dind
privileged: true
needs: [build]
steps:
- name: Environment Setup
run: |
# If we have a proxy use it
if [ -n "${HTTP_PROXY}" ]; then echo "HTTP PROXY [${HTTP_PROXY}]"; sed -i -e s'/https/http/' /etc/apk/repositories; fi
# Some pre-reqs
apk add git curl nodejs
# Start docker
( dockerd --host=tcp://0.0.0.0:2375 --tls=false & ) && sleep 3
- name: Registry FQDN Setup
id: registry
run: |
registry=${{ github.server_url }}
echo "registry=${registry##http*://}" >> "$GITHUB_OUTPUT"
- name: Container Registry Login
uses: docker/login-action@v2
with:
registry: ${{ steps.registry.outputs.registry }}
username: ${{ gitea.actor }}
password: ${{ secrets.PKG_WRITE_TOKEN }}
- name: Build Docker Manifest
run: |
docker manifest create ${{ steps.registry.outputs.registry }}/${{ env.GITHUB_REPOSITORY }}:${{ env.VERSION }} \
${{ steps.registry.outputs.registry }}/${{ env.GITHUB_REPOSITORY }}:${{ env.VERSION }}-x86_64 \
${{ steps.registry.outputs.registry }}/${{ env.GITHUB_REPOSITORY }}:${{ env.VERSION }}-arm64
docker manifest push --purge ${{ steps.registry.outputs.registry }}/${{ env.GITHUB_REPOSITORY }}:${{ env.VERSION }}

View File

@ -1,14 +0,0 @@
stages:
- test
- build
# This folder is cached between builds
# http://docs.gitlab.com/ce/ci/yaml/README.html#cache
cache:
key: ${CI_JOB_NAME_SLUG}-${CI_COMMIT_REF_SLUG}
paths:
- vendor/
include:
- .gitlab-test.yml
- .gitlab-docker-x86_64.yml

View File

@ -1,27 +0,0 @@
docker:
variables:
VERSION: latest
DOCKER_HOST: tcp://docker:2375
stage: build
image: docker:latest
services:
- docker:dind
before_script:
- docker info && docker version
- echo "$CI_JOB_TOKEN" | docker login -u "$CI_REGISTRY_USER" "$CI_REGISTRY" --password-stdin
- if [ -n "$GITHUB_TOKEN" ]; then cat $GITHUB_TOKEN |base64 -d > auth.json; fi
script:
- if [ -f init ]; then chmod 500 init; fi
- echo -n ${CI_COMMIT_SHORT_SHA} > VERSION
- rm -rf vendor/ database/schema database/seeders database/factories/*
- docker build -t ${CI_REGISTRY_IMAGE}:${VERSION} .
- docker push ${CI_REGISTRY_IMAGE}:${VERSION}
tags:
- docker
- x86_64
only:
- master

View File

@ -1,42 +0,0 @@
test:
image: ${CI_REGISTRY}/leenooks/php:8.1-fpm-alpine-pgsql-server-test
stage: test
# NOTE: This service is dependant on project file configuration, which is not there if the cache was deleted
# resulting in the testing to fail on the first run.
services:
- name: postgres:15-alpine
alias: postgres-test
variables:
POSTGRES_USER: test
POSTGRES_PASSWORD: test
tags:
- php
only:
- master
before_script:
- mv .env.testing .env
# Install Composer and project dependencies.
- mkdir -p ${COMPOSER_HOME}
- if [ -n "$GITHUB_TOKEN" ]; then cat $GITHUB_TOKEN |base64 -d > ${COMPOSER_HOME}/auth.json; fi
- composer install
# Generate an application key. Re-cache.
- php artisan key:generate
- php artisan migrate
- php artisan db:seed
script:
# run laravel tests
- XDEBUG_MODE=coverage php vendor/bin/phpunit --coverage-text --colors=never
# run frontend tests
# if you have any task for testing frontend
# set it in your package.json script
# comment this out if you don't have a frontend test
# npm test

View File

@ -28,7 +28,13 @@ class CompressedString implements CastsAttributes
? stream_get_contents($value)
: $value;
// If we get an error decompressing, it might not be zstd (or its already been done)
try {
return $value ? zstd_uncompress(base64_decode($value)) : '';
} catch (\ErrorException $e) {
return $value;
}
}
/**

12
app/Classes/Dynamic.php Normal file
View File

@ -0,0 +1,12 @@
<?php
namespace App\Classes;
/**
* Dynamic files that are sent to systems during a mailer session
*/
abstract class Dynamic
{
abstract public function __toString(): string;
abstract public function getName(): string;
}

View File

@ -0,0 +1,125 @@
<?php
namespace App\Classes\Dynamic;
use Carbon\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use App\Classes\Dynamic;
use App\Models\Address;
/**
* This method will generate the hub status for an upstream Host/RC/ZC
*
* Arg is a collection of arguments. We only understand
* + name - name of file to give to remote
*/
class HubStats extends Dynamic
{
private const LOGKEY = 'DHS';
private string $name = '';
public function __construct(private Address $ao,Collection $arg)
{
Log::debug(sprintf('%s:- Generating Hub Stats for [%s] with arguments',self::LOGKEY,$ao->ftn),['args'=>$arg]);
$this->name = $arg->get('name');
}
public function __toString(): string
{
$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']);
$header = "| %-12s | %4d | %3d | %3d | %16s | %5s | %5s |\r\n";
$output = sprintf("Hub Status for [%s] as at [%s]\r\n",our_address($this->ao)->ftn,$date);
$output .= "\r";
$output .= "+--------------+------+-----+-----+------------------+-------+-------+\r\n";
$output .= "| FTN | ECHO | NET |FILES| LAST SESSION | MODE |AUTOHLD|\r\n";
$output .= "+--------------+------+-----+-----+------------------+-------+-------+\r\n";
$havedown = FALSE;
$havehold = FALSE;
foreach($r->get() as $o) {
if ($o->uncollected_echomail > 10000)
$o->uncollected_echomail = 9999;
if ($o->uncollected_netmail > 10000)
$o->uncollected_netmail = 9999;
if ($o->uncollected_files > 10000)
$o->uncollected_files = 9999;
if ((! $havedown) && $o->is_down)
$havedown = TRUE;
if ((! $havehold) && $o->is_hold)
$havehold = TRUE;
$output .= sprintf($header,
sprintf('%s %s',$o->ftn4d,$o->is_down ? 'd' : ($o->is_hold ? 'h' : ' ')),
$o->uncollected_echomail ?? 0,
$o->uncollected_netmail ?? 0,
$o->uncollected_files ?? 0,
$o->system->last_session?->format('Y-m-d H:i'),
is_null($o->system->pollmode) ? 'HOLD' : ($o->system->pollmode ? 'CRASH' : 'DAILY'),
$o->system->autohold ? 'YES' : 'NO');
}
$output .= "+--------------+------+-----+-----+------------------+-------+-------+\r\n";
$output .= "\r\n";
if ($havehold)
$output .= "(h) Node is on HOLD status.\r\n";
if ($havedown)
$output .= "(d) Node is on DOWN status.\r\n";
return $output;
}
public function getName(): string
{
return $this->name ?: 'hubstats.txt';
}
}

View File

@ -0,0 +1,162 @@
<?php
namespace App\Classes\Dynamic;
use Carbon\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use App\Classes\Dynamic;
use App\Models\{Address,System};
/**
* This method will generate a nodelist for an upstream Host/RC/ZC/NC
*
* // @todo Only generate if nodes have been updated, added or removed
*/
class NodelistSegment extends Dynamic
{
private const LOGKEY = 'DNL';
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->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'),
);
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]);
}
public function __toString(): string
{
return $this->crc($this->generate($this->our_address));
}
private function crc(string $text): string
{
return sprintf(";A %s Nodelist for %s -- Day number %d : %s\r\n",
$this->our_address->role_name,
$this->now->format('l, M d, Y'),
$this->now->format('z'),
crc16($text)
).$text;
}
private function flags(Address $ao): Collection
{
$result = collect();
if (($ao->system->pollmode === TRUE) || in_array($ao->role_name,['ZC','RC','NC','HC']))
$result->push('CM');
if ($ao->system->address) {
$result->push(sprintf('INA:%s',$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 : ''));
if (($x=$ao->system->mailers->pluck('name')->search('EMSI')) !== FALSE)
$result->push(sprintf('ITN%s',(($y=$ao->system->mailers->get($x)->pivot->port) !== 23) ? ':'.$y : ''));
}
return $result;
}
private function entry(Address $ao): string
{
$format = '%s,%d,%s,%s,%s,%s,300';
$prefix = '';
$address = $ao->node_id;
if ((! $ao->system->address) || $ao->isPrivate) {
$prefix = 'Pvt';
} elseif ($ao->isHold) {
$prefix = 'Hold';
} elseif ($ao->isDown) {
$prefix = 'Down';
} else
switch ($ao->role_id) {
case Address::NODE_ZC:
$prefix = 'Zone';
$address = $ao->zone->zone_id;
break;
case Address::NODE_RC:
$prefix = 'Region';
$address = $ao->region_id;
break;
case Address::NODE_NC:
$prefix = 'Host';
$address = $ao->host_id;
break;
case Address::NODE_HC:
$prefix = 'Hub';
break;
case Address::NODE_NN:
break;
case Address::NODE_POINT:
throw new \Exception(sprintf('We have no method to include points in the nodelist [%s]',$ao->ftn));
default:
throw new \Exception(sprintf('Unknown role [%d] for [%s]',$ao->role,$ao->ftn));
}
return sprintf($format,
$prefix,
$address,
$this->format($ao->system->name),
$this->format($ao->system->location),
$this->format($ao->system->sysop),
$ao->system->phone ?: '-Unpublished-',
).(($x=$this->flags($ao)->join(',')) ? sprintf(',%s',$x) : '');
}
private function format(string $string): string
{
return str_replace(' ','_',str_replace(',','',$string));
}
private function generate(Address $ao): string
{
$result = collect();
$so = System::createUnknownSystem();
$result->push($this->entry($ao));
foreach ($ao->children() as $oo) {
// We dont include points in the ndoelist
if ($oo->point_id)
continue;
// We exclude discovered systems
if ($oo->system_id == $so->id)
continue;
$result->push($this->generate($oo) ?: $this->entry($oo));
}
return $result->join("\n");
}
public function getName(): string
{
return $this->name;
}
}

View File

@ -11,7 +11,7 @@ abstract class FTN
public function __get(string $key)
{
switch ($key) {
case 'fftn':
case 'fftn_t':
return sprintf('%d:%d/%d.%d',
$this->fz,
$this->fn,
@ -19,7 +19,7 @@ abstract class FTN
$this->fp,
).($this->zone ? sprintf('@%s',$this->zone->domain->name) : '');
case 'tftn':
case 'tftn_t':
return sprintf('%d:%d/%d.%d',
$this->tz,
$this->tn,
@ -27,30 +27,16 @@ abstract class FTN
$this->tp,
).($this->zone ? sprintf('@%s',$this->zone->domain->name) : '');
case 'fftn_o':
return Address::findFTN($this->fftn);
case 'tftn_o':
return Address::findFTN($this->tftn);
case 'fftn':
return Address::findFTN($this->fftn_t);
case 'tftn':
return Address::findFTN($this->tftn_t);
default:
throw new \Exception('Unknown key: '.$key);
}
}
/**
* Determine if a line is a kludge line.
*
* @param string $kludge
* @param string $string
* @return string
*/
protected function kludge(string $kludge,string $string)
{
return (preg_match("/^{$kludge}/",$string))
? chop(preg_replace("/^{$kludge}/",'',$string),"\r")
: FALSE;
}
/**
* This function creates our unpack header
*

File diff suppressed because it is too large Load Diff

View File

@ -5,23 +5,38 @@ namespace App\Classes\FTN;
use Carbon\Carbon;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
use Symfony\Component\HttpFoundation\File\File;
use App\Classes\FTN as FTNBase;
use App\Models\{Address,Software,System,Zone};
use App\Exceptions\InvalidPacketException;
use App\Models\{Address,Domain,Echomail,Netmail,Software,System,Zone};
use App\Notifications\Netmails\EchomailBadAddress;
/**
* Represents the structure of a message bundle
* Represents a Fidonet Packet, that contains an array of messages.
*
* Thus this object is iterable as an array of Echomail::class or Netmail::class.
*/
class Packet extends FTNBase implements \Iterator, \Countable
abstract class Packet extends FTNBase implements \Iterator, \Countable
{
private const LOGKEY = 'PKT';
private const BLOCKSIZE = 1024;
protected const PACKED_MSG_LEAD = "\02\00";
protected const PACKED_END = "\00\00";
public const MSG_TYPE2 = 1<<0;
public const MSG_TYPE4 = 1<<2;
// @todo Rename this regex to something more descriptive, ie: FILENAME_REGEX
public const regex = '([[:xdigit:]]{4})(?:-(\d{4,10}))?-(.+)';
/**
* Packet types we support, in specific order for auto-detection to work
*
* @var string[]
*/
public const PACKET_TYPES = [
'2.2' => FTNBase\Packet\FSC45::class,
'2+' => FTNBase\Packet\FSC48::class,
@ -30,127 +45,17 @@ class Packet extends FTNBase implements \Iterator, \Countable
];
protected array $header; // Packet Header
protected ?string $name; // Packet name
protected ?string $name = NULL; // Packet name
public File $file; // Packet filename
public Collection $messages; // Messages in the Packet
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
public Collection $errors; // Messages that fail validation
public bool $use_cache = FALSE; // Use a cache for messages.
protected int $index; // Our array index
protected $pass_p = NULL; // Overwrite the packet password (when packing messages)
/**
* @param string|null $header
* @throws \Exception
*/
public function __construct(string $header=NULL)
{
$this->messages = collect();
$this->errors = collect();
$this->domain = NULL;
$this->name = NULL;
if ($header)
$this->header = unpack(self::unpackheader(static::HEADER),$header);
}
/**
* @throws \Exception
*/
public function __get($key)
{
switch ($key) {
// From Addresses
case 'fz': return Arr::get($this->header,'ozone');
case 'fn': return Arr::get($this->header,'onet');
case 'ff': return Arr::get($this->header,'onode');
case 'fp': return Arr::get($this->header,'opoint');
case 'fd': return rtrim(Arr::get($this->header,'odomain',"\x00"));
// To Addresses
case 'tz': return Arr::get($this->header,'dzone');
case 'tn': return Arr::get($this->header,'dnet');
case 'tf': return Arr::get($this->header,'dnode');
case 'tp': return Arr::get($this->header,'dpoint');
case 'td': return rtrim(Arr::get($this->header,'ddomain',"\x00"));
case 'date':
return Carbon::create(
Arr::get($this->header,'y'),
Arr::get($this->header,'m')+1,
Arr::get($this->header,'d'),
Arr::get($this->header,'H'),
Arr::get($this->header,'M'),
Arr::get($this->header,'S')
);
case 'password':
return rtrim(Arr::get($this->header,$key),"\x00");
case 'fftn':
case 'fftn_o':
case 'tftn':
case 'tftn_o':
return parent::__get($key);
case 'software':
$code = Arr::get($this->header,'prodcode-hi')<<8|Arr::get($this->header,'prodcode-lo');
Software::unguard();
$o = Software::singleOrNew(['code'=>$code,'type'=>Software::SOFTWARE_TOSSER]);
Software::reguard();
return $o;
case 'software_ver':
return sprintf('%d.%d',Arr::get($this->header,'prodrev-maj'),Arr::get($this->header,'prodrev-min'));
case 'capability':
// This needs to be defined in child classes, since not all children have it
return NULL;
// Packet Type
case 'type':
return static::TYPE;
// Packet name:
case 'name':
return $this->{$key} ?: sprintf('%08x',timew());
default:
throw new \Exception('Unknown key: '.$key);
}
}
/**
* Return the packet
*
* @return string
* @throws \Exception
*/
public function __toString(): string
{
$return = $this->header();
foreach ($this->messages as $o) {
if ($o->packed)
$return .= self::PACKED_MSG_LEAD.$o;
}
$return .= "\00\00";
return $return;
}
/* STATIC */
/**
* Site of the packet header
*
* @return int
*/
public static function header_len(): int
{
return collect(static::HEADER)->sum(function($item) { return Arr::get($item,2); });
}
/* ABSTRACT */
/**
* This function is intended to be implemented in child classes to test if the packet
@ -160,23 +65,32 @@ class Packet extends FTNBase implements \Iterator, \Countable
* @param string $header
* @return bool
*/
public static function is_type(string $header): bool
abstract public static function is_type(string $header): bool;
abstract protected function header(): string;
/* STATIC */
/**
* Size of the packet header
*
* @return int
*/
public static function header_len(): int
{
return FALSE;
return collect(static::HEADER)->sum(function($item) { return Arr::get($item,2); });
}
/**
* Process a packet file
*
* @param mixed $f
* @param mixed $f File handler returning packet data
* @param string $name
* @param int $size
* @param System|null $system
* @param bool $use_cache
* @param Domain|null $domain
* @return Packet
* @throws InvalidPacketException
*/
public static function process(mixed $f,string $name,int $size,System $system=NULL,bool $use_cache=FALSE): self
public static function process(mixed $f,string $name,int $size,Domain $domain=NULL): self
{
Log::debug(sprintf('%s:+ Opening Packet [%s] with size [%d]',self::LOGKEY,$name,$size));
@ -207,123 +121,185 @@ class Packet extends FTNBase implements \Iterator, \Countable
if (! $o)
throw new InvalidPacketException('Cannot determine type of packet.');
$o->use_cache = $use_cache;
$o->name = $name;
$x = fread($f,2);
if (strlen($x) === 2) {
// End of Packet?
if ((strlen($x) === 2) && ($x === "\00\00"))
if ($x === "\00\00")
return $o;
// Messages start with self::PACKED_MSG_LEAD
if ((strlen($x) === 2) && ($x !== self::PACKED_MSG_LEAD))
elseif ($x !== self::PACKED_MSG_LEAD)
throw new InvalidPacketException('Not a valid packet: '.bin2hex($x));
// No message attached
else if (! strlen($x))
throw new InvalidPacketException('No message in packet: '.bin2hex($x));
} else
throw new InvalidPacketException('Not a valid packet, not EOP or SOM:'.bin2hex($x));
$o->zone = $system?->zones->firstWhere('zone_id',$o->fz);
Log::info(sprintf('%s:- Packet [%s] is a [%s] packet, dated [%s]',self::LOGKEY,$o->name,get_class($o),$o->date));
// If zone is null, we'll take the zone from the packet
if (! $o->zone)
$o->zone = Zone::where('zone_id',$o->fz)->where('default',TRUE)->single();
// Work out the packet zone
if ($o->fz && ($o->fd || $domain)) {
$o->zone = Zone::select('zones.*')
->join('domains',['domains.id'=>'zones.domain_id'])
->where('zone_id',$o->fz)
->where('name',$o->fd ?: $domain->name)
->single();
}
$buf_ptr = 0;
$message = '';
$readbuf = '';
$last = '';
// 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));
while ($buf_ptr || (! feof($f) && ($readbuf=fread($f,self::BLOCKSIZE)))) {
if (! $buf_ptr)
$o->zone = Zone::where('zone_id',$o->fz)
->where('default',TRUE)
->singleOrFail();
}
$message = ''; // Current message we are building
$msgbuf = '';
$leader = Message::header_len()+strlen(self::PACKED_MSG_LEAD);
// We loop through reading from the buffer, to find our end of message tag
while ((! feof($f) && ($readbuf=fread($f,$leader)))) {
$read_ptr = ftell($f);
$msgbuf .= $readbuf;
// Packed messages are Message::HEADER_LEN, prefixed with self::PACKED_MSG_LEAD
if (strlen($message) < (Message::HEADER_LEN+strlen(self::PACKED_MSG_LEAD))) {
$addchars = (Message::HEADER_LEN+strlen(self::PACKED_MSG_LEAD))-strlen($message);
$message .= substr($readbuf,$buf_ptr,$addchars);
$buf_ptr += $addchars;
// See if we have our EOM/EOP marker
if ((($end=strpos($msgbuf,"\x00".self::PACKED_MSG_LEAD,$leader)) !== FALSE)
|| (($end=strpos($msgbuf,"\x00".self::PACKED_END,$leader)) !== FALSE))
{
// Parse our message
$o->parseMessage(substr($msgbuf,0,$end));
// If our buffer wasnt big enough...
if ($buf_ptr >= strlen($readbuf)) {
$buf_ptr = 0;
$msgbuf = substr($msgbuf,$end+3);
continue;
}
}
// Take 2 chars from the buffer and check if we have our end packet signature
if ($last && ($buf_ptr === 0)) {
$last .= substr($readbuf,0,2);
if (($end=strpos($last,"\x00".self::PACKED_MSG_LEAD,$buf_ptr)) !== FALSE) {
$o->parseMessage(substr($message,0,$end-2));
$last = '';
$message = '';
$buf_ptr = 1+$end;
// Loop to rebuild our header for the next message
// If we have more to read
} elseif ($read_ptr < $size) {
continue;
}
$last = '';
// If we get here
throw new InvalidPacketException(sprintf('Cannot determine END of message/packet: %s|%s',get_class($o),hex_dump($message)));;
}
if (($end=strpos($readbuf,"\x00".self::PACKED_MSG_LEAD,$buf_ptr)) === FALSE) {
// Just in case our packet break is at the end of the buffer
$last = substr($readbuf,-2);
if ((str_contains($last,"\x00")) && ($size-$read_ptr > 2)) {
$message .= substr($readbuf,$buf_ptr);
$buf_ptr = 0;
continue;
}
$last = '';
$end = strpos($readbuf,"\x00\x00\x00",$buf_ptr);
}
// See if we have found the end of the packet, if not read more.
if ($end === FALSE && ($read_ptr < $size)) {
$message .= substr($readbuf,$buf_ptr);
$buf_ptr = 0;
continue;
} else {
$message .= substr($readbuf,$buf_ptr,$end-$buf_ptr);
$buf_ptr = $end+3;
if ($buf_ptr >= strlen($readbuf))
$buf_ptr = 0;
}
// Look for the next message
$o->parseMessage($message);
$message = '';
}
// If our message is still set, then we have an unprocessed message
if ($message)
$o->parseMessage($message);
if ($msgbuf)
throw new InvalidPacketException(sprintf('Unprocessed data in packet: %s|%s',get_class($o),hex_dump($msgbuf)));
return $o;
}
/**
* Location of the version
*
* @return int
* @param string|null $header
* @throws \Exception
*/
public static function version_offset(): int
public function __construct(string $header=NULL)
{
return Arr::get(collect(static::HEADER)->get('type'),0);
$this->messages = collect();
$this->errors = collect();
if ($header)
$this->header = unpack(self::unpackheader(static::HEADER),$header);
}
public static function version_offset_len(): int
/**
* @throws \Exception
*/
public function __get($key)
{
return Arr::get(collect(static::HEADER)->get('type'),2);
//Log::debug(sprintf('%s:/ Requesting key for Packet::class [%s]',self::LOGKEY,$key));
switch ($key) {
// From Addresses
case 'fz': return Arr::get($this->header,'ozone');
case 'fn': return Arr::get($this->header,'onet');
case 'ff': return Arr::get($this->header,'onode');
case 'fp': return Arr::get($this->header,'opoint');
case 'fd': return rtrim(Arr::get($this->header,'odomain',"\x00"));
// To Addresses
case 'tz': return Arr::get($this->header,'dzone');
case 'tn': return Arr::get($this->header,'dnet');
case 'tf': return Arr::get($this->header,'dnode');
case 'tp': return Arr::get($this->header,'dpoint');
case 'td': return rtrim(Arr::get($this->header,'ddomain',"\x00"));
case 'date':
return Carbon::create(
Arr::get($this->header,'y'),
Arr::get($this->header,'m')+1,
Arr::get($this->header,'d'),
Arr::get($this->header,'H'),
Arr::get($this->header,'M'),
Arr::get($this->header,'S')
);
case 'password':
return rtrim(Arr::get($this->header,$key),"\x00");
case 'fftn_t':
case 'fftn':
case 'tftn_t':
case 'tftn':
return parent::__get($key);
case 'product':
return Arr::get($this->header,'prodcode-hi')<<8|Arr::get($this->header,'prodcode-lo');
case 'software':
Software::unguard();
$o = Software::singleOrNew(['code'=>$this->product,'type'=>Software::SOFTWARE_TOSSER]);
Software::reguard();
return $o;
case 'software_ver':
return sprintf('%d.%d',Arr::get($this->header,'prodrev-maj'),Arr::get($this->header,'prodrev-min'));
case 'capability':
// This needs to be defined in child classes, since not all children have it
return NULL;
// Packet Type
case 'type':
return static::TYPE;
// Packet name:
case 'name':
return $this->{$key} ?: sprintf('%08x',timew());
case 'messages':
return $this->{$key};
default:
throw new \Exception('Unknown key: '.$key);
}
}
/**
* Return the packet
*
* @return string
* @throws \Exception
*/
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;
}
/* INTERFACE */
@ -336,14 +312,14 @@ class Packet extends FTNBase implements \Iterator, \Countable
return $this->messages->count();
}
public function current(): Message
public function current(): Echomail|Netmail
{
return $this->use_cache ? unserialize(Cache::pull($this->key())) : $this->messages->get($this->index);
return $this->messages->get($this->index);
}
public function key(): mixed
{
return $this->use_cache ? $this->messages->get($this->index) : $this->index;
return $this->index;
}
public function next(): void
@ -358,7 +334,7 @@ class Packet extends FTNBase implements \Iterator, \Countable
public function valid(): bool
{
return (! is_null($this->key())) && ($this->use_cache ? Cache::has($this->key()) : $this->messages->has($this->key()));
return (! is_null($this->key())) && $this->messages->has($this->key());
}
/* METHODS */
@ -369,6 +345,7 @@ class Packet extends FTNBase implements \Iterator, \Countable
* @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
{
@ -394,7 +371,7 @@ class Packet extends FTNBase implements \Iterator, \Countable
'H' => $date->format('H'), // Hour
'M' => $date->format('i'), // Minute
'S' => $date->format('s'), // Second
'password' => (! is_null($passwd)) ? $passwd : $o->session('pktpass'), // Packet Password
'password' => strtoupper((! is_null($passwd)) ? $passwd : $o->session('pktpass')), // Packet Password
];
}
@ -402,12 +379,38 @@ class Packet extends FTNBase implements \Iterator, \Countable
* 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;
$this->fftn_p = our_address($ao);
return $this;
}
/**
* Generate a packet
*
* @return string
*/
public function generate(): string
{
return (string)$this;
}
public function mail(Collection $msgs): self
{
$this->messages = $msgs;
return $this;
}
/**
* Parse a message in a mail packet
*
@ -416,113 +419,100 @@ class Packet extends FTNBase implements \Iterator, \Countable
*/
private function parseMessage(string $message): void
{
Log::info(sprintf('%s:Processing message [%d] bytes',self::LOGKEY,strlen($message)));
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) {
Log::info(sprintf('%s:- Message [%s] has errors',self::LOGKEY,$msg->msgid));
if ($msg->errors->count()) {
Log::info(sprintf('%s:- Message [%s] has [%d] errors',self::LOGKEY,$msg->msgid ?: 'No ID',$msg->errors->count()));
// If the from address doenst exist, we'll create a new entry
if ($msg->errors->messages()->has('to') && $msg->tzone) {
try {
// @todo Need to work out the correct region for the host_id
Address::unguard();
$ao = Address::firstOrNew([
'zone_id' => $msg->tzone->id,
//'region_id' => 0,
'host_id' => $msg->tn,
'node_id' => $msg->tf,
'point_id' => $msg->tp,
'active' => TRUE,
]);
Address::reguard();
if (is_null($ao->region_id))
$ao->region_id = $ao->host_id;
// 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));
if (! $msg->kludges->get('RESCANNED'))
Notification::route('netmail',$this->fftn)->notify(new EchomailBadAddress($msg));
} catch (\Exception $e) {
Log::error(sprintf('%s:! Error finding/creating TO address [%s] for message',self::LOGKEY,$msg->tboss),['error'=>$e->getMessage()]);
$this->errors->push($msg);
return;
}
$ao->role = Address::NODE_UNKNOWN;
// If the $msg->fftn doesnt exist, we'll need to create it
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')));
System::unguard();
$so = System::firstOrCreate([
'name' => 'Discovered System',
'sysop' => 'Unknown',
'location' => '',
'active' => TRUE,
]);
System::reguard();
if ($so->id !== 443)
Log::alert(sprintf('%s:? Just created Discovered System for MSGID [%s] A',self::LOGKEY,$msg->msgid));
$ao = Address::findFTN($msg->set->get('set_fftn'),TRUE);
$so->addresses()->save($ao);
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));
Log::alert(sprintf('%s: - To FTN is not defined, creating new entry for [%s] (%d)',self::LOGKEY,$msg->tboss,$ao->id));
}
if ($msg->errors->messages()->has('from') && $msg->tzone) {
try {
// @todo Need to work out the correct region for the host_id
Address::unguard();
$ao = Address::firstOrNew([
'zone_id' => $msg->fzone->id,
//'region_id' => 0,
'host_id' => $msg->fn,
'node_id' => $msg->ff,
'point_id' => $msg->fp,
'active'=> TRUE,
]);
Address::reguard();
if (is_null($ao->region_id))
$ao->region_id = $ao->host_id;
} catch (\Exception $e) {
Log::error(sprintf('%s:! Error finding/creating FROM address [%s] for message',self::LOGKEY,$msg->fboss),['error'=>$e->getMessage()]);
$this->errors->push($msg);
return;
}
$ao->role = Address::NODE_UNKNOWN;
System::unguard();
$so = System::firstOrCreate([
'name' => 'Discovered System',
'sysop' => 'Unknown',
'location' => '',
'active' => TRUE,
]);
System::reguard();
if ($so->id !== 443)
Log::alert(sprintf('%s:? Just created Discovered System for MSGID [%s] B',self::LOGKEY,$msg->msgid));
$so->addresses()->save($ao);
Log::alert(sprintf('%s: - From FTN is not defined, creating new entry for [%s] (%d)',self::LOGKEY,$msg->fboss,$ao->id));
if (! $ao) {
$so = System::createUnknownSystem();
$ao = Address::createFTN($msg->set->get('set_fftn'),$so);
}
if ($msg->errors->messages()->has('user_from') || $msg->errors->messages()->has('user_to')) {
Log::error(sprintf('%s:! Skipping message [%s] due to errors (%s)...',self::LOGKEY,$msg->msgid,join(',',$msg->errors->messages()->keys())));
$this->errors->push($msg);
$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));
}
// 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);
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));
return;
}
if (! $ao) {
$so = System::createUnknownSystem();
$ao = Address::createFTN($msg->set->get('set_fftn'),$so);
}
$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));
}
// If there is no fftn, then its from a system that we dont know about
if (! $this->fftn) {
Log::alert(sprintf('%s:! No further message processing, packet is from a system we dont know about [%s]',self::LOGKEY,$this->fftn_t));
$this->messages->push($msg);
return;
}
}
if ($this->use_cache) {
$key = urlencode($msg->msgid ?: sprintf('%s %s',$msg->fftn,Carbon::now()->timestamp));
if (! Cache::forever($key,serialize($msg)))
throw new \Exception(sprintf('Caching failed for key [%s]?',$key));
// @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));
$this->messages->push($key);
} else {
$this->messages->push($msg);
}
/**
* Overwrite the packet password
*
* @param string|null $password
* @return self
*/
public function password(string $password=NULL): self
{
if ($password && (strlen($password) < 9))
$this->pass_p = $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

@ -30,14 +30,14 @@ final class FSC39 extends Packet
'dnet' => [0x16,'v',2], // Dest Net
'prodcode-lo' => [0x18,'C',1], // Product Code
'prodrev-maj' => [0x19,'C',1], // Product Version Major
'password' => [0x1a,'a8',8], // Packet Password
'password' => [0x1a,'a8',8], // Packet Password - http://ftsc.org/docs/fsc-0039.004 packet passwords are A-Z,0-9
'ozone' => [0x22,'v',2], // Orig Zone
'dzone' => [0x24,'v',2], // Dest Zone
'reserved' => [0x26,'a2',2], // Reserved
'capvalid' => [0x28,'n',2], // fsc-0039.004 (copy of 0x2c)
'prodcode-hi' => [0x2a,'C',1], // Product Code Hi
'prodrev-min' => [0x2b,'C',1], // Product Version Minor
'capword' => [0x2c,'v',2], // Capability Word
'capword' => [0x2c,'v',2], // Capability Word fsc-0039.004/fsc-0048.002
'dozone' => [0x2e,'v',2], // Orig Zone
'ddzone' => [0x30,'v',2], // Dest Zone
'opoint' => [0x32,'v',2], // Orig Point
@ -46,6 +46,7 @@ final class FSC39 extends Packet
];
public const TYPE = '2e';
public const VERS = self::MSG_TYPE2; //|self::MSG_TYPE4;
public function __get($key)
{
@ -63,34 +64,36 @@ final class FSC39 extends Packet
*/
protected function header(): string
{
$oldest = $this->messages->sortBy('datetime')->last();
try {
return pack(collect(self::HEADER)->pluck(1)->join(''),
$this->ff, // Orig Node
$this->tf, // Dest Node
Arr::get($this->header,'y'), // Year
Arr::get($this->header,'m'), // Month
Arr::get($this->header,'d'), // Day
Arr::get($this->header,'H'), // Hour
Arr::get($this->header,'M'), // Minute
Arr::get($this->header,'S'), // Second
$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->fn, // Orig Net
$this->tn, // Dest Net
$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->password, // Packet Password
$this->fz, // Orig Zone
$this->tz, // Dest Zone
$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
Arr::get($this->header,'capvalid',1<<0), // fsc-0039.004 (copy of 0x2c)
static::VERS, // fsc-0039.004 (copy of 0x2c)
((Setup::PRODUCT_ID >> 8) & 0xff), // Product Code Hi
Setup::PRODUCT_VERSION_MIN, // Product Version Minor
Arr::get($this->header,'capword',1<<0), // Capability Word
$this->fz, // Orig Zone
$this->tz, // Dest Zone
$this->fp, // Orig Point
$this->tp, // Dest Point
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

@ -37,6 +37,20 @@ final class FSC45 extends Packet
public const TYPE = '2.2';
public function __get($key)
{
switch ($key) {
case 'product':
return Arr::get($this->header,'prodcode');
case 'software_ver':
return Arr::get($this->header,'prodrev-maj');
default:
return parent::__get($key);
}
}
/**
* Create our message packet header
*/
@ -44,22 +58,22 @@ final class FSC45 extends Packet
{
try {
return pack(collect(self::HEADER)->pluck(1)->join(''),
$this->ff, // Orig Node
$this->tf, // Dest Node
$this->fp, // Orig Point
$this->tp, // Dest Point
$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->fn, // Orig Net
$this->tn, // Dest Net
$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->password, // Packet Password
$this->fz, // Orig Zone
$this->tz, // Dest Zone
$this->fd, // Orig Domain
$this->td, // Dest Domain
$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
);

View File

@ -11,6 +11,7 @@ use App\Models\Setup;
* FSC-0048 http://ftsc.org/docs/fsc-0048.002
*
* Commonly known as Type 2+ packets, based on FSC-0039 with improved support for FTS-0001
* @note: These packets will be detected as FSC-0039 packets unless it is addressed to a point
*/
final class FSC48 extends Packet
{
@ -36,7 +37,7 @@ final class FSC48 extends Packet
'capvalid' => [0x28,'n',2], // fsc-0039.004 (copy of 0x2c)
'prodcode-hi' => [0x2a,'C',1], // Product Code Hi
'prodrev-min' => [0x2b,'C',1], // Product Version Minor
'capword' => [0x2c,'v',2], // Capability Word
'capword' => [0x2c,'v',2], // Capability Word fsc-0039.004/fsc-0048.002
'dozone' => [0x2e,'v',2], // Orig Zone
'ddzone' => [0x30,'v',2], // Dest Zone
'opoint' => [0x32,'v',2], // Orig Point
@ -45,6 +46,7 @@ final class FSC48 extends Packet
];
public const TYPE = '2+';
public const VERS = self::MSG_TYPE2; //|self::MSG_TYPE4;
public function __get($key)
{
@ -62,34 +64,36 @@ final class FSC48 extends Packet
*/
protected function header(): string
{
$oldest = $this->messages->sortBy('datetime')->last();
try {
return pack(collect(self::HEADER)->pluck(1)->join(''),
$this->ff, // Orig Node
$this->tf, // Dest Node
Arr::get($this->header,'y'), // Year
Arr::get($this->header,'m'), // Month
Arr::get($this->header,'d'), // Day
Arr::get($this->header,'H'), // Hour
Arr::get($this->header,'M'), // Minute
Arr::get($this->header,'S'), // Second
$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->fp ? 0xffff : $this->fn, // Orig Net (0xFFFF when OrigPoint != 0)
$this->tn, // Dest Net
$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->password, // Packet Password
$this->fz, // Orig Zone
$this->tz, // Dest Zone
$this->fp ? $this->fn : 0x00, // Aux Net
Arr::get($this->header,'capvalid',1<<0), // fsc-0039.004 (copy of 0x2c)
$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
Arr::get($this->header,'capword',1<<0), // Capability Word
$this->fz, // Orig Zone
$this->tz, // Dest Zone
$this->fp, // Orig Point
$this->tp, // Dest Point
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

@ -38,30 +38,46 @@ final class FTS1 extends Packet
public const TYPE = '2';
public function __get($key)
{
switch ($key) {
case 'product':
return Arr::get($this->header,'prodcode');
case 'software_ver':
return 'N/A';
default:
return parent::__get($key);
}
}
/**
* Create our message packet header
*/
protected function header(): string
{
$oldest = $this->messages->sortBy('datetime')->last();
try {
return pack(collect(self::HEADER)->pluck(1)->join(''),
$this->ff, // Orig Node
$this->tf, // Dest Node
Arr::get($this->header,'y'), // Year
Arr::get($this->header,'m'), // Month
Arr::get($this->header,'d'), // Day
Arr::get($this->header,'H'), // Hour
Arr::get($this->header,'M'), // Minute
Arr::get($this->header,'S'), // Second
$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->fn, // Orig Net
$this->tn, // Dest Net
$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->password, // Packet Password
$this->fz, // Orig Zone
$this->tz, // Dest Zone
$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
);

View File

@ -2,25 +2,23 @@
namespace App\Classes\FTN;
use App\Models\Echoarea;
use App\Models\{Echoarea,Echomail,Netmail};
/**
* Abstract class to hold the common functions for automatic responding to echomail/netmail messages
*/
abstract class Process
{
public static function canProcess(string $echoarea): bool
public static function canProcess(Echoarea $eao): bool
{
$eao = Echoarea::where('name',$echoarea)->single();
return $eao && $eao->automsgs;
return $eao->automsgs ? TRUE : FALSE;
}
/**
* Return TRUE if the process class handled the message.
*
* @param Message $msg
* @param Echomail|Netmail $mo
* @return bool
*/
abstract public static function handle(Message $msg): bool;
abstract public static function handle(Echomail|Netmail $mo): bool;
}

View File

@ -5,7 +5,8 @@ namespace App\Classes\FTN\Process\Echomail;
use Illuminate\Support\Facades\Notification;
use Illuminate\Support\Facades\Log;
use App\Classes\FTN\{Message,Process};
use App\Classes\FTN\Process;
use App\Models\{Echomail,Netmail};
use App\Notifications\Echomails\Test as TestNotification;
/**
@ -19,16 +20,16 @@ final class Test extends Process
private const testing = ['test','testing'];
public static function handle(Message $msg): bool
public static function handle(Echomail|Netmail $mo): bool
{
if (! self::canProcess($msg->echoarea)
|| (strtolower($msg->user_to) !== 'all')
|| (! in_array(strtolower($msg->subject),self::testing)))
if (! self::canProcess($mo->echoarea)
|| (strtolower($mo->to) !== 'all')
|| (! in_array(strtolower($mo->subject),self::testing)))
return FALSE;
Log::info(sprintf('%s:- Processing TEST message from (%s) [%s] in [%s]',self::LOGKEY,$msg->user_from,$msg->fftn,$msg->echoarea));
Log::info(sprintf('%s:- Processing TEST message from (%s) [%s] in [%s]',self::LOGKEY,$mo->from,$mo->fftn->ftn,$mo->echoarea->name));
Notification::route('echomail',$msg->echoarea)->notify(new TestNotification($msg));
Notification::route('echomail',$mo->echoarea->withoutRelations())->notify(new TestNotification($mo));
return TRUE;
}

View File

@ -0,0 +1,37 @@
<?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

@ -5,7 +5,8 @@ namespace App\Classes\FTN\Process\Netmail;
use Illuminate\Support\Facades\Notification;
use Illuminate\Support\Facades\Log;
use App\Classes\FTN\{Message,Process};
use App\Classes\FTN\Process;
use App\Models\{Echomail,Netmail};
use App\Notifications\Netmails\Ping as PingNotification;
/**
@ -17,14 +18,14 @@ final class Ping extends Process
{
private const LOGKEY = 'RP-';
public static function handle(Message $msg): bool
public static function handle(Echomail|Netmail $mo): bool
{
if (strtolower($msg->user_to) !== 'ping')
if (strtolower($mo->to) !== 'ping')
return FALSE;
Log::info(sprintf('%s:- Processing PING message from (%s) [%s]',self::LOGKEY,$msg->user_from,$msg->fftn));
Log::info(sprintf('%s:- Processing PING message from (%s) [%s]',self::LOGKEY,$mo->from,$mo->fftn->ftn));
Notification::route('netmail',$msg->fftn_o)->notify(new PingNotification($msg));
Notification::route('netmail',$mo->fftn)->notify(new PingNotification($mo));
return TRUE;
}

View File

@ -4,30 +4,30 @@ namespace App\Classes\FTN;
use Carbon\Carbon;
use Illuminate\Contracts\Filesystem\FileNotFoundException;
use Illuminate\Support\Collection;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use League\Flysystem\UnableToWriteFile;
use League\Flysystem\UnableToReadFile;
use App\Classes\FTN as FTNBase;
use App\Exceptions\{InvalidCRCException,
InvalidPasswordException,
NodeNotSubscribedException,
NoWriteSecurityException};
use App\Exceptions\TIC\{NoFileAreaException,NotToMeException,SizeMismatchException};
use App\Models\{Address,File,Filearea,Setup};
use App\Traits\EncodeUTF8;
/**
* Class TIC
* Used create the structure of TIC files
* This class handles the TIC files that accompany file transfers
*
* @package App\Classes
*/
class Tic extends FTNBase
{
use EncodeUTF8;
private const LOGKEY = 'FT-';
private const cast_utf8 = [
];
// Single value kludge items and whether they are required
// http://ftsc.org/docs/fts-5006.001
private array $_kludge = [
@ -52,75 +52,79 @@ class Tic extends FTNBase
'pw' => FALSE, // Password
];
private File $fo;
private Filearea $area;
private Collection $values;
private Address $origin; // Should be first address in Path
private Address $from; // Should be last address in Path
private File $file;
private Address $to; // Should be me
public function __construct()
public function __construct(File $file=NULL)
{
$this->fo = new File;
$this->file = $file ?: new File;
$this->fo->kludges = collect();
$this->fo->set_path = collect();
$this->fo->set_seenby = collect();
$this->fo->rogue_path = collect();
$this->fo->rogue_seenby = collect();
$this->values = collect();
$this->file->kludges = collect();
$this->file->rogue_seenby = collect();
$this->file->set_path = collect();
$this->file->set_seenby = collect();
}
public function __get(string $key): mixed
{
switch ($key) {
case 'fo':
case 'file':
return $this->{$key};
case 'name':
return $this->file->name;
default:
return parent::__get($key);
}
}
/**
* Generate a TIC file for an address
* Generate the TIC file
*
* @param Address $ao
* @param File $fo
* @return string
* @throws \Exception
*/
public static function generate(Address $ao,File $fo): string
public function __toString(): string
{
$sysaddress = Setup::findOrFail(config('app.id'))->system->match($ao->zone)->first();
if (! $this->to)
throw new \Exception('No to address defined');
$sysaddress = our_address($this->to);
$result = collect();
// Origin is the first address in our path
$result->put('ORIGIN',$fo->path->first()->ftn3d);
$result->put('ORIGIN',$this->file->path->first()->ftn3d);
$result->put('FROM',$sysaddress->ftn3d);
$result->put('TO',$ao->ftn3d);
$result->put('FILE',$fo->name);
$result->put('SIZE',$fo->size);
if ($fo->description)
$result->put('DESC',$fo->description);
$result->put('AREA',$fo->filearea->name);
$result->put('AREADESC',$fo->filearea->description);
if ($x=$ao->session('ticpass'))
$result->put('TO',$this->to->ftn3d);
$result->put('FILE',$this->file->name);
$result->put('SIZE',$this->file->size);
if ($this->file->description)
$result->put('DESC',$this->file->description);
if ($this->file->replaces)
$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')))
$result->put('PW',$x);
$result->put('CRC',sprintf("%X",$fo->crc));
$result->put('CRC',sprintf("%X",$this->file->crc));
$out = '';
foreach ($result as $key=>$value)
$out .= sprintf("%s %s\r\n",$key,$value);
foreach ($fo->path as $o)
foreach ($this->file->path as $o)
$out .= sprintf("PATH %s %s %s\r\n",$o->ftn3d,$o->pivot->datetime,$o->pivot->extra);
foreach ($fo->seenby as $o)
// Add ourself to the path:
$out .= sprintf("PATH %s %s\r\n",$sysaddress->ftn3d,Carbon::now());
foreach ($this->file->seenby as $o)
$out .= sprintf("SEENBY %s\r\n",$o->ftn3d);
$out .= sprintf("SEENBY %s\r\n",$sysaddress->ftn3d);
return $out;
}
@ -131,180 +135,253 @@ class Tic extends FTNBase
*/
public function isNodelist(): bool
{
return (($this->fo->nodelist_filearea_id === $this->fo->filearea->domain->filearea_id)
&& (preg_match(str_replace(['.','?'],['\.','.'],'#^'.$this->fo->filearea->domain->nodelist_filename.'$#i'),$this->fo->name)));
Log::critical(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,
str_replace(['.','?'],['\.','[0-9]'],'#^'.$this->file->filearea->domain->nodelist_filename.'$#i'),
$this->file->name,
));
return (($this->file->nodelist_filearea_id === $this->file->filearea->domain->filearea_id)
&& (preg_match(str_replace(['.','?'],['\.','[0-9]'],'#^'.$this->file->filearea->domain->nodelist_filename.'$#i'),$this->file->name)));
}
/**
* Load a TIC file from an existing filename
*
* @param string $filename
* @return void
* @param string $filename Relative to filesystem
* @return File
* @throws FileNotFoundException
* @throws InvalidCRCException
* @throws InvalidPasswordException
* @throws NoFileAreaException
* @throws NoWriteSecurityException
* @throws NodeNotSubscribedException
* @throws NotToMeException
* @throws SizeMismatchException
*/
public function load(string $filename): void
public function load(string $filename): File
{
Log::info(sprintf('%s:+ Processing TIC file [%s]',self::LOGKEY,$filename));
$fs = Storage::disk(config('fido.local_disk'));
$rel_path_name = sprintf('%s/%s',config('fido.dir'),$filename);
if (str_contains($filename,'-')) {
list($hex,$name) = explode('-',$filename);
$hex = basename($hex);
} else {
$hex = '';
if (! $fs->exists($rel_path_name))
throw new FileNotFoundException(sprintf('File [%s] doesnt exist',$fs->path($rel_path_name)));
if ((! is_readable($fs->path($rel_path_name))) || ! ($f = $fs->readStream($rel_path_name)))
throw new UnableToReadFile(sprintf('File [%s] is not readable',$fs->path($rel_path_name)));
/*
* Filenames are in the format X-Y-N.tic
* Where:
* - X is the nodes address that sent us the file
* - Y is the mtime of the TIC file from the sender
* - N is the sender's filename
*/
$aid = NULL;
$mtime = NULL;
$this->file->recv_tic = preg_replace('/\.[Tt][Ii][Cc]$/','',$filename);
$m = [];
if (preg_match(sprintf('/^%s\.[Tt][Ii][Cc]$/',Packet::regex),$filename,$m)) {
$aid = $m[1];
$mtime = $m[2];
$this->file->recv_tic = $m[3];
}
if (! file_exists($filename))
throw new FileNotFoundException(sprintf('File [%s] doesnt exist',$filename));
if (! is_readable($filename))
throw new UnableToWriteFile(sprintf('File [%s] is not readable',realpath($filename)));
$f = fopen($filename,'rb');
if (! $f) {
Log::error(sprintf('%s:! Unable to open file [%s] for writing',self::LOGKEY,$filename));
return;
}
$ldesc = '';
while (! feof($f)) {
$line = chop(fgets($f));
$matches = [];
$m = [];
if (! $line)
continue;
preg_match('/([a-zA-Z]+)\ (.*)/',$line,$matches);
preg_match('/([a-zA-Z]+)\ ?(.*)?/',$line,$m);
if (in_array(strtolower($matches[1]),$this->_kludge)) {
switch ($k=strtolower($matches[1])) {
if (in_array(strtolower(Arr::get($m,1,'-')),$this->_kludge)) {
switch ($k=strtolower($m[1])) {
case 'area':
$this->{$k} = Filearea::singleOrNew(['name'=>strtoupper($matches[2])]);
try {
if ($fo=Filearea::where('name',strtoupper($m[2]))->firstOrFail())
$this->file->filearea_id = $fo->id;
} catch (ModelNotFoundException $e) {
// Rethrow this as No File Area
throw new NoFileAreaException($e->getMessage());
}
break;
case 'origin':
case 'from':
case 'to':
$this->{$k} = Address::findFTN($matches[2]);
if (($ao=Address::findFTN($m[2])) && ((! $aid) || ($ao->zone->domain_id === Address::findOrFail(hexdec($aid))->zone->domain_id)))
$this->file->fftn_id = $ao->id;
else
throw new ModelNotFoundException(sprintf('FTN Address [%s] not found or sender mismatch',$m[2]));
break;
// The origin should be the first address in the path
case 'origin':
// Ignore
case 'areadesc':
case 'created':
break;
// This should be one of my addresses
case 'to':
$ftns = our_address()->pluck('ftn3d');
if (! ($ftns->contains($m[2])))
throw new NotToMeException(sprintf('FTN Address [%s] not found or not one of my addresses',$m[2]));
// @todo If $this->{$k} is null, we have discovered the system and it should be created
break;
case 'file':
if (! Storage::disk('local')->exists($x=sprintf('%s/%s-%s',config('app.fido'),$hex,$matches[2])))
throw new FileNotFoundException(sprintf('File not found? [%s]',$x));
$this->file->name = $m[2];
$this->fo->name = $matches[2];
$this->fo->fullname = $x;
break;
case 'areadesc':
$areadesc = $matches[2];
break;
case 'created':
// ignored
break;
case 'pw':
$pw = $matches[2];
$pw = $m[2];
break;
case 'lfile':
$this->fo->lname = $matches[2];
case 'fullname':
$this->file->lname = $m[2];
break;
case 'desc':
case 'magic':
case 'replaces':
case 'size':
$this->fo->{$k} = $matches[2];
break;
$this->file->{$k} = $m[2];
case 'fullname':
$this->fo->lfile = $matches[2];
break;
case 'date':
$this->fo->datetime = Carbon::create($matches[2]);
$this->file->datetime = Carbon::createFromTimestamp($m[2]);
break;
case 'ldesc':
$this->fo->{$k} .= $matches[2];
$ldesc .= ($ldesc ? "\r" : '').$m[2];
break;
case 'crc':
$this->fo->{$k} = hexdec($matches[2]);
$this->file->{$k} = hexdec($m[2]);
break;
case 'path':
$x = [];
preg_match(sprintf('#^[Pp]ath (%s)\ ?([0-9]+)\ ?(.*)$#',Address::ftn_regex),$line,$x);
$ao = Address::findFTN($x[1]);
if (! $ao) {
$this->fo->rogue_path->push($matches[2]);
} else {
$this->fo->set_path->push(['address'=>$ao,'datetime'=>Carbon::createFromTimestamp($x[8]),'extra'=>$x[9]]);
}
$this->file->set_path->push($m[2]);
break;
case 'seenby':
$ao = Address::findFTN($matches[2]);
if (! $ao) {
$this->fo->rogue_seenby->push($matches[2]);
} else {
$this->fo->set_seenby->push($ao->id);
}
$this->file->set_seenby->push($m[2]);
break;
}
} else {
$this->fo->kludges->push($line);
$this->file->kludges->push($line);
}
}
fclose($f);
$f = fopen($x=Storage::disk('local')->path($this->fo->fullname),'rb');
$stat = fstat($f);
fclose($f);
if ($ldesc)
$this->file->ldesc = $ldesc;
// @todo Check that origin is the first address in the path
// @todo Make sure origin/from are in seenby
// @todo Make sure origin/from are in the path
/*
* Find our file and check the CRC
* If there is more than 1 file, select files that within 24hrs of the TIC file.
* If no files report file not found
* If there is more than 1 check each CRC to match the right one.
* If none match report, CRC error
*/
$found = FALSE;
$crcOK = FALSE;
foreach ($fs->files(config('fido.dir')) as $file) {
if (abs($x=$fs->lastModified($rel_path_name)-$fs->lastModified($file)) > 86400) {
Log::debug(sprintf('%s:/ Ignoring [%s] its mtime is outside of our scope [%d]',self::LOGKEY,$file,$x));
continue;
}
// Our file should have the same prefix as the TIC file
if (preg_match('#/'.($aid ? $aid.'-' : '').'.*'.$this->file->name.'$#',$file)) {
$found = TRUE;
if (sprintf('%08x',$this->file->crc) === ($y=$fs->checksum($file,['checksum_algo'=>'crc32b']))) {
$crcOK = TRUE;
break;
}
}
}
if (($found) && (! $crcOK))
throw new InvalidCRCException(sprintf('TIC file CRC [%08x] doesnt match file [%s] (%s)',$this->file->crc,$fs->path($rel_path_name),$y));
elseif (! $found)
throw new FileNotFoundException(sprintf('File not found? [%s...%s] in [%s]',$aid,$this->file->name,$fs->path($rel_path_name)));
// @todo Add notifications back to the system if the replaces line doesnt match
if ($this->file->replaces && (! preg_match('/^'.$this->file->replaces.'$/',$this->file->name))) {
Log::alert(sprintf('%s:! Regex [%s] doesnt match file name [%s]',self::LOGKEY,$this->file->replaces,$this->file->name));
$this->file->replaces = NULL;
}
// @todo Add notification back to the system if no replaces line and the file already exists
// @todo Add notifictions back to the system
// Validate Size
if ($this->fo->size !== ($y=$stat['size']))
throw new \Exception(sprintf('TIC file size [%d] doesnt match file [%s] (%d)',$this->fo->size,$this->fo->fullname,$y));
// Validate CRC
if (sprintf('%08x',$this->fo->crc) !== ($y=hash_file('crc32b',$x)))
throw new \Exception(sprintf('TIC file CRC [%08x] doesnt match file [%s] (%s)',$this->fo->crc,$this->fo->fullname,$y));
if ($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 ($pw !== ($y=$this->from->session('ticpass')))
throw new \Exception(sprintf('TIC file PASSWORD [%s] doesnt match system [%s] (%s)',$pw,$this->from->ftn,$y));
if (strtoupper($pw) !== ($y=strtoupper($this->file->fftn->session('ticpass'))))
throw new InvalidPasswordException(sprintf('TIC file PASSWORD [%s] doesnt match system [%s] (%s)',$pw,$this->file->fftn->ftn,$y));
// Validate Sender is linked (and permitted to send)
if ($this->from->fileareas->search(function($item) { return $item->id === $this->area->id; }) === FALSE)
throw new \Exception(sprintf('Node [%s] is not subscribed to [%s]',$this->from->ftn,$this->area->name));
// Validate Sender is linked
if ($this->file->fftn->fileareas->search(function($item) { return $item->id === $this->file->filearea_id; }) === FALSE)
throw new NodeNotSubscribedException(sprintf('Node [%s] is not subscribed to [%s]',$this->file->fftn->ftn,$this->file->filearea->name));
// If the filearea is to be autocreated, create it
if (! $this->area->exists) {
$this->area->description = $areadesc;
$this->area->active = TRUE;
$this->area->show = FALSE;
$this->area->notes = 'Autocreated';
$this->area->domain_id = $this->from->zone->domain_id;
$this->area->save();
}
$this->fo->filearea_id = $this->area->id;
$this->fo->fftn_id = $this->origin->id;
// Validate sender is permitted to write
// @todo Send a notification
if (! $this->file->filearea->can_write($this->file->fftn->security))
throw new NoWriteSecurityException(sprintf('Node [%s] doesnt have enough security to write to [%s] (%d)',$this->file->fftn->ftn,$this->file->filearea->name,$this->file->fftn->security));
// If the file create time is blank, we'll take the files
if (! $this->fo->datetime)
$this->fo->datetime = Carbon::createFromTimestamp($stat['ctime']);
if (! $this->file->datetime)
$this->file->datetime = Carbon::createFromTimestamp($fs->lastModified($file));
$this->fo->save();
$this->file->src_file = $file;
$this->file->recv_tic = $filename;
return $this->file;
}
public function save(): bool
{
return $this->file->save();
}
public function to(Address $ao): self
{
$this->to = $ao;
return $this;
}
}

View File

@ -7,6 +7,8 @@ use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpFoundation\File\File as FileBase;
use App\Classes\FTN\Packet;
class File extends FileBase implements \Iterator
{
private const LOGKEY = 'F--';
@ -102,13 +104,13 @@ class File extends FileBase implements \Iterator
}
/**
* Return the name of the file, without a node ID prefix
* Return the name of the file, without a node ID, mtime prefix
*
* @return string
*/
public function rawName(): string
{
return preg_replace('/^[0-9A-F]{4}-/','',$this->getFilename());
return preg_replace(sprintf('/^%s\.pkt$/i',Packet::regex),'\3\4',$this->getFilename());
}
/**
@ -129,7 +131,7 @@ class File extends FileBase implements \Iterator
return preg_replace('/.pkt$/i','',Arr::get(stream_get_meta_data($f),'uri'));
} else {
return $this->isPacket() ? preg_replace('/.pkt$/i','',$this->rawName()) : NULL;
return $this->isPacket() ? $this->rawName() : NULL;
}
}
}

View File

@ -48,7 +48,7 @@ abstract class Base
// 4 BITS of type
protected const IS_FILE = (1<<0);
protected const IS_PKT = (1<<1);
public const IS_PKT = (1<<1);
protected const IS_ARC = (1<<2);
protected const IS_REQ = (1<<3);
protected const IS_TIC = (1<<4);
@ -79,7 +79,7 @@ abstract class Base
return ($this->ftype&0xff) & $type;
}
protected function whatType(): int
public function whatType(): int
{
static $ext = ['su','mo','tu','we','th','fr','sa','req'];

View File

@ -86,7 +86,7 @@ final class File extends Send
{
// 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->full_storage_path);
$this->fd = Storage::readStream($this->f->rel_name);
} else {
$this->fd = fopen($this->full_name,'rb');

View File

@ -2,14 +2,13 @@
namespace App\Classes\File;
use Illuminate\Contracts\Filesystem\Filesystem;
use Illuminate\Support\Facades\Storage;
use App\Models\Address;
final class Item extends Receive
{
private const LOCATION = 'local';
/** @var Address The address that sent us this item */
private Address $ao;
private string $recvas;
@ -34,14 +33,14 @@ final class Item extends Receive
public function __get($key) {
switch ($key) {
case 'exists':
return Storage::disk(self::LOCATION)->exists($this->rel_name);
return Storage::disk(config('fido.local_disk'))->exists($this->rel_name);
case 'stor_name':
return sprintf('%04X-%s',$this->ao->id,$this->recvas);
case 'pref_name':
return sprintf('%04X-%d-%s',$this->ao->id,$this->recvmtime,$this->recvas);
case 'rel_name':
return sprintf('%s/%s',config('app.fido'),$this->stor_name);
return sprintf('%s/%s',config('fido.dir'),$this->pref_name);
case 'full_name':
return Storage::disk(self::LOCATION)->path($this->rel_name);
return Storage::disk(config('fido.local_disk'))->path($this->rel_name);
case 'match_mtime':
return $this->mtime === $this->recvmtime;
@ -59,10 +58,10 @@ final class Item extends Receive
return sprintf('%s %lu %lu',$this->recvas,$this->recvsize,$this->recvmtime);
case 'mtime':
return Storage::disk(self::LOCATION)->lastModified($this->rel_name);
return Storage::disk(config('fido.local_disk'))->lastModified($this->rel_name);
case 'size':
return Storage::disk(self::LOCATION)->size($this->rel_name);
return Storage::disk(config('fido.local_disk'))->size($this->rel_name);
default:
return parent::__get($key);

View File

@ -5,14 +5,18 @@ namespace App\Classes\File;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use App\Classes\Node;
use App\Classes\FTN\{Message,Packet};
final class Mail extends Send
{
private const LOGKEY = 'IFM';
/** @var int Our internal position counter */
private int $readpos;
private ?string $content;
/**
* @throws \Exception
@ -29,7 +33,7 @@ final class Mail extends Send
public function __get($key) {
switch ($key) {
case 'dbids':
return $this->f->messages->pluck('dbid');
return $this->f->messages->pluck('id');
case 'name':
return sprintf('%08x',timew($this->youngest()));
@ -56,6 +60,8 @@ 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')]);
// Update netmail table
if (($this->type === Send::T_NETMAIL)
&& ($x=$this->dbids)->count())
@ -79,6 +85,8 @@ final class Mail extends Send
'sent_at'=>Carbon::now(),
'sent_pkt'=>$this->name,
]);
$this->content = NULL;
}
}
@ -89,12 +97,13 @@ final class Mail extends Send
public function open(string $compress=''): bool
{
$this->content = (string)$this->f;
return TRUE;
}
public function read(int $length): string
{
$result = substr((string)$this->f,$this->readpos,$length);
$result = substr($this->content,$this->readpos,$length);
$this->readpos += strlen($result);
return $result;
@ -106,7 +115,7 @@ final class Mail extends Send
return TRUE;
}
public function youngest(): Carbon
private function youngest(): Carbon
{
return $this->f->messages->pluck('date')->sort()->last();
}

View File

@ -5,15 +5,12 @@ namespace App\Classes\File;
use Carbon\Carbon;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
use Symfony\Component\HttpFoundation\File\Exception\FileException;
use App\Classes\{File,Protocol};
use App\Classes\FTN\{InvalidPacketException,Packet};
use App\Classes\Protocol;
use App\Exceptions\FileGrewException;
use App\Jobs\{MessageProcess,TicProcess};
use App\Jobs\{PacketProcess,TicProcess};
use App\Models\Address;
use App\Notifications\Netmails\PacketPasswordInvalid;
/**
* Object representing the files we are receiving
@ -37,6 +34,7 @@ class Receive extends Base
private ?string $comp;
/** @var string|null The compressed data received */
private ?string $comp_data;
private $queue = FALSE;
public function __construct()
{
@ -63,7 +61,7 @@ class Receive extends Base
case 'nameas':
case 'size':
case 'name_size_time':
case 'stor_name':
case 'pref_name':
return $this->receiving->{$key};
case 'pos':
@ -117,98 +115,25 @@ class Receive extends Base
fclose($this->f);
// Set our mtime
Log::info(sprintf('%s:= Setting file [%s] to time [%s]',self::LOGKEY,$this->receiving->full_name,$this->receiving->recvmtime));
Log::debug(sprintf('%s:= Setting file [%s] to time [%s]',self::LOGKEY,$this->receiving->full_name,$this->receiving->recvmtime));
touch($this->receiving->full_name,$this->receiving->recvmtime);
$this->f = NULL;
// If we received a packet, we'll dispatch a job to process it, if we got it all
if ($this->receiving->complete)
switch ($x=$this->receiving->whatType()) {
switch ($this->receiving->whatType()) {
case self::IS_ARC:
case self::IS_PKT:
Log::info(sprintf('%s:- Processing mail %s [%s]',self::LOGKEY,$x === self::IS_PKT ? 'PACKET' : 'ARCHIVE',$this->receiving->nameas));
try {
$f = new File($this->receiving->full_name);
$processed = FALSE;
foreach ($f as $packet) {
$po = Packet::process($packet,Arr::get(stream_get_meta_data($packet),'uri'),$f->itemSize(),$this->ao->system);
// Check the messages are from the uplink
if ($this->ao->system->addresses->search(function($item) use ($po) { return $item->id === $po->fftn_o->id; }) === FALSE) {
Log::error(sprintf('%s:! Packet [%s] is not from this link? [%d]',self::LOGKEY,$po->fftn_o->ftn,$this->ao->system_id));
break;
}
// Check the packet password
if ($this->ao->session('pktpass') !== $po->password) {
Log::error(sprintf('%s:! Packet from [%s] with password [%s] is invalid.',self::LOGKEY,$this->ao->ftn,$po->password));
Notification::route('netmail',$this->ao)->notify(new PacketPasswordInvalid($po->password,$this->receiving->nameas));
break;
}
Log::info(sprintf('%s:- Packet has [%d] messages',self::LOGKEY,$po->count()));
// Queue messages if there are too many in the packet.
if ($queue = ($po->count() > config('app.queue_msgs')))
Log::info(sprintf('%s:- Messages will be sent to the queue for processing',self::LOGKEY));
$count = 0;
foreach ($po as $msg) {
Log::info(sprintf('%s:- Mail from [%s] to [%s]',self::LOGKEY,$msg->fftn,$msg->tftn));
// @todo Quick check that the packet should be processed by us.
// @todo validate that the packet's zone is in the domain.
/*
* // @todo generate exception when echomail for an area that doesnt exist
* // @todo generate exception when echomail for an area sender cannot post to
* // @todo generate exception when echomail for an area sender not subscribed to
* // @todo generate exception when echomail comes from a system not defined here
* // @todo generate exception when echomail comes from a system doesnt exist
*
* // @todo generate exception when netmail to system that doesnt exist (node/point)
* // @todo generate exception when netmail from system that doesnt exist (node/point)
* // @todo generate warning when netmail comes from a system not defined here
*
* // @todo generate exception when packet has wrong password
*/
try {
// Dispatch job.
if ($queue)
MessageProcess::dispatch($msg,$f->pktName(),$this->ao,$po->fftn_o,$rcvd_time);
else
MessageProcess::dispatchSync($msg,$f->pktName(),$this->ao,$po->fftn_o,$rcvd_time);
// 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);
} else
PacketProcess::dispatchSync($this->receiving->rel_name,$this->ao->zone->domain,TRUE,$rcvd_time);
} catch (\Exception $e) {
Log::error(sprintf('%s:! Got error dispatching message [%s] (%d:%s-%s).',self::LOGKEY,$msg->msgid,$e->getLine(),$e->getFile(),$e->getMessage()));
}
$count++;
}
if ($count === $po->count())
$processed = TRUE;
}
if (! $processed) {
Log::alert(sprintf('%s:- Not deleting packet [%s], it doesnt seem to be processed?',self::LOGKEY,$this->receiving->nameas));
// If we want to keep the packet, we could do that logic here
} elseif (! config('app.packet_keep')) {
Log::debug(sprintf('%s:- Deleting processed packet [%s]',self::LOGKEY,$this->receiving->full_name));
unlink($this->receiving->full_name);
}
} catch (InvalidPacketException $e) {
Log::error(sprintf('%s:- Not deleting packet [%s], as it generated an InvalidPacketException',self::LOGKEY,$this->receiving->nameas),['e'=>$e->getMessage()]);
} catch (\Exception $e) {
Log::error(sprintf('%s:- Not deleting packet [%s], as it generated an uncaught exception',self::LOGKEY,$this->receiving->nameas),['e'=>$e->getMessage()]);
Log::error(sprintf('%s:! Got error dispatching packet [%s] (%d:%s-%s).',self::LOGKEY,$this->receiving->rel_name,$e->getLine(),$e->getFile(),$e->getMessage()));
}
break;
@ -217,7 +142,7 @@ class Receive extends Base
Log::info(sprintf('%s:- Processing TIC file [%s]',self::LOGKEY,$this->receiving->nameas));
// Queue the tic to be processed later, in case the referenced file hasnt been received yet
TicProcess::dispatch($this->receiving->rel_name);
TicProcess::dispatch($this->receiving->pref_name)->delay(60);
break;
@ -232,11 +157,12 @@ class Receive extends Base
/**
* Add a new file to receive
*
* @param array $file
* @param Address $ao
* @param array $file The name of the receiving file
* @param Address $ao Sender sending a file to us
* @param bool $queue Force sending received items to the queue
* @throws \Exception
*/
public function new(array $file,Address $ao): void
public function new(array $file,Address $ao,$queue=FALSE): void
{
Log::debug(sprintf('%s:+ Receiving new file [%s]',self::LOGKEY,join('|',$file)));
@ -244,6 +170,7 @@ class Receive extends Base
throw new \Exception('Can only have 1 file receiving at a time');
$this->ao = $ao;
$this->queue = $queue;
$this->list->push(new Item($ao,Arr::get($file,'name'),(int)Arr::get($file,'mtime'),(int)Arr::get($file,'size')));
$this->index = $this->list->count()-1;

View File

@ -6,6 +6,7 @@ use Exception;
use Illuminate\Support\Facades\Log;
use League\Flysystem\UnreadableFileEncountered;
use App\Classes\File\Send\Dynamic;
use App\Classes\Node;
use App\Models\Address;
@ -122,13 +123,38 @@ class Send extends Base
if ($successful) {
$end = time()-$this->start;
Log::debug(sprintf('%s: - Closing [%s], sent in [%d] with [%s] items',self::LOGKEY,$this->sending->nameas,$end,$this->sending->dbids->count()));
Log::info(sprintf('%s:- Closing [%s], sent in [%d] with [%s] items',self::LOGKEY,$this->sending->nameas,$end,$this->sending->dbids->count()));
}
$this->sending->close($successful,$node);
$this->index = NULL;
}
public function dynamic(Address $ao): bool
{
$file = FALSE;
// If the node is marked as hold - dont send any files.
if ($ao->system->hold) {
Log::info(sprintf('%s:- System [%d] is marked as hold - not checking for files.',self::LOGKEY,$ao->system_id));
return FALSE;
}
// Files
if (($x=$ao->dynamicWaiting())->count()) {
Log::debug(sprintf('%s:- [%d] Dynamic Files(s) added for sending to [%s]',self::LOGKEY,$x->count(),$ao->ftn));
// Add Files
foreach ($x as $do)
$this->list->push(new Dynamic($do,$ao,self::T_FILE));
$file = TRUE;
}
return $file;
}
/*
private function compress(string $comp_mode): void
{
@ -202,7 +228,7 @@ class Send extends Base
if ((($this->index=$this->list->search(function($item) { return $item->complete === FALSE; })) !== FALSE)
&& $this->sending->open())
{
Log::debug(sprintf('%s:- Sending item [%d] (%s)',self::LOGKEY,$this->index,$this->sending->nameas));
Log::info(sprintf('%s:- Sending item [%d] (%s)',self::LOGKEY,$this->index,$this->sending->nameas));
$this->pos = 0;
$this->start = time();
@ -223,11 +249,10 @@ class Send extends Base
* Add our mail to the send queue
*
* @param Address $ao
* @param bool $update
* @return bool
* @throws Exception
*/
public function mail(Address $ao,bool $update=TRUE): bool
public function mail(Address $ao): bool
{
$mail = FALSE;
@ -239,7 +264,7 @@ class Send extends Base
}
// Netmail
if ($x=$ao->getNetmail($update)) {
if ($x=$ao->getNetmail()) {
Log::debug(sprintf('%s:- Netmail(s) added for sending to [%s]',self::LOGKEY,$ao->ftn));
$this->list->push(new Mail($x,self::T_NETMAIL));
@ -247,7 +272,7 @@ class Send extends Base
}
// Echomail
if ($x=$ao->getEchomail($update)) {
if ($x=$ao->getEchomail()) {
Log::debug(sprintf('%s:- Echomail(s) added for sending to [%s]',self::LOGKEY,$ao->ftn));
$this->list->push(new Mail($x,self::T_ECHOMAIL));

View File

@ -0,0 +1,124 @@
<?php
namespace App\Classes\File\Send;
use Carbon\Carbon;
use App\Classes\File\Send;
use App\Classes\Node;
use App\Models\Address;
use App\Models\Dynamic as Model;
use App\Classes\Dynamic as Item;
/**
* Dynamic files that are sent to systems during a mailer session
*/
final class Dynamic extends Send
{
private const LOGKEY = 'FSD';
/** @var int Our internal position counter */
private int $readpos = 0;
private string $buffer;
private Item $item;
private Carbon $sent;
/**
* @throws \Exception
*/
public function __construct(private Model $do,Address $ao,int $type)
{
parent::__construct();
$this->ftype = ((($type&0xff)<<8)|self::IS_FILE);
$this->item = new $this->do->model($ao,$this->do->arguments);
$this->sent = Carbon::now();
}
public function __get($key) {
switch ($key) {
case 'dbids':
return collect([$this->do->id]);
case 'nameas':
return $this->item->getName();
case 'mtime':
return $this->sent->timestamp;
case 'size':
return strlen($this->buffer);
default:
return NULL;
}
}
public function close(bool $successful,Node $node): void
{
if ($successful) {
$this->complete = TRUE;
$next_at = $this->do->next_at
->startOfDay()
->addHours($this->do->start_time->hour)
->addMinutes($this->do->start_time->minute);
switch ($this->do->frequency) {
case 'ONCE':
$this->do->active = FALSE;
break;
case 'DAILY':
$this->do->next_at = $next_at
->addDay();
break;
case 'WEEKLY':
$this->do->next_at = $next_at
->addWeek();
break;
case 'MONTHLY':
$this->do->next_at = $next_at
->addMonth();
break;
default:
throw new \Exception(sprintf('%s:! Unknown frequency [%s] for [%d]',self::LOGKEY,$this->do->frequency,$this->do->id));
}
$this->do->save();
}
}
public function feof(): bool
{
return ($this->readpos === $this->size);
}
public function open(string $compress=''): bool
{
$this->buffer = (string)$this->item;
return TRUE;
}
public function read(int $length): string
{
$result = substr($this->buffer,$this->readpos,$length);
$this->readpos += strlen($result);
return $result;
}
public function seek(int $pos): bool
{
$this->readpos = ($pos < $this->size) ? $pos : $this->size;
return TRUE;
}
}

View File

@ -26,7 +26,7 @@ final class Tic extends Send
$this->ftype = ((($type&0xff)<<8)|self::IS_TIC);
$this->readpos = 0;
$this->tic = FTNTic::generate($ao,$file);
$this->tic = (string)(new FTNTic($file))->to($ao);
}
public function __get($key) {

View File

@ -92,7 +92,7 @@ class Node
// Return how long our session has been connected
case 'session_time':
return Carbon::now()->diffInSeconds($this->start_time);
return sprintf("%d",$this->start_time->diffInSeconds(Carbon::now()));
case 'system':
case 'sysop':

View File

@ -186,6 +186,8 @@ class Page
$this->text .= $text;
$this->text_right = $right;
return $this;
}
/**

View File

@ -9,7 +9,9 @@ use App\Classes\File\{Receive,Send};
use App\Classes\Protocol\EMSI;
use App\Classes\Sock\SocketClient;
use App\Classes\Sock\SocketException;
use App\Models\{Address,Setup,System,SystemLog};
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.
abstract class Protocol
{
@ -26,14 +28,6 @@ abstract class Protocol
protected const RCDO = -3;
protected const ERROR = -5;
// Our sessions Types
public const SESSION_AUTO = 0;
/** @deprecate Use mailers:class */
public const SESSION_EMSI = 1;
/** @deprecate Use mailers:class */
public const SESSION_BINKP = 2;
public const SESSION_ZMODEM = 3;
protected const MAX_PATH = 1024;
/* O_ options - [First 9 bits are protocol P_* (Interfaces/ZModem)] */
@ -132,9 +126,11 @@ abstract class Protocol
private array $comms;
protected bool $force_queue = FALSE;
abstract protected function protocol_init(): int;
abstract protected function protocol_session(): int;
abstract protected function protocol_session(bool $force_queue=FALSE): int;
public function __construct(Setup $o=NULL)
{
@ -174,6 +170,10 @@ abstract class Protocol
$this->comms[$key] = $value;
break;
case 'client':
$this->{$key} = $value;
break;
default:
throw new \Exception('Unknown key: '.$key);
}
@ -309,14 +309,14 @@ abstract class Protocol
$addresses = collect();
foreach (($this->originate ? $this->node->aka_remote_authed : $this->node->aka_remote) as $ao)
$addresses = $addresses->merge($this->setup->system->match($ao->zone,Address::NODE_ZC|Address::NODE_RC|Address::NODE_NC|Address::NODE_HC|Address::NODE_ACTIVE|Address::NODE_PVT|Address::NODE_POINT));
$addresses = $addresses->merge(our_address($ao->zone->domain));
$addresses = $addresses->unique();
Log::debug(sprintf('%s:- Presenting limited AKAs [%s]',self::LOGKEY,$addresses->pluck('ftn')->join(',')));
} else {
$addresses = $this->setup->system->addresses;
$addresses = $this->setup->system->akas;
Log::debug(sprintf('%s:- Presenting ALL our AKAs [%s]',self::LOGKEY,$addresses->pluck('ftn')->join(',')));
}
@ -327,13 +327,13 @@ abstract class Protocol
/**
* Initialise our Session
*
* @param int $type
* @param Mailer $mo
* @param SocketClient $client
* @param Address|null $o
* @return int
* @throws \Exception
*/
public function session(int $type,SocketClient $client,Address $o=NULL): int
public function session(Mailer $mo,SocketClient $client,Address $o=NULL): int
{
if ($o->exists)
Log::withContext(['ftn'=>$o->ftn]);
@ -365,12 +365,11 @@ abstract class Protocol
// We are an IP node
$this->optionSet(self::O_TCP);
$this->setClient($client);
$this->client = $client;
switch ($type) {
/** @noinspection PhpMissingBreakStatementInspection */
case self::SESSION_AUTO:
Log::debug(sprintf('%s:- Trying EMSI',self::LOGKEY));
switch ($mo->name) {
case 'EMSI':
Log::debug(sprintf('%s:- Starting EMSI',self::LOGKEY));
$rc = $this->protocol_init();
if ($rc < 0) {
@ -379,27 +378,19 @@ abstract class Protocol
return self::S_FAILURE;
}
case self::SESSION_EMSI:
Log::debug(sprintf('%s:- Starting EMSI',self::LOGKEY));
$rc = $this->protocol_session();
$rc = $this->protocol_session($this->originate);
break;
case self::SESSION_BINKP:
case 'BINKP':
Log::debug(sprintf('%s:- Starting BINKP',self::LOGKEY));
$rc = $this->protocol_session();
$rc = $this->protocol_session($this->originate);
break;
case self::SESSION_ZMODEM:
Log::debug(sprintf('%s:- Starting ZMODEM',self::LOGKEY));
$this->client->speed = self::TCP_SPEED;
$this->originate = FALSE;
return $this->protocol_session();
default:
Log::error(sprintf('%s:! Unsupported session type [%d]',self::LOGKEY,$type));
Log::error(sprintf('%s:! Unsupported session type [%d]',self::LOGKEY,$mo->id));
return self::S_FAILURE;
}
@ -429,15 +420,16 @@ abstract class Protocol
));
// Add unknown FTNs to the DB
if ($this->node->aka_remote_authed->count()) {
$so = $this->node->aka_remote_authed->first()->system;
} else {
$so = System::where('name','Discovered System')->single();
}
$so = ($this->node->aka_remote_authed->count())
? $this->node->aka_remote_authed->first()->system
: System::createUnknownSystem();
if ($so && $so->exists) {
foreach ($this->node->aka_other as $aka) {
Address::findFTN($aka,TRUE,$so);
foreach ($this->node->aka_other as $aka)
if (! Address::findFTN($aka)) {
$oo = Address::createFTN($aka,$so);
$oo->validated = TRUE;
$oo->save();
}
// Log session in DB
@ -446,12 +438,18 @@ 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->sessiontype = $type;
$slo->mailer_id = $mo->id;
$slo->sessiontime = $this->node->session_time;
$slo->result = ($rc & self::S_MASK);
$slo->originate = $this->originate;
$so->logs()->save($slo);
// If we are autohold, then remove that
if ($so->autohold) {
$so->autohold = FALSE;
$so->save();
}
}
// @todo Optional after session execution event
@ -495,15 +493,4 @@ abstract class Protocol
{
$this->session |= $key;
}
/**
* Set our client that we are communicating with
*
* @param SocketClient $client
* @deprecated use __get()/__set()
*/
protected function setClient(SocketClient $client): void
{
$this->client = $client;
}
}

View File

@ -9,11 +9,12 @@ use Illuminate\Support\Facades\Log;
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\Exceptions\FileGrewException;
use App\Models\Address;
use App\Exceptions\{FileGrewException,InvalidFTNException};
use App\Models\{Address,Mailer};
final class Binkp extends BaseProtocol
{
@ -147,7 +148,6 @@ final class Binkp extends BaseProtocol
* @param SocketClient $client
* @return int|null
* @throws SocketException
* @throws \Exception
*/
public function onConnect(SocketClient $client): ?int
{
@ -155,8 +155,7 @@ final class Binkp extends BaseProtocol
if (! parent::onConnect($client)) {
Log::withContext(['pid'=>getmypid()]);
// @todo If we can use SESSION_EMSI set an object class value that in BINKP of SESSION_BINKP, and move this method to the parent class
$this->session(self::SESSION_BINKP,$client,(new Address));
$this->session(Mailer::where('name','BINKP')->singleOrFail(),$client,(new Address));
$this->client->close();
exit(0);
}
@ -431,17 +430,23 @@ final class Binkp extends BaseProtocol
$rc = TRUE;
} else {
$data = substr($this->rx_buf,1);
// http://ftsc.org/docs/fts-1026.001 - frames may be NULL terminated
$data = rtrim(substr($this->rx_buf,1),"\x00");
switch ($msg) {
case self::BPM_ADR:
Log::debug(sprintf('%s:- ADR:Address [%s]',self::LOGKEY,$data));
$rc = $this->M_adr($data);
// @note It seems taurus may pad data with nulls at the end (esp BPM_ADR), so we should trim that.
$rc = $this->M_adr(trim($data));
break;
case self::BPM_EOB:
Log::debug(sprintf('%s:- EOB:We got an EOB message with [%d] chars in the buffer',self::LOGKEY,strlen($data)));
$rc = $this->M_eob($data);
if (strlen($data))
Log::critical(sprintf('%s:! EOB but we have data?',self::LOGKEY),['data'=>$data]);
$rc = $this->M_eob();
break;
case self::BPM_NUL:
@ -451,7 +456,7 @@ final class Binkp extends BaseProtocol
case self::BPM_PWD:
Log::debug(sprintf('%s:- PWD:We got a password [%s]',self::LOGKEY,$data));
$rc = $this->M_pwd($data);
$rc = $this->M_pwd(ltrim($data));
break;
case self::BPM_ERR:
@ -476,7 +481,7 @@ final class Binkp extends BaseProtocol
case self::BPM_OK:
Log::debug(sprintf('%s:- OK:Got an OK [%s]',self::LOGKEY,$data));
$rc = $this->M_ok($data);
$rc = $this->M_ok(ltrim($data));
break;
case self::BPM_CHAT:
@ -680,26 +685,44 @@ final class Binkp extends BaseProtocol
*/
private function M_adr(string $buf): bool
{
$buf = $this->skip_blanks($buf);
$rc = 0;
while ($rem_aka=$this->strsep($buf,' ')) {
try {
if (! ($o=Address::findFTN($rem_aka,FALSE,NULL,TRUE))) {
if (! ($o=Address::findFTN($rem_aka,TRUE))) {
// @todo when we have multiple inactive records, this returns more than 1, so pluck the active record if there is one
Log::alert(sprintf('%s:? AKA is UNKNOWN [%s]',self::LOGKEY,$rem_aka));
$this->node->ftn_other = $rem_aka;
continue;
// 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(',')));
$this->node->ftn_other = $rem_aka;
continue;
} elseif (! $o->active) {
Log::alert(sprintf('%s:/ AKA is not active [%s], ignoring',self::LOGKEY,$rem_aka));
Log::alert(sprintf('%s:/ AKA is not active [%s] - ignoring',self::LOGKEY,$rem_aka));
$this->node->ftn_other = $rem_aka;
continue;
} else {
Log::info(sprintf('%s:- Got AKA [%s]',self::LOGKEY,$rem_aka));
// We'll update this address status
$o->validated = TRUE;
$o->role &= ~(Address::NODE_HOLD|Address::NODE_DOWN);
$o->save();
}
} catch (InvalidFTNException $e) {
Log::error(sprintf('%s:! AKA is INVALID [%s] (%s) - ignoring',self::LOGKEY,$rem_aka,$e->getMessage()));
continue;
} catch (\Exception $e) {
Log::error(sprintf('%s:! AKA is INVALID [%s] (%d:%s-%s)',self::LOGKEY,$rem_aka,$e->getLine(),$e->getFile(),$e->getMessage()));
@ -744,18 +767,14 @@ final class Binkp extends BaseProtocol
return 0;
}
// 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;
}
$this->send->mail($ao);
$this->send->files($ao);
}
/**
* http://ftsc.org/docs/fts-1026.001
* M_NUL "TRF netmail_bytes arcmail_bytes"
* traffic prognosis (in bytes) for the netmail
* (netmail_bytes) and arcmail + files (arcmail_bytes),
* both are decimal ASCII strings
*/
// @todo This is affectively redundant, because we are not determining our mail until later
$this->msgs(self::BPM_NUL,sprintf('TRF %lu %lu',$this->send->mail_size,$this->send->files_size));
if ($this->md_challenge) {
@ -805,7 +824,7 @@ final class Binkp extends BaseProtocol
*
* @throws \Exception
*/
private function M_eob(string $buf): bool
private function M_eob(): bool
{
if ($this->recv->fd) {
Log::info(sprintf('%s:= Closing receiving file.',self::LOGKEY));
@ -817,21 +836,8 @@ final class Binkp extends BaseProtocol
$this->sessionClear(self::SE_DELAYEOB);
if (! $this->send->togo_count && $this->sessionGet(self::SE_NOFILES) && $this->capGet(self::F_MULTIBATCH,self::O_YES)) {
// Add our mail to the queue if we have authenticated
if ($this->node->aka_authed)
foreach ($this->node->aka_remote_authed as $ao) {
Log::debug(sprintf('%s:- Checking for any new mail and files to [%s]',self::LOGKEY,$ao->ftn));
$this->getFiles($this->node);
if (! $ao->validated) {
Log::alert(sprintf('%s:! Address [%s] is not validated, so we wont bundle mail for it',self::LOGKEY,$ao->ftn));
continue;
}
$this->send->mail($ao);
$this->send->files($ao);
}
Log::debug(sprintf('%s:- We have [%d] items to send to [%s]',self::LOGKEY,$this->send->togo_count,$ao->ftn));
if ($this->send->togo_count)
$this->sessionClear(self::SE_NOFILES|self::SE_SENTEOB);
}
@ -892,7 +898,7 @@ final class Binkp extends BaseProtocol
return TRUE;
}
$this->recv->new($file['file'],$this->node->address);
$this->recv->new($file['file'],$this->node->address,$this->force_queue);
try {
switch ($this->recv->open($file['offs']<0,$file['flags'])) {
@ -1012,6 +1018,9 @@ final class Binkp extends BaseProtocol
$this->send->close(TRUE,$this->node);
}
} else {
Log::error(sprintf('%s:! M_got[skip] not for our file? [%s]',self::LOGKEY,$buf));
}
} else {
@ -1029,16 +1038,16 @@ final class Binkp extends BaseProtocol
Log::info(sprintf('%s:+ M_NUL [%s]',self::LOGKEY,$buf));
if (! strncmp($buf,'SYS ',4)) {
$this->node->system = $this->skip_blanks(substr($buf,4));
$this->node->system = ltrim(substr($buf,4));
} elseif (! strncmp($buf, 'ZYZ ',4)) {
$this->node->sysop = $this->skip_blanks(substr($buf,4));
$this->node->sysop = ltrim(substr($buf,4));
} elseif (! strncmp($buf,'LOC ',4)) {
$this->node->location = $this->skip_blanks(substr($buf,4));
$this->node->location = ltrim(substr($buf,4));
} elseif (! strncmp($buf,'NDL ',4)) {
$data = $this->skip_blanks(substr($buf,4));
$data = ltrim(substr($buf,4));
$comma = strpos($data,',');
$spd = substr($data,0,$comma);
@ -1049,7 +1058,7 @@ final class Binkp extends BaseProtocol
$this->client->speed = $spd;
} else {
$comma = $this->skip_blanks(substr($buf,4));
$comma = ltrim(substr($buf,4));
$c = 0;
while (($x=substr($comma,$c,1)) && is_numeric($x))
$c++;
@ -1071,19 +1080,25 @@ final class Binkp extends BaseProtocol
}
} elseif (! strncmp($buf,'TIME ',5)) {
$this->node->node_time = $this->skip_blanks(substr($buf,5));
$this->node->node_time = ltrim(substr($buf,5));
} elseif (! strncmp($buf,'VER ',4)) {
$data = $this->skip_blanks(substr($buf,4));
$data = ltrim(substr($buf,4));
$matches = [];
preg_match('#^(.+)\s+binkp/([0-9]+)\.([0-9]+)$#',$data,$matches);
preg_match('#^(.+)\s+\(?binkp/([0-9]+)\.([0-9]+)\)?$#',$data,$matches);
if (count($matches) === 4) {
$this->node->software = $matches[1];
$this->node->ver_major = $matches[2];
$this->node->ver_minor = $matches[3];
} else {
$this->node->software = 'Unknown';
$this->node->ver_major = 0;
$this->node->ver_minor = 0;
}
} elseif (! strncmp($buf,'TRF ',4)) {
$data = $this->skip_blanks(substr($buf,4));
$data = ltrim(substr($buf,4));
$matches = [];
preg_match('/^([0-9]+)\s+([0-9]+)$/',$data,$matches);
@ -1097,13 +1112,13 @@ final class Binkp extends BaseProtocol
$this->sessionSet(self::SE_DELAYEOB);
} elseif (! strncmp($buf,'PHN ',4)) {
$this->node->phone = $this->skip_blanks(substr($buf,4));
$this->node->phone = ltrim(substr($buf,4));
} elseif (! strncmp($buf,'OPM ',4)) {
$this->node->message = $this->skip_blanks(substr($buf,4));
$this->node->message = ltrim(substr($buf,4));
} elseif (! strncmp($buf,'OPT ',4)) {
$data = $this->skip_blanks(substr($buf,4));
$data = ltrim(substr($buf,4));
while ($data && ($p = $this->strsep($data,' '))) {
if (! strcmp($p,'MB')) {
@ -1142,12 +1157,15 @@ final class Binkp extends BaseProtocol
$this->capSet(self::F_COMP,self::O_THEY|self::O_EXT);
} elseif (! strncmp($p,'CRAM-MD5-',9) && $this->originate && $this->capGet(self::F_MD,self::O_WANT)) {
if (($x=strlen(substr($p,9))) > 64 ) {
Log::error(sprintf('%s:! The challenge string is TOO LONG [%d] (%s)',self::LOGKEY,$x,$p));
if (strlen($hex=substr($p,9)) > 64 ) {
Log::error(sprintf('%s:! The challenge string is TOO LONG [%d] (%s)',self::LOGKEY,strlen($hex),$p));
} elseif (strlen($hex)%2) {
Log::error(sprintf('%s:! The challenge string is an odd size [%d] (%s)',self::LOGKEY,strlen($hex),$hex));
} else {
Log::info(sprintf('%s:- Remote wants MD5 auth',self::LOGKEY));
$this->md_challenge = hex2bin(substr($p,9));
Log::info(sprintf('%s:- Remote wants MD5 auth with [%s]',self::LOGKEY,$hex));
$this->md_challenge = hex2bin($hex);
if ($this->md_challenge)
$this->capSet(self::F_MD,self::O_THEY);
@ -1184,8 +1202,6 @@ final class Binkp extends BaseProtocol
return FALSE;
}
$buf = $this->skip_blanks($buf);
if ($this->optionGet(self::O_PWD) && $buf) {
while (($t=$this->strsep($buf," \t")))
if (strcmp($t,'non-secure') === 0) {
@ -1208,11 +1224,10 @@ final class Binkp extends BaseProtocol
}
/**
* @throws \Exception
* @todo It appears when we poll a node, we dont ask for passwords, but we still send echomail and files.
*/
private function M_pwd(string $buf): bool
{
$buf = $this->skip_blanks($buf);
$have_CRAM = !strncasecmp($buf,'CRAM-MD5-',9);
$have_pwd = $this->optionGet(self::O_PWD);
@ -1294,24 +1309,13 @@ final class Binkp extends BaseProtocol
if (strlen($opt))
$this->msgs(self::BPM_NUL,sprintf('OPT%s',$opt));
// 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;
}
$this->send->mail($ao);
$this->send->files($ao);
}
// @todo This is effectively redundant, because we are not getting files until later
$this->msgs(self::BPM_NUL,sprintf('TRF %lu %lu',$this->send->mail_size,$this->send->files_size));
if ($this->node->aka_authed) {
$this->msgs(self::BPM_OK,sprintf('%ssecure',$have_pwd ? '' : 'non-'));
} else {
// @todo Send any direct netmail to this node, if that node is unknown to us
$this->msgs(self::BPM_NUL,sprintf('TRF %lu %lu',$this->send->mail_size,$this->send->files_size));
$this->msgs(self::OK,'non-secure');
}
@ -1327,14 +1331,16 @@ final class Binkp extends BaseProtocol
/**
* Set up our BINKP session
*
* @param bool $force_queue
* @return int
* @throws \Exception
*/
protected function protocol_session(): int
protected function protocol_session(bool $force_queue=FALSE): int
{
if ($this->binkp_init() !== self::OK)
return self::S_FAILURE;
$this->force_queue = $force_queue;
$this->binkp_hs();
while (TRUE) {
@ -1344,9 +1350,12 @@ final class Binkp extends BaseProtocol
&& (! $this->sessionGet(self::SE_NOFILES))
&& (! $this->send->fd))
{
if (! $this->send->togo_count)
$this->getFiles($this->node);
// Open our next file to send
if ($this->send->togo_count && ! $this->send->fd) {
Log::info(sprintf('%s:- Opening next file to send',self::LOGKEY));
Log::info(sprintf('%s:- Opening next file to send - we have [%d] left',self::LOGKEY,$this->send->togo_count));
$this->send->open();
}
@ -1374,6 +1383,8 @@ final class Binkp extends BaseProtocol
// We dont have anything to send
} else {
Log::info(sprintf('%s:- Nothing left to send in this batch',self::LOGKEY));
// @todo We should look for more mail/files before thinking about sending an EOB
// IE: When we are set to only send X messages, but we have > X to send, get the next batch.
$this->sessionSet(self::SE_NOFILES);
}
}
@ -1478,13 +1489,50 @@ final class Binkp extends BaseProtocol
return $this->rc;
}
public function getFiles(Node $node): void
{
// Add our mail to the queue if we have authenticated
if ($node->aka_authed) {
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;
}
$this->send->mail($ao);
$this->send->files($ao);
$this->send->dynamic($ao);
/*
* Add "dynamic files", eg: nodelist, nodelist segment, status reports.
* Dynamic files are built on the fly
* * query "dynamic" for items for the address
* * column 'method' identifies the method that will be called, with the $ao as the argument
* * a 'new Item' is added to the queue
* * when it its ready to be sent, the __tostring() is called that renders it
* * when sent, the dynamic table is updated with the sent_at
*/
}
Log::info(sprintf('%s:- We have [%d] items to send to [%s]',self::LOGKEY,$this->send->togo_count,$ao->system->name));
} 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(',')));
}
}
/**
* Strip blanks at the beginning of a string
*
* @param string $str
* @return string
* @throws \Exception
* @todo Can this be replaced with ltrim?
* @deprecated - use ltrim instead
*/
private function skip_blanks(string $str): string
{
@ -1524,6 +1572,7 @@ final class Binkp extends BaseProtocol
* @param string $str
* @return bool
* @throws \Exception
* @deprecated No longer required since we are using ltrim
*/
private function isSpace(string $str):bool
{

View File

@ -7,11 +7,11 @@ use Illuminate\Support\Str;
use App\Classes\Protocol as BaseProtocol;
use App\Classes\Sock\SocketClient;
use App\Http\Controllers\DomainController;
use App\Models\{Address,Domain};
use App\Models\{Address,Domain,Mailer};
/**
* Respond to DNS queries and provide addresses to FTN nodes.
* http://ftsc.org/docs/fts-5004.001
*
* This implementation doesnt support EDNS nor DNSSEC.
*
@ -59,8 +59,8 @@ final class DNS extends BaseProtocol
public const DNS_TYPE_SOA = 6; // SOA Records
public const DNS_TYPE_MX = 15; // MX Records
public const DNS_TYPE_TXT = 16; // TXT Records
public const DNS_TYPE_AAAA = 28; // AAAA Records
public const DNS_TYPE_SRV = 33; // SRV Records
public const DNS_TYPE_OPT = 41; // OPT Records
public const DNS_TYPE_DS = 43; // DS Records (Delegation signer RFC 4034)
@ -70,7 +70,7 @@ final class DNS extends BaseProtocol
if (! parent::onConnect($client)) {
Log::withContext(['pid'=>getmypid()]);
$this->setClient($client);
$this->client = $client;
$this->protocol_session();
Log::info(sprintf('%s:= onConnect - Connection closed [%s]',self::LOGKEY,$client->address_remote));
@ -90,6 +90,7 @@ final class DNS extends BaseProtocol
* Handle a DNS query
*
* https://www.ietf.org/rfc/rfc1035.txt
* https://www.ietf.org/rfc/rfc2308.txt
* https://github.com/guyinatuxedo/dns-fuzzer/blob/master/dns.md
*
* labels 63 octets or less
@ -97,21 +98,29 @@ final class DNS extends BaseProtocol
* TTL positive values of a signed 32 bit number.
* UDP messages 512 octets or less
*
* @param bool $force_queue Not used here
* @return int
* @throws \Exception
*/
public function protocol_session(): int
public function protocol_session(bool $force_queue=FALSE): int
{
Log::debug(sprintf('%s:+ DNS Query',self::LOGKEY));
try {
$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()));
return FALSE;
}
Log::info(sprintf('%s:= DNS Query from [%s] for [%s]',self::LOGKEY,$this->client->address_remote,$this->query->domain));
// 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));
return $this->reply(self::DNS_NOTIMPLEMENTED);
return $this->reply(self::DNS_NOTIMPLEMENTED,[],$this->soa());
}
$dos = Domain::select(['id','name','dnsdomain'])->active();
@ -140,17 +149,9 @@ final class DNS extends BaseProtocol
return $this->reply(
self::DNS_NOERROR,
[serialize([
$this->domain_split(gethostname()),
$this->domain_split(Str::replace('@','.',config('app.mail.mail_from','nobody@'.gethostname()))),
1,
self::DEFAULT_TTL,
self::DEFAULT_TTL,
self::DEFAULT_TTL,
self::DEFAULT_TTL
]) => self::DNS_TYPE_SOA],
$this->soa(),
[],
[serialize($this->domain_split(gethostname())) => self::DNS_TYPE_NS],
[serialize($this->domain_split(config('fido.dns_ns'))) => self::DNS_TYPE_NS],
);
case self::DNS_TYPE_NS:
@ -158,23 +159,52 @@ final class DNS extends BaseProtocol
return $this->reply(
self::DNS_NOERROR,
[serialize($this->domain_split(gethostname())) => self::DNS_TYPE_NS]);
[serialize($this->domain_split(config('fido.dns_ns'))) => self::DNS_TYPE_NS]);
// Respond to A/AAAA/CNAME queries, with value or NAMEERR
case self::DNS_TYPE_CNAME:
case self::DNS_TYPE_A:
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));
$labels = clone($this->query->labels);
$mailer = '';
// If this is a SRV record query
if ($this->query->type === self::DNS_TYPE_SRV) {
if ($labels->skip(1)->first() !== '_tcp')
return $this->reply(self::DNS_NAMEERR);
switch ($labels->first()) {
case '_binkp':
$mailer = Mailer::where('name','BINKP')->singleOrFail();
break;
case '_ifcico':
$mailer = Mailer::where('name','EMSI')->singleOrFail();
break;
default:
return $this->reply(self::DNS_NAMEERR);
}
$labels->shift(2);
}
// First check that it is a query we can answer
// First label should be p.. or f..
if (! is_null($p=$this->parse('p',$labels->first())))
$labels->shift();
else
$p = 0;
if (is_null($f=$this->parse('f',$labels->shift())))
return $this->nameerr();
// We'll assume f0
if (! is_null($f=$this->parse('f',$labels->first())))
$labels->shift();
else
$f = 0;
if (is_null($n=$this->parse('n',$labels->shift())))
return $this->nameerr();
@ -200,22 +230,51 @@ final class DNS extends BaseProtocol
$ao = Address::findFTN(sprintf('%d:%d/%d.%d@%s',$z,$n,$f,$p,$d));
// Check we have the right record
if ((! $ao) || (! $ao->system->address) || (($rootdn !== self::TLD) && ((! $ao->zone->domain->dnsdomain) || ($ao->zone->domain->dnsdomain !== $rootdn)))) {
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())) {
return $this->reply(
self::DNS_NOERROR,
[serialize([
0, // priority
1, // weight
$xx->pivot->port,
$this->domain_split($ao->system->address),
]) => self::DNS_TYPE_SRV]);
} else {
return $this->nodata();
}
case self::DNS_TYPE_TXT:
Log::info(sprintf('%s:= Returning [%s] for DNS query [%s]',self::LOGKEY,$ao->system->name,$ao->ftn));
return $this->reply(
self::DNS_NOERROR,
[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));
return (! $ao->system->address)
? $this->nodata()
: $this->reply(
self::DNS_NOERROR,
[serialize($this->domain_split($ao->system->address)) => self::DNS_TYPE_CNAME]);
}
// Other attributes return NOTIMPL
default:
Log::error(sprintf('%s:! We dont support DNS query types [%d]',self::LOGKEY,$this->query->type));
return $this->reply(self::DNS_NOTIMPLEMENTED);
return $this->reply(self::DNS_NOTIMPLEMENTED,[],$this->soa());
}
}
@ -230,11 +289,36 @@ final class DNS extends BaseProtocol
return pack('n',$offset | (3 << 14));
}
/**
* Split a domain into a DNS domain string
*
* @param string $domain
* @return string
*/
private function domain_split(string $domain): string
{
$a = '';
foreach (explode('.',$domain) as $item)
$a .= pack('C',strlen($item)).$item;
$a .= "\x00";
return $a;
}
private function nameerr(): int
{
Log::error(sprintf('%s:! DNS query for a resource we dont manage [%s]',self::LOGKEY,$this->query->domain));
return $this->reply(self::DNS_NAMEERR);
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));
return $this->reply(self::DNS_NOERROR,[],$this->soa());
}
/**
@ -248,7 +332,7 @@ final class DNS extends BaseProtocol
{
$m = [];
return (preg_match('/^'.$prefix.'([0-9]+)+/',$label,$m) && ($m[1] <= DomainController::NUMBER_MAX))
return (preg_match('/^'.$prefix.'([0-9]+)+/',$label,$m) && ($m[1] <= Address::ADDRESS_FIELD_MAX))
? $m[1]
: NULL;
}
@ -320,24 +404,6 @@ final class DNS extends BaseProtocol
return TRUE;
}
/**
* Split a domain into a DNS domain string
*
* @param string $domain
* @return string
*/
private function domain_split(string $domain): string
{
$a = '';
foreach (explode('.',$domain) as $item)
$a .= pack('C',strlen($item)).$item;
$a .= "\x00";
return $a;
}
/**
* Return a DNS Resource Record
*
@ -378,10 +444,34 @@ final class DNS extends BaseProtocol
$a .= pack('NNNNN',$ars[2],$ars[3],$ars[4],$ars[5],$ars[6]);
break;
case self::DNS_TYPE_SRV:
$a .= pack('nnn',$ars[0],$ars[1],$ars[2]);
$a .= $ars[3];
break;
case self::DNS_TYPE_TXT:
$a .= pack('C',strlen($ars)).$ars;
break;
}
$reply .= pack('n',strlen($a)).$a;
return $reply;
}
private function soa(): array
{
return
[serialize([
$this->domain_split(config('fido.dns_ns')),
$this->domain_split(Str::replace('@','.',config('app.mail.mail_from','nobody@'.gethostname()))),
1, // Serial
self::DEFAULT_TTL, // Refresh
self::DEFAULT_TTL, // Retry
self::DEFAULT_TTL*7,// Expire
self::DEFAULT_TTL // Minimum cache
]) => self::DNS_TYPE_SOA];
}
}

View File

@ -32,7 +32,8 @@ final class Query
'arcount' => [0x05,'n',1], // Resource Records in the addition records section
];
public function __construct(string $buf) {
public function __construct(string $buf)
{
$this->buf = $buf;
$rx_ptr = 0;
@ -49,12 +50,20 @@ final class Query
$this->labels = collect();
while (($len=ord(substr($this->buf,$rx_ptr++,1))) !== 0x00) {
$this->labels->push(substr($this->buf,$rx_ptr,$len));
$this->labels->push(strtolower(substr($this->buf,$rx_ptr,$len)));
$rx_ptr += $len;
}
// Get the query type/class
try {
$result = unpack('ntype/nclass',substr($this->buf,$rx_ptr,4));
} catch (\Exception $e) {
Log::error(sprintf('%s:! Unpack failed: Buffer: [%s] (%d), RXPTR [%d]',self::LOGKEY,hex_dump($this->buf),strlen($this->buf),$rx_ptr));
throw $e;
}
$rx_ptr += 4;
$this->type = $result['type'];
$this->class = $result['class'];
@ -65,17 +74,16 @@ final class Query
if ($this->arcount) {
// Additional records, EDNS: https://datatracker.ietf.org/doc/html/rfc6891
if (($haystack = strstr(substr($this->buf,$rx_ptr+1+10),"\x00",true)) !== FALSE) {
Log::error(sprintf('%s:! DNS additional record format error?',self::LOGKEY));
// @todo catch this
Log::error(sprintf('%s:! DNS additional record format error?',self::LOGKEY),['buf'=>hex_dump($this->buf)]);
return;
}
$this->additional = new RR(substr($this->buf,$rx_ptr,(strlen($haystack) === 0) ? NULL : strlen($haystack)));
$rx_ptr += $this->additional->length;
}
if (strlen($this->buf) !== $rx_ptr) {
dd(['query remaining'=>strlen($this->buf)-$rx_ptr,'hex'=>hex_dump(substr($this->buf,$rx_ptr))]);
}
if (strlen($this->buf) !== $rx_ptr)
throw new \Exception(sprintf('! DNS Buffer still has [%d]: %s',strlen($this->buf)-$rx_ptr,hex_dump(substr($this->buf,$rx_ptr))));
}
public function __get($key)
@ -96,7 +104,8 @@ final class Query
}
}
public static function header_len() {
public static function header_len()
{
return collect(self::header)->sum(function($item) { return $item[2]*2; });
}

View File

@ -4,6 +4,7 @@ namespace App\Classes\Protocol\DNS;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use App\Classes\Protocol\DNS;
@ -26,8 +27,15 @@ final class RR
$domain = strstr($buf,"\x00",TRUE);
$i += strlen($domain)+1;
try {
$this->type = Arr::get(unpack('n',substr($buf,$i,2)),1);
$this->class = Arr::get(unpack('n',substr($buf,$i+2,2)),1);
} catch (\ErrorException $e) {
Log::error(sprintf('%s:! Error unpacking buffer [%s]',self::LOGKEY,$buf),['buf'=>hex_dump($buf)]);
return;
}
$i += 4;
switch ($this->type) {

View File

@ -3,13 +3,13 @@
namespace App\Classes\Protocol;
use Carbon\Carbon;
use Exception;
use Illuminate\Support\Facades\Log;
use App\Classes\Protocol as BaseProtocol;
use App\Classes\Sock\SocketClient;
use App\Classes\Sock\SocketException;
use App\Models\{Address,Setup};
use App\Exceptions\InvalidFTNException;
use App\Models\{Address,Mailer,Setup};
use App\Interfaces\CRC as CRCInterface;
use App\Interfaces\Zmodem as ZmodemInterface;
use App\Traits\CRC as CRCTrait;
@ -88,7 +88,6 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface
* @param SocketClient $client
* @return int|null
* @throws SocketException
* @throws Exception
*/
public function onConnect(SocketClient $client): ?int
{
@ -96,8 +95,7 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface
if (! parent::onConnect($client)) {
Log::withContext(['pid'=>getmypid()]);
// @todo Can this be SESSION_EMSI? if so, set an object class value that in EMSI of SESSION_EMSI, and move this method to the parent class
$this->session(self::SESSION_AUTO,$client,(new Address));
$this->session(Mailer::where('name','EMSI')->singleOrFail(),$client,(new Address));
$this->client->close();
exit(0);
}
@ -108,7 +106,7 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface
/**
* Send our welcome banner
*
* @throws Exception
* @throws \Exception
*/
private function emsi_banner(): void
{
@ -123,7 +121,7 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface
* Create the EMSI_DAT
*
* @return string
* @throws Exception
* @throws \Exception
*/
private function emsi_makedat(): string
{
@ -273,7 +271,7 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface
*
* @param string $str
* @return int
* @throws Exception
* @throws \Exception
*/
private function emsi_parsedat(string $str): int
{
@ -321,17 +319,38 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface
Log::debug(sprintf('%s: - Parsing AKA [%s]',self::LOGKEY,$rem_aka));
try {
if (! ($o = Address::findFTN($rem_aka))) {
if (! ($o = Address::findFTN($rem_aka,TRUE))) {
Log::debug(sprintf('%s: ? AKA is UNKNOWN [%s]',self::LOGKEY,$rem_aka));
$this->node->ftn_other = $rem_aka;
continue;
// 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(',')));
$this->node->ftn_other = $rem_aka;
continue;
} elseif (! $o->active) {
Log::alert(sprintf('%s:/ AKA is not active [%s] - ignoring',self::LOGKEY,$rem_aka));
$this->node->ftn_other = $rem_aka;
continue;
} else {
Log::info(sprintf('%s:- Got AKA [%s]',self::LOGKEY,$rem_aka));
}
} catch (Exception) {
Log::error(sprintf('%s: ! AKA is INVALID [%s]',self::LOGKEY,$rem_aka));
} catch (InvalidFTNException $e) {
Log::error(sprintf('%s:! AKA is INVALID [%s] (%s), ignoring',self::LOGKEY,$rem_aka,$e->getMessage()));
continue;
} catch (\Exception) {
Log::error(sprintf('%s: ! AKA is INVALID [%s]',self::LOGKEY,$rem_aka));
return self::S_FAILURE|self::S_ADDTRY;
}
// Check if the remote has our AKA
@ -504,7 +523,7 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface
* STEP 2A, RECEIVE EMSI HANDSHAKE
*
* @throws SocketException
* @throws Exception
* @throws \Exception
*/
private function emsi_recv(int $mode): int
{
@ -683,7 +702,7 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface
* STEP 2B, TRANSMIT EMSI HANDSHAKE
*
* @throws SocketException
* @throws Exception
* @throws \Exception
*/
private function emsi_send(): int
{
@ -822,7 +841,7 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface
* STEP 1, EMSI INIT
*
* @throws SocketException
* @throws Exception
* @throws \Exception
*/
protected function protocol_init(): int
{
@ -969,13 +988,14 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface
* Setup our EMSI session
*
* @return int
* @throws Exception
* @throws \Exception
*/
protected function protocol_session(): int
protected function protocol_session(bool $force_queue=FALSE): int
{
// @todo introduce emsi_init() to perform the job of protocol_init. Only needs to be done when we originate a session
Log::debug(sprintf('%s:+ Starting EMSI Protocol SESSION',self::LOGKEY));
$this->force_queue = $force_queue;
$was_req = 0;
$got_req = 0;
@ -1184,7 +1204,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);
$rc = (new Zmodem)->zmodem_receive($this->client,$zap,$this->recv,$this->node->address,$this->force_queue);
return ($rc === self::RCDO || $rc === self::ERROR);
}
@ -1194,7 +1214,7 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface
*
* @param int $zap
* @return bool
* @throws Exception
* @throws \Exception
*/
private function wazoosend(int $zap): bool
{

View File

@ -10,7 +10,7 @@ use App\Classes\File\{Receive,Send};
use App\Classes\Sock\{SocketClient,SocketException};
use App\Interfaces\CRC as CRCInterface;
use App\Interfaces\Zmodem as ZmodemInterface;
use App\Models\Address;
use App\Models\{Address,Mailer};
use App\Traits\CRC as CRCTrait;
/**
@ -213,7 +213,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
if (! parent::onConnect($client)) {
Log::withContext(['pid'=>getmypid()]);
$this->session(self::SESSION_ZMODEM,$client);
$this->session(Mailer::where('name','ZMODEM')->singleOrFail(),$client);
$this->client->close();
Log::info(sprintf('%s:= onConnect - Connection closed [%s]',self::LOGKEY,$client->address_remote));
@ -273,10 +273,11 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
/**
* Setup our ZMODEM session
*
* @param bool $force_queue Not used here
* @return int
* @throws \Exception
*/
public function protocol_session(): int
public function protocol_session(bool $force_queue=FALSE): int
{
$proto = $this->originate ? $this->node->optionGet(self::P_MASK) : $this->optionGet(self::P_MASK);
@ -301,7 +302,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
* @param int $canzap
* @return int
*/
public function zmodem_receive(SocketClient $client,int $canzap,Receive $recv,Address $ao): int
public function zmodem_receive(SocketClient $client,int $canzap,Receive $recv,Address $ao,bool $force_queue=FALSE): int
{
Log::debug(sprintf('%s:+ Starting ZModem Receive [%d]',self::LOGKEY,$canzap));
@ -363,7 +364,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
case self::FOP_CONT:
case self::FOP_OK:
Log::info(sprintf('%s: = zmodem_receive Receving [%s] from [%d]',self::LOGKEY,$this->recv->nameas,$this->recv->pos));
Log::info(sprintf('%s:= zmodem_receive Receiving [%s] from [%d]',self::LOGKEY,$this->recv->nameas,$this->recv->pos));
$frame = self::ZRINIT;
switch (($rc=$this->ls_zrecvfile($recv->pos))) {
@ -1489,7 +1490,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
$filesleft = -1;
} else {
$this->recv->new($file,$ao);
$this->recv->new($file,$ao,$this->force_queue);
}
return self::ZFILE;

View File

@ -52,6 +52,99 @@ final class SocketClient {
if ($this->type === SOCK_STREAM) {
socket_getsockname($connection,$this->address_local,$this->port_local);
socket_getpeername($connection,$this->address_remote,$this->port_remote);
// If HAPROXY is used, work get the clients address
if (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');
}
// Version/Command
$vc = $this->read_ch(5);
if (($x=($vc>>4)&0x7) !== 2) {
Log::error(sprintf('%s:! HAPROXY version [%d] is not handled',self::LOGKEY,$x));
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
case 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');
}
// 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]',
self::LOGKEY,
$this->address_remote,
$this->port_remote,
$dst,
Arr::get($dst_port,1),
));
}
Log::info(sprintf('%s:+ Connection host [%s] on port [%d] (%s)',self::LOGKEY,$this->address_remote,$this->port_remote,$this->type));
}
}
@ -108,6 +201,12 @@ final class SocketClient {
$sort = collect(['AAAA','A']);
if (filter_var($address,FILTER_VALIDATE_IP))
$resolved = collect([[
(($x=filter_var($address,FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) ? 'ipv6' : 'ip')=>$address,
'type'=>$x ? 'AAAA' : 'A'
]]);
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; })
@ -320,7 +419,7 @@ final class SocketClient {
}
} catch (\Exception $e) {
Log::error(sprintf('%s: - socket_recv Exception [%s]',self::LOGKEY,$e->getMessage()));
Log::error(sprintf('%s:! socket_recv Exception [%s]',self::LOGKEY,$e->getMessage()));
throw new SocketException($x=socket_last_error($this->connection),socket_strerror($x));
}

View File

@ -128,8 +128,9 @@ final class SocketServer {
try {
$r = new SocketClient($accept);
} catch (\ErrorException $e) {
} catch (\Exception $e) {
Log::error(sprintf('%s:! Creating Socket client failed? [%s]',self::LOGKEY,$e->getMessage()));
socket_close($accept);
continue;
}

View File

@ -0,0 +1,37 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Jobs\AddressIdle as Job;
use App\Models\{Address,Domain};
class AddressIdle extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'address:idle'
.' {domain : Domain}'
.' {--ftn= : Limit to specific address}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Find and mark nodes as hold/down/delist if idle';
/**
* Execute the console command.
*/
public function handle(): int
{
$do = Domain::where('name',$this->argument('domain'))->singleOrFail();
return Job::dispatchSync($do,$this->option('ftn') ? Address::findFTN($this->option('ftn')) : NULL);
}
}

View File

@ -0,0 +1,93 @@
<?php
namespace App\Console\Commands\Areafix;
use Carbon\Carbon;
use Illuminate\Console\Command;
use App\Models\{Address,Echoarea,Echomail};
class Rescan extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'areafix:rescan {ftn} {area} {days?}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Resend some echomail to a node';
/**
* Execute the console command.
*
* @return int
* @throws \Exception
*/
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)
throw new \Exception('FTN not found: '.$this->argument('ftn'));
// Check that the area belongs to the domain for the FTN
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));
// 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));
// 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));
}
}
}
return self::SUCCESS;
}
}

View File

@ -1,6 +1,6 @@
<?php
namespace App\Console\Commands;
namespace App\Console\Commands\BBS;
use Illuminate\Console\Command;
@ -13,7 +13,7 @@ class ANSIDecode extends Command
*
* @var string
*/
protected $signature = 'ansi:decode'
protected $signature = 'bbs:ansi:decode'
.' {file : ANS file to decode}';
/**
@ -26,10 +26,12 @@ class ANSIDecode extends Command
/**
* Execute the console command.
*
* @return mixed
* @return int
*/
public function handle()
public function handle(): int
{
echo ANSI::ansi($this->argument('file'));
return self::SUCCESS;
}
}

View File

@ -1,6 +1,6 @@
<?php
namespace App\Console\Commands;
namespace App\Console\Commands\BBS;
use Illuminate\Console\Command;
@ -13,7 +13,7 @@ class ANSIEncode extends Command
*
* @var string
*/
protected $signature = 'ansi:encode'
protected $signature = 'bbs:ansi:encode'
.' {file : ANS file to encode}';
/**
@ -26,9 +26,9 @@ class ANSIEncode extends Command
/**
* Execute the console command.
*
* @return mixed
* @return int
*/
public function handle()
public function handle(): int
{
foreach (ANSI::binary($this->argument('file')) as $line) {
foreach (str_split(bin2hex($line),2) as $y)
@ -36,5 +36,7 @@ class ANSIEncode extends Command
echo "\r";
}
return self::SUCCESS;
}
}

View File

@ -15,7 +15,7 @@ class FrameImport extends Command
*
* @var string
*/
protected $signature = 'frame:import {frame} {file} '.
protected $signature = 'bbs:frame:import {frame} {file} '.
'{--index=a : The frame index }'.
'{--access=0 : Is frame accessible }'.
'{--public=0 : Is frame limited to CUG }'.

View File

@ -6,7 +6,7 @@
* Inspired by Rob O'Donnell at irrelevant.com
*/
namespace App\Console\Commands;
namespace App\Console\Commands\BBS;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
@ -16,7 +16,7 @@ use App\Classes\Sock\{SocketException,SocketServer};
use App\Models\Mode;
use App\Models\Setup;
class BBSStart extends Command
class Start extends Command
{
private const LOGKEY = 'CBS';
@ -52,7 +52,7 @@ class BBSStart extends Command
'address'=>$o->ansitex_bind,
'port'=>$o->ansitex_port,
'proto'=>SOCK_STREAM,
'class'=>new Ansitex($o),
'class'=>new Ansitex,
]);
if (TRUE || $o->viewdata_active)
@ -60,7 +60,7 @@ class BBSStart extends Command
'address'=>$o->videotex_bind,
'port'=>$o->videotex_port,
'proto'=>SOCK_STREAM,
'class'=>new Videotex($o),
'class'=>new Videotex,
]);
$children = collect();

View File

@ -1,51 +0,0 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
use App\Classes\Sock\SocketException;
use App\Classes\Sock\SocketServer;
use App\Classes\Protocol\Zmodem as ZmodemClass;
class CommZmodemReceive extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'comm:zmodem:receive';
/**
* The console command description.
*
* @var string
*/
protected $description = 'ZMODEM receive';
/**
* Execute the console command.
*
* @return mixed
* @throws SocketException
*/
public function handle()
{
Log::info('Listening for ZMODEM connections...');
$server = new SocketServer(60177,'0.0.0.0');
$server->handler = [new ZmodemClass,'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

@ -1,45 +0,0 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
use App\Classes\Protocol;
use App\Classes\Protocol\Zmodem as ZmodemClass;
use App\Classes\Sock\SocketClient;
class CommZmodemSend extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'comm:zmodem:send {ip}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'ZMODEM send';
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
Log::info('Call ZMODEM send');
[$address,$service_port] = explode(':',$this->argument('ip'),2);
$client = SocketClient::create($address,$service_port);
$o = new ZmodemClass;
$o->session(Protocol::SESSION_ZMODEM,$client);
Log::info(sprintf('Connection ended: %s',$client->address_remote),['m'=>__METHOD__]);
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace App\Console\Commands\Debug;
use Illuminate\Console\Command;
use App\Models\Address;
class AddressCheck extends Command
{
protected $signature = 'debug:address:check'
.' {ftn : FTN}';
protected $description = 'Check the addresses we use for a node';
public function handle(): int
{
$o = Address::findFTN($this->argument('ftn'));
if (! $o) {
$this->error(sprintf('Address: %s doesnt exist?',$this->argument('ftn')));
return Command::FAILURE;
}
$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('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(',')));
return self::SUCCESS;
}
}

View File

@ -0,0 +1,50 @@
<?php
namespace App\Console\Commands\Debug;
use App\Models\Address;
use Illuminate\Console\Command;
class AddressCheckRole extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'debug:address:check:role {--f|fix : Fix the role}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Check address roles and optionally fix';
/**
* Execute the console command.
*/
public function handle(): int
{
foreach (Address::withTrashed()->with(['zone.domain'])->cursor() as $o) {
// Trim the role bit from role, since we now work out a role automatically.
// @todo This doesnt work, because role_id returns back the overridden role, and thus would remove it
if (($o->role & Address::NODE_ALL) === $o->role_id) {
$o->role &= ~$o->role_id;
if ((! $o->role) || ($o->role === Address::NODE_UNKNOWN))
$o->role = NULL;
if ($o->getDirty())
if ($this->option('fix')) {
$o->save();
} else {
$this->warn(sprintf('Not changing [%s](%s) from [%d] to [%d]',$o->ftn,$o->role_name,$o->getOriginal('role'),$o->role));
}
}
}
return self::SUCCESS;
}
}

View File

@ -1,12 +1,12 @@
<?php
namespace App\Console\Commands;
namespace App\Console\Commands\Debug;
use Illuminate\Console\Command;
use Illuminate\Database\QueryException;
use Illuminate\Support\Facades\DB;
use App\Models\Address;
use App\Models\{Address,System};
class AddressMerge extends Command
{
@ -15,7 +15,7 @@ class AddressMerge extends Command
*
* @var string
*/
protected $signature = 'address:merge'
protected $signature = 'debug:address:merge'
.' {src : Source Address}'
.' {dst : Destination Address}'
.' {--F|force : Force}'
@ -34,79 +34,84 @@ class AddressMerge extends Command
*
* @return int
*/
public function handle()
public function handle(): int
{
$src = Address::withTrashed()->findOrfail($this->argument('src'));
$dst = Address::withTrashed()->findOrfail($this->argument('dst'));
if ((! $this->option('ignore')) && ($src->system_id !== $dst->system_id) && ($src->system->name !== 'Discovered System')) {
if ((! $this->option('ignore')) && ($src->system_id !== $dst->system_id) && ($src->system->name !== System::default)) {
$this->error(sprintf('FTN addresses are from different systems (%s/%s)',$src->system->name,$dst->system->name));
exit(1);
return self::FAILURE;
}
if ((! $this->option('force')) && ($src->ftn !== $dst->ftn)) {
$this->error(sprintf('FTN addresses are not the same (%s:%s)',$src->ftn,$dst->ftn));
exit(1);
return self::FAILURE;
}
if ($src->active) {
$this->error(sprintf('Source [%s] is still active',$src->ftn));
exit(1);
return self::FAILURE;
}
DB::beginTransaction();
// Find all echomail seenbys
$x = DB::update('update echomail_seenby set address_id=? where address_id=?',[$dst->id,$src->id]);
$x = DB::update('UPDATE echomail_seenby SET address_id=? WHERE address_id=?',[$dst->id,$src->id]);
$this->info(sprintf('Updated [%d] echomail seenby records',$x));
// Find all echomail paths
$x = DB::update('update echomail_path set address_id=? where address_id=?',[$dst->id,$src->id]);
$x = DB::update('UPDATE echomail_path SET address_id=? WHERE address_id=?',[$dst->id,$src->id]);
$this->info(sprintf('Updated [%d] echomail path records',$x));
// Find all echomails
$x = DB::update('update echomails set fftn_id=? where fftn_id=?',[$dst->id,$src->id]);
$x = DB::update('UPDATE echomails SET fftn_id=? WHERE fftn_id=?',[$dst->id,$src->id]);
$this->info(sprintf('Updated [%d] echomail source records',$x));
// Find all netmails
$x = DB::update('update netmails set fftn_id=? where fftn_id=?',[$dst->id,$src->id]);
$x = DB::update('UPDATE netmails SET fftn_id=? WHERE fftn_id=?',[$dst->id,$src->id]);
$this->info(sprintf('Updated [%d] netmail source records',$x));
// Find all netmails
$x = DB::update('update netmails set tftn_id=? where tftn_id=?',[$dst->id,$src->id]);
$x = DB::update('UPDATE netmails SET tftn_id=? WHERE tftn_id=?',[$dst->id,$src->id]);
$this->info(sprintf('Updated [%d] netmail destination records',$x));
// Find all nodelist
$x = DB::update('update address_nodelist set address_id=? where address_id=?',[$dst->id,$src->id]);
$x = DB::update('UPDATE address_nodelist SET address_id=? WHERE address_id=?',[$dst->id,$src->id]);
$this->info(sprintf('Updated [%d] nodelist records',$x));
// Find all file seenbys
$x = DB::update('update file_seenby set address_id=? where address_id=?',[$dst->id,$src->id]);
$x = DB::update('UPDATE file_seenby SET address_id=? WHERE address_id=?',[$dst->id,$src->id]);
$this->info(sprintf('Updated [%d] file seenby records',$x));
// Find all files
$x = DB::update('update files set fftn_id=? where fftn_id=?',[$dst->id,$src->id]);
$x = DB::update('UPDATE files SET fftn_id=? WHERE fftn_id=?',[$dst->id,$src->id]);
$this->info(sprintf('Updated [%d] file source records',$x));
// Resubscribe echoareas
try {
$x = DB::update('update address_echoarea set address_id=? where address_id=?',[$dst->id,$src->id]);
$x = DB::update('UPDATE address_echoarea SET address_id=? WHERE address_id=?',[$dst->id,$src->id]);
} catch (QueryException $e) {
DB::rollback();
$this->error(sprintf('You may need to remove %s:%s (%d) from echoareas',$src->ftn,$src->system->name,$src->id));
exit(1);
return self::FAILURE;
}
$this->info(sprintf('Updated [%d] echomail subscription records',$x));
// Resubscribe fileareas
try {
$x = DB::update('update address_filearea set address_id=? where address_id=?',[$dst->id,$src->id]);
$x = DB::update('UPDATE address_filearea SET address_id=? WHERE address_id=?',[$dst->id,$src->id]);
} catch (QueryException $e) {
DB::rollback();
$this->error(sprintf('You may need to remove %s:%s (%d) from fileareas',$src->ftn,$src->system->name,$src->id));
exit(1);
return self::FAILURE;
}
$this->info(sprintf('Updated [%d] filearea subscription records',$x));
@ -126,6 +131,6 @@ class AddressMerge extends Command
}
}
return Command::SUCCESS;
return self::SUCCESS;
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace App\Console\Commands\Debug;
use Illuminate\Console\Command;
use App\Classes\File\Send;
use App\Classes\File\Send\Dynamic;
use App\Models\Address;
use App\Models\Dynamic as DynamicModel;
class DynamicItem extends Command
{
protected $signature = 'debug:dynamic:item'
.' {name : Dynamic Item}'
.' {ftn : FTN Address}';
protected $description = 'Generate a dynamic item';
public function handle(): int
{
$do = DynamicModel::where('name',$this->argument('name'))->single();
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->open();
echo $d->read($d->size);
return self::SUCCESS;
}
}

View File

@ -1,6 +1,6 @@
<?php
namespace App\Console\Commands;
namespace App\Console\Commands\Debug;
use Illuminate\Console\Command;
@ -14,7 +14,7 @@ class EchomailDump extends Command
*
* @var string
*/
protected $signature = 'echomail:dump {id}';
protected $signature = 'debug:echomail:dump {id}';
/**
* The console command description.
@ -26,10 +26,12 @@ class EchomailDump extends Command
/**
* Execute the console command.
*
* @return mixed
* @return int
*/
public function handle()
public function handle(): int
{
dump(Echomail::findOrFail($this->argument('id')));
return self::SUCCESS;
}
}

View File

@ -1,6 +1,6 @@
<?php
namespace App\Console\Commands;
namespace App\Console\Commands\Debug;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Notification;
@ -15,7 +15,7 @@ class NetmailTest extends Command
*
* @var string
*/
protected $signature = 'netmail:test'
protected $signature = 'debug:netmail:test'
.' {ftn : FTN to send netmail to}';
/**
@ -28,13 +28,15 @@ class NetmailTest extends Command
/**
* Execute the console command.
*
* @return mixed
* @return int
* @throws \Exception
*/
public function handle()
public function handle():int
{
$ao = Address::findFTN($this->argument('ftn'));
Notification::route('netmail',$ao)->notify(new NetmailTestNotification());
return self::SUCCESS;
}
}

View File

@ -0,0 +1,62 @@
<?php
namespace App\Console\Commands\Debug;
use Illuminate\Console\Command;
use App\Models\{Echomail,Netmail};
use App\Models\Address;
class PacketAddress extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'debug:packet:address'
.' {ftn : FTN Address}'
.' {type : Message Type}'
.' {dbid : Message ID}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Generate a mail packet for a system';
/**
* Execute the console command.
*
* @return int
* @throws \Exception
*/
public function handle(): int
{
$ao = Address::findFTN($this->argument('ftn'));
switch ($this->argument('type')) {
case 'echomail':
$o = new Echomail;
break;
case 'netmail':
$o = new Netmail;
break;
default:
$this->error('Unknown type: '.$this->argument('type'));
exit(1);
}
echo hex_dump($ao
->system
->packet($ao)
->mail($o->where('id',$this->argument('dbid'))->get())
->generate()
);
return self::SUCCESS;
}
}

View File

@ -0,0 +1,67 @@
<?php
namespace App\Console\Commands\Debug;
use Illuminate\Console\Command;
use App\Models\Address;
class PacketDump extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'debug:packet:dump'.
' {type : Type of packet, netmail|echomail }'.
' {ftn : FTN}'.
' {file? : filename}'.
' {--dump : Dump packet}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Create an outgoing FTN packet';
/**
* Execute the console command.
*
* @return int
* @throws \Exception
*/
public function handle(): int
{
$ao = Address::findFTN($this->argument('ftn'));
switch (strtolower($this->argument('type'))) {
case 'netmail':
$pkt = $ao->getNetmail();
break;
case 'echomail':
$pkt = $ao->getEchomail();
break;
default:
$this->error('Unknown type: '.$this->argument('type'));
throw new \Exception('Unknown type: '.$this->argument('type'));
}
if ($this->option('dump')) {
$this->info('Item Name:'.$pkt->name);
$this->info('Item Type:'.get_class($pkt));
$this->info('Dump:');
echo hex_dump($pkt);
} else {
$f = fopen($this->argument('file'),'w+');
fputs($f,(string)$pkt);
fclose($f);
}
return self::SUCCESS;
}
}

View File

@ -1,6 +1,6 @@
<?php
namespace App\Console\Commands;
namespace App\Console\Commands\Debug;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Mail;
@ -15,7 +15,7 @@ class SendTestEmail extends Command
*
* @var string
*/
protected $signature = 'email:test {id}';
protected $signature = 'debug:email:test {id}';
/**
* The console command description.
@ -27,13 +27,15 @@ class SendTestEmail extends Command
/**
* Execute the console command.
*
* @return mixed
* @return int
*/
public function handle()
public function handle(): int
{
$uo = User::find($this->argument('id'));
Mail::to($uo->email)
->send(new MailTest($uo));
return self::SUCCESS;
}
}

View File

@ -0,0 +1,47 @@
<?php
namespace App\Console\Commands\Debug;
use Illuminate\Console\Command;
use App\Models\Domain;
class ZoneCheck extends Command
{
protected $signature = 'debug:zone:check'
.' {domain : Domain Name}'
.' {--Z|zone= : Zone}';
protected $description = 'Check that the addresses in a zone are configured correctly';
public function handle(): int
{
$do = Domain::where('name',$this->argument('domain'))->singleOrFail();
foreach ($do->zones->sortby('zone_id') as $zo) {
if ($this->option('zone') && ($this->option('zone') != $zo->zone_id))
continue;
$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' : '',
];
}));
}
return self::SUCCESS;
}
}

View File

@ -30,9 +30,9 @@ class EchoareaImport extends Command
/**
* Execute the console command.
*
* @return mixed
* @return int
*/
public function handle()
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'));

View File

@ -0,0 +1,40 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Models\Domain;
use App\Jobs\FileareaImport as Job;
class FileareaImport extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'filearea:import'
.' {file : NA File}'
.' {domain : Domain}'
.' {--P|prefix= : Add prefix to description}'
.' {--U|unlink : Delete file after import}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Import Filearea';
/**
* Execute the console command.
*
* @return int
*/
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'));
}
}

View File

@ -0,0 +1,90 @@
<?php
namespace App\Console\Commands\Filefix;
use Carbon\Carbon;
use Illuminate\Console\Command;
use App\Models\{Address,Filearea,File};
class Rescan extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'filefix:rescan {ftn} {area} {file?}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Resend some files to a node';
/**
* Execute the console command.
*
* @return int
* @throws \Exception
*/
public function handle(): int
{
$ao = Address::findFtn($this->argument('ftn'));
if (! $ao)
throw new \Exception('FTN not found: '.$this->argument('ftn'));
// Check that the area belongs to the domain for the FTN
if (! $this->argument('area'))
throw new \Exception('Areaname is required');
$fao = Filearea::where('name',$this->argument('area'))->singleOrFail();
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));
}
}
}
return self::SUCCESS;
}
}

View File

@ -27,7 +27,7 @@ class FilesList extends Command
*
* @return int
*/
public function handle()
public function handle(): int
{
$this->table([
'files.id' => 'ID',
@ -37,6 +37,6 @@ class FilesList extends Command
->join('fileareas',['fileareas.id'=>'files.filearea_id'])
->cursor());
return Command::SUCCESS;
return self::SUCCESS;
}
}

View File

@ -1,34 +0,0 @@
<?php
namespace App\Console\Commands;
use Database\Seeders\InitialSetupSeeder;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Artisan;
class InitialSetup extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'initial:setup';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Initial Setup of DB';
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
Artisan::call('db:seed',['class'=>InitialSetupSeeder::class]);
}
}

View File

@ -25,7 +25,7 @@ class JobList extends Command
/**
* Execute the console command.
*/
public function handle()
public function handle(): int
{
$lastq = NULL;
@ -35,16 +35,17 @@ class JobList extends Command
$lastq = $o->queue;
}
$this->info(sprintf('%s-%d: %s[%s] - %d tries (Created: %s,Timeout: %s,Next: %s)',
$this->info(sprintf('%s-%d: %s[%s] - %d/%d tries [Next:%s]%s',
$o->uuid,
$o->id,
$o->display_name,
$o->command->subject,
$o->attempts,
$o->created_at,
$o->retry_until ?: '-',
$o->reserved_at ?: '-',
$o->command->jobname,
$o->attempts,$o->maxTries,
$o->available_at ?: '-',
$o->attempts ? sprintf(' (Created:%s)',$o->created_at) : ''
));
}
return self::SUCCESS;
}
}

View File

@ -0,0 +1,79 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Models\Address;
class MailList extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'mail:list {ftn : FTN address}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'List mail waiting for a node';
/**
* Execute the console command.
*
* @return int
* @throws \Exception
*/
public function handle(): int
{
$ao = Address::findFTN($this->argument('ftn'),TRUE);
if (! $ao) {
$this->error(sprintf('%s not found?',$this->argument('ftn')));
return self::FAILURE;
}
$this->info('Netmail');
$this->table([
'id' => 'ID',
'msgid' => 'MSGID',
'from' => 'FROM',
'to' => 'TO',
'subject' => 'SUBJECT',
],$ao->netmailWaiting()->map(function($item) {
return [
'id'=>$item->id,
'msgid'=>$item->msgid,
'from'=>$item->from,
'to'=>$item->to,
'subject'=>$item->subject,
];
}));
$this->info('Echomail');
$this->table([
'id' => 'ID',
'msgid' => 'MSGID',
'from' => 'FROM',
'to' => 'TO',
'subject' => 'SUBJECT',
'area' => 'AREA',
],$ao->echomailWaiting()->map(function($item) {
return [
'id'=>$item->id,
'msgid'=>$item->msgid,
'from'=>$item->from,
'to'=>$item->to,
'subject'=>$item->subject,
'area'=>$item->echoarea->name,
];
}));
return self::SUCCESS;
}
}

View File

@ -27,7 +27,7 @@ class MailSend extends Command
/**
* Execute the console command.
*/
public function handle()
public function handle(): int
{
switch ($this->option('type')) {
case 'crash':
@ -48,5 +48,7 @@ class MailSend extends Command
default:
$this->error('Specify -T crash, normal or all');
}
return self::SUCCESS;
}
}

View File

@ -17,6 +17,7 @@ class NodelistImport extends Command
protected $signature = 'nodelist:import'
.' {file : File ID | filename}'
.' {domain? : Domain Name}'
.' {--I|ignorecrc : Ignore the CRC}'
.' {--D|delete : Delete old data for the date}'
.' {--U|unlink : Delete file after import}'
.' {--T|test : Dry run}';
@ -31,16 +32,26 @@ class NodelistImport extends Command
/**
* Execute the console command.
*
* @return mixed
* @return int
*/
public function handle()
public function handle():int
{
try {
return Job::dispatchSync(
is_numeric($x=$this->argument('file')) ? File::findOrFail($x) : $x,
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('test'),
$this->option('ignorecrc'),
);
} catch (\Exception $e) {
$this->error($e->getMessage());
return self::FAILURE;
}
}
}

View File

@ -3,10 +3,11 @@
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Storage;
use App\Classes\File;
use App\Classes\FTN\Packet;
use App\Models\System;
use App\Models\{Address,Echomail};
class PacketInfo extends Command
{
@ -17,7 +18,7 @@ class PacketInfo extends Command
*/
protected $signature = 'packet:info'
.' {file : Packet to process}'
.' {system? : System the packet is from}';
.' {ftn? : FTN the packet is from}';
/**
* The console command description.
@ -29,41 +30,63 @@ class PacketInfo extends Command
/**
* Execute the console command.
*
* @return mixed
* @throws \App\Classes\FTN\InvalidPacketException
* @return int
* @throws \App\Exceptions\InvalidPacketException
*/
public function handle()
public function handle():int
{
$f = new File($this->argument('file'));
$s = $this->argument('system') ? System::where('name',$this->argument('system'))->singleOrFail() : NULL;
$fs = Storage::disk(config('fido.local_disk'));
$rel_name = sprintf('%s/%s',config('fido.dir'),$this->argument('file'));
$a = NULL;
$f = new File($fs->path($rel_name));
$m = NULL;
if ($this->argument('ftn')) {
$a = Address::findFTN($this->argument('ftn'));
} elseif (preg_match(sprintf('/^%s\.(.{3})$/',Packet::regex),$this->argument('file'),$m)) {
$a = Address::findOrFail(hexdec($m[1]));
}
foreach ($f as $packet) {
$pkt = Packet::process($packet,$x=$f->itemName(),$f->itemSize(),$s);
$pkt = Packet::process($packet,$x=$f->itemName(),$f->itemSize(),$a?->zone->domain);
$this->alert(sprintf('File Name: %s',$x));
$this->info(sprintf('Packet Type : %s (%s)',$pkt->type,get_class($pkt)));
$this->info(sprintf('From : %s to %s',$pkt->fftn,$pkt->tftn));
$this->info(sprintf('Dated : %s',$pkt->date));
$this->info(sprintf('Password : %s (%s)',$pkt->password,$pkt->password ? 'SET' : 'NOT set'));
$this->info(sprintf('Messages : %d',$pkt->messages->count()));
$this->info(sprintf('Tosser : %d (%s) version %s',$pkt->software->code,$pkt->software->name,$pkt->software_ver));
$this->info(sprintf('Capabilities: %x',$pkt->capability));
$this->info(sprintf('From : %s to %s',$pkt->fftn->ftn,$pkt->tftn ? $pkt->tftn->ftn : $pkt->tftn_t));
$this->info(sprintf('Dated : %s (%s) [%s]',$pkt->date,$pkt->date->timestamp,$pkt->date->tz->toOffsetName()));
$this->info(sprintf('Password : %s (%s)',$pkt->password ?: '-',$pkt->password ? 'SET' : 'NOT set'));
$this->info(sprintf('Messages : %d',$pkt->count()));
$this->info(sprintf('Tosser : %d (%s) version %s (%x)',$pkt->software->code,$pkt->software->name,$pkt->software_ver,$pkt->product));
$this->info(sprintf('Capabilities: %s',$pkt->capability));
$this->info(sprintf('Has Errors : %s',$pkt->errors->count() ? 'YES' : 'No'));
$this->info(sprintf('Messages : %d',$pkt->count()));
foreach ($pkt as $msg) {
try {
$this->warn(sprintf('- Date : %s',$msg->date));
$this->warn(sprintf(' - Flags : %s',$msg->flags()->filter()->keys()->join(', ')));
$this->warn(sprintf(' - From : %s (%s)',$msg->user_from,$msg->fftn));
$this->warn(sprintf(' - To : %s (%s)',$msg->user_to,$msg->tftn));
$this->warn(sprintf(' - Subject: %s',$msg->subject));
$this->warn(sprintf(' - Area : %s',$msg->echoarea));
echo "\n";
if ($msg->errors)
foreach ($msg->errors->errors()->all() as $error)
$this->line(' - '.$error);
try {
$this->warn(sprintf('- Date : %s (%s)',$msg->datetime,$msg->datetime->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));
$this->warn(sprintf(' - From : %s (%s)',$msg->from,$msg->fftn->ftn));
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(' - Subject: %s',$msg->subject));
if ($msg instanceof Echomail)
$this->warn(sprintf(' - Area : %s',$msg->echoarea->name));
if ($msg->errors->count()) {
echo "\n";
$this->error("Errors:");
foreach ($msg->errors->all() as $error)
$this->error(' - '.$error);
}
} catch (\Exception $e) {
$this->error('! ERROR: '.$e->getMessage());
@ -76,15 +99,17 @@ class PacketInfo extends Command
foreach ($pkt->errors as $msg) {
$this->error(sprintf('- Date: %s',$msg->date));
$this->error(sprintf(' - FLAGS: %s',$msg->flags()->filter()->keys()->join(', ')));
$this->error(sprintf(' - From: %s (%s)',$msg->user_from,$msg->fftn));
$this->error(sprintf(' - To: %s (%s)',$msg->user_to,$msg->tftn));
$this->error(sprintf(' - From: %s (%s)',$msg->from,$msg->fftn));
$this->error(sprintf(' - To: %s (%s)',$msg->to,$msg->tftn));
$this->error(sprintf(' - Subject: %s',$msg->subject));
foreach ($msg->errors->errors()->all() as $error)
foreach ($msg->errors->all() as $error)
$this->line(' - '.$error);
}
$this->line("\n");
}
return self::SUCCESS;
}
}

View File

@ -2,14 +2,39 @@
namespace App\Console\Commands;
use Carbon\Carbon;
use Illuminate\Console\Command;
use App\Classes\File;
use App\Classes\FTN\Packet;
use App\Jobs\MessageProcess as Job;
use App\Jobs\PacketProcess as Job;
use App\Models\Address;
/**
* Things to test
* + Packet
* - Sender doesnt exist (try and send a bounce message in the same session)
* - Sender defined in DB by not ours
* - Sender has wrong password
* - Packet too old
*
* + Echomail
* - Area doesnt exist (to uplink)
* - Sender not subscribed (to uplink)
* - Sender cannot post (to uplink)
* - Sender has wrong address for echorea domain (to uplink)
* - Test message in echoarea
* - Echomail from address doesnt match packet envelope (to uplink)
* - Echomail too old (to uplink)
* - Rescanned dont generate notifications
* - Rescanned dont trigger bots
* - Some Notifications to an uplink should go to the admin instead?
*
* + Netmail
* - To hub, and user not defined (reject)
* - To hub, but user redirect (redirected)
* - To areafix (processed)
* - To ping (respond)
* - With trace turned on (respond)
*/
class PacketProcess extends Command
{
/**
@ -20,7 +45,8 @@ class PacketProcess extends Command
protected $signature = 'packet:process'
.' {file : Packet to process}'
.' {--N|nobot : Dont process bots}'
.' {ftn : System the packet is from}';
.' {ftn? : System the packet is from}'
.' {--Q|dontqueue : Dont queue the message}';
/**
* The console command description.
@ -32,24 +58,28 @@ class PacketProcess extends Command
/**
* Execute the console command.
*
* @return mixed
* @throws \App\Classes\FTN\InvalidPacketException
* @return int
* @throws \Exception
*/
public function handle()
public function handle(): int
{
$f = new File($this->argument('file'));
$a = Address::findFTN($this->argument('ftn'));
$rel_name = sprintf('%s/%s',config('fido.dir'),$this->argument('file'));
foreach ($f as $packet) {
foreach (Packet::process($packet,$f->itemName(),$f->itemSize(),$a->system) as $msg) {
// @todo Quick check that the packet should be processed by us.
// @todo validate that the packet's zone is in the domain.
$m = [];
if ($this->argument('ftn')) {
$ao = Address::findFTN($this->argument('ftn'));
$this->info(sprintf('Processing message from [%s] with msgid [%s] in (%s)',$msg->fboss,$msg->msgid,$f->pktName()));
} elseif (preg_match(sprintf('/^%s\.(.{3})$/',Packet::regex),$this->argument('file'),$m)) {
$ao = Address::findOrFail(hexdec($m[1]));
// Dispatch job.
Job::dispatchSync($msg,$f->pktName(),$a,$a,Carbon::now(),$this->option('nobot'));
}
}
} else {
$this->error('Unable to determine sender FTN address');
return self::FAILURE;
}
Job::dispatchSync($rel_name,$ao->zone->domain,$this->option('dontqueue'));
return self::SUCCESS;
}
}

View File

@ -4,7 +4,7 @@ namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Models\System;
use App\Models\Address;
class PacketSystem extends Command
{
@ -14,7 +14,7 @@ class PacketSystem extends Command
* @var string
*/
protected $signature = 'packet:system'
.' {sid : System ID}';
.' {ftn : System address}';
/**
* The console command description.
@ -26,21 +26,31 @@ class PacketSystem extends Command
/**
* Execute the console command.
*
* @return mixed
* @throws \App\Classes\FTN\InvalidPacketException
* @return int
* @throws \Exception
*/
public function handle()
public function handle(): int
{
$so = System::findOrFail($this->argument('sid'));
$ao = Address::findFTN($this->argument('ftn'));
foreach ($so->addresses as $ao) {
$pkt = $ao->getEchomail(FALSE);
$this->info(sprintf('System address [%s] has [%d] messages.',$ao->ftn,$pkt?->count()));
foreach ($ao->system->addresses->where('validated',TRUE) as $o) {
$pkt = $o->getEchomail();
$this->info(sprintf('System address [%s] has [%d] echomail messages.',$o->ftn,$pkt?->count()));
if ($pkt) {
foreach ($pkt as $msg)
$this->warn(sprintf('- %s',$msg->msgid));
$this->warn(sprintf('- %s (%s)',$msg->msgid,$msg->id));
}
$pkt = $o->getNetmail();
$this->info(sprintf('System address [%s] has [%d] netmail messages.',$o->ftn,$pkt?->count()));
if ($pkt) {
foreach ($pkt as $msg)
$this->warn(sprintf('- %s (%s)',$msg->msgid,$msg->id));
}
}
return self::SUCCESS;
}
}

View File

@ -30,10 +30,10 @@ class ServerStart extends Command
/**
* Execute the console command.
*
* @return void
* @return int
* @throws SocketException
*/
public function handle()
public function handle(): int
{
Log::info(sprintf('%s:+ Server Starting (%d)',self::LOGKEY,getmypid()));
$o = Setup::findOrFail(config('app.id'));
@ -71,7 +71,7 @@ class ServerStart extends Command
if (! $start->count()) {
Log::alert(sprintf('%s:! No servers configured to start',self::LOGKEY));
return;
return self::FAILURE;
}
pcntl_signal(SIGCHLD,SIG_IGN);
@ -105,7 +105,7 @@ class ServerStart extends Command
Log::info(sprintf('%s:= Finished: [%s]',self::LOGKEY,$item));
// Child finished we need to get out of this loop.
exit;
return self::SUCCESS;
}
Log::debug(sprintf('%s:- Forked for [%s] (%d)',self::LOGKEY,$item,$pid));
@ -125,5 +125,7 @@ class ServerStart extends Command
// Done
Log::debug(sprintf('%s:= Finished.',self::LOGKEY));
return self::SUCCESS;
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
use App\Jobs\SystemHeartbeat as Job;
class SystemHeartbeat extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'system:heartbeat'
.' {--F|force : Force poll systems that are auto hold}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Poll systems that we havent seen for a while';
/**
* Execute the console command.
*/
public function handle(): int
{
Log::info('CSH:- Triggering heartbeat to systems');
return Job::dispatchSync($this->option('force'));
}
}

View File

@ -29,11 +29,9 @@ class TicProcess extends Command
*
* @return int
*/
public function handle()
public function handle(): int
{
// Dispatch job.
Job::dispatchSync($this->argument('file'),$this->argument('domain'));
return Command::SUCCESS;
return Job::dispatchSync($this->argument('file'),$this->argument('domain'));
}
}

View File

@ -19,14 +19,16 @@ class UserCodeSend extends Command
/**
* Execute the console command.
*
* @return mixed
* @return int
* @throws \Exception
*/
public function handle()
public function handle(): int
{
$ao = Address::findFTN($this->argument('ftn'));
$uo = User::where('email',$this->argument('email'))->singleOrFail();
Notification::route('netmail',$ao->parent())->notify(new AddressLink($uo));
Notification::route('netmail',$ao->uplink())->notify(new AddressLink($uo));
return self::SUCCESS;
}
}

View File

@ -27,12 +27,14 @@ class UserMakeAdmin extends Command
*
* @return int
*/
public function handle()
public function handle(): int
{
$o = User::where('email',$this->argument('email'))->firstOrfail();
$o->admin = ! $o->admin;
$o->save();
$this->info(sprintf('User [%s] %s an admin',$o->email,$o->admin ? 'IS' : 'is NOT'));
return self::SUCCESS;
}
}

View File

@ -2,10 +2,11 @@
namespace App\Console;
use App\Jobs\MailSend;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
use App\Jobs\{AddressIdleDomain,MailSend,SystemHeartbeat};
class Kernel extends ConsoleKernel
{
/**
@ -27,6 +28,8 @@ class Kernel extends ConsoleKernel
{
$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');
}
/**

View File

@ -0,0 +1,9 @@
<?php
namespace App\Exceptions;
use Exception;
class InvalidCRCException extends Exception
{
}

View File

@ -0,0 +1,9 @@
<?php
namespace App\Exceptions;
use Exception;
class InvalidFTNException extends Exception
{
}

View File

@ -1,6 +1,6 @@
<?php
namespace App\Classes\FTN;
namespace App\Exceptions;
use Exception;

View File

@ -0,0 +1,9 @@
<?php
namespace App\Exceptions;
use Exception;
class InvalidPasswordException extends Exception
{
}

View File

@ -0,0 +1,9 @@
<?php
namespace App\Exceptions;
use Exception;
class NoReadSecurityException extends Exception
{
}

View File

@ -0,0 +1,9 @@
<?php
namespace App\Exceptions;
use Exception;
class NoWriteSecurityException extends Exception
{
}

View File

@ -0,0 +1,9 @@
<?php
namespace App\Exceptions;
use Exception;
class NodeNotSubscribedException extends Exception
{
}

View File

@ -0,0 +1,9 @@
<?php
namespace App\Exceptions\TIC;
use Exception;
class NoFileAreaException extends Exception
{
}

View File

@ -0,0 +1,9 @@
<?php
namespace App\Exceptions\TIC;
use Exception;
class NotToMeException extends Exception
{
}

View File

@ -0,0 +1,9 @@
<?php
namespace App\Exceptions\TIC;
use Exception;
class SizeMismatchException extends Exception
{
}

View File

@ -2,13 +2,14 @@
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Carbon\Carbon;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
class LoginController extends Controller
{
/*
@ -38,7 +39,8 @@ class LoginController extends Controller
*/
public function __construct()
{
$this->middleware('guest')->except('logout');
$this->middleware('guest')
->except('logout');
}
public function login(Request $request)
@ -70,6 +72,7 @@ class LoginController extends Controller
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

@ -2,16 +2,33 @@
namespace App\Http\Controllers;
use Illuminate\Support\Collection;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Gate;
use App\Http\Requests\DomainRequest;
use App\Models\{Address,Domain,Zone};
class DomainController extends Controller
{
// http://ftsc.org/docs/frl-1002.001
public const NUMBER_MAX = 0x7fff;
/**
* Daily stats as shown on the about page
*
* @param Domain $o
* @return Collection
*/
public function api_daily_stats(Request $request): Collection
{
$o = Domain::where('name',$request->name)->firstOrFail();
return $o->echoarea_total_daily()
->sortBy('date')
->groupBy('date')
->transform(function($item,$key) { return [
'x'=>\Carbon\Carbon::createFromFormat('Y-m-d',$key)->timestamp,
'y'=>$item->sum('count')]; } )
->values();
}
/**
* Add or edit a domain
@ -19,12 +36,12 @@ class DomainController extends Controller
public function add_edit(DomainRequest $request,Domain $o)
{
if ($request->post()) {
foreach (['name','dnsdomain','active','public','homepage','notes','flatten'] as $key)
foreach (['name','dnsdomain','active','public','homepage','notes','flatten','accept_app','nodestatus_id'] as $key)
$o->{$key} = $request->post($key);
$o->save();
return redirect()->action([self::class,'home']);
return redirect()->to('domain');
}
return view('domain.addedit')
@ -40,18 +57,16 @@ class DomainController extends Controller
*/
public function api_hosts(Zone $o,int $region): Collection
{
$oo = Address::where('role',Address::NODE_NC)
->where('zone_id',$o->id)
->when($region,function($query,$region) { return $query->where('region_id',$region); })
->when((! $region),function($query) use ($region) { return $query->where('region_id',0); })
->where('point_id',0)
->FTNorder()
->with(['system'])
->get();
return $oo->map(function($item) {
return ['id'=>$item->host_id,'value'=>sprintf('%s %s',$item->ftn_3d,$item->system->name)];
});
return $o->hosts
->filter(fn($item)=>$item->role_id === Address::NODE_NC)
->filter(fn($item)=>$region ? ($item->region_id === $region) : TRUE)
->map(function($item) {
return [
'id'=>$item->host_id,
'value'=>sprintf('%s %s',$item->ftn_3d,$item->system->name)
];
})
->values();
}
/**
@ -63,15 +78,16 @@ class DomainController extends Controller
*/
public function api_hubs(Zone $o,int $host): Collection
{
$oo = Address::where('role',Address::NODE_HC)
->where('zone_id',$o->id)
->when($host,function($query,$host) { return $query->where('host_id',$host)->where('node_id','<>',0); })
->with(['system'])
->get();
return $oo->map(function($item) {
return ['id'=>$item->id,'value'=>sprintf('%s %s',$item->ftn_3d,$item->system->name)];
});
return $o->hubs
->filter(fn($item)=>$item->role_id === Address::NODE_HC)
->filter(fn($item)=>$host ? ($item->host_id === $host) : TRUE)
->map(function($item) {
return [
'id'=>$item->id,
'value'=>sprintf('%s %s',$item->ftn_3d,$item->system->name)
];
})
->values();
}
/**
@ -82,22 +98,25 @@ class DomainController extends Controller
*/
public function api_regions(Zone $o): Collection
{
$oo = Address::where('role',Address::NODE_RC)
->where('zone_id',$o->id)
->where('node_id',0)
->where('point_id',0)
->orderBy('region_id')
->active()
->with(['system'])
->get();
return $oo->map(function($item) {
return ['id'=>$item->region_id,'value'=>sprintf('%s %s',$item->ftn_3d,$item->system->location)];
});
return $o->regions
->filter(fn($item)=>$item->role_id === Address::NODE_RC)
->map(function($item) {
return [
'id'=>$item->region_id,
'value'=>sprintf('%s %s',$item->ftn_3d,$item->system->location)
];
})
->values();
}
public function home()
public function view(Domain $o)
{
return view('domain.home');
if (! $o->public && ! Gate::check('admin',$o))
abort(404);
$o->load(['zones.system','zones.domain','zones.addresses.nodes_hub','zones.addresses.echomail_from']);
return view('domain.view')
->with('o',$o);
}
}

View File

@ -35,15 +35,10 @@ class EchoareaController extends Controller
$o->save();
return redirect()->action([self::class,'home']);
return redirect()->to('echoarea');
}
return view('echoarea.addedit')
->with('o',$o);
}
public function home()
{
return view('echoarea.home');
}
}

View File

@ -40,15 +40,10 @@ class FileareaController extends Controller
$o->domain->save();
}
return redirect()->action([self::class,'home']);
return redirect()->to('filearea');
}
return view('filearea.addedit')
->with('o',$o);
}
public function home()
{
return view('filearea.home');
}
}

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\Auth;
@ -12,7 +13,7 @@ use App\Classes\File;
use App\Classes\FTN\Packet;
use App\Http\Requests\SetupRequest;
use App\Models\File as FileModel;
use App\Models\{Address,Domain,Echomail,Netmail,Setup,System};
use App\Models\{Address,Echomail,Netmail,Setup,System};
class HomeController extends Controller
{
@ -38,25 +39,13 @@ class HomeController extends Controller
})
->get();
return view('file')
return view('widgets.file')
->with('f',$f);
}
public function network(Domain $o)
{
if (! $o->public && ! Gate::check('admin',$o))
abort(404);
$o->load(['zones.system','zones.domain']);
return view('domain.view')
->with('o',$o);
}
public function packet_contents(System $o,string $packet)
{
$nm = Netmail::select('netmails.*')
->distinct()
$nm = Netmail::select(['netmails.id','fftn_id','tftn_id','netmails.datetime'])
->leftJoin('netmail_path',['netmail_path.netmail_id'=>'netmails.id'])
->where(function($query) use ($o,$packet) {
return $query
@ -65,37 +54,30 @@ class HomeController extends Controller
})
->get();
$em = Echomail::select('echomails.*')
->distinct()
$em = Echomail::select(['echomails.id','fftn_id','echoarea_id','msgid','datetime'])
->leftJoin('echomail_seenby',['echomail_seenby.echomail_id'=>'echomails.id'])
->leftJoin('echomail_path',['echomail_path.echomail_id'=>'echomails.id'])
->where(function($query) use ($o,$packet) {
return $query
->where('sent_pkt',$packet)
->whereIn('echomail_seenby.address_id',$o->addresses->pluck('id'));
})
->orWhere(function($query) use ($o,$packet) {
->union(
Echomail::select(['echomails.id','fftn_id','echoarea_id','msgid','datetime'])
->leftJoin('echomail_path',['echomail_path.echomail_id'=>'echomails.id'])
->where(function($query) use ($o,$packet) {
return $query
->where('recv_pkt',$packet)
->whereIn('echomail_path.address_id',$o->addresses->pluck('id'));
})
->with('echoarea')
)
->with(['echoarea'])
->get();
return view('packet')
return view('widgets.packet')
->with('nm',$nm)
->with('em',$em);
}
/**
* Render a view that summarises the users permissions
*/
public function permissions()
{
return view('auth.permissions')
->with('user',Auth::user());
}
/**
* Show a packet dump
*
@ -252,4 +234,60 @@ class HomeController extends Controller
return view('setup')
->with('o',$o);
}
/**
* Return a list of unsent items
*
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|\Illuminate\Foundation\Application
* @see \App\Jobs\AddressIdle:class
*/
public function status()
{
$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();
}
}

File diff suppressed because it is too large Load Diff

View File

@ -5,6 +5,7 @@ namespace App\Http\Controllers;
use Illuminate\Auth\Events\Registered;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use App\Http\Requests\UserRequest;
@ -23,33 +24,23 @@ class UserController extends Controller
if (! $o->exists)
$o->password = base64_encode(random_bytes(20));
elseif ($request->password)
$o->password = Hash::make($request->password);
$o->save();
if ($o->wasRecentlyCreated)
event(new Registered($o));
return redirect()->action([self::class,'home']);
return redirect()
->to('user/addedit/'.$o->id)
->with('success','User Updated');
}
return view('user.addedit')
->with('o',$o);
}
public function dashboard()
{
$user = Auth::user();
$user->load('systems.addresses.zone.domain.echoareas');
return view('dashboard')
->with('user',$user);
}
public function home()
{
return view('user.home');
}
public function link(Request $request)
{
if ($request->post()) {
@ -59,7 +50,7 @@ class UserController extends Controller
]);
$ao = Address::findOrFail($request->address_id);
if ($ao->check_activation(Auth::user(),$request->code)) {
if ($ao->activation_check(Auth::user(),$request->code)) {
$ao->validated = TRUE;
$ao->save();
@ -79,9 +70,4 @@ class UserController extends Controller
return view('user.link');
}
public function register()
{
return view('user/system/register');
}
}

View File

@ -96,7 +96,7 @@ class ZoneController extends Controller
$ao->active = TRUE;
$o->addresses()->save($ao);
return redirect()->action([self::class,'home']);
return redirect()->to('zone');
}
return view('zone.addedit')
@ -126,9 +126,4 @@ class ZoneController extends Controller
'default' => (bool)$request->set,
]);
}
public function home()
{
return view('zone.home');
}
}

View File

@ -0,0 +1,201 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;
use App\Models\Address;
use App\Rules\{FidoInteger,TwoByteInteger};
/**
* Validation to add an address to a system
* o = System::class
*
* @note This validation only expects to be used with POST
* Variables:
* + "action" => "node" // type of address being created
* + "zone_id" => "2"
* + "region_id" => "21"
* + "host_id" => "3"
* + "hub_id" => null
* + "node_id" => "9999"
* + "point_id" => "0"
* + "region_id_new" => null // creating a new region id
* + "host_id_new" => null // creating a new host id
* + "node_id_new" => null // creating a new node id
* + "hub" => "0"
* + "security" => "9"
*
* Rules:
* - ZC is z:0/0.0 - region,node,point must be zero
* ZC is identified when region_id,host_id,node_id and point_id === 0
* - RC is z:r/0.0 - region is non-zero, host_id = region_id, node,point must be zero.
* RC is identified when region_id === host_id and, node_id and point_id === 0
* - NC is z:h/0.0 (r=r, r!=h, h!=z) [parent where z:r/0 and h=r, n=0]
* NC is identified when region_id !== host_id and, node_id and point_id === 0,
* - HC is z:h/n.0 (r=r) [parent pointed by hub_id AND validate by z:h/0 is the hub_id]
* HC is a normal node with, but has children pointing to it with hub_id
* - NN is z:h/n.0 when point_id === 0
* A normal node where node_id !== 0, it may or may not have a hub_id
* - PT is z:h/n.p where point_id !== 0
* PT is identified when point_id !== 0;
*/
class AddressAdd extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
session()->flash('accordion','address');
return request()->isMethod('post')
&& Gate::allows('admin',$this->route('o'));
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(Request $request): array
{
$rules = [];
switch ($request->action) {
case 'region':
$rules = [
'region_id_new' => [
'required',
new TwoByteInteger,
// Check that the region doesnt already exist
function ($attribute,$value,$fail) use ($request) {
$o = Address::where(fn($query)=>
$query
->where('region_id',$value)
// Check that a host doesnt already exist
->orWhere('host_id',$value)
)
->where('zone_id',$request->zone_id)
->where('node_id',0)
->where('point_id',0)
->first();
if ($o && $o->count())
$fail(sprintf('%s already exists [<a href="%s">here</a>]',$o->ftn,url('system/addedit',$o->system_id)));
},
],
];
break;
case 'host':
$rules = [
'region_id' => [
'required',
new FidoInteger, // @todo the RC should exist, ie: z:r/0.0 (h=0)
],
'host_id_new' => [
'required',
new TwoByteInteger,
// Make sure that the host isnt already defined
function ($attribute,$value,$fail) use ($request) {
// Check that the region doesnt already exist
$o = Address::where(fn($query)=>
$query
->where('region_id',$value)
// Check that a host doesnt already exist
->orWhere('host_id',$value)
)
->where('zone_id',$request->zone_id)
->where('node_id',0)
->where('point_id',0)
->first();
if ($o && $o->count())
$fail(sprintf('%s already exists [<a href="%s">here</a>]',$o->ftn,url('system/addedit',$o->system_id)));
},
],
];
break;
case 'update':
case 'node':
$rules = [
'region_id' => [
'required',
new FidoInteger // @todo the RC should exist, ie: z:r/0.0 (h=0)
],
'host_id' => [
'required',new FidoInteger // @todo the NC should exist, ie: z:r/0.0 (h=0)
],
'node_id' => [
'required',
new TwoByteInteger,
function ($attribute,$value,$fail) use ($request) {
if ($request->point_id === 0) {
// Check that the host doesnt already exist
$o = Address::where(function($query) use ($request,$value) {
return $query
->where('zone_id',$request->zone_id)
->where('host_id',$request->host_id)
->where('node_id',$value)
->where('point_id',0)
->where('id','<>',$request->submit);
})
->get();
if ($o && $o->count())
$fail(sprintf('%s already exists [<a href="%s">here</a>]',$o->ftn,url('system/addedit',$o->system_id)));
}
},
],
'point_id' => [
'required',
new FidoInteger,
function ($attribute,$value,$fail) use ($request) {
// Check that the host doesnt already exist
$o = Address::where(function($query) use ($request,$value) {
return $query
->where('zone_id',$request->zone_id)
->where('host_id',$request->host_id)
->where('node_id',$request->node_id)
->where('point_id',$value)
->where('id','<>',$request->submit);
})
->first();
if ($o && $o->count())
$fail(sprintf('%s already exists [<a href="%s">here</a>]',$o->ftn,url('system/addedit',$o->system_id)));
}
],
//'hub' => 'required|boolean',
'hub_id' => 'nullable|exists:addresses,id',
'submit' => 'nullable|exists:addresses,id',
];
break;
}
return array_merge($rules,
[
'action' => [
'required',
'in:region,host,node,update',
],
'zone_id' => [
'required',
'exists:zones,id',
],
'security' => [
'nullable',
'numeric',
'min:0',
'max:7',
]
]);
}
}

View File

@ -6,7 +6,7 @@ use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;
use App\Models\Address;
use App\Models\{Address,System};
class AddressMerge extends FormRequest
{
@ -35,7 +35,7 @@ class AddressMerge extends FormRequest
$dst = Address::withTrashed()->findOrFail($value);
$src = Address::withTrashed()->findOrFail($request->src);
if ((! $dst->active) && ($dst->system_id !== $src->system_id) && ($src->system->name !== 'Discovered System'))
if ((! $dst->active) && ($dst->system_id !== $src->system_id) && ($src->system->name !== System::default))
$fail('Destination must be active, or be from the system system');
},
function ($attribute,$value,$fail) use ($request) {

View File

@ -23,11 +23,11 @@ class AreafixRequest extends FormRequest
return [
'to' => [
'required',
Rule::in(config('app.areafilefix')),
Rule::in(config('fido.areafilefix')),
],
'fftn_id' => [
'required',
Rule::in(Setup::findOrFail(config('app.id'))->system->akas->pluck('id')),
Rule::in(our_address()->pluck('id')),
],
'tftn_id' => [
'required',

View File

@ -5,8 +5,9 @@ namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;
use Illuminate\Validation\Rule;
use App\Models\Domain;
use App\Models\{Domain,Echoarea};
class DomainRequest extends FormRequest
{
@ -27,7 +28,13 @@ class DomainRequest extends FormRequest
'dnsdomain' => 'nullable|regex:/^(?!:\/\/)(?=.{1,255}$)((.{1,63}\.){1,127}(?![0-9]*$)[a-z0-9-]+\.?)$/i|unique:domains,dnsdomain,'.($o->exists ? $o->id : NULL),
'active' => 'required|boolean',
'public' => 'required|boolean',
'accept_app' => 'required|boolean',
'flatten' => 'nullable|boolean',
'nodestatus_id' => [
'nullable',
Rule::exists(Echoarea::class,'id'),
Rule::in($o->echoareas->pluck('id')),
]
];
}
}

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