segunda-feira, 11 de julho de 2011

Uma aplicação cliente servidor com autenticação - Debian

Plataforma: Debian (Linux debian 2.6.32-5-amd64)

Olá Pessoal,

hoje vamos trabalhar em uma aplicação cliente/servidor – client/server – onde nosso cliente irá enviar alguns comandos como (ls, mkdir, etc...) que serão executados no servidor e exibidos na tela do cliente. A topologia é bem simples, nosso servidor deverá ficar escutando – listening - em um determidada porta. Nosso servidor porvê autenticação, e a base de dados é a mesma do sistema. Assim, você irá se logar apenas se o usuário digitado, for um usuário cadastrado no sistema. Para verificar os usuários do sistema você pode verificar em: /etc/passwd.

Bom nosso servidor também loga em um arquivo quando o cliente se conecta e quando o mesmo é desconectado:

Para rodar nosso servidor executamos o seguinte comando: ./teste 4001

Onde 4001 é o número da porta que o servidor deve estar escutando. Você pode selecionar outra porta, sempre tendo o cuidado de não colocar números que estão sendo utilizados por outros serviços, como http – porta 80, hhtps: 443, etc...

Esse é o código do nosso servidor:
Cabeçários:

#include <sys/socket.h>
#include <sys/types.h>
#include <stdio.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <time.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <pwd.h>
#include <crypt.h>
#include <shadow.h>


Funções Auxiliares, é sempre recomendado que você organize os seus códigos escritos na Linguagem C, em funções, assim o código ficará mais claro de ser entendido. Sempre que considerar necessários comentários são bem vindos:

Função responsável por gravar os dados dos cliente conectados. Veeja que a mesma recebe a estrutura de dados do cliente.

int gravaLog(struct sockaddr_in cliaddr, char * mensagem)
{
char buff[4096]; //armazenar informações do cliente
FILE * arqLog; //Arquivo de logs para IP e porta de conexão do cliente
int porta; //porta que o cliente usou para se conectar
time_t tem = time(NULL);
arqLog = fopen("logs","a+");
inet_ntop(AF_INET, &(cliaddr.sin_addr), buff, sizeof(buff));
porta = ntohs(cliaddr.sin_port);
fprintf(arqLog,"%s: IP %s, Porta %d as %s",mensagem,buff,porta, ctime(&tem));
fclose(arqLog);
return 1;
}

Função que faz a consulta no arquivo onde são armazenadas as senhas de nosso sistema.

int AutenPasswd(char * user, char * senha)
{
struct passwd * pw;
struct spwd * shad;
pw = getpwnam(user);
shad = getspnam(user);

if(pw == NULL)
return -1;
else
{
strcpy(pw->pw_passwd,shad->sp_pwdp);

if (strcmp(crypt(senha, pw->pw_passwd),pw->pw_passwd)==0)
return 1;
else
return 0;
}

}

Essa função recebe os dados fornecidos pelo programa cliente, ela faz uso da função AutenPasswd:

//Retorna 0 se a autenticacao falhar, 1 ser ocorrer ocm sucesso
int Autenticacao(int * connfd)
{
int err; //recebe valores de retorno das funções
char buff[256], user[256], password[256];

strcpy(buff, "1");
bzero(user,256);
bzero(password,256);
err = read(*connfd,user,sizeof(user));

err = read(*connfd,password,sizeof(password));

err = AutenPasswd(user,password);

if(err != 1) //Autenticacao falhou
{
strcpy(buff, "0");
write(*connfd,buff,sizeof(buff));
close(*connfd);
return 0;
}
else
write(*connfd,buff,sizeof(buff));

return 1;

}

Funções auxiliares para trabalhar com arquivos. O resultado do comando enviado pelo cliente é colocado em um arquivo, temos que ler este aquivo e colocá-lo em uma matriz para enviar. Aqui nós fixamos o tamanho do array, mas o mais interessante seria trabalhar com alocação de memória dinâmica, pegando o número de caracteres retornados pela função ContaCaracteres, assim o programa fica melhor estruturado, pois o tamanho do array pode passar de 4096 e comportamentos não desejados, ou até que comprometam a segurança do sistema podem ocorrer.

int ContaCaracteres()
{
int contador = 1;
char letra;
FILE *arq = fopen("arqTemp","r");
letra = fgetc(arq);
while (letra != EOF)
{
contador++;
letra = fgetc(arq);
}
fclose(arq);
return contador;

}
void EnviaResultado(int * connfd)
{
FILE * arquivo;
char buffer[4096], letra;
int contador, x=0;


contador = ContaCaracteres();
arquivo = fopen("arqTemp","r");

letra = fgetc(arquivo);
while(x<contador)
{
buffer[x] = letra;
letra = fgetc(arquivo);
x++;
}
buffer[--x]='\0';


fclose(arquivo);


write(*connfd,buffer,sizeof(buffer));

}

Enfim, nosso programa main:

int main(int argc, char ** argv)
{
int listenfd, connfd, porta; //descitores de conexao e varialvel porta que ira armazenar a porta do servidor
struct sockaddr_in servaddr, cliaddr; //estruturas sockadd_in para armazenar os dados do server e do cliente
socklen_t len; //variavel para armazenar o tamanho da estrutura do cliente
char buff[4096];//vai armazenar informacoes sobre o cliente

int n,err;

if (strcmp("-h",argv[1]) ==0)
{
printf("\n\tChamada do programa:\n\t./serv numPort\n\tOnde numPort e o numero da porta TCP\n");
return 1;
}

else
if(argc != 2)
{
printf("\n\tErro parametros invalidos\n");
return 0;
}

porta = atoi(argv[1]);
listenfd = socket(AF_INET, SOCK_STREAM,0);


memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(porta);



bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
listen(listenfd,1);



len = sizeof(cliaddr);
connfd = accept(listenfd, (struct sockaddr *)&cliaddr,&len);//vamos aceitar o cliente

gravaLog(cliaddr, "Cliente conectou");
err = Autenticacao(&connfd);
if(!err)
return 0;



do
{



read(connfd,buff,sizeof(buff));
if(strcmp(buff,"exit")!=0)
strcat(buff, " > arqTemp");

);
system(buff);
EnviaResultado(&connfd);

}while(strcmp(buff,"exit"));

gravaLog(cliaddr, "Cliente desconectou");
close(connfd);


return 1;
}

Agora nosso programa cliente. O mesmo pode ser executado com o comando ./teste

O IP do servidor e a porta, já estão fixos no código. Seria interessante modificar, para que os mesmos sejam passados pela linha de comando.

#include <sys/socket.h>
#include <stdio.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <time.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <pwd.h>

int RequisitaAutenticacao(int * sockfd)
{
int err;
char buff[256];
char user[256], password[256];
char *ptr;


printf("\nInforme o usuário: ");

scanf("%s",user);
ptr = getpass("\nInforme sua senha:");
strcpy(password,ptr);

err = write(*sockfd, user, sizeof(user)); //envia usuario
err = write(*sockfd,password,sizeof(password)); //escreve a senha




err = read(*sockfd,buff,sizeof(buff));

if(!strcmp(buff,"1"))
return 1; //autenticacao OK
else
return -1; //autenticacao falhou


}

int main()
{
char buff[4096];
int sockfd, ret;
struct sockaddr_in servaddr;
sockfd = socket(AF_INET,SOCK_STREAM,0);
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(4001);
inet_pton(AF_INET,"10.0.2.2", &servaddr.sin_addr);
connect(sockfd, (struct sockaddr *)&servaddr,sizeof(servaddr));
ret = RequisitaAutenticacao(&sockfd);
if(ret != 1)
{
printf("\n\tAutenticacao Falhou");
close(sockfd);
return 0;
}

else
printf("\n\tUsuario Autenticado com SUCESSO!!!");

do
{
printf("\n>");


fgets(buff,4095,stdin);
buff[strlen(buff)-1]='\0';
write(sockfd,buff,sizeof(buff));

if(strcmp(buff,"exit")!=0)
{

read(sockfd,buff,sizeof(buff));
printf("\n%s",buff);
}

}while(strcmp(buff,"exit"));
close(sockfd);



return 0;
}

quarta-feira, 6 de julho de 2011

Usando DllImport no Csharp (C#)

Algumas vezes temos a necessidade de usarmos funções que estão em Dlls que não foram escritas em C#. Surge então, alguns problemas, como passagens de parâmetros, passagens de ponteiros, entre outros que não são diretamente compatíveis entre C# e a linguagem em que a Dll foi escrita. O Objetivo desse tutorial é introduzir a utilização do comando DllImport utilizando uma Dll que foi escrita em C. Assim teremos funções que precisaremos passar ponteiros, structs, etc que precisarão de uma compatibilização.

Primeiramente vamos criar um projeto em C++ para criarmos nossa Dll. Vou apenas colocar o código que usamos para nossas funções e structs. O restante acrescentado pelo compilador será omitido.

Assim as funções de nossa DLL são as seguintes: Observe que normalmente você já terá a Dll que será importada, aqui estamos criando para fins de demonstração.

struct dados //estrutura simples
{
int valor;
};

struct dadosString //contem string interna
{
int valor;
char nome[50];
};

extern "C"
{
__declspec(dllexport) void Printf()
{
printf ("Parabens conseguimos importar a DLL\n");
}

}

extern "C"
{
__declspec(dllexport) void Printf_M(char * mensagem)
{
printf("A mensagem que voce quer mostrar na tela:\n%s\n",mensagem);
}
}

extern "C"
{
__declspec(dllexport) int ContaCaracteres(char * mensagem)
{
int contador = 0;
while(*mensagem)
{
contador++;
mensagem++;
}

return contador;
}
}

extern "C"
{
__declspec(dllexport) char* ConverteParaMaiuscula(char * mensagem)
{
char *p;
p = mensagem;
while(*p)
{
*p = toupper(*p);
p++;
}
return mensagem;
}
}

extern "C"
{
__declspec(dllexport) void PreencheEstruturaInt(struct dados * d)
{
d->valor = 45;
}
}

extern "C"
{
__declspec(dllexport) void PreencheEstruturaString(struct dadosString * d)
{
d->valor = 55;
strcpy(d->nome,"Teste da Funcao");
}
}

Bem simples....

Nosso próximo passo será criar um projeto em CSHARP, para chamarmos nossa DLL.

Dentro de nosso projeto vamos criar uma classe chamada: operacional.cs . É nessa classe que faremos a chamada do DllImport.

Aqui está o código da nossa classe. Veja que a string “path” contém o caminho onde está a Dll, você deve ajustar para onde sua Dll está, por exemplo: path = @“C:\minhaDll”;

Importante, para usar DllImport, precisamos incluir o seguinte namespace:

using System.Runtime.InteropServices;


class operacional
{
private const string path = @"C:\Users\Eduardo\Documents\Visual Studio 2010\Projects\Dll\Debug\Dll.dll";
[StructLayout(LayoutKind.Sequential)]
public struct dados
{
public int valor;
}

public struct dadosString
{
public int valor;
[MarshalAs(UnmanagedType.ByValTStr,SizeConst = 50)]public string nome;
}

//importa função: void Printf()
[DllImport(path)]
public static extern void Printf();

//Importa Função: void Printf_M(char * mensagem)
[DllImport(path, CallingConvention = CallingConvention.Cdecl)]
public static extern void Printf_M(string mensagem);

//Importa Função: int ContaCaracteres(char * mensagem)
[DllImport(path, CallingConvention = CallingConvention.Cdecl)]
public static extern int ContaCaracteres(string mensagem);

//Importa Função: char* ConverteParaMaiuscula(char * mensagem)
[DllImport(path, CallingConvention = CallingConvention.Cdecl)]
public static extern string ConverteParaMaiuscula(string mensagem);

//Importa Função: void PreencheEstruturaInt(struct dados * d)
[DllImport(path,CallingConvention = CallingConvention.Cdecl)]
public static extern void PreencheEstruturaInt(ref dados estrutura);

//Importa Função: PreencheEstruturaString(struct dadosString * d)
[DllImport(path,CallingConvention = CallingConvention.Cdecl)]
public static extern void PreencheEstruturaString(ref dadosString estrutura);

}

Agora a segue nosso programa de teste:

namespace ChamaDll
{
class Program
{
static void Main(string[] args)
{

int retorno=0;
string mensagem = "Teste com passagem de ponteiros";
operacional.dados estrutura;
estrutura.valor = 0;

//chamada sem parâmetros e sem valor de retorno
operacional.Printf();

//chamada com parametros
operacional.Printf_M(mensagem);

//Chamada com valor de retorno
retorno = operacional.ContaCaracteres(mensagem);
Console.WriteLine("Numero de caracteres: {0}",retorno);

//Chamada modificando o ponteiro - string
mensagem = operacional.ConverteParaMaiuscula(mensagem);
Console.WriteLine(mensagem);

//chamada com struct
Console.WriteLine("Valor da estrutura antes da chamada: {0}", estrutura.valor);
operacional.PreencheEstruturaInt(ref estrutura);
Console.WriteLine("Valor da estrutura depois da chamda: {0}", estrutura.valor);

//chama strutura que contem uma string
operacional.dadosString estruturaString;
estruturaString.nome = "NULL";
estruturaString.valor = 0;

operacional.PreencheEstruturaString(ref estruturaString);

Console.WriteLine("Nome: {0}, Idade:{1}", estruturaString.nome, estruturaString.valor);
Console.Read();
}
}
}



Resultado:

  

sexta-feira, 24 de junho de 2011

Arrays bidimensionais

Olá pessoal,

hoje vamos tentar trabalhar um pouco com arrays bidimensionais, mais especificamente em como passá-los como parâmetros de funções.

Para isso vamos escrever duas funções, uma para imprimir um array e outra para preencher.

No exemplo vamos utilizar um array de 3 por 4.


void ImprimeMatriz(int matriz[][4], int linha)
{
    int c_linha; //conta linhas
    int c_coluna;//conta colunas
    int coluna = 4;
    for(c_linha = 0; c_linha < linha; c_linha++)
    {
        printf("\n");
        for(c_coluna = 0; c_coluna < coluna; c_coluna++)
            printf("%d ", matriz[c_linha][c_coluna]);
    }

}

void PreencheMatriz(int matriz[][4], int linha)
{
    int contador = 0;
    int c_linha; //conta linhas
    int c_coluna;//conta colunas
    int coluna = 4;
    for(c_linha = 0; c_linha < linha; c_linha++)
    {
        for(c_coluna = 0; c_coluna < coluna; c_coluna++)
            matriz[c_linha][c_coluna] = contador++;
    }
}

Veja que, temos que passar o tamanho da linha como parâmetro da matriz, vejas as assinaturas: void PreencheMatriz(int matriz[][4], int linha) e void ImprimeMatriz(int matriz[][4], int linha)

Bom, isso porque na verdade é passado o endereço do array, e o C precisa saber até onde ele pode ir na linha.

Outra possibilidade é utilizarmos aritmética de ponteiros, na verdade, um array é um conjunto contínua na memória, assim por exemplo, se o endereço inicial é 10, e um inteiro ocupar 2 bytes temos que o primeiro elemento está no endereço 10, o segundo no endereço 12, o terceiro no 14.... Isso deve ser bem observado, por exemplo para chegar no endereço 12, se nosso endereço base é 10, fazemos a seguinte operação com nosso ponteiro: ponteiro +1, ou seja, o + 1 diz que vamos para o próximo endereço, com o passo de acordo com nosso tipo, que no exemplo é int e assumimos que ele é de 2 bytes. Vejamos abaixo uma função que vai fazer esse tipo de operação para imprimir nosso array:

void ImprimeMatrizPonteiros(int * matriz, int tamanho)
{
    int cont = 0;
    for (cont = 0; cont < tamanho; cont ++)
    {
        printf("% d", *(matriz + cont));
    }
}

Nosso método main:

int main()
{
    int matriz[3][4];
    PreencheMatriz(matriz,3);
    ImprimeMatriz(matriz,3);
    printf("\nVamos usar apenas ponteiros:\n");
    ImprimeMatrizPonteiros(matriz[0],12);


    return 0;
}

que irá reproduzir a seguinte saída: