Monday, December 09, 2013

Informix in mobile related searches / Informix em pesquisas relacionadas com mobile

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

In a recently released media statement, job search site Dice.com, made public a list of the top 20 search subjects, that hiring managers and recruiters are searching when they request people with mobile experience. Some of them cause no surprise (Androind and iOS) others may cause some surprise for the ranking where they show up (Blackberry in 4th)... But I'm sure, even in the Informix community, the 10th place is a surprise... Yes... Informix ranks in top 10, and in fact it's the only database that appears on the list.
It's possible to imagine a few reasons for this:

  • Dice.com president, Shravan Goli, suggests Informix (and .NET) may appear because companies are trying to "mobilize" their current infrastructures
  • IBM bet on NoSQL (MongoDB API compatibility) may help on this
  • IBM work porting Informix to ARM platform (in theory we may see Informix on Android in the future) may help also
In any case I feel this is very relevant for a database that accordingly to it's competitors is dead. If we consider the above thee options as possible, it shows it has a past, a present, and more important a future (which other "traditional" RDBMS can claim these two recent enhancements besides Informix?)


Versão Portuguesa

Num recente comunicado de imprensa, o site de empregos Dice.com, tornou pública uma lista de 20 termos, que gestores e recrutadores pesquisam quando procuram perfis de candidatos com experiência em mobile. Alguns desses termos não causam surpresa (Androind e iOS) outros podem causar surpresa pela posição em que aparecem (Blackberry em 4º)... Mas tenho a certeza que, mesmo para a comunidade Informix, o 10º lugar é uma surpresa... Sim... Informix aparece no top 10, e de facto é a única base de dados que aparece na lista.
É possível imaginar algumas razões para isto:
  • O presidente da Dice.com, Shravan Goli, sugere que os termos Informix (e .NET) podem aparecer porque as companhias estão a tentar "mobilizar" as suas infraestruturas atuais (ou seja desenvolver aplicações móveis que integrem com os sistemas existentes)
  • A aposta da IBM  no NoSQL (compatibilidade com a API do MongoDB)
  • O trabalho da IBM em portar o Informix para a plataforma ARM (em teoria podemos ter Informix em Android no futuro)
Em qualquer caso, entendo que isto é muito relevante para uma base de dados que de acordo com a sua concorrência está morta. Se considerar-mos as hipóteses acima como possíveis, mostram que o produto tem um passado, um presente e mais importante, um futuro (que outra RDBMS "tradicional" pode gabar-se dos dois últimos desenvolvimentos para além do Informix?)

Sunday, November 03, 2013

4GL and threads / 4GL e threads

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


English version:

Introduction

Back to a real life situation, this time with 4GL. It has some technical details that are interesting, and it's also a good example of how to debug and solve a problem (modesty apart, but as you'll see the merit is not even mine - several people contributed to this - ). It's also a perfect example of how technical support tends to work these days, and as I usually say, customers receive what they demand (less quality and less availability to help). Again, IBM technical support stood up from other companies. And yes, I'm biased, but along the article I'll show you the differences and you can judge for yourself.
I'm presenting this as a warning to any customer wanting to do something similar (which is not supported, so should be avoided) and as a possible workaround if you already did it, and face the same issues.
I will not mention other vendor's names,as I will criticize their support, and for that I will have to blur some details. Hopefully it will not prevent the understanding of the problem.

Problem description

4GL is a programming language which we can integrate with other components through the usage of C language functions. 4GL is not supposed to be thread safe. Threads can be seen as parallel or concurrent programming aids. They can more or less act as a process, but are lighter and because of that they're used in many situations. However, because a process can run several threads, they all share common address space and the operating systems don't isolate them as they do with processes (in fact, if they did, threads would loose several of their advantages). This can have bad consequences. We must be very carefully when working with a threaded programming model, because one thread can overwrite data used by other threads and this can lead to all kinds of problems. There are several techniques to achieve thread safety like code re-entrance, mutual exclusion, using local instead of global variables etc. For the record, a thread has a private stack and the context of the thread and some local variables can be stored there. So, each thread's stack should be "sacred" ground, not to be invaded by others.
Saying 4GL is not thread safe means that the internal 4GL code was not implemented with the above concepts in mind. It depends on global variables for example. But it can also do "nasty" things that affect other threads on the same process, even when those threads are not running 4GL functions and code.
In my situation, a customer linked a 4GL application with a third-party library (middleware software) that internally uses threads (it creates one thread to be exact). That library allows the 4GL application to extract/request data from other systems. It's important to note that this was done years ago (5-10 years) and it's been working without anybody noticing any issue. At some point, due to a system overload, customer decided to move a group of users to another system, running the same versions of OS, 4GL and third party integration software. Initially a few small differences between the systems were spotted, but after being eliminated the problem still persisted. On the "new" systems (in fact they're relatively old systems, running out of regular support), users started to experience a lot of application crashes. A core dump was generated and it was reported to me for analysis.
I've been working with 4GL since 1991 (wow... 22 years!), first as a programmer and after I joined Informix (1998) as a consultant, so I'd say I have a large experience with the language (although nowadays I honestly feel a  bit rusty because I don't do 4GL every day). But what I'm trying to say is that it's obviously not the first time I see a 4GL core. Typically they're caused by exceeding the array limits or because a C function with some problem was linked with the main process, or very rarely by a bug. So I usually start with a debugger (dbx for example) to extract the core's stack trace. It usually tells us where the problem happened, and we can then do some code analysis. Another option is to recompile all the modules with "-a" flag of c4gl command. It includes some array protection code that will force the program to stop if an invalid array position is used. This can slow the program, but it's a pretty good way to catch array dimensioning issues.
In this particular case, the stack trace was nearly useless because it looked "corrupted", and it only showed operating system functions related to exception handling. Meaning the OS perceived something was wrong, but while trying to generate the core it got confused. The -a option was used without success.. The crashes seem to be relatively random, in different parts of the code or user options.
A more empirical approach was followed. We picked a program that crashed frequently in some option and tried to strip it out of as many things as we could. We ended up with a much more simple test case that we could use to reproduce the crash. After a few repetitions of doing the same thing we usually got the core. We understood that the crash typically happened around reading a key. The first suspect we had was 4GL... There is a documented potential issue when some input structures (INPUT, PROMPT, MENU etc.) are used inside a loop and we exit the loop without EXITing the input structure properly. Mainly because of this (the test code resembled a brief description of the issue), we opened a PMR with IBM. That was the first time we got in touch with any of the vendors involved (IBM for 4GL, one for the OS and another for the third party library). After a bit of investigation we cleared this possibility. IBM technical support was helpful in understanding some details we gathered with a debugger. But for a while we were a bit stuck. I must explain that by then, there were three people deeply involved in this situation: I was trying a more "practical" approach to isolate the problem by changing and trying to simplify the test case; A colleague from IBM technical support who was trying to check similar issues already reported, and helping with our doubts, as he is more knowledgeable in debugging techniques; A customer team member who was trying the "scientific" approach. He spent hours running the test case on a debugger and he even looked at the assembler (and believe me, you'll never be the same after trying to understand the assembler of this old, poorly documented and complex platform).
I managed to confirm that if the application didn't call the third party function it wouldn't happen. Taking a closer look at the code that glue 4GL with the third party system I've found several problems, but fixing them didn't help. And I was getting stuck. But the customer noticed that a certain behavior in the thread status seemed to happen just before the crash. He was almost sure that the threads and/or thread management had something to do with it. With this findings in mind we noticed that on the core dump, the functions from the third party library seemed to vanish from the status of the process after the crash. So I changed the 4GL investigations from user input structures and the like to issues around threads. Needless to say neither me or my colleague could find too many situations, because 4GL is not thread safe... so people usually don't use threads. But we've been able to find a few reports:
  1. One on the same platform as our situation. But the customer had the option to link with a non threaded library, so the problem was solved without further investigation
  2. A very similar problem, related to keyboard reading, on AIX. Some OS parameters controlling thread scheduling were changed and the problem disappeared
  3. A not very well documented issue on Solaris where some workaround was implemented by IBM, but not included in the mainstream code.
It's important to recall that we had two systems where this happened and several where we could not reproduce it. The systems looked similar and the customer system administrators couldn't find any reason why the application crashed on some, but not on most of their systems. After some insistence they agreed to open a case on their operating system supplier. I explained we were looking for help in debugging the issue and that we had no indication that the cause was in the operating system.
A sort of conference call with the possibility of desktop sharing was scheduled and I showed the problem happening. During the core some cryptic message was shown... as soon as the engineer saw the message (something like "unable to unwind stack") she wrote: "That's an application issue!"... Well... Yes.. In theory I could agree with that. But it didn't help us. It didn't explain why it only happened in this system. It also didn't explain why the OS couldn't generate a "clean" core that was helpful for us... After a bit of talk, and after I insisted that what we were looking for was some more deep knowledge on the OS debugging tools and outputs, another engineer joined the call. He was much more willing to help, although at the time no magic was done...
They agreed about investigating some points I raised regarding OS behavior and thread signal handling (referenced in other IBM PMRs), and we proceed with IBM investigation and test case elaboration.

At this point the status was:
1- We were almost sure it had to do with threads (thanks to the customer team member) that allowed us to find a useful old PMR
2- We were puzzled by the fact that the same code behaved differently on different systems and asked OS supplier to help us with that
3- Since 4GL was not meant to work in a multi-thread environment we tried to verify if there was some configuration related to the third-party library that would allow us to avoid thread creation

The OS support got back to us with some more vague details, but nothing that would allow us to change thread scheduling behavior (like it was done for AIX). They couldn't also explain why it happened on some systems and not on others.
Meanwhile we got interested in the way 4GL deals with signal handling. We started to suspect that 4GL was setting up some signal handlers and that one (or more) was being executed in the context of the thread created by the library.

Given 3) above I suggested that we get more involvement of the customer team managing the third party integration tool. But because they were not able to give us any answers, we decided to open a case with their supplier's support. The questions were:
1- Is there a way to avoid the thread creation?
2- If not, is there any way to control the time it stays "idle" (we noticed the thread was mostly idle, but it was waking at regular intervals)
3- Was there any way to control the signals it would respond to?

There was just one answer, and it was staggering simple: "Sorry Mr. customer. You're using an out of support version. Please upgrade before asking any questions"
Don't get me wrong... I do understand and even sympathize with lack of support for very old software versions. But we were not asking for any code debuging, or code change. And the questions would probably be valid if it was a supported version (as per their manual, the same function interfaces were still in use in recent versions). So this answer was a bit strong. I've seen much worse situations being answered by IBM Informix support. But customer accepted it, so I couldn't do anything about it.
Basically we were a bit stuck... not much help from other tech supports.. Our technical support suggested that when reading the keyboard, 4GL was doing something to handle the special key "ESCAPE" that could cause troubles.... Very shortly, the "escape" code can mean the escape key or the beginning of an escape sequence (used in terminal emulators). So it does a "read()" and gets the escape code. After that it has a dilemma: If it's an escape sequence (starts with the escape code) the program has to read() again. But if it was just the escape key the call to read() will block. 4GL solves this by calling "alarm()" and then read() again. The alarm() call will trigger a signal (ALRM) to be sent to the process after the specified time. If by then the read() is still active, the alarm signal handler (setup by 4GL) will cancel the call and proceed. This is done by manipulating the current stack. It's a dirty trick, but it is, or at least was common a few years ago. We were able to find discussions about Apache issues because it uses the same concept (naturally not for keyboard reading).
With this in mind we decided to try a workaround by changing the 4GL internal code. I was impressed by technical support ability and willingness to try this. The mechanism above was changed for another method that avoided the second call to read() (blocking). A non blocking method was used and they created a patch (non-official). This was put into place and the program stopped crashing on that specific point. That was the good news. The bad was that it kept crashing on other places, and assuming it was caused by the signal handling outside of the 4GL thread context, it would not be possible to change it without major changes in the 4GL internal code. And it was clear that it would be very difficult to implement those, and R&D would not see that as a real possibility.

The solution

That was when a more pragmatic approach was taken. System documentation tells us that a thread B created by thread A will inherit the "signal mask" of A. The signal mask is a bit array where each bit matches a specific signal and if it's on/off the signal will/won't be received by the thread. As we don't want the third party thread to receive the signals for which 4GL sets up handlers, we can change the signal mask just before creating that thread, and restoring the initial one just after the thread creation.
IBM technical support agreed this was worth trying and provided a list of possible signals to mask.

Technically, doing this is terribly simple:

#include <sys/types.h>
#include <errno.h>
#include <signal.h>

[…]

sigset_t mask, orig_mask;


sigemptyset (&mask);  /* creates and empty mask */

/* Now let's add a few signals to the mask. Others could be considered like SIGTERM) */ 
sigaddset (&mask, SIGALRM);
sigaddset (&mask, SIGINT);
sigaddset (&mask, SIGQUIT);

/* Now use the mask prepared above to as a "blocking" mask. At the same time, the current mask is saved in orig_mask variable */ 





if ( pthread_sigmask(SIG_BLOCK, &mask, &orig_mask) != 0 ) {
     /* Handle any possible error... *//
}

/*
      Call the function that creates the thread in the third party library
*/

/* And now restore the original mask, saved in orig_mask variable */
if ( pthread_sigmask(SIG_SETMASK, &orig_mask, NULL) != 0 ) {
     /* Handle any possible error */
}

Conclusion

After applying the code above to the customer application, the problem didn't show up again. Since we're blocking the "dangerous" signals before we create the new thread, it will inherit this blocking. And after we block those signals, the handlers setup by 4GL code will not be called in the context of the "foreign" thread. They'll be run only when the context belongs to the 4GL thread.
This means the "nasty" things 4GL does to it's own stack will not ever affect the other thread(s) stack.

I'd like to point out the difference between technical support services: IBM provided a trial patch to "fix" something that works as design. Another company declined to answer some questions because we mentioned an unsupported version, although the questions were valid for currently supported versions. And another one could not explain the difference between their systems, and only after my insistence they managed to provide some details (that were not very helpful, but I grant that because the problem was not on their side this could be reasonable)

The final important note: This is a "trick". 4GL is inherently not thread safe and you shouldn't assume otherwise. But there is a difference between not being able to run two 4GL threads in the same process and having a 4GL thread with one or more non 4GL threads. You can still have problems, and this workaround may not be usable in some cases, but it can be a simple way out if you're already in the hole (like if you have been using threads inside a 4GL process and somehow things start to fail). If you're not in that hole yet, be advised not to enter it!

For some more technical details about this issue you can check the following IBM tech note:

http://www-01.ibm.com/support/docview.wss?uid=swg21642635

To grant the credits to those who deserve it, the progress on this issue would not have been possible without Rui Mourão (from the customer team) and IBM's technical support colleague Alberto Bortolan


Versão Portuguesa:

Introducão

De volta a uma situação real, desta feita com 4GL. Tem alguns detalhes técnicos interessantes, e é também um bom exemplo de como fazer debug e resolver um problema (modéstia à parte, mas como verá o mérito não é meu - várias pessoas contribuíram para isto - ). É também um exemplo perfeito de como os suportes técnicos tendem a trabalhar atualmente, e como costumo dizer, os clientes recebem aquilo que exigem (menor qualidade e menos disponibilidade para ajudar). E novamente o suporte técnico da IBM distinguiu-se de outros. E sim, sou suspeito, mas ao longo do artigo mostrarei as diferenças e poderá julgar por si mesmo.
Estou a apresentar isto como um aviso para qualquer cliente que pense em fazer algo semelhante (que não é suportado e portanto deve ser evitado), e como possível forma de contornar o problema, caso já o tenha feito e tenha os mesmos problemas.
Não mencionarei os nomes dos outros fornecedores, visto que os irei criticar pelo suporte prestado. Devido a isso terei de mascarar alguns detalhes. Espero que isso não impeça o entendimento do problema

Descrição do problema

O 4GL é uma linguagem de programação que pode ser integrada com outros componentes através da utilização de funções em linguagem C. O 4GL não é suposto ser "thread safe". As threads podem ser vistas como auxiliares de programação concorrente. De certa forma atuam como processos, mas são mais leves e devido a isso são usadas em muitas situações. No entanto, porque um processo pode correr várias threads,  todas elas partilham um espaço de endereçamento comum e os sistemas operativos não as isolam como aos processos (na verdade, se o fizessem perdiam-se as maiores vantagens que têm). Isto pode ter más consequências. Temos de ter muito cuidado ao trabalhar num modelo de programação com threads, pois uma thread pode re-escrever dados usados por outra(s) thread(s) e isto pode dar origem a problemas muito variados. Existem várias técnicas para obter segurança na gestão das threads como código re-entrant, exclusão mútua, usar variáveis locais em vez de globais etc. Para que conste, uma thread tem um stack próprio onde é guardado o contexto da thread e algumas variáveis locais por exemplo. O stack de cada thread deve ser considerado "solo sagrado" não devendo ser "invadido" por outra(s) thread(s).
Dizer que o 4GL não é thread safe significa que o código interno do 4GL não foi implementado com os conceitos anteriores em mente. Depende de variáveis globais por exemplo. Mas também pode fazer "maldades" que afetam outras threads dentro do mesmo processo, mesmo quando essas threads não executam funções e/ou código 4GL.
Nesta situação, o cliente tinha linkado uma aplicação 4GL com uma biblioteca de terceiros (software de middleware) que internamente usa threads (cria uma thread para ser exato). Essa biblioteca permite ao 4GL interagir (extraindo ou requisitando dados) com sistemas externos. É importante notar que isto foi feito há 5-10 anos atrás e tem funcionado sem que ninguém se apercebesse de qualquer problema. Em dado momento, devido a sobrecarga num sistema, o cliente decidiu distribuir alguns utilizadores para outro sistema, configurado com as mesmas versões de SO, 4GL e da biblioteca de integração. Ao início detetaram-se diferenças mínimas entre os sistemas, mas depois de eliminadas o problema persistiu. Nos sistemas "novos" (na realidade são sistemas antigos que estão já sem novos desenvolvimentos e em fim de vida) os utilizadores começaram a sentir inúmeros crashes nas aplicações. Core dumps eram gerados e foram-me reportados para análise.
Tenho trabalhado com o 4GL desde 1991 (ena... 22 anos!), primeiro como programador e depois de ter entrado na Infomix (1998) como consultor, portanto diria que tenho uma vasta experiência com a linguagem (apesar de atualmente, honestamente me sentir "enferrujado" porque não trabalho com o produto todos os dias). Mas o que estou a tentar dizer é que obviamente esta não é a primeira vez que vejo um core no 4GL. Tipicamente são causados por exceder-mos os limites dos arrays, ou porque alguma função em C com algum problema foi linkada com a aplicação, ou muito raramente devido a algum bug. Portanto, normalmente começo com um debugger (dbx por exemplo) para extrair o stack trace. Normalmente isto diz-nos onde é que o problema aconteceu  e isso permite-nos fazer alguma análise de código. Outra opção é recompilar os módulos com a opção "-a" do comando c4gl. Isto inclui algum código de proteção aos limites dos arrays que obriga o programa a abortar se os mesmos forem excedidos. Isto teoricamente pode abrandar o programa, mas é uma excelente forma de apanhar problemas de dimensionamento de arrays.
Neste caso particular, o stack trace não era muito útil, pois parecia "corrompido", e apenas mostrava funções de sistema operativo relacionadas com gestão de exceções. Significava isto que o SO se apercebia que algo tinha corrido mal, mas durante a geração do core ficava confuso. A opção "-a" foi usada, mas sem sucesso. Os crashes pareciam relativamente aleatórios, ocorrendo em diferentes zonas do código e diferentes opções do utilizador.
Foi seguida uma abordagem mais empírica. Pegámos num programa que gerava cores frequentemente numa determinada opção e tentámos simplificá-lo ao máximo. Acabámos por chegar a um caso de teste muito mais simples que conseguíamos usar para reproduzir o problema. Depois de algumas repetições da mesma operação obtínhamos o crash. E tipicamente o problema aparecia em torno da leitura de uma tecla. O primeiro suspeito que tivemos foi o 4GL... existe documentação sobre um potencial problema quando algumas estruturas de input (INPUT, PROMPT, MENU etc.) são usadas dentro de um ciclo e saímos do mesmo sem sair da estrutura devidamente. Essencialmente devido a isto (o código de teste assemelhava-se a uma descrição do problema) decidimos abrir um PMR  na IBM. Foi a primeira interação com qualquer dos suportes técnicos dos vendedores envolvidos (IBM pelo 4GL; um para o SO e outro para a biblioteca de integração). Após alguma investigação descartámos esta hipótese. O suporte técnico da IBM foi útil para entendermos alguns detalhes que reunimos com o debugger. Mas durante um curto perído estávamos bloqueados. Devo explicar que por esta altura éramos três pessoas bastante envolvidas no assunto: Eu estava a tentar seguir uma abordagem prática para isolar o problema tentando alterar o caso de teste e simplificá-lo; Um colega do suporte técnico da IBM que estava a tentar encontrar situações semelhantes já reportadas e nos dava suporte em dúvidas várias visto ser muito mais conhecedor de debuggers; Um membro da equipa do cliente que estava a seguir a abordagem mais "científica". Passou horas a tentar executar o processo num debugger e chegou mesmo a analisar o assembler (e acreditem-me que ninguém se mantém inalterado depois de tentar entender o assembler de uma plataforma complexa, antiga e mal documentada)
Pela minha parte consegui confirmar que se a aplicação não chamasse a biblioteca de integração o problema não acontecia. A verificação do código que "colava" o 4GL a essa biblioteca permitiu identificar vários problemas, mas mesmo depois de corrigidos, o problema mantinha-se. Eu estava a ficar sem opções... Mas o cliente notou que parecia haver uma alteração de comportamento nas threads mesmo antes de o core ser gerado. Ele estava plenamente convencido que as threads ou a gestão das mesmas estavam diretamente relacionados com o problema. Com estes dados em mente, notámos que as funções da biblioteca de integração pareciam "desaparecer" do stack trace do processo após o crash. Assim, mudámos a investigação das estruturas do 4GL para problemas com threads. Escusado será dizer que nem eu nem o colega da IBM conseguimos encontrar muitas situações, pois como o 4GL não é thread safe não há muitas pessoas a usá-lo com threads. Mas conseguimos encontrar alguns relatos:
  1. Uma ocorrência na mesma plataforma que o nosso caso. Mas o cliente tinha a opção de linkar com uma biblioteca que não usava threads e o problema foi fechado sem mais investigação
  2. Um problema semelhante, relacionado com a leitura de teclado, mas em AIX. Foram mudados alguns parâmetros que controlam o thread scheduling e o problema desapareceu
  3. Um problema não muito bem documentado, em Solaris. Terá sido fornecido um workaround pela IBM sob a forma de patch específico
É importante recordar que tínhamos dois sistemas onde isto acontecia e outros onde não era possível reproduzir. Os sistemas pareciam semelhantes e os administradores de sistema não encontravam explicação para a diferença de comportamentos. Após alguma insistência concordaram em abrir um caso no suporte do sistema operativo. Expliquei que procurávamos ajuda para fazer o debug e que não tínhamos indicação nenhuma que a origem do problema fosse no SO. Uma espécie de conferência com possibilidade de partilha do desktop foi agendada e demonstrei o problema a acontecer. Durante a geração do core uma mensagem pouco clara (algo como "unable to unwind the stack") era mostrada... e assim que a pessoa do suporte a viu escreveu: "Isso é um problema aplicacional!"... Bom... Sim... Em teoria podia concordar com isso. Mas tal não nos ajudava. E não explicava porque só acontecia em alguns sistemas E não explicava porque é que o SO não gerava um core "limpo"... Depois de mais alguma conversa, e depois de reforçar que o que procurávamos era um conhecimento mais profundo nas ferramentas de debug do SO, e na interpretação dos outputs, um outro elemento do suporte juntou-se á conferência. Era claramente mais experiente e tinha outra atitude, muito mais disposta a ajudar, mas apesar disso, durante a conferência não se fez magia...
Concordaram em investigar alguns pontos que levantei sobre o comportamento do SO e na gestão de sinais (referido nos outros PMRs na IBM), e prosseguimos com a investigação pela IBM e continuamos a elaboração do caso de teste.
Nesta altura o ponto de situação era:
  1. Estávamos quase certos que o problema estava relacionado com threads (graças ao elemento da equipa do cliente que nos permitiu encontrar algo relevante na base de dados de casos da IBM)
  2. Estávamos intrigados pelo facto de o mesmo código ter comportamentos diferentes em sistemas diferentes. Daí o pedido de ajuda ao suporte do SO
  3. Dado que o 4GL não foi pensado para correr num ambiente de múltiplas threads, tentámos verificar se haveria alguma configuração da biblioteca de integração que permitisse fazer as mesmas operações, mas sem criar nova thread
O suporte do SO voltou a contactar-nos com alguns detalhes algo vagos. mas nada que nos permitisse modificar o comportamento do scheduling das threads (como teria sido feito no caso em AIX). E não conseguiam uma explicação sobre porque só acontecia em alguns sistemas.
Entretanto ficámos interessados na forma como o 4GL trabalha com os handlers  de sinais. Começamos a suspeitar que o 4GL estava a posicionar alguns handlers de sinais e que um (ou mais) estava a ser executado no contexto da thread criada pela biblioteca.

Dado o 3) acima, sugeri que obtivéssemos mais envolvimento da equipa que gere a biblioteca. Mas como não nos conseguiram dar grandes respostas, decidimos abrir um caso no suporte do fornecedor da biblioteca. As questões eram:
  1. Há alguma forma de evitar a criação da thread?
  2. Senão, há alguma forma de controlar o tempo que permanece idle (notámos que a thread passava a maior parte do tempo idle e que era acordada a intervalos regulares)
  3. Haveria alguma forma de controlar os sinais aos quais a thread poderia responder?

Houve apenas uma resposta, e foi desconcertante: "Desculpe Sr. cliente. Está a usar uma versão sem suporte. Por favor atualize a versão antes de fazer qualquer pergunta".
Não me interprete mal... Entendo e concordo com o fim de suporte para versões muito antigas. Mas não estávamos a pedir análise de código, debug ou alterações ao mesmo. E as questões (segundo o manual da versão atual as mesmas funções ainda existiam) seriam válidas para as versões correntes do produto. Portanto  a resposta pareceu um pouco forte. Já presenciei situações muito piores serem atendidas pelo suporte IBM Informix. Mas o cliente aceitou a resposta, portanto não podia fazer nada sobre isso. Voltámos novamente a estar um pouco encravados... Sem muita ajuda de outros suportes técnicos. O nosso suporte técnico sugeriu que o 4GL, ao ler o teclado, estava a fazer algo para lidar com a tecla especial "ESCAPE" que poderia causar problemas... De forma muito resumida, o código de "escape" pode ser a tecla de "ESCAPE" ou o inicio daquilo que se designa por sequência de escape (usadas em emuladores de terminais). O código faz uma chamada à função read() e obtém o código de escape. E isso coloca-o num dilema: Se é uma sequência de escape (começa com o código de escape) o programa tem de voltar a ler (nova chamada read()). Mas se era apenas o código da tecla, esta nova chamada vai bloquear (pois não há nada para ler). A forma como o 4GL resolve isto é chamar a função alarm() e depois o segundo read(). A chamada a alarm() despoleta um sinal (ALRM) a ser enviado ao processo ao fim do tempo especificado na chamada. Se nessa altura o read() ainda estiver pendente, o handler do sinal (estabelecido pelo 4GL) irá cancelar a chamada e prosseguir com a execução normal. Isto é possível manipulando o stack. É um truque "feio", mas é, ou pelo menos foi comum há alguns anos atrás. Conseguimos encontrar discussões sobre problemas no Apache porque utiliza o mesmo conceito (naturalmente não para ler o teclado).
Com isto em mente, decidimos tentar um workaround mudando o código interno do 4GL. Fiquei impressionado com a capacidade e a vontade do suporte técnico em tentar isto. O mecanismo anterior foi modificado para outro método que evita a segunda chamada ao read() (a que bloqueava). Uma chamada não bloqueante foi usada e criaram um patch não oficial para teste. Este foi colocado no ambiente do cliente e o programa deixou de crashar naquele ponto específico. Isto foram as boas notícias. As más é que começamos a ter outros crashes noutros pontos, e assumindo que estes eram ainda causados pela execução de handlers de sinais fora do contexto da thread do 4GL, não seria possível alterar isso sem efetuar mudanças estruturais ao código interno do 4GL. E era claro que seria difícil fazer essa implementação e em princípio o desenvolvimento da IBM não veria isso como uma possibilidade viável.

A solução

Foi então que uma abordagem mais pragmática foi assumida. A documentação de sistema diz-nos que uma thread B criada por uma thread A, herda a "signal mask" da A. A "signal mask" é um array de bits onde cada um representa um sinal específico, e se estiver on/off o sinal será/não será recebido pela thread. Como não queremos que a thread criada pelo software de integração receba sinais para os quais o 4GL preparou handlers, podemos mudar a máscara imediatamente antes de criar a thread, e restaurar a inicial imediatamente depois de criar a thread.
O suporte técnico da IBM concordou que valeria a pena testar isto e forneceu uma lista de sinais que valia a pena impedir.
Tecnicamente, fazer isto é muito simples:

#include <sys/types.h>
#include <errno.h>
#include <signal.h>

[…]

sigset_t mask, orig_mask;


sigemptyset (&mask);  /* cria uma máscara vazia */

/* Agora vamos adicionar alguns sinais à máscara. Outros poderiam ser considerados como o SIGTERM) */ 
sigaddset (&mask, SIGALRM);
sigaddset (&mask, SIGINT);
sigaddset (&mask, SIGQUIT);

/* Agora usamos a máscara que preparámos antes como uma máscara de bloqueio. Ao mesmo tempo a máscara actual é salvaguardada na variável orig_mask */ 





if ( pthread_sigmask(SIG_BLOCK, &mask, &orig_mask) != 0 ) {
     /* Lidar com qualquer possível erro.. *//
}

/*
     Chamar a função que cria a thread na biblioteca de integração
*/

/* E agora restaurar a máscara original, entretanto slavguardada na variável orig_mask */
if ( pthread_sigmask(SIG_SETMASK, &orig_mask, NULL) != 0 ) {
     /* Lidar com qualquer possível erro */
}

Conclusão

Após aplicar a alteração explicada acima na aplicação do cliente, o problema não se voltou a verificar. Como estamos a bloquear sinais "perigosos" antes de criarmos a nova thread, esta irá herdar esse bloqueio. Depois disso os handlers ativados pelo código 4GL não serão chamados no contexto da thread "externa". Apenas serão executados quando o contexto corrente pertence à thread de 4GL.
Isto quer dizer que as coisas "más" que o 4GL faz ao seu próprio stack nunca irão afetar o stack da(s) outra(s) thread(s).

Gostaria de salientar a diferença que verifiquei entre os serviços de suporte técnico: A IBM providenciou um patch de teste para "corrigir" algo que funciona como foi desenhado. Outra companhia declinou responder a algumas questões porque mencionámos uma versão fora de suporte, ainda que as questões fossem válidas para as versões que estão suportadas. E outra companhia não pôde explicar as diferenças de comportamento entre sistemas, e apenas depois da minha insistência foram capazes de providenciar alguns detalhes (que não foram muito úteis, mas compreendo que dado o problema não estar do lado deles isto poderá ser visto como razoável).

Uma nota final importante. Isto é um truque. O 4GL não é, nunca foi e provavelmente nunca será thread safe. Mas existe uma diferença entre não ser possível correr duas threads "4GL" no mesmo processo e ter uma thread 4GL com uma ou mais threads não 4GL. Pode mesmo assim haver problemas e esta solução poderá não ser viável em alguns casos, mas pode ser uma saída se já se encontrar no "buraco" (caso já venha a usar threads dentro de processos 4GL e de repente as coisas começarem a falhar). Se ainda não está nesse buraco, fica o aviso para não se deixar arrastar para ele!

Para mais detalhes técnicos sobre este problema pode consultar a seguinte nota técnica da IBM:

http://www-01.ibm.com/support/docview.wss?uid=swg21642635

Para dar o crédito a quem o merece, a evolução deste problema só foi possível graças à participação do Rui Mourão (pelo cliente) e do colega do suporte técnico da IBM Alberto Bortolan

Saturday, October 26, 2013

IBM IOD 2013: Informix sessions

This article is written only in English (original post here)

If you're interested in Informix and will be present at 2013 IBM IOD (Information On Demand) conference in Las Vegas, 3-7 November, you can find your complete list of Informix related sessions in this URL:

https://www-950.ibm.com/events/global/iod/agendabuilder/preview.html?agendaid=495

Topics that will be covered include:

  • Latest V12.10 improvements
  • Timeseries
  • Warehouse Accelerator
  • NoSQL (MongoDB compatibility)
  • Genero
And although we usually say that what happens in Vegas stays in Vegas, that should not be the case with this conference. Either if you're there or not, check the conference website regularly for news, videos and so on. It contains easy links to social sites where you can also follow it:

http://www-01.ibm.com/software/data/2013-conference/

Wednesday, October 16, 2013

Informix 12.10.xC2: No(?)SQL?

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



English version:

As usual lately, this article comes a bit late... But this time it was not just due to lack of time. I really hate to write about what I don't understand, and the new fixpack of Informix (12.10.xC2) is really about something I do not know: NoSQL functionality.
More specifically It's about the possibility to connect MongoDB applications (better saying, applications using MongoDB drivers and API) to Informix. In a previous article I wondered about this possibility and in fact it became available on September 13 (a Friday, so not all Fridays 13 bring bad things). As I mentioned, because I don't like to write about things I don't understand I spent some time these weeks trying to learn a bit more about it. Naturally it would be presumptuous from me to assume I'm now an expert on the subject, but at least I was able to read about it, try it and essentially think about it and discuss it with other people. In other words, I'm almost as ignorant as I was before, but I feel a lot more confident now :)
Before we dive into the feature list, as usual in my posts about new fixpacks, let me try to explain what is my current perception about this:
With the appearance of global sites, with millions of users, a need to scale emerged which goes beyond the normal perception of us, the IT staff in traditional business (local or international).
For things like Google, Twitter, Facebook and so on, the traditional relational repositories used in the traditional  ways, would not be able to provide the scalability, flexibility and response times needed. Does it mean they're not good? Not at all... It just means they were created to solve different challenges.
So a new breed of data repositories was developed. Lot's of new players like BigTable, MongoDB, Cassandra, Redis and many others. They are all called "NoSQL" systems, because they don't use or implement the SQL language. But the fact that they're generally referred to as NoSQL doesn't mean they're all alike. In fact they have significant differences between them that makes some good for some purpose and not good for other.They can be considered highly specialized data repositories. And of course, then there are some which are similar, but personal preferences lead to choosing one instead of other. Just like with traditional SQL databases.
But how are they different from the SQL databases? Well, essentially they don't implement a lot of things we consider as "granted". By doing so, they in a certain way assume some risks, but are able to provide simplicity and scalability beyond what we'd expect from "our" databases. A good reading regarding that is this article about Eric Brewer CAP theorem. I should also reference Ognjen Orel article about the new Informix release to point me to it. I do urge you to read both articles!
Some of these NoSQL databases also store data in a "document centric" way. MongoDB in particular stores a JSON (or BSON where B stands for binary) document as a "row". These documents don't need to have the same structure (even in the same "collection" which is the equivalent of a table) which makes schema change issues irrelevant. Another important point is that it doesn't allow joins. As you can see from the above article, most NoSQL databases are not ACID compliant. I suppose by now you're either very interested or shocked (or both). But these are just some of the things the new developers have given up to allow these repositories to scale beyond our general perception, and they do that horizontally, by dding nodes, usually implemented in the least expensive hardware (typically x86_64 based machines with some Linux flavor).

Now, this is relatively easy to understand, and you'll find a lot of information available to capture the details. The next step is to try to understand or figure out why and what kind of integration IBM have implemented. Is Informix going away from the SQL standard? Not at all! Will this allow it to scale horizontally as these solutions do? No, in my opinion of course. Will it allow Informix to compete with NoSQL solutions? Maybe in my opinion, in certain circumstances, but not at the "high level of scale" I mentioned before (Google, Twitter, Facebook etc.).
But I think the important point is slightly different. It's not as easy to find as the technical details and implementation decisions, but it you search carefully you'll find blog posts, opinions on forums etc. that defend that although NoSQL may be required to solve some issues, the same applications sometimes get over complicated because of the (assumed) lack of functionality. So, in those cases, it would be nice to have a "traditional" database providing the usual consistency, SQL ease of use and so on. It wouldn't mean the NoSQL was a bad choice, but that it would be nice to complement it with the "old" stuff. When we think about it, it makes sense. NoSQL became a requirement to solve a new challenge. By no means it's to be expected that for the same environments, the "traditional" problems don't exist. On the other hand, why not offer the MongoDB flexibility and ease of development within a traditional RDBMS? This is my personal perspective on the new features.

Features list

The complete feature list can be found here: http://pic.dhe.ibm.com/infocenter/informix/v121/topic/com.ibm.po.doc/new_features_ce.htm I will mention just a few (which I consider more important...).

  • Installation enhancements
    During the installation, if we decide to create an instance it will contain more auto-tunable features and will be ready for the JSON compatibility feature
  • Administration
    • New event alarm for network failures
      If the engine cannot start one of it's listeners a new alarm and an assert fail will be generated.
    • Back up and restore
      • Enhanced support for IBM Tivoli Storage Manager features
        Informix will allow bigger chunks of data to be sent between OnBAR and TSM (or PSM)
      • Restore critical files
        During the release day, I was just returning home from a foreign customer assignment where this was raised. Previously if you need to get the critical files from the storage manager you'd have to use filesystem backup interface or non documented features in OnBar. Now the documented syntax allows the user to retrieve the critical files from storage manager with an OnBAR option. This was required for PSM (Primary Storage Manager)
      • ON-Bar activity log timestamps
        More like a fix... Onbar would write the wrong timestamps in the log when it wrote messages stating it was stalled (waiting on storage manager XBSA library calls)
  • Performance
    • Faster operations
      • In-place alter operations on serial data types
        SERIAL to SERIAL8 and BIGINT to BIGINT8 becomes in-place alters
      • Faster queries for the Committed Read Last Committed isolation level
        Read ahead and light scans can now be used for this isolation level. I'm curious about the later...
    • Dynamic tuning
      • Dynamic private memory caches for CPU virtual processors
        VP_MEMORY_CACHE_KB will change dynamically to adapt to the system needs.
    • Troubleshooting
      • Monitor resource contention
        This will not raise Informix visibility or marketing recognition, but I believe it will be a favorite between existing users. It shows all the threads waiting on some condition together with information about the blocker. That information can include similar output to an onstat -g ses and the stack trace. Should be great to understand "hang" situations. I bet this was requested by tech support :)
  • Application development
    • JSON compatibility
      • JSON compatibility
        This is the core of this release. MongoDB applications (using MongoDB drivers) can connect to Informix and store and retrieve JSON/BSON documents, as well as get data stored in "native" Informix format in JSON format. It also includes the ability to use several Informix nodes in a way similar to how MongoDB uses nodes to split the records. This distributed capability is based on existing and new Informix Enterprise Replication features
      • JSON compatibility in OAT
        As usual, OAT allows some configuration on the new features
    • New syntax for queries
      • Joins with lateral references
        ANSI SQL compatibility feature for queries that use derived tables in the FROM clause
    • Enhanced basic text searching
      Several feature and performance enhancements for BTS datablade
    • Client products
      • IBM Informix .NET Provider support for .NET Framework versions
        Improved compatibility with newer .NET framework versions
    • New default
      • Defining separators for fractional seconds in date-time values
        I have a personal history with this one... Basically, in previous versions certain TO_CHAR and TO_DATE operations would force a "." (dot) to be included as the separator for fractions of a second. Now we can specify if we want it, or if we want a specific character. This was rather annoying because adding a character was easy, but removing the dot was impossible without nasty tricks
  • Enterprise Replication
    • Easier setup
      • Simplified schema changes for replicated tables
      • Set up and query time series data through a grid
    • Configuration
      • Control the replication of large objects
        We can force LOB columns to be replicated together with a row, even if it's content doesn't change
      • Custom checksum function for consistency checking
        A user can provide a checksum function to replace the default for consistency checking
    • Sharding
      • Shard tables across database servers
        Rows can be sent to different nodes based on hash or other expression to split data across a number of nodes (reducing the data volume in each node)
        The shard can define if the original row (on the original server) is kept after confirmation it was sent to destination, or deleted. So effectively you can distribute the data, without having to consider where (in which node) you insert it. And as we know, since 11.7 you can run a grid query that will retrieve the rows from all the relevant servers.
  • Time series data
    • Enhancements
      • Replicate time series data with all high-availability clusters
        Previously only HDR was supported. Now both SDS and RSS joined the party
      • Order TimeSeries columns in query results
      • Improvements for time series loader programs
        API enhancements
    • Faster queries
      • Faster aggregation of an interval of time series data
      • Faster queries on time series virtual tables
        Improvement for virtual tables based on fragmented TimeSeries tables
      • Accelerate queries on time series data
  • Warehousing
    • Additional types of data
      • Accelerate queries on time series data
      • Load data from external tables into data marts
 I hope to write some articles showing a bit more about this MongoDB compatibility. Some really basic stuff that should allow people with some MongoDB knowledge to see how easy it is to connect to Informix and manage some data on it

Versão Portuguesa:

Como tem sido habitual ultimamente, este artigo chega um pouco tarde... Mas desta vez não foi só a falta de tempo. Eu detesto escrever sobre o que não entendo, e o novo fixpack do Informix (12.10.xC2) é fundamentalmente dedicado a algo que desconheço: Funcionalidades NoSQL.
Mais especificamente é sobre a possibilidade de conectar aplicações MongoDB (ou melhor dizendo, aplicações que utilizem os drivers e API do MongoDB) ao Informix. Num artigo anterior divaguei sobre essa possibilidade e de facto ficou disponível no dia 13 de Setembro (uma sexta-feira, portanto nem todas as sextas-feiras 13 trazem coisas más). Como referi, porque não gosto de escrever sobre coisas que não entendo passei algum tempo destas últimas semanas a aprender um pouco mais sobre o tema. Seria presunção minha assumir que sou agora perito no assunto, mas ao menos consegui ler sobre o assunto, testar algumas coisas, pensar sobre o assunto e discuti-lo com outras pessoas. Por outras palavras, estou quase tão ignorante como anteriormente, mas sinto-me muito mais confiante :)
Antes de abordar a lista de novas funcionalidades como é hábito nos artigos sobre os novos fixpacks, deixe-me tentar explicar qual é a minha perceção atual sobre o tema:
Com o aparecimento de sites globais, com milhões de utilizadores, emergiu uma necessidade de escalabilidade para além da perceção normal que nós, meros membros do pessoal de TI de companhias com negócios tradicionais (locais ou internacionais), nem sequer assimilamos. Para ambientes como Google, Twitter, Facebook etc., os repositórios relacionais tradicionais, ou com abordagens tradicionais, não poderiam suportar a escalabilidade, flexibilidade e tempos de resposta necessários. Quererá isso dizer que não são boas? De forma nenhuma.... Apenas quer dizer que foram criadas para resolver problemas diferentes. Nota: Em alguns casos são usados repositórios tradicionais mas numa arquitetura totalmente diferente do habitual.
Assim, uma nova espécie de repositórios foi sendo desenvolvida. Muitos novos nomes, como o BigTable, MongoDB, Cassandra, Redis e muitos outros. A todos se chama habitualmente sistemas "NoSQL", porque não usam nem implementam a linguagem SQL. Mas o facto de serem genericamente referidos como "NoSQL" não significam que sejam iguais entre si. Na verdade têm diferenças muito significativas que levam a que sejam adequados a casos diferentes. São assim repositórios altamente especializados, embora naturalmente haja semelhanças entre alguns, e as preferências pessoais de cada programador ou arquiteto de sistemas leva a escolhas diferentes, tal como nas bases de dados SQL tradicionais.
Mas em que medida são diferentes das bases de dados SQL?, Bom, essencialmente não implementam uma série de coisas que damos como adquiridas. Ao fazê-lo assumem riscos, mas isso permite-lhes oferecer outros níveis de escalabilidade e extrema simplicidade para além do que podemos esperar das "nossas" bases de dados. Uma boa leitura sobre este tema é este artigo sobre o teorema CAP de Eric Brewer. Devo também referir o artigo sobre esta versão do Informix do Ognjen Orel que me indicou o anterior. Recomendo vivamente a leitura de ambos.

Algumas destas bases de dados NoSQL armazenam dados com base em "documentos". O MongoDB em particular armazena um documento JSON (ou BSON onde o "B" vem de "binário") como uma linha numa base de dados tradicional. Estes documentos não têm de ter todos a mesma estrutura (mesmo dentro de uma "coleção" que será o equivalente a uma tabela). Isto elimina os tradicionais problemas de mudança no schema da base de dados. Outro aspeto importante é que estas BDs não implementam joins. E como pode ler no artigo acima, a maioria falha os conceitos ACID. Suponho que neste momento ou está interessado ou chocado (ou ambos). Mas estas são algumas coisas de que os criadores abdicaram para permitir a estes repositórios atingir níveis de escalabilidade para além das expectatvas tradicionais. E fazem-no crescendo horizontalmente, adicionando nós, normalmente implementados no menos dispendioso hardware (tipicamente máquinas x86_64 usando algum tipo de Linux).

Bom, tudo isto é relativamente fácil de entender, e poderá encontrar muita informação que providencia mais detalhes. O próxmo passo será tentar entender ou descobrir porquê, e que tipo de integração a IBM implementou no Informix. Vai o Informix afastar-se da norma SQL? Nem por sombras. Irá isto permitir escalar horizontalmente como estas soluções fazem? No limite, penso que não. Irá permitir ao Informix competir com as soluções NoSQL? A meu ver talvez, em determinadas circunstancias, mas julgo que não em situações extremas de escalabilidade (mais umas vez os exemplos clássicos dos gigantes da Internet, onde várias soluções costumizadas tiveram de ser implementadas)
Mas julgo que o ponto mais importante é ligeiramente diferente. E não é tão fácil de encontrar quanto os detalhes e opções de implementação, mas se procurar cuidadosamente irá encontrar em blogs, opiniões em fóruns etc. Refiro-me à opinião de que embora soluções NoSQL possam ser necessárias ou adequadas a solucionar determinados problemas, as mesmas aplicações muitas vezes tornam-se demasiado complexas e difíceis de manter devido à falta de funcionalidades. Coisas que seriam lineares e fáceis de fazer num RDBMS, tornam-se complexas e logo dispendiosas. Nesses casos seria bom ter uma base de dados tradicional que fornecesse o habitual: garantia de consistência, facilidade de utilização do SQL etc. Isto não significa que o NoSQL tenha sido uma má escolha para esses casos, mas apenas que pode ser bom poder complementá-lo com as coisas "tradicionais". Se pensarmos no assunto parece fazer sentido. NoSQL tornou-se uma necessidade para resolver novos desafios, mas de forma nenhuma devemos esperar que nos mesmos ambientes, os problemas "tradicionais" não existam, e logo a solução tradicional, acessível com a mesma facilidade de programação é importante. Por outro lado, porque não oferecer a flexibilidade de desenvolvimento do MongoDB numambiente relacional clássico? Esta é a minha perspetiva sobre este tema.

Lista de funcionalidades

A lista completa das novas funcionalidades pode ser encontrada aqui: http://pic.dhe.ibm.com/infocenter/informix/v121/topic/com.ibm.po.doc/new_features_ce.htm Vou mencionar apenas algumas que me parecem mais importantes.

  • Melhorias na instalação
    Durante a instalação, se decidirmos criar uma instância, irá conter mais funcionalidades de auto-otimização e ficará pronta para a funcionalidade JSON/BSON
  • Administração
    • Novos alarmes para eventos de falha de rede
      Se o motor não conseguir levantar um dos listeners um novo alarme será despoletado, bem como um assert fail
    • Backups e restores
      • Melhor suporte ao storage manager da IBM, o Tivoli
        O informix permite usar pacotes de maior dimensão na troca de informação entre o onbar e o TSM (ou PSM)
      • Restauro dos ficheiros criticos
        No dia do lançamento desta versão estava de regresso de um workshop num cliente fora de Portugal, onde este problema tinha sido levantado. Previamente, se necessitásse-mos de restaurar os ficheiros criticos via storage mnager era necessário utilizar as ferramentas de backup/restore de filesystem ou funcionalidades não documentadas do onbar. Agora a sintaxe documentada permite obter os ficheiros criticos do storage manager com opções do onbar. Isto foi um requisito para o PSM (Primeary Storage Manager) introduzido na 12.10.xC1
      • timestamps no log de actividade do onbar
        É mais uma correcção.... O onbar escrevia os timestamps errados no seu log de actividade quando informava que as chamadas ao storage manager estavam paradas
  • Performance
    • Operações mais rápidas
      • Alterações in-place em tipos de dados SERIAL
        De SERIAL para SERIAL8 e de BIGINT para BIGINT8 passam agora a ser alterações in-place
      • Queries mais rápidas quando o nível de isolamento é Committed Read Last Committed
        O motor pode agora usar read-ahead e light scans em leituras neste modo de isolamento. Os light scans deixam-me intrigado...
    • Optimização dinâmica
      • A cache de memória privada para os CPU VPs pode ser controlada automaticamente. O parâmetro VP_MEMORY_CACHE_KB será ajustado conforme as necessidades
    • Resolução de problemas
      • Monitorização de contenção de recursos
        Não será uma funcionalidade que promova a visibilidade do Informix ou receba reconhecimento a nível de marketing, mas julgo que será umas das favoritas para clientes actuais. Permite mostras as threads que estão à espera numa condição, juntamente com informação sobre quem as está a bloquear. Essa informação pode incluir um output semelhante ao onstat -g ses juntamente com o stack trace. Deve ser óptimo para entender e recolher evidências para situações de bloqueios no motor. Quase aposto que isto foi um requisito introduzido pelo suporte técnico :)
  • Desenvolvimento aplicacional
    • Compatibilidade JSON
      • JSON
        Esta é a novidade principal desta release. As aplicações MongoDB (ou que utilizem dirvers MongoDB) podem ligar-se ao Informix e gerir documentos JSON/BSON, bem como manipular informação dentro das tabelas clássicas do Informix mas num formato JSON. Incluí também a possibilidade de utilizar vários nós Informix de forma semelhante à que o MongoDB utiliza para distribuir registos (sharding). Esta funcionalidade para processamento distribuído assenta em funcionalidades anteriores e nóvas do Enterprise Replicaction do Informix.
      • JSON no OAT
        Como é usual, o OAT permite gerir as novas funcionalidades
    • Nova sintaxe para queries
      • Joins com lateral references
        Funcionalidade de compatibilidade com o ANSI SQL, para queries que utilizem tabelas derivadas na cláusula FROM
    • Melhorias na pesquisa de texto
      Várias novas funcionalidades e melhorias de performance no datablade BTS (Basic Text Search)
    • Produtos clientes
      • Suporte do IBM Informix .NET Provider a versões do .NET Framework
        Melhor compatibilidade do diriver com as versões mais recentes da framework .NET
    • Novo default
      • Definição de separador para valores fraccionados em tratamento de tipos de daods date-time
        Tenho uma história pessoal com este tema... Basicamente, em versões anteriores, certas utilizações do TO_CHAR() e TO_DATE forçavam a utilização de um "." (ponto), como separador das frações de segundo. Agora podemos especificar se o queremos, ou se queremos um carácter específico. O antigo comportamento era particularmente irritante, pois sendo fácil adicionar um caracter, tirar o ponto era impossível sem truques "feios"
  • Enterprise Replication
    • Configuração facilitada
      • Simplificação nas alterações de schema das tabelas replicadas
      • Configurar e fazer queries sobre dados timeseries numa configuração de grid
    • Configuração
      • Controlo sobre a replicação  de LOBs (large objects)
        Podemos forçar a que uma coluna LOB seja replicada em conjunto com a linha, mesmo que o seu conteúdo não mude
      • Funções customizadas para verificação de consistência
        O utilizador pode criar uma função para substituir a função por omissão para verificação da consistência
    • Sharding
      • Tabelas shard entre servidores de bases de dados
        As linhas podem ser repartidas por vários nós, baseado numa função de hash ou outra expressão (reduzindo assim o volume de dados em cada nó).
        O shard pode definir se a linha original (no servidor onde originalmente foi inserida) é mantida ou apagada depois de ser copiada para o destino. Assim, podemos efetivamente distribuir os dados sem nos preocupar-mos em que nó vão ser inseridos. E como sabemos, desde a versão 11.7 podemos executar uma grid query que obterá as linhas de todos os nós da grid
  • Dados timeseries
    • Melhorias
      • Replicação de dados timeseries com todos os nós de clusters de alta disponibilidade
        Anteriormente apenas o HDR era suportado. Agora tanto os SDS como os RSS juntam-se à festa
      • Ordenação de colunas timeseries nos resultados das queries
      • Melhorias em programas de carregamento de dados timeseries
        Melhorias na API
    • Queries mais rápidas
      • Agregação mais rápida de intervalos de valores timeseries
      • Acessos mais eficientes a tabelas virtuais
        Melhorias em tabelas virtuais construídas sobre tabelas timeseries fragmentadas
      • Aceleração de queries em dados timeseries
  • Warehousing
    • Tipos de dados adicionais
      • Aceleração de queries sobre dados timeseries
      • Carregamento direto de tabelas externas para data marts
Espero escrever mais alguns artigos onde mostre um pouco mais sobre a compatibilidade com MongoDB. Algo realmente básico que possa demonstrar a quem tenha algum conhecimento de MongoDB como é fácil conectar-se ao Informix e manipular dados

Thursday, October 03, 2013

Informix @IOD 2013

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

English version:
Information On Demand (IOD) conference 2013 will be held in Las Vegas on November 3-7.
As you would expect Informix portfolio will have a strong presence on the conference and you'll be able to attend 20 technical sessions and hands-on labs. Please check Jerry Keesee note (director of Informix developement) about this.
Naturally the new NoSQL features in 12.10.xC2 will be a big topic. And naturally you'll get access to a lot more sessions about other portfolio areas that you can use with Informix like Guardium, Optim, Cognos and so on...


Versão Portuguesa:
A conferência Information On Demand (IOD) de 2013 terá lugar de 3 a 7 de Novembro em Las Vegas.
Como seria de esperar, o portflio Informix terá uma presença forte na conferência, podendo assistir a 20 sessões técnicas e laboratórios com exercícios. Consulte a nota do Jerry Keesee (director de sedenvolvimento Informix) sobre isto.
Naturalmente as novas funcionalidades NoSQL da versão 12.10.xC2 serão um tópico "quente". Poderá também assistir a muitas mais sessões sobre outros produtos do portfolio IBM que pode usar com o Informix, como o Guardium, o Optim, Cognos e outros...

Saturday, August 10, 2013

Google's two factor authenticator / Autenticação com 2 factores da Google

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


English version:

Introduction

It should not be a surprise to anyone that I'm a big fan of PAM (plugable authentication modules). I've written several articles about it in this blog. This time I'll pick up a Google project and show you how to glue it together with Informix to achieve more security for your connections. And this happens thanks to PAM obviously.
Google authenticator is a project that implements one time password (OTP) generators for several mobile platforms. In practice you'll use your smartphone as a token generator. This avoids the need of a specific piece of hardware and has the advantages of being open source and making available a server side PAM library. Meaning you can integrate it with anything that supports PAM. This means your SSH daemon and naturally your favorite database server software.
Google authenticator can be seen and used as a component of a multi-factor authentication mechanism. This implies that a user must present (and I quote) "two or more of the three authentication factors: a knowledge factor ("something the user knows"), a possession factor ("something the user has"), and an inherence factor ("something the user is"). This article will show you how to configure Informix for two factor authentication. In our scenario we'll use a traditional password (something the user knows) and Google's authenticator as the second factor (something the user has). In the future, if the rumors about the introduction of biometric readers (like finger print readers) on mobile phones becomes a reality, it may be possible to extend this to three factors (something the user is).
It's possible that we'll see more services using this kind of technology. Just recently Twitter introduces two factor authentication by sending a request to their app when you try to login in their web site. The user will need to authorize that connection by using the twitter app on an authorized phone. Essentially this implements the same concept, but in an easier way.

The code

To start with this, we need to get the code for the PAM library. We can find it in Google's authenticator website. So I downloaded the package into a Linux VM, and uncompressed it:
[root@kimball google_authenticator]# pwd;ls -lia;tar -xjvf libpam-google-authenticator-1.0-source.tar.bz2
/usr/local/google_authenticator
total 44
1671484 drwxr-xr-x  2 root root  4096 Aug  6 11:43 .
1605654 drwxr-xr-x 14 root root  4096 Aug  6 09:46 ..
 327870 -rw-r--r--  1 root root 32708 Aug  6 11:40 libpam-google-authenticator-1.0-source.tar.bz2
libpam-google-authenticator-1.0/base32.c
libpam-google-authenticator-1.0/demo.c
libpam-google-authenticator-1.0/google-authenticator.c
libpam-google-authenticator-1.0/hmac.c
libpam-google-authenticator-1.0/pam_google_authenticator.c
libpam-google-authenticator-1.0/pam_google_authenticator_unittest.c
libpam-google-authenticator-1.0/sha1.c
libpam-google-authenticator-1.0/base32.h
libpam-google-authenticator-1.0/hmac.h
libpam-google-authenticator-1.0/sha1.h
libpam-google-authenticator-1.0/totp.html
libpam-google-authenticator-1.0/Makefile
libpam-google-authenticator-1.0/FILEFORMAT
libpam-google-authenticator-1.0/README
libpam-google-authenticator-1.0/utc-time/
libpam-google-authenticator-1.0/utc-time/app.yaml
libpam-google-authenticator-1.0/utc-time/utc-time.py

After this, following very simple instructions, I type "make install":

[root@kimball libpam-google-authenticator-1.0]# make install
gcc --std=gnu99 -Wall -O2 -g -fPIC -c  -fvisibility=hidden  -o google-authenticator.o google-authenticator.c
gcc --std=gnu99 -Wall -O2 -g -fPIC -c  -fvisibility=hidden  -o base32.o base32.c
gcc --std=gnu99 -Wall -O2 -g -fPIC -c  -fvisibility=hidden  -o hmac.o hmac.c
gcc --std=gnu99 -Wall -O2 -g -fPIC -c  -fvisibility=hidden  -o sha1.o sha1.c
gcc -g   -o google-authenticator google-authenticator.o base32.o hmac.o sha1.o  -ldl
gcc --std=gnu99 -Wall -O2 -g -fPIC -c  -fvisibility=hidden  -o pam_google_authenticator.o pam_google_authenticator.c
gcc -shared -g   -o pam_google_authenticator.so pam_google_authenticator.o base32.o hmac.o sha1.o -lpam
gcc --std=gnu99 -Wall -O2 -g -fPIC -c  -fvisibility=hidden  -o demo.o demo.c
gcc -DDEMO --std=gnu99 -Wall -O2 -g -fPIC -c  -fvisibility=hidden  -o pam_google_authenticator_demo.o pam_google_authenticator.c
gcc -g   -rdynamic -o demo demo.o pam_google_authenticator_demo.o base32.o hmac.o sha1.o  -ldl
gcc -DTESTING --std=gnu99 -Wall -O2 -g -fPIC -c  -fvisibility=hidden        \
              -o pam_google_authenticator_testing.o pam_google_authenticator.c
gcc -shared -g   -o pam_google_authenticator_testing.so pam_google_authenticator_testing.o base32.o hmac.o sha1.o -lpam
gcc --std=gnu99 -Wall -O2 -g -fPIC -c  -fvisibility=hidden  -o pam_google_authenticator_unittest.o pam_google_authenticator_unittest.c
gcc -g   -rdynamic -o pam_google_authenticator_unittest pam_google_authenticator_unittest.o base32.o hmac.o sha1.o -lc  -ldl
cp pam_google_authenticator.so /lib64/security
cp google-authenticator /usr/local/bin
[root@kimball libpam-google-authenticator-1.0]#

and we can verify that the two components were installed correctly:

[root@kimball libpam-google-authenticator-1.0]# ls -lia /lib64/security/pam_google* /usr/local/bin/google*
1212428 -rwxr-xr-x 1 root root 116777 Aug  6 11:50 /lib64/security/pam_google_authenticator.so
1613197 -rwxr-xr-x 1 root root  55498 Aug  6 11:50 /usr/local/bin/google-authenticator
[root@kimball libpam-google-authenticator-1.0]#

Setup

We have the PAM library and the google-authenticator binary that we'll use to generate a secret key for our user. So this is the next step. I'll not do this with user "informix", because the engine will always ignore PAM for informix user when connecting locally. But before, let's check the other fundamental component of the solution: Your smartphone. Currently I use Android, but the app is available to iOS and Blackberry. You should know how to find and install the app. For Android devices it's available on GooglePlay store. The app for Android and iOS supports both reading a QR code or manual introduction of the secret key generated by the google-authenticator binary. For Blackbery, according to the Google Authenticator website, it only supports manual introduction.

Assuming the app is properly installed, we can proceed with the server side configuration. As mentioned above, the next step is to generate the secret key between the server account and the mobile app. For that we use the google-authenticator binary installed before. When we run it it outputs the information needed and also makes some questions. I will not dig into those as you can find out more in the documentation:

-bash-3.2$ google-authenticator

Do you want authentication tokens to be time-based (y/n) y
https://www.google.com/chart?chs=200x200&chld=M|0&cht=qr&chl=otpauth://totp/fnunes@kimball%3Fsecret%3D6LWH24ZIUDY46HJU
Your new secret key is: 6LWH24ZIUDY46HJU
Your verification code is
649019
Your emergency scratch codes are:
  38268905
  24335468
  11497220
  81653596
  89796862

Do you want me to update your "/opt/fnunes/.google_authenticator" file (y/n) y

Do you want to disallow multiple uses of the same authentication
token? This restricts you to one login about every 30s, but it increases
your chances to notice or even prevent man-in-the-middle attacks (y/n) n

By default, tokens are good for 30 seconds and in order to compensate for
possible time-skew between the client and the server, we allow an extra
token before and after the current time. If you experience problems with poor
time synchronization, you can increase the window from its default
size of 1:30min to about 4min. Do you want to do so (y/n) n

If the computer that you are logging into isn't hardened against brute-force
login attempts, you can enable rate-limiting for the authentication module.
By default, this limits attackers to no more than 3 login attempts every 30s.
Do you want to enable rate-limiting (y/n) y
-bash-3.2$

It shows us a URL and the key. If we have a library installed (libqrencode) it will also show a QR code that we can use directly with the phone. If we open the URL in a browser it will show a QR code that we can use in the app to install the key. Otherwise we need to introduce the key manually. Either way it's pretty simple and in a minute you'll have a token (or one time password) generator on your mobile phone:

After this we need to go and configure our informix instance to take advantage of this. For that I'll create a new listener port using PAM. As you probably already know, this is done by altering the $INFORMIXSQLHOSTS file and add a line similar to this:

tpch_pam     onsoctcp             kimball.onlinedomus.net     1527   s=4,pam_serv=(ids_pam_service),pamauth=(challenge)


Field by field:
  • tpch_pam
    The INFORMIXSERVER name for this alias
  • onsoctcp
    The protocol to be used
  • kimball.onlinedomus.net
    The hostname
  • 1527
    The unique TCP port number for this listener
  • s=4,pam_serv=(ids_pam_service),pamauth=(challenge)
    options field. s=4 forces PAM usage. The PAM service name is "ids_pam_service" and the PAM mode will be "challenge" 
After the $INFORMIXSQLHOSTS we must make sure that the name "ids_pam_service" is configured in the PAM configuration. Being Linux, this means having a file called ids_pam_service in /etc/pam.d. The content of the file will be:

auth       required     pam_unix.so
auth       required     pam_google_authenticator.so
account    required     pam_unix.so

This will be used for our "basic by the book" configuration. Later I'll explain some issues with it, and will show you a better way to use it. As mentioned in other articles we just need "auth" and "account" configuration lines. And we're "stacking" modules (Google and pam_unix) so that we achieve the "two" factor authentication. First we'll test the unix password and then the Google's authenticator token or code (the one time password)

Next we need to make sure "tpch_pam" is configured in the $INFORMIXDIR/etc/$ONCONFIG file in the parameter DBSERVERALIAS and after that we can start the listener with:

onmode -P start tpch_pam

Testing

So, if we have everything setup, it should work. Let's test it with dbaccess:

tpch@kimball:fnunes-> id;hostname;echo $INFORMIXSERVER
uid=1002(fnunes) gid=1002(fnunes) groups=1002(fnunes)
kimball
tpch
tpch@kimball:fnunes-> dbaccess - -
> CONNECT TO 'sysmaster@tpch_pam' user 'fnunes';
   ENTER PASSWORD:
Password:
Verification code:

Connected.

> SELECT USER, * FROM syscfgtab WHERE cf_name LIKE 'DBSERVER%';



(expression)  fnunes
cf_id         2
cf_name       DBSERVERNAME
cf_flags      0
cf_original   tpch
cf_effective  tpch
cf_default    kimball

(expression)  fnunes
cf_id         77
cf_name       DBSERVERALIASES
cf_flags      0
cf_original   tpch_pam
cf_effective  tpch_pam
cf_default

2 row(s) retrieved.

> tpch@kimball:fnunes->

Firts impression is good. It works. But there's something weird. If you take a close look you'll see that once I do a CONNECT, dbaccess asks me for "ENTER PASSWORD:". Then it asks me for "Password:" and finally for "Verification code:". Three prompts... I hope you understand two of them but not the repeated request for password. Let me explain. By default, whenever we try to CONNECT dbaccess will ask for a password. That's the first prompt. Actually, with this configuration (challenge mode) we can enter whatever we want here... it will be ignored. Than we start the PAM stack layer. And the first module is pam_unix.so. Since we're not sending it any password (more about this later) it asks us for the password ("Password:" prompt). Here you have to write the system user password. After that we move to the second module, the Google authenticator module and it behaves the same way. Since it doesn't have a password it asks for one ("Verification code:" prompt). And here we need to take a look into our smartphone and copy the current generated token (or one time password). After that we're logged in. Both passwords were verified (one for each module in the stack).
This behavior justifies the "challenge" mode. The module sends a challenge back to the engine, and the engine send it back to the client (dbaccess in our case) and the client must have registered a callback function to handle the challenges and user responses. Basically what it does in dbaccess is to echo the challenge and read the response, and finally sending it back to the engine which sends it back to the module for verification.
Although it works, it's a bit ackward and needs a client side function to handle the challenges. This means we could possibly have to change the application. dbaccess already knows how to handle it, but other clients wouldn't know what to do.
Informix APIs have functions to register a callback function. But again, code changes are never welcome. So, let's see what we can do...

Improvement

As you probably know (I mentioned it in previous PAM articles), Informix pam can be configured in "password" or "challenge" mode. The documentation sometimes leads us to think that for this kind of usage we need to use challenge. But in fact it really depends on the modules you use and the options they provide. In our case I noticed that Google's authenticator module supports two interesting options:
  • try_first_pass
    makes it check the PAM framework for a previously supplied password
  • forward_pass
    makes it smart... if you provide a password composed by the concatenation of the system password and the verification token, it will try to split them, verify the code and send the rest through the PAM stack of modules
I also noticed pam_unix.so supports both try_first_pass and use_first_pass. So, what all this suggests is that assuming we have a system password like "mypasswd" and a verification code like "096712" we could use a composed password "mypasswd096712", give it to the dbaccess password request, and don't be bothered by each module prompts. let's change the configuration and test again. The file /etc/pam.d/ids_pam_service becomes this:

auth       required     pam_google_authenticator.so try_first_pass forward_pass
auth       required     pam_unix.so use_first_pass
account    required     pam_unix.so

And in INFORMIXSQLHOSTS we'll change "challenge" for "password":

tpch_pam     onsoctcp             kimball.onlinedomus.net     1527   s=4,pam_serv=(ids_pam_service),pamauth=(password)

And now let's restart the listener and repeat the test:

tpch@kimball:root-> onmode -P restart tpch_pam


tpch@kimball:fnunes-> dbaccess - -
> CONNECT TO 'sysmaster@tpch_pam' user 'fnunes';
   ENTER PASSWORD:

Connected.

> SELECT USER, * FROM syscfgtab WHERE cf_name LIKE 'DBSERVER%';

(expression)  fnunes
cf_id         2
cf_name       DBSERVERNAME
cf_flags      0
cf_original   tpch
cf_effective  tpch
cf_default    kimball

(expression)  fnunes
cf_id         77
cf_name       DBSERVERALIASES
cf_flags      0
cf_original   tpch_pam
cf_effective  tpch_pam
cf_default

2 row(s) retrieved.

> tpch@kimball:fnunes->

And voilá.... The first module now is Google authenticator. It gets the double password from the stack, extracts it's part (it knows it's the last 6 digits), verifies it, and sends the rest to the second module (pam_unix) that validates the password in the system

Now... we've seen that dbaccess does some magic... Because it knows we're dealing with a PAM port. So, to be absolutely sure this is transparent for the applications, let's try an external JDBC connection that has no knowledge that it's a PAM enabled port:

Success!
There not much more we can do. It's basic and very simple to setup. It shows Informix flexibility. And because it's PAM you can of course enrich it with additional modules if you like

Considerations

There are many things to note about this subject. First we could wonder about the usage cases for something like this. A few ideas come to mind:

  • added security for privilege users. You could assume that applications only connect trough  a safer network, using normal authentication, but DBSAs may need to connect from the "external" world and it requires added security
  • You can use it to construct a "double" password and have part of if available to the application code and let the user introduce the verification code. This would prevent a user to authenticate from outside the application (because the user would never know first password components, but on the other hand an application manager would not be able to impersonate the user even if he got to know the user password)
  • Other extended uses could be achieved by tweaking the module code.
It's important to keep in mind that as with any security related component you should consider carefully what you need and discuss the possibilities with security conscious people. I did a sort of brain storming with two ex-Informix DBAs that now work in the security team and some interesting points were raised. Among them, here are a few:

  1. This sort of token generator as opposed to specialized hardware like RSA tokens
    • Possible advantages of this method:
      • You probably notice you lost your smartphone faster than if you lost a specialized token
      • It's cheaper
      • If the device needs renewal, this method looks simpler (a user can do it once he gets the new phone)
      • You don't depend on any external supplier
    • Possible disadvantages of this method:
      • It seems easier to remotely "hack" a smartphone than to compromise the security of  a specialized hardware token
      • The application could provide some security measure to prevent unauthorized access to the generated codes to anybody who has physical access to the phone. Note this also happens on the hardware token, and with a phone you could always protect it with PIN or pattern code. This is not exactly a disadvantage comparing to hardware tokens, but could be something to improve
  2.  This would be hard to use for non-interactive processes. Unless, and I believe this could be a possibility, that we work the other way around... Meaning we have the code generator inside the applications servers, and that we setup a callback function to answer the module challenge. This would possibly avoid the usage of the application user from outside the application server environment
  3. A generic issue with any two factor authentication mechanism is that ideally the second factor should use a different communication channel from the first. That doesn't happen here, and this allows for man in the middle attacks
  4. Another point, which can be related to the one before, is the possibility to use, or not, the same token in a certain time interval (even a short one). The codes generated by Google authenticator are valid by default during 30 seconds (to allow some time for the user to introduce the code and also to compensate for small clock differences between client and server). The module allows that the code before and after the correct one to be used, so the time interval becomes 1m30s. But all this can be configured, and we even have the possibility of not allowing the same code to be used twice. This however will limit the ability of making more than one login each 30 seconds

Issues found

During the preparation and testing for this article I've faced two main issues:
  1. On first attempts I got the "Invalid verification code" error from the module. As the documentation says this is usually caused by clock synchronization issues between the server side and the mobile app side. After some checking I've found that I was mixing clock time with timezone offsets and it caused too much difference (the module allows configuration for some small difference)
  2. As usual with PAM and PAM modules the hardest part was debugging. Most modules tend to be very quiet about the errors. Not sure why, but on first attempts of concatenating user password with the verification code I was attempting the wrong order (code + password) instead of the proper order (password + code). I ended up looking into the code and changing it to be much more verbose.
Another possible issue is that at the moment, there is no App for Windows phones. And apparently they are grwoing in the market (according to recent studies, they shipped more units than Blackberry)

Acknowledgements

It's not unusual that I discuss some aspects while working on new articles. I recall having some help from several IBM colleagues, some of them already mentioned here once or twice. In this case I had the pleasure to discuss this subject with two ex-Informix DBAs, with whom I worked for several years on a customer team. Now they're working in the security team and the first time I heard about Google authenticator was during a chat with one of them. During the writing of this article and before it was published we interacted a couple of times. Several aspects of the post should be credited to them. So for that, and for the good time we spent working together (we still do occasionally because they have a long history in this customer and there are always subjects where we can work together) a big "Thank you!" to Daniel Valente and Rui Mourão.


Versão Portuguesa:

Introducão 

Não será uma surpresa para ninguém se disser que sou um grande fã do PAM (plugable authentication modules). Já escrevi vários artigos sobre o tema aqui no blog. Desta vez vou partir de um projeto da Google e mostrar como o usar em conjunto com o Informix para aumentar a segurança das conexões. E tudo graças ao PAM naturalmente.
O Google authenticator é um projeto que implementa um gerador de senhas descartáveis (one time passwords - OTP) para várias plataformas móveis. Na prática, transforma um smartphone num gerador se senhas (tokens). Isto evita a necessidade de uma peça física e tem as vantagens de ser open source e disponibilizar um módulo PAM sob a forma de uma biblioteca dinâmica. Isto permite que seja integrável com qualquer sistema que suporte PAM. Tanto pode ser um servidor de SSH como a sua base de dados favorita.
O Google authenticator pode ser visto como um componente de um mecanismo de autenticação multifatorial. Traduzindo, isto significa que o utilizador deve apresentar (e cito traduzindo) "dois ou mais dos três fatores de autenticação: um fator de conhecimento ("algo que o utilizador sabe"), um fator de posse ("algo que o utilizador tem") e um fator de inerência ("algo que o utilizador é"). Este artigo mostrará como configurar o Informix para uma autenticação de dois fatores. No nosso cenário usaremos uma senha tradicional (algo que o utilizador sabe) e o Google authenticator como segundo fator (algo que o utilizador tem). No futuro, se os rumores sobre a introdução de leitores de dados biométricos (impressão digital por exemplo) em dispositivos móveis se tornar uma realidade, poderá ser possível estender para três fatores (algo que o utilizador é).
É possível que vejamos mais serviços a usar este tipo de tecnologia. Ainda recentemente o Twitter introduziu a autenticação de dois fatores, usando o envio de um pedido à sua App quando tenta fazer login no seu site. O utilizador tem de autorizar a ligação usando a aplicação do Twitter num telefone autorizado. Na essência é o mesmo conceito, mas talvez de uma forma ainda mais fácil para o utilizador.

O código

Para começar, necessitamos da biblioteca PAM. Podemos encontrá-la no website do Google authenticator. Fiz a transferência do pacote e coloquei-o numa máquina virtual Linux, tendo de seguida descomprimido:
[root@kimball google_authenticator]# pwd;ls -lia;tar -xjvf libpam-google-authenticator-1.0-source.tar.bz2
/usr/local/google_authenticator
total 44
1671484 drwxr-xr-x  2 root root  4096 Aug  6 11:43 .
1605654 drwxr-xr-x 14 root root  4096 Aug  6 09:46 ..
 327870 -rw-r--r--  1 root root 32708 Aug  6 11:40 libpam-google-authenticator-1.0-source.tar.bz2
libpam-google-authenticator-1.0/base32.c
libpam-google-authenticator-1.0/demo.c
libpam-google-authenticator-1.0/google-authenticator.c
libpam-google-authenticator-1.0/hmac.c
libpam-google-authenticator-1.0/pam_google_authenticator.c
libpam-google-authenticator-1.0/pam_google_authenticator_unittest.c
libpam-google-authenticator-1.0/sha1.c
libpam-google-authenticator-1.0/base32.h
libpam-google-authenticator-1.0/hmac.h
libpam-google-authenticator-1.0/sha1.h
libpam-google-authenticator-1.0/totp.html
libpam-google-authenticator-1.0/Makefile
libpam-google-authenticator-1.0/FILEFORMAT
libpam-google-authenticator-1.0/README
libpam-google-authenticator-1.0/utc-time/
libpam-google-authenticator-1.0/utc-time/app.yaml
libpam-google-authenticator-1.0/utc-time/utc-time.py

Depois disso, seguindo instruções muito simples basta fazer "make install":

[root@kimball libpam-google-authenticator-1.0]# make install
gcc --std=gnu99 -Wall -O2 -g -fPIC -c  -fvisibility=hidden  -o google-authenticator.o google-authenticator.c
gcc --std=gnu99 -Wall -O2 -g -fPIC -c  -fvisibility=hidden  -o base32.o base32.c
gcc --std=gnu99 -Wall -O2 -g -fPIC -c  -fvisibility=hidden  -o hmac.o hmac.c
gcc --std=gnu99 -Wall -O2 -g -fPIC -c  -fvisibility=hidden  -o sha1.o sha1.c
gcc -g   -o google-authenticator google-authenticator.o base32.o hmac.o sha1.o  -ldl
gcc --std=gnu99 -Wall -O2 -g -fPIC -c  -fvisibility=hidden  -o pam_google_authenticator.o pam_google_authenticator.c
gcc -shared -g   -o pam_google_authenticator.so pam_google_authenticator.o base32.o hmac.o sha1.o -lpam
gcc --std=gnu99 -Wall -O2 -g -fPIC -c  -fvisibility=hidden  -o demo.o demo.c
gcc -DDEMO --std=gnu99 -Wall -O2 -g -fPIC -c  -fvisibility=hidden  -o pam_google_authenticator_demo.o pam_google_authenticator.c
gcc -g   -rdynamic -o demo demo.o pam_google_authenticator_demo.o base32.o hmac.o sha1.o  -ldl
gcc -DTESTING --std=gnu99 -Wall -O2 -g -fPIC -c  -fvisibility=hidden        \
              -o pam_google_authenticator_testing.o pam_google_authenticator.c
gcc -shared -g   -o pam_google_authenticator_testing.so pam_google_authenticator_testing.o base32.o hmac.o sha1.o -lpam
gcc --std=gnu99 -Wall -O2 -g -fPIC -c  -fvisibility=hidden  -o pam_google_authenticator_unittest.o pam_google_authenticator_unittest.c
gcc -g   -rdynamic -o pam_google_authenticator_unittest pam_google_authenticator_unittest.o base32.o hmac.o sha1.o -lc  -ldl
cp pam_google_authenticator.so /lib64/security
cp google-authenticator /usr/local/bin
[root@kimball libpam-google-authenticator-1.0]#

e podemos verificar que os dois componentes foram instalados corretamente:

[root@kimball libpam-google-authenticator-1.0]# ls -lia /lib64/security/pam_google* /usr/local/bin/google*
1212428 -rwxr-xr-x 1 root root 116777 Aug  6 11:50 /lib64/security/pam_google_authenticator.so
1613197 -rwxr-xr-x 1 root root  55498 Aug  6 11:50 /usr/local/bin/google-authenticator
[root@kimball libpam-google-authenticator-1.0]#

Configuração

Temos a biblioteca PAM e o binário google-authenticator que iremos usar para gerar uma chave secreta para cada utilizador. Portanto este será o próximo passo. Não irei usar o utilizador "informix" porque o motor ignora sempre o PAM para conexões locais desse utilizador. Mas antes vamos apenas verificar o outro componente fundamental da solução: O seu smartphone. Atualmente utilizo Android, mas a aplicação está também disponível para iOS e Blackberry. Deverá saber como encontrar e instalar a App. Para dispositivos Android está disponível na loja Google Play. A App para Android e iOS suporta a introdução manual da chave secreta bem como a leitura de um código QR. Para Blackberry, segundo o website apenas suporta a introdução manual.
Assumindo que a App está devidamente instalada, podemos prosseguir com a configuração do lado do servidor. Como mencionado anteriormente o próximo passo é a geração da chave secreta que será partilhada entre o servidor e a aplicação móvel. Para isso usamos o binário google-authenticator instalado acima. Quando o executamos mostra a informação necessária e coloca também algumas questões. Não me vou debruçar sobre as mesmas, pois a documentação tem explicações completas:
-bash-3.2$ google-authenticator

Do you want authentication tokens to be time-based (y/n) y
https://www.google.com/chart?chs=200x200&chld=M|0&cht=qr&chl=otpauth://totp/fnunes@kimball%3Fsecret%3D6LWH24ZIUDY46HJU
Your new secret key is: 6LWH24ZIUDY46HJU
Your verification code is 649019

Your emergency scratch codes are:
  38268905
  24335468
  11497220
  81653596
  89796862

Do you want me to update your "/home/fnunes/.google_authenticator" file (y/n) y

Do you want to disallow multiple uses of the same authentication
token? This restricts you to one login about every 30s, but it increases
your chances to notice or even prevent man-in-the-middle attacks (y/n) n

By default, tokens are good for 30 seconds and in order to compensate for
possible time-skew between the client and the server, we allow an extra
token before and after the current time. If you experience problems with poor
time synchronization, you can increase the window from its default
size of 1:30min to about 4min. Do you want to do so (y/n) n

If the computer that you are logging into isn't hardened against brute-force
login attempts, you can enable rate-limiting for the authentication module.
By default, this limits attackers to no more than 3 login attempts every 30s.
Do you want to enable rate-limiting (y/n) y
-bash-3.2$

Como vemos mostra tanto um URL como a chave. Se tivermos uma biblioteca instalada (libqrencode) irá mostrar logo um código QR que podemos usar directamente com o telefone.
Se abrirmos o URL num browser a página obtida mostrará o mesmo código QR, que podemos usar na aplicação móvel para instalar a chave secreta. Alternativamente podemos introduzi-la manualmente. Qualquer das formas é bastante fácil e num instante terá um gerador de senhas descartáveis no seu telefone móvel:

Depois disto temos de configurar a nossa instância Informix para tirar proveito do mecanismo. Para tal irei criar um listener que use PAM. Isto é feito adicionando uma linha semelhante a esta ao $INFORMIXSQLHOSTS:

tpch_pam     onsoctcp             kimball.onlinedomus.net     1527   s=4,pam_serv=(ids_pam_service),pamauth=(challenge)

Campo por campo:
  • tpch_pam
    O INFORMIXSERVER para este alias
  • onsoctcp
    O protocolo de comunicação a usar
  • kimball.onlinedomus.net
    O nome da máquina
  • 1527
    O porto TCP único que será usado por este listener
  • s=4,pam_serv=(ids_pam_service),pamauth=(challenge)
    Campos de opções: s=4 indica a utilização de PAM. O nome do serviço PAM será "ids_pam_service" e o modo de funcionamento PAM será "challenge" 
Depois de alterado o $INFORMIXSQLHOSTS  temos de configurar um serviço PAM na máquina com o nome "ids_pam_service". Sendo Linux isto é feito criando um ficheiro com o mesmo nome em /etc/pam.d/ cujo conteúdo será:

auth       required     pam_unix.so
auth       required     pam_google_authenticator.so
account    required     pam_unix.so

Isto será usado para a nossa configuração "básica seguindo as regras". Mais tarde vou explicar alguns problemas deste método e mostrarei uma melhor forma de configurar. Como já mencionei noutros artigos apenas precisamos das linhas de configuração para "auth" e "account". Estamos a "empilhar módulos" (Google e pam_unix) para que tenhamos os "dois" fatores de autenticação. Primeiro iremos testar a senha Unix e depois o código de verificação Google (a senha descartável)

De seguida temos de garantir que "tpch_pam" está configurado no ficheiro $INFORMIXDIR/etc/$ONCONFIG no parâmetro DBSERVERALIAS e após isso podemos iniciar o listener com:

onmode -P start tpch_pam

Testes

Portanto, se tudo estiver bem configurado deverá funcionar. Vamos testar com o dbaccess:

tpch@kimball:fnunes-> id;hostname;echo $INFORMIXSERVER
uid=1002(fnunes) gid=1002(fnunes) groups=1002(fnunes)
kimball
tpch
tpch@kimball:fnunes-> dbaccess - -
> CONNECT TO 'sysmaster@tpch_pam' user 'fnunes';
   ENTER PASSWORD:
Password:
Verification code:

Connected.

> SELECT USER, * FROM syscfgtab WHERE cf_name LIKE 'DBSERVER%';



(expression)  fnunes
cf_id         2
cf_name       DBSERVERNAME
cf_flags      0
cf_original   tpch
cf_effective  tpch
cf_default    kimball

(expression)  fnunes
cf_id         77
cf_name       DBSERVERALIASES
cf_flags      0
cf_original   tpch_pam
cf_effective  tpch_pam
cf_default

2 row(s) retrieved.

> tpch@kimball:fnunes->

A primeira impressão é boa. Funciona. Mas há algo estranho. Se olharmos com cuidado vemos que assim que fazemos o CONNECT, o dbaccess pede-me para "ENTER PASSWORD:". Depois pede "Password:" e finalmente pede "Verification code:". Três pedidos... Espero que dois deles sejam óbvios, mas não o pedido de password repetido. Deixe-me explicar. Por omissão, sempre que tentamos um CONNECT o dbaccess pede uma senha. Esse é o primeiro pedido. Na verdade, com esta configuração (challenge mode) podemos introduzir o que quisermos... será ignorado. Depois entramos na pilha de módulos PAM. E o primeiro módulo que temos configurado é o pam_unix.so. Como não lhe estamos a passar nenhuma senha "internamente" (mais sobre isto adiante), ele pede-nos uma senha (pedido "Password:"). Aqui temos de inserir a senha de sistema do utilizador. Depois passamos para o segundo módulo, o do Google authenticator, que se comporta da mesma forma. Como não lhe é disponibilizada uma senha pede-a (pedido "Verification code:"). Nesta fase temos de olhar para o smartphone e transcrever o número que foi gerado para esta conta (a nossa senha descartável). Após isto, completa-se o login. Ambas as senhas foram validadas (uma por cada módulo).
Este comportamento justifica o challenge mode. O(s) módulo envia um desafio ao motor que o encaminha para o cliente (no nosso caso o dbaccess) e o cliente tem de ter uma função para lidar com estes desafios e as respostas do utilizador. Essa função designa-se por função de callback. No caso do dbaccess a função genérica apenas recebe o desafio, imprime-o, lê a resposta do utilizador e envia-a para o motor que depois responde ao módulo para verificação.
Embora isto funcione, é um pouco estranho e necessita que o cliente tenha uma função que lide com os desafios. Isto quer dizer que poderá ser necessário alterar a aplicação. O dbaccess já sabe o que fazer, mas outros clientes não saberiam.
As APIs do Informix têm funções para registar a função de callback. Mas de qualquer forma, alterações nunca são bem vindas... Vejamos o que podemos fazer.

Melhorias

Como já saberá (já o mencionei noutros artigos sobre PAM), o Informix pode ser configurado em modo "password" ou "challenge". A documentação por vezes leva-nos a pensar que para este tipo de implementação temos de usar challenge. Mas na realidade isto depende dos módulos que vamos usar e das opções que disponibilizam. No nosso caso verifiquei que o Google authenticator suporta duas opções interessantes:
  • try_first_pass
    obriga-o a verificar na infra-estrutura PAM se foi passada anteriormente uma senha
  • forward_pass
    torna-o inteligente... Se fornecermos uma senha que seja a concatenação da senha de sistema com o código de verificação, o módulo vai separá-los, verifica a parte dele, e passa o resto para os módulos seguintes
Notei igualmente que o módulo pam_unix.so também suporta as opções try_first_pass e use_first_pass. Portanto o que tudo isto sugere é que se tivermos uma senha de sistema "MinhaPass" e um código como "096712" poderia-mos usar uma senha composta "MinhaPass096712", dá-la como senha ao dbaccess e não nos preocupamos mais com os pedidos (desafios) de cada módulo. Vamos mudar a configuração e testar novamente. O ficheiro /etc/pam.d/ids_pam_service passa a ter:

auth       required     pam_google_authenticator.so try_first_pass forward_pass
auth       required     pam_unix.so use_first_pass
account    required     pam_unix.so

E no ficheiuro $INFORMIXSQLHOSTS mudamos "challenge" para "password":

tpch_pam     onsoctcp             kimball.onlinedomus.net     1527   s=4,pam_serv=(ids_pam_service),pamauth=(password)

E vamos re-iniciar o listener e testar:

tpch@kimball:root-> onmode -P restart tpch_pam


tpch@kimball:fnunes-> dbaccess - -
> CONNECT TO 'sysmaster@tpch_pam' user 'fnunes';
   ENTER PASSWORD:

Connected.

> SELECT USER, * FROM syscfgtab WHERE cf_name LIKE 'DBSERVER%';

(expression)  fnunes
cf_id         2
cf_name       DBSERVERNAME
cf_flags      0
cf_original   tpch
cf_effective  tpch
cf_default    kimball

(expression)  fnunes
cf_id         77
cf_name       DBSERVERALIASES
cf_flags      0
cf_original   tpch_pam
cf_effective  tpch_pam
cf_default

2 row(s) retrieved.

> tpch@kimball:fnunes->

E voilá....  O primeiro módulo agora passou a ser o Google authenticator. Recebe a senha "dupla" internamente pelo PAM (inicializado pelo motor), extrai a parte que lhe diz respeito (últimos seis caracteres), verifica esse código como senha descartável, e envia o resto para o segundo módulo (pam_unix) que valida a senha no sistema.
Entretanto... nós vimos que o dbaccess faz alguma mágica... Porque sabe que está a lidar com um porto PAM. Para ficarmos absolutamente seguros que este método é transparente para a aplicação, vamos tentar uma ligação externa com JDBC, que não terá qualquer noção que está a falar com um porto PAM:

Sucesso!
E não há muito mais a fazer. É básico e muito simples de configurar. Mostra a flexibilidade do Informix. E como usa PAM, podemos enriquecer a solução adicionando outros módulos se desejarmos

Considerações

Existem muitos aspetos a considerar neste assunto. Primeiro podemos imaginar os cenários de utilização para isto. Vêm-me algumas ideias à cabeça:
  • Segurança adicional para utilizadores priveligiados. Podemos assumir que as aplicações se conectam apenas por uma rede mais segura, utilizando a autenticação normal, mas os DBSAs podem ter de se ligar através de uma interface mais exposta que por isso justifique segurança adicional
  • Pode usar isto para trabalhar com uma senha "dupla" em que parte esteja disponível internamente na aplicação e os utilizadores apenas fornecem o código de verificação. Isto impediria os utilizadores de se autenticarem fora da aplicação (porque o utilizador não teria conhecimento sobre o outro componente da senha, ao mesmo tempo que o gestor aplicacional não poderia assumir a identidade do utilizador pois não teria o código de verificação)
  • Podem criar-se outros cenários bem mais complexos se estivermos dispostos a mexer no código do módulo
É importante ter sempre presente que como em qualquer assunto relacionado com segurança, temos de avaliar cuidadosamente as necessidades e discutir as possibilidades com pessoas que entendam bem os mecanismos de segurança. Tive uma espécie de brain storming com dois ex-DBAs Informix que agora trabalham em segurança e alguns pontos interessantes foram levantados. Entre eles aqui ficam alguns:
  1. Este tipo de gerador de senhas descartáveis em oposição aos dispositivos especializados como os tokens RSA
    • Possíveis vantagens deste método:
      • Provavelmente mais rapidamente se apercebe da perda de um telemóvel que de um equipamento especializado
      • É mais barato
      • Se o dispositivo necessitar de substituição, este método parece mais simples (o próprio utilizador pode fazê-lo após ter um novo telefone na sua posse)
      • Não existe dependência de um fornecedor externo
    • Possíveis desvantagens deste método:
      • Parece mais fácil atacar remotamente um smartphone que comprometer a segurança de um aparelho específico
      • A própria App poderia disponibilizar algum mecanismo para dificultar o acesso não autorizado aos códigos por parte de quem tenha acesso físico ao aparelho. Note-se que isto também é um problema para os dispositivos específicos, e num telefone este pode ser protegido por um PIN ou pela introdução de um padrão de autenticação. Não será necessariamente uma desvantagem deste método, mas algo que poderia ser melhorado
      • Um telefone está mais sujeito a problemas que afetem o funcionamento do gerador que um dispositivo específico
  2. Seria difícil utilizar este método em processos não interativos. A menos, e acredito que isto fosse possível, que se trabalhe ao contrário... Ou seja, que tenhamos o gerador de códigos dentro dos servidores aplicacionais, e que se configure uma função de callback que responda ao desafio do módulo. Isto poderia evitar a utilização do utilizador aplicacional fora do ambiente dos servidores aplicacionais
  3. Um problema genérico da autenticaçao de dois fatores é que idealmente o segundo fator deverá utilizar um canal diferente do primeiro. Tal não acontece aqui e isso permite que um ataque do tipo man in the middle possa ser feito.
  4. Outra questão que pode estar relacionada com a de cima é a possibilidade ou não de reutilizar o mesmo código (senha descartável) num determinado período de tempo ainda que curto. As senhas geradas pelo Google authenticator têm por omissão uma duração de 30 segundos (para permitir que o utilizador tenha tempo de a introduzir e também para compensar algum desfazamento de relógios). O módulo permite ainda usar o código imediatemente antes e depois, passando o intervalo para 1m30. Mas tudo isto pode ser controlado, e inclusivamente podemos limitar cada código a apenas uma utilização. Isto no entanto limitará o utilizador a não fazer mais que um login em cada 30 segundos

Problemas encontrados

Durante a preparação e testes destes artigo encontrei dois problemas principais:
  1. Nas primeiras tentativas obtive o erro "Invalid verification code" gerado pelo módulo. Como é referido na documentação, isto é habitualmente causado por problemas de sincronização de relógios entre o servidor e o lado da aplicação móvel. Após algumas verificações percebi que estava a fazer confusão entre a hora do relógio e a timezone. Isto causava uma diferença de relógios demasiado grande (o módulo permite alguma configuração para pequenas diferenças)
  2. Como é habitual com PAM e os seus módulos, a parte mais difícil é perceber os erros. A maioria dos módulos tendem a ser bastante "secretos" em relação às causas dos erros. Por exemplo, e não sei bem porquê, nas primeiras tentativas de concatenar as duas senhas, assumi que a ordem era a contrária (código + senha de sistema) em vez da correta (senha de sistema + código). Naturalmente não funcionava mas foi difícil perceber porquê. Acabei por alterar o código do módulo para introduzir mensagens que permitissem o debug
Outro possível problema é que actualmente não há versão da App para telefones Windows que aparentemente estão em crescimento no mercado (segundo números recentes venderam-se recentemente mais telefones Windows que Blackberry)

Agradecimentos

Não é inédito que eu discuta alguns aspetos enquanto escrevo estes artigos. Lembro-me de já ter tido ajuda de vários colegas da IBM, alguns já aqui mencionados algumas vezes. Neste caso tive o prazer de discutir este assunto com dois ex-DBAs Informix, com quem já trabalhei durante vários anos numa equipa de um cliente. Agora ambos trabalham na equipa de segurança e a primeira vez que o tema do Google authenticator me chamou a atenção foi durante uma conversa com um deles. Enquanto escrevia este artigo interagi com eles várias vezes. Vários aspetos deste artigo devem-se a essas conversas. Portanto, por isso e pelos bons tempos em que trabalhámos juntos (ocasionalmente ainda o fazemos, pois ambos têm uma longa história nesse cliente e é frequente os assuntos cruzarem-se), um grande "Obrigado!" ao Daniel Valente e Rui Mourão