Subject: Re: Printer Accounting with papd
From: Jon Knight (J.P.Knight@lboro.ac.uk)
Date: Fri Jan 14 2000 - 09:04:44 EST
On Fri, 14 Jan 2000, S.Barbaresi wrote:
> we would like to integrate our Mac's with the printer accounting system by
> using "papd". We've installed and configured atalkd, afpd and papd on our
> Solaris boxes, but all print jobs are created by the same user,
> consequently, the printer accounting system cant distinguish each job.
>
> The Question: is it possible to pass the user-id to papd, and hence to
> lpd?
I had exactly the same problem and ended up patching
netatalk-1.4b2+asun2.1.3 so that it did the same sort of thing as CAP. In
other words your users have to mount an AppleTalk share from the UNIX box
before they can print. When they mount it, a file is written out that
contains their node address. When they try to print through the UNIX box,
only nodes that have mounted OK are allowed in.
Seems to work for us anyway[1]. For what its worth, I've attached the
patches for the various bits of netatalk to this email (this is something
I've been meaning to do for months but not had enough round tuits until
now).
Tatty bye,
Jim'll
[1] Other than the fact that I advertise 37 printers into the same
AppleTalk zone. Whilst other netatalk boxes see the NBP advertisments
fine, Macs and CAP boxes only see a variable number of LaserWriters from
the total. However I don't think this is related to this patch - looks
more like a limitation with Macs being about to distinguish devices of the
same AppleTalk type that are on the same node.
*** /home/jon/netatalk-1.4b2+asun2.1.3/etc/afpd/afp_asp.c Thu Oct 14 17:54:00 1999
--- /home/martin/netatalk-1.4b2+asun2.1.3/etc/afpd/afp_asp.c Sun Feb 7 20:35:35 1999
***************
*** 19,33 ****
#include <atalk/atp.h>
#include <atalk/asp.h>
#include <atalk/compat.h>
- #include <atalk/paths.h>
#include "globals.h"
#include "switch.h"
#include "auth.h"
- #ifdef SECURE_PRINTING
- char secure_path[MAXPATHLEN + 1];
- #endif SECURE_PRINTING
-
extern struct oforks *writtenfork;
static AFPObj *child;
--- 19,28 ----
***************
*** 35,48 ****
static void afp_asp_die(int sig)
{
ASP asp = child->handle;
- #ifdef SECURE_PRINTING
- char path[MAXPATHLEN + 1];
-
- sprintf(path, "%s/%u.%u", _PATH_AFPCONNDIR,
- ntohs( asp->asp_sat.sat_addr.s_net ),
- asp->asp_sat.sat_addr.s_node);
- unlink(path);
- #endif /* SECURE_PRINTING */
asp_attention(asp, AFPATTN_SHUTDOWN);
if ( asp_shutdown( asp ) < 0 ) {
--- 30,35 ----
***************
*** 98,108 ****
struct sigaction action;
int func, ccnt = 0, reply = 0;
- #ifdef SECURE_PRINTING
- char path[MAXPATHLEN + 1];
- FILE *fd;
- #endif /* SECURE_PRINTING */
-
obj->exit = afp_asp_die;
obj->reply = (int (*)()) asp_cmdreply;
obj->attention = (int (*)(void *, AFPUserBytes)) asp_attention;
--- 85,90 ----
***************
*** 133,155 ****
atp_sockaddr( asp->asp_atp )->sat_addr.s_node,
atp_sockaddr( asp->asp_atp )->sat_port );
- #ifdef SECURE_PRINTING
- sprintf(secure_path, "%s/%u.%u", _PATH_AFPCONNDIR,
- ntohs( asp->asp_sat.sat_addr.s_net ),
- asp->asp_sat.sat_addr.s_node);
- #endif SECURE_PRINTING
-
while (reply = asp_getrequest(asp)) {
switch (reply) {
case ASPFUNC_CLOSE :
-
- #ifdef SECURE_PRINTING
- sprintf(path, "%s/%u.%u", _PATH_AFPCONNDIR,
- ntohs( asp->asp_sat.sat_addr.s_net ),
- asp->asp_sat.sat_addr.s_node);
- syslog( LOG_INFO, "Unlinking %s",path );
- unlink(path);
- #endif SECURE_PRINTING
asp_close( asp );
#ifdef USE_PAM
if (pamh) {
--- 115,123 ----
*** /home/jon/netatalk-1.4b2+asun2.1.3/etc/afpd/auth.c Wed Oct 13 23:27:19 1999
--- /home/martin/netatalk-1.4b2+asun2.1.3/etc/afpd/auth.c Wed Oct 6 16:48:11 1999
***************
*** 44,53 ****
#include "status.h"
#include "switch.h"
- #ifdef SECURE_PRINTING
- extern char secure_path[MAXPATHLEN + 1];
- #endif SECURE_PRINTING
-
#define PASSWDLEN 8
#ifdef USE_PAM
--- 44,49 ----
***************
*** 438,466 ****
return( AFP_OK );
}
! login( name, uid, gid )
char *name;
uid_t uid;
gid_t gid;
{
- #ifdef SECURE_PRINTING
- FILE *fd;
- #endif SECURE_PRINTING
-
if ( uid == 0 ) { /* don't allow root login */
syslog( LOG_ERR, "login: root login denied!" );
return AFPERR_NOTAUTH;
}
- #ifdef SECURE_PRINTING
- if ( (fd = fopen(secure_path, "w")) < 0 ) {
- syslog( LOG_ERR, "Failed to open %s", secure_path);
- } else {
- fprintf(fd, "%s\n", name);
- fclose(fd);
- }
- #endif SECURE_PRINTING
-
syslog( LOG_INFO, "login %s (uid %d, gid %d)", name, uid, gid );
if (initgroups( name, gid ) < 0) {
#ifdef RUN_AS_USER
--- 434,450 ----
return( AFP_OK );
}
! login( obj, name, uid, gid )
! AFPObj *obj;
char *name;
uid_t uid;
gid_t gid;
{
if ( uid == 0 ) { /* don't allow root login */
syslog( LOG_ERR, "login: root login denied!" );
return AFPERR_NOTAUTH;
}
syslog( LOG_INFO, "login %s (uid %d, gid %d)", name, uid, gid );
if (initgroups( name, gid ) < 0) {
#ifdef RUN_AS_USER
***************
*** 629,635 ****
if (( pwd = getpwnam( ad.pname )) == NULL ) {
return( AFPERR_NOTAUTH );
}
! return( login( pwd->pw_name, pwd->pw_uid, pwd->pw_gid ));
#else AFS
/* get principals */
*p++ = KRB4RPL_PRINC;
--- 613,619 ----
if (( pwd = getpwnam( ad.pname )) == NULL ) {
return( AFPERR_NOTAUTH );
}
! return( login( &obj, pwd->pw_name, pwd->pw_uid, pwd->pw_gid ));
#else AFS
/* get principals */
*p++ = KRB4RPL_PRINC;
***************
*** 697,703 ****
if (( pwd = getpwnam( ad.pname )) == NULL ) {
return( AFPERR_NOTAUTH );
}
! return( login( pwd->pw_name, pwd->pw_uid, pwd->pw_gid ));
#endif AFS
default :
--- 681,687 ----
if (( pwd = getpwnam( ad.pname )) == NULL ) {
return( AFPERR_NOTAUTH );
}
! return( login( &obj, pwd->pw_name, pwd->pw_uid, pwd->pw_gid ));
#endif AFS
default :
***************
*** 785,796 ****
if ( pwd->pw_passwd != NULL ) {
#ifdef AFS
if ( kcheckuser( pwd, ibuf ) == 0 ) {
! return( login( pwd->pw_name, pwd->pw_uid, pwd->pw_gid ));
}
#endif AFS
p = crypt( ibuf, pwd->pw_passwd );
if ( strcmp( p, pwd->pw_passwd ) == 0 ) {
! return( login( pwd->pw_name, pwd->pw_uid, pwd->pw_gid ));
}
}
return AFPERR_NOTAUTH;
--- 769,780 ----
if ( pwd->pw_passwd != NULL ) {
#ifdef AFS
if ( kcheckuser( pwd, ibuf ) == 0 ) {
! return( login( &obj, pwd->pw_name, pwd->pw_uid, pwd->pw_gid ));
}
#endif AFS
p = crypt( ibuf, pwd->pw_passwd );
if ( strcmp( p, pwd->pw_passwd ) == 0 ) {
! return( login( &obj, pwd->pw_name, pwd->pw_uid, pwd->pw_gid ));
}
}
return AFPERR_NOTAUTH;
*** /home/jon/netatalk-1.4b2+asun2.1.3/etc/papd/main.c Thu Oct 14 19:39:22 1999
--- /home/martin/netatalk-1.4b2+asun2.1.3/etc/papd/main.c Wed Feb 3 20:07:01 1999
***************
*** 43,54 ****
struct printer *printers = NULL;
int debug = 0;
- #ifdef SECURE_PRINTING
- char secureprint = 0;
- char secpath[MAXPATHLEN + 1];
- char *username;
- FILE *fd;
- #endif /* SECURE_PRINTING */
char *conffile = _PATH_PAPDCONF;
char *printcap = _PATH_PAPDPRINTCAP;
unsigned char connid, quantum, sock, oquantum = PAP_MAXQUANTUM;
--- 43,48 ----
***************
*** 167,173 ****
defprinter.p_pagecost_msg = NULL;
defprinter.p_lock = "lock";
! while (( c = getopt( ac, av, "adf:p:P:s" )) != EOF ) {
switch ( c ) {
case 'a' : /* for compatibility with old papd */
break;
--- 161,167 ----
defprinter.p_pagecost_msg = NULL;
defprinter.p_lock = "lock";
! while (( c = getopt( ac, av, "adf:p:P:" )) != EOF ) {
switch ( c ) {
case 'a' : /* for compatibility with old papd */
break;
***************
*** 188,205 ****
pidfile = optarg;
break;
- case 's' :
- #ifdef SECURE_PRINTING
- secureprint++;
- break;
- #else SECURE_PRINTING
- fprintf(stderr,"Secure printing not configured in binary\n");
- exit( 1 );
- #endif SECURE_PRINTING
-
default :
fprintf( stderr,
! "Usage:\t%s [ -d ] [ -f conffile ] [ -p printcap ] [-s]\n",
*av );
exit( 1 );
}
--- 182,190 ----
pidfile = optarg;
break;
default :
fprintf( stderr,
! "Usage:\t%s [ -d ] [ -f conffile ] [ -p printcap ]\n",
*av );
exit( 1 );
}
***************
*** 358,400 ****
if ( err ) {
continue;
}
-
- #ifdef SECURE_PRINTING
- syslog( LOG_INFO, "About to check security file");
- sprintf(secpath,"%s/%u.%u",
- _PATH_AFPCONNDIR,
- ntohs( sat.sat_addr.s_net ),
- sat.sat_addr.s_node );
- syslog (LOG_INFO, "Opening file %s",secpath);
- if ( (fd = fopen(secpath, "r")) == NULL ) {
- syslog( LOG_ERR,
- "Rejected printing from %u.%u:%u when unauthenticated\n",
- ntohs( sat.sat_addr.s_net ),
- sat.sat_addr.s_node,
- sat.sat_port
- );
- syslog(LOG_ERR, "Couldn't open file: %s",
- strerror(errno));
- continue;
- } else {
- username = (char *)malloc(25 * sizeof(char));
- syslog(LOG_INFO, "About to read username");
- fgets(username,25,fd);
- syslog( LOG_INFO, "Got a username, closing file");
- username[strlen(username)-1]='\0';
- fclose(fd);
- syslog( LOG_INFO, "Setting username in lp_person");
- lp_person(username);
- syslog( LOG_ERR,
- "Accepted job from user %s from %u.%u:%u\n",
- username,
- ntohs( sat.sat_addr.s_net ),
- sat.sat_addr.s_node,
- sat.sat_port
- );
- }
-
- #endif SECURE_PRINTING
switch ( c = fork()) {
case -1 :
--- 343,348 ----
*** /home/jon/netatalk-1.4b2+asun2.1.3/etc/papd/lp.c Thu Oct 14 19:44:11 1999
--- /home/martin/netatalk-1.4b2+asun2.1.3/etc/papd/lp.c Thu Jun 26 21:48:43 1997
***************
*** 66,80 ****
#include <netdb.h>
#include <fcntl.h>
- #ifdef LPRNG
- #include <sys/wait.h>
- #include <signal.h>
-
- #ifndef LPR
- #define LPR "/usr/local/bin/lpr"
- #endif
- #endif LPRNG
-
#include "printer.h"
#include "file.h"
--- 66,71 ----
***************
*** 178,188 ****
lp_init( out )
struct papfile *out;
{
- #ifndef LPRNG
int fd, n, len;
char *cp, buf[ BUFSIZ ];
struct stat st;
- #endif /* LPRNG */
#ifdef ABS_PRINT
char cost[ 22 ];
char balance[ 22 ];
--- 169,177 ----
***************
*** 220,226 ****
lp.lp_letter = 'A';
if ( printer->p_flags & P_SPOOLED ) {
- #ifndef LPRNG
/* check if queuing is enabled: mode & 010 on lock file */
if ( stat( printer->p_lock, &st ) < 0 ) {
syslog( LOG_ERR, "lp_init: %s: %m", printer->p_lock );
--- 209,214 ----
***************
*** 266,272 ****
lseek( fd, 0L, 0 );
write( fd, buf, strlen( buf ));
close( fd );
- #endif /* LPRNG */
} else {
lp.lp_flags |= LP_PIPE;
lp.lp_seq = getpid();
--- 254,259 ----
***************
*** 298,315 ****
return( -1 );
}
} else {
! #ifdef LPRNG
! sprintf( name, "%s/papd.%d%c", _PATH_VARTMP, getpid(), lp.lp_letter++ );
! #else
! sprintf( name, "df%c%03d%s", lp.lp_letter++, lp.lp_seq, hostname );
! #endif
! if (( fd = open( name, O_WRONLY|O_CREAT|O_EXCL,
! #ifdef LPRNG
! 0600
! #else
! 0660
! #endif
! )) < 0 ) {
syslog( LOG_ERR, "lp_open %s: %m", name );
spoolerror( out, NULL );
return( -1 );
--- 285,293 ----
return( -1 );
}
} else {
! sprintf( name, "df%c%03d%s", lp.lp_letter++, lp.lp_seq, hostname );
!
! if (( fd = open( name, O_WRONLY|O_CREAT|O_EXCL, 0660 )) < 0 ) {
syslog( LOG_ERR, "lp_open %s: %m", name );
spoolerror( out, NULL );
return( -1 );
***************
*** 365,375 ****
}
for ( letter = 'A'; letter < lp.lp_letter; letter++ ) {
- #ifdef LPRNG
- sprintf( name, "%s/papd.%d%c", _PATH_VARTMP, getpid(), letter );
- #else
sprintf( name, "df%c%03d%s", letter, lp.lp_seq, hostname );
- #endif
if ( unlink( name ) < 0 ) {
syslog( LOG_ERR, "lp_cancel unlink %s: %m", name );
}
--- 343,349 ----
***************
*** 399,472 ****
lp_close();
if ( printer->p_flags & P_SPOOLED ) {
- char *job, *person;
- #ifdef LPRNG
- char **argv, **ptr, **fileptr;
- pid_t pid;
- #endif
-
- job = (lp.lp_job && *lp.lp_job) ? lp.lp_job : "Mac Job";
- person = lp.lp_person ? lp.lp_person : printer->p_operator;
-
- #ifdef LPRNG
- if (! ((ptr = argv = (char **) malloc(sizeof(*argv) *
- (6 + (lp.lp_letter - 'A')))) &&
- (*ptr++ = (char *) malloc(sizeof("lpr"))) &&
- (*ptr++ = (char *) malloc(strlen(printer->p_printer) + 3)) &&/* -P */
- (*ptr++ = (char *) malloc(strlen(job) + 3)) && /* -J */
- (*ptr++ = (char *) malloc(strlen(job) + 3)) && /* -T */
- (*ptr++ = (char *) malloc(strlen(person) + 3)))) { /* -U */
- syslog( LOG_ERR, "malloc: %m" );
- exit( 1 );
- }
-
- for ( letter = 'A'; letter < lp.lp_letter; letter++ ) {
- if (! (*ptr++ = (char *) malloc( strlen(_PATH_VARTMP) + 20 ))) {
- syslog( LOG_ERR, "malloc: %m" );
- exit( 1 );
- }
- }
-
- ptr = argv;
-
- (void) sprintf( *ptr++, "lpr" );
- (void) sprintf( *ptr++, "-P%s", printer->p_printer );
- (void) sprintf( *ptr++, "-J%s", job );
- (void) sprintf( *ptr++, "-T%s", job );
- (void) sprintf( *ptr++, "-U%s", person );
-
- fileptr = ptr;
-
- for ( letter = 'A'; letter < lp.lp_letter; letter++ )
- (void) sprintf( *ptr++, "%s/papd.%d%c", _PATH_VARTMP, getpid(), letter );
-
- *ptr = NULL;
- syslog(LOG_INFO, "lp_print: About to run %s %s %s %s %s %s",
- argv[0], argv[1], argv[2], argv[3], argv[4], argv[5]);
- signal( SIGCHLD, SIG_DFL );
-
- switch (( pid = fork() )) {
- case 0:
- (void) execv( LPR, argv );
- syslog( LOG_ERR, "execvp(%s): %m", LPR );
- exit( 1 );
- case -1:
- syslog( LOG_ERR, "fork: %m" );
- exit( 1 );
- default: {
- int status;
-
- pid = waitpid( pid, &status, 0 );
- if ( WIFEXITED(status) && WEXITSTATUS(status) )
- syslog( LOG_ERR, "lp_print: child exited with status %d",
- WEXITSTATUS(status) );
- while ( *fileptr )
- (void) unlink( *fileptr++ );
- for ( ptr = argv; *ptr; ptr++ )
- (void) free( *ptr );
- }
- }
- #else
sprintf( tfname, "tfA%03d%s", lp.lp_seq, hostname );
if (( fd = open( tfname, O_WRONLY|O_EXCL|O_CREAT, 0660 )) < 0 ) {
syslog( LOG_ERR, "lp_print %s: %m", tfname );
--- 373,378 ----
***************
*** 478,496 ****
}
fprintf( cfile, "H%s\n", hostname ); /* XXX lp_host? */
! printf( cfile, "P%s\n", person );
- fprintf( cfile, "J%s\n", job );
- fprintf( cfile, "T%s\n", job );
fprintf( cfile, "C%s\n", hostname ); /* XXX lp_host? */
! fprintf( cfile, "L%s\n", person );
for ( letter = 'A'; letter < lp.lp_letter; letter++ ) {
fprintf( cfile, "fdf%c%03d%s\n", letter, lp.lp_seq, hostname );
fprintf( cfile, "Udf%c%03d%s\n", letter, lp.lp_seq, hostname );
}
! fprintf( cfile, "N%s\n", job );
fclose( cfile );
sprintf( cfname, "cfA%03d%s", lp.lp_seq, hostname );
--- 384,421 ----
}
fprintf( cfile, "H%s\n", hostname ); /* XXX lp_host? */
! if ( lp.lp_person ) {
! fprintf( cfile, "P%s\n", lp.lp_person );
! } else {
! fprintf( cfile, "P%s\n", printer->p_operator );
! }
!
! if ( lp.lp_job && *lp.lp_job ) {
! fprintf( cfile, "J%s\n", lp.lp_job );
! fprintf( cfile, "T%s\n", lp.lp_job );
! } else {
! fprintf( cfile, "JMac Job\n" );
! fprintf( cfile, "TMac Job\n" );
! }
fprintf( cfile, "C%s\n", hostname ); /* XXX lp_host? */
!
! if ( lp.lp_person ) {
! fprintf( cfile, "L%s\n", lp.lp_person );
! } else {
! fprintf( cfile, "L%s\n", printer->p_operator );
! }
for ( letter = 'A'; letter < lp.lp_letter; letter++ ) {
fprintf( cfile, "fdf%c%03d%s\n", letter, lp.lp_seq, hostname );
fprintf( cfile, "Udf%c%03d%s\n", letter, lp.lp_seq, hostname );
}
! if ( lp.lp_job && *lp.lp_job ) {
! fprintf( cfile, "N%s\n", lp.lp_job );
! } else {
! fprintf( cfile, "NMac Job\n" );
! }
fclose( cfile );
sprintf( cfname, "cfA%03d%s", lp.lp_seq, hostname );
***************
*** 523,535 ****
syslog( LOG_ERR, "lp_print lpd said %c: %m", buf[ 0 ] );
return;
}
- #endif
}
syslog( LOG_INFO, "lp_print queued" );
return;
}
-
lp_disconn_unix( fd )
{
return( close( fd ));
--- 448,458 ----
***************
*** 609,617 ****
lp_rmjob( job )
int job;
{
- #ifdef LPRNG
- return( -1 );
- #else
char buf[ 1024 ];
int n, s;
--- 532,537 ----
***************
*** 637,643 ****
lp_disconn_inet( s );
return( 0 );
- #endif
}
char *kw_rank = "Rank";
--- 557,562 ----
***************
*** 653,661 ****
lp_queue( out )
struct papfile *out;
{
- #ifdef LPRNG
- return( -1 );
- #else
char buf[ 1024 ], *start, *stop, *p, *q;
static struct papfile pf;
int n, len, s;
--- 572,577 ----
***************
*** 800,804 ****
return( 0 );
}
}
- #endif /* LPRNG */
}
--- 716,719 ----
*** /home/jon/netatalk-1.4b2+asun2.1.3/etc/papd/session.c Thu Oct 14 18:00:42 1999
--- /home/martin/netatalk-1.4b2+asun2.1.3/etc/papd/session.c Sat Feb 6 06:42:47 1999
***************
*** 300,306 ****
}
}
}
-
-
-
-
--- 300,302 ----
*** /home/jon/netatalk-1.4b2+asun2.1.3/etc/papd/Makefile Thu Oct 14 18:00:30 1999
--- /home/martin/netatalk-1.4b2+asun2.1.3/etc/papd/Makefile Tue May 20 18:35:42 1997
***************
*** 4,10 ****
bprint.o magics.o headers.o queries.o
INCPATH = -I../../include ${KRBINCPATH} ${ABSINCPATH}
! CFLAGS= ${DEFS} ${KRBDEFS} ${ABSDEFS} ${OPTOPTS} ${INCPATH} -DLPRNG
TAGSFILE= tags
LIBDIRS= -L../../libatalk ${KRBLIBDIRS} ${ABSLIBDIRS}
LIBS= -latalk ${ABSLIBS} ${KRBLIBS} ${ADDLIBS}
--- 4,10 ----
bprint.o magics.o headers.o queries.o
INCPATH = -I../../include ${KRBINCPATH} ${ABSINCPATH}
! CFLAGS= ${DEFS} ${KRBDEFS} ${ABSDEFS} ${OPTOPTS} ${INCPATH}
TAGSFILE= tags
LIBDIRS= -L../../libatalk ${KRBLIBDIRS} ${ABSLIBDIRS}
LIBS= -latalk ${ABSLIBS} ${KRBLIBS} ${ADDLIBS}
*** /home/jon/netatalk-1.4b2+asun2.1.3/sys/linux/Makefile Wed Oct 13 20:35:23 1999
--- /home/martin/netatalk-1.4b2+asun2.1.3/sys/linux/Makefile Sat Feb 27 21:10:59 1999
***************
*** 1,14 ****
# Linux specific defines, passed to subdirectories.
#DEFS= -DNEED_QUOTACTL_WRAPPER -DSENDFILE_FLAVOR_LINUX
! DEFS= -DNEED_QUOTACTL_WRAPPER -DSECURE_PRINTING
OPTOPTS= -O2 -fomit-frame-pointer -fsigned-char -Wunused -Wuninitialized
#OPTOPTS= -g -fsigned-char
CC= gcc
INSTALL= install
# if you aren't using pam and are using glibc, you'll need to add -lcrypt
# if you're using libc5, you'll need to take out the -lrpcsvc
! AFPLIBS=
! ADDLIBS= -lrpcsvc
ALL= ../../libatalk ../../include ../../bin ../../etc ../../man
--- 1,14 ----
# Linux specific defines, passed to subdirectories.
#DEFS= -DNEED_QUOTACTL_WRAPPER -DSENDFILE_FLAVOR_LINUX
! DEFS= -DNEED_QUOTACTL_WRAPPER
OPTOPTS= -O2 -fomit-frame-pointer -fsigned-char -Wunused -Wuninitialized
#OPTOPTS= -g -fsigned-char
CC= gcc
INSTALL= install
# if you aren't using pam and are using glibc, you'll need to add -lcrypt
# if you're using libc5, you'll need to take out the -lrpcsvc
! AFPLIBS=-lrpcsvc
! ADDLIBS=
ALL= ../../libatalk ../../include ../../bin ../../etc ../../man
This archive was generated by hypermail 2b28 : Wed Jan 17 2001 - 14:29:50 EST