/*
* $Id: opt.c,v 1.5 2002/08/23 13:38:14 howardjp Exp $
*
* Copyright (c) 1990
* Jan Wolter. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by Jan Wolter
* and his contributors.
* 4. Neither the name of Jan Wolter nor the names of his contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY JAN WOLTER AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL JAN WOLTER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
/* PARTY PROGRAM -- OPTION MAINTAINANCE ROUTINES -- Jan Wolter */
#include "party.h"
#include "opt.h"
#define VRSZ 1024 /* More than the total size of all options */
char var_buf[VRSZ]; /* Storage for string values of options */
int var_end= 0; /* Index of first unused byte of var_buf */
struct optent dflt_copt[NCOPT];
FILE *openchn();
/* The partytab contains any number of lines of the form:
*
* progname default-option-list
*
* progname is the name of the program, as in argv[0]. The options are in the
* usual format. This lets you set up different versions of party by making a
* link to the party program with a different name and entering a line setting
* the options for that name in partytab.
*/
readtab()
{
FILE *fp;
register int pnl;
char *partytab= PARTYTAB;
if ((fp= fopen(partytab,"r")) == NULL)
{
err("cannot open %s -- using default options\n",partytab);
return;
}
pnl= strlen(progname);
while (fgets(txbuf,BFSZ,fp) != NULL)
if (!strncmp(txbuf,progname,pnl))
{
if (txbuf[pnl] != ' ' && txbuf[pnl] != '\t') continue;
parseopts(txbuf+pnl+1,1);
}
fclose(fp);
return;
}
/* This sets the channel options from the chantab. The chantab contains
* any number of lines of the form:
*
* chn_name_pat channel-option-list
*
* chn_name_pat is an text pattern that is matched against the channel name.
* It may include the usual ?, *, \ and [] constructs, but not {}. The
* options are in the usual format. Normally only channel options should be
* specified here because only those are reset when you leave a channel. The
* "file=<partyfile>" option should be specified on just about all channels.
*
* The first line with a pattern matching the named channel will be used. If
* no line matches, then no options are read and the function returns 1.
*/
int chnopt(newchn)
char *newchn;
{
int i;
FILE *fp;
char *endpat;
/* Open the Channel Table */
if ((fp= openchn()) == NULL)
return(1);
/* Check if the Channel Table includes a line for this name */
for (;;)
{
if (fgets(txbuf,BFSZ,fp) == NULL)
return(1);
*(endpat= firstin(txbuf," \t\n"))= '\0';
if (patmatch(newchn,txbuf))
break;
}
/* First, reset defaults values of all channel options */
for (i= 0; i<NCOPT; i++)
{
if (opt[i].pf & (PF_NO|PF_SEE))
opt[i].yes= dflt_copt[i].yes;
if (opt[i].pf & PF_STR)
setstr(i,dflt_copt[i].str,strlen(dflt_copt[i].str));
}
/* Load the options */
parseopts(endpat+1,1);
return (0);
}
/*
* Check if a channel option has it's default value.
*/
int hasdefval(i)
int i;
{
return((!(opt[i].pf & (PF_NO|PF_SEE)) || opt[i].yes == dflt_copt[i].yes) &&
(!(opt[i].pf & PF_STR) || !strcmp(opt[i].str,dflt_copt[i].str)));
}
/*
* Set options to the compiled-in defaults. This must be done to
* initialize.
*/
initopts()
{
int i,len;
/* Default value of "alias" is realname */
opt[OPT_ALIAS].str= realname;
/* Save the defaults for channel options */
for (i= 0; i<NCOPT; i++)
dflt_copt[i]= opt[i]; /* Structure Assignment */
/* Move all defaults into the buffer so they can be modified */
var_end= 0;
for (i= 0; i<NOPT; i++)
if (opt[i].pf & PF_STR)
{
len= strlen(opt[i].str);
if (var_end + len + 1 > VRSZ)
{
err("Out of variable space\n");
return;
}
strcpy(var_buf+var_end,opt[i].str);
opt[i].str= var_buf + var_end;
var_end+= len + 1;
}
}
/*
* Set the list of options given by the string c.
* source == 1 if this comes from partytab or chantab.
* source == 2 if this comes from command line.
* source == 0 otherwise.
*/
parseopts(c,source)
register char *c;
int source;
{
char *optbeg,*keybeg,*keyend,*strbeg,*strend,*optend,*p;
register int i;
int prefix,quote;
for (;;)
{
/* Skip white leading space and punctuation */
c= firstout(c," \t,;:");
/* Is this the end of the line? */
if (*c == '\0' || *c == '\n') return;
/* Find end of keyword */
optbeg= c++;
optend= c= firstin(c," ,;:\t=\n");
/* Is it a channel selector? */
if (source == 2 && *optbeg == '#')
{
setstr(OPT_START,optbeg+1,optend-optbeg-1);
continue;
}
/* Is there a prefix? */
keybeg= optbeg;
prefix= 1; /* Assume no prefix */
if (keybeg[0] == 'n' && keybeg[1] == 'o')
{
prefix= 0;
keybeg+= 2;
}
if (keybeg[0]=='s' && keybeg[1]=='e' && keybeg[2]=='e')
{
prefix= (prefix == 1) ? 2 : 1;
keybeg+= 3;
}
/* Is there a string value? */
if (*c == '=')
{
strbeg= ++c;
if (source == 2)
{
strend= c= firstin(c,"\n");
}
else
{
if (*c == '\042') {
/* Delimit string in double-quotes */
strbeg= ++c;
strend= c= firstin(c,"\n\042");
} else if (*c == '\047') {
/* Delimit string in single-quotes */
strbeg= ++c;
strend= c= firstin(c,"\n\047");
} else
/* Delimit unquoted string */
strend= c= firstin(c," ,:;\t\n");
if (*c=='\042' || *c=='\047') c++;
}
}
else
strbeg= NULL;
optend= c;
/* Look for name in table */
for (i=0; i < NOPT; i++)
{
if (!strncmp(keybeg,opt[i].name,strlen(opt[i].name)))
{
/* Am I allowed to change this option? */
if ((opt[i].pf & (PF_SYS|PF_CHN)) && source != 1)
{
err("%s is not a user settable option\n",opt[i].name);
break;
}
/* Do string valued options */
if (strbeg)
{
/* Check that number is assigned to numeric option */
if (opt[i].pf & PF_NUM)
{
for (p=strbeg; p < strend; p++)
if (*p < '0' || *p > '9')
{
err("Nonnumeric value assigned to %s\n",
opt[i].name);
goto next;
}
}
/* Save the new value */
if (opt[i].pf & PF_STR)
setstr(i,strbeg,strend-strbeg);
else
{
err("%s does not take a string value\n",opt[i].name);
break;
}
if (i == OPT_FILTER && opt[OPT_FILTER].yes)
start_filter();
if (i == OPT_MAILDIR)
setmailfile();
if (i == OPT_WRAP)
stashname();
}
/* Do "See" options */
if (prefix == 2)
{
if (opt[i].pf & PF_SEE)
opt[i].yes= prefix;
else
err("Invalid option: see%s\n", opt[i].name);
}
else /* Do boolean options */
{
if (!(opt[i].pf & PF_NO))
{
if (prefix == 0)
{
err("%s does not take a boolean value\n",
opt[i].name);
}
}
else
{
opt[i].yes= prefix;
if (i == OPT_FILTER)
{
if (prefix)
start_filter();
else
stop_filter();
}
if (i == OPT_DEBUG) set_debug();
}
}
break;
}
}
if (i == NOPT)
err("Invalid option: %.*s\n",optend-optbeg,optbeg);
next:;
}
}
setnum(nopt,new)
int nopt;
int new;
{
char anew[12];
sprintf(anew,"%d",new);
setstr(nopt,anew,strlen(anew));
}
/* Set the string valued option number nopt to the value new which has a
* length of newlen.
*/
setstr(nopt,new,newlen)
int nopt;
char *new;
int newlen;
{
register char *src, *dst;
register int i;
char *stop;
int oldlen= strlen(opt[nopt].str);
int inc;
if (oldlen != newlen)
{
if ((inc= newlen - oldlen) > 0)
{
/* String lengthened */
if (var_end + inc >= VRSZ)
{
err("Out of variable space\n");
return;
}
src= var_buf+var_end;
dst= src+inc;
stop= opt[nopt].str + oldlen;
while (src > stop)
*(--dst)= *(--src);
}
else
{
/* String shortened */
src= opt[nopt].str + oldlen;
dst= src + inc;
stop= var_buf+var_end;
while (src < stop)
*(dst++)= *(src++);
}
/* Fix affected pointers */
var_end+= inc;
for (i=nopt+1; i<NOPT; i++)
if (opt[i].str)
opt[i].str +=inc;
}
/* Put in new value */
strncpy(opt[nopt].str,new,newlen);
}
/* Print options. If list is NULL, print all PF_SHOW options. If it is "all"
* print all options. If it is "chan" print channel options which differ
* from their default values. Otherwise print only the options in the list.
* printed gives the number of characters already printed on the current line.
* prefix is a character to start each line with. Returns 0 if anything is
* printed, 1 otherwise.
*/
int printopts(fp,printed,prefix,list)
FILE *fp;
int printed;
char *list;
char prefix;
{
register int i,a;
char *pref;
int cols= convert(opt[OPT_COLS].str);
int all= (list && inlist("all",list));
int chan= (list && !all && inlist("chan",list));
int didprint=0;
printed= 0;
for (i= 0; i<NOPT; i++)
{
/* Decide if we should print this */
if (!all &&
(!chan || i >= NCOPT || hasdefval(i)) &&
(!list || !inlist(opt[i].name,list)) &&
(list || !(opt[i].pf & PF_SHOW)))
continue;
didprint=1;
if (opt[i].pf & (PF_NO|PF_SEE))
{
pref= (opt[i].yes == 1) ? "" : ((opt[i].yes == 0) ? "no" : "see");
printed+= (a= strlen(opt[i].name) + strlen(pref) + 2);
if (printed >= cols)
{
fputc('\n',fp);
printed= a;
}
fprintf(fp,"%c %s%s",
(printed==a) ? prefix : ' ', pref, opt[i].name);
}
if (opt[i].pf & PF_NUM)
{
printed+= (a= strlen(opt[i].name) + strlen(opt[i].str) + 3);
if (printed >= cols)
{
fputc('\n',fp);
printed= a;
}
fprintf(fp,"%c %s=%s",
(printed==a) ? prefix : ' ', opt[i].name, opt[i].str);
}
else if (opt[i].pf & PF_STR)
{
printed+= (a= strlen(opt[i].name) + strlen(opt[i].str) + 5);
if (printed >= cols)
{
fputc('\n',fp);
printed= a;
}
fprintf(fp,"%c %s=\042%s\042",
(printed==a) ? prefix : ' ', opt[i].name, opt[i].str);
}
}
if (printed != 0) fputc('\n',fp);
return(!didprint);
}
int inlist(name,list)
char *name,*list;
{
int nmlen;
char *p;
nmlen= strlen(name);
p= list-1;
while ((p= strstr(p+1,name)) != NULL)
{
if ((p == list || p[-1] == ' ' || p[-1] == '\t') &&
strchr(" \t\n",p[nmlen]))
return(1);
}
return(0);
}
/* Firstin() returns a pointer to the first character in s that is in l.
* \0 is always considered to be in the string l.
*
* Firstout() returns a pointer to the first character s that is not in l.
* \0 is always considered to be not in the string l.
*
* Note that unlike strpbrk() these never return NULL. They always return
* a valid pointer into string s, if only a pointer to it's terminating
* \0. They are amazingly useful for simple tokenizing.
*/
char *firstin(s,l)
register char *s;
char *l;
{
for (;*s;s++)
if (strchr(l,*s)) break;
return(s);
}
char *firstout(s,l)
register char *s;
char *l;
{
for (;*s;s++)
if (!strchr(l,*s)) break;
return(s);
}
/* STRSTR() find a string in a string. This is the dumb algorithm, meant only
* for short strings. It is only here for those Unixes who don't have one in
* the system library.
*/
#ifdef NOSTRSTR
char *strstr(s,p)
char *s, *p;
{
register char *sp, *pp;
for(sp= s, pp= p; *sp && *pp; )
{
if (*sp == *pp)
{
sp++;
pp++;
}
else
{
sp= sp - (pp - p) + 1;
pp= p;
}
}
return(*pp ? NULL : sp-(pp-p));
}
#endif /*NOSTRSTR*/
/* LISTCHN: prints a list of channels. This will include any channel named
* uniquely in the chantab, plus any volatile channels with at least one user.
*/
int listchn()
{
FILE *fp;
char *o,*n;
struct chnname *head=NULL, *ch, *chn;
int ln,clen;
/* Open the Channel Table */
if ((fp= openchn()) == NULL)
return(1);
/* Put names from the chantab into the list */
while(fgets(txbuf,BFSZ,fp) != NULL)
{
*firstin(txbuf," \t\n")= '\0';
/* Scan for wild cards, and eliminate backslashes */
for (o= n= txbuf; *o != '\0'; o++,n++)
{
if (*o == '*' || *o == '?' || *o == '[')
goto skipit;
if (*o == '\\') o++;
*n= *o;
}
*n= '\0';
head= addchn(head,txbuf,0);
skipit: ;
}
/* Count up the users and find any volatile channels */
head= who_clist(head);
/* Print out the list */
fputs(" Channel Users | Channel Users | Channel Users",
stderr);
ln= 0;
for (ch= head; ch != NULL; ch= ch->next)
{
if ((ln++)%3 == 0)
fputc('\n',stderr);
else
fputs(" | ",stderr);
fputc(strncmp(ch->name,channel,CHN_LEN) ? ' ' : '>', stderr);
fputs(ch->name,stderr);
clen= strlen(ch->name);
#ifndef NOCLOSE
if (check_open(ch->name) != 1)
{
fputs("(closed)",stderr);
clen+= 8;
}
#endif NOCLOSE
for ( ; clen < 20; clen++)
fputc(' ', stderr);
fprintf(stderr,"%3d",ch->users);
}
fputc('\n',stderr);
if (ln == 0) fputs("No Channels\n",stderr);
/* Free the list */
for (ch= head; ch != NULL; ch= chn)
{
chn= ch->next;
free(ch);
}
}
/* ADDCHN: Add an entry to the channel list in alphabetical order.
*/
struct chnname *addchn(head,name,users)
struct chnname *head;
char *name;
int users;
{
struct chnname *c, *p, *new;
for (c= head, p= NULL; c != NULL; c= (p= c)->next)
if (strncmp(name,c->name,CHN_LEN) < 0)
break;
new= (struct chnname *)malloc((mtype)sizeof(struct chnname));
strncpy(new->name,name,CHN_LEN);
new->name[CHN_LEN]= '\0';
new->users= users;
new->next= c;
if (p == NULL)
head= new;
else
p->next= new;
return(head);
}
/* OPENCHN: Open the chantab file for reading and returns the file descriptor.
* (actually it only opens it on the first call. Later it just rewinds.)
*/
FILE *openchn()
{
static FILE *fp= NULL;
/* Open the Channel Table */
if (fp == NULL)
{
if ((fp= fopen(opt[OPT_CHANTAB].str,"r")) == NULL)
{
err("cannot open %s\n", opt[OPT_CHANTAB].str);
return(NULL);
}
}
else
rewind(fp);
return(fp);
}
/* This returns true if the string s matches the pattern p. The pattern syntax
* is similar to the shell's globbing mechanism, with ? matching any character,
* [...] matching any character in the brackets, and * matching any sequence
* of characters. Backslashes escape any of these special characters. This
* version doesn't support {...} patterns, mainly because the kludgy way I
* wrote it makes those a bit difficult.
*/
int patmatch(s,p)
char *s, *p;
{
int found;
char *q;
while (*s != '\0')
{
switch (*p)
{
case '\0': /* End of pattern but not end of text -- fail */
return(0);
case '?': /* '?' matches any one character */
break;
case '*': /* '*' matches any sequence of characters */
if (*(p+1) == '\0') return(1); /* speeds common case */
return(patmatch(s,p+1) || patmatch(s+1,p));
case '[': /* [...] matches any character in the brackets */
found= 0;
while (*(++p) != ']')
{
if (*p == '\\') p++;
if (*p == '\0') return(0);
if (*p == *s)
{found= 1; break;}
if (*(p+1) == '-' && *(q= p+2) != ']')
{
if (*q == '\\') q++;
if (*q == '\0') return(0);
if (*p < *s && *s <= *q)
{found= 1; p= q; break;}
p= q;
}
}
if (!found) return(0);
while (*(++p) != ']')
{
if (*p == '\\') p++;
if (*p == '\0') return(0);
}
break;
case '\\': /* '\x' matches character x */
p++;
default:
if (*s != *p) return(0);
break;
}
p++; s++;
}
return(*p == '\0');
}
set_debug()
{
long tm;
if (debug) fclose(debug);
if (opt[OPT_DEBUG].str[0] == '\0')
opt[OPT_DEBUG].yes= 0;
if (!opt[OPT_DEBUG].yes)
{
debug= NULL;
err("debugging off\n");
return;
}
if ((debug= fopen(opt[OPT_DEBUG].str,"a")) == NULL)
{
fprintf(stderr,"cannot open debug file %s\n",
opt[OPT_DEBUG].str);
return;
}
tm= time((long *)0);
fprintf(debug,"debug started %s",ctime(&tm));
printopts(debug,0,' ',"all");
fflush(debug);
fprintf(stderr,"debugging output in %s\n",opt[OPT_DEBUG].str);
}
syntax highlighted by Code2HTML, v. 0.9.1