2010-09-13

Script: receba seu IP externo via gmail, ou ainda, automatize seu sistema com o auxílio de e-mails

Muitas vezes precisamos acessar nossas máquinas remotamente (via SSH principalmente), mas como aqui no Brasil os IPs estáticos são absurdamente caros e os dinâmicos podem facilmente mudar: perda de sinal do modem, falta de energia, problemas no provedor de acesso... precisamos de algum modo simples e transparente de obtê-lo.
É, eu sei, existe o NoIP e o DynDNS, mas esta mesma solução pode resolver outros problemas para outros usuários, que simplesmente não consigo prever.
Então, mãos à obra pessoal.

Nossa tarefa será dividida em 3 partes:

1. Instalar o software de envio de e-mails;
2. Criar os scripts para ler automaticamente os e-mails;
3. Criar um script para ser executado pelo boot e pelo cron.


Índice


1. Instalando o msmtp;
    1.1 Gerando os certificados PEM;
    1.2 Configurando o .msmtprc;
2. Lendo os e-mails com o wget;
    2.1 Acessando o servidor remoto;
3. Criando um script de checagem e envio;
  3.1 O script rc.mailMyIp.



1 - Enviando os e-mails: msmtp + gmail


O msmtp é um cliente capaz de enviar e-mails para qualquer servidor SMTP, é um bom substituto simplificado para o sendmail.

Sua documentação pode ser lida aqui:
  < http://msmtp.sourceforge.net/doc/msmtp.html >

O código-fonte pode ser baixado aqui:
     < http://sourceforge.net/projects/msmtp/

Instalação 
$ wget http://downloads.sourceforge.net/project/msmtp/msmtp/1.4.21/msmtp-1.4.21.tar.bz2
$ tar xjvf msmtp-1.4.21.tar.bz2
$ cd msmtp-1.4.21
$ ./configure
$ make
# make install

Se você estiver no Debian ou algum variante rode: 
$ sudo apt-get install `apt-cache search msmtp | awk '{print $1}'`

Para configurar o msmtp eu me baseei neste ótimo artigo do Andrew's Corner. Como agora o gmail só permite POP e SMTP via conexão SSL, precisaremos gerar dois certificados PEM (Privacy-enhanced Electronic Mail). Vamos lá.


1.1 - Gerando os certificados PEM


O Andrew criou um script em Perl para gerarmos localmente os certificados e só me levou meia hora para configurar todas os módulos que o Perl necessitava. Então, por caridade, pus o código abaixo, é só copiar e colar:

$ mkdir -pv $HOME/.certs && cat > $HOME/.certs/Equifax_Secure_CA.pem << FIM
-----BEGIN CERTIFICATE-----
MIIDJzCCApCgAwIBAgIBATANBgkqhkiG9w0BAQQFADCBzjELMAkGA1UEBhMCWkExFTATBgNVBAgT
DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3dGUgQ29uc3Vs
dGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UE
AxMYVGhhd3RlIFByZW1pdW0gU2VydmVyIENBMSgwJgYJKoZIhvcNAQkBFhlwcmVtaXVtLXNlcnZl
ckB0aGF3dGUuY29tMB4XDTk2MDgwMTAwMDAwMFoXDTIwMTIzMTIzNTk1OVowgc4xCzAJBgNVBAYT
AlpBMRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNhcGUgVG93bjEdMBsGA1UEChMU
VGhhd3RlIENvbnN1bHRpbmcgY2MxKDAmBgNVBAsTH0NlcnRpZmljYXRpb24gU2VydmljZXMgRGl2
aXNpb24xITAfBgNVBAMTGFRoYXd0ZSBQcmVtaXVtIFNlcnZlciBDQTEoMCYGCSqGSIb3DQEJARYZ
cHJlbWl1bS1zZXJ2ZXJAdGhhd3RlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0jY2
aovXwlue2oFBYo847kkEVdbQ7xwblRZH7xhINTpS9CtqBo87L+pW46+GjZ4X9560ZXUCTe/LCaIh
Udib0GfQug2SBhRz1JPLlyoAnFxODLz6FVL88kRu2hFKbgifLy3j+ao6hnO2RlNYyIkFvYMRuHM/
qgeN9EJN50CdHDcCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQQFAAOBgQAm
SCwWwlj66BZ0DKqqX1Q/8tfJeGBeXm43YyJ3Nn6yF8Q0ufUIhfzJATj/Tb7yFkJD57taRvvBxhEf
8UqwKEbJw8RCfbz6q1lu1bdRiBHjpIUZa4JMpAwSremkrj/xw0llmozFyD4lt5SZu5IycQfwhl7t
UCemDaYj+bvLpgcUQg==
-----END CERTIFICATE-----
FIM
$ cat > $HOME/.certs/Thawte_Premium_Server_CA.pem << FIM
-----BEGIN CERTIFICATE-----
MIIDIDCCAomgAwIBAgIENd70zzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJVUzEQMA4GA1UE
ChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2VydGlmaWNhdGUgQXV0aG9yaXR5
MB4XDTk4MDgyMjE2NDE1MVoXDTE4MDgyMjE2NDE1MVowTjELMAkGA1UEBhMCVVMxEDAOBgNVBAoT
B0VxdWlmYXgxLTArBgNVBAsTJEVxdWlmYXggU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0eTCB
nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwV2xWGcIYu6gmi0fCG2RFGiYCh7+2gRvE4RiIcPR
fM6fBeC4AfBONOziipUEZKzxa1NfBbPLZ4C/QgKO/t0BCezhABRP/PvwDN1Dulsr4R+AcJkVV5MW
8Q+XarfCaCMczE1ZMKxRHjuvK9buY0V7xdlfUNLjUA86iOe/FP3gx7kCAwEAAaOCAQkwggEFMHAG
A1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEQMA4GA1UEChMHRXF1aWZheDEtMCsGA1UE
CxMkRXF1aWZheCBTZWN1cmUgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMBoG
A1UdEAQTMBGBDzIwMTgwODIyMTY0MTUxWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUSOZo+SvS
spXXR9gjIBBPM5iQn9QwHQYDVR0OBBYEFEjmaPkr0rKV10fYIyAQTzOYkJ/UMAwGA1UdEwQFMAMB
Af8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUAA4GBAFjOKer89961
zgK5F7WF0bnj4JXMJTENAKaSbn+2kmOeUJXRmm/kEd5jhW6Y7qj/WsjTVbJmcVfewCHrPSqnI0kB
BIZCe/zuf6IWUrVnZ9NA2zsmWLIodz2uFHdh1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee95
70+sB3c4
-----END CERTIFICATE-----
FIM

Hora de dizer ao openssl que temos os certificados:
$ c_rehash $HOME/.certs/

OK, tudo pronto para configurar o .msmtprc.



1.2 - Configurando o .msmtprc


Bom, criados os certificados, é hora de fazer nosso arquivo de configuração.
Para evitar confusão entre o que é variável, função, comando e dados a serem preenchidos, o arquivo será configurado para o Zé abaixo.
Mude para seus próprios dados.
Eis, aí o Zé:
Endereço no gmail:zeh@gmail.com
Senha do gmail:53nH*
USER: zeh
HOSTNAME:NAVI

E seu arquivo de configuração:
$ cat > $HOME/.msmtprc << FIM
account default
host smtp.gmail.com
port 587
from zeh@gmail.com
tls on
tls_starttls on
#tls_trust_file /home/zeh/.certs/Thawte_Premium_Server_CA.pem
tls_trust_file /home/zeh/.certs/Equifax_Secure_CA.pem
auth on
user zeh@gmail.com
password 53nH*
logfile ~/.msmtp.log
FIM
$ chmod 600 $HOME/.msmtprc

Note que o chmod é muito importante já que não queremos que outros usuários possam ler nossa senha.
Vamos testar para ver se deu tudo certo:
$ echo -e "Subject: Teste 1\n\nCan you read me?" | msmtp zeh@gmail.com

Veja em sua caixa de entrada se o e-mail chegou.
Em casa ele leva menos de 5 segundos para enviar o e-mail.

Agora que já podemos enviar os e-mails, vamos ver como lê-los.



2 - Lendo e-mails com o wget


Há muitas formas diferentes de ler e-mails pelo bash, eu vou usar uma função pouco explorada do gmail, são os Atom Feeds.
Eles podem ser naturalmente acessados via https através de seu login e senha. No caso do Zé é só jogar a URL abaixo no navegador:

https://zeh:53nH*@mail.google.com/mail/feed/atom

O mesmo efeito pode ser obtido sem um navegador através do auxílio do wget:

$ /usr/bin/wget --secure-protocol=TLSv1 --timeout=3 -t1 -q -O - https://zeh:53nH*@mail.google.com/mail/feed/atom --no-check-certificate

Reparou que a saída está em XML? Se o e-mail enviado for bem padronizado será fácil analisá-lo. Nosso objetivo é fazer algo como:

Subject: NAVI has booted, check your IP
NAVI was booted at 2010-09-12 23:51:15
NAVI IP=207.135.111.25

Um grep 'IP=' no comando acima bastaria para retornar
<summary>NAVI was booted at 2010-09-12 23:51:15 NAVI IP=207.135.111.25</summary>

Mas este sed consumirá muito menos processos:
sed '/NAVI\ IP/!d;s/.*=//g;s/<.*//g;q'

Ele se divide em quatro partes:
/NAVI\ IP/!dNão apague a linha que contiver o padrão NAVI(espaço)IP
s/.*=//gApague tudo do 'igual' para trás
s/<.*//gApague tudo do início da tag '<' para frente
qPegue apenas a primeira linha (o último e-mail) e saia (quit)
Por fim, para não ficar com a senha em um script, simplemente compilei a chamada em C em um arquivo naviip.c:
#include <stdio.h>
#include <stdlib.h>

int main() {

    system( "/usr/bin/wget --secure-protocol=TLSv1 --timeout=3 -t1 -q -O - https://zeh:53nH*@mail.google.com/mail/feed/atom --no-check-certificate | sed '/NAVI\ IP/!d;s/.*=//g;s/<.*//g;q'" );
    return 0;
}

Compile assim:
$ gcc naviip.c -Wall -ansi -o naviip

Teste o programa:
$ ./naviip

De preferência jogue-o em algum lugar do seu PATH.
$ echo $PATH

No Slackware o diretório $HOME está incluído.
Pessoalmente gosto de:
$ mkdir -pv $HOME/bin
$ echo -e 'PATH=$PATH:$HOME/bin\nexport PATH' >> $HOME/.bashrc
$ source $HOME/.bashrc
e vou supô-lo de agora em diante.



2.1 - Acessando o servidor remoto


É claro que não queremos manipular o endereço nós mesmos, um código deve fazer isso.
É mais rápido, é mais fácil.

Este script deve lhe ajudar:
$ curl http://pastebin.com/download.php?i=upi4gMG4 | sed 's/.$//' > $HOME/bin/sshome && chmod 700 $HOME/bin/sshome

Repare que por muita ou pouca paranóia eu configurei meu SSH para escutar atrás de uma porta não-padrão. Normalmente usamos a porta 22, escolhi outra aleatória, a 2247.
Seria interessante fazer um script no servidor para alterar aleatoriamente esta porta e informá-la no e-mail, melhor ainda seria usar o iptables para fazer um Port-Knocking! (TODO ;^)
Repare também que o cliente só tentará se conectar no servidor se realmente houver um SSH escutando atrás daquela porta, o que evita a tentativa infrutífera de acesso a outra máquina que, pelos motivos mais diversos, está agora com nosso antigo IP.



3 - Criando o script de checagem e envio


No Slackware para que um script seja executado por último lugar no boot, duas coisas são necessárias:
  1. Que ele seja chamado pelo /etc/rc.d/rc.local
  2. Que ele seja executável

Adicione as linhas abaixo em seu /etc/rc.d/rc.local como root
if [ -x /etc/rc.d/rc.mailMyIp ]; then
   . /etc/rc.d/rc.mailMyIp start
fi

E vamos criar o rc.mailMyIp.
Há 3 técnicas que gostaria de mencionar:
  1. Na função MSG() envio os dados para um arquivo. Talvez você prefira comparar o IP atual com o que está no arquivo.
    No dia-a-dia achei mais fácil comparar sempre com o que está legível no e-mail, vai que você deleta a mensagem...
  2. Eu obtenho o IP externo na linha 45:
    IP=`curl -s http://myip.com.br/index2.php | sed '/Seu\ IP/!d;s/.*\IP://g;s/<.*//g;s/\ //g'`
    Basicamente uso o curl para acessar um site que informa meu IP e parseio o endereço da mesma forma que fizemos com o e-mail.
  3. A estratégia para enviar o e-mail é a seguinte:
    1. A placa de rede deve ter um endereço válido (em casa é 192.x.y.z, por isso a linha 52);
    2. Então na linha 57 damos 3 tiros de ping (-l3 -w1) para o google e contamos quantos chegam.
      1. Dê preferência a usar o IP, porque em caso de DNS inacessível, o tempo de espera cai bastante. Caso o DNS esteja DISPONÍVEL há um ganho de 17% (OK, 0.192s em média).
      2. Se nenhum ping chegar, não tem internet. Desencane do resto.
    3. Se o ping chegou, busque o IP externo.
    4. Pegue o IP lá do e-mail e compare os dois.
    5. Se forem iguais não há nada a fazer.
    6. Se forem diferentes envie o e-mail.
    7. A versão start é para ser usada no boot, a versão check pelo cron.



3.1 - O script rc.mailMyIp


Preciso deixar colorido o que é para mudar?
Comece pelo ADDRS.
Cheque a linha 24.
Onde você pôs seu naviip (linhas 80, 106)?
O que você acha posso fazer para melhorar isso?
Uma cópia deste script está aqui: http://pastebin.com/B4YHkiHA

7 comentários:

  1. Salve mano!

    Seu script é bem mais completo do que o meu, obrigado!

    Um único detalhe é que o myip.com é bom quando se está usando um router ou AP, mas quando se tem uma conexão dial-up ou pppoe, podemos pescar o IP usando ifconfig, grep, awk, que é tudo local (mais rápido e econômico).

    Talvez possamos intergrar isso no teu script com um .conf ou uma variável "routed=on,off" que habilita um modo ou outro.

    Como disse, apenas um detalhe! Eu acho que a coisa certa mesmo é você debugar a coisa e começar a produzir rpms e debs do seus scripts, assim quem quiser pode clicar e usar.

    []s,
    Callado.

    ResponderExcluir
  2. @Renato, você tem razão quanto ao router e as opções --router --bridge dão conta desse caso. Eu tenho um outro código para gerenciar meu modem, mas de todas as opções eu só usava mesmo duas:

    modem reboot
    modem ip

    O reboot é interessante porque o modem fica na sala e o escritório no fundo da casa (você sabe ;^). Mas o `modem ip` demora o dobro do tempo que buscar "fora".
    Em modo bridge é trivialmente mesmo e bem mais rápido. É legal a ideia dos pacotes, valeu.

    []s.

    ResponderExcluir
  3. Só reparei agora que esqueci de falar do cron.
    Você certamente vai querer que o script rode de tempos em tempos. O comando "rc.mailMyIp check" roda silenciosamente e lh'envia um IP sempre que encontrar um IP desatualizado em seu e-mail ou não encontrar IP algum.
    Adicione o comando acima em seu crontab.
    No Slackware há diretórios específicos para serem executados de hora em hora, diariamente, semanalmente e mensalmente; isso deixa o crontab mais limpo.
    Se este for seu caso crie o script mailMyIp.watchdog no diretório /etc/cron.hourly:

    #!/bin/bash
    /etc/rc.d/rc.mailMyIp check

    Por fim, torne-o executável
    # chmod u+x /etc/cron.hourly/mailMyIp.watchdog

    Para fazer as verificações ele leva cerca de 4 segundos. Se precisar enviar um novo e-mail também, 9 segundos.

    []s

    ResponderExcluir
  4. Pessoalmente prefiro colocar no cron um script "spawner" que lê de um arquivo texto o nome dos scripts que ele deve executar periodicamente.

    A vantagem disso é que o arquivo texto pode ser rw por um usuário comum (e certamente 600 pois senão alguém que obtiver suas creds pode injetar algo que será executado como root!) e assim eu posso escolher meus daemons sem precisar dar sudo ou su.

    Hmm. Me parece overkill para um sistema só, mas acredite, se você administra um conjunto de muitas máquinas, é mais fácil ter um spawner em cada uma, que você nunca mexe, e uma lista de scripts em que você mexe direto.

    ResponderExcluir
  5. Muito interessante sua técnica.
    Obrigado por compartilhar.
    Só fiquei com uma dúvida, a dificuldade da sua user account ser crackeada não é menor que a do root?

    Voltando a mexer no script consegui diminuir em 2 segundos o tempo de execução mudando as linhas 45 e 57.
    Então agora são 2 segundos para fazer todas as checagens e 7 segundos se ele precisar enviar o e-mail.

    45 IP=`curl -s http://www.whatismyip.com/automation/n09230945.asp`
    57 PING_COUNT=`$PING -l3 -w1 -c3 200.160.4.2 2> /dev/null | $SED '/rec/!d;s/.*ted,\ //g;s/\ .*//g'`

    As mudanças são as seguintes, na linha 45 acessamos uma página que não precisa ser parseada e na linha 57, ao invés de pingar a Google, pingamos a CGI.br que tem um ping time 4.5 vezes menor para os brasileiros.

    Obs: descobri a URL da linha 45 lendo o funcoeszz do Aurelio "Verde".

    ResponderExcluir
  6. Baixe a versão corrigida aqui:

    < http://pastebin.com/R7vWtxEm >

    PS: Cuidado que o blogspot zoa todos os caracteres especiais, mesmo escapeados.

    ResponderExcluir
  7. A dificuldade de crackear uma conta depende de restrições de sistema e da força da senha.

    Se o ssh não aceita login de root, certamente que esta conta é mais segura que a de usuário normal.

    Por outro lado, se o root pode logar por ssh, um atacante pode não saber nenhum username do sistema, mas sabe que o sistema tem um usuário root.

    Finalmente, se a senha de usuário é forte e a de root é fraca, é mais fácil crackear a de root.

    Por outro lado, se uma conta de usuário já foi crackeada, ou se o oponente é um usuário comum, o problema se torna um de "escalada" de privilégios indevida.

    Dito isso, meu comentário foi para indicar a facilidade de mexer num script como usuário normal, sabendo que em poucos minutos ele será executado em N máquinas como root. Bem mais fácil do que ficar dando sudo, e mais seguro (no sentido de foolproof) do que ficar logado como root.

    ResponderExcluir