Il matrimonio di Google con KeyHole, la nota azienda che già tempo fa aveva proposto un software per la navigazione di mappe satellitari, ha finalmente generato prole. E' nato GoogleEarth, il nuovo software gratuito che consente di vedere con un dettaglio eccellente tutto il mondo. Le mappe di GoogleEarth per inciso sono le medesime di GoogleMaps, ma l'interfaccia è più accattivante ed usabile e soprattutto non è basata su un browser con tutti i vantaggi che questo può portare. Nel software è possibile oltre che navigare il mondo intero anche reperire informazioni riguardo i luoghi che si stanno visitando. Davvero eccezionale.

Link: http://earth.google.com/

 

powered by IMHO 1.2

tags: - categories: News

Anche oggi mi è arrivata una sequenza di keyword interessante per un articoletto nella categoria Ask Mr. Key. L'articolo parla di come creare una login in un sito ASP.NET seguendo le più comuni norme di sicurezza che si possono adottare per proteggere l'applicazione. Quanto ho scritto in realtà riguarda per minima parte ASP.NET e la FormsAuthentication, ma illustra soprattutto problematiche quali la difesa da SqLInjection, e l'hashing delle password che sono certamente di più ampio respiro.

Naturalmente i commenti all'articolo sono a vostra disposizione per segnalarmi ulteriori problematiche.

Articolo: http://www.boschin.it/blogs/radicalmente/articles/aspnet_login.aspx

keyword: login asp.net esempio

powered by IMHO 1.2

tags: - categories:

La richiesta di quest'oggi a Mr. Key riguarda un argomento che di certo i più smaliziati ormai conosceranno a menadito, ma che con altrettanta certezza si pone come un delicato problema di fronte a chi lo affronta per le prime volte. Creare una form di login in realtà prende in causa un serie di aspetti che riguardano la sicurezza di un sito web privato che non si limitano alla semplice creazione di una paginetta contenente due caselle di testo ed un pulsante, ma anche tutta una serie di misure che impediscano ad un utente malintenzionato di violare la sicurezza di cui necessitiamo. I problemi che dovremo affrontare rientrano nelle seguenti categorie:
  1. Protezione di una zona privata del sito web
  2. Protezione dei dati contenuti nel database in cui le credenziali sono memorizzate
  3. Protezione delle credenziali nel supporto di persistenza
  4. Protezione delle credenziali durante il loro invio

ASP.NET mette a disposizione tre sistemi per rendere privata una particolare area del nostro sito web. Essi vanno sotto il nome di Windows, Passport e Forms Authentication. La prima basa il suo funzionamento sulla sicurezza fornita dai sistemi operativi derivati da NT. Sostanzialmente questo comporta che l'accesso all'area protetta sia garantito solo a degli utenti che dispongono di un account sul sistema operativo, e quindi richiede che si sia in grado di creare questi account e di fornire ad essi i necessari privilegi sulle cartelle da proteggere. Questo rende inutilizzabile la Windows Authentication ad esempio alla maggior parte di coloro i quali non dispongano di un web server proprio ma utilizzino dei servizi di hosting. Inoltre va detto che fornire le credenziali di un utente del sistema operativo espone il sistema stesso ad attacchi se tale account non è ben configurato. Ritengo che per la maggior parte dei casi questo tipo di autenticazione sia da evitare.

Il secondo metodo, dei tre che ho anticipato, si basa sull'utilizzo di Passport, un servizio di single-sign-on messo a disposizione da Microsoft ma che ultimamente è stato abbandonato pertanto non ritengo utile spendere altre parole su di esso. Molto più interessante invece è l'approfondimento della Forms Authentication che ci consente di implementare un nostro sistema di autenticazione privato. Questo sistema, di gran lunga il più flessibile dei tre è però anche quello che più facilmente esporrà la nostra area protetta all'intrusione di malintenzionati se non useremo le dovute cautele. Occorre innanziutto accertarsi che al framework .NET sia applicata l'ultima service pack (la SP1) per evitare i problemi dovuti al bug della canonicalization. A questo punto si può iniziare a proteggere l'area semplicemente impostando la Forms Authentication nel web.config come segue:


   
        mode="Forms">
                            
name=".ASPXAUTH"
                
loginUrl="/login.aspx" 
                
protection="All" 
                
timeout="30" />
        <
/authentication>        
        
            users="?"/>
        <
/authorization>
   <
/system.web>
<
/configuration>

Questa configurazione si occupa di impostare la autenticazione di tipo Forms, e di definirne i dettagli quali il nome del cookie che che verrà usato per mantenere le informazioni sull'accesso effettuato, il tipo di protezione di questo cookie e il suo timeout. Inoltre nello stesso elemento forms si definisce anche qual'è la pagina nella quale si dovrà effettuare la login. Questo comporta che un tentativo di accesso ad una pagina protetta da un browser che non disponga del cookie suddetto verrà automaticamente rediretto sulla pagina login.aspx.  Infine nella sezione authorization si definisce che gli utenti sconosciuti (questo significa il "?"), non verranno fatti accedere. Per ulteriori chiarimenti su questi parametri e su altri esposti da questa sezione consiglio la lettura della documentazione dell'SDK che è sufficientemente completa. Tuttavia mi corre l'obbligo di segnalare che è sconsigliabile l'uso della sezione forms->credentials che consentirebbe di mettere le credenziali nel web.config. Mi sembra abbastanza ovvio che questo sia un repository di scarsa sicurezza. Così, il punto uno della scaletta che avevo proposto all'inizio ha ottenuto soddisfazione.

Dopo aver composto la webform in login.aspx viene il momento di affrontare l'autenticazione. Posto che questa webform contenga due TextBox, txtUserName e txtPassword quest'ultima impostata con TextMode="Password" per asteriscare l'input, e un Button denominato bEnter dovremmo gestire la verifica delle credenziali su un nostro database. per comodità faremo riferimento ad un database SQL Server o MSDE, ma quando dirò può essere esteso a qualunque altro RDBMS supportato da ADO.NET. E' evidente che per effettuare il controllo dovremmo eseguire una query simile alla seguente:

SELECT COUNT(*) 
   FROM Users 
   
WHERE UserName @userName AND 
         
UserPassword @password;

E' di vitale importanza l'uso dei parameters in questo caso, per sostituire a runtime le variabili @userName e @password perchè una sostituzione di tipo per così dire "casalingo" mediante concatenazione potrebbe portare alla compromissione di tutta la base dati e non solo dell'area protetta. Questa problematica va sotto il nome di SqlInjection e la sua trattazione esula dallo scopo di queste righe, ma basti ricordare che con questo termine si definiscono quegli attacchi che sfruttano l'immissioni di porzioni si codice SQL in alcune form per ottenere accesso a dati che invece non sarebbero consentiti. Un buon articolo (in inglese) sull'argomento lo si può trovare qui. L'uso dei parameters di ADO.NET consente così di soddisfare anche il punto due della scaletta perchè essi si adoperano ad apportare il necessario escape alle stringhe immesse in base al tipo dei parametri. Ecco di seguito il codice dell'evento click del bottone di login:

public void bEnter_Click( object sender, EventArgs e )
{
    
// stringa concatenata per esigenze di spazio
    
string sql = "select count(*) from Users" +
                 " where UserName = @userName and" + 
                 " UserPassword = @password";

    
// creo una connessione
    
using( SqlConnection conn = new SqlConnection( "" ) )
    {
        
// mi connetto al database
        
conn.Open();

        
// creo un comando
        
using( SqlCommand cmd = new SqlCommand( sql, conn ) )
        {
            
// aggiungo un parametro per lo username
            
SqlParameter param1 = cmd.CreateParameter();
            param1.SqlDbType = SqlDbType.VarChar;
            param1.ParameterName = "@userName";
            param1.Value = txtUserName.Text;
            cmd.Parameters.Add( param1 );

            
// aggiungo un parametro per la password
            
SqlParameter param2 = cmd.CreateParameter();
            param2.SqlDbType = SqlDbType.VarBinary;
            param2.ParameterName = "@password";
            param2.Value = CreateHash( txtPassword.Text,txtUserName.Text );
            cmd.Parameters.Add( param2 );

            
// eseguo la query e restituisco come boolean
            
bool result = Convert.ToBoolean( cmd.ExecuteScalar() );

            
// se la login ha buon fine entro nell'applicazione
            
if ( result )
                FormsAuthentication.RedirectFromLoginPage( txtUserName.Text, 
false );                        
        }
    }
}

Innanzitutto bisogna precisare che la concatenazione dell'istruzione SQL è dovuta a problemi di spazio. Comunque nell'esempio si vede come opportunamente per eseguire la query si siano creati i due parametri che poi sono stati aggiunti al comando stesso. I più attenti si saranno sicuramente accorti della presenza del metodo CreateHash() e del fatto che il SqlDbType del parametro della password è un varbinary. Questo serve per soddisfare anche il punto tre, ovvero la protezione delle credenziali. Tale contromisura si rende necessaria per evitare che un utente che riesca ad accedere al database (ad esempio anche un amministratore di sistema), sia in grado di leggere le password nella tabella Users. Perciò si genererà uno hash a partire dalla password e si memorizzerà quest'ultimo. Per chi non lo ha mai sentito nominare, un algoritmo di hashing è in grado di generare una sequenza di caratteri a partire da un'altra (in questo caso la password originale) che varia molto al variare anche minimo della stringa di partenza. Questa nuova stringa rappresenta in modo univoco la password ma non è reversibile ovvero è impossibile risalire alla stringa originaria. Perciò se memorizziamo nel database l'hash della password invece che la password vera e propria potremmo applicare il medesimo algoritmo alla password immessa dall'utente e poi verificare la correttezza comparando i due hash invece che le due password. Questo meccanismo soffre comunque di una lieve insicurezza dovuta al fatto che disponendo di un gran numero di hash generate sarà possibile individuare delle ripetizioni che con poco sforzo potrebbero portare ad individuare la password originale per tentativi. Per ovviare a questo inconveniente è consigliabile applicare un "salt", un granellino di sale. Questo si può ottenere ad esempio prependendo lo username alla password prima di generare l'hash. In questo modo due utenti che abbiano la medesima password avranno comunque un hash ben diverso e perciò sarà più difficile individuare le pericolose ripetizioni. Ecco il codice del metodo CreateHash().

public byte [] CreateHash( string password, string userName )
{
    MD5CryptoServiceProvider provider = 
        
new 
MD5CryptoServiceProvider();

    
return 
        
provider.ComputeHash( Encoding.UTF8.GetBytes( userName + password ) );
}

Con questo ci siamo messi al sicuro anche dal terzo punto. In merito al quarto ed ultimo punto occorre tener presente che il transito delle credenziali sulla rete, durante la post di una form avviene sempre in chiaro. Per questo motivo, ad esempio se navighiamo dietro ad un proxy server, o comunque se qualcuno decide di intercettare il nostro traffico di rete, sarà sempre possibile che lo username e la password vengano scoperte. L'unica protezione per questo tipo di problema è l'adozione di un certificato digitale e del protocollo SSL almeno per la fase di login al sistema. Sotto SSL i dati che vanno dal client al server e viceversa vengono crittati in modo molto sicuro perciò saremmo in grado di dare la massima sicurezza a chi usa la nostra applicazione.

In questo lungo articolo ho tentato di spiegare con semplicità come arrivare a proteggere con cognizione di causa la propria applicazione. E' evidente che quanto detto potrebbe essere soggetto ad ulteriori integrazioni, ma ritengo che per una applicazione di medie esigenze questa soluzione sia più che buona perchè consente di evitare i più comuni errori che tipicamente affliggono buona parte dei sistemi web odierni.

keyword: login asp.net esempio 

powered by IMHO 1.2

tags: - categories:

Mentre archiviavo le foto della cena stasera, mi sono reso conto che sono sfuggite all'upload nella gallery una decina di scatti. Chi volesse invidiarci un'altro pochetto può rimirarli ora nella gallery a partire dal numero 1955 alla 1971.

Mi corre l'obbligo di informarvi che nonostante la faccia di Alejandro possa far intendere diversamente, non eravamo in possesso di stupefacenti durante la serata.

powered by IMHO 1.2

tags: - categories: News

Il primo articolo della categoria riguarda un argomento davvero interessante, cui probabilmente molti sapranno dare una risposta, ma che ritengo molto utile per chi si avvicina al framework .NET da un punto di vista produttivo. Nell'articolo che ho aggiunto alla categoria viene illustrato come creare un file ZIP in C# per mezzo della conosciuta SharpZipLib ed in seguito decomprimerlo.

Articolo: http://www.boschin.it/blogs/radicalmente/articles/sharpziplib.aspx

Keywords: .net c# creare uno zip

powered by IMHO 1.2

tags: - categories:

Con questo post apro una nuova categoria di post che da corpo ad una idea avevo solo preannunciato qualche tempo addietro. La categoria nasce da una semplice osservazione delle statistiche del mio weblog. Spesso rilevo che arrivano alle mie pagine persone che sono alla ricerca di informazioni relative a .NET e che per trovarle si affidano a delle ricerche nei motori più comuni. Non sempre però queste ricerche trovano la meritata risposta perchè l'interpretazione delle keyword da parte dei motori lascia molto margine all'errore. Ho pensato così di tentare di rispondere alle combinazioni più interessanti, non per aiutare chi ha già effettuato questa ricerca, che probabilmente avrà già trovato quello che cercava, ma soprattutto chi si troverà ad aver bisogno della medesima informazione in futuro. Ecco le semplici regole che mi imporrò di seguire:

  1. Nel titolo del post inserirò tutte le keyword specificate nel motore di ricerca anche se mi concedo la "licenza poetica" di mescolarle per ottenere una frase compiuta
  2. Risponderò nel modo più completo possibile con un articolo che linkerò in home page del weblog. A questo scopo creerò un tool che farà ciclare in home i links
  3. Tutte le risposte saranno nella categoria "Ask Mr. Keyword" così da essere più facilmente individuabili.

Naturalmente nessuno si deve aspettare che gli argomenti trattati siano sempre affascinanti e di elevatissimo taglio, anzi, dall'osservazione che ho dedicato in questo ultimo periodo alle keyword talvolta ci saranno argomenti molto basilari, ma saranno affiancati anche da altri che richiederanno un ulteriore approfondimento da parte mia, e spero anche da parte dei miei lettori cui lascerò ampio spazio nei commenti per proporre ulteriori soluzioni.

Buona lettura a voi... e buona fortuna a me.

powered by IMHO 1.2


Chiunque sviluppi software per professione si sarà almeno una volta trovato di fronte alla necessità di creare o viceversa di scompattare un file zip. Spesso la trasmissione di dati sulla rete richiede che si comprimano i file originali in un unico archivio sia per esigenze di spazio sia di semplice unitarietà come fa ad esempio SCORM, un formato di packaging per l'e-learning. Infatti zip è in grado oltre che di ridurre le dimensioni fisiche di un singolo file adottando un algoritmo di compressione, anche di memorizzare la struttura di directory che originariamente conteneva una serie di file. L'algoritmo ZIP, utilizzato per operare la compressione risale ormai al lontano 1989 ed è nato in seguito ad una disputa legale tra Phil Katz e la SEA sulla proprietà di un precedente e meno efficiente algoritmo. In seguito alla perdita della causa, Katz decise di realizzare un nuovo algoritmo più efficace. Fu così che vide la luce ZIP, adottato per la prima volta dall'ormai famoso PKZIP dell'azienda PKWARE di proprietà dello stesso Katz. Fu solo in seguito, durante gli anni '90 che venne creata l'interfaccia grafica cui tutti siamo ormai abituati e che va sotto il nome di Winzip.

Accantonando il fascino della storia di questo algoritmo cerchiamo di capire in che modo si possa dotare una nostra applicazione .NET di una compressione/decompressione di file che usi ZIP. Pensare di scrivere del codice che ricalchi questo algoritmo, per se fattibile è davvero complesso e rischia di risolversi in uno spreco di tempo eccessivo. Per fortuna esistono in rete alcune librerie, create e soprattutto testate da appassionati che permettono di raggiungere lo scopo con semplicità, includendole nei nostri progetti. La libreria che personalmente ritengo più efficace va sotto il nome di SharpZipLib ed è liberamente scaricabile presso il sito di IcSharpCode, il gruppo che ha realizzato anche una apprezzata IDE per .NET.

Una volta scaricato il file della distribuzione binaria dal sito, occorre innanzitutto scompattarlo in una directory per poter usufruire del contenuto. Nella cartella si troveranno un buon numero di directory contenenti tutto il codice sorgente della libreria. Inoltre, nella cartella principale si trovano anche dei file batch, che consentono di installare l'unico assembly nella GAC del proprio PC. Questa è un'operazione molto importante ma non indispensabile. Inserire nella GAC l'assembly consentirà ai vostri programmi di accedere ad esso più rapidamente perchè le operazioni di ricerca e validazione compiute da fusion, il componente di .NET che si occupa del caricamento degli assembly verranno notevolmente semplificate. Tuttavia esso è in grado di reperirlo e caricarlo correttamente anche se tale assembly è presente nella medesima cartella cui fa capo il vostro programma. Qualora desideriate installare la SharpZipLib nella GAC potrete farlo comodamente lanciando il batch InstallGac.bat. Viceversa sarà possibile disinstallarlo con l'altro file UninstallGac.bat. L'assembly ICSharpCode.SharpZipLib.dll, presente nel medesimo file è l'assembly che dovremmo utilizzare nelle nostre applicazioni qualora non desideriamo porlo nella GAC. Tale assembly inoltre dovrà essere referenziato da VisualStudio .NET se si usa tale IDE per sviluppare.

Una volta referenziato l'assembly, arriva il momento di creare il codice necessario. Per questo articolo mi riferirò al codice di esempio che si può trovare nella distribuzione dei sorgenti della libreria. Fulcro principale della libreria sono gli Stream di input e output che dovranno essere usati per comprimere e decomprimere il flusso dei dati. Le classi corrispondenti sono la ZipOutputStream e ZipInputStream che chiaramente si occuperanno rispettivamente di comprimere e decomprimere il flusso. Il meccanismo di compressione prevede innanzitutto che si apra lo stream di output verso il file destinazione e lo stream di input dal file di origine. Quest'ultimo sarà un normale stream non compresso. Naturalmente la logica di funzionamento degli Stream di .NET consentirà non solo di aprire un file fisico, ma anche una connessione di rete, un'area di memoria, etc... Aperto lo stream di output quindi si scandiranno uno ad uno tutti i file da comprimere aprendo uno stream di input da ognuno, e creando così una ZipEntry che consentirà di specificare le informazioni del file in questione nell'indice dello zip. La ZipEntry in particolare raccoglierà il nome del file, la dimensioen ed il CRC. Il CRC, per esteso Cyclic Redundancy Code è una sorta di firma elettronica del file che lo rappresenta in modo quasi del tutto univoco. Il CRC verrà utilizzato in fase di decompressione per verificare che il file estratto sia uguale all'originale e non abbia subito corruzione dall'operazione di compressione/decompressione. Per il calcolo del CRC la libreria mette a disposizione una classe omonima che si incarica di tutti gli oneri.

Inserita la entry nel nuovo Zip con il metodo PutNextEntry() si provvederà a scriverne il contenuto nello stream per mezzo di una semplice Write(). Infine i metodi Finish() e Close() al termine porranno fine all'operazione di compressione. Ecco un breve esempio di quanto finora spiegato:

public static void Main(string[] args)
{
    
string[] filenames = Directory.GetFiles(args[0]);
    
    Crc32 crc = 
new Crc32();
    ZipOutputStream s = 
new ZipOutputStream(File.Create(args[1]));
    
    s.SetLevel(6); 

    
foreach (string file in filenames)
    {
        FileStream fs = File.OpenRead(file);
        
        
byte[] buffer = new byte[fs.Length];
        fs.Read(buffer, 0, buffer.Length);
        ZipEntry entry = 
new ZipEntry(file);            
        entry.DateTime = DateTime.Now;
        entry.Size = fs.Length;
        fs.Close();
        
        crc.Reset();
        crc.Update(buffer);
        entry.Crc  = crc.Value;
        
        s.PutNextEntry(entry);            
        s.Write(buffer, 0, buffer.Length);
    }
    
    s.Finish();
    s.Close();
}

Esempio 1 - Creare il file ZIP
 
 
Utile segnalare che per mezzo del metodo SetLevel() si può specificare con un numero da 0 a 9 il livello di compressione dove 0 disabilita del tutto la compressione ma si limita a mantenere la struttura delle directory.
 
L'operazione inversa non è più complessa di quella appena illustrata. Richiede semplicemente che si tenga conto della struttura presente nel file zip e che sulla base di essa si ricreino le directory e i file di destinazione. Gli stream di input e output saranno questa volta invertiti e sarà quello di output ad essere ripetutamente aperto e chiuso per scrivere il contenuto decompresso. Ecco di seguito il codice relativo:
 
 
public static void Main(string[] args)
{
    ZipInputStream s = 
new ZipInputStream(File.OpenRead(args[0]));
    ZipEntry theEntry;

    
while ((theEntry = s.GetNextEntry()) != null)
    {
        
        Console.WriteLine(theEntry.Name);
        
        
string directoryName = Path.GetDirectoryName(theEntry.Name);
        
string fileName      = Path.GetFileName(theEntry.Name);
        
        Directory.CreateDirectory(directoryName);
        
        
if (fileName != String.Empty)
        {
            FileStream streamWriter = File.Create(theEntry.Name);
            
int size = 2048;
            
byte[] data = new byte[2048];

            
while (true
            {
                size = s.Read(data, 0, data.Length);

                
if (size > 0)
                    streamWriter.Write(data, 0, size);
                
else 
                    break
;
            }
            
            streamWriter.Close();
        }
    }
    s.Close();
}
 
Esempio 2 - Decomprimere uno zip
 
L'unica reale differenza la si può riscontrare nella necessità di verificare la presenza di directory nel file e di ricrearle nella directory di destinazione per mantenere la struttura oiginale. Chiudo questo lungo articolo suggerendo lo studio del progetto HtpCompressionModule presente tra i sorgenti. Esso implementa un modulo per ASP.NET che consente la compressione/decompressione zip dello stream di output/input da e verso il browser che la supporti. Utile potrà essere anche la lettura del mio articolo, scritto per UgiDotNet, "Inside HttpHandlers" che spiega come realizzare un handler che mostri la struttura web di un file zip come se si trattasse di unsa normale virtual directory.
 
tags: - categories:

Mi è appena giunto un messaggio da Wind che mi informa che il costo mensile del piano NOI2 passa da 2€ a 3€. NOI2, per chi non lo sapesse, è quel piano che consente con un esborso mensile di poter effettuare fino a 500 minuti gratuiti di telefonate verso un altro numero Wind (nel mio caso quello di mia moglie).

Ora, due sono le cose: o questi signori considerano un contratto stipulato all'apertura del servizio alla stregua di carta igienica, oppure se come penso contrattualmente Wind si riserva di adeguare il prezzo a suo insindacabile giudizio, quella che deve essere messa sotto accusa è la pubblicità ingannevole che a suo tempo recitava "gratis... per sempre".

Non mancherò di buttare un po' del mio tempo a spulciare il contratto, per scovare la spiegazione di questo inghippo, ma resta comunque l'amarezza di sapere che se queste cose sono possibili tutto dipende dai nostri governanti e magistrati che di certo si guardano bene dal disturbare un così importante gruppo di potere.

powered by IMHO 1.2


Stamane, al ritorno dalla Java Conference un collega ci ha spiegato in poche parole che il futuro di Java nel mondo web si chiama JSF. Avevo già sentito parlare di questo nuovo acronimo, ma ho voluto lo stesso andare a curiosare di cosa si trattasse. Ecco una faq dal sito http://www.jsfcentral.com

JavaServer Faces (JSF) is a framework for building web-based user interfaces in Java. Like Swing, it provides a set of standard widgets (buttons, hyperlinks, checkboxes, and so on), a model for creating custom widgets, a way to process client-generated events (like changing the value of a text box or clicking on a button), and excellent tool support. Since web-based applications, unlike their Swing cousins, must often appease multiple clients (desktop browsers, phones, PDAs, and so on), JSF has a powerful architecture for displaying components in different ways. It also has extensible facilities for validating input (like the length of a field) and converting objects to and from strings for display. Faces can also automatically keep your user interface components in sync with your business model objects.

Non so a voi, ma a me ricorda qualcosa.

powered by IMHO 1.2

tags: - categories:

della mia vita e di altre amenità

Commenti (4) -

# | Riccardo Golia | 15.01.2005 - 12.09

Ma qua il venerdì sera non dorme proprio nessuno!!! Smile

Ciao, Ricky.

# | Riccardo Golia | 15.01.2005 - 12.27

A proposito, ti è arrivata la mia mail?

Ciao, Ricky.

# | Luca Minudel | 16.01.2005 - 00.06

E io che volevo dare la notizia in anteprima... ma ovviamente non potevo sperare di essere + veloce dei diretti interessati!!!



Cia Andrea, ciao Ricky

# | Riccardo Golia | 17.01.2005 - 06.16

Abbiamo attivato il servizio Dottext. Anche tutti i blogger di ASPITALIA possono da ora usare IMHO. Ci tenevo a fartelo sapere.

Ciao, Ricky.

Aggiungi Commento