Table of Contents Previous Next Index

Section 6 The LISTSERV TCPGUI Interface

Section 6 The LISTSERV TCPGUI Interface
The TCPGUI interface is the part of LISTSERV that listens for TCP/IP connections coming into the LISTSERV port on the LISTSERV host. To use the TCPGUI interface, you set up TCPGUI, then you send commands to it through TCP/IP. You have two options for sending commands to LISTSERV through TCP/IP:
Most simple commands can be sent directly through the TCPGUI interface, but some advanced operations require special handling.
6.1 Setting Up the TCPGUI Interface
By default, LISTSERV listens to port 2306 on all the IP addresses assigned to the local host.
If it needs to be restricted to a given IP address, or if port 2306 is already in use by another application, LISTSERV must be set up so that it listens to the correct IP address and port.
To specify an IP address that LISTSERV should listen to, you need to add the TCPGUI_IPADDR parameter to your site configuration file.
For example, under Windows NT, you would add the following line to the SITE.CFG file:
TCPGUI_IPADDR=nnn.nnn.nn.nn
where nnn.nnn.nn.nn is the IP address assigned to the LISTSERV host.
You can instruct LISTSERV to listen to a different port by setting the TCPGUI_PORT parameter to an unused port number. For example:
TCPGUI_PORT=nnnn
Recall that you need to stop and restart LISTSERV for site configuration changes to take effect.
6.2 Running lcmdx
The lcmdx program (C language code at the end of the chapter) allows you to send a LISTSERV command from a command line (DOS, shell, or DCL prompt). Compile and link it (you may need to make minor changes to get it to compile, depending on your C compiler) to produce an executable.
Note: Under Solaris, most network programs require you to pass the ‘-lsocket -lnsl’ flags to the compiler when compiling. If this is not done, then compiling LCMDX.C under Solaris will fail with network-related library errors.
You can run lcmdx from any computer that is connected via TCP/IP to the LISTSERV host (i.e. via the Internet or a TCP/IP-based intranet).
You need to have a password registered for your email address in LISTSERV in order to send commands via TCP/IP. Instructions for registering a password are in the List Owner's Manual.
The format for the lcmdx command line is:
lcmdx hostname[:port] address password command
where
hostname is the DNS name of the LISTSERV host,
command is the one-line LISTSERV command.
Note: The password parameter MUST imperatively be typed in UPPER CASE.
For example, if francoise@lsoft.com wants to set her subscription to the MSVC list to digest, and her password on PEACH.EASE.LSOFT.COM (where this list is hosted) is "ABCDE", she can use the following command:
lcmdx PEACH.EASE.LSOFT.COM francoise@lsoft.com ABCDE SET MSVC DIGEST
LISTSERV responds:
Your subscription options have been successfully updated. You are being mailed a short report with the exact settings now in use for your subscription. Please take a few moments to check that this is indeed what you wanted.
Any one-line command can be submitted to LISTSERV in this way. To use lcmdx directly from your application, just spawn a subprocess or send the lcmdx command using whatever means is provided by the operating system under which your application is running.
6.3 Sending LISTSERV Commands Directly From Your Application
The lcmdx program is convenient, as it doesn't require programming to use, but it does not allow much flexibility in how your application can react to LISTSERV's responses to the commands sent. For greater flexibility, you can integrate the techniques used in lcmdx for communicating with LISTSERV directly into your application.
The lcmdx source code consists of three C functions:
receive - used by LSV_send_command to receive a string of a given length from a socket
LSV_send_command - the function that sends a command from LISTSERV and returns the answer; this function can be used directly by your application with few or no changes.
main - the main function simply parses the command line for lcmdx into its component parts, passes them to LSV_send_command, and prints LISTSERV's response; your application would replace main.
The main change that you may want to make to the LSV_send_command function, will be to have it write the LISTSERV response to a string or an array of strings rather than to a file, as is the case in lcmdx. What you actually do with LISTSERV's response will be dictated by the needs of your application.
The only other changes that might be required are changes that relate to how sockets are implemented on your operating system. For example, if you are using Windows sockets (WINSOCK), you would have to add the Windows sockets initialization code at the start of your application, calling and checking the status of the WSAStartup routine; and the cleanup code at the end, calling the routine WSACleanup. With Windows sockets, you would also have to include the header file <winsock.h> instead of <sys/socket.h>, <netdb.h>, and <netinet/in.h>; and replace the call to close(ss) with a call to closesocket(ss).
The LSV_send_command function uses the following calling sequence:
int LSV_send_command(char *hostname, unsigned short port,
char *origin, char *pw, char *command, FILE *writeto)
where the return value is:
0 if the command was sent and the answer received without a problem, from a TCP/IP point of view - this does not indicate that the command itself was successfully executed by LISTSERV: your application needs to look at the answer received from LISTSERV to determine whether the command itself was successful;
a socket error code if a socket error occurred while communicating with LISTSERV - the socket errors should be described in the documentation for socket functions for your C compiler.
and the parameters are:
hostname - a pointer to a character array containing the name of the LISTSERV host to which to send the command
port - the port number to which to connect (use 2306 unless otherwise specified in the site configuration file of the LISTSERV host)
origin - a pointer to a character array containing the e-mail address of the "originator" of the command
pw - a pointer to a character array containing the LISTSERV password registered for the originator's e-mail address (must be UPPER CASE)
command - a pointer to a character array containing the one-line command to send to LISTSERV
writeto - a pointer to the file in which to write LISTSERV's response; as mentioned above, depending on the needs of your application, you may want to change this parameter and the code within LSV_send_command that writes to this file.
Of course, if you use LSV_send_command without modification, you will be opening and closing a connection to LISTSERV for each command you send. You can make your application more efficient by opening up a connection to LISTSERV and sending a series of commands before closing the connection again. The LSV_send_command function will work "out of the box", but an experienced programmer can use it as a guide for developing customized functions for working with the TCPGUI interface.
6.4 Advanced TCPGUI Programming Issues
The technique described above, using the LSV_send_command function to send commands over TCP/IP to LISTSERV, will work for most one-line LISTSERV commands, such as ADD, DELETE, SET, SHOW, etc. However, some commands require a different or modified approach:
There may also be some special error handling involved.
6.4.1 Creating or Replacing a List Header
Multi-line commands cannot be sent through the TCPGUI interface, therefore the usual approach for sending a list header to LISTSERV (the PUT command followed by the multiple lines of the header) cannot be used. Instead, TCPGUI has a special command for sending a list header: X-STL.
The syntax of the X-STL command is:
X-STL listname header-data
where header-data is a text string that contains every header line (including the leading asterisk), one after the other, each preceded by a count of the characters on the line, followed by an underscore character. The last header line should not have any trailing spaces.
Thus, if the command you would use to put your list through e-mail was:
PUT listname PW=password
* test1
* Owner=francoise@lsoft.com
* Notebook=No
* Confidential=Yes
The command you send through the TCPGUI interface would be:
X-STL listname 6_* test1_*28_* Owner= francoise@lsoft.com13_*
Notebook=No18_* Confidential=Yes
(Remember that this is a one-line command.)
Obviously, you need to use the "create" password when creating a new header, and an owner's or postmaster's personal password when replacing an existing header.
Note: If double quotes are required (for instance, for the Prime= or Sender= keyword settings), you MUST escape them with a backslash character, thus:
lcmdx listserv.example.com xxxxxxxxx@example.com XXXXX X-STL XXXXXXXXX 6_* test1_*31_* Owner= xxxxxxxxx@example.com13_* Notebook=No18_* Confidential=Yes49_* Sender= \"test <xxxxxxxxx@listserv.example.com>\"
(remember that the entire command must be on one line, not wrapped as is unavoidable in this document). Additionally, when counting characters for the line counts, the backslash-double quote combination counts as a single character. If the line count is wrong, you will get an "Error in header data stream" error.
6.4.2 Adding or Replacing a Password
You can’t send a "PW ADD" or "PW REP" command through the TCPGUI interface. There is a special command for adding a password:
X-PWADD e-mail-address password
The LSV_send_command function generally requires a password, which you don’t necessarily have when you’re in the process of adding one. In this case, you can send the command anonymously through TCPGUI by using the anonymous e-mail address "@" as the "originator" in the call to LSV_send_command (but not in the X-PWADD command, obviously).
When LISTSERV receives the X-PWADD command, it sends e-mail to the given e-mail-address requesting confirmation. E-mail confirmation is the only way for LISTSERV to determine that the e-mail address provided is truly the correct address. Therefore, an application should never count on the password being immediately available. A message to the user, letting them know that they can continue after they have successfully confirmed the password registration, may be advisable.
6.4.3 Bulk Operations
As noted above, the TCPGUI interface can only handle single-line commands. Therefore bulk operations, such as the bulk add and delete commands cannot be sent through TCPGUI, and can only be sent through the mail. The only ways to send bulk adds and deletes from an application are:
Turn them into individual add or delete commands, and send each of these through the TCPGUI interface. If there are many subscribers to add or delete, this can extremely slow, and is therefore not recommended.
Open up a connection with the SMTP port on the LISTSERV host and use SMTP commands (documented in RFC821) to send a mail file (documented in RFC822) containing the bulk add or delete job. This is essentially the same thing as the previous bullet, except that in the latter case, you used a third-party application to send the file, whereas with this method you must write your own.
6.4.4 Commands that Respond Over Email
LISTSERV will accept almost any one-line command through the TCPGUI interface. The answer you receive through the TCPGUI interface, however, may not always be what you expected. Commands whose responses tend to be long will generally be sent through e-mail. LISTSERV will always send the response to the following commands back through e-mail:
For these other commands, LISTSERV will send the response back through e-mail unless you use an option to make it come back through the TCPGUI interface:
6.4.5 Application-Friendly Commands
Some commands that can be sent through the TCPGUI interface and to which LISTSERV will send the response back through the TCPGUI interface nevertheless have responses that are human-friendly but not application-friendly. One such command is the QUERY command, which sends a response that looks like:
Subscription options for Francoise Becker , list MYLIST:
 
MAIL You are sent individual postings as they are received
MIME You prefer to receive messages in MIME format
SUBJECTHDR Full (normal) mail headers with list name in message subject
REPRO You receive a copy of your own postings
NOACK No acknowledgement of successfully processed postings
 
Subscription date: 12 Mar 1997
 
The topics you subscribe to are: Mytopic, Other
 
To assist the application developer, the LISTSERV provides alternate commands for the following:
6.4.5.1 QUERY
To QUERY subscriber options from an application you should use:
QUERY ***GUI*** listname [FOR e-mail-address]
The response will look like the following:
 
***HDR*** e-mail-address
***NAME*** firstname lastname
 
***OPT*** option1
***OPT*** option2
...
***OPT*** optionN
 
***SUBDATE*** date
***TOPICS*** subscriber-topics
***TOPLIST*** list-topics
***HDR*** e-mail-address (next subscriber)
etc.
 
N matching entries found.
Where:
The "***HDR***" line denotes the beginning of the subscription settings for a particular subscriber (recall that the QUERY command could yield information for multiple subscriptions) and identifies the e-mail address of the subscription.
The "***OPT***" lines each show one option set for the subscription. The first option would always be MAIL or NOMAIL. All the other options are only those options that are NOT the default options in LISTSERV (as opposed to the default options set for the particular list -- these do not apply here).
The "***TOPLIST***" line lists all the topics that are available for the list, regardless of whether this particular subscription has them selected, except for "ALL" and "OTHER".
6.4.5.2 QUERY DEFSUB
There is also a special QUERY command for obtaining default subscription options:
QUERY ***GUI*** ***DEFSUB*** listname
If this command is sent using an address that is subscribed to the given list, it works exactly the same as the "QUERY ***GUI***" command described above. If it is sent anonymously (see "Adding or replacing a password" above) or from an address which is not subscribed, then the first line in the response is:
***DEF***
And the remainder of the response is the same as the response to "QUERY ***GUI***" for a subscriber with the list’s default subscription settings.
6.4.5.3 SCAN
To scan a list for a pattern, you should use
SCAN ***GUI*** listname pattern
The response will look like the following:
***MBX*** user1@host.domain
Firstname Lastname <user1@host.domain>
***MBX*** user2@host2.domain
"Full name w/ special characters" <user2@host2.domain>
etc.
***END***
SCAN: N matches.
6.4.6 Error handling
When the command you send produces an error the TCPGUI interface sends back the exact error that you would receive through the mail when that error is detected by LISTSERV (for example, if you use the wrong listname, or misspell a command). However, the TCPGUI interface does have some error messages specific to it, for when the error occurs within TCPGUI rather than LISTSERV. These are:
***BADPW*** -- the password provided in the TCPGUI command does not match the password registered for the given e-mail address.
6.5 LCMDX.C
/******************************************************************************
* *
* LISTSERV V2 - send command to LISTSERV on remote node via TCPGUI interface *
* *
* Copyright L-Soft international 1996-97 - All rights reserved *
* *
* Syntax: *
* *
* lcmdx hostname[:port] address password command *
* *
* Connects to 'hostname' on port 'port' (default=2306) using the LISTSERV *
* TCPGUI protocol, then executes the LISTSERV command 'command' from the *
* origin 'address'. 'password' is the personal LISTSERV password associated *
* with the command origin ('address') - see the description of the PW ADD *
* command for more information on LISTSERV passwords. The reply from *
* LISTSERV is echoed to standard output (the command is executed *
* synchronously). *
* *
******************************************************************************/
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#ifdef INTUX
#include <net/errno.h>
#endif
 
#define DEFAULT_PORT 2306
 
#ifdef ultrix
/* Use read() rather than recv() to bypass a bug in Ultrix */
#define recv(a, b, c, d) read(a, b, c)
#endif
 
static int receive(int ss, char *buf, int len)
{
char *w, *e;
int l;
 
for (w = buf, e = buf + len; w < e;) {
l = recv(ss, w, e - w, 0);
if (l <= 0)
return(l);
w += l;
}
return(len);
}
 
int LSV_send_command(char *hostname, unsigned short port, char *origin,
char *pw, char *command, FILE *writeto)
{
char buf[256], *reply = 0, *cmd, *w, *r, *e;
unsigned char *wb;
int rc, ss, len, orglen, n;
unsigned int ibuf[2];
struct sockaddr_in sa_connect;
struct hostent *H;
 
/* Initialize */
cmd = malloc(strlen(command) + strlen(pw) + 5);
sprintf(cmd, "%s PW=%s", command, pw);
orglen = strlen(origin);
 
/* Create a socket */
if ((ss = socket(AF_INET, SOCK_STREAM, 0)) < 0)
goto Socket_Error;
 
/* Prepare sa_connect structure */
memset(&sa_connect, 0, sizeof(sa_connect));
sa_connect.sin_family = AF_INET;
sa_connect.sin_port = htons(port);
if ((H = gethostbyname(hostname)) && H->h_addr_list[0])
memcpy(&sa_connect.sin_addr, H->h_addr_list[0], 4);
else
goto Socket_Error;
 
/* Connect to the TCPGUI port */
if (connect(ss, (struct sockaddr *)&sa_connect,
sizeof(sa_connect)) < 0)
goto Socket_Error;
 
/* Send the protocol level request and the command header */
wb = (unsigned char *)buf;
len = strlen(cmd);
n = len + orglen + 1; /* Byte length */
*wb++ = '1'; /* Protocol level: 1 */
*wb++ = 'B'; /* Mode: binary */
*wb++ = '\r';
*wb++ = '\n';
*wb++ = n / 256; /* Request length byte 1 */
*wb++ = n & 255; /* Request length byte 2 */
*wb++ = orglen; /* Origin length: 1 */
for (r = origin; *r;)
*wb++ = (unsigned char)*r++;
 
if (send(ss, buf, (char *)wb - buf, 0) < 0)
goto Socket_Error;
 
/* Await confirmation */
for (w = buf;;) {
n = recv(ss, w, buf + sizeof(buf) - w, 0);
if (n <= 0)
goto Socket_Error;
w += n;
for (r = buf; r < w && *r != '\n'; r++);
if (r != w)
break;
}
 
/* Anything other than 250 is an error */
if (buf[0] != '2' || buf[1] != '5' || buf[2] != '0')
goto Protocol_Error;
 
/* Finish sending the command text */
if (send(ss, cmd, len, 0) < 0)
goto Socket_Error;
 
/* Read the return code and reply length */
if (receive(ss, (char *)ibuf, 8) <= 0)
goto Socket_Error;
 
/* Exit if the return code is not 0 */
if (ntohl(ibuf[0]))
goto Protocol_Error;
 
/* Read the reply */
len = ntohl(ibuf[1]);
reply = malloc(len + 1);
if (receive(ss, (char *)reply, len) <= 0)
goto Socket_Error;
 
/* Cut it into individual lines, and output it */
for (r = reply, e = reply + len; r < e;) {
for (w = r; w < e && *w != '\r'; w++);
*w++ = '\0';
fprintf(writeto, "%s\n", r);
r = w;
if (r < e && *r == '\n')
r++;
}
 
/* Close the socket and return */
rc = 0;
goto Done;
 
Protocol_Error:
rc = 1000;
goto Done;
 
Socket_Error:
rc = errno;
goto Done;
 
Done:
free(cmd);
if (reply)
free(reply);
if (ss >= 0)
close(ss);
return(rc);
}
 
 
#ifndef NO_MAIN
int main(int argc, char **argv)
{
char cmd[8192], hostname[80], *w, *r;
int rc, n;
unsigned short port;
 
/* Parse positional parameters */
if (argc < 5) {
printf("\
Syntax: lcmdx hostname[:port] address password command\n");
return(EINVAL);
}
 
port = DEFAULT_PORT;
for (r = argv[1], w = hostname; *r && *r != ':';)
*w++ = *r++;
*w = '\0';
if (*r == ':')
port = atoi(++r);
 
for (n = 4, w = cmd; n < argc; n++) {
if (w != cmd)
*w++ = ' ';
for (r = argv[n]; *r; *w++ = *r++);
}
*w = '\0';
 
/* Execute the command */
rc = LSV_send_command(hostname, port, argv[2], argv[3], cmd,
stdout);
if (rc == 1000)
printf("\
>>> Protocol error while communicating with LISTSERV.");
else if (rc != 0)
printf("\
>>> Error - unable to initiate communication with LISTSERV (errno=%d).\n", rc);
 
return(rc);
}
#endif