Quelle postconf_edit.c
Sprache: unbekannt
/*++
/* NAME
/* postconf_edit 3
/* SUMMARY
/* edit main.cf or master.cf
/* SYNOPSIS
/* #include <postconf.h>
/*
/* void pcf_edit_main(mode, argc, argv)
/* int mode;
/* int argc;
/* char **argv;
/*
/* void pcf_edit_master(mode, argc, argv)
/* int mode;
/* int argc;
/* char **argv;
/* DESCRIPTION
/* pcf_edit_main() edits the \fBmain.cf\fR configuration file.
/* It replaces or adds parameter settings given as "\fIname=value\fR"
/* pairs given on the command line, or removes parameter
/* settings given as "\fIname\fR" on the command line. The
/* file is copied to a temporary file then renamed into place.
/*
/* pcf_edit_master() edits the \fBmaster.cf\fR configuration
/* file. The file is copied to a temporary file then renamed
/* into place. Depending on the flags in \fBmode\fR:
/* .IP PCF_MASTER_ENTRY
/* With PCF_EDIT_CONF, pcf_edit_master() replaces or adds
/* entire master.cf entries, specified on the command line as
/* "\fIname/type = name type private unprivileged chroot wakeup
/* process_limit command...\fR".
/*
/* With PCF_EDIT_EXCL or PCF_COMMENT_OUT, pcf_edit_master()
/* removes or comments out entries specified on the command
/* line as "\fIname/type\fR.
/* .IP PCF_MASTER_FLD
/* With PCF_EDIT_CONF, pcf_edit_master() replaces the value
/* of specific service attributes, specified on the command
/* line as "\fIname/type/attribute = value\fR".
/* .IP PCF_MASTER_PARAM
/* With PCF_EDIT_CONF, pcf_edit_master() replaces or adds the
/* value of service parameters, specified on the command line
/* as "\fIname/type/parameter = value\fR".
/*
/* With PCF_EDIT_EXCL, pcf_edit_master() removes service
/* parameters specified on the command line as "\fIparametername\fR".
/* DIAGNOSTICS
/* Problems are reported to the standard error stream.
/* FILES
/* /etc/postfix/main.cf, Postfix configuration parameters
/* /etc/postfix/main.cf.tmp, temporary name
/* /etc/postfix/master.cf, Postfix configuration parameters
/* /etc/postfix/master.cf.tmp, temporary name
/* LICENSE
/* .ad
/* .fi
/* The Secure Mailer license must be distributed with this software.
/* AUTHOR(S)
/* Wietse Venema
/* IBM T.J. Watson Research
/* P.O. Box 704
/* Yorktown Heights, NY 10598, USA
/*--*/
/* System library. */
#include <sys_defs.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
/* Utility library. */
#include <msg.h>
#include <mymalloc.h>
#include <htable.h>
#include <vstring.h>
#include <vstring_vstream.h>
#include <edit_file.h>
#include <readlline.h>
#include <stringops.h>
#include <split_at.h>
/* Global library. */
#include <mail_params.h>
/* Application-specific. */
#include <postconf.h>
#define STR(x) vstring_str(x)
/* pcf_find_cf_info - pass-through non-content line, return content or null */
static char *pcf_find_cf_info(VSTRING *buf, VSTREAM *dst)
{
char *cp;
for (cp = STR(buf); ISSPACE(*cp) /* including newline */ ; cp++)
/* void */ ;
/* Pass-through comment, all-whitespace, or empty line. */
if (*cp == '#' || *cp == 0 ) {
vstream_fputs(STR(buf), dst);
return (0 );
} else {
return (cp);
}
}
/* pcf_next_cf_line - return next content line, pass non-content */
static char *pcf_next_cf_line(VSTRING *buf, VSTREAM *src, VSTREAM *dst, int *lineno)
{
char *cp;
int last_char;
while ((last_char = vstring_get(buf, src)) != VSTREAM_EOF) {
if (last_char != '\n' ) {
VSTRING_ADDCH(buf, '\n' );
VSTRING_TERMINATE(buf);
}
if (lineno)
*lineno += 1 ;
if ((cp = pcf_find_cf_info(buf, dst)) != 0 )
return (cp);
}
return (0 );
}
/* pcf_gobble_cf_line - accumulate multi-line content, pass non-content */
static void pcf_gobble_cf_line(VSTRING *full_entry_buf, VSTRING *line_buf,
VSTREAM *src, VSTREAM *dst, int *lineno)
{
int ch;
vstring_strcpy(full_entry_buf, STR(line_buf));
for (;;) {
if ((ch = VSTREAM_GETC(src)) != VSTREAM_EOF)
vstream_ungetc(src, ch);
if ((ch != '#' && !ISSPACE(ch))
|| vstring_get(line_buf, src) == VSTREAM_EOF)
break ;
lineno += 1 ;
if (pcf_find_cf_info(line_buf, dst))
vstring_strcat(full_entry_buf, STR(line_buf));
}
}
/* pcf_cmp_ht_key - qsort helper for ht_info pointer array */
static int pcf_cmp_ht_key(const void *a, const void *b)
{
HTABLE_INFO **ap = (HTABLE_INFO **) a;
HTABLE_INFO **bp = (HTABLE_INFO **) b;
return (strcmp(ap[0 ]->key, bp[0 ]->key));
}
/* pcf_edit_main - edit main.cf file */
void pcf_edit_main(int mode, int argc, char **argv)
{
const char *path;
EDIT_FILE *ep;
VSTREAM *src;
VSTREAM *dst;
VSTRING *buf = vstring_alloc(100 );
VSTRING *key = vstring_alloc(10 );
char *cp;
char *pattern;
char *edit_value;
HTABLE *table;
struct cvalue {
char *value;
int found;
};
struct cvalue *cvalue;
HTABLE_INFO **ht_info;
HTABLE_INFO **ht;
int interesting;
const char *err;
/*
* Store command - line parameters for quick lookup .
*/
table = htable_create(argc);
while ((cp = *argv++) != 0 ) {
if (strchr(cp, '\n' ) != 0 )
msg_fatal("-e, -X, or -# accepts no multi-line input" );
while (ISSPACE(*cp))
cp++;
if (*cp == '#' )
msg_fatal("-e, -X, or -# accepts no comment input" );
if (mode & PCF_EDIT_CONF) {
if ((err = split_nameval(cp, &pattern, &edit_value)) != 0 )
msg_fatal("%s: \" %s\"" , err, cp);
} else if (mode & (PCF_COMMENT_OUT | PCF_EDIT_EXCL)) {
if (*cp == 0 )
msg_fatal("-X or -# requires non-blank parameter names" );
if (strchr(cp, '=' ) != 0 )
msg_fatal("-X or -# requires parameter names without value" );
pattern = cp;
trimblanks(pattern, 0 );
edit_value = 0 ;
} else {
msg_panic("pcf_edit_main: unknown mode %d" , mode);
}
if ((cvalue = htable_find(table, pattern)) != 0 ) {
if (edit_value && cvalue->value
&& strcmp(edit_value, cvalue->value) != 0 )
msg_warn("ignoring earlier request: '%s = %s'" ,
pattern, cvalue->value);
htable_delete(table, pattern, myfree);
}
cvalue = (struct cvalue *) mymalloc(sizeof (*cvalue));
cvalue->value = edit_value;
cvalue->found = 0 ;
htable_enter(table, pattern, (void *) cvalue);
}
/*
* Open a temp file for the result . This uses a deterministic name so we
* don ' t leave behind thrash with random names .
*/
path = pcf_get_main_path();
if ((ep = edit_file_open(path, O_CREAT | O_WRONLY, 0644 )) == 0 )
msg_fatal("open %s%s: %m" , path, EDIT_FILE_SUFFIX);
dst = ep->tmp_fp;
/*
* Open the original file for input .
*/
if ((src = vstream_fopen(path, O_RDONLY, 0 )) == 0 ) {
/* OK to delete, since we control the temp file name exclusively. */
(void ) unlink(ep->tmp_path);
msg_fatal("open %s for reading: %m" , path);
}
/*
* Copy original file to temp file , while replacing parameters on the
* fly . Issue warnings for names found multiple times .
*/
#define STR(x) vstring_str(x)
interesting = 0 ;
while ((cp = pcf_next_cf_line(buf, src, dst, (int *) 0 )) != 0 ) {
/* Copy, skip or replace continued text. */
if (cp > STR(buf)) {
if (interesting == 0 )
vstream_fputs(STR(buf), dst);
else if (mode & PCF_COMMENT_OUT)
vstream_fprintf(dst, "#%s" , STR(buf));
}
/* Copy or replace start of logical line. */
else {
vstring_strncpy(key, cp, strcspn(cp, CHARS_SPACE "=" ));
cvalue = (struct cvalue *) htable_find(table, STR(key));
if ((interesting = !!cvalue) != 0 ) {
if (cvalue->found++ == 1 )
msg_warn("%s: multiple entries for \" %s\"" , path, STR(key));
if (mode & PCF_EDIT_CONF)
vstream_fprintf(dst, "%s = %s\n" , STR(key), cvalue->value);
else if (mode & PCF_COMMENT_OUT)
vstream_fprintf(dst, "#%s" , cp);
} else {
vstream_fputs(STR(buf), dst);
}
}
}
/*
* Generate new entries for parameters that were not found .
*/
if (mode & PCF_EDIT_CONF) {
ht_info = htable_list(table);
qsort((void *) ht_info, table->used, sizeof (*ht_info), pcf_cmp_ht_key);
for (ht = ht_info; *ht; ht++) {
cvalue = (struct cvalue *) ht[0 ]->value;
if (cvalue->found == 0 )
vstream_fprintf(dst, "%s = %s\n" , ht[0 ]->key, cvalue->value);
}
myfree((void *) ht_info);
}
/*
* When all is well , rename the temp file to the original one .
*/
if (vstream_fclose(src))
msg_fatal("read %s: %m" , path);
if (edit_file_close(ep) != 0 )
msg_fatal("close %s%s: %m" , path, EDIT_FILE_SUFFIX);
/*
* Cleanup .
*/
vstring_free(buf);
vstring_free(key);
htable_free(table, myfree);
}
/*
* Data structure to hold a master . cf edit request .
*/
typedef struct {
int match_count; /* hit count */
const char *raw_text; /* unparsed command-line argument */
char *parsed_text; /* destructive parse */
ARGV *service_pattern; /* service name, type, ... */
int field_number; /* attribute field number */
const char *param_pattern; /* parameter name */
char *edit_value; /* value substring */
} PCF_MASTER_EDIT_REQ;
/* pcf_edit_master - edit master.cf file */
void pcf_edit_master(int mode, int argc, char **argv)
{
const char *myname = "pcf_edit_master" ;
const char *path;
EDIT_FILE *ep;
VSTREAM *src;
VSTREAM *dst;
VSTRING *line_buf = vstring_alloc(100 );
VSTRING *parse_buf = vstring_alloc(100 );
int lineno;
PCF_MASTER_ENT *new_entry;
VSTRING *full_entry_buf = vstring_alloc(100 );
char *cp;
char *pattern;
int service_name_type_matched;
const char *err;
PCF_MASTER_EDIT_REQ *edit_reqs;
PCF_MASTER_EDIT_REQ *req;
int num_reqs = argc;
const char *edit_opts = "-Me, -Fe, -Pe, -X, or -#" ;
char *service_name;
char *service_type;
/*
* Sanity check .
*/
if (num_reqs <= 0 )
msg_panic("%s: empty argument list" , myname);
/*
* Preprocessing : split pattern = value , then split the pattern components .
*/
edit_reqs = (PCF_MASTER_EDIT_REQ *) mymalloc(sizeof (*edit_reqs) * num_reqs);
for (req = edit_reqs; *argv != 0 ; req++, argv++) {
req->match_count = 0 ;
req->raw_text = *argv;
cp = req->parsed_text = mystrdup(req->raw_text);
if (strchr(cp, '\n' ) != 0 )
msg_fatal("%s accept no multi-line input" , edit_opts);
while (ISSPACE(*cp))
cp++;
if (*cp == '#' )
msg_fatal("%s accept no comment input" , edit_opts);
/* Separate the pattern from the value. */
if (mode & PCF_EDIT_CONF) {
if ((err = split_nameval(cp, &pattern, &req->edit_value)) != 0 )
msg_fatal("%s: \" %s\"" , err, req->raw_text);
#if 0
if ((mode & PCF_MASTER_PARAM)
&& req->edit_value[strcspn(req->edit_value, PCF_MASTER_BLANKS)])
msg_fatal("whitespace in parameter value: \" %s\"" ,
req->raw_text);
#endif
} else if (mode & (PCF_COMMENT_OUT | PCF_EDIT_EXCL)) {
if (strchr(cp, '=' ) != 0 )
msg_fatal("-X or -# requires names without value" );
pattern = cp;
trimblanks(pattern, 0 );
req->edit_value = 0 ;
} else {
msg_panic("%s: unknown mode %d" , myname, mode);
}
#define PCF_MASTER_MASK (PCF_MASTER_ENTRY | PCF_MASTER_FLD | PCF_MASTER_PARAM)
/*
* Split name / type or name / type / whatever pattern into components .
*/
switch (mode & PCF_MASTER_MASK) {
case PCF_MASTER_ENTRY:
if ((req->service_pattern =
pcf_parse_service_pattern(pattern, 2 , 2 )) == 0 )
msg_fatal("-Me, -MX or -M# requires service_name/type" );
break ;
case PCF_MASTER_FLD:
if ((req->service_pattern =
pcf_parse_service_pattern(pattern, 3 , 3 )) == 0 )
msg_fatal("-Fe or -FX requires service_name/type/field_name" );
req->field_number =
pcf_parse_field_pattern(req->service_pattern->argv[2 ]);
if (pcf_is_magic_field_pattern(req->field_number))
msg_fatal("-Fe does not accept wild-card field name" );
if ((mode & PCF_EDIT_CONF)
&& req->field_number < PCF_MASTER_FLD_CMD
&& req->edit_value[strcspn(req->edit_value, PCF_MASTER_BLANKS)])
msg_fatal("-Fe does not accept whitespace in non-command field" );
break ;
case PCF_MASTER_PARAM:
if ((req->service_pattern =
pcf_parse_service_pattern(pattern, 3 , 3 )) == 0 )
msg_fatal("-Pe or -PX requires service_name/type/parameter" );
req->param_pattern = req->service_pattern->argv[2 ];
if (PCF_IS_MAGIC_PARAM_PATTERN(req->param_pattern))
msg_fatal("-Pe does not accept wild-card parameter name" );
if ((mode & PCF_EDIT_CONF)
&& req->edit_value[strcspn(req->edit_value, PCF_MASTER_BLANKS)])
msg_fatal("-Pe does not accept whitespace in parameter value" );
break ;
default :
msg_panic("%s: unknown edit mode %d" , myname, mode);
}
}
/*
* Open a temp file for the result . This uses a deterministic name so we
* don ' t leave behind thrash with random names .
*/
path = pcf_get_master_path();
if ((ep = edit_file_open(path, O_CREAT | O_WRONLY, 0644 )) == 0 )
msg_fatal("open %s%s: %m" , path, EDIT_FILE_SUFFIX);
dst = ep->tmp_fp;
/*
* Open the original file for input .
*/
if ((src = vstream_fopen(path, O_RDONLY, 0 )) == 0 ) {
/* OK to delete, since we control the temp file name exclusively. */
(void ) unlink(ep->tmp_path);
msg_fatal("open %s for reading: %m" , path);
}
/*
* Copy original file to temp file , while replacing service entries on
* the fly .
*/
service_name_type_matched = 0 ;
new_entry = 0 ;
lineno = 0 ;
while ((cp = pcf_next_cf_line(parse_buf, src, dst, &lineno)) != 0 ) {
vstring_strcpy(line_buf, STR(parse_buf));
/*
* Copy , skip or replace continued text .
*/
if (cp > STR(parse_buf)) {
if (service_name_type_matched == 0 )
vstream_fputs(STR(line_buf), dst);
else if (mode & PCF_COMMENT_OUT)
vstream_fprintf(dst, "#%s" , STR(line_buf));
}
/*
* Copy or replace ( start of ) logical line .
*/
else {
service_name_type_matched = 0 ;
/*
* Parse out the service name and type .
*/
if ((service_name = mystrtok(&cp, PCF_MASTER_BLANKS)) == 0
|| (service_type = mystrtok(&cp, PCF_MASTER_BLANKS)) == 0 )
msg_fatal("file %s: line %d: specify service name and type "
"on the same line" , path, lineno);
if (strchr(service_name, '=' ))
msg_fatal("file %s: line %d: service name syntax \" %s\" is "
"unsupported with %s" , path, lineno, service_name,
edit_opts);
if (service_type[strcspn(service_type, "=/" )] != 0 )
msg_fatal("file %s: line %d: "
"service type syntax \" %s\" is unsupported with %s" ,
path, lineno, service_type, edit_opts);
/*
* Match each service pattern .
*
* Additional care is needed when a request adds or replaces an
* entire service definition , instead of a specific field or
* parameter . Given a command " postconf - M name1 / type1 = ' name2
* type2 . . . ' " , where name1 and name2 may differ , and likewise
* for type1 and type2 :
*
* - First , if an existing service definition a ) matches the service
* pattern ' name1 / type1 ' , or b ) matches the name and type in the
* new service definition ' name2 type2 . . . ' , remove the service
* definition .
*
* - Then , after an a ) or b ) type match , add a new service
* definition for ' name2 type2 . . . ' , but only after the first
* match .
*
* - Finally , if a request had no a ) or b ) type match for any
* master . cf service definition , add a new service definition for
* ' name2 type2 . . . ' .
*/
for (req = edit_reqs; req < edit_reqs + num_reqs; req++) {
PCF_MASTER_ENT *tentative_entry = 0 ;
int use_tentative_entry = 0 ;
/* Additional care for whole service definition requests. */
if ((mode & PCF_MASTER_ENTRY) && (mode & PCF_EDIT_CONF)) {
tentative_entry = (PCF_MASTER_ENT *)
mymalloc(sizeof (*tentative_entry));
if ((err = pcf_parse_master_entry(tentative_entry,
req->edit_value)) != 0 )
msg_fatal("%s: \" %s\"" , err, req->raw_text);
}
if (PCF_MATCH_SERVICE_PATTERN(req->service_pattern,
service_name,
service_type)) {
service_name_type_matched = 1 ; /* Sticky flag */
req->match_count += 1 ;
/*
* Generate replacement master . cf entries .
*/
if ((mode & PCF_EDIT_CONF)
|| ((mode & PCF_MASTER_PARAM) && (mode & PCF_EDIT_EXCL))) {
switch (mode & PCF_MASTER_MASK) {
/*
* Replace master . cf entry field or parameter
* value .
*/
case PCF_MASTER_FLD:
case PCF_MASTER_PARAM:
if (new_entry == 0 ) {
/* Gobble up any continuation lines. */
pcf_gobble_cf_line(full_entry_buf, line_buf,
src, dst, &lineno);
new_entry = (PCF_MASTER_ENT *)
mymalloc(sizeof (*new_entry));
if ((err = pcf_parse_master_entry(new_entry,
STR(full_entry_buf))) != 0 )
msg_fatal("file %s: line %d: %s" ,
path, lineno, err);
}
if (mode & PCF_MASTER_FLD) {
pcf_edit_master_field(new_entry,
req->field_number,
req->edit_value);
} else {
pcf_edit_master_param(new_entry, mode,
req->param_pattern,
req->edit_value);
}
break ;
/*
* Replace entire master . cf entry .
*/
case PCF_MASTER_ENTRY:
if (req->match_count == 1 )
use_tentative_entry = 1 ;
break ;
default :
msg_panic("%s: unknown edit mode %d" , myname, mode);
}
}
} else if (tentative_entry != 0
&& PCF_MATCH_SERVICE_PATTERN(tentative_entry->argv,
service_name,
service_type)) {
service_name_type_matched = 1 ; /* Sticky flag */
req->match_count += 1 ;
if (req->match_count == 1 )
use_tentative_entry = 1 ;
}
if (tentative_entry != 0 ) {
if (use_tentative_entry) {
if (new_entry != 0 )
pcf_free_master_entry(new_entry);
new_entry = tentative_entry;
} else {
pcf_free_master_entry(tentative_entry);
}
}
}
/*
* Pass through or replace the current input line .
*/
if (new_entry) {
pcf_print_master_entry(dst, PCF_FOLD_LINE, new_entry);
pcf_free_master_entry(new_entry);
new_entry = 0 ;
} else if (service_name_type_matched == 0 ) {
vstream_fputs(STR(line_buf), dst);
} else if (mode & PCF_COMMENT_OUT) {
vstream_fprintf(dst, "#%s" , STR(line_buf));
}
}
}
/*
* Postprocessing : when editing entire service entries , generate new
* entries for services not found . Otherwise ( editing fields or
* parameters ) , " service not found " is a fatal error .
*/
for (req = edit_reqs; req < edit_reqs + num_reqs; req++) {
if (req->match_count == 0 ) {
if ((mode & PCF_MASTER_ENTRY) && (mode & PCF_EDIT_CONF)) {
new_entry = (PCF_MASTER_ENT *) mymalloc(sizeof (*new_entry));
if ((err = pcf_parse_master_entry(new_entry, req->edit_value)) != 0 )
msg_fatal("%s: \" %s\"" , err, req->raw_text);
pcf_print_master_entry(dst, PCF_FOLD_LINE, new_entry);
pcf_free_master_entry(new_entry);
} else if ((mode & PCF_MASTER_ENTRY) == 0 ) {
msg_warn("unmatched service_name/type: \" %s\"" , req->raw_text);
}
}
}
/*
* When all is well , rename the temp file to the original one .
*/
if (vstream_fclose(src))
msg_fatal("read %s: %m" , path);
if (edit_file_close(ep) != 0 )
msg_fatal("close %s%s: %m" , path, EDIT_FILE_SUFFIX);
/*
* Cleanup .
*/
vstring_free(line_buf);
vstring_free(parse_buf);
vstring_free(full_entry_buf);
for (req = edit_reqs; req < edit_reqs + num_reqs; req++) {
argv_free(req->service_pattern);
myfree(req->parsed_text);
}
myfree((void *) edit_reqs);
}
Messung V0.5 in Prozent C=74 H=94 G=84
[Dauer der Verarbeitung: 0.35 Sekunden, vorverarbeitet 2026-08-08]
2026-08-25