/*
##### ## ##### #### ###### ##### ####
# # # # # # # # # # # #
# # # # # # #### ##### # # #
##### ###### ##### # # ##### ### #
# # # # # # # # # # ### # #
# # # # # #### ###### # # ### ####
Parse a configuration file.
*/
/*
* $Id: parser.c,v 3.0.1.13 1997/09/15 15:03:51 ram Exp $
*
* Copyright (c) 1990-1993, Raphael Manfredi
*
* You may redistribute only under the terms of the Artistic License,
* as specified in the README file that comes with the distribution.
* You may reuse parts of this distribution only within the terms of
* that same Artistic License; a copy of which may be found at the root
* of the source tree for mailagent 3.0.
*
* $Log: parser.c,v $
* Revision 3.0.1.13 1997/09/15 15:03:51 ram
* patch57: changed ordering of include files
*
* Revision 3.0.1.12 1997/02/20 11:38:07 ram
* patch55: skip group-writable and exec-safe checks if told to
*
* Revision 3.0.1.11 1997/01/31 18:07:11 ram
* patch54: forgot one more get_confval vs get_confstr translation
*
* Revision 3.0.1.10 1997/01/08 08:42:31 ram
* patch53: must use get_confstr() to get at the execsafe variable
*
* Revision 3.0.1.9 1997/01/07 18:27:57 ram
* patch52: don't perform extended exec() checks when execsafe is OFF
*
* Revision 3.0.1.8 1996/12/26 10:46:35 ram
* patch51: include <unistd.h> for X_OK and define fallbacks
*
* Revision 3.0.1.7 1996/12/24 14:01:13 ram
* patch45: enhanced security checks performed on files
* patch45: the _ character was not correctly parsed in variables
*
* Revision 3.0.1.6 1995/08/31 16:25:42 ram
* patch42: now uses say() to print messages on stderr
*
* Revision 3.0.1.5 1995/08/07 16:11:13 ram
* patch37: removed useless local variable declaration
*
* Revision 3.0.1.4 1995/02/03 17:56:15 ram
* patch30: moved definition of S_IWOTH and S_IWGRP to the top
*
* Revision 3.0.1.3 1994/09/22 13:47:21 ram
* patch12: extended security checks to mimic those done by mailagent
*
* Revision 3.0.1.2 1994/07/01 14:53:57 ram
* patch8: new routine get_confval to get integer config variables
*
* Revision 3.0.1.1 1994/01/26 09:27:37 ram
* patch5: typo fix in a comment
*
* Revision 3.0 1993/11/29 13:48:18 ram
* Baseline for mailagent 3.0 netwide release.
*
*/
#include "config.h"
#include "portable.h"
#include <sys/types.h>
#include <stdio.h>
#include <ctype.h>
#include <pwd.h>
#include <sys/stat.h>
#ifdef I_STRING
#include <string.h>
#else
#include <strings.h>
#endif
#ifdef I_SYS_PARAM
#include <sys/param.h>
#endif
#ifndef MAX_PATHLEN
#define MAX_PATHLEN 2048 /* Maximum path length allowed by kernel */
#endif
#ifndef HAS_GETHOSTNAME
#ifdef HAS_UNAME
#include <sys/utsname.h>
#endif
#endif
#ifdef I_UNISTD
#include <unistd.h> /* X_OK and friends */
#endif
#ifdef I_FCNTL
#include <fcntl.h>
#endif
#ifdef I_SYS_FILE
#include <sys/file.h> /* Needed for X_OK */
#endif
#ifndef I_FCNTL
#ifndef I_SYS_FILE
#include <sys/fcntl.h> /* Try this one in last resort */
#endif
#endif
/*
* The following should be defined in <sys/stat.h>.
*/
#ifndef S_IWOTH
#define S_IWOTH 00002 /* Write permissions for other */
#endif
#ifndef S_IWGRP
#define S_IWGRP 00020 /* Write permissions for group */
#endif
#ifndef S_ISUID
#define S_ISUID 04000 /* Set user ID on execution */
#endif
#ifndef S_ISGID
#define S_ISGID 02000 /* Set group ID on execution */
#endif
#ifndef X_OK
#define X_OK 1 /* Test for execute (search) permission */
#endif
#include "hash.h"
#include "msg.h"
#include "parser.h"
#include "logfile.h"
#include "environ.h"
#include "confmagic.h"
#define MAX_STRING 2048 /* Maximum length for strings */
#define SYMBOLS 50 /* Expected number of symbols */
/* check_perm flags */
#define MUST_OWN 0x0001 /* File/directory must be owned */
#define MAY_PANIC 0x0002 /* Whether we may panic */
#define SECURE_ON 0x0004 /* Force secure tests */
/* Function declarations */
public void read_conf(); /* Read configuration file */
public void set_env_vars(); /* Set envrionment variables */
public int exec_secure(); /* Checks whether exec() is safe on file */
private void secure(); /* Perform basic security checks on file */
private int check_perm(); /* Check permissions on file */
private void get_home(); /* Extract home from /etc/passwd */
private void substitute(); /* Variable and ~ substitutions */
private void add_home(); /* Replace ~ with home directory */
private void add_variable(); /* Replace $var by its value */
private void insert_value(); /* Record variable value in H table */
private char *machine_name(); /* Return the machine name */
private char *strip_down(); /* Strip down domain name from host name */
private void strip_comment(); /* Strip trailing comment in config line */
private void start_log(); /* Start up logging */
private char *home = (char *) 0; /* Location of the home directory */
public struct htable symtab; /* Symbol table */
extern char *strsave(); /* Save string value in memory */
extern struct passwd *getpwuid(); /* Fetch /etc/passwd entry from uid */
extern char *getenv(); /* Get environment variable */
public void read_conf(myself, file)
char *myself;
char *file;
{
/* Read file in the home directory and build a symbol H table on the fly.
* The ~ substitution and usual $var substitution occur (but not ${var}).
* As we go, we perform basic sanity and security checks on the overall
* configuration.
*/
char path[MAX_STRING]; /* Full path of the config file */
char *rules; /* Path of the rule file, if any */
char mailagent[MAX_STRING]; /* Path of the configuration file */
FILE *fd; /* File descriptor used for config file */
int line = 0; /* Line number */
struct stat buf; /* Statistics buffer */
if (home == (char *) 0) /* Home not already artificially set */
get_home(); /* Get home directory via /etc/passwd */
/* Build full path for configuration file, based on $HOME */
strcpy(path, home);
strcat(path, "/");
strcat(path, file);
strcpy(mailagent, path); /* Save configuration path for later */
fd = fopen(path, "r");
if (fd == (FILE *) 0)
fatal("cannot open config file %s", path);
/* Initialize the H table */
if (-1 == ht_create(&symtab, SYMBOLS))
fatal("cannot create symbol table");
while((char *) 0 != fgets(path, MAX_STRING - 1, fd)) {
line ++; /* One more line */
substitute(path); /* Standard parameter substitutions */
insert_value(path, line); /* Record value in hash table */
}
fclose(fd);
/* Some security checks are in order here, or someone could set up a fake
* a config file for us and then let the mailagent execute arbitrary
* commands under our uid. These tests are performed after the parsing of
* the file, to allow logging of errors.
*/
start_log(); /* Start up loging */
secure(mailagent); /* Perform basic security checks */
/* Final security check on the rule file, if provided. The constraints are
* the same as those for the ~/.mailagent configuration file. This is
* because a rule can specify a RUN command, which will start a process
* with the user's privileges.
*/
rules = get_confstr_opt("rules"); /* Fetch rules location */
if (rules) /* No rule file is perfectly fine */
check_perm(rules, MUST_OWN | MAY_PANIC); /* Might not exist */
/* Make sure I cannot get compromised... */
if (*myself == '/') { /* Only possible with absoulte path */
add_log(19, "checking myself at %s", myself);
if (!exec_secure(myself)) {
char *error = "ERROR --FILTER PROGRAM CAN BE TAMPERED WITH--";
say(error); /* Make sure they see it */
add_log(1, error);
}
}
/* And that the .forward which invoked me is secure... */
strcpy(path, home);
strcat(path, "/");
strcat(path, ".forward");
if (-1 != stat(path, &buf)) { /* File exists, not called manually */
add_log(19, "checking %s", path);
if (!exec_secure(path)) {
char *error = "ERROR --YOUR .forward FILE CAN BE TAMPERED WITH--";
say(error); /* Make sure they see it */
add_log(1, error);
}
}
}
private void start_log()
{
/* Start up logging, if possible. Note that not defining a logging
* directory or a logging level is a fatal error.
*/
char logfile[MAX_STRING]; /* Location of logfile */
char *value; /* Symbol value */
int level = 0; /* Logging level wanted */
value = get_confstr("logdir", CF_MANDATORY);
strcpy(logfile, value);
strcat(logfile, "/"); /* Logging directory */
value = get_confstr("log", CF_MANDATORY); /* Log file basename*/
strcat(logfile, value);
level = get_confval("level", CF_MANDATORY);
set_loglvl(level); /* Logging level wanted */
if (-1 == open_log(logfile))
say("cannot open logfile %s", logfile);
}
private void stat_check(file)
char *file;
{
/* Make sure we can stat() the file */
struct stat buf; /* Statistics buffer */
if (-1 == stat(file, &buf)) {
add_log(1, "SYSERR stat: %m (%e)");
fatal("cannot stat file %s", file);
}
}
private void secure(file)
char *file;
{
/* Make sure the file is owned by the effective uid, and that it is not
* world writable. Otherwise, simply abort with a fatal error.
* Returning from this routine implies that the security checks succeeded.
*/
stat_check(file);
check_perm(file, MUST_OWN | MAY_PANIC); /* Check permissions */
}
public int exec_secure(file)
char *file;
{
/* Same checks as secure(), but without file/directory ownership.
* We propagate SECURE_ON only when execsafe is ON or when the
* user is the superuser.
*
* When execskip is ON, we don't perform the exec() checks at all.
* This variable if OFF by default, i.e. they must explicitely
* turn it ON to disable checking.
*/
char *execsafe = get_confstr("execsafe", CF_DEFAULT, "OFF");
char *execskip = get_confstr("execskip", CF_DEFAULT, "OFF");
int flag = (0 == strcasecmp(execsafe, "ON") || ROOTID == geteuid()) ?
SECURE_ON : 0;
stat_check(file);
if (0 == strcasecmp(execskip, "ON"))
return 1;
return check_perm(file, flag); /* Check permissions */
}
/* VARARGS3 */
private void check_fatal(flags, reason, arg1, arg2, arg3, arg4, arg5)
int flags;
char *reason;
long arg1, arg2, arg3, arg4, arg5;
{
/* Die with a fatal error if MAY_PANIC is specified in flags, otherwise
* simply log the error.
*/
char buffer[MAX_STRING];
if (flags & MAY_PANIC)
fatal(reason, arg1, arg2, arg3, arg4, arg5);
sprintf(buffer, "ERROR %s", reason);
add_log(1, buffer, arg1, arg2, arg3, arg4, arg5);
}
private int check_perm(file, flags)
char *file;
int flags; /* MAY_PANIC | MUST_OWN */
{
/* Check basic permissions on the specified file. It cannot be world
* writable and must be owned by the user. If the file specified does not
* exist, no error is reported however. If the 'secure' option is set
* to ON, or if we are running with superuser credentials, further checks
* are performed on the directory containing the file.
*
* We return true if the file is OK, false otherwise, unless MAY_PANIC
* is activated in which case we don't return but exit with fatal().
*/
struct stat buf; /* Statistics buffer */
char parent[MAX_PATHLEN+1]; /* For parent directory */
char *cfsecure; /* Config value for the 'secure' parameter */
char *c; /* Last slash position in file name */
int wants_secure = 0; /* Set to true for extra security checks */
int wants_group = 1; /* Set to true unless 'groupsafe' is OFF */
if (-1 == stat(file, &buf))
return 0; /* Missing file is not secure! */
if (buf.st_mode & S_IWOTH) {
check_fatal(flags, "file %s is world writable!", file);
return 0; /* Failed checks */
}
if ((flags & MUST_OWN) && buf.st_uid != geteuid()) {
check_fatal(flags, "file %s not owned by user!", file);
return 0; /* Failed checks */
}
/*
* If file is setuid of setgid, make sure only the owner can write to
* it. It's too critical and the system might not clear the set[ug]id bit
* on a write to the file.
*/
if (-1 != access(file, X_OK)) { /* User may execute the file */
if ((buf.st_mode & S_ISUID) && (buf.st_mode & (S_IWOTH|S_IWGRP))) {
check_fatal(flags, "setuid file %s is writable!", file);
return 0;
}
if ((buf.st_mode & S_ISGID) && (buf.st_mode & (S_IWOTH|S_IWGRP))) {
check_fatal(flags, "setgid file %s is writable!", file);
return 0;
}
}
cfsecure = get_confstr_opt("secure"); /* Do they want extra security? */
if (
(flags & SECURE_ON) || /* They want secure checks anyway */
(cfsecure != (char *) 0 && /* Ok, secure is defined */
0 == strcasecmp(cfsecure, "ON")) || /* And extra checks wanted */
geteuid() == ROOTID /* Running as superuser */
)
wants_secure = 1; /* Activate checks */
if (!wants_secure) {
add_log(12, "basic checks ok for file %s", file);
return 1; /* OK */
}
/*
* Extra security checks for group writability and parent directory.
*/
add_log(17, "performing additional checks on %s", file);
if (0 == strcasecmp(get_confstr("groupsafe", CF_DEFAULT, "ON"), "OFF"))
wants_group = 0; /* They trust all the groups! */
if (wants_group && (buf.st_mode & S_IWGRP)) {
check_fatal(flags, "file %s is group writable!", file);
return 0; /* Failed checks */
}
/*
* Ok, go on and check the parent directory...
*/
if (*file != '/') { /* Path is not abosule, assume from home */
strcpy(parent, home); /* Prefill with home */
strcat(parent, "/");
} else
*parent = '\0'; /* Null string */
strcat(parent, file); /* Append file to get an absolute path */
if (c = rindex(parent, '/'))
*c = '\0'; /* Strip down last path component */
add_log(17, "checking directory %s", parent);
if (-1 == stat(parent, &buf)) {
add_log(1, "SYSERR stat: %m (%e)");
check_fatal(flags, "cannot stat directory %s", parent);
return 0; /* Failed checks */
}
if (buf.st_mode & S_IWOTH) {
check_fatal(flags, "directory %s is world writable!", parent);
return 0; /* Failed checks */
}
if (wants_group && (buf.st_mode & S_IWGRP)) {
check_fatal(flags, "directory %s is group writable!", parent);
return 0; /* Failed checks */
}
if ((flags & MUST_OWN) && buf.st_uid != geteuid()) {
check_fatal(flags, "directory %s not owned by user!", parent);
return 0; /* Failed checks */
}
add_log(12, "file %s seems to be secure", file);
return 1; /* OK */
}
public char *homedir()
{
return home; /* Location of the home directory */
}
public void env_home()
{
home = getenv("HOME"); /* For tests only -- see main.c */
if (home != (char *) 0)
home = strsave(home); /* POSIX getenv() returns ptr to static data */
}
private void get_home()
{
/* Get home directory out of /etc/passwd file */
struct passwd *pp; /* Pointer to passwd entry */
pp = getpwuid(geteuid());
if (pp == (struct passwd *) 0)
fatal("cannot locate home directory");
home = strsave(pp->pw_dir);
if (home == (char *) 0)
fatal("no more memory");
}
public void set_env_vars(envp)
char **envp; /* The environment pointer */
{
/* Set the all environment variable correctly. If the configuration file
* defines a variable of the form 'p_host' where "host" is the lowercase
* name of the machine (domain name stripped), then that value is prepended
* to the current value of the PATH variable. We also set HOME and TZ if
* there is a 'timezone' variable in the config file.
*/
char *machine = machine_name(); /* The machine name */
char *path_val; /* Path value to append */
char *tz; /* Time zone value */
char name[MAX_STRING]; /* Built 'p_host' */
init_env(envp); /* Built the current environment */
/* If there is a path: entry in the ~/.mailagent, it is used to replace
* then current PATH value. This entry is of course not mandatory. If not
* present, we'll simply prepend the added path 'p_host' to the existing
* value provided by sendmail, cron, or whoever invoked us.
*/
path_val = get_confstr_opt("path");
if (path_val != (char *) 0) {
if (-1 == set_env("PATH", path_val))
fatal("cannot initialize PATH");
}
sprintf(name, "p_%s", machine); /* Name of field in ~/.mailagent */
path_val = get_confstr_opt(name); /* Exists ? */
if (path_val != (char *) 0) { /* Yes, prepend its value */
add_log(19, "updating PATH with '%s' from config file", name);
if (-1 == prepend_env("PATH", ":"))
fatal("cannot set PATH variable");
if (-1 == prepend_env("PATH", path_val))
fatal("cannot set PATH variable");
}
/* Also set a correct value for the home directory */
if (-1 == set_env("HOME", home))
fatal("cannot set HOME variable");
/* If there is a 'timezone' variable, set TZ accordingly */
tz = get_confstr("timezone", CF_DEFAULT, (char *) 0); /* Exists ? */
if (tz != (char *) 0) {
if (-1 == set_env("TZ", tz))
add_log(1, "ERROR cannot set TZ variable");
}
}
public char *get_confstr(name, type, dflt)
char *name; /* Option name */
int type; /* Type: mandatory or may be defaulted */
char *dflt; /* Default value to be used if option not defined */
{
/* Return string value for option and use default if not defined, or
* raise a fatal error when option is mandatory.
*/
char buffer[MAX_STRING];
char *namestr; /* String in H table */
char *val; /* Returned value */
namestr = ht_value(&symtab, name);
if (namestr == (char *) 0) {
switch(type) {
case CF_MANDATORY: /* Variable should have been defined */
sprintf(buffer, "variable '%s' not defined in config file", name);
fatal(buffer);
/* NOTREACHED */
case CF_DEFAULT: /* May use default if variable not defined */
val = dflt;
break;
default:
fatal("BUG: get_confval");
}
} else
val = namestr;
return val;
}
public int get_confval(name, type, dflt)
char *name; /* Option name */
int type; /* Type: mandatory or may be defaulted */
int dflt; /* Default value to be used if option not defined */
{
/* Return int value for option and use default if not defined, or yield a
* fatal error when option is mandatory.
*/
char *namestr; /* String in H table */
int val; /* Returned value */
namestr = get_confstr(name, type, (char *) 0);
if (namestr == (char *) 0) /* get_confstr() panics if CF_MANDATORY */
return dflt;
sscanf(namestr, "%d", &val);
return val;
}
private void substitute(value)
char *value;
{
/* Run parameter and ~ substitution in-place */
char buffer[MAX_STRING]; /* Copy on which we work */
char *ptr = buffer; /* To iterate over the buffer */
strcpy(buffer, value); /* Make a copy of original line */
while (*value++ = *ptr) /* Line is updated in-place */
switch(*ptr++) {
case '~': /* Replace by home directory */
add_home(&value);
break;
case '$': /* Variable substitution */
add_variable(&value, &ptr);
break;
}
}
private void add_home(to)
char **to; /* Pointer to address in substituted text */
{
/* Add home directory at the current location. If the 'home' symbol has
* been found, use that instead.
*/
char *value = *to - 1; /* Go back to overwrite the '~' */
char *ptr = home; /* Where home directory string is stored */
char *symbol; /* Symbol entry for 'home' */
if (strlen(home) == 0) /* As a special case, this is empty when */
ptr = "/"; /* referring to the root directory */
symbol = ht_value(&symtab, "home"); /* Maybe we saw 'home' already */
if (symbol != (char *) 0) /* Yes, we did */
ptr = symbol; /* Use it for ~ substitution */
while (*value++ = *ptr++) /* Copy string */
;
*to = value - 1; /* Update position in substituted string */
}
private void add_variable(to, from)
char **to; /* Pointer to address in substituted text */
char **from; /* Pointer to address in original text */
{
/* Add value of variable at the current location */
char *value = *to - 1; /* Go back to overwrite the '$' */
char *ptr = *from; /* Start of variable's name */
char buffer[MAX_STRING]; /* To hold the name of the variable */
char *name = buffer; /* To advance in buffer */
char *dol_value; /* $value of variable */
/* Get variable's name */
while (*name++ = *ptr) {
if (isalnum(*ptr) || *ptr == '_')
ptr++;
else
break;
}
*(name - 1) = '\0'; /* Ensure null terminated string */
*from = ptr; /* Update pointer in original text */
/* Fetch value of variable recorded so far */
dol_value = ht_value(&symtab, buffer);
if (dol_value == (char *) 0)
return;
/* Do the variable substitution */
while (*value++ = *dol_value++)
;
*to = value - 1; /* Update pointer to substituted text */
}
private void insert_value(path, line)
char *path; /* The whole line */
int line; /* The line number, for error reports */
{
/* Analyze the line after parameter substitution and record the value of
* the variable in the hash table. The line has the following format:
* name : value # trailing comment
* If only spaces are encoutered or if the first non blank value is a '#',
* then the line is ignored. Otherwise, any error in parsing is reported.
*/
char name[MAX_STRING]; /* The name of the variable */
char *nptr = name; /* To fill in the name buffer */
while (isspace(*path)) /* Skip leading spaces */
path++;
if (*path == '#') /* A comment */
return; /* Ignore the whole line */
if (*path == '\0') /* A line full of spaces */
return; /* Ignore it */
while (*nptr++ = *path) { /* Copy everything until non alphanum */
if (*path == '_') {
/* Valid variable character, although not 'isalnum' */
path++;
continue;
} else if (!isalnum(*path++)) /* Reached a non-alphanumeric char */
break; /* We got variable name */
}
*(nptr - 1) = '\0'; /* Overwrite the ':' with '\0' */
path--; /* Go back on non-alphanum char */
while (*path) /* Now go and find the ':' */
if (*path++ == ':') /* Found it */
break;
/* We reached the end of the string without seeing a ':' */
if (*path == '\0') {
say("syntax error in config file, line %d", line);
return;
}
while (isspace(*path)) /* Skip leading spaces in value */
path++;
path[strlen(path) - 1] = '\0'; /* Chop final newline */
strip_comment(path); /* Remove trailing comment */
(void) ht_put(&symtab, name, path); /* Add value into symbol table */
}
private void strip_comment(line)
char *line;
{
/* Remove anything after first '#' on line (trailing comment) and also
* strip any trailing spaces (including those right before the '#'
* character).
*/
char *first = (char *) 0; /* First space in sequence */
char c; /* Character at current position */
while (c = *line++) {
if (isspace(c) && first != (char *) 0)
continue;
if (c == '#') { /* This has to be a comment */
if (first != (char *) 0) /* Position of first preceding space */
*first = '\0'; /* String ends at first white space */
*(line - 1) = '\0'; /* Also truncate at '#' position */
return; /* Done */
}
if (isspace(c))
first = line - 1; /* Record first space position */
else
first = (char *) 0; /* Signal: no active first space */
}
/* We have not found any '#' sign, so there is no comment in this line.
* However, there might be trailing white spaces... Trim them.
*/
if (first != (char *) 0)
*first = '\0'; /* Get rid of trailing white spaces */
}
private char *machine_name()
{
/* Compute the local machine name, using only lower-cased names and
* stipping down any domain name. The result points on a freshly allocated
* string. A null pointer is returned in case of error.
*/
#ifdef HAS_GETHOSTNAME
char name[MAX_STRING + 1]; /* The host name */
#else
#ifdef HAS_UNAME
struct utsname un; /* The internal uname structure */
#else
#ifdef PHOSTNAME
char *command = PHOSTNAME; /* Shell command to get hostname */
FILE *fd; /* File descriptor on popen() */
char name[MAX_STRING + 1]; /* The host name read from command */
char buffer[MAX_STRING + 1]; /* Input buffer */
#endif
#endif
#endif
#ifdef HAS_GETHOSTNAME
if (-1 != gethostname(name, MAX_STRING))
return strip_down(name);
add_log(1, "SYSERR gethostname: %m (%e)");
return (char *) 0;
#else
#ifdef HAS_UNAME
if (-1 != uname(&un))
return strip_down(un.nodename);
add_log(1, "SYSERR uname: %m (%e)");
return (char *) 0;
#else
#ifdef PHOSTNAME
fd = popen(PHOSTNAME, "r");
if (fd != (FILE *) 0) {
fgets(buffer, MAX_STRING, fd);
fclose(fd);
sscanf(buffer, "%s", name);
return strip_down(name);
}
add_log(1, "SYSERR cannot run %s: %m (%e)", PHOSTNAME);
#endif
return strip_down(MYHOSTNAME);
#endif
#endif
}
private char *strip_down(host)
char *host;
{
/* Return a freshly allocated string containing the host name. The string
* is lower-cased and the domain part is removed from the name.
* If any '-' is found in the hostname, it is translated into a '_', since
* it would not otherwise be a valid variable name for perl.
*/
char name[MAX_STRING + 1]; /* Constructed name */
char *ptr = name;
char c;
if (host == (char *) 0)
return (char *) 0;
while (c = *host) { /* Lower-case name */
if (isupper(c))
*ptr = tolower(c);
else {
if (c == '-') /* Although '-' is a valid hostname char */
c = '_'; /* It's not a valid perl variable char */
*ptr = c;
}
if (c != '.') { /* Found a domain delimiter? */
host++; /* No, continue */
ptr++;
} else
break; /* Yes, we end processing there */
}
*ptr = '\0'; /* Ensure null-terminated string */
add_log(19, "hostname is %s", name);
return strsave(name); /* Save string in memory */
}