Back when I wrote this article, I didn't think it would get such a huge response, but it did, hence, due to the mails coming from everywhere I decided to write again on that same topic adding some issues on which I was consulted and also some new ideas that occurred to me.
First of all, I wish to thank to "Edelmiro" who phoned me from Spain two days after the publishing in order to help me with a little problem in the coding of the examples.
Objectives
I will propose to enhance the classes WSendMail and Wsocket in order to get some fixes on them and add new functionality.
We will go through the following points:
Fix in WSocket class
In the first place, we must do a small fix to the Wsocket class, because the last article had a minor problem. It was that the Wsocket class worked in an asynchronous way, so, when sending an info pack to the server via the API function "send" it returned immediately, resulting in an asynchronous queuing of the information sending. Thus, when trying to send a new packet, there was no guarantee that the former one had been completely sent, so, the method "Sent" of the WSendMail class ended up believing that all required info was sent, which was not always correct.
In order to make the API "send" function work in synchronous mode, we should modify a socket option to have it working as we wish. We achieve this using another API function: "setsockopt".
optionValue = SO_SYNCHRONOUS_NONALERT IF( setsockopt( ; INVALID_SOCKET, ; SOL_SOCKET, ; SO_OPENTYPE, ; @optionValue, ; 4 ; && sizeof(int) ) == SOCKET_ERROR ) RETURN .F. ENDIF
This change should be added to the "CrearSock" method, thus making it work in a way that the synchronous mode will not produce non-controlled error messages.
Another critic modification that should be done is in the methods "Enviar" and "Recibir" [Send and Receive] where we need to call the "ioctlsocket" API function. As you may remember, we used this function to know whether the socket had pending info to receive or whether it was able to send, but the function leaves the socket in "nonblocking" state, meaning asynchronous; this makes it unfit for our purposes.
Once modified, the methods should look like this:
PROCEDURE CrearSock &CreateSock LOCAL wSpaceData LOCAL optionValue wSpaceData = SPACE( 400 ) && Space for struct WSAData *|-- Init WS2_32.DLL to use Sockets.- IF ( WSAStartup( THIS.MAKEWORD(1, 1), @wSpaceData) != 0 ) RETURN .F. && The OS can't init socket ENDIF optionValue = SO_SYNCHRONOUS_NONALERT IF( setsockopt( ; INVALID_SOCKET, ; SOL_SOCKET, ; SO_OPENTYPE, ; @optionValue, ; 4 ; && sizeof(int) ) == SOCKET_ERROR ) RETURN .F. ENDIF *|-- Get handle to OS' Socket THIS.m_HandleSocket = socket( This.m_SockFamily, This.m_SockType, 0 ) IF THIS.m_HandleSocket == INVALID_SOCKET RETURN .F. && The OS can't get handle of socket ENDIF ENDPROC PROCEDURE Enviar( msg ) &Send LOCAL slen, nlenMsg, nindex, smsgAux, bWait, nCountWait LOCAL lnControl bWait = .T. nCountWait = 0 nlenMsg = LEN( msg ) nindex = 0 slen = 0 IF( This.CheckBeforeSend ) nRead = 0x1 DO WHILE( bWait ) slen = ioctlsocket( THIS.m_HandleSocket, FIONBIO, @nRead ) IF ( slen != 0 ) && SOCKET_ERROR
IF( nCountWait > 5 ) RETURN .F. ELSE *|-- The server is busy.- nCountWait = nCountWait + 1 Sleep( 50 ) ENDIF ELSE bWait = .F. ENDIF ENDDO ENDIF smsgAux = msg nlenMsgGlobal = nlenMsg nlenMsg2 = nlenMsg Vuelta = 0 PaqEnv = 512 IF( This.PaqueteEnvio <= 0 ) PaqEnv = nlenMsgGlobal ELSE PaqEnv = This.PaqueteEnvio ENDIF DO WHILE( nlenMsgGlobal > 0 ) smsgAux = SUBSTR( msg, ( Vuelta * PaqEnv ) + 1, PaqEnv ) nlenMsg = LEN( smsgAux ) nlenMsg2 = nlenMsg DO WHILE( nlenMsg > 0 ) slen = send( THIS.m_HandleSocket, smsgAux, nlenMsg, This.SockSendCallMode ) IF ( slen < 1 ) RETURN .F. && Server Send failed.- ENDIF nlenMsg = nlenMsg - slen nindex = nindex + slen smsgAux = SUBSTR( msg, nindex, LEN( msg ) - nindex ) ENDDO nlenMsgGlobal = nlenMsgGlobal - nlenMsg2 Vuelta = Vuelta + 1 ENDDO RETURN .T. ENDPROC PROCEDURE Recibir() &Receive LOCAL buffer, retval, reply, nRead, slen, bWait, nCountWait bWait = .T. nCountWait = 0 IF( This.CheckBeforeRec ) nRead = 0x1 DO WHILE( bWait ) slen = ioctlsocket( THIS.m_HandleSocket, FIONREAD, @nRead )
IF ( slen == SOCKET_ERROR OR nRead <= 0 ) IF( nCountWait > 5 ) RETURN "" ELSE *|-- The server is busy.- nCountWait = nCountWait + 1 Sleep( 50 ) ENDIF ELSE bWait = .F. ENDIF ENDDO ENDIF reply = "" buffer = "" retval = 0 DO WHILE( retval = 0 ) buffer = SPACE( 4096 ) retval = recv( THIS.m_HandleSocket, @buffer, LEN( buffer ), 0 ) IF( retval == SOCKET_ERROR ) LOCAL nErr nErr = WSAGetLastError() ENDIF IF retval <> 0 AND retval <> SOCKET_ERROR reply = reply + LEFT( buffer, retval ) ENDIF ENDDO RETURN reply ENDPROC
As you can see, in the methods "Enviar" (Send) and "Recibir" (Receive), before calling the API function "ioctlsocket", the state of the CheckBeforeSend and CheckBeforeRec properties is checked. They are false by default, so that the state of the socket remains unmodified. You will also notice that comments are both in Spanish and English, according to the requests I received, since many people don't know enough Spanish. I tried my best in this issue, since my English is not too good.
The method "Enviar" (Send) has a change that will be apparent for those who read the former article, which consists in how the info packets are generated. Originally, the whole info was sent to the "send" and what couldn't be sent was processed in a later cycle, but there is a property now called "PaqueteEnvio" that defines the maximum size for the packet that will be sent to the server, and if it is bigger than the proper size, the string is truncated and is sent in a later round. The property "PaqueteEnvio" is set to -1 by default, so that it will try to send the info all at once, but if any server requires to set a maximum per packet, you have to modify this property.
New properties and inheritance in the WSendMail class
At this point, we will see the new properties that our mailing class will expose and how to use them. Then, we will change the inheritance for the class and discuss a bit why to do this.
New properties:
m_UsrSMTP: This is the user name that will be used to authenticate with the SMTP server.
m_PassSMTP: This is the password that will be used to authenticate with the SMTP server.
m_TipoAut: This property accepts one of two values.
m_UltMsg: Messages received from the SMTP server are stored here.
m_TipoMailPartID: This property accepts one of two values.
m_MailPartID: Holds the ID generated for the mail boundary.
m_SeparadorDir: Holds the character to separate the addresses set in the "m_Destinatarios" (recipients). By default, ";"
m_MIME_Version: Holds the MIME version for the mail to be sent. By default, "1.0"
m_SocketConn: Holds the connection socket object, the WSocket class.
m_ObjUtils: Holds the utilities object, the FoxUtils class.
m_HostName: Holds the name of the SMTP host we will connect to.
m_HostPort: Port of the SMTP host we will connect to, by default: 25.
Those who read my first article may be surprised to find the m_SocketConn, m_HostName and m_HostPort properties, formerly unneeded. The reason for this is simple, the "WSendMail" class does not inherit from "WSocket" (which provides the connection) anymore, instead, it uses it as a "Socket" object, just as "WinSock".
This simple change, as you will soon see, has a reason, and will give use certain benefits.
To control the commands sent to the eMail server
One of the greats weaknesses of the former version of the "WSendMail" class is that it did not validate the messages returned by the SMTP server, meaning each time we send a command to the SMTP server, this responds with a number and a descriptive message.
Let's see an example with telnet.
220 postino3.prima.com.ar ESMTP HELO 10.10.10.1 250 postino3.prima.com.ar MAIL FROM:<ianni_roberto@hotmail.com> 250 ok RCPT TO:<ianni_roberto@hotmail.com> 250 ok DATA 354 go ahead X-Sender: ianni_roberto@hotmail.com X-Mailer: M$ SendMail Build 1.0.0019 Date: Sat, 24 Jan 2004 13:45:00 0300 From: "My Name and Surname" Subject: Mail Test with Telnet To: "IANNI, ROBERTO" <ianni_roberto@hotmail.com> Prueba de texto en el cuerpo del mail (Email body) . 250 ok 1074969345 qp 85748 QUIT 221 postino3.prima.com.ar
So, looking at this old example we realize that when we connect to the SMTP we get a message "220 postino3...", this means that it IS an SMTP and it is ready to receive a message.
Then we can see that each command produces a response, being "HELO"; "MAIL FROM", "RCPT TO", "DATA", "QUIT", all of them trigger responses.
We will create a method called VerificarRespuesta [Verify Reply] in the WSendMail class to verify the commands. This method will have the responsibility of telling whether or not the response is correct.
#DEFINE CHK_220_REPLY "220" #DEFINE CHK_221_REPLY "221" #DEFINE CHK_235_REPLY "235" #DEFINE CHK_250_REPLY "250" #DEFINE CHK_334_REPLY "334" #DEFINE CHK_354_REPLY "354" PROTECTED PROCEDURE Helo ... ... THIS.m_SocketConn.Enviar( "HELO " + ALLTRIM( LocalHost ) + ENTER ) IF( !This.VerificarRespuesta( THIS.m_SocketConn.Recibir(), CHK_250_REPLY ) ) RETURN .F. ENDIF ... ... ENDPROC PROCEDURE VerificarRespuesta( lsString, lsToVerify ) &Verify Reply This.m_UltMsg = lsString RETURN( SUBSTR( lsString, 1, LEN(lsToVerify) ) == lsToVerify ) ENDPROC
You can see that this is quite simple, but we need no more to achieve our goal.
Sending email with authenticated SMTP
This is one of the most popular issues, according to the mails I received after publishing the first article. So, let us look into it.
Some SMTP servers support authentication. For some of them, authentication is optional and for others, it is mandatory. To deal with this, SMTP provides the EHLO and AUTH commands. An SMTP server that supports authentication will respond to the EHLO command with the code "250". What is the EHLO command for? It accomplishes the same function that HELO; to hail (contact) the server, but in addition, (since it is an extended command) it also inform in advance that it will use authentication.
Provided SMTP properly responded to EHLO, we are able to choose what kind of authentication to use. We will only explain the two more commonly used methods here, but there are others, depending on the SMTP server and its version.
For our examples, the AUTH command is used as follows:
After sending the proper AUTH and type, SMTP responds with the code "334" thus informing that the server supports that kind of authentication. Afterwards, we just need to send user and password without any other command; provided the server accepts the user, it returns "334" and if the password is right, the answer is the code "235"; at this point, we are already authenticated into the server. Let us see some examples.
Plain Authentication:
220 postino3.prima.com.ar ESMTP EHLO 10.10.10.1 250 postino3.prima.com.ar AUTH PLAIN 334 MyUser 334 MyPassword 235 QUIT 221 postino3.prima.com.ar
64base Autentificación:
220 postino3.prima.com.ar ESMTP EHLO 10.10.10.1 250 postino3.prima.com.ar AUTH LOGIN 334 TWlVc3Vhcmlv 334 TWlDbGF2ZQ== 235 QUIT 221 postino3.prima.com.ar
As you can see, plain authentication is pretty straight-forward; we just send the user name and password. However, with 64base authentication it is not so clear to the naked eye. In 64base, the user name and password are encrypted prior to be sent to the server validation. So, the actual string sent to the server was "TWlVc3Vhcmlv" while we really typed "MyUser" as we could see if the string got decrypted. And just the same happens with the password.
Since we need to implement this, we will code the encryption and decryption 64base functions for VFP. For this goal, let us first analyze how the Encode64 would be written in C++ because this is a more natural language for this job, and then create a similar routine in VFP.
char *m_base64Table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; bool InternalEncode64( const unsigned char *in, unsigned inLen, unsigned char *out, unsigned outmax) { unsigned olen = (inLen + 2) / 3 * 4; if (outmax < olen) return false; while (inLen >= 3) { *out++ = m_base64Table[in[0] >> 2]; *out++ = m_base64Table[((in[0] << 4) & 0x30) | (in[1] >> 4)]; *out++ = m_base64Table[((in[1] << 2) & 0x3c) | (in[2] >> 6)]; *out++ = m_base64Table[in[2] & 0x3f]; in += 3; inLen -= 3; } if (inLen > 0) { *out++ = m_base64Table[in[0] >> 2]; unsigned char oval = (in[0] << 4) & 0x30; if (inLen > 1) oval |= in[1] >> 4; *out++ = m_base64Table[oval]; *out++ = (inLen < 2) ? '=' : m_base64Table[(in[1] << 2) & 0x3c]; *out++ = '='; } if (olen < outmax) *out = '\0'; return true; }
Does it look entangled? Really it is not. 64base encryption relies on taking a certain character from the string, get its ASCII equivalent and modify that number so that it lies within the range 0 to 63.
Looking at the "m_base64Table" variable, we find a string that depicts how the characters will be converted, as is shown in the following table:
The 64base Alphabet
Well then, for those who don't know C++ I will briefly explain some of the operators used in the code:
For instance, decimal 50 is binary 110010, if we move the binary representation one position to the right, it becomes "11001" (last zero is gone) and that is the binary for decimal 25, so, it is the same as dividing 50 by 2. You may ask why we should not just code the division. Well, the reason is that to divide and multiply are quite more expensive operations for the processor than the binary shifting. Think about a very big number, divided by two, and again the result by two and so on for fifty times. Expensive, isn't it? This is not so with the binary shifting.
The former example should suffice, just change the direction from right to left.
For example, the numbers 3 and 6 are: 011 110 010 && 010 && would be the result for binary "And"
For example, the numbers 5 and 6 are: 101 110 111 && would be the result for Inclusive binary "Or"
m_base64Table[in[0] >> 2]
Then, the first character's ASCII value for the group is divided twice by two. If it was 256 (the biggest ASCII character), this operation would result into 64. Then, the position for that number is located in the array of Encode.
Now for the two remaining characters of the group of three, the procedure is as follows:
m_base64Table[((in[0] << 4) & 0x30) | (in[1] >> 4)]; m_base64Table[((in[1] << 2) & 0x3c) | (in[2] >> 6)]; m_base64Table[in[2] & 0x3f];
combining thus the first of them with the second, then obtaining the character in the array, then combining the second with the third and obtaining the position in the array, and finally, the third character operates a binary "And" with the mask 0x3f in order to obtain the character within the array. This cycle keeps repeating while there are at least three characters in the array available to process.
Then, the remaining characters are processed (if any) keeping in mind that if a remainder exists the last character must be "=".
Once Encode64 is understood, we can easily write it in VFP.
PROCEDURE Encode64( lsIn, lsOut ) LOCAL inlen, lsOut, lnInRel, oval inlen = LEN( lsIn ) olen = (inlen + 2) / 3 * 4 outmax = inlen * 2 lsOut = "" lnInRel = 1 IF( outmax < olen ) RETURN .F. ENDIF DO WHILE( inlen >= 3 ) lsOut = lsOut + This.m_base64Table[ (BITRSHIFT( ASC(SUBSTR( lsIn, lnInRel, 1 )), 2 )) + 1 ] lsOut = lsOut + This.m_base64Table[ (BITOR( BITAND( (BITLSHIFT( ASC( ; SUBSTR( lsIn, lnInRel, 1 )), 4 )), 0x30 ), ; BITRSHIFT( ASC(SUBSTR( lsIn, lnInRel + 1, 1 )), 4 ) )) + 1] lsOut = lsOut + This.m_base64Table[ (BITOR( BITAND( (BITLSHIFT( ; ASC(SUBSTR( lsIn, lnInRel + 1, 1 )), 2 )), 0x3C ), BITRSHIFT( ; ASC(SUBSTR( lsIn, lnInRel + 2, 1 )), 6 ) )) + 1] lsOut = lsOut + This.m_base64Table[ (BITAND( ASC(SUBSTR( ; lsIn, lnInRel + 2, 1 )), 0x3F )) + 1 ] lnInRel = lnInRel + 3 inlen = inlen - 3 ENDDO IF( inlen > 0 ) lsOut = lsOut + This.m_base64Table[ (BITRSHIFT( ASC(SUBSTR( lsIn, lnInRel, 1 )), 2 )) + 1 ] oval = BITAND( BITLSHIFT( ASC(SUBSTR( lsIn, lnInRel, 1 )), 4 ), 0x30 ) IF( inlen > 1 ) oval = BITOR( BITRSHIFT( ASC(SUBSTR( lsIn, lnInRel + 1, 1 )), 4 ), oval ) ENDIF lsOut = lsOut + This.m_base64Table[ oval + 1 ] lsOut = lsOut + IIF( (inlen < 2), '=', This.m_base64Table[ (BITAND( ; BITLSHIFT( ASC(SUBSTR( lsIn, lnInRel + 1, 1 )), 2 ), 0x3C )) + 1 ] ) lsOut = lsOut + '=' ENDIF RETURN .T. ENDPROC
As we can see in the VFP code, the logic is the same but the way to perform the calculations varies. VFP lacks the operators ">>", "<<", "|", "&", but equivalent functions are available: BITRSHIFT, BITLSHIFT, BITOR, BITAND.
Thus, replacing the operators with the functions, the work gets done. Of course, being functions, they work much slower than an operator, so the code is not efficient for very long strings, but since our goal is to encrypt just one user and one password, it is good enough.
Now, with the encryption ready, we may create a new method in the WSendMail class that performs the authentication in the SMTP server.
PROTECTED PROCEDURE Login LOCAL lsPass, lsUsr IF( EMPTY( This.m_UsrSMTP ) AND EMPTY( This.m_PassSMTP ) ) RETURN .T. ENDIF DO CASE CASE This.m_TipoAut == 0 THIS.m_SocketConn.Enviar( "AUTH PLAIN" + ENTER ) IF( !This.VerificarRespuesta( THIS.m_SocketConn.Recibir(), CHK_334_REPLY ) ) RETURN .F. ENDIF lsUsr = This.m_UsrSMTP lsPass = This.m_PassSMTP CASE This.m_TipoAut == 1 THIS.m_SocketConn.Enviar( "AUTH LOGIN" + ENTER ) IF( !This.VerificarRespuesta( THIS.m_SocketConn.Recibir(), CHK_334_REPLY ) ) RETURN .F. ENDIF IF( This.m_ObjUtils.Encode64( This.m_UsrSMTP, @lsUsr ) ) IF( !This.m_ObjUtils.Encode64( This.m_PassSMTP, @lsPass ) ) RETURN .F. ENDIF ELSE RETURN .F. ENDIF ENDCASE *|-- Envía el usuario.- *|-- Sends the user name.- THIS.m_SocketConn.Enviar( lsUsr + ENTER ) IF( !This.VerificarRespuesta( THIS.m_SocketConn.Recibir(), CHK_334_REPLY ) ) RETURN .F. ENDIF *|-- Envia el password.- *|-- Sends the password.- THIS.m_SocketConn.Enviar( lsPass + ENTER ) IF( !This.VerificarRespuesta( THIS.m_SocketConn.Recibir(), CHK_235_REPLY ) ) RETURN .F. ENDIF RETURN .T. ENDPROC
As you can see, the implementation is quite simple; we check first that a user and password are actually defined in the class, and then the type of authentication to be used. If it is plain, we send the data to the server just as it is. If it is 64base instead, we process the data with the "Encode64" before they get sent.
Generation of the ID for the mail
In order to generate the identification for the boundaries of the mail, we will implement two alternatives, the first one to generate GUIDs and the other to generate UUIDs. You will surely ask why to invest time to generate an ID for the boundary of the mail, so let us see the image.
Figure 1: Parts of the Body
The ID that defines the boundary is useful to separate the mail's blocks; if we use a separador defined by the user in the subject or the body of the message, is quite possible that the program that receives the mail gets confused with the boundaries thus misunderstanding something that is actually a text and taking it as a boundary.
We will use in order to generate the GUID the API called CoCreateGuid and StringFromGUID2, whose definition is as follows:
HRESULT CoCreateGuid( GUID *pguid //Pointer to the GUID on return );
pguid: Pointer where the new GUID will be returned
int StringFromGUID2( REFGUID rguid, //GUID to be converted LPOLESTR lpsz, //Pointer to resulting string int cchMax //Size of array at lpsz );
rguid: The GUID we want to convert to string.
lpsz: The pointer to the string where the converted GUID will be stored.
cchMax: Maximum size for the variable lpsz.
DECLARE INTEGER CoCreateGuid IN "ole32" ; STRING @pGuid
DECLARE INTEGER StringFromGUID2 IN "ole32" ; STRING rGuid, STRING @lpsz, INTEGER cchMax PROCEDURE CrearGuid LOCAL lsGuid, lsGGuid lsGuid = SPACE(16) lsGGuid = REPLICATE( CHR(0), 76 ) *|-- Creates the GUID and converts it to a string.- CoCreateGuid( @lsGuid ) StringFromGUID2( lsGuid, @lsGGuid, 76 ) lsGGuid = STRTRAN( lsGGuid, CHR(0) ) lsGGuid = SUBSTR( lsGGuid, 2, 36 ) RETURN lsGGuid ENDPROC
APIs in general are easy to use and as we see in the CrearGuid method, what we do is to define a variable 16 spaces long to be able to call the CoCreateGuid API function and then we convert it with the StringFromGUID2 function.
Then, we locate the end of the string, given by CHR(0) and remove the first and last character from the result, since a GUID looks something like this: {F57E0FC1-17CC-49ED-A3A5-CCF10E08BDA1}, and we are only interested in the unique numbers: F57E0FC1-17CC-49ED-A3A5-CCF10E08BDA1
The GUID separators are the "-" characters, and since we need a "_" character for separador, we'll create a new function to perform the replacement.
PROCEDURE ObtenerGuid LOCAL lsGuid lsGuid = This.CrearGuid() RETURN STRTRAN( lsGuid, "-", "_" ) ENDPROC
The ObtenerGuid (GetGuid) method, is in charge of creating the GUID and returning it ready for use in the eMail.
Now, to generate a UUID we will just use the API UUIDCreate function, which has the following definition:
RPC_STATUS RPC_ENTRY UuidCreate( UUID *Uuid );
Uuid: String type pointer, where the new UUID will be allocated
PROCEDURE CrearUuid( lsUIDPart1, lsUIDPart2, lsUIDPart3, lsUIDPart4 ) LOCAL lsUUID lsUUID = SPACE( 16 ) *|-- Generates UUID.- UUIDCreate( @lsUUID ) *|-- Convert values from binary to hex.- lsUIDPart1 = This.BinTOHex( SUBSTR( lsUUID, 1, 4 ) )
lsUIDPart2 = This.BinTOHex( SUBSTR( lsUUID, 5, 4 ) ) lsUIDPart3 = This.BinTOHex( SUBSTR( lsUUID, 8, 4 ) ) lsUIDPart4 = This.BinTOHex( SUBSTR( lsUUID, 13, 4 ) ) ENDPROC
The UUIDCreate API function returns a string with the four parts of the ID but with such a conformation that we can not see them normally as if they were numbers. Hence, you need to take four bits of the string and convert them to a number that VFP is able to understand. The BinTOHex method does that. Finally, the combination of the four parts of the UUID conform the ID.
PROCEDURE ObtenerUuid LOCAL lsUIDPart1, lsUIDPart2, lsUIDPart3, lsUIDPart4 This.CrearUuid( @lsUIDPart1, @lsUIDPart2, @lsUIDPart3, @lsUIDPart4 ) RETURN lsUIDPart1 + "_" + lsUIDPart2 + "_" + lsUIDPart3 + "_" + lsUIDPart4 ENDPROC
I considered it was not worth-while to implement the ID generator functions in VFP or C++ since APIs are available to do the job, but if someone is interested in a deeper insight in the ID creation theory, they may take a look at the following url http://www.opengroup.org/dce/info/draft-leach-uuids-guids-01.txt.
To be continued...
In the following and last issue of this article, we will see the rest of the functionality required to finish the implementation of the class, including:
By the way, let me tell you that I started to develop a system for sending and receiving mails, based upon these classes, via SMTP and POP3, called "FoxLook".
This client will run in Win32 and ASP.NET (C#) modes; both for Intranet and Internet, using the FOX classes as the core of both projects. The project will be OpenSource as well as the classes, and I intend that everyone who is interested to participate, feel free to do so. At the beginning, I will concentrate all the code via mail at my address and afterwards, the idea is to upload it to some web site where it can be shared.