Newsgroup: it.comp.java
Titolo: Proteggere i programmi Java

Ciao ragazzi,
qualcuno di voi ha esperienza con sistemi di protezione del software java?
Non mi riferisco ad offuscatori di codice, ma proprio a sistemi che 
impediscono l'esecuzione di copie non autorizzate del codice.
Mi va bene tutto, anche soluzioni che prevedono chiavi hardware (anche 
se ciò potrebbe comportare una perdita di portabilità).
Sarei interessato anche alla gestione di eventuali licenze temporizzate 
o cose del genere.
Grazie mille.
Delvy

>>>>> "D" == Delvy  <delvyPU...@liberoALTROPUNTO.it> writes:
D> Ciao ragazzi, qualcuno di voi ha esperienza con sistemi di
D> protezione del software java?  Non mi riferisco ad offuscatori di
D> codice, ma proprio a sistemi che impediscono l'esecuzione di copie
D> non autorizzate del codice.
Da un programma java risali al sorgente, non importa quanto offuscato.
Il sorgente lo modifichi.
-- 
 /\           ___
/___/\_|_|\_|__|___Gian Uberto Lauri_____
  //--\| | \|  |   Integralista GNUslamico
\/                 e coltivatore diretto di Software

 copie non autorizzate del codice?
Evitare di distribuire anche una sola copia del programma a chicchessia... 

 Il sorgente lo modifichi
Si questo è vero ma quante persone (clienti per la precisione)
effettivamente sanno farlo?
O meglio molti neanche sanno che si può fare.
Quindi qualche forma di protezione (anche se facilmente rimovibile)
può sempre essere utile.
Potresti fare per esempio un controllo in fase di avvio su una data se
è valida avvii il programma altrimenti no.
Magari la data la puoi mettere serializzata in un oggetto che in fase
di lettura lo deserializzi, lo leggi e così via.
Oppure protresti usare l'indirizzo MAC della scheda di rete che dato
che è univoco puoi trattarlo per tirarne fuori un codice che permette
l'avvio al programma.
Ti posto il codice per l'indirizzo MAC. (il codice non è stato scritto
da me ma trovato dopo varie ricerche su internet).
package 
lstar.shared.net
;
import java.net.*;
import java.io.*;
import java.text.*;
import java.util.*;
import java.util.regex.*;
public abstract class NetworkInfo
{
    private static final String LOCALHOST = "localhost";
    public static final String NSLOOKUP_CMD     = "nslookup";
    //modificato per ritornare un array di stringhe
    public abstract String[] parseMacAddress() throws ParseException;
    /** Not too sure of the ramifications here, but it just doesn't
seem right */
    public String parseDomain()
        throws ParseException
    {
        return parseDomain(LOCALHOST);
    }
    /** Universal entry for retrieving MAC Address */
    //modificato per ritornare un array di stringhe
    public final static String[] getMacAddress()
            throws IOException
    {
        try {
            NetworkInfo info = getNetworkInfo();
            String[] mac = info.parseMacAddress();
            return mac;
        } catch(ParseException ex) {
            ex.printStackTrace();
            throw new IOException(ex.getMessage());
        }
    }
    /** Universal entry for retrieving domain info */
    public final static String
    getNetworkDomain()
        throws IOException
    {
        try {
            NetworkInfo info = getNetworkInfo();
            String domain = info.parseDomain();
            return domain;
        } catch(ParseException ex) {
            ex.printStackTrace();
            throw new IOException(ex.getMessage());
        }
    }
    protected String
    parseDomain(String hostname)
        throws ParseException
    {
        // get the address of the host we are looking for -
verification
        java.net.InetAddress addy = null;
        try {
            addy = java.net.InetAddress.getByName(hostname);
        } catch ( UnknownHostException e ) {
            e.printStackTrace();
            throw new ParseException(e.getMessage(),0);
        }
        // back out to the hostname - just validating
        hostname = addy.getCanonicalHostName();
        String nslookupCommand = NSLOOKUP_CMD + " " + hostname;
        // run the lookup command
        String nslookupResponse = null;
        try {
            nslookupResponse = runConsoleCommand(nslookupCommand);
        } catch ( IOException e ) {
            e.printStackTrace();
            throw new ParseException(e.getMessage(), 0);
        }
        StringTokenizer tokeit = new
StringTokenizer(nslookupResponse,"\n",false);
        while( tokeit.hasMoreTokens() ) {
            String line = tokeit.nextToken();
            if( line.startsWith("Name:") ) {
                line = line.substring(line.indexOf(":") + 1);
                line = line.trim();
                if( isDomain(line, hostname) ) {
                    line = line.substring(hostname.length()+1);
                    return line;
                }
            }
        }
        return "n.a.";
    }
    private static boolean
    isDomain(String domainCandidate, String hostname)
    {
        Pattern domainPattern =
Pattern.compile("[\\w-]+\\.[\\w-]+\\.[\\w-]+\\.[\\w-]+");
        Matcher m = domainPattern.matcher(domainCandidate);
        return m.matches() && domainCandidate.startsWith(hostname);
    }
    protected String
    runConsoleCommand(String command)
        throws IOException
    {
        Process p = Runtime.getRuntime().exec(command);
        InputStream stdoutStream = new
BufferedInputStream(p.getInputStream());
        StringBuffer buffer= new StringBuffer();
        for (;;) {
            int c = stdoutStream.read();
            if (c == -1) break;
            buffer.append((char)c);
        }
        String outputText = buffer.toString();
        stdoutStream.close();
        return outputText;
    }
    /** Sort of like a factory... */
    private static NetworkInfo
    getNetworkInfo()
        throws IOException
    {
        String os = System.getProperty("
os.name
");
        if(os.startsWith("Windows")) {
            return new WindowsNetworkInfo();
        } else if(os.startsWith("Linux")) {
            return new LinuxNetworkInfo();
        } else {
            throw new IOException("unknown operating system: " + os);
        }
    }
    protected String
    getLocalHost()
        throws ParseException
    {
        try {
            return
java.net.InetAddress.getLocalHost().getHostAddress();
        } catch ( java.net.UnknownHostException e ) {
            e.printStackTrace();
            throw new ParseException(e.getMessage(), 0);
        }
    }
    /** Test method */
    public final static void main(String[] args) {
        try {
            System.out.println("Network infos");
            System.out.println("  Operating System: " +
System.getProperty("
os.name
"));
            System.out.println("  IP/Localhost: " +
InetAddress.getLocalHost().getHostAddress());
            //modifica per stampare più mac address
            String[] mac=getMacAddress();
            for(int i=0;i<mac.length;i++)
            {
            	int n=i+1;
            	System.out.println("  MAC Address "+n+": "+mac[i]);
            }
            //modifica originaria
            //System.out.println("  MAC Address: " + getMacAddress());
            System.out.println("  Domain: " + getNetworkDomain());
        } catch(Throwable t) {
            t.printStackTrace();
        }
    }
}
--------------------------------------------------------------------------------------------------------------
package 
lstar.shared.net
;
import java.io.*;
import java.text.*;
import java.util.LinkedList;
import java.util.regex.*;
public class WindowsNetworkInfo
        extends NetworkInfo
{
    public static final String IPCONFIG_COMMAND = "ipconfig /all";
    public String[]
    parseMacAddress()
            throws ParseException
    {
    	//modifica per inserire tutti i mac address all'interno
    	//della lista e poi stamparla.
    	LinkedList<String> listaMacAddress=new LinkedList<String>();
        // run command
        String ipConfigResponse = null;
        try {
            ipConfigResponse = runConsoleCommand(IPCONFIG_COMMAND);
        } catch ( IOException e ) {
            e.printStackTrace();
            throw new ParseException(e.getMessage(), 0);
        }
        // get localhost address
        String localHost =  getLocalHost();
        java.util.StringTokenizer tokenizer = new
java.util.StringTokenizer(ipConfigResponse, "\n");
        String lastMacAddress = null;
        while(tokenizer.hasMoreTokens())
        {
            String line = tokenizer.nextToken().trim();
            // see if line contains IP address, this means stop if
we've already seen a MAC address
            if(line.endsWith(localHost) && lastMacAddress != null) {
            	//questa qui sotto è la modifica originaria per ritornare
            	//l'ultimo mac address trovato
            	//return lastMacAddress;
            	//modifica apportata per ritornare tutti i mac address
            	Object[] tmp=listaMacAddress.toArray();
            	String[] mac=new String[tmp.length];
            	for(int i=0;i<mac.length;i++)
            	{
            		mac[i]=(String)tmp[i];
            	}
            	return mac;
            }
            // see if line might contain a MAC address
            int macAddressPosition = line.indexOf(":");
            if(macAddressPosition <= 0) {
                continue;
            }
            // trim the line and see if it matches the pattern
            String macAddressCandidate =
line.substring(macAddressPosition + 1).trim();
            if(isMacAddress(macAddressCandidate)) {
                lastMacAddress = macAddressCandidate;
                //modifica aggiunta per inserire più mac address
all'interno
                //della lista
                listaMacAddress.addLast(lastMacAddress);
                continue;
            }
        }
        ParseException ex = new ParseException("Cannot read MAC address
from [" + ipConfigResponse + "]", 0);
        ex.printStackTrace();
        throw ex;
    }
    private static boolean
    isMacAddress(String macAddressCandidate)
    {
        Pattern macPattern =
Pattern.compile("[0-9a-fA-F]{2}-[0-9a-fA-F]{2}-[0-9a-fA-F]{2}-[0-9a-fA-F]{2}-[0-9a-fA-F]{2}-[0-9a-fA-F]{2}");
        Matcher m = macPattern.matcher(macAddressCandidate);
        return m.matches();
    }
}
-------------------------------------------------------------------------------------------------------
package 
lstar.shared.net
;
import java.io.*;
import java.text.*;
import java.util.LinkedList;
public class LinuxNetworkInfo
        extends NetworkInfo
{
    public static final String IPCONFIG_COMMAND = "ifconfig";
    public String[]
    parseMacAddress()
        throws ParseException
    {
//    	modifica per inserire tutti i mac address all'interno
    	//della lista e poi stamparla.
    	LinkedList<String> listaMacAddress=new LinkedList<String>();
        String ipConfigResponse = null;
        try {
            ipConfigResponse = runConsoleCommand(IPCONFIG_COMMAND);
        } catch ( IOException e ) {
            e.printStackTrace();
            throw new ParseException(e.getMessage(), 0);
        }
        String localHost = null;
        try {
            localHost =
java.net.InetAddress.getLocalHost().getHostAddress();
        } catch(java.net.UnknownHostException ex) {
            ex.printStackTrace();
            throw new ParseException(ex.getMessage(), 0);
        }
        java.util.StringTokenizer tokenizer = new
java.util.StringTokenizer(ipConfigResponse, "\n");
        String lastMacAddress = null;
        while(tokenizer.hasMoreTokens()) {
            String line = tokenizer.nextToken().trim();
            boolean containsLocalHost = line.indexOf(localHost) >= 0;
            // see if line contains IP address
            if(containsLocalHost && lastMacAddress != null) {
//            	questa qui sotto è la modifica originaria per ritornare
            	//l'ultimo mac address trovato
            	//return lastMacAddress;
            	//modifica apportata per ritornare tutti i mac address
            	Object[] tmp=listaMacAddress.toArray();
            	String[] mac=new String[tmp.length];
            	for(int i=0;i<mac.length;i++)
            	{
            		mac[i]=(String)tmp[i];
            	}
            	return mac;
            }
            // see if line contains MAC address
            int macAddressPosition = line.indexOf("HWaddr");
            if(macAddressPosition <= 0) {
                continue;
            }
            String macAddressCandidate =
line.substring(macAddressPosition + 6).trim();
            if(isMacAddress(macAddressCandidate)) {
                lastMacAddress = macAddressCandidate;
                //modifica aggiunta per inserire più mac address
all'interno
                //della lista
                listaMacAddress.addLast(lastMacAddress);
                continue;
            }
        }
        ParseException ex = new ParseException("cannot read MAC address
for " + localHost + " from [" + ipConfigResponse + "]", 0);
        ex.printStackTrace();
        throw ex;
    }
    public String
    parseDomain(String hostname)
            throws ParseException
    {
        return "";
    }
    private final boolean
    isMacAddress(String macAddressCandidate)
    {
        if(macAddressCandidate.length() != 17) return false;
        return true;
    }
}
------------------------------------------------------------------------------------------
Ovviamente ora sta a te utilizzare tale codice come ritieni più
opportuno.
Penso comunque che questa sia la soluzione migliore poi non so anche
perchè quello che ti sto dicendo non l'ho messo finora in pratica ma
ho solo raccolto informazioni in quanto mi serve la stessa cosa per una
mia applicazione.
Anzi se riesci a fare qualcosa ti sarei grato se postassi la soluzione
sul groups.
Spero di essere stato utile.
Ciao Rocco.

Il sorgente lo modifichi.
vero
però
io ho provato a decompilare miei programmi offuscati
per capire l'affidabilità dell'offuscamento
Auguri!
non riconoscevo più i miei sorgenti
per non parlare di linee in assembler della vm
che non ho sapevo bene come interpretare
sec me
l'offuscamento incasina veramente molto
Ciao
Chairam

 
:-) Purtroppo non posso.
Devo dare la possibilità ad alcuni clienti selezionati di visionare per 
un periodo di tempo limitato ed esclusivamente sulle loro macchine una 
nuova versione di un applicazione.
Inoltre, visto che non mi occupo della commercializzazione del 
programma, ma solo della sua realizzazione, vorrei che i clienti siano 
"obbligati" a chiedermi il codice di attivazione ;-)
Delvy

 
Ciao Rocco, hai colto nel segno.
Non ho visto il codice, ma penso di poterlo usare per fare una specie di 
generatore di chiavi.
Appena ho fatto qualche prova posto la mia esperienza qui.
Grazie
Delvy

 o cose del genere.
Giusto per curiosita', visto che ho fatto un sistema di protezione(anche
se logicamente so benissimo che nessun sistema e' sicuro) per un
applicativo client il tuo applicativo per poter funzionare necessita di
connessione internet?
Perche se cosi fosse puoi, con poco sforzo, implementare un sistema di
gesione delle licenze/attivazioni remoto utilizzando la classe inviata da
hunterbit per generare le chiavi/macchina.
Sistemi(piu o meno complessi) che utilizzano anche le grandi case di
produzione software.
Ciao,crx

 Ciao,crx
Delvy quando farai delle prove fammi sapere sul groups...se avrò del
tempo subito dopo l'esame mi ci metto anch'io.
Per quanto riguarda la soluzione proposta da crx sarebbe un'ottima cosa
fare l'attivazione tramite internet prendendo come chiave di partenza
quella della scheda di rete e poi generare appunto un codice di
attivazione.
A parte questo per offuscare il codice che programma usate?
Grazie 
Hunterbit

 A parte questo per offuscare il codice che programma usate?
Mi trovo bene con Proguard + serializzazioni + trasformazioni di stringe 
in bytearray tanto per creare piu' confiusione possibile :)
Ciao,
crx

 attivazione.
Per come la vedo io, la scheda di rete e' soggetta a cambiamenti e 
dunque non del tutto affidabile per questo tipo di cose. Ho sentito in 
giro che si puo' usare il seriale dell'hd come "parametro" per 
l'attivazione di un prodotto.
Saluti
CarMas

 l'attivazione di un prodotto.
Non se a che tipo di cambiamenti ti riferisci ma se intendevi la
sostituzione della scheda di rete non penso proprio che venga
sostituita molto spesso.
Per quanto riguarda il seriale del HD ci avevo pensato anch'io ma non
ho trovato niente in java su come farlo.
Sicuramente si avrà bisogno di una libreria nativa per poter fare
determinate operazioni come prendere il seriale HD oppure della CPU ed
anche MAINBOARD.
Certo se si trova un modo per fare questo penso anch'io che sia la
soluzione migliore ma al momento, alemno per me, quella della scheda di
rete penso sia la migliore.

> offuscato.  Il sorgente lo modifichi
h> Si questo è vero ma quante persone (clienti per la precisione)
h> effettivamente sanno farlo?
Chiunque abbia  interesse a farlo.  La "security by obscurity"  non ha
mai funzionato.
(Nota: personalmente non  mi piacciono nè le protezioni  nè le licenze
restrittive)


> offuscato.  Il sorgente lo modifichi.
c> vero però io ho provato a decompilare miei programmi offuscati per
c> capire l'affidabilità dell'offuscamento Auguri!
Dipende dal decompilatore e dalla determinazione dell'attaccante.


 ho trovato niente in java su come farlo.
Purtroppo hai ragione, ho chiesto e c'e' una libreria c++ che svolge 
questa operazione...
Quindi sarebbe una cosa un po' laboriosa da integrare, dipende dalle tue 
esigenze.
Saluti
CarMas

 o cose del genere.
Eutron produce chiavi hardware con librerie java per windows.
su altri sistemi unix/linux like, dovrai implementare un'interfaccia java
dalle librerie c++ fornite.
ciao
gl

Rispondo un pò a tutti.
La mia applicazione è, per adesso, una tipica applicazione desktop che 
non richiede la connessione. Cmq per l'attivazione del prodotto mi 
dovrei orientare sul sistema a chiave basato sul mac address. Mi sembra 
la cosa più facile.
Posso anche fare a meno del server per l'attivazione automatica via 
internet almeno in una prima fase che prevede poche copie distribite tra 
i clienti.
Purtroppo per adesso non ho tempo di pensare ad implementare questo 
sistema, ma stò raccogliendo informazioni. Chi prima implementa prima 
avverte :-)
Delvy

 sembra la cosa più facile.
e la cosa pppiu' stupida da aggirare, il mac alla scheda di rete si
cambia in 2 mosse :), cmq e se il pc non ha schede di rete? o ha piu'
di una scheda di rete?

g...@decadence.it
 ha scritto:

Ciao, ho dato un occhiata al loro sito. La cosa sembra interessante, hai 
avuto esperienza diretta e di sai qualche cosa circa gli ordini di 
grandezza della spesa necessaria alla protezione di ogni copia del software?
Delvy

 di una scheda di rete?
La cosa potrebbe anche essere stupida ma penso proprio che le persone
che sappiano cambiare il MAC siano poche in confronto a chi non lo sa
fare.
Inoltre penso proprio che la maggior parte dei clienti una cosa del
genere non la sappiano prorpio fare.
Per quanto riguarda le schede di rete ormai tutti i pc ne sono dotati
ed in particolare è quasi impossibile trovare schede madri che non ne
abbiano una integrata e se proprio ci si mette male beh la si può
anche offrire (il costo è irrisorio).
Il problema potrebbe essere se c'è ne sono più di una.
Beh in questo caso il listato mostrato sopra non fa altro che prendere
tutti i MAC delle schede e quindi si potrebbe tranquillamente usare il
primo che viene prelevato.
Giusto per dirla qualche mese fa ho fatto un programma per un cliente e
non sapendo come proteggerlo ho creto un verifica sulla data di
scadenza e un codice univoco con il programma e sia la data che il
codice l'ho salvati su un file serializzato sul pc del cliente.
Beh il cliente ovviamente non è mai riuscito a sbloccarlo tant'è vero
che ad oggi ne ho venduto un altra copia ad un suo amico.
In conclusione ne ho dedotto che seppur banali le protezioni non tutti
sanno come eliminarle, non tutti sanno che dal bytecode java si può
risalire al codice, e la maggior parte è già assai se riesco ad
accendere il pc ed utilizzare word quindi penso proprio che questo
genere di protezioni possa esere un inizio.

 l'avvio al programma.
Basta crederci che sia univoco eh.
Per il resto concordo con Gian. Peraltro io avrei nettamente preferenza
che chi scrivesse software per me impiegasse il suo tempo eliminando i
bachi e scrivendo test piuttosto che scrivendo features che servono solo
a lui, ma non a me (se vogliamo intendere un blocco hardware come una
feature, eh...)
-- 
blog:  
http://www.akropolix.net/rik0/blogs
 | Uccidete i filosofi,
site:  
http://www.akropolix.net/rik0/
      | tenetevi riso e
forum: 
http://www.akropolix.net/forum/
     | bacchette per voi.

 genere di protezioni possa esere un inizio.
Guarda, a quanto ho visto, pubblicità di ditte interessate aparte, mi
sembra di capire che la protezione dei programmi faccia ben poco per
l'azienda produttrice salvo spendere soldi. Dovresti domandarti se il
costo in tempo e altre risorse non è maggiore di quello in soldi che
perdi perchè qualcuno copia il tuo programma.
Saludos
Adriano

Dipende dal decompilatore e dalla determinazione dell'attaccante.
sicuramente!
in effetti non sono sicuro che il mio decompilatore sia il non plus
ultra, me ne consigli uno buono?
Ciao
Chairam

Adriano Varoli Piazza wrote:
...
Ahem. Scusa hunterbit, me la sono presa col tuo messaggio mentre dovrei
avere diretto le mie critiche e opinioni a Delvy.
Saludos
Adriano

> piu' di una scheda di rete?
h> La cosa potrebbe anche essere stupida ma penso proprio che le
h> persone che sappiano cambiare il MAC siano poche in confronto
Di nuovo. La security  by obscurity NON funziona. L'informazione verrà
fuori molto molto molto più rapidamente di quello che immagini.
Le protezioni NON sono la soluzione.


 nuova versione di un applicazione.
dagli un portatile col software installato con seriali parallele 
ethernet usb sigillate con la gomma da masticare, se il portatile torna 
senza sigilli tu parti con un fucile a canne mozze..
.. no eh?
-- 
sbi

Adriano Varoli Piazza ha scritto:

Allora, ti dò ragione su tutto. Ma bisogna esaminare il caso specifico.
Creare una protezione, per quanto poco efficiente, ha un doppio effetto.
Da un lato ti consente di avre un buon controllo sul tuo software e sui 
tuoi clienti e ti consente di avere semrpe un buon quadro della 
situazione soprattutto quando non sei te a vendere il programma.
Dall'altro lato è il cliente stesso che "vuole" una protezione 
soprattutto se COMPRA il programma. Vuole avere la certezza che il suo 
vicino concorrente non abbia lo stesso programma senza averlo pagato.
Queste due cose potrebbero sembrare irrilevanti rispetto all'eventuale 
sforzo necessario ad implementare una protezione.
Ma, come dicevo, bisogna esaminare il caso specifico.
Il mio software prevede la vendita di 30000 copie in italia e 
all'estero. Quindi non prevedo una grande distribuzione e la necessità 
di proteggermi da copie pirata (anche se in molti casi potrebbe essere 
visto come una forma di marketing).
il caso specifico è questo:
il mercato che vado ad attaccare è di nicchia, attualmente quasi il 50% 
degli operatori in questo mercato sono ancora al cartaceo, il restante 
50% non usa software specifico.
L'azienda che si occupa della commercializzazione di questo software ha, 
giustamente, pensato di approcciare il cliente con un offerta bundle che 
prevede la vendita di un pc con il programma "a prezzo speciale", quindi 
  per la scheda di rete non ci sono problemi.
La campagna che questa ditta stà svolgendo è basata sulla possibilità 
pergli acquirenti di ottimizzare i tempi e quindi avere un leggero 
vantaggio sui concorrenti.
Altra cosa: sulle vendite io mi becco una percentuale... non vorrei che 
i venditori passino copie ad amici e agli amici degli amici.
Tutto qui. E' ovvio che se uno vuole ti frega, ma almeno facciamolo 
lavorare un pò.
Delvy


Nessun problema io rispetto sempre e comunque le opinioni di tutti....
E' ovvio che non tutti la pensano allo stesso modo, quindi proprio per
questo è utile parlarne e confrontarsi.
Per quanto riguarda la soluzione delle chiavette USB hai pienamente
ragione infatti quanto prima userò tale soluzione perchè come hai
illustrato tu ci potrebberò essere alcuni problemi riguardo ad
installazione e montaggio di una scheda di rete (anche se è un
problema che io personalmente non mi pongo in quanto ormai come ben
saprai ogni scheda madre ne possiede una ma giustamente mai dire mai)
quindi consiglierei anche io la soluzione USB.
Visto che è tardi ora vado a nanna.
Ci sentiamo Ciao a tutti.


Beh ovviamente questa è una tua opinione ed in parte la condivido ma
alla fine dei conti che soluzione consiglieresti?
Io per esperienza personale ti dico che il primo programma che ho fatto
era per un amico al quale chiesi che se qualcuno chiedeva informazioni
su chi gli avesse fatto il programma di indirizzarlo così da avere
nuovi clienti...
Sai cos'è successo....che altre due persone alla fine si trovano col
mio programma in mano senza io averci preso niente.
Essendo un programma molto semplice (per la creazione di preventivi) mi
sono ritrovato che non ci ho preso neanche un soldo per quanto riguarda
eventuale assistenza ( che tra parentesi neanche servirebbe) quindi in
conclusione dei fatti chi vuole i miei servizi li avrà sotto pagamento
e con il programma in comodato d'uso annuale e con assistenza compresa
nel prezzo.
Potrebbe essere una forzatura per il cliente ma alla fine non mi va di
lavorare e poi vedermi fregato sotto al naso...ed in secondo luogo io
parlo prima di vendere i miei servizi esponendo tutto in modo chiaro
quindi non obbligo nessuno a venire da me ma chi lo fa deve stare alle
mie condizioni.
Ripeto in parte condivido la tua opinione ma nella mia zona non è
proprio auspicabile una cosa del genere quindi preferisco tutelarmi.
Ora vado veramente a nanna.
Notte.

> 
h>  Io per
h> esperienza personale ti dico che il primo programma che ho fatto
h> era per un amico al quale chiesi che se qualcuno chiedeva
h> informazioni su chi gli avesse fatto il programma di indirizzarlo
h> così da avere nuovi clienti...
Io  avrei usato  una licenza  che mi  permettesse di  non  "vessare il
cliente" e  che nello stesso  tempo ponesse dei vincoli  alla furbizia
dell'"amico"  e permettesse  al  tuo lavoro  di  essere retribuito  in
maniera adeguata (pare che ne esistano!!!  :)).
Dare un programma "sulla parola" è equivalente a metterlo nel pubblico
dominio, purtroppo. E' il motivo per cui sono nate licenze come la GNU
GPL.
