[Closes #55] Generate predictable MAC address (but avoid sneaky data leakage)

This commit is contained in:
Casper Warden
2022-04-19 16:12:21 +01:00
parent 2104e03b40
commit 12b11b4b52
5 changed files with 81 additions and 88 deletions

View File

@@ -1,10 +1,10 @@
import * as xmlrpc from 'xmlrpc';
import * as crypto from 'crypto';
import * as uuid from 'uuid';
import * as url from 'url';
import { LoginParameters } from './classes/LoginParameters';
import { LoginResponse } from './classes/LoginResponse';
import { ClientEvents } from './classes/ClientEvents';
import { Utils } from './classes/Utils';
import { BotOptionFlags } from './enums/BotOptionFlags';
export class LoginHandler
@@ -12,23 +12,6 @@ export class LoginHandler
private clientEvents: ClientEvents;
private options: BotOptionFlags;
static GenerateMAC(): string
{
const hexDigits = '0123456789ABCDEF';
let macAddress = '';
for (let i = 0; i < 6; i++)
{
macAddress += hexDigits.charAt(Math.round(Math.random() * 15));
macAddress += hexDigits.charAt(Math.round(Math.random() * 15));
if (i !== 5)
{
macAddress += ':';
}
}
return macAddress;
}
constructor(ce: ClientEvents, options: BotOptionFlags)
{
this.clientEvents = ce;
@@ -61,7 +44,16 @@ export class LoginHandler
rejectUnauthorized: false,
timeout: 60000
};
const viewerDigest = 'ce50e500-e6f0-15ab-4b9d-0591afb91ffe';
const client = (secure) ? xmlrpc.createSecureClient(secureClientOptions) : xmlrpc.createClient(secureClientOptions);
const nameHash = Utils.SHA1String(params.firstName + params.lastName + viewerDigest);
const macAddress: string[] = [];
for (let i = 0; i < 12; i = i + 2)
{
macAddress.push(nameHash.substr(i, 2));
}
client.methodCall('login_to_simulator',
[
{
@@ -74,8 +66,8 @@ export class LoginHandler
'patch': '1',
'build': '0',
'platform': 'win',
'mac': LoginHandler.GenerateMAC(),
'viewer_digest': uuid.v4(),
'mac': macAddress.join(':'),
'viewer_digest': viewerDigest,
'user_agent': 'node-metaverse',
'author': 'nmv@caspertech.co.uk',
'options': [

View File

@@ -359,7 +359,7 @@ export class Comms
private async groupChatExpired(groupID: UUID): Promise<void>
{
// Reconnect to group chat since it's been idle for 15 minutes
await this.agent.currentRegion.clientCommands.comms.endGroupChatSession(groupID, false);
await this.agent.currentRegion.clientCommands.comms.endGroupChatSession(groupID);
await this.agent.currentRegion.clientCommands.comms.startGroupChatSession(groupID, '');
}
}

View File

@@ -12,6 +12,7 @@ import { GlobalPosition } from './public/interfaces/GlobalPosition';
import { Quaternion } from './Quaternion';
import { Vector3 } from './Vector3';
import Timeout = NodeJS.Timeout;
import * as crypto from 'crypto';
export class Utils
{
@@ -28,6 +29,11 @@ export class Utils
return Buffer.from(str + '\0', 'utf8');
}
static SHA1String(str: string): string
{
return crypto.createHash('sha1').update(str).digest('hex');
}
static BufferToStringSimple(buf: Buffer): string
{
if (buf.length === 0)

View File

@@ -1,25 +1,24 @@
import { CommandsBase } from './CommandsBase';
import { UUID } from '../UUID';
import { Utils } from '../Utils';
import { ImprovedInstantMessageMessage } from '../messages/ImprovedInstantMessage';
import { Vector3 } from '../Vector3';
import { ChatFromViewerMessage } from '../messages/ChatFromViewer';
import * as LLSD from '@caspertech/llsd';
import { AssetType } from '../../enums/AssetType';
import { ChatType } from '../../enums/ChatType';
import { FilterResponse } from '../../enums/FilterResponse';
import { InstantMessageDialog } from '../../enums/InstantMessageDialog';
import { ScriptDialogReplyMessage } from '../messages/ScriptDialogReply';
import { InstantMessageOnline } from '../../enums/InstantMessageOnline';
import { PacketFlags } from '../../enums/PacketFlags';
import { GroupChatSessionJoinEvent } from '../../events/GroupChatSessionJoinEvent';
import { ScriptDialogEvent } from '../../events/ScriptDialogEvent';
import { StartLureMessage } from '../messages/StartLure';
import { InventoryItem } from '../InventoryItem';
import { InventoryFolder } from '../InventoryFolder';
import { InstantMessageOnline } from '../../enums/InstantMessageOnline';
import { AssetType } from '../../enums/AssetType';
import { InventoryItem } from '../InventoryItem';
import { ChatFromViewerMessage } from '../messages/ChatFromViewer';
import { ImprovedInstantMessageMessage } from '../messages/ImprovedInstantMessage';
import { ScriptDialogReplyMessage } from '../messages/ScriptDialogReply';
import { StartLureMessage } from '../messages/StartLure';
import { Utils } from '../Utils';
import { UUID } from '../UUID';
import { Vector3 } from '../Vector3';
import { CommandsBase } from './CommandsBase';
import Timer = NodeJS.Timer;
import * as LLSD from '@caspertech/llsd';
export class CommunicationsCommands extends CommandsBase
{
async giveInventory(to: UUID | string, itemOrFolder: InventoryItem | InventoryFolder): Promise<void>
@@ -389,7 +388,7 @@ export class CommunicationsCommands extends CommandsBase
});
}
public async endGroupChatSession(groupID: UUID | string, removeEntry = true): Promise<void>
public async endGroupChatSession(groupID: UUID | string): Promise<void>
{
if (typeof groupID === 'string')
{
@@ -423,63 +422,55 @@ export class CommunicationsCommands extends CommandsBase
im.EstateBlock = {
EstateID: 0
};
if (removeEntry)
{
this.agent.deleteChatSession(groupID);
}
this.agent.deleteChatSession(groupID);
const sequenceNo = this.circuit.sendMessage(im, PacketFlags.Reliable);
return this.circuit.waitForAck(sequenceNo, 10000);
}
startGroupChatSession(groupID: UUID | string, message: string): Promise<void>
public async startGroupChatSession(groupID: UUID | string, message: string): Promise<void>
{
return new Promise<void>((resolve, reject) =>
if (typeof groupID === 'string')
{
if (typeof groupID === 'string')
{
groupID = new UUID(groupID);
}
groupID = new UUID(groupID);
}
const circuit = this.circuit;
const agentName = this.agent.firstName + ' ' + this.agent.lastName;
const im: ImprovedInstantMessageMessage = new ImprovedInstantMessageMessage();
im.AgentData = {
AgentID: this.agent.agentID,
SessionID: circuit.sessionID
};
im.MessageBlock = {
FromGroup: false,
ToAgentID: groupID,
ParentEstateID: 0,
RegionID: UUID.zero(),
Position: Vector3.getZero(),
Offline: 0,
Dialog: InstantMessageDialog.SessionGroupStart,
ID: groupID,
Timestamp: Math.floor(new Date().getTime() / 1000),
FromAgentName: Utils.StringToBuffer(agentName),
Message: Utils.StringToBuffer(message),
BinaryBucket: Utils.StringToBuffer('')
};
im.EstateBlock = {
EstateID: 0
};
const waitForJoin = this.currentRegion.clientEvents.onGroupChatSessionJoin.subscribe((event: GroupChatSessionJoinEvent) =>
if (this.agent.hasChatSession(groupID))
{
return;
}
const circuit = this.circuit;
const agentName = this.agent.firstName + ' ' + this.agent.lastName;
const im: ImprovedInstantMessageMessage = new ImprovedInstantMessageMessage();
im.AgentData = {
AgentID: this.agent.agentID,
SessionID: circuit.sessionID
};
im.MessageBlock = {
FromGroup: false,
ToAgentID: groupID,
ParentEstateID: 0,
RegionID: UUID.zero(),
Position: Vector3.getZero(),
Offline: 0,
Dialog: InstantMessageDialog.SessionGroupStart,
ID: groupID,
Timestamp: Math.floor(new Date().getTime() / 1000),
FromAgentName: Utils.StringToBuffer(agentName),
Message: Utils.StringToBuffer(message),
BinaryBucket: Utils.StringToBuffer('')
};
im.EstateBlock = {
EstateID: 0
};
circuit.sendMessage(im, PacketFlags.Reliable);
await Utils.waitOrTimeOut(this.currentRegion.clientEvents.onGroupChatSessionJoin, 10000, (event: GroupChatSessionJoinEvent) =>
{
if (event.sessionID.toString() === groupID.toString())
{
if (event.sessionID.toString() === groupID.toString())
{
if (event.success)
{
waitForJoin.unsubscribe();
resolve();
}
else
{
reject();
}
}
});
circuit.sendMessage(im, PacketFlags.Reliable);
return FilterResponse.Finish;
}
return FilterResponse.NoMatch;
});
}
@@ -510,12 +501,16 @@ export class CommunicationsCommands extends CommandsBase
async sendGroupMessage(groupID: UUID | string, message: string): Promise<number>
{
await this.startGroupChatSession(groupID, message);
if (typeof groupID === 'string')
{
groupID = new UUID(groupID);
}
if (!this.agent.hasChatSession(groupID))
{
await this.startGroupChatSession(groupID, message);
}
const circuit = this.circuit;
const agentName = this.agent.firstName + ' ' + this.agent.lastName;
const im: ImprovedInstantMessageMessage = new ImprovedInstantMessageMessage();

View File

@@ -1,6 +1,6 @@
{
"name": "@caspertech/node-metaverse",
"version": "0.5.32",
"version": "0.5.33",
"description": "A node.js interface for Second Life.",
"main": "dist/lib/index.js",
"types": "dist/lib/index.d.ts",