12. File I/O and Operating System Access

Previous chapter

Next chapter

A GemStone application will generally need to interact with services provided outside of GemStone—for example, to work with text files, includes filing out and in source code and object representations. Other capabilities that rely on the operating system include communicating with other processes via sockets.

This chapter explains how to access and update disk files, execute operating system operations, and communicate over sockets to other machines.

Accessing Files
describes the protocol provided by class GsFile to open and close files, read their contents, and write to them.

Executing Operating System Commands
how to execute operating system commands from GemStone.

File In and File Out
filing out your application source code.

PassiveObject
describes the mechanism that GemStone provides for creating serialized versions of the objects that represent your data.

Creating and Using Sockets
describes the protocol provided by class GsSocket and GsSecureSocket to create operating system sockets and exchange data between two independent interface processes.

12.1 Accessing Files

The class GsFile provides the protocol to create and access operating system files. This section provides a few examples of the more common operations for text files. For a complete description of the functionality available, including the set of messages for manipulating binary files, see the comment for the class GsFile in the image.

Instances of GsFile understand most protocol common to Streams.

Specifying Files

Many of the methods in the class GsFile take as arguments a file specification, which is any string that constitutes a legal file specification in the operating system under which GemStone is running. Wildcard characters are legal in a file specification if they are legal in the operating system.

Many of the methods in the class GsFile distinguish between files on the client versus the server machine. In this context, the term client refers to the machine on which the interface is executing, and the server refers to the machine on which the Gem is executing. (This may not necessarily be the same machine on which the Stone is executing.) In the case of a linked interface, the interface and the Gem execute as a single process, so the client machine and the server machine are the same. In the case of an RPC interface, the interface and the Gem are separate processes, and the client machine can be different from the server machine.

Specifying Files Using Environment Variables

If you supply an environment variable instead of a full path when using the methods described in this chapter, the way in which the environment variable is expanded depends upon whether the process is running on the client or the server machine.

  • If you are running a linked interface or you are using methods that create processes on the server, the environment variables accessed by your GemStone Smalltalk methods are those defined in the shell under which the Gem process is running.
  • If you are running an RPC interface and using methods that create processes on a separate client machine, the environment variables are instead those defined by the remote user account on the client machine on which the application process is running.

NOTE
If you do not want to worry about these details, supply full path names and avoid the use of environment variables. This allows your application to work uniformly across different environments.

The examples in this section use a UNIX path as a file specification.

Creating a File

You can create a new operating system file from GemStone Smalltalk using several class methods for GsFile. Example 12.1 creates a file named aFileName in the current directory on the client machine.

Example 12.1

| myFile myFilePath | 
myFilePath := 'aFileName'. 
myFile := GsFile openWrite: myFilePath. 
"Here would go code to write data to the file"
myFile close
 

Example 12.2 creates a file named aFileName in the current directory on the server.

Example 12.2

myFile := GsFile openWriteOnServer: mySpec
"Here would go code to write data to the file"
myFile close
 

These methods return the instance of GsFile that was created, or nil if an error occurred. Common errors include invalid paths or insufficient permissions. To determine the specific problem, use the techniques described under GsFile Errors.

Opening a File

GsFile provides a wide variety of protocol to open files. For a complete set of methods, see the image. These methods return the GsFile instance if successful, or nil if an error occurs.

Table 12.1 GsFile Class Methods to Open Files

Method

Description

openRead:

openReadCompressed:

Opens a file on the client machine for reading.

openWrite:

openWriteCompressed:

 

Opens a file on the client machine for writing. Creates a new file if one does not exist, or truncates an existing file to 0.

openAppend:

Opens a file on the client machine for writing, appending the new contents instead of replacing the existing contents. Creates the file if it does not exist.

openUpdate:

Opens a file on the client machine for both reading and writing. Creates the file if it does not exist.

openReadOnServer:

openReadOnServerCompressed:

Opens a file on the server for reading.

openWriteOnServer:

openWriteOnServerCompressed:

Opens a file on the server for writing. Creates a new file if one does not exist, or truncates an existing file to 0.

openAppendOnServer:

Opens a file on the server for reading, appending the new contents instead of replacing the existing contents. Creates the file if it does not exist.

openUpdateOnServer:

Opens a file on the server for both reading and writing. Creates the file if it does not exist.

Closing a File or Files

The following methods close the current instance, or multiple open files:

Table 12.2 GsFile Method Summary

Method

Description

GsFile >> close

Closes the receiver.

GsFile class >> closeAll

Closes all open GsFile instances on the client machine except stdin, stdout, and stderr.

GsFile class >> closeAllOnServer

Closes all open GsFile instances on the server except stdin, stdout, and stderr.

Your operating system limits the number of files a process can concurrently access. Using GemStone classes to open, read or write, and close files does not lift your application’s responsibility for closing open files. Make sure you write and close files as soon as possible.

Writing to a File

After you have opened a file for writing, you can add new contents to it in several ways.

For example, the instance methods addAll: and nextPutAll: take strings as arguments and write the string to the end of the file specified by the receiver. The method add: takes a single character as argument and writes the character to the end of the file. And various methods such as cr, lf, and ff write specific characters to the end of the file—in this case, a carriage return, a line feed, and a form feed character, respectively.

The write methods return the number of bytes that were written to the file, or nil if an error occurs.

For example, the following code writes the two strings specified to the file myFile.txt, separated by end-of-line characters.

Example 12.3

myFile := GsFile openWrite: 'myFile.txt'.
myFile nextPutAll: 'All of us are in the gutter,'.
myFile cr.
myFile nextPutAll: 'but some of us are looking at the stars.'.
myFile close.
 
 

If the text you wish to write contains characters outside the ASCII range (that is, with codePoints greater than 127), then there are additional considerations.

Text with Characters with codePoints greater than 255 require more than one byte to represent. These must be encoded before they can be written to a GsFile. Encoding to UTF-8 is done by using GsFile >> nextPutAllUtf8: or by passing an instance of Utf8 to the write methods.

For example, the Euro character € has the Unicode value U+20AC.

Example 12.4 Writing the Extended Character € to a File

| myfile str | 
myfile := GsFile openWrite: 'extendedCharacterExample.txt'.
str := String new.
str add: 'How to write a Euro character '.
str add: (Character codePoint: 16r20AC).
str add: ' to a file'; lf.
myfile nextPutAllUtf8: str.
myfile close.
 

Text with Characters with codePoints in the range of 128..254 have ambiguity between the legacy output and UTF-8 encoding. Traditionally, GsFiles write Characters as byte data without encoding or decoding. Most modern systems encode files as UTF-8, and expect files to be encoded as UTF-8. However, to ensure legacy uses of GsFile do not create invalid Strings, the GsFile write methods continue to write data as bytes.

While unlikely, it is possible that Characters with codePoints in the range 128..254 could be either a portion of a UTF-8 encoding or an 8-bit character. Most often, specifying to default to the incorrect format will result in a badly formed UTF-8 error, or un-decoded bytes.

Since for ASCII Characters (codePoints in the 7-bit range), the legacy output and the UTF-8 encoding are the same, encoding all writes (and decoding all reads) is an effective way to remove ambiguity.

Reading from a File

Instances of GsFile can be accessed in many of the same ways as instances of Stream subclasses. Like streams, GsFile instances also include the notion of a position, or pointer into the file. When you first open a file, the pointer is positioned at the beginning of the file. Reading or writing elements of the file ordinarily repositions the pointer as if you were processing elements of a stream.

A variety of methods allow you to read some or all of the contents of a file from within GemStone Smalltalk. For example, the contents method (at the end of Example 12.3) returns the entire contents of the specified file and positions the pointer at the end of the file.

In Example 12.5, next: into: takes the 12 characters after the current pointer position and places them into the specified string object. It then advances the pointer by 12 characters.

Example 12.5

| result |
result := String new.
myFile := GsFile openRead: 'myFileName'.
myFile next: 12 into: result.
myFile close
result.
 

To read a file containing data encoded in UTF-8, you may read the file as usual, and then send decodeFromUTF8ToString or decodeFromUTF8ToUnicode to decode the results. Alternatively, you may use the method

GsFile >> contentsAsUtf8

which you can then decode from the instance of Utf8 similarly using decodeToString or decodeToUnicode.

Note that when reading files whose contents logically contain Characters with codePoints larger than 127, you must be aware of the whether the file is encoded in order to decode appropriately. GsFile reads the bytes and does not distinguish between encoded or un-encoded contents. A UTF-8 encoded file when read in using a GsFile and not explicitly decoded will be garbled, and a file written as 8-bit characters that you attempt to decode will almost always result in a badly formed UTF-8 error.

If the file will be read into GemStone using the topaz input command, you may include a header line in the output file, either:

fileformat utf8
fileformat 8bit

to instruct topaz of the file encoding.

Positioning

You can also reposition the pointer without reading characters, or peek at characters without repositioning the pointer. The method:

GsFile peek

allows you to view the next character in the file without advancing the pointer.

To advance the pointer without reading the intervening characters, use:

GsFile skip: anInteger

Testing Files

The class GsFile provides a variety of methods that allow you to determine facts about a file.

To test for existence of a file, use:

GsFile exists: aFileNameString
GsFile existsOnServer: aFileNameString

These methods returns true if the file exists, false if it does not, and nil if an error occurred.

Renaming Files

Files on the client or server can be renamed or moved. For example:

GsFile rename: '/tmp/myfile.txt' to: '/tmp/newname.txt'.
 
GsFile renameFileOnServer: '$GEMSTONE/data/system.conf' to:
'/users/david/mysystem.conf'.

Removing Files

To remove a file from the client machine, use an expression of the form:

GsFile closeAll.
GsFile removeClientFile: mySpec.

To remove a file from the server machine, use the method removeServerFile: instead. These methods return the receiver or nil if an error occurred.

Examining a Directory

To get a list of the names of files in a directory, send GsFile the message contentsOfDirectory: aFileSpec onClient: aBoolean. This message acts very much like the UNIX ls command, returning an array of file specifications for all entries in the directory.

If the argument to the onClient: keyword is true, GemStone searches on the client machine. If the argument is false, it searches on the server instead.

For example:

Example 12.6

GsFile contentsOfDirectory: '$GEMSTONE/examples/admin' onClient: false
%
an Array
  #1 /dbf/gsadmin/GS6435/examples/admin/.
  #2 /dbf/gsadmin/GS6435/examples/admin/..
  #3 /dbf/gsadmin/GS6435/examples/admin/onlinebackup.sh
  #4 /dbf/gsadmin/GS6435/examples/admin/archivelogs.sh 
 

If the argument is a directory name, this message returns the full pathnames of all files in the directory, as shown in Example 12.6. However, if the argument is a filename, this message returns the full pathnames of all files in the current directory that match the filename. The argument can contain wildcard characters such as *. The following example shows a different use of this message.

GsFile contentsOfDirectory: '$GEMSTONE/ver*' onClient: false
%
an Array
  #1 /dbf/gsadmin/GS6432/version.txt

If you wish to distinguish between files and directories, you can use the message contentsAndTypesOfDirectory:onClient: instead. This method returns an array of pairs of elements. After the name of the directory element, a value of true indicates a file; a value of false indicates a directory. For example:

Example 12.7

GsFile contentsAndTypesOfDirectory: '$GEMSTONE/ualib' onClient: false
%
a Array
  #1 /dbf/gsadmin/GS6433/ualib/.
  #2 false
  #3 /dbf/gsadmin/GS6433/ualib/..
  #4 false
  #5 /dbf/gsadmin/GS6433/ualib/liboraapi23-643.so
  #6 true
 

All the above methods, like most GsFile methods, return nil if an error occurs.

GsFile Errors

GsFile operations return nil in cases where an error occurs during the operation. For this reason, most GsFile operations should check for nil return. There are separate methods to check for errors within file operations on server files and client files.

To check for errors in an operation on a server file, the method is GsFile >> serverErrorString. It is nil if no error has occurred. This error is available until the next GsFile operation is executed.

Example 12.8

| myFile | 
myFile := GsFile openReadOnServer: 'nonexistentfile'. 
myFile isNil 
  ifTrue: [GsFile serverErrorString]
  ifFalse: ['Succesfully opened'].
%
errno=2,ENOENT, The file or directory specified cannot be found
 

To check for similar errors for a client file, use the method lastErrorString. For example:

Example 12.9

| myFile | 
myFile := GsFile openRead: 'privatefile'. 
myFile isNil 
  ifTrue: [GsFile lastErrorString]
  ifFalse: ['Succesfully opened'].
%
errno=13,EACCES, Authorization failure (permission denied)
 

12.2 Executing Operating System Commands

Simple Commands

System also understands the message performOnServer: aString, which causes the UNIX shell commands given in aString to execute in a subprocess of the current GemStone process. The output of the commands is returned as an instance of String. For example:

System performOnServer: 'date'
%
Mon Mar 11 15:19:56 PDT 2019

The commands in the argument to performOnServer: can have exactly the same form as a shell script; for example, new lines or semicolons can separate commands, and the character “\” can be used as an escape character. The string returned is whatever an equivalent shell command writes to stdout. If the command or commands cannot be executed successfully by the subprocess, the interpreter halts and GemStone returns an error message.

The GemStone (reverse) privilege NoPerformOnServer controls the ability to execute this method. If a user account is given this privilege, that user cannot execute performOnServer:.

The String returned from the performOnServer: command is always composed of 8-bit Characters. If the operating system command produces UTF-8 encoded results, then the Smalltalk String will be UTF-8 encoded. You will need to send decodeFromUTF8ToString or decodeFromUTF8ToUnicode to decode the results. performOnServer: may also return results as 8-bit non-encoded, extended ASCII, if that is what was returned by the operating system commands that were executed.

Using other shells

By default, performOnServer: executes using /bin/sh. To use other shells, specify the full path to the shell executabl using System class >> performOnServer: aString withShell: aShellOrNil.

Not all shells will work as an argument to performOnServer:withShell:. bash (/bin/bash), Korn Shell (/bin/ksh), Z Shell (/bin/zsh) and are known to work. C Shell (/bin/csh) and its variants (/bin/tcsh) are known to not work.

More complex interactions

System >> performOnServer: can execute arbitrary OS code on the server, but only operates synchronously; Smalltalk will block until the command has completed.

To provide an asynchronous perform, and to allow Smalltalk to read from stdout or write to stdin, you can use the class GsHostProcess.

To use this, use the class method fork:, passing the command line you wish to execute. This will return immediately with an instance of GsHostProcess with sockets on stdin, stdout, and stderr. You can use socket protocol to read from or write to these sockets.

Note that pathname resolution is not provided. You must fully qualify executable paths.

For example:

run
| hostprocess |
hostprocess := GsHostProcess fork: '/bin/date'.
hostprocess stdout read: 1024
%
Tue Mar 11 11:03:14 PDT 2017

The GsHostProcess can report its status using childStatus; you may kill the child process using killChild or killChild: timeoutSeconds.

12.3 Setting environment variables

You can ask about and set environment variables in the server or client environments using the following methods:

System class >> clientEnvironmentVariable: varName
System class >> clientEnvironmentVariable: varName put: valueStr
System class >> gemEnvironmentVariable: varName
System class >> gemEnvironmentVariable: varName put: valueStr 

These methods cannot be used if the NoGsFileOnServer privilege is set for the UserProfile.

For example,

run
System gemEnvironmentVariable: 'GS_MY_INDEX_KEY' put: '47'.
System clientEnvironmentVariable: 'GS_MY_KEY' put: '84'.
 
System performOnServer: 'env | grep GS_MY'
%
GS_MY_KEY=84
GS_MY_INDEX_KEY=47

When methods that accept a filename, such as GsFile methods, include an environment variable, environment variables set in this way are used in the expansion.

12.4 File In and File Out

To archive your application or transfer GemStone classes to another repository you can file out GemStone Smalltalk source code for classes and methods to a text file. To port your application to another repository, you can file in that text file, and the source code for your classes and methods is immediately available in the new repository.

Fileout

Methods in behavior allow you to file out a class, category, or method. For example, to file out a single class named Customer:

| myFile | 
myFile := GsFile openWrite: 'CustomerClassFileout.gs'. 
myFile isNil 
  ifTrue: [^GsFile serverErrorString].
Customer fileOutClassOn: myFile.
myFile close.
%

Using ClassOrganizer, you can file out all the classes and methods in a SymbolDictionary, ordered correctly for filein. For example, to file out UserGlobals:

| myFile | 
myFile := GsFile openWrite: 'UserGlobalsFileout.gs'. 
myFile isNil 
  ifTrue: [^GsFile serverErrorString].
ClassOrganizer new fileOutClassesAndMethodsInDictionary:
	UserGlobals on: myFile.
myFile close.
%

File out can also be done using the topaz command fileout. See the Topaz User’s Guide for more information.

Filein

File in is done using topaz input command, or facilities provided by GBS.

For example, to file in the fileout of UserGlobals from the previous example:

topaz 1> input UserGlobalsFileout.gs

12.5 PassiveObject

To archive your data, you can passivate objects themselves to a file. Objects representing your data are stored into a serialized, text-based form by the GemStone class PassiveObject. PassiveObject starts with a root object and traces through its instance variables, and their instance variables, recursively until it reaches special objects (instances of SmallInteger, Character, Boolean, SmallDouble, or UndefinedObject), or classes that can be reduced to special objects (strings and numbers that are not integers), creating a representation of the object that preserves all of the values required to re-create it. The resulting network of object descriptions can be written to a file, stream, or string. Each file can hold only one network—you cannot append additional networks to an existing passive object file, stream, or string.

A few objects and aspects of objects are not preserved:

The relationship between two objects is conserved only so long as they are described in the same network. Similarly, if two separate objects A and B both refer to the same third object C, then making A and B passive in two separate operations will result in duplicating the object C, which will be represented in both A’s and B’s network. Because the resulting network of objects can be quite large anyway, you want to avoid such unnecessary duplication. For this reason, it is usually a good idea to create one collection to hold all the objects you wish to preserve before invoking one of the PassiveObject methods.

In addition, since object identity is not preserved, behavior that depends on identity may not work as expected. For example, for objects that implement = using ==, the re-activated object will not be = to the original.

The class PassiveObject implements the method passivate: anObject toStream: aGsFileOrStream to write objects out to a stream or a file. To write the object AllEmployees out to the file allEmployees.obj in the current directory, execute an expression of the form shown in Example 12.10.

Example 12.10

| empFile |
empFile := GsFile openWriteOnServer: 'allEmployees.obj'.
PassiveObject passivate: AllEmployees toStream: empFile.
empFile close.
 

The class PassiveObject implements the method newOnStream: aGsFileOrStream to read objects from a stream or file into a repository. The method activate then restores the object to its previous form.

The following example reads the file allEmployees.obj into a GemStone repository:

Example 12.11

| empFile passivatedEmployees |
empFile := GsFile openReadOnServer: 'allEmployees.obj'.
passivatedEmployees := PassiveObject newOnStream: empFile.
AllEmployees := passivatedEmployees activate.
empFile close.
 

12.6 Creating and Using Sockets

Sockets open a connection between two processes, allowing a two-way exchange of data. The class GsSocket provides a mechanism for manipulating operating system sockets from within GemStone Smalltalk.

Methods in the class GsSocket do not use the terms client and server in the same way as the methods in class GsFile. Instead, these terms refer to the roles that two processes play with respect to the socket: the server process creates the socket, binds it to a port number, and listens for the client, while the client connects to an already created socket. Both client and server are processes created (or spawned) by a Gem process.

In addition to standard sockets created by GsSocket, you can create secure SSL sockets using the class GsSecureSocket. GsSecureSocket is a subclass of GsSocket that adds protocol to specify certificates and require authentication.

Both GsSocket and GsSecureSocket contain class methods such as clientExample and serverExample. These methods provide examples of how to create a socket connection between two sessions. The example methods work together; they require two separate sessions running from two independently executing interfaces, one running the server example and one running the client example. You can execute these methods from Topaz or from GemBuilder for Smalltalk, but note that serverExample, which should be started first, will take control of the interface until the clientExample completes the socket connection.

GsSocket

GsSocket is the class representing a basic socket.

Establishing the connection

To setup a socket connection, you create instances of GsSocket in both the client and server processes.

1. On the server side, create an instance of GsSocket, and call makeServerAtPort: This creates a listening socket on the given port.

To have the operating system select a port, use a wildcard bind using makeServer:, or pass nil as the port argument. You will then need to determine the port that the client should connect at using the port method.

2. On the client side, create an instance of a GsSocket and call one of the following:

  • connectTo: for a connection to a process on the same host
  • connectTo:on: if the server is on a different machine
  • connectTo:on:timeoutMs: to specify a timeout for the connection

Provided there was a listening server socket setup as in step 1, this will initiate the connection to the server.

3. The server then does an accept, or acceptTimeoutMs: (to specify a timeout) . This returns a new instance of GsSocket for the client connection.

Note that the server side has two sockets; a listening socket and the established socket with the client.

Communication on the socket

Each process can write and read to the socket using protocol such as write: and read:. See the image methods in the categories Reading and Writing for specific methods.

Writes and reads are of byte objects such as String or ByteArray. Read operations are for a specified number of bytes, and return the actual number of bytes read if fewer bytes were available (if fewer bytes were written to the socket by the peer). A return value of nil means an error occurred, and for read operations, a return value of 0 means the socket EOF was reached.

Closing the socket

When completed, the client should close its socket and the server close the listening and established sockets. This is done by simply sending close to the sockets.

Socket Configuration

Socket configuration can be done using the method

GsSocket >> option:put:

See the comments in this method for details on socket configuration.

The most common option is blocking.

Blocking

Sockets can be made blocking or non-blocking, and the blocking status checked, using the following methods:

GsSocket >> makeNonBlocking
GsSocket >> makeBlocking
GsSocket >> isNonBlocking
GsSocket >> isBlocking

GsSecureSocket

GsSecureSocket creates a secure socket Secure Sockets Layer (SSL), providing access to the open-source OpenSSL library. This product includes software developed by the OpenSSL Project for use in the OpenSSL Toolkit. (http://www.openssl.org/).

The protocol is Transport Layer Security (TLS), the successor to SSL; GemStone continues to use the term "SSL" in most cases, since SSL remains commonly used to refer to both,.

To create a secure socket, you create instances of the GsSecureSocket class and first establish the connection as a regular socket. Then, further protocol authenticates the connection to make the socket secure.

GsSecureSocket class protocol includes setup of certificates and types of authentication, which can be set for general socket operations, so each new socket does not have to be separately configured.

Certificates, keys, and passphrases

GsSecureSocket instances must be configured with the CA certificates, private key files, and passphrases, to allow them to complete the secure handshake. This can be done using class methods that apply to all new instances, or you can send instance methods to configure individual settings.

The required certificates, CA certificates, private key files and passphrases may be provided by your organization. The GemStone distribution includes example certificates, keys, and passphrases to verify your code, under the directories:

$GEMSTONE/examples/openssl/certs/
directory containing example public keys

$GEMSTONE/examples/openssl/private/
directory containing example private keys and passphrases

The distribution includes script that will allow you to generate certificates:

$GEMSTONE/examples/openssl/create_ca.sh
$GEMSTONE/examples/openssl/create_new_certs.sh

And the openssl executable, matching the version that GemStone uses:

$GEMSTONE/bin/openssl

Using this openssl executable, rather than any version that may be present on your system, is recommended. For details on the openssl interface, see http://www.openssl.org/docs/apps/openssl.html.

Enable or disable verifying CA Certificate

Certificate Authority (CA) certificates should be setup prior to creating instance of GsSecureSocket. An example CA certificate file is provided here:

$GEMSTONE/examples/openssl/certs/cacert.pem'

By default, client sockets verify the certificates presented by the server, and are configured with the CA certificate. By default, servers do not verify certificates. This is the usual case, and if this is your preferred behavior you do not need to explicitly enable or disable verification.

Class methods that configure verification must be executed prior to the creation of the client or server GsSecureSocket instance.

Client Sockets

By default, client sockets verify connections from the server. You will need to specify the client certificate file, or the directory containing CA certificates, using one of the following methods.

GsSecureSocket class >> useCACertificateFileForClients: certfile 
GsSecureSocket class >> useCACertificateDirectoryForClients: aDir

A CA certificate file must be in PEM format. It may contain more than one certificate. The certificate has effect only for the life of the session; the state is not committed to the repository.

To disable validation on the client, use the methods:

GsSecureSocket disableCertificateVerificationOnClient
aGsSecureClientSocket disableCertificateVerification

Parallel methods exist to re-enable validation after validation is disabled.

Server Sockets

By default, server sockets do not verify the certificate from the client, so you do not need to specify the CA certificate file or directory.

Setting the CA certificate file or directory also enables verification. To enable, execute one of the following:

GsSecureSocket class >> useCACertificateFileForServers: certfile
GsSecureSocket class >> useCACertificateDirectoryForServers: aDir

Parallel methods exist to disable validation after validation is enabled.

The server accepts additional verification options. Using the methods

GsSecureSocket setCertificateVerificationOptionsForServer: 
aGsSecureServerSocket setCertificateVerificationOptions:

The following options may be set:

#SSL_VERIFY_FAIL_IF_NO_PEER_CERT
if the client did not return a certificate, the TLS/SSL handshake is immediately terminated with a 'handshake failure' alert.

#SSL_VERIFY_CLIENT_ONCE
only request a client certificate on the initial TLS/SSL handshake. Do not ask for a client certificate again in case of a renegotiation.

Set certificate, private key, and passphrase

The certificate, private key, and private key passphrase can be setup by class methods to apply to all instances, or by sending messages to the instance of GsSecureSocket.

You can pass the certificate and private key either by using pathnames to the files, or by the string. If a string is used, it must exactly match the contents of the corresponding certificate file (including white space, line feeds, etc.), or the strings will not be accepted.

Both certificate and private key must be in PEM format, and the private key must match the certificate. The same file may be specified for the certificate file and the private key file.

The certificate may contain a certificate chain or a single certificate.

If the private key requires a passphrase, it must be specified as a string. If the private key does not require a passphrase, the argument is expected to be nil.

Example certificates, public and private keys, and passphrases are under the $GEMSTONE/examples/openssl/ directory. A number of examples are provided, for example:

$GEMSTONE/examples/openssl/certs/server_1_servercert.pem
$GEMSTONE/examples/openssl/private/server_1_serverkey.pem
$GEMSTONE/examples/openssl/private/server_1_server_passwd.txt
Class setup for server sockets

To specify the server certificates, private key file, and passphrase (if required), that will be used for all secure server sockets that are created after these methods are invoked, use the method:

GsSecureSocket class >> 
	useServerCertificateFile: certFilePathAndName
	withPrivateKeyFile: privateKeyFilePathAndName
	privateKeyPassphrase: passphraseStringOrNil

A variant is available that allows you to pass in the strings containing the certificate and key as string; see the image methods. If a string is used, it must exactly match the contents of the corresponding certificate file (including white space, line feeds, etc.), or the strings will not be accepted.

Class setup for client sockets

By default, certificates are not verified on the client, so the certificate and private key do not need to be setup for clients.

If you need to enable client validation, the follow methods specify the client certificates, private key file, and passphrase, that will be used to validate server connections for secure client sockets that are created after these methods are invoked:

GsSecureSocket class >> 
useClientCertificateFile: certFilePathAndName
	withPrivateKeyFile: privateKeyFilePathAndName 
privateKeyPassphraseFile: passphraseStringOrNil

A variant that accepts the content strings instead of filenames is available.

Instance setup for client or server sockets

You can specify the certificate, private key, and passphrase for a single specific instance of GsSecureSocket (either a server socket or a client socket), using instance method:

GsSecureSocket >> 
useCertificateFile: certFilePathAndName 
withPrivateKeyFile: privateKeyFilePathAndName 
privateKeyPassphraseFile: passphraseStringOrNil

Again, a variant that accepts the content strings instead of filenames is available.

Setup the Cipher list

The list of ciphers that are acceptable to use can be configured, either on the class side for servers and clients, or for specific instances of GsSecureSocket client or server sockets.

The cipher list is specified as a formatted string. See http://www.openssl.org/docs/apps/ciphers.html for details on the format of this string (as well as other information on ciphers).

For example, to use all ciphers except NULL ciphers and anonymous Diffie-Hellman (DH), and sort by strength, use the following string:

'ALL:!ADH:@STRENGTH'

To configure the cipher list for all instances of GsSecureSocket, use the following methods. These methods return true if the specification finds one or more usable ciphers, false if no usable ciphers match the specification.

GsSecureSocket class >> 
	setClientCipherListFromString: aString
GsSecureSocket class >> 
	setServerCipherListFromString: aString

To configure the cipher list for a specific instance of a server socket or client socket, the ciphers must be set before secureConnect:/secureAccept are executed. This method returns true if the specification finds one or more usable ciphers, false if no usable ciphers match the specification, and nil if the operation has no affect because the receiver is already connected.

GsSecureSocket class >> 
	setCipherListFromString: aString

Once an instance of GsSecureSocket is successfully connected, you can fetch the cipher in use using:

GsSecureSocket >> fetchCipherDescription

Establishing the connection

Rather than creating instances using GsSocket class >> new, with GsSecureSocket sockets are instantiated using newClient and newServer.

To establish the socket connection, as with regular GsSocket,

1. The server creates the socket using newServer, and calls makeServerAtPort: on an unused port, to create the server listener socket on that port.

2. The client creates the socket using newClient, and calls connectTo:, specify the same port as in Step 1.

3. The server socket calls accept, or acceptTimeoutMs:, which creates the connected socket on the given port.

This establishes the standard socket, but the connection is not secure. Another client-server interaction is required to make this a secure socket.

At this point, you can setup specific certificates and ciphers that will apply to these sockets only, as described in the preceding sections. This is needed if you have not previously set up certificates and ciphers that apply to all GsSecureSocket connections.

Then continue with the process that makes the socket secure:

4. The client socket calls secureConnect.

5. The server socket calls secureAccept, or secureAcceptTimeoutMs:.

If these methods return true, then the connection is secure. To determine if you have a secure connection, use the method:

GsSecureSocket >> hasSecureConnection

Communication on the socket

At this point reads and writes are done as for standard sockets.

Closing the socket

You can either close the socket connection entirely, or close the secure connection and remain connected for normal (not secure) communication.

To close the socket entirely, use

GsSecureSocket >> close

Which performs both the secure close and the regular close.

Note that the secure close requires a handshake. If the socket is blocking, and the peer does not respond, then the close will hang. To close the socket, we recommend first making it non blocking:

mySecureSocket makeNonBlocking.
mySecureSocket close.

To close only the secure socket and leave the connection available for non-secure communication, you can use the method

GsSecureSocket >> secureClose

Which must be executed by both sockets on the connection. You can then call close later, to close the connection entirely.

HTTPS connection

The image includes an example of using a GsSecureSocket to setup an HTTPS connection to a well known search engine URL, verifying of the server certificate, and performing a simple GET request. See the image for the example method:

GsSecureSocket class >> httpsClientExample

Error handling

GsSocket

Many GsSocket operations return nil if an error occurs.

On a nil return, you can query the system for the details on the error that occurred using the following methods. These methods are implemented both for the class and instance of GsSocket. For errors in GsSocket class methods, use the class side error methods, and for errors in GsSocket instance methods, use the instance methods

lastErrorString
Returns a String containing information about the last error or nil if no error has occurred. Clears the error information.

lastErrorCode
Returns an integer representing the last OS error or nil if no error has occurred. Does not clear the error information

lastErrorSymbol
Returns a Symbol representing the last OS error or nil if no error has occurred. Does not clear the error information.

Signalling errors

You can send raiseExceptionOnError: to a socket, in which case a SocketError is signalled in cases where a nil was returned from the C code.

GsSecureSocket

Most GsSecureSocket operations signal a SocketError or a SecureSocketError if an error occurs. Ordinary socket operations will signal a SocketError; errors in the security configuration will signal a SecureSocketError.

You can query for the last error from an instance operation using the instance methods:

GsSecureSocket >> lastErrorString

GsSecureSocket >> fetchLastIoErrorString
These methods fetch and clear the error string from a call to SSL functions for connect, accept, read, or write.

On the class side, the following methods return error strings for any SSL function call errors:

GsSecureSocket class >> fetchErrorStringArray
Returns an Array of error strings generated by the OpenSSL package. The errors returned are cleared from the SSL error queue. The array is ordered from oldest to newest error.

GsSecureSocket class >> lastErrorString
Returns a string describing the elements in the SSL error queue, based on the contents of fetchErrorStringArray. The errors are cleared from the error queue.

GsSecureSocket class >> fetchLastCertificateVerificationErrorForClient
GsSecureSocket class >> fetchLastCertificateVerificationErrorForServer

These methods fetch and clear a string representing the last certificate verification error logged by, respectively, a client SSL socket or a server SSL socket.

To clear the error queue, use the method

GsSecureSocket class >> clearErrorQueue

 

Previous chapter

Next chapter