Skip site navigation (1)Skip section navigation (2)
Date:      Sun, 23 Aug 1998 16:54:55 +0200
From:      Julian Stacey <jhs@FreeBSD.ORG>
To:        hm@kts.org
Cc:        freebsd-isdn@FreeBSD.ORG
Subject:   Re. Patches to add names instead of numbers to i4b monitor and log
Message-ID:  <199808231454.QAA05602@jhs.muc.de>
In-Reply-To: 	Your message of "Mon, 17 Aug 1998 20:56:39 %2B0200." <001501bdca10$c4e6f600$0300a8c0@tvainc.cybrtyme.com> 

next in thread | previous in thread | raw e-mail | index | archive | help
This is a Repost as I didn't see this come back to me on the list.

PS (I gather a version of my diffs (but not sure if last or this set of diffs)
is integrated with some changes in developers kit i4b-dev-210898.tgz,
I'll take a look & report back to hm@ &garyj )
-----------

Hellmuth & list,
Here is a new set of diffs on i4b 00.63,
to add number to name conversion on display & log.
Please discard my old subsidiary set of a few days ago, as
I have since added alias file rescanning on {signal & 'isdnd -f .... \n 3' }
& extended manuals.  Diffs are just 20K so appended, & will soon also be in
	http://www.muc.de/~jhs/src/bsd/fixes/FreeBSD/src/i4b_gen/i4b/
		isdnd/	&
		isdntel/
I don't expect to do much more to it, so I suggest go grab & play :-)

As before you also need in isdnd/
	alias.c -> ../isdntel/alias.c
	defs.h -> ../isdntel/defs.h

PS I also have a C program & manual
	http://www.muc.de/~jhs/src/bsd/jhs/bin/public/phone/
which generates an alias file from an address book of format:
	][
        fn:Forenames
        sn:Surname
	bn:Business name
        co:Context
        ah:Address home
              indented continution of previous field
        aw:Address work
        th:Telephone home
        tw:Telephone work
        tm:Telephone mobile
        fh:Fax Home
        fw:Fax Work
        fm:Fax Mobile
	][
	.... next person ....
	][
	
----------------
*** 00.63/src/i4b/isdnd/Makefile	Thu Aug 20 18:46:58 1998
--- numbers_to_names/src/i4b/isdnd/Makefile	Mon Aug 17 17:58:12 1998
***************
*** 8,14 ****
  
  PROG	=	isdnd
  SRCS	=	rc_parse.y rc_scan.l main.c rc_config.c log.c curses.c	\
! 		process.c rates.c msghdl.c fsm.c support.c timer.c	\
  		exec.c dial.c monitor.c pcause.c
  
  .if (${BSDTYPE} == "FreeBSD" && ${RELEASE} == "3.0-CURRENT")
--- 8,14 ----
  
  PROG	=	isdnd
  SRCS	=	rc_parse.y rc_scan.l main.c rc_config.c log.c curses.c	\
! 		process.c rates.c alias.c msghdl.c fsm.c support.c timer.c \
  		exec.c dial.c monitor.c pcause.c
  
  .if (${BSDTYPE} == "FreeBSD" && ${RELEASE} == "3.0-CURRENT")
*** 00.63/src/i4b/isdnd/curses.c	Thu Aug 20 18:46:25 1998
--- numbers_to_names/src/i4b/isdnd/curses.c	Thu Aug 20 17:50:43 1998
***************
*** 165,171 ****
  	{
  		"1 - Display refresh",
  		"2 - Hangup (choose a channel)",
! 		"3 - Reread config file",		
  		"4 - Quit the program",		
  	};
  
--- 165,171 ----
  	{
  		"1 - Display refresh",
  		"2 - Hangup (choose a channel)",
! 		"3 - Reread config & alias files",		
  		"4 - Quit the program",		
  	};
  
*** 00.63/src/i4b/isdnd/isdnd.8	Thu Aug 20 18:46:25 1998
--- numbers_to_names/src/i4b/isdnd/isdnd.8	Thu Aug 20 17:41:47 1998
***************
*** 41,46 ****
--- 41,47 ----
  .Nd isdn4bsd ISDN connection management daemon
  .Sh SYNOPSIS
  .Nm isdnd
+ .Op Fl a Ar aliasfile
  .Op Fl b
  .Op Fl c Ar configfile
  .Op Fl d Ar debuglevel
***************
*** 60,65 ****
--- 61,89 ----
  .Pp
  The options are as follows:
  .Bl -tag -width Ds
+ .\" ---
+ .It Fl a
+ Use
+ .Ar aliasfile
+ (instead of the default file
+ .Li /etc/isdn/isdntel.alias 
+ ) to convert displayed numbers to names,
+ for non data incoming calls,
+ shown by
+ .Nm isdnd -f
+ and 
+ .Li /var/log/isdnd.log.
+ Aliasfile is merely cosmetic, with no effect on data connectivity,
+ never the less, it is currently a fatal error not to have one !
+ See also 
+ .Nm isdntel.
+ This file is re-read by 
+ .Nm isdnd -f
+ option 3 "Reread config file" & by signal(SIGHUP).
+ .sp
+ Temporary Limitations:
+ Untested behaviour with bad format.
+ .\" ---
  .It Fl b
  The famous jhs-bell: in full-screen mode, ring the bell when connecting 
  or disconnecting a call.
***************
*** 328,334 ****
  .El
  
  .Sh FILES
! .Bl -tag -width /etc/isdn/isdnd.rates -compact
  .It Pa /dev/i4b
  The device-file used to communicate with the kernel ISDN driver subsystem.
  
--- 352,358 ----
  .El
  
  .Sh FILES
! .Bl -tag -width /etc/isdn/isdntel.alias -compact
  .It Pa /dev/i4b
  The device-file used to communicate with the kernel ISDN driver subsystem.
  
***************
*** 354,359 ****
--- 378,386 ----
  
  .It Pa /etc/isdn/isdnd.rates
  The default unit charging rates specification file.
+ 
+ .It Pa /etc/isdn/isdntel.alias
+ The default table to convert phone number to caller's name.
  .El
  
  .Sh EXAMPLES
***************
*** 387,392 ****
--- 414,420 ----
  .Sh SEE ALSO
  .Xr syslogd 8 ,
  .Xr isdntrace 8 ,
+ .Xr isdntel 8 ,
  .Xr isdnd.rc 5 ,
  .Xr isdnd.rates 5 ,
  .Xr i4bisppp 4 ,
*** 00.63/src/i4b/isdnd/isdnd.h	Thu Aug 20 18:46:25 1998
--- numbers_to_names/src/i4b/isdnd/isdnd.h	Thu Aug 20 11:25:05 1998
***************
*** 446,451 ****
--- 446,454 ----
  char *configfile = CONFIG_FILE_DEF;		/* configuration filename */
  int config_error_flag = 0;			/* error counter */
  
+ char *aliasfile = ALIAS_FILE_DEF;		/* number to name alias file */
+ int alias_error_flag = 0;			/* error counter, alias file */
+ 
  #ifdef DEBUG
  int do_debug = 0;				/* debug mode flag	*/
  int debug_flags = 0;				/* debug options	*/
***************
*** 514,520 ****
--- 517,525 ----
  int isdnfd;
  
  char *configfile;
+ char *aliasfile;
  int config_error_flag;
+ int alias_error_flag;
  
  #ifdef DEBUG
  int do_debug;
***************
*** 581,586 ****
--- 586,592 ----
  void check_pid ( void );
  void close_allactive ( void );
  void configure ( char *filename, int reread );
+ void configure_a ( char *filename, int reread );
  void daemonize ( void );
  void display_acct ( cfg_entry_t *cep );
  void display_bell ( void );
*** 00.63/src/i4b/isdnd/main.c	Thu Aug 20 18:46:26 1998
--- numbers_to_names/src/i4b/isdnd/main.c	Thu Aug 20 11:34:18 1998
***************
*** 91,101 ****
  	int remotesockfd = -1;		/* tcp/ip monitor socket */
  #endif
  #endif
  	
! 	while ((i = getopt(argc, argv, "bmc:d:fFlL:r:s:t:u:?")) != EOF)
  	{
  		switch (i)
  		{
  			case 'b':
  				do_bell = 1;
  				break;
--- 91,106 ----
  	int remotesockfd = -1;		/* tcp/ip monitor socket */
  #endif
  #endif
+ 	void init_alias(char *filename) ;
  	
! 	while ((i = getopt(argc, argv, "a:bmc:d:fFlL:r:s:t:u:?")) != EOF)
  	{
  		switch (i)
  		{
+ 			case 'a':
+                                 aliasfile = optarg; 
+                                 break;
+ 
  			case 'b':
  				do_bell = 1;
  				break;
***************
*** 333,338 ****
--- 338,347 ----
  	}
  #endif
  
+ 	/* Initialise aliases table (not necessary for functionality,
+ 	   but names look nicer than numbers in log & monitor display mode */
+ 	configure_a(aliasfile,0) ;
+ 
  	srandom(580403);	/* init random number gen */
  	
  	mloop(		/* enter loop of no return .. */
***************
*** 379,388 ****
  {
  	fprintf(stderr, "\n");
  	fprintf(stderr, "isdnd - i4b ISDN manager daemon, version %02d.%02d, compiled %s %s\n", VERSION, REL, __DATE__, __TIME__);
  #ifdef DEBUG
! 	fprintf(stderr, "  usage: isdnd [-b] [-c file] [-d level] [-F]\n");
  #else
! 	fprintf(stderr, "  usage: isdnd [-b] [-c file] [-F]\n");
  #endif	
  	fprintf(stderr, "                [-f [-r dev] [-t termtype]] [-u time]\n");
  	fprintf(stderr, "                [-l] [-L file] [-s facility] [-m]\n");
--- 388,398 ----
  {
  	fprintf(stderr, "\n");
  	fprintf(stderr, "isdnd - i4b ISDN manager daemon, version %02d.%02d, compiled %s %s\n", VERSION, REL, __DATE__, __TIME__);
+ 	fprintf(stderr, "  usage: isdnd -a <filename> alias file name (def: %s)\n", ALIAS_FILE_DEF);
  #ifdef DEBUG
! 	fprintf(stderr, "  [-b] [-c file] [-d level] [-F]\n");
  #else
! 	fprintf(stderr, "  [-b] [-c file] [-F]\n");
  #endif	
  	fprintf(stderr, "                [-f [-r dev] [-t termtype]] [-u time]\n");
  	fprintf(stderr, "                [-l] [-L file] [-s facility] [-m]\n");
***************
*** 600,626 ****
  }
  
  /*---------------------------------------------------------------------------*
!  *	re-read the config file on SIGHUP or menu command
   *---------------------------------------------------------------------------*/
  void
  rereadconfig(int dummy)
  {
  	extern int entrycount;
  
! 	log(LL_DMN, "re-reading configuration file");
  	
  	close_allactive();
  
  	entrycount = -1;
  	nentries = 0;
  	
! 	/* read runtime configuration file and configure ourselves */
  	
  	configure(configfile, 1);
  
  	if(config_error_flag)
  	{
  		log(LL_ERR, "there were %d error(s) in the configuration file, terminating!", config_error_flag);
  		unlink(PIDFILE);		
  		exit(1);
  	}
--- 610,644 ----
  }
  
  /*---------------------------------------------------------------------------*
!  *	re-read the config & alias files on SIGHUP or menu command
   *---------------------------------------------------------------------------*/
  void
  rereadconfig(int dummy)
  {
  	extern int entrycount;
  
! 	log(LL_DMN, "re-reading configuration & alias files");
  	
  	close_allactive();
  
  	entrycount = -1;
  	nentries = 0;
  	
! 	/* read runtime configuration & alias files and configure ourselves */
  	
  	configure(configfile, 1);
  
  	if(config_error_flag)
  	{
  		log(LL_ERR, "there were %d error(s) in the configuration file, terminating!", config_error_flag);
+ 		unlink(PIDFILE);		
+ 		exit(1);
+ 	}
+ 
+ 	configure_a(aliasfile, 1);
+ 	if(alias_error_flag)
+ 	{
+ 		log(LL_ERR, "there were %d error(s) in the alias file, terminating!", alias_error_flag);
  		unlink(PIDFILE);		
  		exit(1);
  	}
*** 00.63/src/i4b/isdnd/msghdl.c	Thu Aug 20 18:46:26 1998
--- numbers_to_names/src/i4b/isdnd/msghdl.c	Thu Aug 20 17:25:04 1998
***************
*** 46,63 ****
  #include "isdnd.h"
  
  /*---------------------------------------------------------------------------*
   *	handle incoming CONNECT_IND (=SETUP) message
   *---------------------------------------------------------------------------*/
  void
  msg_connect_ind(msg_connect_ind_t *mp)
  {
  	cfg_entry_t *cep;
  
  	if((cep = find_entry(mp->header.cdid, mp->controller, mp->channel,
  		mp->bprot, mp->dst_telno, mp->src_telno, mp->scr_ind)) == NULL)
  	{
! 		log(LL_CHD, "%05d <unknown> incoming call from %s to %s",
! 		    CDID_UNUSED, mp->src_telno, mp->dst_telno);
  		sendm_connect_resp(NULL, mp->header.cdid, SETUP_RESP_DNTCRE, 0);
  		return;
  	}
--- 46,111 ----
  #include "isdnd.h"
  
  /*---------------------------------------------------------------------------*
+  *      fatal alias error, exit
+  *---------------------------------------------------------------------------*/
+ #include <stdarg.h>
+ void
+ fatal(char *fmt, ...) /* ideas from ../isdntel/main.c */
+ {
+ 	char tmp[500] ;
+         va_list ap;
+         va_start(ap, fmt);
+         strcpy(tmp,"Fatal alias error: ");
+         vsprintf(tmp + strlen(tmp) , fmt, ap);
+         strcat(tmp,"\n");
+         va_end(ap);
+         fprintf(stderr, "%s",tmp);
+ 	log(LL_CHD, "%05d %s", CDID_UNUSED, tmp );
+ 	do_exit(1);
+ }                               
+ 
+ /*---------------------------------------------------------------------------*
+  *	convert phone numbers to names where possible
+  *---------------------------------------------------------------------------*/
+ char *
+ number_name(char *number)
+ {
+ 	char *get_alias(char *number) ;
+ 	char *tmp ;
+ 
+ 	if (*number == '\0') return("Unknown");
+ 	else if ((tmp = get_alias(number)) != NULL ) return tmp ;
+ 	else return number ;
+ }
+ 
+ /*---------------------------------------------------------------------------*
   *	handle incoming CONNECT_IND (=SETUP) message
   *---------------------------------------------------------------------------*/
  void
  msg_connect_ind(msg_connect_ind_t *mp)
  {
  	cfg_entry_t *cep;
+ 	char *src_telna, *dst_telna ;
+ 
+ 	src_telna = number_name(mp->src_telno);
+ 	dst_telna = number_name(mp->dst_telno);
  
  	if((cep = find_entry(mp->header.cdid, mp->controller, mp->channel,
  		mp->bprot, mp->dst_telno, mp->src_telno, mp->scr_ind)) == NULL)
  	{
! 		/* A call has occured on the S0 bus which does not match the
! 		   list of acceptable data connections. It could be any of:
! 			- A call via a PBX (TK-Anlage) to a phone or fax.
! 			- A call to an ISDN phone or fax.
! 			- A call to another ISDN card [on another computer]
! 			  that answers other numbers.
! 			- Distant computer trying a new number before local
! 			  computer has been configured.
! 			- An ISDN data security probe/attack.
! 			- An ISDN data call to a wrong number.
! 		   */
! 		log(LL_CHD, "%05d call from %s to %s",
! 		    CDID_UNUSED, src_telna, dst_telna );
  		sendm_connect_resp(NULL, mp->header.cdid, SETUP_RESP_DNTCRE, 0);
  		return;
  	}
***************
*** 65,71 ****
  	if(cep->inout == DIR_OUTONLY)
  	{
  		log(LL_CHD, "%05d %s incoming call from %s to %s not allowed by configuration!",
! 		    CDID_UNUSED, cep->name, mp->src_telno, mp->dst_telno);
  		sendm_connect_resp(NULL, mp->header.cdid, SETUP_RESP_DNTCRE, 0);
  		return;
  	}
--- 113,119 ----
  	if(cep->inout == DIR_OUTONLY)
  	{
  		log(LL_CHD, "%05d %s incoming call from %s to %s not allowed by configuration!",
! 		    CDID_UNUSED, cep->name, src_telna, dst_telna);
  		sendm_connect_resp(NULL, mp->header.cdid, SETUP_RESP_DNTCRE, 0);
  		return;
  	}
***************
*** 77,90 ****
  	{
  		case REACT_ACCEPT:
  			log(LL_CHD, "%05d %s accepting: incoming call from %s to %s",
! 				mp->header.cdid, cep->name, mp->src_telno, mp->dst_telno);
  			next_state(cep, EV_MCI);
  			break;
  
  		case REACT_REJECT:
  			log(LL_CHD, "%05d %s rejecting: incoming call from %s to %s",
  				CDID_UNUSED, cep->name,
! 				mp->src_telno, mp->dst_telno);
  			sendm_connect_resp(cep, mp->header.cdid, SETUP_RESP_REJECT,
  				(CAUSET_I4B << 8) | CAUSE_I4B_REJECT);
  			cep->cdid = CDID_UNUSED;
--- 125,139 ----
  	{
  		case REACT_ACCEPT:
  			log(LL_CHD, "%05d %s accepting: incoming call from %s to %s",
! 				mp->header.cdid, cep->name, 
! 				src_telna, dst_telna);
  			next_state(cep, EV_MCI);
  			break;
  
  		case REACT_REJECT:
  			log(LL_CHD, "%05d %s rejecting: incoming call from %s to %s",
  				CDID_UNUSED, cep->name,
! 				src_telna, dst_telna);
  			sendm_connect_resp(cep, mp->header.cdid, SETUP_RESP_REJECT,
  				(CAUSET_I4B << 8) | CAUSE_I4B_REJECT);
  			cep->cdid = CDID_UNUSED;
***************
*** 93,99 ****
  		case REACT_IGNORE:
  			log(LL_CHD, "%05d %s ignoring: incoming call from %s to %s",
  				CDID_UNUSED, cep->name,
! 				mp->src_telno, mp->dst_telno);
  			sendm_connect_resp(NULL, mp->header.cdid, SETUP_RESP_DNTCRE, 0);
  			break;
  
--- 142,148 ----
  		case REACT_IGNORE:
  			log(LL_CHD, "%05d %s ignoring: incoming call from %s to %s",
  				CDID_UNUSED, cep->name,
! 				src_telna, dst_telna);
  			sendm_connect_resp(NULL, mp->header.cdid, SETUP_RESP_DNTCRE, 0);
  			break;
  
***************
*** 102,115 ****
  			{
  				log(LL_CHD, "%05d %s alerting: incoming call from %s to %s",
  				        mp->header.cdid, cep->name,
! 				        mp->src_telno, mp->dst_telno);
  				next_state(cep, EV_ALRT);
  			}
  			else
  			{
  				log(LL_CHD, "%05d %s answering: incoming call from %s to %s",
  					mp->header.cdid, cep->name,
! 				        mp->src_telno, mp->dst_telno);
  				next_state(cep, EV_MCI);
  			}
  			break;
--- 151,164 ----
  			{
  				log(LL_CHD, "%05d %s alerting: incoming call from %s to %s",
  				        mp->header.cdid, cep->name,
! 				        src_telna, dst_telna);
  				next_state(cep, EV_ALRT);
  			}
  			else
  			{
  				log(LL_CHD, "%05d %s answering: incoming call from %s to %s",
  					mp->header.cdid, cep->name,
! 				        src_telna, dst_telna);
  				next_state(cep, EV_MCI);
  			}
  			break;
***************
*** 119,125 ****
  			{
  				log(LL_CHD, "%05d %s reserved: incoming call from %s to %s",
  					CDID_UNUSED, cep->name,
! 					mp->src_telno, mp->dst_telno);
  				sendm_connect_resp(cep, mp->header.cdid, SETUP_RESP_REJECT,
  					(CAUSET_I4B << 8) | CAUSE_I4B_NORMAL);
  				/* no state change */
--- 168,174 ----
  			{
  				log(LL_CHD, "%05d %s reserved: incoming call from %s to %s",
  					CDID_UNUSED, cep->name,
! 					src_telna, dst_telna);
  				sendm_connect_resp(cep, mp->header.cdid, SETUP_RESP_REJECT,
  					(CAUSET_I4B << 8) | CAUSE_I4B_NORMAL);
  				/* no state change */
***************
*** 128,134 ****
  			{
  				log(LL_CHD, "%05d %s callback: incoming call from %s to %s",
  					CDID_UNUSED, cep->name,
! 					mp->src_telno, mp->dst_telno);
  				sendm_connect_resp(cep, mp->header.cdid, SETUP_RESP_REJECT,
  					(CAUSET_I4B << 8) | CAUSE_I4B_NORMAL);
  				cep->last_release_time = time(NULL);
--- 177,183 ----
  			{
  				log(LL_CHD, "%05d %s callback: incoming call from %s to %s",
  					CDID_UNUSED, cep->name,
! 					src_telna, dst_telna);
  				sendm_connect_resp(cep, mp->header.cdid, SETUP_RESP_REJECT,
  					(CAUSET_I4B << 8) | CAUSE_I4B_NORMAL);
  				cep->last_release_time = time(NULL);
*** 00.63/src/i4b/isdnd/pathnames.h	Thu Aug 20 18:46:26 1998
--- numbers_to_names/src/i4b/isdnd/pathnames.h	Thu Aug 20 17:44:19 1998
***************
*** 48,53 ****
--- 48,56 ----
  
  #define ETCPATH		"/etc/isdn"
  #define CONFIG_FILE_DEF	"/etc/isdn/isdnd.rc"
+ #define ALIAS_FILE_DEF	"/etc/isdn/isdntel.alias"
+ 	/* isdndtel.alias is also defined in ../isdntel/defs.h,
+ 	   hm@kts.org please move this to a common .h file */
  #define RATES_FILE_DEF	"/etc/isdn/isdnd.rates"
  
  #define LIBDIR		"/usr/local/lib/isdn"
*** 00.63/src/i4b/isdnd/rc_config.c	Thu Aug 20 18:46:27 1998
--- numbers_to_names/src/i4b/isdnd/rc_config.c	Thu Aug 20 11:34:34 1998
***************
*** 61,66 ****
--- 61,101 ----
  static int nregprog = 0;
  
  /*---------------------------------------------------------------------------*
+  *	discard old malloc'd aliases on re-read
+  *---------------------------------------------------------------------------*/
+ /* this struct copied from ../isdntel/alias.c
+ 	hm@kts.org please move this to some common .h file */
+ struct alias {
+ 	char *number;		/* telephone number string	*/
+ 	char *name;		/* name string			*/
+ 	struct alias *next;	/* ptr to next alias		*/
+ };
+ 
+ extern struct alias *firsta ;
+ 	void
+ free_alias(ptr)
+ 	struct alias *ptr;
+ 	{
+ 	if (ptr == NULL) return ;
+ 	if (ptr->next != NULL) 		free_alias(ptr->next) ;
+ 	if (ptr->number != NULL)	free(ptr->number) ;
+ 	if (ptr->name != NULL)		free(ptr->name) ;
+ 	free(ptr) ;
+ 	}
+ 
+ /*---------------------------------------------------------------------------*
+  *	called from main to read and process alias file
+  *---------------------------------------------------------------------------*/
+ void
+ configure_a(char *filename, int reread)
+ {
+ 	extern void init_alias(char *);
+ 
+ 	if(reread) free_alias(firsta);
+ 	init_alias(aliasfile);
+ }
+ 
+ /*---------------------------------------------------------------------------*
   *	called from main to read and process config file
   *---------------------------------------------------------------------------*/
  void
*** 00.63/src/i4b/isdntel/alias.c	Thu Aug 20 18:46:29 1998
--- numbers_to_names/src/i4b/isdntel/alias.c	Thu Aug 20 10:43:03 1998
***************
*** 49,55 ****
  	struct alias *next;	/* ptr to next alias		*/
  };
  
! static struct alias *firsta = NULL;
  
  #define MAXBUFSZ	256
  
--- 49,55 ----
  	struct alias *next;	/* ptr to next alias		*/
  };
  
! struct alias *firsta ;
  
  #define MAXBUFSZ	256
  
***************
*** 67,72 ****
--- 67,73 ----
  	struct alias *newa = NULL;
  	struct alias *lasta = NULL;
  	
+ 	firsta = NULL;
  	if((fp = fopen(filename, "r")) == NULL)
  		fatal("cannot open aliasfile %s!", filename);
  
----------------
Julian
--
Julian H. Stacey         jhs@freebsd.org         http://www.freebsd.org/~jhs/

To Unsubscribe: send mail to majordomo@FreeBSD.org
with "unsubscribe freebsd-isdn" in the body of the message



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?199808231454.QAA05602>