MokaByte 80 - Dicembre 2003 
JNetSend
Utilizzare il servizio messenger di Windows in Java
di
Paolo Mascia
Uno dei comandi di rete maggiormente utilizzati dagli utenti dei sistemi operativi Windows NT/2000 è il net send. Questo comando consente di inviare un breve messaggio di testo ad una qualsiasi macchina connessa in rete. In questo articolo vedremo come implementare il protocollo di comunicazione utilizzato dal servizio messenger e simulare in Java il comando net send

Introduzione
Il comando net send consente di inviare un breve messaggio di testo ad una qualsiasi macchina connessa in rete e su cui gira un sistema operativo Windows NT/2000 o XP. Il messaggio ricevuto, se il servizio messenger della macchina di destinazione è avviato, viene visualizzato istantaneamente mediante una finestra di dialogo.
Proprio l'immediatezza nella visualizzazione dei messaggi fa di net send uno strumento indispensabile per l'amministrazione di sistema (avvisi di shutdown, failure, ecc.) nonchè uno strumento utilissimo tutte le volte che si ha la necessità di comunicare in tempo reale una informazione.

Per comprendere come sviluppare una applicazione Java in grado di inviare un messaggio verso il servizio messenger di Windows occorre, in primo luogo, analizzare l'architettura delle applicazioni Microsoft di rete.

Il protocollo NetBios
Le applicazioni Microsoft basate sulle reti Tcp/Ip possono essere distinte sostanzialmente in due categorie: le Windows Socket Application e le NetBios Application.

La prima categoria di applicazioni si basa sulle Windows Socket API, queste ultime forniscono una serie di funzionalità per la comunicazione attraverso i protocolli TCP e UDP, analoghe a quelle fornite dalle API Java del package java.net.
La seconda categoria di applicazioni utilizza le API NetBios per gestire la comunicazione di rete. NetBios definisce, una interfaccia software e una convenzione per i nomi, creando uno strato aggiuntivo tra le applicazioni e la rete e garantendo alle stesse di girare indipendentemente dal protocollo sottostante.
E' a carico delle singole implementazioni dell'interfaccia NetBios la gestione della comunicazione attraverso la rete. In particolare NetBios over Tcp/Ip (NetBT) fornisce l'interfaccia di programmazione che consente la comunicazione tra le applicazioni NetBios attraverso la rete Tcp/ip. Le specifiche del protocollo NetBios sono definite nelle RFCs 1001 e 1002.
In figura 1 viene schematizzata l'interfaccia Microsoft applicativa verso il protocollo TCP/IP.


Figura 1: Interfaccia Microsoft applicativa
verso il protocollo TCP/IP


Le singole risorse messe a disposizione da un server sono identificate dai nomi NetBios, registrati dinamicamente quando la macchina viene avviata, quando partono i servizi, o l'utente effettua il login. I nomi NetBios devono essere unici all'interno del namespace ed avere una lunghezza fissa di 16 caratteri.
I servizi di rete della Microsoft consentono all'utente o all'amministratore di specificare solo i primi 15 caratteri, riservando il sedicesimo carattere per identificare il tipo di risorsa. La figura seguente mostra la struttura di un nome NetBios


Figura 2
: Schema di un nome NetBios

La tabella seguente elenca un esempio di nomi NetBios utilizzati dai servizi Microsoft. L'elenco dei nomi NetBios registrati da una macchina può essere visualizzato lanciando il comando 'nbtstat -n' da un prompt dei comandi MsDos.


Tabella 1: Elenco dei nomi NetBios dei servizi Microsoft


Il servizio messenger, così come il Workstation service, il Server service, e il NetLogon sono tutti servizi NetBios.
Il primo passo per la realizzazione della nostra applicazione è quindi l'implementazione base di questo protocollo.

 

Implementazione del protocollo
Una connessione NetBios over Tcp/Ip viene stabilita attraverso i seguenti passaggi:

- risoluzione del nome NetBios in un indirizzo IP
- connessione TCP fra workstation e server, utilizzando la porta 139
- invio di una richiesta di sessione NetBios

Una volta che la sessione NetBios è stata stabilita il client e il server iniziano la negoziazione attraverso il protocollo SMB. Considerando che l'indirizzo IP della macchina destinazione del nostro messaggio è già noto possiamo passare direttamente all'invio di una richiesta di sessione. Il procedimento è alquanto elementare. Infatti, una volta aperta una connessione Tcp/Ip è sufficiente scrivere nello stream di output del socket il Session Request Packet rappresentato in figura 3.


Figura3
: Formato del Session Request Racket

Il primo byte definisce il tipo di messaggio e nel caso di richiesta di sessione vale 0x81 come si evince dalla tabella seguente:



Tabella 2
: Significato del byte TYPE

Il secondo byte imposta una serie di parametri relativi al messaggio, i cui dettagli sono descritti nelle RFC1002 [2].
I due byte successivi indicano la lunghezza del pacchetto in formato big endian.

Segue la codifica del nome del chiamante e del destinatario. Senza addentrarci nei dettagli del protocollo possiamo semplicemente analizzare l'algoritmo di codifica della classe NetBiosName riportata di seguito.
Il metodo encode si occupa di codificare un nome NetBios nel formato previsto e di valorizzare i byte del buffer di scrittura.

import java.io.UnsupportedEncodingException;

class NetBiosName {
static final String ENCODING = "ISO8859_1";
static final int MESSENGER_SERVICE = 0x03;

String name;
int hexCode;

NetBiosName(String name) {
this(name, MESSENGER_SERVICE);
}

NetBiosName(String name, int hexCode) {
if (name.length() > 15) {
name = name.substring(0, 15);
}
this.name = name.toUpperCase();
this.hexCode = hexCode;
}

int encode(byte[] dst, int dstIndex) throws UnsupportedEncodingException {
dst[dstIndex] = 0x20;


byte tmp[] = name.getBytes(ENCODING);
int i;
for (i = 0; i < tmp.length; i++) {
dst[dstIndex + (2 * i + 1)] = (byte) (((tmp[i] & 0xF0) >> 4) + 0x41);
dst[dstIndex + (2 * i + 2)] = (byte) ((tmp[i] & 0x0F) + 0x41);
}
for (; i < 15; i++) {
dst[dstIndex + (2 * i + 1)] = (byte) 0x43;
dst[dstIndex + (2 * i + 2)] = (byte) 0x41;
}
dst[dstIndex + 31] = (byte) (((hexCode & 0xF0) >> 4) + 0x41);
dst[dstIndex + 32] = (byte) ((hexCode & 0x0F) + 0x41);
dst[dstIndex + 33] = (byte) 0x00;

return 34;
}

}

Una volta inviata una richiesta di connessione occorre verificare che quest'ultima sia andata a buon fine, leggendo il pacchetto di risposta che può avere uno dei due formati di figura 4.



Figura 4 : Formato del Session Response Racket

Se la connessione non va a buon fine, il codice di errore del Negative Response Packet assume il seguente significato.

Una volta verificato che il pacchetto in risposta, letto dallo stream di input del socket aperto verso la porta 139, è un Positive Session Response Racket, la connessione è stabilita.
Il file NetBiosSocket.java mostra il codice necessario per stabilire una connessione NetBios .

import java.net.Socket;
import java.net.InetAddress;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;


public class NetBiosSocket extends Socket {
private static final int SSN_SRVC_PORT = 139;
private static final int POSITIVE_SESSION_RESPONSE = 0x82;
private static final int BUFFER_SIZE = 512;

NetBiosName toName;
NetBiosName fromName;

public NetBiosSocket(NetBiosName fromName, NetBiosName toName) throws Exception {
super(InetAddress.getByName(toName.name), SSN_SRVC_PORT);

this.toName = toName;
this.fromName = fromName;

connect();
}

public OutputStream getOutputStream() throws IOException {
return new NetBiosSocketOutputStream(super.getOutputStream());
}

private void connect() throws Exception {
byte[] buffer = new byte[BUFFER_SIZE];

InputStream in = super.getInputStream();
OutputStream out = super.getOutputStream();

NetBiosSessionRequestPacket ssp = new NetBiosSessionRequestPacket(toName, fromName);
out.write(buffer, 0, ssp.writePacket(buffer, 0));

int packetType = ssp.readPacketType(in, buffer, 0);
if (packetType != POSITIVE_SESSION_RESPONSE) {
throw new IOException("NetBios Connection error.");
}
}
}

Stabilita la connessione, possiamo inviare il nostro messaggio nel formato SMB attraverso un NetBios Session Service . Il metodo write dello stream di output ottenuto con il metodo getOutputStream della classe NetBiosSocket si occupa di scrivere i byte del pacchetto.
La struttura del pacchetto SMB si può dedurre facilmente dal codice della classe SmbPacket.


import java.io.OutputStream;
import java.io.IOException;


public class SmbPacket {
private static final byte ASCII_FORMAT = 0x04;
private static final byte DATA_BLOCK = 0x01;

private byte[] header = {(byte) 0xff, 0x53, 0x4d, 0x42, // Server Component
(byte) 0xd0, // SMB Command
0x00, // Error Class
0x00, // Reserved
0x00, 0x00, // Error Code
0x00, 0x00, 0x00, // Flags
0x00, 0x00, // Process ID
0x00, 0x00, 0x00, 0x00, // Signature ...
0x00, 0x00, 0x00, 0x00, // ...
0x00, 0x00, // Reserved
0x00, 0x00, // Tree ID
0x00, 0x00, // Process ID
0x00, 0x00, // User ID
0x00, 0x00 // Multiplex ID
};

private String senderName;
private String destinationName;
private String message;

public SmbPacket(String senderName, String destinationName, String message) {
this.senderName = senderName;
this.destinationName = destinationName;
this.message = message;
}

public void send(OutputStream out) throws IOException {
out.write(header);
out.write(0x00);
out.write(getBytes(senderName.length() + destinationName.length() + message.length() + 7));
out.write(ASCII_FORMAT);
out.write(senderName.getBytes());
out.write(0x00);
out.write(ASCII_FORMAT);
out.write(destinationName.getBytes());
out.write(0x00);
out.write(DATA_BLOCK);
out.write(getBytes(message.length()));
out.write(message.getBytes());
//
}

private byte[] getBytes(int number) {
byte[] bytes = new byte[2];
bytes[0] = (byte) (number & 0x00FF);
bytes[1] = (byte) ((number & 0xFF00) >> 16);
return bytes;
}
}

Invocando il metodo send di questa classe il messaggio è finalmente inviato.


Conclusioni
In allegato sono disponibili i sorgenti completi dell'applicazione JnetSend con la quale è possibile inviare un messaggio mediante il comando JnetSend <from> <to> <message>.

Un ultima nota. Il programma descritto presuppone che, trovandoci in una rete Tcp/Ip, sia già noto l'indirizzo IP della macchina di destinazione, ma probabilmente non è noto il suo nome NetBios.
Mentre il nome del chiamante non è significativo, affinché il servizio messenger riceva il messaggio, è necessario che il nome del chiamato coincida con il nome con cui la macchina ha registrato il servizio.
Come è possibile vedere dalla tabella 1 possiamo provare sia utilizzando il nome della macchina sia il nome dell'utente.
Se ciò non dovesse bastare è possibile creare ed inviare una richiesta NetBios Name Service verso la macchina di destinazione che ci risponderà con un elenco dei nomi NetBios registrati.
Analizzando la risposta è possibile determinare, oltre alle altre informazioni, anche il nome del servizio messenger.

 

Webliografia
[1] RFC 1001 - Protocol Standard for a NetBIOS Service on a TCP/UDP Transport: Concepts and Methods
[2] RFC 1002 - Protocol Standard for a NetBIOS Service on a TCP/UDP Transport: Detailed Specifications
[3] Dave MacDonald and Warren Barkley - Microsoft Windows 2000 TCP/IP Implementation Details

 

Risorse
Scarica i sorgenti allegati all'articolo

 
MokaByte® è un marchio registrato da MokaByte s.r.l. 
Java®, Jini® e tutti i nomi derivati sono marchi registrati da Sun Microsystems.
Tutti i diritti riservati. E' vietata la riproduzione anche parziale.
Per comunicazioni inviare una mail a info@mokabyte.it