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
|