@serve.zone/dcrouter 11.0.44 → 11.0.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist_serve/bundle.js +1 -1
- package/dist_ts/cache/classes.cache.cleaner.d.ts +47 -0
- package/dist_ts/cache/classes.cache.cleaner.js +130 -0
- package/dist_ts/cache/classes.cached.document.d.ts +76 -0
- package/dist_ts/cache/classes.cached.document.js +100 -0
- package/dist_ts/cache/classes.cachedb.d.ts +60 -0
- package/dist_ts/cache/classes.cachedb.js +126 -0
- package/dist_ts/cache/documents/classes.cached.email.d.ts +125 -0
- package/dist_ts/cache/documents/classes.cached.email.js +337 -0
- package/dist_ts/cache/documents/classes.cached.ip.reputation.d.ts +119 -0
- package/dist_ts/cache/documents/classes.cached.ip.reputation.js +323 -0
- package/dist_ts/cache/documents/index.d.ts +2 -0
- package/dist_ts/cache/documents/index.js +3 -0
- package/dist_ts/cache/index.d.ts +4 -0
- package/dist_ts/cache/index.js +7 -0
- package/dist_ts/classes.cert-provision-scheduler.d.ts +53 -0
- package/dist_ts/classes.cert-provision-scheduler.js +110 -0
- package/dist_ts/classes.storage-cert-manager.d.ts +18 -0
- package/dist_ts/classes.storage-cert-manager.js +43 -0
- package/dist_ts/config/classes.api-token-manager.d.ts +46 -0
- package/dist_ts/config/classes.api-token-manager.js +150 -0
- package/dist_ts/config/classes.route-config-manager.d.ts +35 -0
- package/dist_ts/config/classes.route-config-manager.js +231 -0
- package/dist_ts/config/index.d.ts +3 -0
- package/dist_ts/config/index.js +5 -0
- package/dist_ts/config/validator.d.ts +104 -0
- package/dist_ts/config/validator.js +152 -0
- package/dist_ts/errors/base.errors.d.ts +224 -0
- package/dist_ts/errors/base.errors.js +320 -0
- package/dist_ts/errors/error.codes.d.ts +115 -0
- package/dist_ts/errors/error.codes.js +136 -0
- package/dist_ts/logger.d.ts +21 -0
- package/dist_ts/logger.js +81 -0
- package/dist_ts/monitoring/classes.metricscache.d.ts +32 -0
- package/dist_ts/monitoring/classes.metricscache.js +63 -0
- package/dist_ts/monitoring/classes.metricsmanager.d.ts +178 -0
- package/dist_ts/monitoring/classes.metricsmanager.js +642 -0
- package/dist_ts/monitoring/index.d.ts +1 -0
- package/dist_ts/monitoring/index.js +2 -0
- package/dist_ts/opsserver/classes.opsserver.d.ts +37 -0
- package/dist_ts/opsserver/classes.opsserver.js +85 -0
- package/dist_ts/opsserver/handlers/admin.handler.d.ts +31 -0
- package/dist_ts/opsserver/handlers/admin.handler.js +180 -0
- package/dist_ts/opsserver/handlers/api-token.handler.d.ts +6 -0
- package/dist_ts/opsserver/handlers/api-token.handler.js +62 -0
- package/dist_ts/opsserver/handlers/certificate.handler.d.ts +32 -0
- package/dist_ts/opsserver/handlers/certificate.handler.js +421 -0
- package/dist_ts/opsserver/handlers/config.handler.d.ts +7 -0
- package/dist_ts/opsserver/handlers/config.handler.js +192 -0
- package/dist_ts/opsserver/handlers/email-ops.handler.d.ts +30 -0
- package/dist_ts/opsserver/handlers/email-ops.handler.js +227 -0
- package/dist_ts/opsserver/handlers/index.d.ts +11 -0
- package/dist_ts/opsserver/handlers/index.js +12 -0
- package/dist_ts/opsserver/handlers/logs.handler.d.ts +25 -0
- package/dist_ts/opsserver/handlers/logs.handler.js +256 -0
- package/dist_ts/opsserver/handlers/radius.handler.d.ts +6 -0
- package/dist_ts/opsserver/handlers/radius.handler.js +295 -0
- package/dist_ts/opsserver/handlers/remoteingress.handler.d.ts +6 -0
- package/dist_ts/opsserver/handlers/remoteingress.handler.js +156 -0
- package/dist_ts/opsserver/handlers/route-management.handler.d.ts +14 -0
- package/dist_ts/opsserver/handlers/route-management.handler.js +117 -0
- package/dist_ts/opsserver/handlers/security.handler.d.ts +9 -0
- package/dist_ts/opsserver/handlers/security.handler.js +231 -0
- package/dist_ts/opsserver/handlers/stats.handler.d.ts +11 -0
- package/dist_ts/opsserver/handlers/stats.handler.js +399 -0
- package/dist_ts/opsserver/helpers/guards.d.ts +27 -0
- package/dist_ts/opsserver/helpers/guards.js +43 -0
- package/dist_ts/opsserver/index.d.ts +1 -0
- package/dist_ts/opsserver/index.js +2 -0
- package/dist_ts/paths.d.ts +26 -0
- package/dist_ts/paths.js +45 -0
- package/dist_ts/plugins.d.ts +79 -0
- package/dist_ts/radius/classes.accounting.manager.d.ts +218 -0
- package/dist_ts/radius/classes.accounting.manager.js +417 -0
- package/dist_ts/radius/classes.radius.server.d.ts +171 -0
- package/dist_ts/radius/classes.radius.server.js +385 -0
- package/dist_ts/radius/classes.vlan.manager.d.ts +128 -0
- package/dist_ts/radius/classes.vlan.manager.js +279 -0
- package/dist_ts/radius/index.d.ts +13 -0
- package/dist_ts/radius/index.js +14 -0
- package/dist_ts/remoteingress/classes.remoteingress-manager.d.ts +82 -0
- package/dist_ts/remoteingress/classes.remoteingress-manager.js +227 -0
- package/dist_ts/remoteingress/classes.tunnel-manager.d.ts +59 -0
- package/dist_ts/remoteingress/classes.tunnel-manager.js +165 -0
- package/dist_ts/remoteingress/index.d.ts +2 -0
- package/dist_ts/remoteingress/index.js +3 -0
- package/dist_ts/security/classes.securitylogger.d.ts +144 -0
- package/dist_ts/security/classes.securitylogger.js +233 -0
- package/dist_ts/storage/classes.storagemanager.d.ts +83 -0
- package/dist_ts/storage/classes.storagemanager.js +350 -0
- package/dist_ts/storage/index.d.ts +1 -0
- package/dist_ts/storage/index.js +3 -0
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/package.json +2 -2
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts_web/00_commitinfo_data.ts +1 -1
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import type { StorageManager } from '../storage/index.js';
|
|
2
|
+
import { VlanManager, type IMacVlanMapping, type IVlanManagerConfig } from './classes.vlan.manager.js';
|
|
3
|
+
import { AccountingManager, type IAccountingManagerConfig } from './classes.accounting.manager.js';
|
|
4
|
+
/**
|
|
5
|
+
* RADIUS client (NAS) configuration
|
|
6
|
+
*/
|
|
7
|
+
export interface IRadiusClient {
|
|
8
|
+
/** Client name for identification */
|
|
9
|
+
name: string;
|
|
10
|
+
/** IP address or CIDR range */
|
|
11
|
+
ipRange: string;
|
|
12
|
+
/** Shared secret for this client */
|
|
13
|
+
secret: string;
|
|
14
|
+
/** Optional description */
|
|
15
|
+
description?: string;
|
|
16
|
+
/** Whether this client is enabled */
|
|
17
|
+
enabled: boolean;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* RADIUS server configuration
|
|
21
|
+
*/
|
|
22
|
+
export interface IRadiusServerConfig {
|
|
23
|
+
/** Authentication port (default: 1812) */
|
|
24
|
+
authPort?: number;
|
|
25
|
+
/** Accounting port (default: 1813) */
|
|
26
|
+
acctPort?: number;
|
|
27
|
+
/** Bind address (default: 0.0.0.0) */
|
|
28
|
+
bindAddress?: string;
|
|
29
|
+
/** NAS clients configuration */
|
|
30
|
+
clients: IRadiusClient[];
|
|
31
|
+
/** VLAN assignment configuration */
|
|
32
|
+
vlanAssignment?: IVlanManagerConfig & {
|
|
33
|
+
/** Static MAC to VLAN mappings */
|
|
34
|
+
mappings?: Array<Omit<IMacVlanMapping, 'createdAt' | 'updatedAt'>>;
|
|
35
|
+
};
|
|
36
|
+
/** Accounting configuration */
|
|
37
|
+
accounting?: IAccountingManagerConfig & {
|
|
38
|
+
/** Whether accounting is enabled */
|
|
39
|
+
enabled: boolean;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* RADIUS authentication result
|
|
44
|
+
*/
|
|
45
|
+
export interface IRadiusAuthResult {
|
|
46
|
+
/** Whether authentication was successful */
|
|
47
|
+
success: boolean;
|
|
48
|
+
/** Reject reason (if not successful) */
|
|
49
|
+
rejectReason?: string;
|
|
50
|
+
/** Reply message to send to client */
|
|
51
|
+
replyMessage?: string;
|
|
52
|
+
/** Session timeout in seconds */
|
|
53
|
+
sessionTimeout?: number;
|
|
54
|
+
/** Idle timeout in seconds */
|
|
55
|
+
idleTimeout?: number;
|
|
56
|
+
/** VLAN to assign */
|
|
57
|
+
vlanId?: number;
|
|
58
|
+
/** Framed IP address to assign */
|
|
59
|
+
framedIpAddress?: string;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Authentication request data from RADIUS
|
|
63
|
+
*/
|
|
64
|
+
export interface IAuthRequestData {
|
|
65
|
+
username: string;
|
|
66
|
+
password?: string;
|
|
67
|
+
nasIpAddress: string;
|
|
68
|
+
nasPort?: number;
|
|
69
|
+
nasPortType?: string;
|
|
70
|
+
nasIdentifier?: string;
|
|
71
|
+
calledStationId?: string;
|
|
72
|
+
callingStationId?: string;
|
|
73
|
+
serviceType?: string;
|
|
74
|
+
framedMtu?: number;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* RADIUS Server wrapper that provides:
|
|
78
|
+
* - MAC Authentication Bypass (MAB) for network devices
|
|
79
|
+
* - VLAN assignment based on MAC address
|
|
80
|
+
* - Accounting for session tracking and billing
|
|
81
|
+
* - Integration with SmartProxy routing
|
|
82
|
+
*/
|
|
83
|
+
export declare class RadiusServer {
|
|
84
|
+
private radiusServer?;
|
|
85
|
+
private vlanManager;
|
|
86
|
+
private accountingManager;
|
|
87
|
+
private config;
|
|
88
|
+
private storageManager?;
|
|
89
|
+
private clientSecrets;
|
|
90
|
+
private running;
|
|
91
|
+
private stats;
|
|
92
|
+
constructor(config: IRadiusServerConfig, storageManager?: StorageManager);
|
|
93
|
+
/**
|
|
94
|
+
* Start the RADIUS server
|
|
95
|
+
*/
|
|
96
|
+
start(): Promise<void>;
|
|
97
|
+
/**
|
|
98
|
+
* Stop the RADIUS server
|
|
99
|
+
*/
|
|
100
|
+
stop(): Promise<void>;
|
|
101
|
+
/**
|
|
102
|
+
* Handle authentication request
|
|
103
|
+
*/
|
|
104
|
+
private handleAuthentication;
|
|
105
|
+
/**
|
|
106
|
+
* Handle accounting request
|
|
107
|
+
*/
|
|
108
|
+
private handleAccounting;
|
|
109
|
+
/**
|
|
110
|
+
* Perform MAC Authentication Bypass
|
|
111
|
+
*/
|
|
112
|
+
private performMabAuthentication;
|
|
113
|
+
/**
|
|
114
|
+
* Extract MAC address from authentication data
|
|
115
|
+
*/
|
|
116
|
+
private extractMacAddress;
|
|
117
|
+
/**
|
|
118
|
+
* Check if a string looks like a MAC address
|
|
119
|
+
*/
|
|
120
|
+
private looksLikeMac;
|
|
121
|
+
/**
|
|
122
|
+
* Normalize MAC address format
|
|
123
|
+
*/
|
|
124
|
+
private normalizeMac;
|
|
125
|
+
/**
|
|
126
|
+
* Build client secrets map from configuration
|
|
127
|
+
*/
|
|
128
|
+
private buildClientSecretsMap;
|
|
129
|
+
/**
|
|
130
|
+
* Get default secret for unknown clients
|
|
131
|
+
*/
|
|
132
|
+
private getDefaultSecret;
|
|
133
|
+
/**
|
|
134
|
+
* Add a RADIUS client
|
|
135
|
+
*/
|
|
136
|
+
addClient(client: IRadiusClient): Promise<void>;
|
|
137
|
+
/**
|
|
138
|
+
* Remove a RADIUS client
|
|
139
|
+
*/
|
|
140
|
+
removeClient(name: string): boolean;
|
|
141
|
+
/**
|
|
142
|
+
* Get configured clients
|
|
143
|
+
*/
|
|
144
|
+
getClients(): IRadiusClient[];
|
|
145
|
+
/**
|
|
146
|
+
* Get VLAN manager for direct access to VLAN operations
|
|
147
|
+
*/
|
|
148
|
+
getVlanManager(): VlanManager;
|
|
149
|
+
/**
|
|
150
|
+
* Get accounting manager for direct access to accounting operations
|
|
151
|
+
*/
|
|
152
|
+
getAccountingManager(): AccountingManager;
|
|
153
|
+
/**
|
|
154
|
+
* Get server statistics
|
|
155
|
+
*/
|
|
156
|
+
getStats(): {
|
|
157
|
+
running: boolean;
|
|
158
|
+
uptime: number;
|
|
159
|
+
authRequests: number;
|
|
160
|
+
authAccepts: number;
|
|
161
|
+
authRejects: number;
|
|
162
|
+
accountingRequests: number;
|
|
163
|
+
activeSessions: number;
|
|
164
|
+
vlanMappings: number;
|
|
165
|
+
clients: number;
|
|
166
|
+
};
|
|
167
|
+
/**
|
|
168
|
+
* Check if server is running
|
|
169
|
+
*/
|
|
170
|
+
isRunning(): boolean;
|
|
171
|
+
}
|
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
import * as plugins from '../plugins.js';
|
|
2
|
+
import { logger } from '../logger.js';
|
|
3
|
+
import { VlanManager } from './classes.vlan.manager.js';
|
|
4
|
+
import { AccountingManager } from './classes.accounting.manager.js';
|
|
5
|
+
/**
|
|
6
|
+
* RADIUS Server wrapper that provides:
|
|
7
|
+
* - MAC Authentication Bypass (MAB) for network devices
|
|
8
|
+
* - VLAN assignment based on MAC address
|
|
9
|
+
* - Accounting for session tracking and billing
|
|
10
|
+
* - Integration with SmartProxy routing
|
|
11
|
+
*/
|
|
12
|
+
export class RadiusServer {
|
|
13
|
+
radiusServer;
|
|
14
|
+
vlanManager;
|
|
15
|
+
accountingManager;
|
|
16
|
+
config;
|
|
17
|
+
storageManager;
|
|
18
|
+
clientSecrets = new Map();
|
|
19
|
+
running = false;
|
|
20
|
+
// Statistics
|
|
21
|
+
stats = {
|
|
22
|
+
authRequests: 0,
|
|
23
|
+
authAccepts: 0,
|
|
24
|
+
authRejects: 0,
|
|
25
|
+
accountingRequests: 0,
|
|
26
|
+
startTime: 0,
|
|
27
|
+
};
|
|
28
|
+
constructor(config, storageManager) {
|
|
29
|
+
this.config = {
|
|
30
|
+
authPort: config.authPort ?? 1812,
|
|
31
|
+
acctPort: config.acctPort ?? 1813,
|
|
32
|
+
bindAddress: config.bindAddress ?? '0.0.0.0',
|
|
33
|
+
...config,
|
|
34
|
+
};
|
|
35
|
+
this.storageManager = storageManager;
|
|
36
|
+
// Initialize VLAN manager
|
|
37
|
+
this.vlanManager = new VlanManager(config.vlanAssignment, storageManager);
|
|
38
|
+
// Initialize accounting manager
|
|
39
|
+
this.accountingManager = new AccountingManager(config.accounting, storageManager);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Start the RADIUS server
|
|
43
|
+
*/
|
|
44
|
+
async start() {
|
|
45
|
+
if (this.running) {
|
|
46
|
+
logger.log('warn', 'RADIUS server is already running');
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
logger.log('info', `Starting RADIUS server on ${this.config.bindAddress}:${this.config.authPort} (auth) and ${this.config.acctPort} (acct)`);
|
|
50
|
+
// Initialize managers
|
|
51
|
+
await this.vlanManager.initialize();
|
|
52
|
+
await this.accountingManager.initialize();
|
|
53
|
+
// Import static VLAN mappings if provided
|
|
54
|
+
if (this.config.vlanAssignment?.mappings) {
|
|
55
|
+
await this.vlanManager.importMappings(this.config.vlanAssignment.mappings);
|
|
56
|
+
}
|
|
57
|
+
// Build client secrets map
|
|
58
|
+
this.buildClientSecretsMap();
|
|
59
|
+
// Create the RADIUS server
|
|
60
|
+
this.radiusServer = new plugins.smartradius.RadiusServer({
|
|
61
|
+
authPort: this.config.authPort,
|
|
62
|
+
acctPort: this.config.acctPort,
|
|
63
|
+
bindAddress: this.config.bindAddress,
|
|
64
|
+
defaultSecret: this.getDefaultSecret(),
|
|
65
|
+
authenticationHandler: this.handleAuthentication.bind(this),
|
|
66
|
+
accountingHandler: this.handleAccounting.bind(this),
|
|
67
|
+
});
|
|
68
|
+
// Configure per-client secrets
|
|
69
|
+
for (const [ip, secret] of this.clientSecrets) {
|
|
70
|
+
this.radiusServer.setClientSecret(ip, secret);
|
|
71
|
+
}
|
|
72
|
+
// Start the server
|
|
73
|
+
await this.radiusServer.start();
|
|
74
|
+
this.running = true;
|
|
75
|
+
this.stats.startTime = Date.now();
|
|
76
|
+
logger.log('info', `RADIUS server started with ${this.config.clients.length} configured clients`);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Stop the RADIUS server
|
|
80
|
+
*/
|
|
81
|
+
async stop() {
|
|
82
|
+
if (!this.running) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
logger.log('info', 'Stopping RADIUS server...');
|
|
86
|
+
if (this.radiusServer) {
|
|
87
|
+
await this.radiusServer.stop();
|
|
88
|
+
this.radiusServer = undefined;
|
|
89
|
+
}
|
|
90
|
+
this.running = false;
|
|
91
|
+
logger.log('info', 'RADIUS server stopped');
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Handle authentication request
|
|
95
|
+
*/
|
|
96
|
+
async handleAuthentication(request) {
|
|
97
|
+
this.stats.authRequests++;
|
|
98
|
+
const authData = {
|
|
99
|
+
username: request.attributes?.UserName || '',
|
|
100
|
+
password: request.attributes?.UserPassword,
|
|
101
|
+
nasIpAddress: request.attributes?.NasIpAddress || request.source?.address || '',
|
|
102
|
+
nasPort: request.attributes?.NasPort,
|
|
103
|
+
nasPortType: request.attributes?.NasPortType,
|
|
104
|
+
nasIdentifier: request.attributes?.NasIdentifier,
|
|
105
|
+
calledStationId: request.attributes?.CalledStationId,
|
|
106
|
+
callingStationId: request.attributes?.CallingStationId,
|
|
107
|
+
serviceType: request.attributes?.ServiceType,
|
|
108
|
+
};
|
|
109
|
+
logger.log('debug', `RADIUS Auth Request: user=${authData.username}, NAS=${authData.nasIpAddress}`);
|
|
110
|
+
// Perform MAC Authentication Bypass (MAB)
|
|
111
|
+
// In MAB, the username is typically the MAC address
|
|
112
|
+
const result = await this.performMabAuthentication(authData);
|
|
113
|
+
if (result.success) {
|
|
114
|
+
this.stats.authAccepts++;
|
|
115
|
+
logger.log('info', `RADIUS Auth Accept: user=${authData.username}, VLAN=${result.vlanId}`);
|
|
116
|
+
// Build response with VLAN attributes
|
|
117
|
+
const response = {
|
|
118
|
+
code: plugins.smartradius.ERadiusCode.AccessAccept,
|
|
119
|
+
replyMessage: result.replyMessage,
|
|
120
|
+
};
|
|
121
|
+
// Add VLAN attributes if assigned
|
|
122
|
+
if (result.vlanId !== undefined) {
|
|
123
|
+
response.tunnelType = 13; // VLAN
|
|
124
|
+
response.tunnelMediumType = 6; // IEEE 802
|
|
125
|
+
response.tunnelPrivateGroupId = String(result.vlanId);
|
|
126
|
+
}
|
|
127
|
+
// Add session timeout if specified
|
|
128
|
+
if (result.sessionTimeout) {
|
|
129
|
+
response.sessionTimeout = result.sessionTimeout;
|
|
130
|
+
}
|
|
131
|
+
// Add idle timeout if specified
|
|
132
|
+
if (result.idleTimeout) {
|
|
133
|
+
response.idleTimeout = result.idleTimeout;
|
|
134
|
+
}
|
|
135
|
+
// Add framed IP if specified
|
|
136
|
+
if (result.framedIpAddress) {
|
|
137
|
+
response.framedIpAddress = result.framedIpAddress;
|
|
138
|
+
}
|
|
139
|
+
return response;
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
this.stats.authRejects++;
|
|
143
|
+
logger.log('warn', `RADIUS Auth Reject: user=${authData.username}, reason=${result.rejectReason}`);
|
|
144
|
+
return {
|
|
145
|
+
code: plugins.smartradius.ERadiusCode.AccessReject,
|
|
146
|
+
replyMessage: result.rejectReason || 'Access Denied',
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Handle accounting request
|
|
152
|
+
*/
|
|
153
|
+
async handleAccounting(request) {
|
|
154
|
+
this.stats.accountingRequests++;
|
|
155
|
+
if (!this.config.accounting?.enabled) {
|
|
156
|
+
// Still respond even if not tracking
|
|
157
|
+
return { code: plugins.smartradius.ERadiusCode.AccountingResponse };
|
|
158
|
+
}
|
|
159
|
+
const statusType = request.attributes?.AcctStatusType;
|
|
160
|
+
const sessionId = request.attributes?.AcctSessionId || '';
|
|
161
|
+
const accountingData = {
|
|
162
|
+
sessionId,
|
|
163
|
+
username: request.attributes?.UserName || '',
|
|
164
|
+
macAddress: request.attributes?.CallingStationId,
|
|
165
|
+
nasIpAddress: request.attributes?.NasIpAddress || request.source?.address || '',
|
|
166
|
+
nasPort: request.attributes?.NasPort,
|
|
167
|
+
nasPortType: request.attributes?.NasPortType,
|
|
168
|
+
nasIdentifier: request.attributes?.NasIdentifier,
|
|
169
|
+
calledStationId: request.attributes?.CalledStationId,
|
|
170
|
+
callingStationId: request.attributes?.CallingStationId,
|
|
171
|
+
inputOctets: request.attributes?.AcctInputOctets,
|
|
172
|
+
outputOctets: request.attributes?.AcctOutputOctets,
|
|
173
|
+
inputPackets: request.attributes?.AcctInputPackets,
|
|
174
|
+
outputPackets: request.attributes?.AcctOutputPackets,
|
|
175
|
+
sessionTime: request.attributes?.AcctSessionTime,
|
|
176
|
+
terminateCause: request.attributes?.AcctTerminateCause,
|
|
177
|
+
serviceType: request.attributes?.ServiceType,
|
|
178
|
+
};
|
|
179
|
+
try {
|
|
180
|
+
switch (statusType) {
|
|
181
|
+
case plugins.smartradius.EAcctStatusType.Start:
|
|
182
|
+
logger.log('debug', `RADIUS Acct Start: session=${sessionId}, user=${accountingData.username}`);
|
|
183
|
+
await this.accountingManager.handleAccountingStart(accountingData);
|
|
184
|
+
break;
|
|
185
|
+
case plugins.smartradius.EAcctStatusType.Stop:
|
|
186
|
+
logger.log('debug', `RADIUS Acct Stop: session=${sessionId}`);
|
|
187
|
+
await this.accountingManager.handleAccountingStop(accountingData);
|
|
188
|
+
break;
|
|
189
|
+
case plugins.smartradius.EAcctStatusType.InterimUpdate:
|
|
190
|
+
logger.log('debug', `RADIUS Acct Interim: session=${sessionId}`);
|
|
191
|
+
await this.accountingManager.handleAccountingUpdate(accountingData);
|
|
192
|
+
break;
|
|
193
|
+
default:
|
|
194
|
+
logger.log('debug', `RADIUS Acct Unknown status type: ${statusType}`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
logger.log('error', `RADIUS accounting error: ${error.message}`);
|
|
199
|
+
}
|
|
200
|
+
return { code: plugins.smartradius.ERadiusCode.AccountingResponse };
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Perform MAC Authentication Bypass
|
|
204
|
+
*/
|
|
205
|
+
async performMabAuthentication(data) {
|
|
206
|
+
// Extract MAC address from username or CallingStationId
|
|
207
|
+
const macAddress = this.extractMacAddress(data);
|
|
208
|
+
if (!macAddress) {
|
|
209
|
+
return {
|
|
210
|
+
success: false,
|
|
211
|
+
rejectReason: 'No MAC address found',
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
// Look up VLAN assignment
|
|
215
|
+
const vlanResult = this.vlanManager.assignVlan(macAddress);
|
|
216
|
+
if (!vlanResult.assigned) {
|
|
217
|
+
return {
|
|
218
|
+
success: false,
|
|
219
|
+
rejectReason: 'Unknown MAC address',
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
// Build successful result
|
|
223
|
+
const result = {
|
|
224
|
+
success: true,
|
|
225
|
+
vlanId: vlanResult.vlan,
|
|
226
|
+
replyMessage: vlanResult.isDefault
|
|
227
|
+
? `Assigned to default VLAN ${vlanResult.vlan}`
|
|
228
|
+
: `Assigned to VLAN ${vlanResult.vlan}`,
|
|
229
|
+
};
|
|
230
|
+
// Apply any additional settings from the matched rule
|
|
231
|
+
if (vlanResult.matchedRule) {
|
|
232
|
+
// Future: Add session timeout, idle timeout, etc. from rule
|
|
233
|
+
}
|
|
234
|
+
return result;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Extract MAC address from authentication data
|
|
238
|
+
*/
|
|
239
|
+
extractMacAddress(data) {
|
|
240
|
+
// Try CallingStationId first (most common for MAB)
|
|
241
|
+
if (data.callingStationId) {
|
|
242
|
+
return this.normalizeMac(data.callingStationId);
|
|
243
|
+
}
|
|
244
|
+
// Try username (often MAC address in MAB)
|
|
245
|
+
if (data.username && this.looksLikeMac(data.username)) {
|
|
246
|
+
return this.normalizeMac(data.username);
|
|
247
|
+
}
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Check if a string looks like a MAC address
|
|
252
|
+
*/
|
|
253
|
+
looksLikeMac(value) {
|
|
254
|
+
// Remove common separators and check length
|
|
255
|
+
const cleaned = value.replace(/[-:. ]/g, '');
|
|
256
|
+
return /^[0-9a-fA-F]{12}$/.test(cleaned);
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Normalize MAC address format
|
|
260
|
+
*/
|
|
261
|
+
normalizeMac(mac) {
|
|
262
|
+
return this.vlanManager.normalizeMac(mac);
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Build client secrets map from configuration
|
|
266
|
+
*/
|
|
267
|
+
buildClientSecretsMap() {
|
|
268
|
+
this.clientSecrets.clear();
|
|
269
|
+
for (const client of this.config.clients) {
|
|
270
|
+
if (!client.enabled) {
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
// Handle CIDR ranges
|
|
274
|
+
if (client.ipRange.includes('/')) {
|
|
275
|
+
// For CIDR ranges, we'll use the network address as key
|
|
276
|
+
// In practice, smartradius may handle this differently
|
|
277
|
+
const [network] = client.ipRange.split('/');
|
|
278
|
+
this.clientSecrets.set(network, client.secret);
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
this.clientSecrets.set(client.ipRange, client.secret);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Get default secret for unknown clients
|
|
287
|
+
*/
|
|
288
|
+
getDefaultSecret() {
|
|
289
|
+
// Use first enabled client's secret as default, or a random one
|
|
290
|
+
for (const client of this.config.clients) {
|
|
291
|
+
if (client.enabled) {
|
|
292
|
+
return client.secret;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return plugins.crypto.randomBytes(16).toString('hex');
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Add a RADIUS client
|
|
299
|
+
*/
|
|
300
|
+
async addClient(client) {
|
|
301
|
+
// Check if client already exists
|
|
302
|
+
const existingIndex = this.config.clients.findIndex(c => c.name === client.name);
|
|
303
|
+
if (existingIndex >= 0) {
|
|
304
|
+
this.config.clients[existingIndex] = client;
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
this.config.clients.push(client);
|
|
308
|
+
}
|
|
309
|
+
// Update client secrets if running
|
|
310
|
+
if (this.running && this.radiusServer && client.enabled) {
|
|
311
|
+
if (client.ipRange.includes('/')) {
|
|
312
|
+
const [network] = client.ipRange.split('/');
|
|
313
|
+
this.radiusServer.setClientSecret(network, client.secret);
|
|
314
|
+
this.clientSecrets.set(network, client.secret);
|
|
315
|
+
}
|
|
316
|
+
else {
|
|
317
|
+
this.radiusServer.setClientSecret(client.ipRange, client.secret);
|
|
318
|
+
this.clientSecrets.set(client.ipRange, client.secret);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
logger.log('info', `RADIUS client ${client.enabled ? 'added' : 'disabled'}: ${client.name} (${client.ipRange})`);
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Remove a RADIUS client
|
|
325
|
+
*/
|
|
326
|
+
removeClient(name) {
|
|
327
|
+
const index = this.config.clients.findIndex(c => c.name === name);
|
|
328
|
+
if (index >= 0) {
|
|
329
|
+
const client = this.config.clients[index];
|
|
330
|
+
this.config.clients.splice(index, 1);
|
|
331
|
+
// Remove from secrets map
|
|
332
|
+
if (client.ipRange.includes('/')) {
|
|
333
|
+
const [network] = client.ipRange.split('/');
|
|
334
|
+
this.clientSecrets.delete(network);
|
|
335
|
+
}
|
|
336
|
+
else {
|
|
337
|
+
this.clientSecrets.delete(client.ipRange);
|
|
338
|
+
}
|
|
339
|
+
logger.log('info', `RADIUS client removed: ${name}`);
|
|
340
|
+
return true;
|
|
341
|
+
}
|
|
342
|
+
return false;
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Get configured clients
|
|
346
|
+
*/
|
|
347
|
+
getClients() {
|
|
348
|
+
return [...this.config.clients];
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Get VLAN manager for direct access to VLAN operations
|
|
352
|
+
*/
|
|
353
|
+
getVlanManager() {
|
|
354
|
+
return this.vlanManager;
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Get accounting manager for direct access to accounting operations
|
|
358
|
+
*/
|
|
359
|
+
getAccountingManager() {
|
|
360
|
+
return this.accountingManager;
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Get server statistics
|
|
364
|
+
*/
|
|
365
|
+
getStats() {
|
|
366
|
+
return {
|
|
367
|
+
running: this.running,
|
|
368
|
+
uptime: this.running ? Date.now() - this.stats.startTime : 0,
|
|
369
|
+
authRequests: this.stats.authRequests,
|
|
370
|
+
authAccepts: this.stats.authAccepts,
|
|
371
|
+
authRejects: this.stats.authRejects,
|
|
372
|
+
accountingRequests: this.stats.accountingRequests,
|
|
373
|
+
activeSessions: this.accountingManager.getStats().activeSessions,
|
|
374
|
+
vlanMappings: this.vlanManager.getStats().totalMappings,
|
|
375
|
+
clients: this.config.clients.filter(c => c.enabled).length,
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Check if server is running
|
|
380
|
+
*/
|
|
381
|
+
isRunning() {
|
|
382
|
+
return this.running;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5yYWRpdXMuc2VydmVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vdHMvcmFkaXVzL2NsYXNzZXMucmFkaXVzLnNlcnZlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGVBQWUsQ0FBQztBQUN6QyxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sY0FBYyxDQUFDO0FBRXRDLE9BQU8sRUFBRSxXQUFXLEVBQWlELE1BQU0sMkJBQTJCLENBQUM7QUFDdkcsT0FBTyxFQUFFLGlCQUFpQixFQUEwRCxNQUFNLGlDQUFpQyxDQUFDO0FBOEU1SDs7Ozs7O0dBTUc7QUFDSCxNQUFNLE9BQU8sWUFBWTtJQUNmLFlBQVksQ0FBb0M7SUFDaEQsV0FBVyxDQUFjO0lBQ3pCLGlCQUFpQixDQUFvQjtJQUNyQyxNQUFNLENBQXNCO0lBQzVCLGNBQWMsQ0FBa0I7SUFDaEMsYUFBYSxHQUF3QixJQUFJLEdBQUcsRUFBRSxDQUFDO0lBQy9DLE9BQU8sR0FBWSxLQUFLLENBQUM7SUFFakMsYUFBYTtJQUNMLEtBQUssR0FBRztRQUNkLFlBQVksRUFBRSxDQUFDO1FBQ2YsV0FBVyxFQUFFLENBQUM7UUFDZCxXQUFXLEVBQUUsQ0FBQztRQUNkLGtCQUFrQixFQUFFLENBQUM7UUFDckIsU0FBUyxFQUFFLENBQUM7S0FDYixDQUFDO0lBRUYsWUFBWSxNQUEyQixFQUFFLGNBQStCO1FBQ3RFLElBQUksQ0FBQyxNQUFNLEdBQUc7WUFDWixRQUFRLEVBQUUsTUFBTSxDQUFDLFFBQVEsSUFBSSxJQUFJO1lBQ2pDLFFBQVEsRUFBRSxNQUFNLENBQUMsUUFBUSxJQUFJLElBQUk7WUFDakMsV0FBVyxFQUFFLE1BQU0sQ0FBQyxXQUFXLElBQUksU0FBUztZQUM1QyxHQUFHLE1BQU07U0FDVixDQUFDO1FBQ0YsSUFBSSxDQUFDLGNBQWMsR0FBRyxjQUFjLENBQUM7UUFFckMsMEJBQTBCO1FBQzFCLElBQUksQ0FBQyxX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
|