Writing an Alphanumeric Pager Server for Linux
Alphanumeric pages—at least the type we will consider here—are delivered via modem. To deliver a page, we connect to our service provider, communicate with it through its specified protocol, receive a confirmation that the page has been delivered, then disconnect.
To send a message to someone, we need to know two things about them: their pager service dial-up number and their PIN (personal identification number). Alphanumeric pager owners should be able to contact their pager service provider and get this information with little trouble.
Several protocols are used for delivering pages. The most popular (and among the simplest) is IXO, named after the company which invented it. It's also been called TAP (Telocator Alpha-entry Protocol) and PET (Personal Entry Terminal); these names were assigned to it by other companies over its history.
In this article, I will talk solely about the IXO protocol. It is a bit involved so I won't go into great detail, but it is relatively straightforward. In brief summary, here is a description of the overall transaction between the remote dial-up site (us, when delivering a page) and the pager service provider.
First, we establish a connection and get the service's attention. After the service acknowledges our intent to transmit a message, we declare our wish to enter automatic mode—this is the simplest method through which to transmit a message, although in rare cases we may want to use manual mode.
We wait for the service to acknowledge our request. When we get the go-ahead signal to send the content of our message, we send a series of fields. Usually we would send two fields, consisting of the PIN of the pager to which we wish to send a message, and the message. We follow the message with a checksum, so the provider can verify (to some degree) that our message has been faithfully received. Finally, once we receive an acknowledgement that our message has been received, we politely request to be disconnected and drop carrier.
The IXO protocol (see Resources) supports sending multiple pages to the same service, but not necessarily the same pager, during a single transaction. Thus, this method provides the possibility for optimization in a large-scale service, if we find many pages are being sent concurrently to people using the same service, i.e., the same dial-up number.
To start with, we'll need a mechanism for identifying each pager to which we want to send messages; we'll call them profiles. As mentioned earlier, there are two things we need in order to be able to send messages to a particular pager: the phone number of its pager service and the PIN of that particular pager. These may be kept in configuration files, which an administration program can edit and delete.
If we want to support multiple modems—that is, the ability to deliver multiple pages simultaneously—we also need to know about and keep track of the physical modems. We'll call them devices. As with profiles, we'll keep a list of the devices allocated to our server in a configuration file.
Concurrent page delivery should take advantage of multiprocessing; use fork to send multiple pages simultaneously with different devices. This makes things much simpler in terms of the overall flow control and doesn't pose any significant problems, since we can simply check the exit status (succeed or fail) of the subprocess through waitpid and take action accordingly.
We'll keep the list of devices handy, and when we are delivering a page using one, flag it as busy. Unless we're going to be running on a system where the modems are dedicated solely to our server, we will almost certainly want to use standard lockfiles, in addition to flagging the devices as used. (Even if the modems truly are dedicated, it can't hurt to be extra careful.) This will prevent other processes from interfering with our server while it is actually in the process of delivering a page.
Alphanumeric pagers tend to be limited in terms of how many characters a message can display; the typical maximum-length support with the IXO protocol is 256 characters, including the PIN expressed as a string, so in practice it's usually closer to 250.
People who get a lot of pages frequently run into this limit, particularly when longer messages (such as e-mail or notes) are being sent. A useful solution is to implement a filter, which processes messages to be sent and does simple searches and replaces in order to substitute shorter equivalents for commonly appearing words and phrases. For instance, the word “and” might be represented with an ampersand (&), “talk to you later” might be abbreviated TTYL, and so on.
Filters could also be used to provide a form of security through obscurity. With most pagers, messages delivered over the airwaves are not secure; anyone with the right equipment can intercept them. This means, obviously, that the average person would probably not want their name, address and other private information being transmitted over the air. A simple choice of filters could replace this sensitive information with abbreviations recognizable to the party receiving the page, but meaningless to anyone else.
It would also be straightforward to implement a filter based on regular expressions, using the POSIX regex library. Since both the simple and regular expression searches are relatively straightforward, I will leave them as an exercise to the reader.
The server is the workhorse of our operation. Servers process pager delivery requests (we'll call them jobs) and then deliver them via the IXO protocol over an available device.
Jobs consist of two very simple things: the profile to use and the message to send. How the server receives each job depends on how we want to structure it. I'll discuss two basic approaches here: TELNET-based and file-based servers. Each has advantages and disadvantages, and your choice of which to implement will depend largely on what sort of clients you want to support.
TELNET-based servers listen to a specific port on the host machine and use a simple protocol to send the profile and the message. A protocol such as:
should suffice (provided, of course, that the strings identifying protocols don't contain newlines).
For security reasons, such servers should require some sort of authentication before accepting a job for delivery. The type of authentication we should use will depend strongly on our particular security needs, and is beyond the scope of this article.
A file-based server, on the other hand, watches a specific directory on the host machine (pausing for a brief length of time, then scanning its contents) and reads each new file, creates a job for it, then processes it; the file can be deleted when the server no longer needs it. The format of this file need be no different from the protocol of the TELNET-based server:
As with TELNET-based servers, file-based servers need to consider security issues. The server would run as a specific group, and only programs which are part of that group may submit a job. When job files are created, they must be owned by the group and given group-writeable permissions (in a group-writeable directory), so that the server may delete files when it is done with them. An authentication scheme would also probably be useful here; at the very least, some simple encryption may well be in order.
Both TELNET- and file-based servers have their respective strengths. The most obvious for a TELNET-based server is it is quick and simple, and a client can be run on any machine that can be reached by the server using the telnet command. (Without adequate security considerations, this can also be a liability.)
The clients to file-based servers must run on the same machine (disregarding NFS-mounted directories for the sake of simplicity) and must be run by a user who is a member of the appropriate group. The most obvious advantage to a file-based server is that in the case of a server shutdown or a machine reboot, the pending jobs will still be waiting, whereas TELNET-based jobs must offload their pending jobs to disk (or to another server) if they are shut down.
Clients are the programs we use to send jobs to the server. At this point, writing the clients should be easy, since by choosing the type of server we are going to implement, we have implicitly chosen the type of client we must use to communicate with it.
Clients need to know how to contact the server, as well as verify that a profile is valid (the client should know when it has been asked to send a message to a nonexistent pager). To start with, we need a basic client, one we can run using the command line (or perhaps STDIN) to specify the profile and message and deliver a job—no frills, no fancy interfaces.
For file-based servers, this is a program (executable only by members of the appropriate group) which creates a job file in the appropriate directory with the appropriate permissions. With TELNET-based servers, the program can be run on any machine which has TELNET access to the machine on which the server runs and delivers the message via TELNET.
Once we have the basic client, we can move on to other types of clients. Here are two we might want to support:
An e-mail-based client: something like procmail could help us do the difficult work here—procmail can selectively process incoming e-mail and route it to a program of our choice. That program could then pick out the relevant headers we want transmitted (e.g., From, To and Subject) and the body of the e-mail, then pass it on to the basic client.
A web-based client: a CGI script can do the job here, picking out appropriate form entities and passing them on to the client. In the file-based server, we must make sure that the user the web server runs as, often “nobody”, is also a member of the pager group. (Be sure to send an HUP signal to the server after adding it.)
In both cases, there are security issues to contend with. We certainly wouldn't want just anyone who has discovered the e-mail or web gateway to be able to send any page they wanted to anyone. The specifics regarding what sort of security precautions are advisable will depend strongly on the type of service we are running, so I will not go into detail about them here.
Now we can fit the pieces of the puzzle together to come up with our server.
The main server will read in configuration and data files (where to find things, the list of protocols it knows about, the available devices, etc.). It then goes into its main loop.
If we support multiple devices, use waitpid and its WNOHANG option (so that the server will merely see if any subprocesses have finished, as opposed to waiting indefinitely—and blocking all other server activities until a subprocess does finish) to determine whether any jobs have finished being delivered; remove them from the queue if they have.
Then, check to see whether a device is available and a new job is ready to be delivered; if both are true, then take the following steps. First, determine the profile and the message from the job. Then, get the next available device and lock it, so our delivery will not receive any interference. After that, filter the message (if appropriate), and finally, start up a subprocess to deliver it.
Erik Max Francis is a UNIX engineer who lives in San Jose, California. His main interests are programming, Linux, physics and mathematics. He's been using Linux at home exclusively since kernel version 1.2.8 and has been reading and contributing avidly to Usenet since 1989. He can be reached via e-mail by firstname.lastname@example.org.