Проблема быстрого роста торрентов не только в том, что защитить программное обеспечение от копирования невозможно, но еще и в том, что многие программисты даже не задумываются о защите, особенно, если пишешь небольшую программу, предназначенную для ограниченного числа пользователей. Многие думают, что защита копирования - это сложно, либо заранее считают, что их приложение не станет сверх-популярным, а многие даже и не задумываются, что могут заработать на создании простенького приложения. Удивительный факт в мире программистов заключается в том, что для того, чтобы создать популярное приложение, достаточно сделать более простой аналог со сложного коммерческого приложения, решая для пользователя хоть и маленькую, зато частую проблему.
Итак, в этом посте я расскажу про лучшую защиту для .Net программ, в том плане, что просто настроить, - про популярный
DeployLX. Открыв их сайт вы обнаружите другую неприятную новость, что экспресс версия этого продукта стоит порядка 200у.е.,а стандарт так вообще под 1000у.е. Лично я бы не поскупился на 200у.е. за стандарт, и вы вероятно тоже хотите стандарт, так как только в нем есть удаленный мониторинг запусков программы. Но платить 1000у.е. - это для нас через чур. Поэтому вот
полная бесплатная версия, правда устаревшая. Это выражается тем, что генератор ключей не пойдет на Vista, w7, w2k8, поэтому у меня установлена одна виртуальная машина с winXP. А также генератор работает только в 32х битной среде, но и это легко решается. Та библиотека, что должна идти вместе с вашим ПО и обеспечивать проверку лицензий можно взять после установки в папке redistributable и идет как Any CPU.
Ниже идет пример реализации компонента, от которого нужно наследовать контролы. При помещении их на страницу будет проверена лицензия и если вы в генераторе ключей задали удаленное отслеживание, то и обращение к вашему серверу лицензий для проверки валидности использования программы.
Copy Source | Copy HTML- // Adding the following attribute enables Visual Studio.NET designer license embedding support
- [LicenseProvider(typeof(SecureLicenseManager))]
- public class ProtectedUserControl : UserControl
- {
- ///Компонент защиты
- private SecureLicense _license;
-
- /// <summary>
- /// Базовая функция, нужна ли защита?
- /// Если не наш сервер, то нужна!
- /// </summary>
- protected virtual Boolean NeedProtection
- {
- get
- {
- return !Request.Url.Host.Contains("localhost");
- }
- }
-
- /// <summary>
- /// Поехала инициализация
- /// </summary>
- /// <param name="e"></param>
- protected override void OnInit(EventArgs e)
- {
- base.OnInit(e);
- if (NeedProtection)
- {
- try
- {
- string keys_dir = Global.LicensePath;
- if(String.IsNullOrEmpty(keys_dir))
- {
- keys_dir = @"C:\Keys";
- }
- Exception e0 = null;
- foreach(string filename in Directory.GetFiles(keys_dir, "*.lic", SearchOption.TopDirectoryOnly))
- {
- LicenseFile file = new LicenseFile();
- file.Load(filename);
- if(file.Id != null)
- {
- LicenseValidationRequestInfo info = new LicenseValidationRequestInfo
- {
- LicenseFile = file,
- SerialNumbers =
- new[] {"DLXLENT-3TX1-2UQ8-87YR-647Y-16WS"}
- };
- try
- {
- _license = SecureLicenseManager.Validate(this, typeof (Components_Campaign), info);
- //нашли рабочую лицензию
- e0 = null;
- break;
- }catch(NoLicenseException nle)
- {
- //если соединились, но что-то на стороне лицензиара пошло не так, то плюем на это дело
- //if(nle.Message.Contains("Cannot validate license at server."))
- if(nle.Message.Contains("The license is still encrypted and the internal properties cannot be accessed"))
- {
- break;
- }
- e0 = nle;
- }
- catch(Exception e1)
- {
- e0 = e1;
- }
- }
- }
- if(e0!=null)
- {
- throw e0;
- }
- }
- catch (Exception e1)
- {
- Global.Logger.Error("licence", e1);
- State.GlobalError = e1.Message;
- Response.Redirect("~/PurchaseLicense.aspx");
- }
- }
- }
-
- public override void Dispose()
- {
- if (_license != null)
- _license.Dispose();
-
- base.Dispose();
- }
-
- }
И конечно же нужно привести возможный код сервера лицензий, работающий с Mysql, так как из документации вы не сможете получить его автоматически для mysql базы:
Copy Source | Copy HTML- using System;
- using System.Data;
- using MySql.Data.MySqlClient;
- using System.Collections;
- using System.Web.Services;
- using DeployLX.Licensing.v3;
- using DeployLX.Licensing.Management.v3;
-
- [WebService(
- // WARNING: Do not change the Namespace
- Namespace="http://www.xheo.com/licensing/v3_0",
- Description="Implements a license server for the DeployLX\u00AE Licensing copy protection system. Additional info at http://www.deploylx.com.",
- Name="DeployLX|Licensing\u00AE Server" )]
- public class MyLicenseServer : LicenseServer
- {
- public MyLicenseServer( )
- {
- KeyFolder = Server.MapPath("~/Keys");
- DeployLxSerialNumbers = new string[] { "DLXLENT-3TX1-2UQ8-87YR-647Y-16WS" };
- Global.Logger.Info("License for " + Context.Request.ServerVariables["REMOTE_ADDR"]);
- }
-
- private const string LicenseTableName = "License";
- private const string ActivationTableName = "Activation";
-
- private string ConnectionString
- {
- get
- {
- return @"Server=servername;Database=license;User=username;Password=password;";
- }
- }
-
- #region Validate...
- protected override bool Validate( ServerRequestContext context, bool autoValidate )
- {
- if( ! base.Validate( context, autoValidate ) )
- return false;
-
-
- // TODO: Add any custom validation logic
-
- using( MySqlConnection conn = new MySqlConnection( ConnectionString ) )
- {
- conn.Open();
- using (MySqlCommand cmd = new MySqlCommand(string.Concat("SELECT IsValid FROM ", LicenseTableName, " WHERE SerialNumber = @SerialNumber"), conn))
- {
- string sn = context.SerialNumber;
- cmd.Parameters.Add( new MySqlParameter( "@SerialNumber", sn) );
-
- object obj = cmd.ExecuteScalar();
- if(int.Parse(obj.ToString())== 0)
- return false;
- }
- }
- return true;
- }
- #endregion
-
-
- #region Activation Support
-
- #region CanActivate...
-
- protected override bool CanActivate( ServerRequestContext context, ActivationLimit limit, ref ActivationProfile suggestedProfile )
- {
- bool result = false;
-
- using( MySqlConnection conn = new MySqlConnection( ConnectionString ) )
- {
- conn.Open();
- using (MySqlCommand cmd = new MySqlCommand(string.Concat("SELECT * FROM ", ActivationTableName, " WHERE SerialNumber = @SerialNumber"), conn))
- {
- cmd.Parameters.Add( new MySqlParameter( "@SerialNumber", context.SerialNumber ) );
-
- using( MySqlDataReader reader = cmd.ExecuteReader( CommandBehavior.SingleResult ) )
- {
- if( reader == null || !reader.Read() )
- {
- result = true;
- }
- else
- {
- ArrayList results = new ArrayList();
- int matchingIdIndex = -1;
-
- do
- {
- object[] values = new object[ 5 ];
- values[ 0 ] = reader[ "SerialNumber" ];
- values[ 1 ] = reader[ "ReferenceId" ];
- values[ 2 ] = reader[ "ProfileHash" ];
- values[ 3 ] = reader[ "DateActivated" ];
- values[ 4 ] = reader[ "AllowNewMachine" ];
- if( (int)values[ 1 ] == suggestedProfile.ReferenceId )
- matchingIdIndex = results.Count;
- results.Add( values );
- }
- while( reader.Read() );
-
- if( matchingIdIndex != -1 )
- {
- object[] values = results[ matchingIdIndex ] as object[];
- result = ( values[ 4 ] != DBNull.Value && (bool)values[ 4 ] ) || CheckProfile( context, limit, values[ 2 ] as string, (DateTime)values[ 3 ], (int)values[ 1 ], out suggestedProfile );
- }
- else
- {
- result = true;
- return result;
- }
-
- if( !result )
- {
- for( int ix = 0; ix < results.Count; ix++ )
- {
- if( ix != matchingIdIndex )
- {
- object[] values = results[ ix ] as object[];
- result = ( values[ 4 ] != DBNull.Value && (bool)values[ 4 ] ) || CheckProfile( context, limit, values[ 2 ] as string, (DateTime)values[ 3 ], (int)values[ 1 ], out suggestedProfile );
- if( result )
- break;
- }
- }
- }
-
- }
- }
- }
- }
-
- return result;
- }
- #endregion
-
- #region RecordActivation...
- protected override void RecordActivation( ServerRequestContext context, ActivationLimit limit, ActivationProfile profile )
- {
- using( MySqlConnection conn = new MySqlConnection( ConnectionString ) )
- {
- conn.Open();
- using( MySqlTransaction transaction = conn.BeginTransaction() )
- {
- using( MySqlCommand cmd = new MySqlCommand( String.Format(
- @"IF EXISTS ( SELECT * FROM {0} WHERE SerialNumber = @SerialNumber AND ReferenceId = @ReferenceId )
UPDATE {0} SET ProfileHash = @ProfileHash, DateActivated = @DateActivated, AllowNewMachine = 0 WHERE SerialNumber = @SerialNumber AND ReferenceId = @ReferenceId
ELSE
INSERT INTO {0} ( SerialNumber, DateActivated, ProfileHash, ReferenceId, AllowNewMachine ) VALUES ( @SerialNumber, @DateActivated, @ProfileHash, @ReferenceId, 0 )", ActivationTableName), conn, transaction))
- {
-
- cmd.Parameters.Add( new MySqlParameter( "@SerialNumber", context.SerialNumber ) );
- cmd.Parameters.Add( new MySqlParameter( "@ReferenceId", profile.ReferenceId ) );
- cmd.Parameters.Add( new MySqlParameter( "@ProfileHash", profile.Hash ) );
- cmd.Parameters.Add( new MySqlParameter( "@DateActivated", DateTime.UtcNow ) );
- // Additional ordered parameters since not all MySql providers support named parameters
- cmd.ExecuteNonQuery();
-
- }
-
- transaction.Commit();
- }
- }
- }
- #endregion
-
- #region CanDeactivate...
- protected override bool CanDeactivate( ServerRequestContext context, ActivationLimit limit, DeactivationPhase phase, ref ActivationProfile suggestedProfile )
- {
- // Only allow deactivation if the license was previously activated and has not already
- // been deactivated.
- bool result = false;
-
- using( MySqlConnection conn = new MySqlConnection( ConnectionString ) )
- {
- conn.Open();
- using (MySqlCommand cmd = new MySqlCommand(string.Concat("SELECT * FROM ", ActivationTableName, " WHERE SerialNumber = @SerialNumber"), conn))
- {
- cmd.Parameters.Add( new MySqlParameter( "@SerialNumber", context.SerialNumber ) );
-
- using( MySqlDataReader reader = cmd.ExecuteReader( CommandBehavior.SingleResult ) )
- {
- if( reader == null || !reader.Read() )
- {
- // Never activated so it cannot be deactivated
- }
- else
- {
- ArrayList results = new ArrayList();
- int matchingIdIndex = -1;
-
- do
- {
- object[] values = new object[ 5 ];
- values[ 0 ] = reader[ "SerialNumber" ];
- values[ 1 ] = reader[ "ReferenceId" ];
- values[ 2 ] = reader[ "ProfileHash" ];
- values[ 3 ] = reader[ "DateActivated" ];
- values[ 4 ] = reader[ "AllowNewMachine" ];
- if( (int)values[ 1 ] == suggestedProfile.ReferenceId )
- matchingIdIndex = results.Count;
- results.Add( values );
- }
- while( reader.Read() );
-
- if( matchingIdIndex != -1 )
- {
- object[] values = results[ matchingIdIndex ] as object[];
- result = (bool)values[ 4 ] == false;
- }
- }
- }
- }
- }
-
- return result;
- }
- #endregion
-
- #region RecordDeactivation...
- protected override void RecordDeactivation( ServerRequestContext context, ActivationLimit limit, ActivationProfile profile )
- {
- using( MySqlConnection conn = new MySqlConnection( ConnectionString ) )
- {
- conn.Open();
- using( MySqlTransaction transaction = conn.BeginTransaction() )
- {
- using( MySqlCommand cmd = new MySqlCommand( String.Format(
- @"UPDATE {0} SET AllowNewMachine = 1 WHERE SerialNumber = @SerialNumber AND ReferenceId = @ReferenceId", ActivationTableName), conn, transaction))
- {
-
- cmd.Parameters.Add( new MySqlParameter( "@SerialNumber", context.SerialNumber ) );
- cmd.Parameters.Add( new MySqlParameter( "@ReferenceId", profile.ReferenceId ) );
-
- cmd.ExecuteNonQuery();
-
- }
-
- transaction.Commit();
- }
- }
- }
- #endregion
-
- #endregion
-
- #region Extension & Subscription Support
-
- #region CanExtend...
- protected override bool CanExtend( ServerRequestContext context, IExtendableLimit limit, out int extensionAmount )
- {
- // TODO: Determine if the limit can be extended.
- extensionAmount = -1;
- return false;
- }
- #endregion
-
- #region RecordExtension...
- protected override void RecordExtension( ServerRequestContext context, IExtendableLimit limit )
- {
- // TODO: Record the extension
- }
- #endregion
-
- #endregion
-
-
-
- #region Register...
- protected override bool Register( ServerRequestContext context, LicenseValuesDictionary registrationInfo, bool autoRegister )
- {
- if( ! base.Register( context, registrationInfo, autoRegister ) )
- return false;
- // TODO: Add any custom validation logic
- using (MySqlConnection conn = new MySqlConnection(ConnectionString))
- {
- conn.Open();
- using (MySqlTransaction transaction = conn.BeginTransaction())
- {
- string sn = context.SerialNumber;
- string xml = context.License.ToXmlString();
- if (autoRegister)
- {
- using (MySqlCommand cmd = new MySqlCommand(
- String.Format(@"INSERT INTO {0} ( SerialNumber, LicenseXml, IsValid ) VALUES ( @SerialNumber, @LicenseXml, 1 ) ON DUPLICATE KEY UPDATE LicenseXml=@LicenseXml", LicenseTableName), conn, transaction))
- {
- cmd.Parameters.Add(new MySqlParameter("@SerialNumber", sn));
- cmd.Parameters.Add(new MySqlParameter("@LicenseXml", xml));
-
- cmd.ExecuteNonQuery();
- }
- }
- else
- {
- using (MySqlCommand cmd = new MySqlCommand(
- String.Format(@"INSERT INTO {0} ( SerialNumber, LicenseXml, IsValid ) VALUES ( @SerialNumber, @LicenseXml, 1 ) ON DUPLICATE KEY UPDATE LicenseXml=@LicenseXml", LicenseTableName), conn, transaction))
- {
- // Repeat parameters since not all MySql providers support named parameters
- cmd.Parameters.Add(new MySqlParameter("@SerialNumber", sn));
- cmd.Parameters.Add(new MySqlParameter("@LicenseXml", xml));
-
-
- cmd.ExecuteNonQuery();
- }
- }
-
- transaction.Commit();
- }
- }
-
- return true;
- }
- #endregion