mirror of https://github.com/Cisco-Talos/clamav
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
510 lines
16 KiB
510 lines
16 KiB
/*
|
|
* Copyright (C) 2002 - 2007 Tomasz Kojm <tkojm@clamav.net>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
* MA 02110-1301, USA.
|
|
*/
|
|
|
|
#if HAVE_CONFIG_H
|
|
#include "clamav-config.h"
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
|
|
#include "shared/cfgparser.h"
|
|
#include "shared/misc.h"
|
|
|
|
#include "libclamav/str.h"
|
|
|
|
struct cfgoption cfg_options[] = {
|
|
{"LogFile", OPT_QUOTESTR, -1, NULL, 0, OPT_CLAMD},
|
|
{"LogFileUnlock", OPT_BOOL, 0, NULL, 0, OPT_CLAMD},
|
|
{"LogFileMaxSize", OPT_COMPSIZE, 1048576, NULL, 0, OPT_CLAMD | OPT_FRESHCLAM},
|
|
{"LogTime", OPT_BOOL, 0, NULL, 0, OPT_CLAMD | OPT_FRESHCLAM},
|
|
{"LogClean", OPT_BOOL, 0, NULL, 0, OPT_CLAMD},
|
|
{"LogVerbose", OPT_BOOL, 0, NULL, 0, OPT_CLAMD | OPT_FRESHCLAM},
|
|
{"LogSyslog", OPT_BOOL, 0, NULL, 0, OPT_CLAMD | OPT_FRESHCLAM},
|
|
{"LogFacility", OPT_QUOTESTR, -1, "LOG_LOCAL6", 0, OPT_CLAMD | OPT_FRESHCLAM},
|
|
{"PidFile", OPT_QUOTESTR, -1, NULL, 0, OPT_CLAMD | OPT_FRESHCLAM},
|
|
{"TemporaryDirectory", OPT_QUOTESTR, -1, NULL, 0, OPT_CLAMD},
|
|
{"ScanPE", OPT_BOOL, 1, NULL, 0, OPT_CLAMD},
|
|
{"ScanELF", OPT_BOOL, 1, NULL, 0, OPT_CLAMD},
|
|
{"DetectBrokenExecutables", OPT_BOOL, 0, NULL, 0, OPT_CLAMD},
|
|
{"ScanMail", OPT_BOOL, 1, NULL, 0, OPT_CLAMD},
|
|
{"MailFollowURLs", OPT_BOOL, 0, NULL, 0, OPT_CLAMD},
|
|
{"PhishingSignatures", OPT_BOOL, 1, NULL, 0, OPT_CLAMD},
|
|
{"PhishingScanURLs",OPT_BOOL, 1, NULL, 0, OPT_CLAMD},
|
|
/* these are FP prone options, if default isn't used */
|
|
{"PhishingAlwaysBlockCloak", OPT_BOOL, 0, NULL, 0, OPT_CLAMD},
|
|
{"PhishingAlwaysBlockSSLMismatch", OPT_BOOL, 0, NULL, 0, OPT_CLAMD},
|
|
{"PhishingRestrictedScan", OPT_BOOL, 1, NULL, 0, OPT_CLAMD},
|
|
/* end of FP prone options */
|
|
{"DetectPUA", OPT_BOOL, 0, NULL, 0, OPT_CLAMD},
|
|
{"AlgorithmicDetection", OPT_BOOL, 1, NULL, 0, OPT_CLAMD},
|
|
{"ScanHTML", OPT_BOOL, 1, NULL, 0, OPT_CLAMD},
|
|
{"ScanOLE2", OPT_BOOL, 1, NULL, 0, OPT_CLAMD},
|
|
{"ScanPDF", OPT_BOOL, 0, NULL, 0, OPT_CLAMD},
|
|
{"ScanArchive", OPT_BOOL, 1, NULL, 0, OPT_CLAMD},
|
|
{"MaxScanSize", OPT_COMPSIZE, 104857600, NULL, 0, OPT_CLAMD}, /* FIXMELIMITS */
|
|
{"MaxFileSize", OPT_COMPSIZE, 10485760, NULL, 0, OPT_CLAMD},
|
|
{"MaxRecursion", OPT_NUM, 8, NULL, 0, OPT_CLAMD},
|
|
{"MaxFiles", OPT_NUM, 1000, NULL, 0, OPT_CLAMD},
|
|
{"ArchiveLimitMemoryUsage", OPT_BOOL, 0, NULL, 0, OPT_CLAMD},
|
|
{"ArchiveBlockEncrypted", OPT_BOOL, 0, NULL, 0, OPT_CLAMD},
|
|
{"DatabaseDirectory", OPT_QUOTESTR, -1, DATADIR, 0, OPT_CLAMD | OPT_FRESHCLAM},
|
|
{"TCPAddr", OPT_QUOTESTR, -1, NULL, 0, OPT_CLAMD},
|
|
{"TCPSocket", OPT_NUM, -1, NULL, 0, OPT_CLAMD},
|
|
{"LocalSocket", OPT_QUOTESTR, -1, NULL, 0, OPT_CLAMD},
|
|
{"MaxConnectionQueueLength", OPT_NUM, 15, NULL, 0, OPT_CLAMD},
|
|
{"StreamMaxLength", OPT_COMPSIZE, 10485760, NULL, 0, OPT_CLAMD},
|
|
{"StreamMinPort", OPT_NUM, 1024, NULL, 0, OPT_CLAMD},
|
|
{"StreamMaxPort", OPT_NUM, 2048, NULL, 0, OPT_CLAMD},
|
|
{"MaxThreads", OPT_NUM, 10, NULL, 0, OPT_CLAMD},
|
|
{"ReadTimeout", OPT_NUM, 120, NULL, 0, OPT_CLAMD},
|
|
{"IdleTimeout", OPT_NUM, 30, NULL, 0, OPT_CLAMD},
|
|
{"MaxDirectoryRecursion", OPT_NUM, 15, NULL, 0, OPT_CLAMD},
|
|
{"FollowDirectorySymlinks", OPT_BOOL, 0, NULL, 0, OPT_CLAMD},
|
|
{"FollowFileSymlinks", OPT_BOOL, 0, NULL, 0, OPT_CLAMD},
|
|
{"ExitOnOOM", OPT_BOOL, 0, NULL, 0, OPT_CLAMD},
|
|
{"Foreground", OPT_BOOL, 0, NULL, 0, OPT_CLAMD | OPT_FRESHCLAM},
|
|
{"Debug", OPT_BOOL, 0, NULL, 0, OPT_CLAMD | OPT_FRESHCLAM},
|
|
{"LeaveTemporaryFiles", OPT_BOOL, 0, NULL, 0, OPT_CLAMD},
|
|
{"FixStaleSocket", OPT_BOOL, 1, NULL, 0, OPT_CLAMD},
|
|
{"User", OPT_QUOTESTR, -1, NULL, 0, OPT_CLAMD},
|
|
{"AllowSupplementaryGroups", OPT_BOOL, 0, NULL, 0, OPT_CLAMD | OPT_FRESHCLAM},
|
|
{"SelfCheck", OPT_NUM, 1800, NULL, 0, OPT_CLAMD},
|
|
{"VirusEvent", OPT_FULLSTR, -1, NULL, 0, OPT_CLAMD},
|
|
{"ClamukoScanOnAccess", OPT_BOOL, -1, NULL, 0, OPT_CLAMD},
|
|
{"ClamukoScanOnOpen", OPT_BOOL, -1, NULL, 0, OPT_CLAMD},
|
|
{"ClamukoScanOnClose", OPT_BOOL, -1, NULL, 0, OPT_CLAMD},
|
|
{"ClamukoScanOnExec", OPT_BOOL, -1, NULL, 0, OPT_CLAMD},
|
|
{"ClamukoIncludePath", OPT_QUOTESTR, -1, NULL, 1, OPT_CLAMD},
|
|
{"ClamukoExcludePath", OPT_QUOTESTR, -1, NULL, 1, OPT_CLAMD},
|
|
{"ClamukoMaxFileSize", OPT_COMPSIZE, 5242880, NULL, 0, OPT_CLAMD},
|
|
{"DatabaseOwner", OPT_QUOTESTR, -1, CLAMAVUSER, 0, OPT_FRESHCLAM},
|
|
{"Checks", OPT_NUM, 12, NULL, 0, OPT_FRESHCLAM},
|
|
{"UpdateLogFile", OPT_QUOTESTR, -1, NULL, 0, OPT_FRESHCLAM},
|
|
{"DNSDatabaseInfo", OPT_QUOTESTR, -1, "current.cvd.clamav.net", 0, OPT_FRESHCLAM},
|
|
{"DatabaseMirror", OPT_QUOTESTR, -1, NULL, 1, OPT_FRESHCLAM},
|
|
{"MaxAttempts", OPT_NUM, 3, NULL, 0, OPT_FRESHCLAM},
|
|
{"ScriptedUpdates", OPT_BOOL, 1, NULL, 0, OPT_FRESHCLAM},
|
|
{"CompressLocalDatabase", OPT_BOOL, 0, NULL, 0, OPT_FRESHCLAM},
|
|
{"HTTPProxyServer", OPT_QUOTESTR, -1, NULL, 0, OPT_FRESHCLAM},
|
|
{"HTTPProxyPort", OPT_NUM, -1, NULL, 0, OPT_FRESHCLAM},
|
|
{"HTTPProxyUsername", OPT_QUOTESTR, -1, NULL, 0, OPT_FRESHCLAM},
|
|
{"HTTPProxyPassword", OPT_QUOTESTR, -1, NULL, 0, OPT_FRESHCLAM},
|
|
{"HTTPUserAgent", OPT_FULLSTR, -1, NULL, 0, OPT_FRESHCLAM},
|
|
{"NotifyClamd", OPT_QUOTESTR, -1, NULL, 0, OPT_FRESHCLAM},
|
|
{"OnUpdateExecute", OPT_FULLSTR, -1, NULL, 0, OPT_FRESHCLAM},
|
|
{"OnErrorExecute", OPT_FULLSTR, -1, NULL, 0, OPT_FRESHCLAM},
|
|
{"OnOutdatedExecute", OPT_FULLSTR, -1, NULL, 0, OPT_FRESHCLAM},
|
|
{"LocalIPAddress", OPT_QUOTESTR, -1, NULL, 0, OPT_FRESHCLAM},
|
|
{"ConnectTimeout", OPT_NUM, 30, NULL, 0, OPT_FRESHCLAM},
|
|
{"ReceiveTimeout", OPT_NUM, 30, NULL, 0, OPT_FRESHCLAM},
|
|
|
|
{"DevACOnly", OPT_BOOL, -1, NULL, 0, OPT_CLAMD},
|
|
{"DevACDepth", OPT_NUM, -1, NULL, 0, OPT_CLAMD},
|
|
|
|
{NULL, 0, 0, NULL, 0, 0}
|
|
};
|
|
|
|
static int regcfg(struct cfgstruct **copt, const char *optname, char *strarg, int numarg, short multiple);
|
|
|
|
struct cfgstruct *getcfg(const char *cfgfile, int verbose)
|
|
{
|
|
char buff[LINE_LENGTH], *name, *arg, *c;
|
|
FILE *fs;
|
|
int line = 0, i, found, ctype, calc, val;
|
|
struct cfgstruct *copt = NULL;
|
|
struct cfgoption *pt;
|
|
|
|
|
|
for(i = 0; ; i++) {
|
|
pt = &cfg_options[i];
|
|
if(!pt->name)
|
|
break;
|
|
|
|
if(regcfg(&copt, pt->name, pt->strarg ? strdup(pt->strarg) : NULL, pt->numarg, pt->multiple) < 0) {
|
|
fprintf(stderr, "ERROR: Can't register new options (not enough memory)\n");
|
|
freecfg(copt);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if((fs = fopen(cfgfile, "rb")) == NULL) {
|
|
/* do not print error message here! */
|
|
freecfg(copt);
|
|
return NULL;
|
|
}
|
|
|
|
while(fgets(buff, LINE_LENGTH, fs)) {
|
|
line++;
|
|
|
|
if(buff[0] == '#')
|
|
continue;
|
|
|
|
if(!strncmp("Example", buff, 7)) {
|
|
if(verbose)
|
|
fprintf(stderr, "ERROR: Please edit the example config file %s.\n", cfgfile);
|
|
fclose(fs);
|
|
freecfg(copt);
|
|
return NULL;
|
|
}
|
|
|
|
if((name = cli_strtok(buff, 0, " \r\n"))) {
|
|
arg = cli_strtok(buff, 1, " \r\n");
|
|
found = 0;
|
|
for(i = 0; ; i++) {
|
|
pt = &cfg_options[i];
|
|
if(pt->name) {
|
|
if(!strcmp(name, pt->name)) {
|
|
found = 1;
|
|
switch(pt->argtype) {
|
|
case OPT_STR:
|
|
/* deprecated. Use OPT_QUOTESTR instead since it behaves like this, but supports quotes to allow values to contain whitespace */
|
|
if(!arg) {
|
|
if(verbose)
|
|
fprintf(stderr, "ERROR: Parse error at line %d: Option %s requires string argument.\n", line, name);
|
|
fclose(fs);
|
|
free(name);
|
|
freecfg(copt);
|
|
return NULL;
|
|
}
|
|
if(regcfg(&copt, name, arg, -1, pt->multiple) < 0) {
|
|
fprintf(stderr, "ERROR: Can't register new options (not enough memory)\n");
|
|
fclose(fs);
|
|
free(name);
|
|
free(arg);
|
|
freecfg(copt);
|
|
return NULL;
|
|
}
|
|
break;
|
|
case OPT_FULLSTR:
|
|
/* an ugly hack of the above case */
|
|
if(!arg) {
|
|
if(verbose)
|
|
fprintf(stderr, "ERROR: Parse error at line %d: Option %s requires string argument.\n", line, name);
|
|
fclose(fs);
|
|
free(name);
|
|
freecfg(copt);
|
|
return NULL;
|
|
}
|
|
free(arg);
|
|
arg = strstr(buff, " ");
|
|
arg = strdup(++arg);
|
|
if((arg) && (c = strpbrk(arg, "\n\r")))
|
|
*c = '\0';
|
|
if((!arg) || (regcfg(&copt, name, arg, -1, pt->multiple) < 0)) {
|
|
fprintf(stderr, "ERROR: Can't register new options (not enough memory)\n");
|
|
fclose(fs);
|
|
free(name);
|
|
free(arg);
|
|
freecfg(copt);
|
|
return NULL;
|
|
}
|
|
break;
|
|
case OPT_QUOTESTR:
|
|
/* an ugly hack of the above case */
|
|
if(!arg) {
|
|
if(verbose)
|
|
fprintf(stderr, "ERROR: Parse error at line %d: Option %s requires string argument.\n", line, name);
|
|
fclose(fs);
|
|
free(name);
|
|
freecfg(copt);
|
|
return NULL;
|
|
}
|
|
if((*arg == '\'') || (*arg == '"')) {
|
|
free(arg);
|
|
c = strstr(buff, " ");
|
|
arg = strdup(c+2);
|
|
if(arg) {
|
|
if((c = strchr(arg, c[1])))
|
|
*c = '\0';
|
|
else {
|
|
if(verbose)
|
|
fprintf(stderr, "ERROR: Parse error at line %d: Option %s missing closing quote.\n", line, name);
|
|
fclose(fs);
|
|
free(name);
|
|
free(arg);
|
|
freecfg(copt);
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
if((!arg) || (regcfg(&copt, name, arg, -1, pt->multiple) < 0)) {
|
|
fprintf(stderr, "ERROR: Can't register new options (not enough memory)\n");
|
|
fclose(fs);
|
|
free(name);
|
|
free(arg);
|
|
freecfg(copt);
|
|
return NULL;
|
|
}
|
|
break;
|
|
case OPT_NUM:
|
|
if(!arg || !isnumb(arg)) {
|
|
if(verbose)
|
|
fprintf(stderr, "ERROR: Parse error at line %d: Option %s requires numerical argument.\n", line, name);
|
|
fclose(fs);
|
|
free(name);
|
|
free(arg);
|
|
freecfg(copt);
|
|
return NULL;
|
|
}
|
|
if(regcfg(&copt, name, NULL, atoi(arg), pt->multiple) < 0) {
|
|
fprintf(stderr, "ERROR: Can't register new options (not enough memory)\n");
|
|
fclose(fs);
|
|
free(name);
|
|
free(arg);
|
|
freecfg(copt);
|
|
return NULL;
|
|
}
|
|
free(arg);
|
|
break;
|
|
case OPT_COMPSIZE:
|
|
if(!arg) {
|
|
if(verbose)
|
|
fprintf(stderr, "ERROR: Parse error at line %d: Option %s requires argument.\n", line, name);
|
|
fclose(fs);
|
|
free(name);
|
|
freecfg(copt);
|
|
return NULL;
|
|
}
|
|
ctype = tolower(arg[strlen(arg) - 1]);
|
|
if(ctype == 'm' || ctype == 'k') {
|
|
char *cpy = (char *) calloc(strlen(arg), 1);
|
|
strncpy(cpy, arg, strlen(arg) - 1);
|
|
if(!isnumb(cpy)) {
|
|
if(verbose)
|
|
fprintf(stderr, "ERROR: Parse error at line %d: Option %s requires numerical (raw/K/M) argument.\n", line, name);
|
|
fclose(fs);
|
|
free(name);
|
|
free(arg);
|
|
freecfg(copt);
|
|
return NULL;
|
|
}
|
|
if(ctype == 'm')
|
|
calc = atoi(cpy) * 1024 * 1024;
|
|
else
|
|
calc = atoi(cpy) * 1024;
|
|
free(cpy);
|
|
} else {
|
|
if(!isnumb(arg)) {
|
|
if(verbose)
|
|
fprintf(stderr, "ERROR: Parse error at line %d: Option %s requires numerical (raw/K/M) argument.\n", line, name);
|
|
fclose(fs);
|
|
free(name);
|
|
free(arg);
|
|
freecfg(copt);
|
|
return NULL;
|
|
}
|
|
calc = atoi(arg);
|
|
}
|
|
free(arg);
|
|
if(regcfg(&copt, name, NULL, calc, pt->multiple) < 0) {
|
|
fprintf(stderr, "ERROR: Can't register new options (not enough memory)\n");
|
|
fclose(fs);
|
|
free(name);
|
|
free(arg);
|
|
freecfg(copt);
|
|
return NULL;
|
|
}
|
|
break;
|
|
case OPT_BOOL:
|
|
|
|
if(!arg) {
|
|
if(verbose)
|
|
fprintf(stderr, "ERROR: Parse error at line %d: Option %s requires boolean argument.\n", line, name);
|
|
fclose(fs);
|
|
free(name);
|
|
freecfg(copt);
|
|
return NULL;
|
|
}
|
|
|
|
if(!strcasecmp(arg, "yes") || !strcmp(arg, "1") || !strcasecmp(arg, "true")) {
|
|
val = 1;
|
|
} else if(!strcasecmp(arg, "no") || !strcmp(arg, "0") || !strcasecmp(arg, "false")) {
|
|
val = 0;
|
|
} else {
|
|
if(verbose)
|
|
fprintf(stderr, "ERROR: Parse error at line %d: Option %s requires boolean argument.\n", line, name);
|
|
fclose(fs);
|
|
free(name);
|
|
free(arg);
|
|
freecfg(copt);
|
|
return NULL;
|
|
}
|
|
free(arg);
|
|
if(regcfg(&copt, name, NULL, val, pt->multiple) < 0) {
|
|
fprintf(stderr, "ERROR: Can't register new options (not enough memory)\n");
|
|
fclose(fs);
|
|
free(name);
|
|
free(arg);
|
|
freecfg(copt);
|
|
return NULL;
|
|
}
|
|
break;
|
|
default:
|
|
if(verbose)
|
|
fprintf(stderr, "ERROR: Parse error at line %d: Option %s is of unknown type %d\n", line, name, pt->argtype);
|
|
fclose(fs);
|
|
free(name);
|
|
free(arg);
|
|
freecfg(copt);
|
|
return NULL;
|
|
}
|
|
}
|
|
} else
|
|
break;
|
|
}
|
|
|
|
if(!found) {
|
|
if(verbose)
|
|
fprintf(stderr, "ERROR: Parse error at line %d: Unknown option %s.\n", line, name);
|
|
free(name);
|
|
fclose(fs);
|
|
freecfg(copt);
|
|
return NULL;
|
|
}
|
|
free(name);
|
|
}
|
|
}
|
|
|
|
fclose(fs);
|
|
return copt;
|
|
}
|
|
|
|
void freecfg(struct cfgstruct *copt)
|
|
{
|
|
struct cfgstruct *handler;
|
|
struct cfgstruct *arg;
|
|
|
|
while(copt) {
|
|
arg = copt->nextarg;
|
|
while(arg) {
|
|
if(arg->strarg) {
|
|
free(arg->optname);
|
|
free(arg->strarg);
|
|
handler = arg;
|
|
arg = arg->nextarg;
|
|
free(handler);
|
|
} else
|
|
arg = arg->nextarg;
|
|
}
|
|
if(copt->optname)
|
|
free(copt->optname);
|
|
|
|
if(copt->strarg)
|
|
free(copt->strarg);
|
|
|
|
handler = copt;
|
|
copt = copt->next;
|
|
free(handler);
|
|
}
|
|
return;
|
|
}
|
|
|
|
const struct cfgstruct *cfgopt(const struct cfgstruct *copt, const char *optname)
|
|
{
|
|
while(copt) {
|
|
if(copt->optname && !strcmp(copt->optname, optname))
|
|
return copt;
|
|
|
|
copt = copt->next;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct cfgstruct *cfgopt_i(struct cfgstruct *copt, const char *optname)
|
|
{
|
|
while(copt) {
|
|
if(copt->optname && !strcmp(copt->optname, optname))
|
|
return copt;
|
|
|
|
copt = copt->next;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int regcfg(struct cfgstruct **copt, const char *optname, char *strarg, int numarg, short multiple)
|
|
{
|
|
struct cfgstruct *newnode, *pt;
|
|
|
|
|
|
newnode = (struct cfgstruct *) malloc(sizeof(struct cfgstruct));
|
|
|
|
if(!newnode)
|
|
return -1;
|
|
|
|
newnode->optname = optname ? strdup(optname) : NULL;
|
|
newnode->nextarg = NULL;
|
|
newnode->next = NULL;
|
|
newnode->enabled = 0;
|
|
newnode->multiple = multiple;
|
|
|
|
if(strarg) {
|
|
newnode->strarg = strarg;
|
|
newnode->enabled = 1;
|
|
} else {
|
|
newnode->strarg = NULL;
|
|
}
|
|
|
|
newnode->numarg = numarg;
|
|
if(numarg != -1 && numarg != 0)
|
|
newnode->enabled = 1;
|
|
|
|
if((pt = cfgopt_i(*copt, optname))) {
|
|
if(pt->multiple) {
|
|
|
|
if(pt->enabled) {
|
|
while(pt->nextarg)
|
|
pt = pt->nextarg;
|
|
|
|
pt->nextarg = newnode;
|
|
} else {
|
|
if(pt->strarg)
|
|
free(pt->strarg);
|
|
pt->strarg = newnode->strarg;
|
|
pt->numarg = newnode->numarg;
|
|
pt->enabled = newnode->enabled;
|
|
if(newnode->optname)
|
|
free(newnode->optname);
|
|
free(newnode);
|
|
}
|
|
return 3; /* registered additional argument */
|
|
|
|
} else {
|
|
if(pt->strarg)
|
|
free(pt->strarg);
|
|
pt->strarg = newnode->strarg;
|
|
pt->numarg = newnode->numarg;
|
|
pt->enabled = newnode->enabled;
|
|
if(newnode->optname)
|
|
free(newnode->optname);
|
|
free(newnode);
|
|
return 2;
|
|
}
|
|
|
|
} else {
|
|
newnode->next = *copt;
|
|
*copt = newnode;
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
|