Showing posts with label connection. Show all posts
Showing posts with label connection. Show all posts

Thursday, April 07, 2016

Error -76719 / Erro -76719

If you see this error, you're most likely using a J2EE application server, and you have a firewall between it and your database (original version http://informix-technology.blogspot.com/2015/02/error-76719-erro-76719.html)




English version
I've found this error many times. And recently it happened again while working on a customer. Invariably it happens on application servers with a firewall between them and the database. The error description is:

-79716    System or internal error.

An operating or runtime system error or a driver internal error
occurred.

The accompanying message describes the problem.

Often the error also shows this message:

java.net.SocketException: Broken pipe

If you find this, most likely you have a firewall between the database and the application server and your firewall is forcing the closing of the sockets. Why would it do that? Typically the firewalls are configured to close connections they consider "idle" for a period of time. And usually in an application server, using connection pooling it may happen that a specific connection is not used for many minutes. Without traffic in the connection the firewall will decide the connecting is "forgotten" and will close it. To be honest this is the part I never was able to figure out... when a system decides to close a socket, a signal (13) is usually sent to the endpoints. Upon receiving the signal, the client and server learn the connection was broken and they can create another one, or raise an appropriate error. The problem I'm used to, is that the client (in this case the J2EE application server) never receive (or ignore?) that signal. So they don't "know" the socket is not there anymore. Instead they keep the socket reference associated with a connection that is part of their pools. Once a client application requests a connection from the pool, it may get one of these "phantom" connections. When it tries to send the SQL request, the exception is generated and the error is raised.
All this explains the problem. But what we really want is the solution. And that is a bit more complex to explain but trivial to implement. In fact there are two solutions:

  1.  Have you network administrators change the firewall rules either to increment the period before considering a connection as stale, or to ignore Informix connections. I never saw a situation where this happened. Somehow the network admins act like dictators and they refuse any change.
  2. Force some sort of traffic through the connection so that the firewall doesn't think the connection is stale
As mentioned, the first option is usually not considered possible. And the second is easy to implement. Thankfully the operating systems already have a mechanism to test the connections which is called "keepalive" or "keepalive probes". The idea is very simple. At specific intervals the system sends a "probe" (special packet) to the other side and wait until either a response is received or a timeout is reached. Keep in mind this is an OS mechanism. And as you would expect it requires a few things to work:
  1. That the OS "decides" to activate the mechanism (as we'll see we can direct Informix to ask for it)
  2. That we configure the "interval" at which a "probe" will be sent
  3. That we configure the maximum time to wait for an answer
  4. Eventually that we configure the number of probes to send before assuming the connection is not good if no answer is received
Typical defaults imply that keepalive is not used, and that the interval is 7200 seconds (2H). Not all operating systems have the timeout and number of probes as parameters.. But I mentioned that there is a way to instruct Informix to request for the keepalive mechanism on it's sockets. Currently we can do it on the server side and on the client (JDBC) side:
  • On the server side just adding a "k=1" to the $INFORMIXSQLHOSTS options field
  • On the client (JDBC) side just add the property IFX_SOC_KEEPALIVE and set it to true. Note that this is available only on version 4.10.JC6+
Keep in mind that these options only request the OS to activate the keepalive functionality. The intervals and timeouts must be configured at the operating system level where the Informix functionality is activated. Informix does not send any "fake" traffic by itself.

Another option on some application servers would be to "test" the connections (usually requires some dummy and simple query), or define a maximumage for the connections. But these configurations depend on your application server 

A final note: Sometimes we need to check if an opened socket is using the keepalive option. In most operating systems we can use the lsof command with the option "-Tf". This adds the socket options used to open the socket (or added later) to the output. But this option does not work on Linux. In this OS we can use the netstat command with the "-o" option:


castelo@primary:informix-> netstat -no | grep ":10000"
tcp        0      0 192.168.142.202:10000       192.168.142.202:33053       ESTABLISHED keepalive (5332.90/0/0)
tcp        0      0 192.168.142.202:33053       192.168.142.202:10000       ESTABLISHED keepalive (5332.88/0/0)
castelo@primary:informix->
 


The number shown between parenthesis is the counter till the sending of the next probe.





Versão Portuguesa
Encontrei este erro muitas vezes. E voltou a acontecer novamente enquanto estava num cliente. Invariavelmente isto acontece quando existe um firewall entre um servidor aplicacional (normalmente J2EE) e a base de dados. A descrição do erro é:

-79716    System or internal error.

An operating or runtime system error or a driver internal error
occurred.

The accompanying message describes the problem.

Muitas vezes aparece também a seguinte mensagem:

java.net.SocketException: Broken pipe

Se encontrar isto, a probabilidade é que exista um firewall entre o servidor aplicacional e a base de dados que esteja a fechar os sockets de comunicação. Porque o haveria de fazer? Porque tipicamente os firewalls estão configurados para fecharem as ligações que consideram inativas por um determinado período de tempo. E é normal que num servidor aplicacional que utilize connection pooling uma determinada conexão pode não ser usada durante largos minuto. Sem tráfego na ligação o firewall irá considerá-la como "esquecida" e fechá-la-á. Para ser honesto esta é a parte que nunca percebi.... Quando um sistema quebra uma ligação (por interrupção física da comunicação por exemplo) a aplicação associada ao socket normalmente recebe um sinal (13). Ao receber este sinal a aplicação sabe que a conexão foi fechada e pode tomar as ações apropriadas, como recriá-la ou gerar um erro.
O problema a que me habituei é que o cliente (neste caso o servidor aplicacional J2EE) nunca recebe (ou porventura ignora?) o sinal. Por isso não "sabe" que o socket já não existe. E portanto mantém uma referência a um socket que já não existe na sua pool de conexões. Quando uma aplicação pede uma conexão dessa pool pode receber a referência "fantasma". E ao tentar usá-la para enviar uma instrução SQL para a base de dados é gerada a exceção com o erro referido.
Tudo isto explica o problema. Mas o que queremos realmente é resolvê-lo. E isso é um pouco mais complicado de explicar, mas simples de implementar. De facto há duas soluções:

  1. Fazer com que os administradores de rede mudem as regras do firewall para aumentar o período de inatividade aceitável ou para ignorar completamente as conexões ao informix. Nunca presenciei uma situação onde tal acontecesse. Por algum motivo que me ultrapassa os administradores de rede atuam como ditadores e recusam qualquer mudança
  2. Forçar o envio de algum tipo de tráfego através de cada conexão para que o firewall não a considere "inativa"
Como referido, a primeira opção normalmente não é considerada. E a segunda é fácil de implementar. Felizmente os sistemas operativos já dispõem de um mecanismo que testa as conexões, o qual é chamado de "keepalive" ou "keepalive probes". A ideia é muito simples. De tempos a tempos o sistema envia uma "sonda" (pacote especial) para a outra ponta da conexão e espera por uma resposta ou que seja atingindo um timeout. Não nos esqueçamos que isto é um mecanismo do sistema operativo. E para tal há que configurar algumas coisas para que funcione adequadamente:
  1. Que o SO "decida" ativar o mecanismo para determinadas conexões (como veremos podemos configurar o Informix para requisitar isto ao SO)
  2. Que se configure o "intervalo" de teste das ligações (quando serão enviadas as "sondas")
  3. Que se configure o tempo máximo para receção da resposta
  4. Eventualmente que se configurem o número de "sondas" a serem enviadas e que fiquem sem resposta antes de o SO considerar a ligação como "morta"
Por omissão, muitas vezes o mecanismo de keepalive não é utilizado. E os intervalos frequentement estão definidos para 7220s (2H). Nem todos os sistemas operativos usam os parâmetros para timeouts e retries.
Atrás referi que é possível forçar o Informix a activar este mecanismo no SO. Atualmente podemos fazer isso no lado do servidor e no lado do cliente (JDBC)::
  • No lado do servidor basta acrescentar a opção "k=1" no campo das opções no $INFORMIXSQLHOSTS
  • No cliente (JDBC) basta adicionar a propriedade IFX_SOC_KEEPALIVE com o valor true. Note-se que isto só está disponível nas versões 4.10.JC6+
Saliento que estas opções só pedem ao SO que ative a funcionalidade de keepalive. Os intervalos e timeouts serão configurados com ferramentas do sistema operativo. O Informix nunca envia "falso" tráfego por ele próprio.

Outra opção em alguns servidores aplicacionais seria "testar" as conexões (normalmente com uma query simples e rápida), ou definir uma idade máxima para as conexões. Mas estas configurações dependem de servidor para servidor.

Uma nota final: Por vezes é necessário perceber ou verificar se um socket aberto tem mesmo a opção de keepalive ativa. Na maioria dos sistemas operativos podemos usar o comando lsof com a opção "-Tf". Isto acrescenta as flags usadas na abertura do socket. Mas esta opção não funciona em Linux. Neste SO pode usar-se o "netstat" com a opção "-o":

castelo@primary:informix-> netstat -no | grep ":10000"
tcp        0      0 192.168.142.202:10000       192.168.142.202:33053       ESTABLISHED keepalive (5332.90/0/0)
tcp        0      0 192.168.142.202:33053       192.168.142.202:10000       ESTABLISHED keepalive (5332.88/0/0)
castelo@primary:informix->
 


O número entre parenteses é o contador até ao envio da próxima "sonda"

Thursday, February 13, 2014

DNS changes? Ok... / Mudanças no DNS? Ok...

This article is written in English and Portuguese (original version here)
Este artigo está escrito em Inglês e Português (versão original aqui)

English version:
For the blog followers, you probably know I have a special feeling for PAM. I've written several articles about it, and I've been working on other two articles which I hope will be very interesting. I just love PAM flexibility and what we can do with it. During this work, an idea popped up and not even me would have thought that I could be using PAM for something that is not related to authentication. Confused? So was I.
As I mentioned I was messing around with PAM tests that involve a customized module and suddenly I realized I could solve one of the most annoying problems we have in Informix by using PAM!
On a previous article (DNS impact on Informix - a very extensive and detailed article that received a lot of positive feedback) I showed that we cannot change DNS configuration without stopping Informix to assume the new configuration. This is a natural consequence of how the operating system functions work, but applications can overcome that. Informix currently doesn't. That's why I created RFE 33797 (Request For Enhancement). I urge you to vote for it. It's currently classified as "Under consideration".
On that same article I showed that if we managed to get the MSC VP(s) to run a standard C function, it would solve the problem, as the next connection would require the application (Informix) to re-read the resolver configuration. Well... guess what MSC processors also run? Yep... The PAM module's functions are run inside the MSC VP process. So... What's this crazy idea? Simple: create a dummy PAM authentication module that when called runs the referred function. This will clear that process cache and next authentication requests will follow the new DNS configuration. What we need:

  • The PAM module source (I provide that)
  • A C compiler and a few header files
  • Administrator (root) privilege to configure the PAM service
  • Configure a new engine listener (DBSERVERALIAS) for PAM authentication.
So let's start with the source code. It's listed below, and I'll just explain a few aspects referring to their line numbers:
  • Let's start by lines 64 till the end. These are just functions that every PAM module needs to contain. But as we're not going to use them (at least seriously) we just create empty ones that return "ok"
  • The only function that we'll really need is pam_sm_authenticate() which will be called when we try to authenticate against a listener using this port. It starts on line 10
  • Lines 24-34 just serve to get a module parameter that represents a log file. If that parameter is not used, or if it contains a file name that cannot be written to or created, then we'll just use a default in "/tmp". The use for this log file will become clearer ahead
  • Line 39 is the really important stuff. This will clear all the resolver configuration that is cached inside each process that calls resolver routines. I should test the result of calling it and adjust the logging... but keep in mind this is a demo...
  • Lines 45-57 are just used to get a timestamp and the PID of the MSC VP where we're running, and write it to the log file. Again the importance of this will be discussed ahead
  • Finally on line 62 we return an error. As I mentioned, this is meant to be a dummy module which purpose is to solve a limitation. But as it must be configured as an authentication mechanism for a listener I think it's safer if it always refuses the authentication
So, assuming we have this in a source file called pam_clear_dns_cache.c we can compile it with:


pam@primary:informix-> gcc -shared -fPIC -o pam_clear_dns_cache.so pam_clear_dns_cache.c
pam@primary:informix-> ls -lia pam_clear_dns_cache.so
81342 -rwxr-xr-x 1 informix informix 8092 Feb 12 17:40 pam_clear_dns_cache.so
pam@primary:informix->


Then we need to copy it to the system PAM module location. My test environment is a 64bit Linux so this will be:


pam@primary:informix-> cp -p pam_clear_dns_cache.so /lib64/security/
pam@primary:informix-> ls -lia /lib64/security/pam_clear_dns_cache.so
1212609 -rwxr-xr-x 1 informix informix 8092 Feb 12 17:40 /lib64/security/pam_clear_dns_cache.so
pam@primary:informix->


Then we need to setup $INFORMIXSQLHOSTS with a new entry:


pam_clear_dns_cache onsoctcp 127.0.0.1 20002 s=4,pam_serv=(ids_pam_clear_dns_cache),pamauth=(challenge),k=1


I used localhost for added security (in case the database server can only be accessed by DBAs and SysAdmins). Then create the service file in /etc/pam.d:

pam@primary:root-> cat ids_pam_clear_dns_cache
auth required pam_clear_dns_cache.so /tmp/ids_pam_clear_dns_cache.txt
account required
pam_clear_dns_cache.so
pam@primary:root->


As usual we just need two entries. "auth" where we use our module, and "account" where for simplicity I used the same module (remember those other functions that return "success"?)

Ok... so now let's check if it works.

Step 1

I have my system configured to use DNS and then the files. It's not a typical setup, but it suites our test needs. In /etc/resolv.conf I have a DNS address where there is no DNS (192.168.142.2).
I'll start the engine and I'll find out the MSC VP PID with onstat -g glo:


Individual virtual processors:
vp pid class usercpu syscpu total Thread Eff
1 26703 cpu 0.42 0.43 0.85 1.52 55%
2 26704 adm 0.00 0.02 0.02 0.00 0%
3 26705 lio 0.00 0.00 0.00 0.02 0%
4 26706 pio 0.00 0.00 0.00 0.00 0%
5 26707 aio 0.00 0.01 0.01 0.07 13%
6 26708 msc 0.00 0.00 0.00 19.76 0%
7 26709 fifo 0.00 0.00 0.00 0.02 0%
8 26712 soc 0.00 0.00 0.00 NA NA
tot 0.42 0.46 0.88


on another session I'll trace the MSC VP with:


strace -T -o strace_step1.txt -p 26708


while I try to connect from a remote server. The trace includes this:


pam@primary:root-> head -20 strace_step1.txt
semop(2818050, 0x1c39a90, 1)            = 0 <18.944722>
socket(PF_INET, SOCK_DGRAM, IPPROTO_IP) = 3 <0.000022>
connect(3, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("192.168.142.2")}, 28) = 0 <0.000018>
fcntl(3, F_GETFL)                       = 0x2 (flags O_RDWR) <0.000011>
fcntl(3, F_SETFL, O_RDWR|O_NONBLOCK)    = 0 <0.000011>
poll([{fd=3, events=POLLOUT}], 1, 0)    = 1 ([{fd=3, revents=POLLOUT}]) <0.000012>
sendto(3, "\376m\1\0\0\1\0\0\0\0\0\0\003115\003142\003168\003192\7in-"..., 46, MSG_NOSIGNAL, NULL, 0) = 46 <0.000081>
poll([{fd=3, events=POLLIN}], 1, 5000)  = 0 (Timeout) <4.990392>
poll([{fd=3, events=POLLOUT}], 1, 0)    = 1 ([{fd=3, revents=POLLOUT}]) <0.000019>
sendto(3, "\376m\1\0\0\1\0\0\0\0\0\0\003115\003142\003168\003192\7in-"..., 46, MSG_NOSIGNAL, NULL, 0) = 46 <0.000540>
poll([{fd=3, events=POLLIN}], 1, 5000)  = 0 (Timeout) <4.998902>
close(3)                                = 0 <0.000024>
open("/etc/hosts", O_RDONLY)            = 3 <0.000025>


Notice the connect to the server I have defined. And notice the poll() call takes around 5s to return by timeout. So, we're in a situation where our DNS stopped working and we want to fix it. This situation (5s delay) would happen to any request.

Step 2

Now, assuming we want to exchange our DNS servers we would edit the /etc/resolv.conf. I'll put the working DNS (192.168.142.1) there and will repeat the trace. The new trace is identical:

pam@primary:root-> strace -T -o strace_step2.txt -p 26708
Process 26708 attached - interrupt to quit
Process 26708 detached
pam@primary:root-> head -20 strace_step2.txt
semop(2818050, 0x1c39a90, 1)            = 0 <11.093218>
socket(PF_INET, SOCK_DGRAM, IPPROTO_IP) = 3 <0.000019>
connect(3, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("192.168.142.2")}, 28) = 0 <0.000110>
fcntl(3, F_GETFL)                       = 0x2 (flags O_RDWR) <0.000009>
fcntl(3, F_SETFL, O_RDWR|O_NONBLOCK)    = 0 <0.000009>
poll([{fd=3, events=POLLOUT}], 1, 0)    = 1 ([{fd=3, revents=POLLOUT}]) <0.000011>
sendto(3, "\254/\1\0\0\1\0\0\0\0\0\0\003115\003142\003168\003192\7in-"..., 46, MSG_NOSIGNAL, NULL, 0) = 46 <0.000017>
poll([{fd=3, events=POLLIN}], 1, 5000)  = 0 (Timeout) <5.002838>
poll([{fd=3, events=POLLOUT}], 1, 0)    = 1 ([{fd=3, revents=POLLOUT}]) <0.000099>
sendto(3, "\254/\1\0\0\1\0\0\0\0\0\0\003115\003142\003168\003192\7in-"..., 46, MSG_NOSIGNAL, NULL, 0) = 46 <0.000076>
poll([{fd=3, events=POLLIN}], 1, 5000)  = 0 (Timeout) <4.999880>
close(3)                                = 0 <0.000058>
open("/etc/hosts", O_RDONLY)            = 3 <0.000013>

Step 3

So, let's do some magic.... I'm going to start the listener that uses our customized "dummy" PAM module, and I'll try to connect to it:


pam@primary:informix-> onmode -P start pam_clear_dns_cache
pam@primary:informix-> onstat -m | tail -3
19:38:19 Starting listen thread for sqlhosts server pam_clear_dns_cache
19:38:29 Listen thread init SUCCESS

pam@primary:informix-> dbaccess - -
> CONNECT TO "@pam_clear_dns_cache" USER "fnunes";
ENTER PASSWORD:

1809: Server rejected the connection.
Error in line 1
Near character position 1
>


As expected, the authentication fails. But that's not the purpose of... Let's check the log file we defined in the module configuration:


pam@primary:root-> cat /tmp/ids_pam_clear_dns_cache.txt
2014-02-12 19:42:10 resolver caches cleared for PID (26708)
pam@primary:root->

Good... It says it cleared the resolver caches for the PID we know it's the MSC VP.
So let's try to connect now and see what happens:


pam@primary:root-> strace -T -o strace_step3.txt -p 26708
Process 26708 attached - interrupt to quit
^CProcess 26708 detached
pam@primary:root-> head -20 strace_step3.txt
semop(2818050, 0x1c39a90, 1)            = 0 <12.664204>
socket(PF_INET, SOCK_DGRAM, IPPROTO_IP) = 4 <0.000031>
connect(4, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("192.168.142.1")}, 28) = 0 <0.000033>
fcntl(4, F_GETFL)                       = 0x2 (flags O_RDWR) <0.000014>
fcntl(4, F_SETFL, O_RDWR|O_NONBLOCK)    = 0 <0.000016>
poll([{fd=4, events=POLLOUT}], 1, 0)    = 1 ([{fd=4, revents=POLLOUT}]) <0.000231>
sendto(4, "=B\1\0\0\1\0\0\0\0\0\0\003115\003142\003168\003192\7in-"..., 46, MSG_NOSIGNAL, NULL, 0) = 46 <0.000195>
poll([{fd=4, events=POLLIN}], 1, 5000)  = 1 ([{fd=4, revents=POLLIN}]) <0.106848>
ioctl(4, FIONREAD, [123])               = 0 <0.000022>
recvfrom(4, "=B\201\203\0\1\0\0\0\1\0\0\003115\003142\003168\003192\7in-"..., 1024, 0, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("
192.168.142.1")}, [16]) = 123 <0.000022>
close(4)                                = 0 <0.000030>
open("/etc/hosts", O_RDONLY)            = 4 <0.000031>


As you can see, it opened the socket to the new DNS, got the answer pretty quick (this one is working), but as it does not recognize the client it still went to look into the /etc/hosts file.

Conclusions

So, what have we done? We effectively overcome an Informix limitation by using a PAM module. The trick here is that we managed to execute the function res_init() inside the MSC VP process. We took advantage of the fact that PAM module functions are executed in this virtual processor. But there is a catch... We can have more than one MSC VP and we can't control to which one our connection attempt will be scheduled to. So, if you happen to have more than one MSC VP processor you may have to do several connection attempts so that you get all of them to run the function. I'd say most customers are running just one but you should keep this in mind.

Keep in mind that this is a workaround. I hope the request for enhancement will be implemented by IBM. But if/when it does, it will probably be implemented only in the latest version. So if you have an older one you can use this. The decision to leave the listener running, or even start it by default on every instance is up to the reader. It will not accept any connections. And it can be just a local listener (127.0.0.1).

This has been fun to implement and it may be helpful. But as usual, the code and setup comes with a standard disclaimer. This is not IBM official information. Test it if you think about using it. And use at your own risk.


Versão Portuguesa:

Quem segue o blog já deve ter percebido que tenho um sentimento especial pelo PAM. Já escrevi vários artigos sobre o tema e tenho estado a trabalhar em mais dois que espero sejam interessantes. Simplesmente adoro o PAM pela flexibilidade e pelo que se pode fazer com ele.. Durante este trabalho surgiu-me uma ideia, e nem eu me lembraria que um dia iria usar o PAM para algo que não está relacionado com autenticação. Confuso? Também eu fiquei!
Como referi estava a "brincar" um pouco com testes de PAM que envolvem um módulo costumizado e de repente apercebi-me que poderia resolver um dos problemas mais irritantes que temos no Informix através do PAM. Num artigo anterior (impacto do DNS no Informix - um artigo extenso e detalhado que recebeu bastante feedback positivo) eu mostrei que não podemos mudar as configurações de DNS sem parar o Informix para assumir a nova configuração. Isto é uma consequência natural da forma como as funções de resolução de nomes do sistema operativo funcionam, mas as aplcações podem contornar o problema. O Informix atualmente não o faz. Daí ter registado o RFE 33797 (request for enhancement). Apelo a que vote nele. Está neste momento classificado como "Under Consideration).
Nesse mesmo artigo, mostrei que se conseguirmos que os MSC VP(s) executem uma função standard (res_init) isso resolveria o problema, pois a próxima tentativa de conexão obrigaria o Informix a reler as configurações do sistema de resolução de nomes. Bom... adivinhe o que é que os MSC VPs também executam? Sim... as funções dos módulos PAM configurados no motor são executadas pelos processos da classe MSC. Então... Qual é a ideia maluca? Simples: criar um módulo PAM "dummy" que quando chamado, execute a referida função. Isto irá limpar a cache que esse processo fez das configurações e no próximo pedido de autenticação as novas configurações serão lidas e ativadas. O que precisamos:
  • O código fonte do módulo PAM (eu forneço isto)
  • Um compilador de C e alguns header files
  • Privilégios de administrador (root) para configurar o serviço PAM
  • Configurar um novo listener do motor (DBSERVERALIAS) para autenticação PAM
Vamos começar com o código fonte do módulo. Está listado abaixo e irei explicar alguns aspectos com recurso às linhas apresentadas:
  • Vamos começar pela linha 64, até ao fim. Todas estas funções são obrigatórias num módulo, mas como as não vamos usar (pelo menos de forma séria), vamos criá-las apenas retornando "ok"
  • A única função que vamos realmente usar é a pam_sm_authenticate() que será chamada cada vez que nos tentarmos autenticar por este módulo. Começa na linha 10
  • As linhas 24-34 servem apenas para obter um parâmetro do módulo que deve representar um ficheiro de log. Se o parâmetro não for dado ou se contiver um caminho que não possa ser escrito ou criado, então usaremos um ficheiro em /tmp. A vantagem deste ficheiro ficará clara mais adiante
  • A linha 39 é o mais importante. Esta chamada deverá limpar do processo a "memória" das configurações do sistema de resolução de nomes. Deveria testar o resultado da chamada e ajustar a informação do log de acordo... mas isto é apenas uma demonstração
  • As linhas 45-57 permitem apenas recolher um timestamp e o PID do processo em que estamos a executar para escrever no log. Mais uma vez a importância de o fazer ficará mais clara adiante
  • Finalmente, na linha 62 retornamos um erro standard do PAM. Como mencionei, isto pretende ser um módulo dummy cujo único propósito é resolver uma limitação.Mas dado que tem de ser configurado como mecanismo de autenticação, parece-me mais seguro que recuse sempre a autenticação
Assim, assumindo que temos um ficheiro com o código fonte chamado pam_clear_dns_cache.c podemos compilar o módulo com:

pam@primary:informix-> gcc -shared -fPIC -o pam_clear_dns_cache.so pam_clear_dns_cache.c
pam@primary:informix-> ls -lia pam_clear_dns_cache.so
81342 -rwxr-xr-x 1 informix informix 8092 Feb 12 17:40 pam_clear_dns_cache.so
pam@primary:informix->


Depois necessitamos de o copiar para a localização standard dos módulos PAM na nossa plataforma. No meu caso estou a usar um Linux de 64bits, portanto será:


pam@primary:informix-> cp -p pam_clear_dns_cache.so /lib64/security/
pam@primary:informix-> ls -lia /lib64/security/pam_clear_dns_cache.so
1212609 -rwxr-xr-x 1 informix informix 8092 Feb 12 17:40 /lib64/security/pam_clear_dns_cache.so
pam@primary:informix->


Prosseguimos, com a configuração do $INFORMIXSQLHOSTS com uma nova entrada:


pam_clear_dns_cache onsoctcp 127.0.0.1 20002 s=4,pam_serv=(ids_pam_clear_dns_cache),pamauth=(challenge),k=1


Usei o localhost para segurança reforçada (caso o servidor de base de dados só possa ser acedido por DBAs e administradores de sistema). Depois configuramos o ficheiros do serviço PAM em /etc/pam.d:

pam@primary:root-> cat ids_pam_clear_dns_cache
auth required pam_clear_dns_cache.so /tmp/ids_pam_clear_dns_cache.txt
account required
pam_clear_dns_cache.so
pam@primary:root->


Como sempre necessitamos de duas entradas:  "auth" onde usamos o nosso módulo e "account" onde por simplicidade utilizei o mesmo módulo (lembra-se daquelas funções "inúteis" que retornavam "sucesso"?)

Bom... Mas temos de verificar se funciona

Passo 1

Tenho o meu sistema configurado para procurar primeiro nos DNS e depois nos ficheiros. Não é uma configuração habitual, mas ajusta-se aos testes que queremos fazer. No ficheiro /etc/resolv.conf tenho um endereço de DNS onde não existe DNS ativo (192.168.142.2)
Vou iniciar o motor e ver o PID do MSC VP com o comando onstat -g glo:


Individual virtual processors:
vp pid class usercpu syscpu total Thread Eff
1 26703 cpu 0.42 0.43 0.85 1.52 55%
2 26704 adm 0.00 0.02 0.02 0.00 0%
3 26705 lio 0.00 0.00 0.00 0.02 0%
4 26706 pio 0.00 0.00 0.00 0.00 0%
5 26707 aio 0.00 0.01 0.01 0.07 13%
6 26708 msc 0.00 0.00 0.00 19.76 0%
7 26709 fifo 0.00 0.00 0.00 0.02 0%
8 26712 soc 0.00 0.00 0.00 NA NA
tot 0.42 0.46 0.88


numa outra sessão faço um trace ao MSC VP com:


strace -T -o strace_step1.txt -p 26708


enquanto tento conectar-me de um servidor remoto. O trace incluí isto:


pam@primary:root-> head -20 strace_step1.txt
semop(2818050, 0x1c39a90, 1)            = 0 <18.944722>
socket(PF_INET, SOCK_DGRAM, IPPROTO_IP) = 3 <0.000022>
connect(3, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("192.168.142.2")}, 28) = 0 <0.000018>
fcntl(3, F_GETFL)                       = 0x2 (flags O_RDWR) <0.000011>
fcntl(3, F_SETFL, O_RDWR|O_NONBLOCK)    = 0 <0.000011>
poll([{fd=3, events=POLLOUT}], 1, 0)    = 1 ([{fd=3, revents=POLLOUT}]) <0.000012>
sendto(3, "\376m\1\0\0\1\0\0\0\0\0\0\003115\003142\003168\003192\7in-"..., 46, MSG_NOSIGNAL, NULL, 0) = 46 <0.000081>
poll([{fd=3, events=POLLIN}], 1, 5000)  = 0 (Timeout) <4.990392>
poll([{fd=3, events=POLLOUT}], 1, 0)    = 1 ([{fd=3, revents=POLLOUT}]) <0.000019>
sendto(3, "\376m\1\0\0\1\0\0\0\0\0\0\003115\003142\003168\003192\7in-"..., 46, MSG_NOSIGNAL, NULL, 0) = 46 <0.000540>
poll([{fd=3, events=POLLIN}], 1, 5000)  = 0 (Timeout) <4.998902>
close(3)                                = 0 <0.000024>
open("/etc/hosts", O_RDONLY)            = 3 <0.000025>


Repare no connect() ao servidor que tenho configurado. E depois o poll() demora cerca de 5s a retornar por timeout. Portanto estamos a reproduzir uma situação onde o nosso DNS tenha deixado de funcionar, e queremos "arranjar" isso. Esta situação (atraso de 5s) aconteceria em cada tentativa de conexão.

Passo 2

Agora, assumindo que queremos trocar os nossos servidores DNS, editaríamos o ficheiro /etc/resolv.conf. Vou colocar o endereço do servidor DNS activo (192.168.142.1) e vou repeitr o trace durante uma nova conexão. O novo trace está igual:

pam@primary:root-> strace -T -o strace_step2.txt -p 26708
Process 26708 attached - interrupt to quit
Process 26708 detached
pam@primary:root-> head -20 strace_step2.txt
semop(2818050, 0x1c39a90, 1)            = 0 <11.093218>
socket(PF_INET, SOCK_DGRAM, IPPROTO_IP) = 3 <0.000019>
connect(3, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("192.168.142.2")}, 28) = 0 <0.000110>
fcntl(3, F_GETFL)                       = 0x2 (flags O_RDWR) <0.000009>
fcntl(3, F_SETFL, O_RDWR|O_NONBLOCK)    = 0 <0.000009>
poll([{fd=3, events=POLLOUT}], 1, 0)    = 1 ([{fd=3, revents=POLLOUT}]) <0.000011>
sendto(3, "\254/\1\0\0\1\0\0\0\0\0\0\003115\003142\003168\003192\7in-"..., 46, MSG_NOSIGNAL, NULL, 0) = 46 <0.000017>
poll([{fd=3, events=POLLIN}], 1, 5000)  = 0 (Timeout) <5.002838>
poll([{fd=3, events=POLLOUT}], 1, 0)    = 1 ([{fd=3, revents=POLLOUT}]) <0.000099>
sendto(3, "\254/\1\0\0\1\0\0\0\0\0\0\003115\003142\003168\003192\7in-"..., 46, MSG_NOSIGNAL, NULL, 0) = 46 <0.000076>
poll([{fd=3, events=POLLIN}], 1, 5000)  = 0 (Timeout) <4.999880>
close(3)                                = 0 <0.000058>
open("/etc/hosts", O_RDONLY)            = 3 <0.000013>

Passo 3

Vamos então fazer a magia.... Vou iniciar um listener que usa o nosso módulo PAM, e vou tentar ligar-me ao mesmo:

pam@primary:informix-> onmode -P start pam_clear_dns_cache
pam@primary:informix-> onstat -m | tail -3
19:38:19 Starting listen thread for sqlhosts server pam_clear_dns_cache
19:38:29 Listen thread init SUCCESS

pam@primary:informix-> dbaccess - -
> CONNECT TO "@pam_clear_dns_cache" USER "fnunes";
ENTER PASSWORD:

1809: Server rejected the connection.
Error in line 1
Near character position 1
>


Tal como esperado, a autenticação falha. Mas não era esse o seu propósito... Vamos verificar o ficheiro de log que definimos na configuração do módulo:


pam@primary:root-> cat /tmp/ids_pam_clear_dns_cache.txt
2014-02-12 19:42:10 resolver caches cleared for PID (26708)
pam@primary:root->

Bom... Diz que limpou a cache das funções para o PID que sabemos ser o do nosso MSC VP.
Portanto vamos tentar nova ligação e ver o que acontece com o trace:

pam@primary:root-> strace -T -o strace_step3.txt -p 26708
Process 26708 attached - interrupt to quit
^CProcess 26708 detached
pam@primary:root-> head -20 strace_step3.txt
semop(2818050, 0x1c39a90, 1)            = 0 <12.664204>
socket(PF_INET, SOCK_DGRAM, IPPROTO_IP) = 4 <0.000031>
connect(4, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("192.168.142.1")}, 28) = 0 <0.000033>
fcntl(4, F_GETFL)                       = 0x2 (flags O_RDWR) <0.000014>
fcntl(4, F_SETFL, O_RDWR|O_NONBLOCK)    = 0 <0.000016>
poll([{fd=4, events=POLLOUT}], 1, 0)    = 1 ([{fd=4, revents=POLLOUT}]) <0.000231>
sendto(4, "=B\1\0\0\1\0\0\0\0\0\0\003115\003142\003168\003192\7in-"..., 46, MSG_NOSIGNAL, NULL, 0) = 46 <0.000195>
poll([{fd=4, events=POLLIN}], 1, 5000)  = 1 ([{fd=4, revents=POLLIN}]) <0.106848>
ioctl(4, FIONREAD, [123])               = 0 <0.000022>
recvfrom(4, "=B\201\203\0\1\0\0\0\1\0\0\003115\003142\003168\003192\7in-"..., 1024, 0, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("
192.168.142.1")}, [16]) = 123 <0.000022>
close(4)                                = 0 <0.000030>
open("/etc/hosts", O_RDONLY)            = 4 <0.000031>


Como se pode verificar, abriu um socket para o novo DNS, obteve a resposta como era de esperar e rapidamente (este está activo), mas como o cliente nao é conhecido no servvidor prossegiu para pesquisar no ficheiro /etc/hosts.

Conclusões

Então, o que fizemos? Efectivamente superámos uma limitação do Informix através de um módulo PAM.
O truque é que conseguimos que a função res_init() fosse executada dentro do processo de um MSC VP. Tirámos proveito do faco de os módulos PAM serem executados neste processador virtual. Mas há um senão.... Podemos ter mais que um MSC VP, e não podemos controlar para qual é que a nossa tentativa de conexão vai parar. Portanto se por acaso usar mais de um MSC VP poderá ter de repetir as tentativas de autenticação até que todos eles executem a função. Diria que a maioria dos clientes apenas usam um MSC VP, mas terá de ter este ponto em mente. O log é particularmente útil para isto.

Considere isto um workaround. Espero que o pedido de melhoria venha a ser implementado pela IBM. Mas se/quando o for será muito provavelmente apenas na última versão. Portanto se usar uma mais antiga pode optar por este mecanismo. A decisão de deixar o novo listener a correr ou até mesmo de o levantar logo com o motor será uma decisão do leitor. Não autorizará nenhuma autenticação. E pode ser configurado como local (127.0.0.1)

Isto foi divertido de implementar, e pode ser útil. Mas como é habitual, o código e a configuração vêm com um termo de desresponsabilização. Isto não é informação oficial da IBM. Teste se pensar em usar. E use por sua conta e risco

The code / O código

1  #include <time.h>
2  #include <string.h>
3  #include <stdio.h>
4  #include <security/pam_modules.h>
5  #include <resolv.h>

Saturday, April 18, 2009

Informix authentication and connections

The idea for this post comes from a recent discussion on c.d.i and also from checking the URL referrers that bring people here.
Most of this blog visits comes from Google or other search engines. And apparently there are a lot of questions about how Informix authenticates and establishes user connections.

I will try go give a generic overview on these subjects. I'll point out a few less known aspects of Informix authentication.
This article in no way tries to substitute the official documentation, but I hope this can be used as a concentrated information resource about the subject.


Authentication versus privileges

First, we have to create a distinction between two steps involved in a connection establishment.
As we all know, IDS and other databases have several privileges required for doing any action in the database.
At the first level we have Database level privileges. These are CONNECT, RESOURCE and DBA. Then we have object level privileges. For tables we have INSERT, DELETE, ALTER, REFERENCE and for column level we have SELECT and UPDATE
The list above is not exhaustive. The privileges relevant for this article are the database level ones. In particular the CONNECT privilege. Without it we will not be able to connect.
Privileges can be given using the SQL stament GRANT and removed with the REVOKE. Object level privileges can be GRANT'ed to individual users or to ROLEs. You can imagine ROLEs as groups and you can GRANT a role to individual users.

But let's get back on track. The purpose here is not to explain the privilege infra-structure, but to explain how do we connect to the database. I just explained the privileges because we need to go through two steps for establishing a connection:

  1. First Informix has to make sure we are who we pretend to be. This is called the authentication phase
  2. Second, Informix will check if the user we are defining in the connection has the required privileges to establish the connection.
    It cannot do the second step without first completing the authentication.
    To be able to connect we need at least CONNECT privilege on the database level. This privilege can be granted specifically to the user or to the special role PUBLIC which means "anyone"

User repositories

One very important aspect of Informix is that it doesn't use the concept of database only users. Any user in an Informix database must be recognized by the underlying OS (up to the current version at the time of writing which is IDS 11.50.xC3).
It's essential to know this in order to understand how it works. For example, if you want to create a user for Informix Dynamic Server usage, you have to create it in the OS or other repository, but you must make the OS aware of that user. And for users created in the OS, if you need to change their password you must do it using the OS tools.

Some people may consider this a big limitation, but the subject is a bit more complex than what it may look at first.
There are reasons for this requirement:
  • In an Informix stored procedure, you can call an OS command or script. This is done using the SYSTEM() SPL statement. This command will be run with the user identity that was used to establish the database connection.
    Other databases execute these commands as a specifically configured user or as the user running the database software. The way Informix works can give you a lot of flexibility, but the price for that is the need to have the user recognized at the OS level
  • When we ask the database server to print out the explain of a query using the SET EXPLAIN SQL instruction, Informix will create a file on the database server. The information is written by the database instance processes, so it has to be written on the server running the database.
    This files are written with the user ID used for the connection. The file is written on the current client directory for connections local to the database server and on the users $HOME for remote connections. Again, this requires a user created in the OS
  • The SET DEBUG FILE/TRACE ON SPL instructions have the same behavior and requirements

The above doesn't necessarily means we need to have the users in /etc/passwd (Unix/Linux). It means that getpwnam() must be able to retrieve the user info. This will happen transparently if you configure your system to use LDAP or NIS authentication.
So you can have a central user management infra-structure like LDAP, MS Active Directory or NIS. Informix also doesn't require the user to be able to establish a session (ssh or telnet for example) on the database server at the OS level. You can for example set the users Shell to /bin/false or use other means to prevent the users to connect to the OS.
To complicate things a little bit more, we should not confuse the need to have the user id recognized in the OS with the authentication mechanism. Since 9.40 Informix can use PAM for authentication. This gives you complete freedom to implement complex and sophisticated authentication methods. You can use whatever PAM modules you desire to implement the authentication, which means your Informix authentication doesn't have to be the same as your OS authentication.
But the user has to be known by the OS due to the reasons presented before. As a quick example, you can create your users with random passwords in the OS (not known to anyone) and configure IDS to authenticate your users based on files, or any remote process using PAM.

The fact that the user has to be known in the OS is generally accepted as an inconvenience, and as such I believe it's safe to assume that in a future version Informix will relax these requirements. It could probably accept connections from users not recognized by the OS (either authenticated internally or with PAM or both) and have some parameter to define a default user for the situations where it needs a user id.


Types of connections

Informix implements the following types of connections:
  • Trusted connections
    These connections are based on trusted relations between hosts. Basically the database server host is configured to trust the user if he connects from a specific host or list of hosts.
    As such, this connections can be established without providing a password or any other authentication mechanism. The way it works is exactly the same as the "r" services (rshell, rcmd, rlogin).
    The configuration is done using the network connectivity files, /etc/hosts.equiv and ~/.rhosts

  • Non trusted connections
    These connections are the standard type of connections. Typically we provide a user and a password that is used to check the identity. The password is checked against the user's stored password


  • Challenge/response based authentication using PAM modules
    Connections made to PAM configured ports (DBSERVERALIAS). PAM stands for Plugin Authentication Modules and can be used in any Unix/Linux system (AIX, HP-UX, Linux and Solaris)

  • Distributed query connections
    These connections can be considered trusted connections. But they're established implicitly when a client connected to instance "A" sends a query referencing instance "B".
    In these situation the IDS server "A" will make an implicit connection on behalf of the user, to IDS server "B". The connection authentication will follow the rules for implicit connections, or will use a different authentication mechanism if server "B" is setup with PAM authentication (more on this later)

Non trusted connections

We use non-trusted connections when we give a user id and a token (typically a password) that guarantees our identity (only the user should know it's personal authentication secret or password) . Meaning we have not only the user id, but also it's secret key.
These connections are used mostly in applications that use JDBC, ODBC, .NET, PHP (PDO), Perl (DBI) etc. These APIs require a connection string or URL, or use specific API connection functions. So we define the password in the string, or we provide is as an argument for the API connection function.
Here's an example of a JDBC URL to connect to an Informix instance:

jdbc:informix-sqli://ids_server_machine:9088/my_database;user=username;password=secret

In ESQL/C (embedded SQL/C - which is basically C language with SQL commands) or Informix 4GL we can use the CONNECT instruction:

CONNECT TO database@ids_instance USER <"username"|variable_user> USING <variable_password>


One important note, that allows me to show the two steps in a connection establishment: authentication and database open:
Typically in 4GL we use a "database test_auth" instruction. This, by default, makes the two steps by trying to make a trusted connection using the owner of the process.
But let's create a simple 4GL program to show the difference between authentication and the database opening phase. Here's the code (don't worry if you don't know 4GL, because these lines are self explanatory):

DEFINE username,password char(20)
DEFINE c CHAR

MAIN
PROMPT "Insert your username: " FOR username
PROMPT "Insert your password: " FOR password ATTRIBUTE ( INVISIBLE )
CONNECT TO "@cheetah2" USER username USING password
PROMPT "We are authenticated, but not yet connected to a database..." FOR CHAR c
DATABASE test_auth

PROMPT "Now we have an opened database!" FOR CHAR c
END MAIN


So, we're asking the user's name and password. Than we're CONNECTing to the server. Note that I didn't use a database name. This is a supported syntax, although not very frequently used.
After the connect, we then issue the usual "DATABASE" instruction. Let's see what happens when we run the program above. First the status of the IDS instance showing the existing sessions:

cheetah2@PacMan.onlinedomus.net:informix-> onstat -u

IBM Informix Dynamic Server Version 11.50.UC2 -- On-Line -- Up 09:52:57 -- 88064 Kbytes

Userthreads
address flags sessid user tty wait tout locks nreads nwrites
4780c018 ---P--D 1 informix - 0 0 0 69 559
4780c5f0 ---P--F 0 informix - 0 0 0 0 1032
4780cbc8 ---P--F 0 informix - 0 0 0 0 596
4780d1a0 ---P--- 5 informix - 0 0 0 0 0
4780d778 ---P--B 6 informix - 0 0 0 48 0
4780e328 ---P--- 17 informix - 0 0 1 315 224
4780e900 ---P--D 9 informix - 0 0 0 3 0
4780eed8 ---P--- 16 informix - 0 0 1 1286 1093
4780f4b0 ---P--- 15 informix - 0 0 1 189 3
47810060 Y--P--D 21 informix - 440cfe28 0 0 0 0
10 active, 128 total, 18 maximum concurrent


So... Only informix user system sessions. Now let's run the program (I'll do it as root):

cheetah2@pacman.onlinedomus.net:root-> ./test.4ge
Insert your username: fnunes

Insert your password: [......][ENTER]

We are authenticated, but not yet connected to a database...


The program is now holding at the first "PROMPT" instruction after the connect. Let's see the list of sessions:

cheetah2@PacMan.onlinedomus.net:informix-> onstat -u

IBM Informix Dynamic Server Version 11.50.UC2 -- On-Line -- Up 09:57:30 -- 88064 Kbytes

Userthreads
address flags sessid user tty wait tout locks nreads nwrites
4780c018 ---P--D 1 informix - 0 0 0 69 559
4780c5f0 ---P--F 0 informix - 0 0 0 0 1032
4780cbc8 ---P--F 0 informix - 0 0 0 0 596
4780d1a0 ---P--- 5 informix - 0 0 0 0 0
4780d778 ---P--B 6 informix - 0 0 0 48 0
4780e328 ---P--- 17 informix - 0 0 1 315 224
4780e900 ---P--D 9 informix - 0 0 0 3 0
4780eed8 ---P--- 16 informix - 0 0 1 1286 1093
4780f4b0 ---P--- 15 informix - 0 0 1 189 3
4780fa88 Y--P--- 61 fnunes 4 485bff38 0 0 0 0
47810060 Y--P--D 21 informix - 440cfe28 0 0 0 0
11 active, 128 total, 18 maximum concurrent

cheetah2@PacMan.onlinedomus.net:informix-> onstat -g ses 61

IBM Informix Dynamic Server Version 11.50.UC2 -- On-Line -- Up 09:57:39 -- 88064 Kbytes

session effective #RSAM total used dynamic
id user user tty pid hostname threads memory memory explain
61 fnunes - 4 15267 pacman.o 1 40960 37280 off

tid name rstcb flags curstk status
85 sqlexec 4780fa88 Y--P--- 5728 cond wait netnorm -

Memory pools count 1
name class addr totalsize freesize #allocfrag #freefrag
61 V 483c3028 40960 3680 68 7

name free used name free used
overhead 0 1672 scb 0 96
opentable 0 784 filetable 0 192
misc 0 64 log 0 16512
temprec 0 1608 gentcb 0 1232
ostcb 0 2632 sqscb 0 8192
sql 0 40 hashfiletab 0 280
osenv 0 1720 sqtcb 0 2208
fragman 0 48

sqscb info
scb sqscb optofc pdqpriority sqlstats optcompind directives
488a18a0 48558018 0 0 0 2 1

Sess SQL Current Iso Lock SQL ISAM F.E.
Id Stmt type Database Lvl Mode ERR ERR Vers Explain
61 - - - Not Wait 0 0 9.29 Off


So what do we see? We have a session, on behalf of user "fnunes" but the "Current Database" is not defined. So we've just gone through the authentication process with success.
Now, if I press ENTER on the program I'll force the program to execute the DATABASE statement and wait on the second PROMPT:

cheetah2@pacman.onlinedomus.net:root-> ./test.4ge
Insert your username: fnunes

Insert your password: [...] [ENTER]

We are authenticated, but not yet connected to a database... [ENTER]
Now we have an opened database!

ok... now let's look at the database session again:

IBM Informix Dynamic Server Version 11.50.UC2     -- On-Line -- Up 10:09:32 -- 88064 Kbytes

session effective #RSAM total used dynamic
id user user tty pid hostname threads memory memory explain
61 fnunes - 4 15267 pacman.o 1 49152 45544 off

tid name rstcb flags curstk status
85 sqlexec 4780fa88 Y--P--- 5728 cond wait netnorm -

Memory pools count 1
name class addr totalsize freesize #allocfrag #freefrag
61 V 483c3028 49152 3608 83 6

name free used name free used
overhead 0 1672 scb 0 96
opentable 0 1792 filetable 0 352
misc 0 64 log 0 16512
temprec 0 1608 gentcb 0 1232
ostcb 0 2632 sqscb 0 13712
sql 0 40 rdahead 0 832
hashfiletab 0 280 osenv 0 1720
sqtcb 0 2792 fragman 0 208

sqscb info
scb sqscb optofc pdqpriority sqlstats optcompind directives
488a18a0 48558018 0 0 0 2 1

Sess SQL Current Iso Lock SQL ISAM F.E.
Id Stmt type Database Lvl Mode ERR ERR Vers Explain
61 - test_auth CR Not Wait 0 0 9.29 Off


So, now we have a current database, in the same session.
This ends the description of non trusted connections using username and passwords. Pretty simple, just like in any other database server.


Challenge/response connections using PAM

Another kind of non trusted connections are connections established through PAM configured DBSERVERALIAS (specific ports where the instance will listen for connections).
I will not go into details about this, because I already wrote an article about it. In http://informix-technology.blogspot.com/2007/11/informix-user-authentication-pam-for.html I explain how to setup and give examples of challenge/response scenarios.

In summary, a non-trusted connection in Informix is similar to connections in other RDBMS. You supply a username and a token that certifies you're entitled to connect as that user.
Typically this token is a password. But with PAM this can be a much more complex item.
In IDS 11.50 we can also use single sign on with kerberos. Due to the complexity of this scenario I've left it out in this article.
Non trusted connections are used mostly in more recent applications, like Web based applications, Windows client applications, or script languages (PHP, Perl, Ruby etc.) languages.

Trusted connections

These connections are made without any token to confirm the user identity. It's assumed that the user is already identified on the client system and we configure the database server to trust that identification. Trusted connections are mostly used in applications created using Informix 4GL and Informix ESQL/C.
The definition of the trusts are done by configuration of the so called network security files. These files are /etc/hosts.equiv and the ~/.rhosts (.rhosts created in each users's home dir).
Before we dig into these files content, and what it means I have to make a parenthesis to deal with something I'd almost call a myth surrounding Informix trusted connections. If you look around on the Internet you'll easy find a lot of information about how insecure is to use these files. In particular, you may found explicit instructions to not use the ~/.rhosts file.
The main reasons for these are two facts:
  1. These files are not specific for Informix, and in fact they were created to configure a group of services, sometimes called the "r" services. These services are rexec (allows remote execution of a command) on TCP port 512, rlogin (allows remote login) on TCP port 513 and rcmd (all remote shell execution) on TCP port 514.
  2. Many of these services are considered insecure and have a long history of security flaws. The most famous worm also took advantage of some of these security flaws, and also from very relaxed security setup on many of the networks connected to the Internet at the time.

Why did I referrer before the "myth"? Well, many customers have the idea that since IDS uses the same files for trusted connections authentication that you need to be running the above services for it to work. This is completely false. I cannot stress this enough. Even in the official documentation, the administrator guide, is written:

To determine whether a client is trusted, execute the following statement on the client computer: rlogin hostname If you log in successfully without receiving a password prompt, the client is a trusted computer.


Although this is not wrong, it misses the point, and gives the idea that the rlogin service should be running. The above sentence, saying that if rlogin works the user is trusted, is true. But you can create a trust for an Informix connection without having the service(s) running. In this case you'll get a "can't connect error" instead of the password prompt when you run rlogin. And it doesn't mean it won't work with Informix.
I strongly believe Informix should not use these same files for defining it's trust relationships. But the concern that this usage raises is not really an issue. If you're concerned about security, you probably will not use the "r" services. Most of the sites which take security seriously have replaced them with ssh or something similar. So the truth is that these files should be useless in a modern system. And in this scenario, the fact that they're used by Informix should not be considered too critical, since nothing else should depend on them.


I hope this contributes to the disappearance of the "myth". Now let's see how we have to change the network security files in order to create the trusts.
The contents of the files should be similar to how you would configure "r" services trusts. Let's see the two files, because there some slight but very important differences.
  • /etc/hosts.equiv
    Each line in this file defines a trust relation. You can specify a host only, or a host and a user. This file is a system file. No user other than system administrator should have permission to change it.
    If you specify only the host name, you're saying that any user from the remote host is trusted to connect as the same user in the local host. If you specify a "+" (plus sign) this means "any host" and you should never do that...
    There is a significant difference between how Informix and "r" services interpret the file if you specify a hostname followed by a user name. For "r" services, this means that you trust the remote user on the remote system to connect as ANY user in the local system (except root). This is obviously a very serious issue... And by the way, "+ +" means any user from anywhere can login locally as any user! (don't try this even at home :) )
    Informix on the other hand interprets this as a way to specify user by user and not all the users. The remote user cannot connect as another local user without providing a username and password.
  • ~/.rhosts
    This file also includes a trust relation in each non comment line. This file, being in the user home directory, means that you allow the user on the local host to define who he trusts. Many systems verify that the file has no write permissions to the "world". In this case it ignores the settings.
    A line with a host and a username means that the specified username on the specified remote host can connect as the local user owning the .rhosts file. So, for "r" services, if you specify a host and user in /etc/hosts.equiv it means that user can connect as any local user. If you do the same in ~/.rhosts you're reducing the scope of local users that the remote user can authenticate as, to only the file owner
    For Informix, the interpretation is the same: The remote user can authenticate on the local machine, but without changing it's identity. It means that for example, if the local user is luser and it's ~/.rhosts contains "remotehost ruser", then this entry is useless because Informix doesn't have a way to specify the identity change in a connection attempt.

Some additional notes on these files:
  • You can specify negative entries. For example you could use the following in /etc/hosts.equiv:

    pacman -informix
    pacman

    This would allow all users from host "pacman" to connect except informix
  • An entry with a host name means "trust all users except root". A trust relation for root user has to be defined in it's .rhosts file.
  • The order of the entries is relevant. If we exchange the order of the entries in the example above, even informix will be allowed to connect. That's because a positive entry will be found first


Let's see some examples. I have two hosts (pacman and ids1150srvr). I'm running IDS on the ids1150srvr host. If I want to allow the user informix to connect to this instance from the pacman host I can do it in the following ways (these file entries are on the ids1150srvr host):

  • /etc/hosts.equiv:

    pacman
    pacman informix

    The first entry allows all users (except root) to connect from pacman. The second will allow only informix. But be aware that if you're running "r" services you're saying that informix on pacman can connect as any user (again, except root) on ids1150srvr.

  • ~informix/.rhosts

    pacman

Distributed query connections

We've seen a client/server trusted connection. But we have another situation where we must establish trusts between systems. That's the case when you need to join data from two Informix instances. The SQL syntax to do this will look like this:

SELECT
local_table.column, remote_table.column
FROM
table1 local_table, remote_database@remote_ids_instance:table2 remote_table
WHERE
local_table.join_column = remote_table.join_column;

Assuming we are connected to a "local_ids_instance" on ServerA, and that "remote_ids_instance" is running on ServerB, this query will establish a connection on "remote_ids_instance" from the "local_ids_instance". Informix will establish this connection on behalf of our user on "local_ids_instance". As such, our user must be trusted on ServerB when connecting from ServerA. Note that the trust is not done between our client and the ServerB. It's between ServerA and ServerB. If the "remote_ids_instance" is accessed through a normal Informix port, the authentication mechanism used is the same as for trusted connections.

The situation will be different if the "remote_ids_instance" port is configured with PAM. In this situation the trust configuration is done entirely through SQL instructions. IDS versions that support PAM also have a new system database. It's called sysuser. In it we have a table called sysauth with the following schema:


{ TABLE "informix".sysauth row size = 322 number of columns = 4 index size = 295 }
create table "informix".sysauth
(
username char(32) not null ,
groupname char(32),
servers varchar(128) not null ,
hosts varchar(128) not null ,
check (groupname IS NULL )
);
revoke all on "informix".sysauth from "public" as "informix";


create unique index "informix".sysauth_idx on "informix".sysauth (username,servers,hosts) using btree ;



This table is used to define trusts for distributed queries when the destination Informix instance is configured for PAM authentication.
We must specify the username, the hostname and the IDS server from where the connection is made. Note that this not only avoids the usage of the network security files but also provides more control. With the network security files we cannot specify the Informix instance originating the connection.

So, picking up the query above, and assuming the username is "fnunes" we would need the following configuration on ServerB for it to work:
  • Using /etc/hosts.equiv one of the following lines:

    ServerA
    # this would create a trust for all users from ServerA
    ServerA fnunes
    # This would restrict the trust in Informix for user fnunes.
    # But for "r" services it would allow fnunes to login as any user except root on ServerB

  • Using ~fnunes/.rhosts
    ServerA

  • Using the sysuser:sysauth table (if the port was configured for PAM):
    INSERT INTO sysauth ( username, groupname, servers, hosts) VALUES ('fnunes', NULL, 'local_ids_instance', 'ServerA');
Additional notes about sysauth:
  • Although the field names "servers" and "hosts" suggest you could use a list of servers or hosts the documentation says we should create one entry per user/server/host
  • The field "groupname" is not used currently as expected from the check constraint




Additional notes

There are a few less none configuration details about connections in Informix. Let's check them.
  • There are some options that we can specify in the sqlhosts file that control the way IDS deals with trusted connection attempts (for the specific ALIAS of the sqlhosts line).The options I'm referring to are "s=X". "s" stands for security on the server side. The following values are supported:
    • s=0
      Disables both hosts.equiv and rhosts lookup from the database server side (only incoming connections with passwords are accepted).
    • s=1
      Enables only the hosts.equiv lookup from the database server side.
    • s=2
      Enables only the rhosts lookup from the database server side.
    • s=3
      Enables both hosts.equiv and rhosts lookup on the database server side (default setting for the database server side).
    • s = 6
      Marks the port/ALIAS as a replication only (HDR or ER) port
      If you use the option s=6 on sqlhosts options field, than you can create a file called hosts.equiv in $INFORMIXDIR/etc, owned by informix:informix and only modifiable by user informix. This makes the informix user independent of the system administrator for configuring the trusts, and additionally doesn't interfere with "r" services if they're in use
  • There is another security option that defines if the client uses or not the .netrc file. I haven't yet mentioned the .netrc file... You can use this file so specify a user and password, per host that you want to connect. If you setup the file and try to make a trusted connection you'll instead make a non-trusted connection. An example of this file content would be:

    ids1150srvr login fnunes password mysecret

    This would connect you as user "fnunes" using password "mysecret" if you attempt to make a trusted connection to an Informix instance running on host "ids1150srvr". You can have several lines, one for each host you connect to.
    There are obvious security issues derived from having a password in a file...
    The option that specifies if the client libraries will look and use this file is the "r=X" settings on the sqlhosts options (client side):
    • r=0 it ignores the .netrc file
    • r=1 it looks at the .netrc file when a client program tries to make a connection without using username and password

Windows specific information

In MS Windows, the network security files are used in a similar way, but they're in different locations. /etc/hosts.equiv becomes %SYSTEMROOT%\system32\drivers\etc\hosts.equiv.
The .rhosts and .netrc should exist in the users HOME

Common error messages

The following is not an exhaustive list of connection errors, but a small list of the more common ones with a very brief comment. For more information about each one consult the documentation or use "finderr errorcode":
  • -908 Attempt to connect to database server (servername) failed.
    Usually means some problem on the TCP level. Either your server is not running or you have your client SQLHOSTS points to the wrong server or port
  • -930 Cannot connect to database server servername.
    Typically the client cannot obtain the server IP address. Check your DNS configuration.
  • -951 Incorrect password or user user@host is not known on the database
    You can get this error on the server side (online.log) and on the client side.
    On the server side it means the user does not exist.
    On the client side you'll see these error in three situations.. The same error is sent to the client side on different situations in case someone is trying to guess a user or password. The database system administrator can see the real problem. The situations where the error is sent to the client are:
    • The user does not exists on the database server
    • The user's password cannot be validated, although the user exists
    • The user exists, but you're trying to make a trusted connection and the trust is not setup correctly
  • -952 User ()'s password is not correct for the database server.
    This is the error given on the server side when the user password did not validate successfully
  • -956 Client client-name or user is not trusted by the database server.
    Server side error when a trusted connection fails because it's not properly setup
  • -1809 Server rejected the connection.
    This is the only error you'll see when a connection to a PAM configured server happens. In order to understand what went wrong you have to check the PAM stack modules messages
  • -25596 The INFORMIXSERVER value is not listed in the sqlhosts file or the Reg
    The INFORMIXSERVER you're trying to connect is not properly defined in you configured SQLHOSTS
Summary

I really hope this article covers most practical aspects of connection establishment in Informix. There are several aspects I left out for simplicity. These include:

  • Single sign-on (introduced in IDS 11.50)
  • Password encryption
  • SSL connections
These topics may be covered in future articles. You can find more information about Informix connection setup in the Administrator Guide and the Security Guide.