Adicionando uma página de cadastro de usuários (Hotspot) em ASP.NET no Mikrotik

Cada projeto que pego, é uma aventura nova a ser encarada! Dessa vez me adentrei no universo de Linux, rede, SSH, infra e tudo o que é direito. Quem não conhece, a Mikrotik é uma empresa de grande referência no que tange em equipamentos para redes de computadores. Seu principal destaque é o RouterOS que é sistema operacional que torna o roteador um poderoso gerente de rede. Dentro do Mikrotik (irei me referenciar sempre dessa forma generalizando como um produto), há o HotSpot. Com ele pode-se gerenciar quem irá autenticar na rede para usar a internet (ou apenas a intranet), banda usada, taxa de transferência e outras funcionalidades por usuário ou de forma geral. Um exemplo de uso desse sistema é de quando você vai a uma rede corporativa onde precisa autenticar-se para usar a internet... é isso!


Qual é o objetivo desse artigo então? A princípio é apenas obter as páginas originais de hotspot do Mikrotik, colocar um redirecionador para uma página externa, criar uma página externa de cadastro de usuário (para que possa acessar a rede) e fazer funcionar. Não ensinarei como configurar ou criar o hotspot. Pela net há vários tutoriais muito bons sobre o assunto e também não é de minha área. Recomendo fortemente o fórum da Under-Linux que lá o pessoal manja muito no assunto.

Nesse artigo usei o Mikrotik MKBR100 que é bem fácil de usar: basta plugar o cabo de rede da internet, do servidor e da energia. Pronto!

Para acessar seu SO, deve-se utilizar o Winbox como se fosse um daqueles players de máquina virtual ou OpenSSH. Vamos lá colocar a mão na massa!

Conecte o cabo de rede de seu PC (ou servidor) na porta (interface) ether2 do Mikrotik. Agora conecte outro cabo na ether1 para os outros computadores da rede (ou roteador simples). Porquê na ether1? Estou considerando que o hotspot está configurado para monitorar essa interface enquanto a ether2 será utilizado para que o servidor de comunique com o aparelho. Seria algo do tipo:



Vá em IP > Address no Mikrotik para que sejam configuradas as interfaces e sejam endereçados conforme os exemplos abaixo:

  • ether1: 192.168.88.1
  • ether2: 192.168.88.2

Somente através de IP que será possível enviar os arquivos do hotspot. Se ficar conectando, via MAC, pelo Winbox, você terá a mensagem de erro: "router has been disconnected" (logo, conecte-se usando a ether2 - 192.168.88.2). E outra coisa é: se não seguir o esquema acima, outros erros você terá, como:

could not connect to ... no response
could not connect to ... connection refused

could not connect to ... network is unreachable
could not fetch index from ... not found


Tudo isso por causa de configuração apenas! É bom deixar o IP do servidor fixo para que possa dar permissão de porta, firewall, etc. No caso deixei como 192.168.88.30 (que no caso era minha própria máquina). Uma dica: às vezes dê um reboot no aparelho!

O próximo passo é ativar a porta da API que o Mikrotik dispõe para integração com linguagens de programação. É através dela que iremos conectar e fazer a comunicação. Só que precisamos ativá-la. Vá em IP > Services e ative a API na porta 8728.


Também libere, no Firewall, as portas para conexão tanto do sistema quanto do Winbox (pelo IP), senão quando estiver acessando pela página de cadastro, irá surgir o erro: "nenhuma conexão pôde ser feita porque a máquina de destino as recusou ativamente ..:8728.".


Para permitir a navegação e envio de informações, adicione as permissões no Walled Garden.


Vejam que até o momento tudo é configuração e nada de programação. Continuemos...

Agora vamos em Files e selecione todos os arquivos da pasta hotspot, arraste para sua área de trabalho que será copiado. Para enviar faça o processo inverso... Se estiver acessando pelo IP vai dar certo, senão cairá a conexão (lembra que disse acima?).

Abra o HTML da página login.html e acione um link para a página de cadastro que ficará hospedada no servidor. Adicionei o link <a href="http://192.168.88.30/mikrotik/cadastro.aspx">cadastre-se</a> . Customize as demais páginas como desejar e as envie de volta. Praticamente terminamos de trabalhar no Mikrotik.

Agora vem o mais simples: criar a página ASP.NET. Você pode utilizar qualquer linguagem de programação que possa usar TCP na comunicação e que a API suporte: PHP, Delphi, C, C++, C#, Flash, Ruby on Rails, Java, Python, VB.NET, etc.

Faça o download da classe MK, em C# (eles já dispõe de classes prontas para outras linguagens também), nesse link aqui e adicione no seu projeto. Agora crie um Web Form adicionando os campos que queira trabalhar. No meu exemplo, só quis login (username) e senha. Adicione um Button e um método para executar o cadastro do usuário conforme exemplo abaixo:


             // Autenticação
            MK mikrotik = new MK("192.168.88.2");
            if (!mikrotik.Login("admin", ""))
            {
                ScriptManager.RegisterStartupScript(Page, typeof(Page), "alert", "alert('Houve um problema de comunicação com o Hotspot! Por favor, tente mais tarde.');", true);
                mikrotik.Close();
                return;
            }

            // Requisição
            mikrotik.Send("/ip/hotspot/user/add");
            mikrotik.Send("=name=" + nome.Text);
            mikrotik.Send("=password=" + senha.Text, true);

            //Retorno
            string retorno = string.Empty;
            foreach (string h in mikrotik.Read())
            {
                if (retorno != string.Empty)
                    retorno += ", ";
                retorno += h;
            }

            // Fecha objeto
            mikrotik.Close();

            // Validação
            if (retorno.ToLower().Contains("!done=ret="))
                ScriptManager.RegisterStartupScript(Page, typeof(Page), "alert", "alert('Cadastro realizado com sucesso!'); location.href='http://192.168.88.1/login.html';", true);
            else if (retorno.ToLower().Contains("message=failure: already have user with this name for this server"))
                ScriptManager.RegisterStartupScript(Page, typeof(Page), "alert", "alert('Já existe um usuário com esse nome!');", true);
            else
                ScriptManager.RegisterStartupScript(Page, typeof(Page), "alert", "alert('Falha no cadastro: " + retorno + "!');", true);


Você poderia não só fazer o cadastro de usuário no hotspot como também executar qualquer comando (SSH) no Mikrotik. Lembrando que eu atropelei muita coisa aí no que diz respeito a configuração do dispositivo na qual disse logo no início que não era minha finalidade. Vocês observaram que a parte de programação é bem simples do que configuração... e com certeza é! Agora nunca irá sair de minha cabeça a palavra Mikrotik Mikrotik Mikrotik Mikrotik Mikrotik ... Por favor, qualquer erro ou informação adicional me avise!

[]'s

UPDATE: disponibilizei uma implementação no post http://thiagomarcal.blogspot.com.br/2016/09/implementacao-do-aspnet-c-com-o.html

Recuperando a senha da conta identidade do Pool de Aplicativos do Plesk no IIS

Mais um título longo... um tanto confuso, mas em resposta a uma dúvida de um amigo:
"Exclui um Application Pool no IIS e nela estava configurada uma conta interna do Plesk: IWAM_plesk. Não sei a senha utilizada. Tem como recuperar a senha para recriar um novo application?"
Sim! O Plesk sempre nos pregando aquela peça... Os dois pools que o Plesk utiliza estão vinculados à conta IWAM_plesk (conta interna) para que possam manipular corretamente os arquivos e seus aplicativos (bem como as permissões necessárias) no sistema operacional:

  1. plesk(default)(2.0)(pool)
  2. plesk(default)(4.0)(pool)

 Então façamos as seguintes etapas:

  • Abra o Prompt de Comando em Modo Administrador;
  • Navegue para a pasta C:\Windows\system32\inetsrv;
  • Execute o comando appcmd.exe list apppool “plesk(default)(2.0)(pool)” /text:*
Aparecerá a descrição completa desse pool. Procure a entrada referente ao usuário e senha do Plesk conforme a figura abaixo:


Agora você tem a senha do usuário! No site de Dhiraj tem mais detalhes acerca desse tipo de recuperação.

Calculando saldo final no Crystal Reports

Antes de começar o artigo, queria dizer que nesse mês irei responder muitas das questões que recebo por e-mail (calma que um dia sempre respondo) em forma de artigo, então muitos deles serão curtos e rápidos. Uma das respostas será nesse artigo.
Bem, como calculo o saldo, linha a linha, de entradas e saídas no Crystal Reports? De forma bem simples: uso de variáveis globais. Um bom artigo que sempre recomendo é o de Vince Varallo: não tem erro!
Supondo que você tenha o seguinte relatório e com os respectivos campos:


Esses dados já devem ser puxados do DataSet utilizado (na dúvida, veja o post de criar relatórios). Agora adicionamos um Formula Field com o nome SALDO.


Clique em Use Editor e escreva a seguinte fórmula:



Global NumberVar SALDO_FINAL;

if (RECORDNUMBER = 1) then
    SALDO_FINAL := @ENTRADA - @SAIDA
else
    SALDO_FINAL := SALDO_FINAL + @ENTRADA - @SAIDA


Ou seja, na primeira linha obtenho o saldo inicial diminuindo a ENTRADA da SAIDA. Nos demais já adiciono o saldo anterior. Tranquilo? Agora basta adicionar no relatório esse campo criado.

Testando um Web-Site na própria máquina como se fosse externamente

Tentei elaborar um título de forma que ficasse entendível o que eu queria dizer. Não sei se ajuda, mas significa que:
"Testaremos o acesso a um web-site que está publicado no IIS mas que no navegador não iremos usar localhost para buscá-lo e sim o próprio endereço."
Ficou bom agora? Então vamos lá. No IIS crie um web-site. O processo é bem simples:


Pronto! Já temos nosso site, mas se formos testar: http://thiagomarcal.com.br/ não encontra!


Por que isso? Quando queremos procurar um site, o sistema operacional analisa o hostname para saber onde encontrar esse endereço: se for interno e mapeado usa-se a rede interna, senão procura a rede externa dentre outros. Como não existe o mapeamento e tampouco o domínio externo, não acha nada. Então vamos alterar o arquivo de host.

Siga para o caminho: C:\Windows\System32\drivers\etc e abra o arquivo hosts, em modo texto, para adicionarmos algumas entradas.

# copyright (c) 1993-2009 microsoft corp.
#
# this is a sample hosts file used by microsoft tcp/ip for windows.
#
# this file contains the mappings of ip addresses to host names. each
# entry should be kept on an individual line. the ip address should
# be placed in the first column followed by the corresponding host name.
# the ip address and the host name should be separated by at least one
# space.
#
# additionally, comments (such as these) may be inserted on individual
# lines or following the machine name denoted by a '#' symbol.
#
# for example:
#
#      102.54.94.97     rhino.acme.com          # source server
#       38.25.63.10     x.acme.com              # x client host

# localhost name resolution is handled within dns itself.
#	127.0.0.1       localhost
#	::1             localhost

127.0.0.1	thiagomarcal.com.br

Coloque, na última linha, o IP, tabulação e nome do domínio nessa ordem para o site desejado. Salve e teste novamente o acesso. E pronto!


Mais fácil do que isso: impossível!

Consumindo Web-Service PHP através do ASP.NET (C#)

Já passou por aqui um post sobre de como consumir um web-service, mas era em ASP.NET. Agora como consumir um web-service em PHP? Da mesma forma. Não muda! O fato de escrever esse post é apenas e pelo simples fato de ter um certo inconveniente na elaboração do web-service em PHP.
Se procurar na net, recomendo o artigo de Maurício Reckziegel da iMasters que é muito bom e explica certinho como fazer. Se você seguir à risca o que está escrito irá conseguir elaborar um web-service em PHP normalmente e irá conseguir consumir, menos em ASP.NET. Não é que a matéria esteja errada, é porque o ASP.NET é chato com a validação do WSDL que é gerado pelo PHP. O documento (WSDL) tem que ser gerado na mão e, quando não é gerado de forma automática, estamos susceptíveis ao erro. Com o web-service em PHP é assim...
Então, digamos que você tenha elaborado o web-service em PHP e já fez referência na sua aplicação. Contudo sua aplicação pode dar erro ou retornar nada. Primeiramente, todo WSDL que iremos usar deve ser validado antes de ser devidamente consumido. O Visual Studio dispõe de uma ferramenta que valida e gera a classe consumidora (que não precisar ser usada pois está intrínseco na ferramenta). Vá em Iniciar > Todos os Programas > Microsoft Visual StudioVisual Studio ToolsVisual Studio 2008 Command Prompt. Irá abrir um prompt de comando. Escreva por exemplo: wsdl http://thiagomarcal.blogspot.com/webservice.php?wsdl . Se der tudo certo, irá surgir um resultado semelhante a esse:


Se estiver tudo OK, então pode usar tranquilamente, caso contrário irá surgir o seguinte problema:

Microsoft (R) Web Services Description Language Utility
[Microsoft (R) .NET Framework, Version 2.0.50727.3038]
Copyright (C) Microsoft Corporation. All rights reserved.

Warning: This web reference does not conform to WS-I Basic Profile v1.1.

Ou seja, o WSDL gerado pelo PHP não está conforme o padrão. Dando uma alterada aqui e ali, podemos usar o seguinte código para gerarmos o web-service em PHP dentro dos conformes:

<?php
// Requerimento aos componentes
require_once('nusoap/nusoap.php');
include("class.phpmailer.php");
include("class.smtp.php");


// Criação da instância
$server = new soap_server();


// Registro do método
$server->register('EnviaMail');


// WSDL
$server->configureWSDL('server.EnviaMail','urn:server.EnviaMail');
$server->wsdl->schemaTargetNamespace = 'urn:server.EnviaMail';
// registra o método a ser oferecido
$server->register('EnviaMail', //nome do método
array('email' => 'xsd:string'), //parâmetros de entrada
array('return' => 'xsd:string'), //parâmetros de saída
'urn:server.EnviaMail', //namespace
'urn:server.EnviaMail#EnviaMail', //soapaction
'rpc', //style
'literal', //use
'Retorna se o e-mail foi enviado' //documentação do serviço
);


// Definição do método a ser utilizado
function EnviaMail($email) {
return "E-email enviado com sucesso!";
}


// Requisição para uso do serviço
$HTTP_RAW_POST_DATA = isset($HTTP_RAW_POST_DATA) ? $HTTP_RAW_POST_DATA : '';
$server->service($HTTP_RAW_POST_DATA);
?>

Perceberam a diferença? Praticamente nenhuma, mas há: o estilo do documento. Não irei entrar em detalhes, mas dois tipos de documentos mais usados: encoded e literal (veja mais nesse artigo). Use literal! Com isso seu web-service será consumido certinho...
Se estiver com o erro: O cliente encontrou o tipo conteúdo de resposta de ' text/html; charset = utf-8 ', mas era esperado 'text/xml' ou The client find the answer 'text/html', but was expected 'text/xml'. verifique se seu web-service está imprimindo conteúdo HTML (echo) e comprometendo o retorno dos dados. Tranquilo, né?

Problemas de cache no SQL Server

Se você estiver recebendo uma mensagem do tipo:

O SQL Server encontrou %d ocorrência(s) de liberação de armazenamento em cache para o cache '%s' (parte do cache do esquema) devido à manutenção do banco de dados ou operações de reconfiguração.
ou
SQL Server has encountered %d occurrence(s) of cachestore flush for the '%s' cachestore (part of plan cache) due to some database maintenance or reconfigure operations.
É porque, segundo a MSDN, ao limpar o cache do plano gera uma recompilação de todos os planos de execução subseqüentes e pode provocar uma queda repentina e temporária no desempenho da consulta. Para cada armazenamento em cache limpo no cache do plano, aparece a mensagem supracitada.

Para resolver isso, basta ir no banco correspondente, clicar com o direito sobre ele e escolher Properties (Propriedades). Entre em Options (Opções) e configure o Auto-Close (Fechamento Automático) para False.

Error 500 Internal Server Error - Como descobrir o problema

Quando dá esse erro muitas pessoas tremem só de ver! Abaixo darei uma dica para quem é marinheiro de primeira viagem e se depara com isso.

Essa tela é gerada pelo IIS para camuflar o erro para o usuário. Se a aplicação não for bem tratada quanto a erros, esse é o último recurso que o IIS faz para não exibir o erro na tela. Seria bem incômodo para o usuário ver na tela o erro de seu site, por exemplo. Para quem está gerenciando a aplicação é incômodo até certa parte, pois muitas vezes o desenvolvedor resolve o problema mais olhando o erro do que analisando log, events, etc. Pois bem, vamos lá!

Abra o IIS e procure pela função Error Pages (Páginas de Erro) no módulo IIS. Entre e procure pelo link Edit Resource  Settings (Editar Configurações de Recurso). Ao abrir, a tela de Edit Error Pages Settings (Editar Configurações de Página de Erro) escolha a opção Detailed errors (Erros detalhados) e OK.


Ou, mais especificamente para o ASP.NET, procure a função .NET Error Pages (Páginas de Erro do .NET) no módulo ASP.NET. Entre e procure pelo link Edit Resource  Settings (Editar Configurações de Recurso). Ao abrir, a tela de Edit Error Pages Settings (Editar Configurações de Página de Erro) escolha a opção Desactive (Desativar) e OK.


Com isso, a depender do erro, já estarão sendo enviados para a tela. Lembram do post sobre segurança? No web.config, deixe o customErrors com o atributo mode="Off" caso necessário para que os erros sejam exibidos.

Caso ainda não tenha descoberto o problema, acesse a configuração do ASP  no módulo IIS. Expanda a propriedade Debugging Properties (Propriedades de Depuração) e coloque como True a função Send Errors to Browser (Enviar Erros ao Navegador).


Isso deve ser mais do que o suficiente para exibir o erro e identificar qual o problema está na aplicação. Lembrando que, se preferível, não deixar o erro ser exibido para o cliente. Deve-se fazer o possível para tratar e localizar adequadamente o problema. Segurança em primeiro lugar!

Cufon na Master Page

Para quem está com problemas no carregamento/aplicação do Cufon quando estiver usando Master Page, chame-o da seguinte forma:

<script>
Cufon.now();
function pageLoad(sender, args) {
      Cufon.refresh();
}
</script>

Assim ele aplica corretamente após o carregamento da página.

Out of Memory at Line - Memória Insuficiente

Dica rápida para quem tiver esse tipo de problema: remova o atributo LoadScriptsBeforeUI='false' from ScriptManager. Dentro de Grid editáveis ou em formulários dinâmicos a validação do Javascript falha quando estiver fazendo esse tipo de otimização.

Integração ASP.NET (C#) com o Google Analytics

Nesse artigo irei demonstrar como fazer consultas no Google Analytics, de um determinado site, para ser usado em cálculos estatísticos afim de gerar gráficos semelhantes. Ou seja, irei demonstrar como fazer a consulta e retornar os dados. Os gráficos e exposição dos dados ficará ao seu critério. Ao final poderá ter algo assim:


Primeiramente e óbvio, é preciso dos dados da conta Google como o e-mail e senha para autenticação. Cada site cadastrado no Analytics tem um ID do Perfil e ele pode ser localizado na seguinte página  de Configurações do Perfil. Através desse Id é que consultamos os dados de um determinado site.


Com esses dados em mãos, vamos ao entendimento da integração. O Google disponibilizou um framework que auxilia na consulta e retorno de dados em .NET e ela pode ser baixada no Google GData. Dentro dela contêm vários binários mas precisaremos apenas dos seguintes:

  1. Google.GData.Analytics.dll
  2. Google.GData.Client.dll
  3. Google.GData.Extensions.dll

Adicione-os ao seu projeto. Mas antes de começar a programar isso, vamos ao entendimento das consultas realizadas. No link http://code.google.com/intl/pt-BR/apis/analytics/docs/gdata/gdataExplorer.html tem um simulador que será similar à consulta feita pela sua aplicação. Já no link http://code.google.com/intl/pt-BR/apis/analytics/docs/gdata/gdataReferenceDimensionsMetrics.html contêm toda as especificações necessárias.
Como não gosto de perder tempo e, para muitos pode ser um pouco confuso o seu entendimento logo de cara, vamos fazer uma abstração:

  • ids: é o ID do Perfil conforme vimos anteriormente;
  • dimensions: vamos "acatar" que seja os relacionamentos (linhas)  (não é obrigatório);
  • metrics: vamos "acatar" que seja o que deseja buscar (colunas);
  • segment: especificação do tipo de consulta (não é obrigatório); 
  • filters: é o WHERE da consulta (não é obrigatório);
  • sort: ordenação  (não é obrigatório);
  • start-date: data de início (formato yyyy-MM-dd);
  • end-date: data final (formato yyyy-MM-dd);
  • start-index: serve para paginar dados (como o MySQL usa o LIMIT);
  • max-result: quantidade máxima de registros retornados.

Essa é a explicação mais entendível possível e que posso passar para vocês. Logo, antes de botar a mão na massa, sugiro fazer várias simulações e ver como é retornado os objetos. De acordo com a simulação que fiz, por exemplo, e de acordo com a figura seria a seguinte busca:

"Me retorne todas as cidades, que visitaram meu blog, com suas respectivas quantidades de visitas no período de 03/07/2011 a 17/07/2011 ordenada por cidade. Só me traga os 50 primeiros registros."

Então, me seria retornado a seguinte "tabela":


Ficou claro? Tranquilo, né? A pior parte já passou. Agora, porquê escrevi "tabela"... Porque não é! Lembra que abstraímos para que seja isso. Sabendo de como a API trabalha então bastemos implementar. Vamos lá! Pode-se criar uma classe ou incorporar na mesma página o código. Fica a seu critério!

Adicione os seguintes namespaces:

using Google.Analytics;
using Google.GData.Analytics;
using Google.GData.Client;
using Google.GData.Extensions;

Também as variáveis abaixo são necessárias para a devida autenticação e consulta nas quais descrevem:

// Variáveis
string user; // Usuário da Conta Google Analytics
string pass; // Senha  da Conta Google Analytics
string id_tabela; // ID do Perfil

// Inicialização
user = "email@gmail.com";
pass = "senha";
id_tabela = "ga:30897221"; // Tem que colocar a string "ga" na frete do ID do Perfil

Agora adicione os seguinte métodos:

/// <summary>
/// Consulta o Analytics
/// </summary>
/// <param name="query"></param>
    private DataFeed ConsultaAnalytics(DataQuery query)
    {
        AnalyticsService asv = new AnalyticsService("gaExportAPI_acctSample_v2.0");
        asv.setUserCredentials(user, pass);
        String baseUrl = "https://www.google.com/analytics/feeds/data";
        query.Ids = id_tabela;
        query.Uri = new Uri(baseUrl);
        return asv.Query(query);
    }


/// <summary>
/// Retorna o resultado da consulta
/// </summary>
/// <param name="query"></param>
/// <returns></returns>
    private DataTable RetornaTabela(DataQuery query)
    {
        DataTable resultado = new DataTable();
        DataFeed feed = ConsultaAnalytics(query);
        if (feed.Entries.Count > 0)
        {
            // Cria as colunas
            DataEntry colunEntry = feed.Entries[0] as DataEntry;
            foreach (Dimension colundimension in colunEntry.Dimensions)
                resultado.Columns.Add(new DataColumn(colundimension.Name.Substring(3)));
            foreach (Metric colunmetric in colunEntry.Metrics)
                resultado.Columns.Add(new DataColumn(colunmetric.Name.Substring(3)));          


            // Insere os dados
            foreach (DataEntry singleEntry in feed.Entries)
            {
                DataRow rw = resultado.NewRow();
                foreach (Metric metric in singleEntry.Metrics)
                    rw[metric.Name.Substring(3)] = metric.Value;
                foreach (Dimension dimension in singleEntry.Dimensions)
                    rw[dimension.Name.Substring(3)] = dimension.Value;
                resultado.Rows.Add(rw);
            }
        }
        return resultado;
    }

Óbvio que essas são apenas sugestões de implementações e você podem mudar o quanto achar melhor a depender de sua unidade de negócio. Agora vamos fazer aquela consulta:


        // Dados da consulta
        DataQuery q = new DataQuery();
        q.Dimensions = "ga:city";
        q.Metrics = "ga:visits";
        q.GAStartDate = DateTime.Parse("03/07/2011").ToString("yyyy-MM-dd");
        q.GAEndDate = DateTime.Parse("17/07/2011").ToString("yyyy-MM-dd");
        q.Sort = "ga:city";
        q.NumberToRetrieve = 50;


        // Resultado
        DataTable resultado = RetornaTabela(q);


Pronto! Irá me retornar um DataTable com aquela tabela de dados. Basicamente todas aquelas informações que você vê no Analytics são vindas em formato tabela e que, posteriormente, são feitos tratamentos e cálculos sobre eles. Como disse no início do artigo, agora basta usar a tabela de dados conforme desejar...
Espero que tenham gostado depois de um tempo sem postar nada...