1 /* $NetBSD: postmulti.c,v 1.3 2020/03/18 19:05:18 christos Exp $ */ 2 3 /*++ 4 /* NAME 5 /* postmulti 1 6 /* SUMMARY 7 /* Postfix multi-instance manager 8 /* SYNOPSIS 9 /* .fi 10 /* .ti -4 11 /* \fBEnabling multi-instance management:\fR 12 /* 13 /* \fBpostmulti\fR \fB-e init\fR [\fB-v\fR] 14 /* 15 /* .ti -4 16 /* \fBIterator mode:\fR 17 /* 18 /* \fBpostmulti\fR \fB-l\fR [\fB-aRv\fR] [\fB-g \fIgroup\fR] 19 /* [\fB-i \fIname\fR] 20 /* 21 /* \fBpostmulti\fR \fB-p\fR [\fB-av\fR] [\fB-g \fIgroup\fR] 22 /* [\fB-i \fIname\fR] \fIpostfix-command...\fR 23 /* 24 /* \fBpostmulti\fR \fB-x\fR [\fB-aRv\fR] [\fB-g \fIgroup\fR] 25 /* [\fB-i \fIname\fR] \fIunix-command...\fR 26 /* 27 /* .ti -4 28 /* \fBLife-cycle management:\fR 29 /* 30 /* \fBpostmulti\fR \fB-e create\fR [\fB-av\fR] 31 /* [\fB-g \fIgroup\fR] [\fB-i \fIname\fR] [\fB-G \fIgroup\fR] 32 /* [\fB-I \fIname\fR] [\fIparam=value\fR ...] 33 /* 34 /* \fBpostmulti\fR \fB-e import\fR [\fB-av\fR] 35 /* [\fB-g \fIgroup\fR] [\fB-i \fIname\fR] [\fB-G \fIgroup\fR] 36 /* [\fB-I \fIname\fR] [\fBconfig_directory=\fI/path\fR] 37 /* 38 /* \fBpostmulti\fR \fB-e destroy\fR [\fB-v\fR] \fB-i \fIname\fR 39 /* 40 /* \fBpostmulti\fR \fB-e deport\fR [\fB-v\fR] \fB-i \fIname\fR 41 /* 42 /* \fBpostmulti\fR \fB-e enable\fR [\fB-v\fR] \fB-i \fIname\fR 43 /* 44 /* \fBpostmulti\fR \fB-e disable\fR [\fB-v\fR] \fB-i \fIname\fR 45 /* 46 /* \fBpostmulti\fR \fB-e assign\fR [\fB-v\fR] \fB-i \fIname\fR 47 /* [\fB-I \fIname\fR] [-G \fIgroup\fR] 48 /* DESCRIPTION 49 /* The \fBpostmulti\fR(1) command allows a Postfix administrator 50 /* to manage multiple Postfix instances on a single host. 51 /* 52 /* \fBpostmulti\fR(1) implements two fundamental modes of 53 /* operation. In \fBiterator\fR mode, it executes the same 54 /* command for multiple Postfix instances. In \fBlife-cycle 55 /* management\fR mode, it adds or deletes one instance, or 56 /* changes the multi-instance status of one instance. 57 /* 58 /* Each mode of operation has its own command syntax. For this 59 /* reason, each mode is documented in separate sections below. 60 /* BACKGROUND 61 /* .ad 62 /* .fi 63 /* A multi-instance configuration consists of one primary 64 /* Postfix instance, and one or more secondary instances whose 65 /* configuration directory pathnames are recorded in the primary 66 /* instance's main.cf file. Postfix instances share program 67 /* files and documentation, but have their own configuration, 68 /* queue and data directories. 69 /* 70 /* Currently, only the default Postfix instance can be used 71 /* as primary instance in a multi-instance configuration. The 72 /* \fBpostmulti\fR(1) command does not currently support a \fB-c\fR 73 /* option to select an alternative primary instance, and exits 74 /* with a fatal error if the \fBMAIL_CONFIG\fR environment 75 /* variable is set to a non-default configuration directory. 76 /* 77 /* See the MULTI_INSTANCE_README tutorial for a more detailed 78 /* discussion of multi-instance management with \fBpostmulti\fR(1). 79 /* ITERATOR MODE 80 /* .ad 81 /* .fi 82 /* In iterator mode, \fBpostmulti\fR performs the same operation 83 /* on all Postfix instances in turn. 84 /* 85 /* If multi-instance support is not enabled, the requested 86 /* command is performed just for the primary instance. 87 /* .PP 88 /* Iterator mode implements the following command options: 89 /* .SH "Instance selection" 90 /* .IP \fB-a\fR 91 /* Perform the operation on all instances. This is the default. 92 /* .IP "\fB-g \fIgroup\fR" 93 /* Perform the operation only for members of the named \fIgroup\fR. 94 /* .IP "\fB-i \fIname\fR" 95 /* Perform the operation only for the instance with the specified 96 /* \fIname\fR. You can specify either the instance name 97 /* or the absolute pathname of the instance's configuration 98 /* directory. Specify "-" to select the primary Postfix instance. 99 /* .IP \fB-R\fR 100 /* Reverse the iteration order. This may be appropriate when 101 /* updating a multi-instance system, where "sink" instances 102 /* are started before "source" instances. 103 /* .sp 104 /* This option cannot be used with \fB-p\fR. 105 /* .SH "List mode" 106 /* .IP \fB-l\fR 107 /* List Postfix instances with their instance name, instance 108 /* group name, enable/disable status and configuration directory. 109 /* .SH "Postfix-wrapper mode" 110 /* .IP "\fB-p \fIpostfix-command\fR" 111 /* Invoke \fBpostfix(1)\fR to execute \fIpostfix-command\fR. 112 /* This option implements the \fBpostfix-wrapper\fR(5) interface. 113 /* .RS 114 /* .IP \(bu 115 /* With "start"-like commands, "postfix check" is executed for 116 /* instances that are not enabled. The full list of commands 117 /* is specified with the postmulti_start_commands parameter. 118 /* .IP \(bu 119 /* With "stop"-like commands, the iteration order is reversed, 120 /* and disabled instances are skipped. The full list of commands 121 /* is specified with the postmulti_stop_commands parameter. 122 /* .IP \(bu 123 /* With "reload" and other commands that require a started 124 /* instance, disabled instances are skipped. The full list of 125 /* commands is specified with the postmulti_control_commands 126 /* parameter. 127 /* .IP \(bu 128 /* With "status" and other commands that don't require a started 129 /* instance, the command is executed for all instances. 130 /* .RE 131 /* .IP 132 /* The \fB-p\fR option can also be used interactively to 133 /* start/stop/etc. a named instance or instance group. For 134 /* example, to start just the instances in the group "msa", 135 /* invoke \fBpostmulti\fR(1) as follows: 136 /* .RS 137 /* .IP 138 /* # postmulti -g msa -p start 139 /* .RE 140 /* .SH "Command mode" 141 /* .IP "\fB-x \fIunix-command\fR" 142 /* Execute the specified \fIunix-command\fR for all Postfix instances. 143 /* The command runs with appropriate environment settings for 144 /* MAIL_CONFIG, command_directory, daemon_directory, 145 /* config_directory, queue_directory, data_directory, 146 /* multi_instance_name, multi_instance_group and 147 /* multi_instance_enable. 148 /* .SH "Other options" 149 /* .IP \fB-v\fR 150 /* Enable verbose logging for debugging purposes. Multiple 151 /* \fB-v\fR options make the software increasingly verbose. 152 /* LIFE-CYCLE MANAGEMENT MODE 153 /* .ad 154 /* .fi 155 /* With the \fB-e\fR option \fBpostmulti\fR(1) can be used to 156 /* add or delete a Postfix instance, and to manage the 157 /* multi-instance status of an existing instance. 158 /* .PP 159 /* The following options are implemented: 160 /* .SH "Existing instance selection" 161 /* .IP \fB-a\fR 162 /* When creating or importing an instance, place the new 163 /* instance at the front of the secondary instance list. 164 /* .IP "\fB-g \fIgroup\fR" 165 /* When creating or importing an instance, place the new 166 /* instance before the first secondary instance that is a 167 /* member of the specified group. 168 /* .IP "\fB-i \fIname\fR" 169 /* When creating or importing an instance, place the new 170 /* instance before the matching secondary instance. 171 /* .sp 172 /* With other life-cycle operations, apply the operation to 173 /* the named existing instance. Specify "-" to select the 174 /* primary Postfix instance. 175 /* .SH "New or existing instance name assignment" 176 /* .IP "\fB-I \fIname\fR" 177 /* Assign the specified instance \fIname\fR to an existing 178 /* instance, newly-created instance, or imported instance. 179 /* Instance 180 /* names other than "-" (which makes the instance "nameless") 181 /* must start with "postfix-". This restriction reduces the 182 /* likelihood of name collisions with system files. 183 /* .IP "\fB-G \fIgroup\fR" 184 /* Assign the specified \fIgroup\fR name to an existing instance 185 /* or to a newly created or imported instance. 186 /* .SH "Instance creation/deletion/status change" 187 /* .IP "\fB-e \fIaction\fR" 188 /* "Edit" managed instances. The following actions are supported: 189 /* .RS 190 /* .IP \fBinit\fR 191 /* This command is required before \fBpostmulti\fR(1) can be 192 /* used to manage Postfix instances. The "postmulti -e init" 193 /* command updates the primary instance's main.cf file by 194 /* setting: 195 /* .RS 196 /* .IP 197 /* .nf 198 /* multi_instance_wrapper = 199 /* ${command_directory}/postmulti -p -- 200 /* multi_instance_enable = yes 201 /* .fi 202 /* .RE 203 /* .IP 204 /* You can set these by other means if you prefer. 205 /* .IP \fBcreate\fR 206 /* Create a new Postfix instance and add it to the 207 /* multi_instance_directories parameter of the primary instance. 208 /* The "\fB-I \fIname\fR" option is recommended to give the 209 /* instance a short name that is used to construct default 210 /* values for the private directories of the new instance. The 211 /* "\fB-G \fIgroup\fR" option may be specified to assign the 212 /* instance to a group, otherwise, the new instance is not a 213 /* member of any groups. 214 /* .sp 215 /* The new instance main.cf is the stock main.cf with the 216 /* parameters that specify the locations of shared files cloned 217 /* from the primary instance. For "nameless" instances, you 218 /* should manually adjust "syslog_name" to yield a unique 219 /* "logtag" starting with "postfix-" that will uniquely identify 220 /* the instance in the mail logs. It is simpler to assign the 221 /* instance a short name with the "\fB-I \fIname\fR" option. 222 /* .sp 223 /* Optional "name=value" arguments specify the instance 224 /* config_directory, queue_directory and data_directory. 225 /* For example: 226 /* .RS 227 /* .IP 228 /* .nf 229 /* # postmulti -I postfix-mumble \e 230 /* -G mygroup -e create \e 231 /* config_directory=/my/config/dir \e 232 /* queue_directory=/my/queue/dir \e 233 /* data_directory=/my/data/dir 234 /* .fi 235 /* .RE 236 /* .IP 237 /* If any of these pathnames is not supplied, the program 238 /* attempts to generate the pathname by taking the corresponding 239 /* primary instance pathname, and by replacing the last pathname 240 /* component by the value of the \fB-I\fR option. 241 /* .sp 242 /* If the instance configuration directory already exists, and 243 /* contains both a main.cf and master.cf file, \fBcreate\fR 244 /* will "import" the instance as-is. For existing instances, 245 /* \fBcreate\fR and \fBimport\fR are identical. 246 /* .IP \fBimport\fR 247 /* Import an existing instance into the list of instances 248 /* managed by the \fBpostmulti\fR(1) multi-instance manager. 249 /* This adds the instance to the multi_instance_directories 250 /* list of the primary instance. If the "\fB-I \fIname\fR" 251 /* option is provided it specifies the new name for the instance 252 /* and is used to define a default location for the instance 253 /* configuration directory (as with \fBcreate\fR above). The 254 /* "\fB-G \fIgroup\fR" option may be used to assign the instance 255 /* to a group. Add a "\fBconfig_directory=\fI/path\fR" argument 256 /* to override a default pathname based on "\fB-I \fIname\fR". 257 /* .IP \fBdestroy\fR 258 /* Destroy a secondary Postfix instance. To be a candidate for 259 /* destruction an instance must be disabled, stopped and its 260 /* queue must not contain any messages. Attempts to destroy 261 /* the primary Postfix instance trigger a fatal error, without 262 /* destroying the instance. 263 /* .sp 264 /* The instance is removed from the primary instance main.cf 265 /* file's alternate_config_directories parameter and its data, 266 /* queue and configuration directories are cleaned of files 267 /* and directories created by the Postfix system. The main.cf 268 /* and master.cf files are removed from the configuration 269 /* directory even if they have been modified since initial 270 /* creation. Finally, the instance is "deported" from the list 271 /* of managed instances. 272 /* .sp 273 /* If other files are present in instance private directories, 274 /* the directories may not be fully removed, a warning is 275 /* logged to alert the administrator. It is expected that an 276 /* instance built using "fresh" directories via the \fBcreate\fR 277 /* action will be fully removed by the \fBdestroy\fR action 278 /* (if first disabled). If the instance configuration and queue 279 /* directories are populated with additional files (access and 280 /* rewriting tables, chroot jail content, etc.) the instance 281 /* directories will not be fully removed. 282 /* .sp 283 /* The \fBdestroy\fR action triggers potentially dangerous 284 /* file removal operations. Make sure the instance's data, 285 /* queue and configuration directories are set correctly and 286 /* do not contain any valuable files. 287 /* .IP \fBdeport\fR 288 /* Deport a secondary instance from the list of managed 289 /* instances. This deletes the instance configuration directory 290 /* from the primary instance's multi_instance_directories list, 291 /* but does not remove any files or directories. 292 /* .IP \fBassign\fR 293 /* Assign a new instance name or a new group name to the 294 /* selected instance. Use "\fB-G -\fR" to specify "no group" 295 /* and "\fB-I -\fR" to specify "no name". If you choose to 296 /* make an instance "nameless", set a suitable syslog_name in 297 /* the corresponding main.cf file. 298 /* .IP \fBenable\fR 299 /* Mark the selected instance as enabled. This just sets the 300 /* multi_instance_enable parameter to "yes" in the instance's 301 /* main.cf file. 302 /* .IP \fBdisable\fR 303 /* Mark the selected instance as disabled. This means that 304 /* the instance will not be started etc. with "postfix start", 305 /* "postmulti -p start" and so on. The instance can still be 306 /* started etc. with "postfix -c config-directory start". 307 /* .SH "Other options" 308 /* .IP \fB-v\fR 309 /* Enable verbose logging for debugging purposes. Multiple 310 /* \fB-v\fR options make the software increasingly verbose. 311 /* .RE 312 /* ENVIRONMENT 313 /* .ad 314 /* .fi 315 /* The \fBpostmulti\fR(1) command exports the following environment 316 /* variables before executing the requested \fIcommand\fR for a given 317 /* instance: 318 /* .IP \fBMAIL_VERBOSE\fR 319 /* This is set when the -v command-line option is present. 320 /* .IP \fBMAIL_CONFIG\fR 321 /* The location of the configuration directory of the instance. 322 /* CONFIGURATION PARAMETERS 323 /* .ad 324 /* .fi 325 /* .IP "\fBconfig_directory (see 'postconf -d' output)\fR" 326 /* The default location of the Postfix main.cf and master.cf 327 /* configuration files. 328 /* .IP "\fBdaemon_directory (see 'postconf -d' output)\fR" 329 /* The directory with Postfix support programs and daemon programs. 330 /* .IP "\fBimport_environment (see 'postconf -d' output)\fR" 331 /* The list of environment parameters that a privileged Postfix 332 /* process will import from a non-Postfix parent process, or name=value 333 /* environment overrides. 334 /* .IP "\fBmulti_instance_directories (empty)\fR" 335 /* An optional list of non-default Postfix configuration directories; 336 /* these directories belong to additional Postfix instances that share 337 /* the Postfix executable files and documentation with the default 338 /* Postfix instance, and that are started, stopped, etc., together 339 /* with the default Postfix instance. 340 /* .IP "\fBmulti_instance_group (empty)\fR" 341 /* The optional instance group name of this Postfix instance. 342 /* .IP "\fBmulti_instance_name (empty)\fR" 343 /* The optional instance name of this Postfix instance. 344 /* .IP "\fBmulti_instance_enable (no)\fR" 345 /* Allow this Postfix instance to be started, stopped, etc., by a 346 /* multi-instance manager. 347 /* .IP "\fBpostmulti_start_commands (start)\fR" 348 /* The \fBpostfix\fR(1) commands that the \fBpostmulti\fR(1) instance manager treats 349 /* as "start" commands. 350 /* .IP "\fBpostmulti_stop_commands (see 'postconf -d' output)\fR" 351 /* The \fBpostfix\fR(1) commands that the \fBpostmulti\fR(1) instance manager treats 352 /* as "stop" commands. 353 /* .IP "\fBpostmulti_control_commands (reload flush)\fR" 354 /* The \fBpostfix\fR(1) commands that the \fBpostmulti\fR(1) instance manager 355 /* treats as "control" commands, that operate on running instances. 356 /* .IP "\fBsyslog_facility (mail)\fR" 357 /* The syslog facility of Postfix logging. 358 /* .IP "\fBsyslog_name (see 'postconf -d' output)\fR" 359 /* A prefix that is prepended to the process name in syslog 360 /* records, so that, for example, "smtpd" becomes "prefix/smtpd". 361 /* .PP 362 /* Available in Postfix 3.0 and later: 363 /* .IP "\fBmeta_directory (see 'postconf -d' output)\fR" 364 /* The location of non-executable files that are shared among 365 /* multiple Postfix instances, such as postfix-files, dynamicmaps.cf, 366 /* and the multi-instance template files main.cf.proto and master.cf.proto. 367 /* .IP "\fBshlib_directory (see 'postconf -d' output)\fR" 368 /* The location of Postfix dynamically-linked libraries 369 /* (libpostfix-*.so), and the default location of Postfix database 370 /* plugins (postfix-*.so) that have a relative pathname in the 371 /* dynamicmaps.cf file. 372 /* FILES 373 /* $meta_directory/main.cf.proto, stock configuration file 374 /* $meta_directory/master.cf.proto, stock configuration file 375 /* $daemon_directory/postmulti-script, life-cycle helper program 376 /* SEE ALSO 377 /* postfix(1), Postfix control program 378 /* postfix-wrapper(5), Postfix multi-instance API 379 /* README FILES 380 /* .ad 381 /* .fi 382 /* Use "\fBpostconf readme_directory\fR" or "\fBpostconf 383 /* html_directory\fR" to locate this information. 384 /* .nf 385 /* .na 386 /* MULTI_INSTANCE_README, Postfix multi-instance management 387 /* HISTORY 388 /* .ad 389 /* .fi 390 /* The \fBpostmulti\fR(1) command was introduced with Postfix 391 /* version 2.6. 392 /* LICENSE 393 /* .ad 394 /* .fi 395 /* The Secure Mailer license must be distributed with this software. 396 /* AUTHOR(S) 397 /* Victor Duchovni 398 /* Morgan Stanley 399 /* 400 /* Wietse Venema 401 /* IBM T.J. Watson Research 402 /* P.O. Box 704 403 /* Yorktown Heights, NY 10598, USA 404 /* 405 /* Wietse Venema 406 /* Google, Inc. 407 /* 111 8th Avenue 408 /* New York, NY 10011, USA 409 /*--*/ 410 411 /* System library. */ 412 413 #include <sys_defs.h> 414 #include <sys/stat.h> 415 #include <sys/wait.h> 416 #include <vstream.h> 417 #include <stdlib.h> 418 #include <unistd.h> 419 #include <string.h> 420 #include <fcntl.h> 421 #include <errno.h> 422 #include <ctype.h> 423 #ifdef USE_PATHS_H 424 #include <paths.h> 425 #endif 426 #include <stddef.h> 427 428 /* Utility library. */ 429 430 #include <msg.h> 431 #include <msg_vstream.h> 432 #include <vstream.h> 433 #include <vstring_vstream.h> 434 #include <stringops.h> 435 #include <clean_env.h> 436 #include <argv.h> 437 #include <safe.h> 438 #include <mymalloc.h> 439 #include <htable.h> 440 #include <name_code.h> 441 #include <ring.h> 442 #include <warn_stat.h> 443 444 /* Global library. */ 445 446 #include <mail_version.h> 447 #include <mail_params.h> 448 #include <mail_conf.h> 449 #include <mail_parm_split.h> 450 #include <maillog_client.h> 451 452 /* Application-specific. */ 453 454 /* 455 * Configuration parameters, specific to postmulti(1). 456 */ 457 char *var_multi_start_cmds; 458 char *var_multi_stop_cmds; 459 char *var_multi_cntrl_cmds; 460 461 /* 462 * Shared directory pathnames. 463 */ 464 typedef struct { 465 const char *param_name; 466 char **param_value; 467 } SHARED_PATH; 468 469 static SHARED_PATH shared_dir_table[] = { 470 VAR_COMMAND_DIR, &var_command_dir, 471 VAR_DAEMON_DIR, &var_daemon_dir, 472 VAR_META_DIR, &var_meta_dir, 473 VAR_SHLIB_DIR, &var_shlib_dir, 474 0, 475 }; 476 477 /* 478 * Actions. 479 */ 480 #define ITER_CMD_POSTFIX (1<<0) /* postfix(1) iterator mode */ 481 #define ITER_CMD_LIST (1<<1) /* listing iterator mode */ 482 #define ITER_CMD_GENERIC (1<<2) /* generic command iterator mode */ 483 484 #define ITER_CMD_MASK_ALL \ 485 (ITER_CMD_POSTFIX | ITER_CMD_LIST | ITER_CMD_GENERIC) 486 487 #define EDIT_CMD_CREATE (1<<4) /* create new instance */ 488 #define EDIT_CMD_IMPORT (1<<5) /* import existing instance */ 489 #define EDIT_CMD_DESTROY (1<<6) /* destroy instance */ 490 #define EDIT_CMD_DEPORT (1<<7) /* export instance */ 491 #define EDIT_CMD_ENABLE (1<<8) /* enable start/stop */ 492 #define EDIT_CMD_DISABLE (1<<9) /* disable start/stop */ 493 #define EDIT_CMD_ASSIGN (1<<10) /* assign name/group */ 494 #define EDIT_CMD_INIT (1<<11) /* hook into main.cf */ 495 496 #define EDIT_CMD_MASK_ADD (EDIT_CMD_CREATE | EDIT_CMD_IMPORT) 497 #define EDIT_CMD_MASK_DEL (EDIT_CMD_DESTROY | EDIT_CMD_DEPORT) 498 #define EDIT_CMD_MASK_ASSIGN (EDIT_CMD_MASK_ADD | EDIT_CMD_ASSIGN) 499 #define EDIT_CMD_MASK_ENB (EDIT_CMD_ENABLE | EDIT_CMD_DISABLE) 500 #define EDIT_CMD_MASK_ALL \ 501 (EDIT_CMD_MASK_ASSIGN | EDIT_CMD_MASK_DEL | EDIT_CMD_MASK_ENB | \ 502 EDIT_CMD_INIT) 503 504 /* 505 * Edit command to number mapping, and vice versa. 506 */ 507 static NAME_CODE edit_command_table[] = { 508 "create", EDIT_CMD_CREATE, 509 "import", EDIT_CMD_IMPORT, 510 "destroy", EDIT_CMD_DESTROY, 511 "deport", EDIT_CMD_DEPORT, 512 "enable", EDIT_CMD_ENABLE, 513 "disable", EDIT_CMD_DISABLE, 514 "assign", EDIT_CMD_ASSIGN, 515 "init", EDIT_CMD_INIT, 516 0, -1, 517 }; 518 519 #define EDIT_CMD_CODE(str) \ 520 name_code(edit_command_table, NAME_CODE_FLAG_STRICT_CASE, (str)) 521 #define EDIT_CMD_STR(code) str_name_code(edit_command_table, (code)) 522 523 /* 524 * Mandatory prefix for non-empty instance names. 525 */ 526 #ifndef NAME_PREFIX 527 #define NAME_PREFIX "postfix-" 528 #endif 529 #define HAS_NAME_PREFIX(name) \ 530 (strncmp((name), NAME_PREFIX, sizeof(NAME_PREFIX)-1) == 0) 531 #define NEED_NAME_PREFIX(name) \ 532 ((name) != 0 && strcmp((name), "-") != 0 && !HAS_NAME_PREFIX(name)) 533 #define NAME_SUFFIX(name) ((name) + sizeof(NAME_PREFIX) - 1) 534 535 /* 536 * In-core instance structure. Only private information is kept here. 537 */ 538 typedef struct instance { 539 RING ring; /* linkage. */ 540 char *config_dir; /* private */ 541 char *queue_dir; /* private */ 542 char *data_dir; /* private */ 543 char *name; /* null or name */ 544 char *gname; /* null or group */ 545 int enabled; /* start/stop enable */ 546 int primary; /* special */ 547 } INSTANCE; 548 549 /* 550 * Managed instance list (edit mode and iterator mode). 551 */ 552 static RING instance_hd[1]; /* instance list head */ 553 554 #define RING_TO_INSTANCE(ring_ptr) RING_TO_APPL(ring_ptr, INSTANCE, ring) 555 #define RING_PTR_OF(x) (&((x)->ring)) 556 557 #define FOREACH_INSTANCE(entry) \ 558 for ((entry) = instance_hd; \ 559 ((entry) = ring_succ(entry)) != instance_hd;) 560 561 #define FOREACH_SECONDARY_INSTANCE(entry) \ 562 for ((entry) = ring_succ(instance_hd); \ 563 ((entry) = ring_succ(entry)) != instance_hd;) 564 565 #define NEXT_ITERATOR_INSTANCE(flags, entry) \ 566 (((flags) & ITER_FLAG_REVERSE) ? ring_pred(entry) : ring_succ(entry)) 567 568 #define FOREACH_ITERATOR_INSTANCE(flags, entry) \ 569 for ((entry) = instance_hd; \ 570 ((entry) = NEXT_ITERATOR_INSTANCE(flags, (entry))) != instance_hd;) 571 572 /* 573 * Instance selection. One can either select all instances, select by 574 * instance name, or select by instance group. 575 */ 576 typedef struct { 577 int type; /* see below */ 578 char *name; /* undefined or name */ 579 } INST_SELECTION; 580 581 #define INST_SEL_NONE 0 /* default: no selection */ 582 #define INST_SEL_ALL 1 /* select all instances */ 583 #define INST_SEL_NAME 2 /* select instance name */ 584 #define INST_SEL_GROUP 3 /* select instance group */ 585 586 /* 587 * Instance name assignment. Each instance may be assigned an instance name 588 * (this must be globally unique within a multi-instance cluster) or an 589 * instance group name (this is intended to be shared). Externally, empty 590 * names may be represented as "-". Internally, we use "" only, to simplify 591 * the code. 592 */ 593 typedef struct { 594 char *name; /* null or assigned instance name */ 595 char *gname; /* null or assigned group name */ 596 } NAME_ASSIGNMENT; 597 598 /* 599 * Iterator controls for non-edit commands. One can reverse the iteration 600 * order, or give special treatment to disabled instances. 601 */ 602 #define ITER_FLAG_DEFAULT 0 /* default setting */ 603 #define ITER_FLAG_REVERSE (1<<0) /* reverse iteration order */ 604 #define ITER_FLAG_CHECK_DISABLED (1<<1) /* check disabled instances */ 605 #define ITER_FLAG_SKIP_DISABLED (1<<2) /* skip disabled instances */ 606 607 /* 608 * Environment export controls for edit commands. postmulti(1) exports only 609 * things that need to be updated. 610 */ 611 #define EXP_FLAG_MULTI_DIRS (1<<0) /* export multi_instance_directories */ 612 #define EXP_FLAG_MULTI_NAME (1<<1) /* export multi_instance_name */ 613 #define EXP_FLAG_MULTI_GROUP (1<<2) /* export multi_instance_group */ 614 615 /* 616 * To detect conflicts, each instance name and each shared or private 617 * pathname is registered in one place, with its owner. Everyone must 618 * register their claims when they join, and will be rejected in case of 619 * conlict. 620 * 621 * Each claim value involves a parameter value (either a directory name or an 622 * instance name). Each claim owner is the config_directory pathname plus 623 * the parameter name. 624 * 625 * XXX: No multi.cf lock file, so this is not race-free. 626 */ 627 static HTABLE *claim_table; 628 629 #define IS_CLAIMED_BY(name) \ 630 (claim_table ? htable_find(claim_table, (name)) : 0) 631 632 /* 633 * Forward references. 634 */ 635 static int iterate_command(int, int, char **, INST_SELECTION *); 636 static int match_instance_selection(INSTANCE *, INST_SELECTION *); 637 638 /* 639 * Convenience. 640 */ 641 #define INSTANCE_NAME(i) ((i)->name ? (i)->name : (i)->config_dir) 642 #define STR(buf) vstring_str(buf) 643 644 /* register_claim - register claim or bust */ 645 646 static void register_claim(const char *instance_path, const char *param_name, 647 const char *param_value) 648 { 649 const char *myname = "register_claim"; 650 char *requestor; 651 const char *owner; 652 653 /* 654 * Sanity checks. 655 */ 656 if (instance_path == 0 || *instance_path == 0) 657 msg_panic("%s: no or empty instance pathname", myname); 658 if (param_name == 0 || *param_name == 0) 659 msg_panic("%s: no or empty parameter name", myname); 660 if (param_value == 0) 661 msg_panic("%s: no parameter value", myname); 662 663 /* 664 * Make a claim or report a conflict. 665 */ 666 if (claim_table == 0) 667 claim_table = htable_create(100); 668 requestor = concatenate(instance_path, ", ", param_name, (char *) 0); 669 if ((owner = htable_find(claim_table, param_value)) == 0) { 670 (void) htable_enter(claim_table, param_value, requestor); 671 } else if (strcmp(owner, requestor) == 0) { 672 myfree(requestor); 673 } else { 674 msg_fatal("instance %s, %s=%s conflicts with instance %s=%s", 675 instance_path, param_name, param_value, owner, param_value); 676 } 677 } 678 679 /* claim_instance_attributes - claim multiple private instance attributes */ 680 681 static void claim_instance_attributes(INSTANCE *ip) 682 { 683 684 /* 685 * Detect instance name or pathname conflicts between this instance and 686 * other instances. XXX: No multi.cf lock file, so this is not race-free. 687 */ 688 if (ip->name) 689 register_claim(ip->config_dir, VAR_MULTI_NAME, ip->name); 690 register_claim(ip->config_dir, VAR_CONFIG_DIR, ip->config_dir); 691 register_claim(ip->config_dir, VAR_QUEUE_DIR, ip->queue_dir); 692 register_claim(ip->config_dir, VAR_DATA_DIR, ip->data_dir); 693 } 694 695 /* alloc_instance - allocate a single instance object */ 696 697 static INSTANCE *alloc_instance(const char *config_dir) 698 { 699 INSTANCE *ip = (INSTANCE *) mymalloc(sizeof(INSTANCE)); 700 701 ring_init(RING_PTR_OF(ip)); 702 ip->config_dir = config_dir ? mystrdup(config_dir) : 0; 703 ip->queue_dir = 0; 704 ip->data_dir = 0; 705 ip->name = 0; 706 ip->gname = 0; 707 ip->enabled = 0; 708 ip->primary = 0; 709 710 return (ip); 711 } 712 713 #if 0 714 715 /* free_instance - free a single instance object */ 716 717 static void free_instance(INSTANCE *ip) 718 { 719 720 /* 721 * If we continue after secondary main.cf file read error, we must be 722 * prepared for the case that some parameters may be missing. 723 */ 724 if (ip->name) 725 myfree(ip->name); 726 if (ip->gname) 727 myfree(ip->gname); 728 if (ip->config_dir) 729 myfree(ip->config_dir); 730 if (ip->queue_dir) 731 myfree(ip->queue_dir); 732 if (ip->data_dir) 733 myfree(ip->data_dir); 734 myfree((void *) ip); 735 } 736 737 #endif 738 739 /* insert_instance - insert instance before selected location, claim names */ 740 741 static void insert_instance(INSTANCE *ip, INST_SELECTION *selection) 742 { 743 RING *old; 744 745 #define append_instance(ip) insert_instance((ip), (INST_SELECTION *) 0) 746 747 /* 748 * Insert instance before the selected site. 749 */ 750 claim_instance_attributes(ip); 751 if (ring_succ(instance_hd) == 0) 752 ring_init(instance_hd); 753 if (selection && selection->type != INST_SEL_NONE) { 754 FOREACH_SECONDARY_INSTANCE(old) { 755 if (match_instance_selection(RING_TO_INSTANCE(old), selection)) { 756 ring_prepend(old, RING_PTR_OF(ip)); 757 return; 758 } 759 } 760 if (selection->type != INST_SEL_ALL) 761 msg_fatal("No matching secondary instances"); 762 } 763 ring_prepend(instance_hd, RING_PTR_OF(ip)); 764 } 765 766 /* create_primary_instance - synthetic entry for primary instance */ 767 768 static INSTANCE *create_primary_instance(void) 769 { 770 INSTANCE *ip = alloc_instance(var_config_dir); 771 772 /* 773 * There is no need to load primary instance parameter settings from 774 * file. We already have the main.cf parameters of interest in memory. 775 */ 776 #define SAVE_INSTANCE_NAME(val) (*(val) ? mystrdup(val) : 0) 777 778 ip->name = SAVE_INSTANCE_NAME(var_multi_name); 779 ip->gname = SAVE_INSTANCE_NAME(var_multi_group); 780 ip->enabled = var_multi_enable; 781 ip->queue_dir = mystrdup(var_queue_dir); 782 ip->data_dir = mystrdup(var_data_dir); 783 ip->primary = 1; 784 return (ip); 785 } 786 787 /* load_instance - read instance parameters from config_dir/main.cf */ 788 789 static INSTANCE *load_instance(INSTANCE *ip) 790 { 791 VSTREAM *pipe; 792 VSTRING *buf; 793 char *name; 794 char *value; 795 ARGV *cmd; 796 int count = 0; 797 static NAME_CODE bool_code[] = { 798 CONFIG_BOOL_YES, 1, 799 CONFIG_BOOL_NO, 0, 800 0, -1, 801 }; 802 803 /* 804 * Expand parameter values in the context of the target main.cf file. 805 */ 806 #define REQUEST_PARAM_COUNT 5 /* # of requested parameters */ 807 808 cmd = argv_alloc(REQUEST_PARAM_COUNT + 3); 809 name = concatenate(var_command_dir, "/", "postconf", (char *) 0); 810 argv_add(cmd, name, "-xc", ip->config_dir, 811 VAR_QUEUE_DIR, VAR_DATA_DIR, 812 VAR_MULTI_NAME, VAR_MULTI_GROUP, VAR_MULTI_ENABLE, 813 (char *) 0); 814 myfree(name); 815 pipe = vstream_popen(O_RDONLY, CA_VSTREAM_POPEN_ARGV(cmd->argv), 816 CA_VSTREAM_POPEN_END); 817 argv_free(cmd); 818 if (pipe == 0) 819 msg_fatal("Cannot parse %s/main.cf file: %m", ip->config_dir); 820 821 /* 822 * Read parameter settings from postconf. See also comments below on 823 * whether we should continue or skip groups after error instead of 824 * bailing out immediately. 825 */ 826 buf = vstring_alloc(100); 827 while (vstring_get_nonl(buf, pipe) != VSTREAM_EOF) { 828 if (split_nameval(STR(buf), &name, &value)) 829 msg_fatal("Invalid %s/main.cf parameter: %s", 830 ip->config_dir, STR(buf)); 831 if (strcmp(name, VAR_QUEUE_DIR) == 0 && ++count) 832 ip->queue_dir = mystrdup(value); 833 else if (strcmp(name, VAR_DATA_DIR) == 0 && ++count) 834 ip->data_dir = mystrdup(value); 835 else if (strcmp(name, VAR_MULTI_NAME) == 0 && ++count) 836 ip->name = SAVE_INSTANCE_NAME(value); 837 else if (strcmp(name, VAR_MULTI_GROUP) == 0 && ++count) 838 ip->gname = SAVE_INSTANCE_NAME(value); 839 else if (strcmp(name, VAR_MULTI_ENABLE) == 0 && ++count) { 840 /* mail_conf_bool(3) is case insensitive! */ 841 ip->enabled = name_code(bool_code, NAME_CODE_FLAG_NONE, value); 842 if (ip->enabled < 0) 843 msg_fatal("Unexpected %s/main.cf entry: %s = %s", 844 ip->config_dir, VAR_MULTI_ENABLE, value); 845 } 846 } 847 vstring_free(buf); 848 849 /* 850 * XXX We should not bail out while reading a bad secondary main.cf file. 851 * When we manage dozens or more instances, the likelihood increases that 852 * some file will be damaged or missing after a system crash. That is not 853 * a good reason to prevent undamaged Postfix instances from starting. 854 */ 855 if (count != REQUEST_PARAM_COUNT) 856 msg_fatal("Failed to obtain all required %s/main.cf parameters", 857 ip->config_dir); 858 859 if (vstream_pclose(pipe)) 860 msg_fatal("Cannot parse %s/main.cf file", ip->config_dir); 861 return (ip); 862 } 863 864 /* load_all_instances - compute list of Postfix instances */ 865 866 static void load_all_instances(void) 867 { 868 INSTANCE *primary_instance; 869 char **cpp; 870 ARGV *secondary_names; 871 872 /* 873 * Avoid unexpected behavior when $multi_instance_directories contains 874 * only comma characters. Count the actual number of elements, before we 875 * decide that the list is empty. 876 */ 877 secondary_names = argv_split(var_multi_conf_dirs, CHARS_COMMA_SP); 878 879 /* 880 * First, the primary instance. This is synthesized out of thin air. 881 */ 882 primary_instance = create_primary_instance(); 883 if (secondary_names->argc == 0) 884 primary_instance->enabled = 1; /* Single-instance mode */ 885 append_instance(primary_instance); 886 887 /* 888 * Next, instances defined in $multi_instance_directories. Note: 889 * load_instance() has side effects on the global config dictionary, but 890 * this does not affect the values that have already been extracted into 891 * C variables. 892 */ 893 for (cpp = secondary_names->argv; *cpp != 0; cpp++) 894 append_instance(load_instance(alloc_instance(*cpp))); 895 896 argv_free(secondary_names); 897 } 898 899 /* match_instance_selection - match all/name/group constraints */ 900 901 static int match_instance_selection(INSTANCE *ip, INST_SELECTION *selection) 902 { 903 char *iname; 904 char *name; 905 906 /* 907 * When selecting (rather than assigning names) an instance, we match by 908 * the instance name, config_directory path, or the instance name suffix 909 * (name without mandatory prefix). Selecting "-" selects the primary 910 * instance. 911 */ 912 switch (selection->type) { 913 case INST_SEL_NONE: 914 return (0); 915 case INST_SEL_ALL: 916 return (1); 917 case INST_SEL_GROUP: 918 return (ip->gname != 0 && strcmp(selection->name, ip->gname) == 0); 919 case INST_SEL_NAME: 920 name = selection->name; 921 if (*name == '/' || ip->name == 0) 922 iname = ip->config_dir; 923 else if (!HAS_NAME_PREFIX(name) && HAS_NAME_PREFIX(ip->name)) 924 iname = NAME_SUFFIX(ip->name); 925 else 926 iname = ip->name; 927 return (strcmp(name, iname) == 0 928 || (ip->primary && strcmp(name, "-") == 0)); 929 default: 930 msg_panic("match_instance_selection: unknown selection type: %d", 931 selection->type); 932 } 933 } 934 935 /* check_setenv - setenv() with extreme prejudice */ 936 937 static void check_setenv(const char *name, const char *value) 938 { 939 #define CLOBBER 1 940 if (setenv(name, value, CLOBBER) < 0) 941 msg_fatal("setenv: %m"); 942 } 943 944 /* prepend_command_path - prepend command_directory to PATH */ 945 946 static void prepend_command_path(void) 947 { 948 char *cmd_path; 949 950 /* 951 * Carefully prepend "$command_directory:" to PATH. We can free the 952 * buffer after check_setenv(), since the value is copied there. 953 */ 954 cmd_path = safe_getenv("PATH"); 955 cmd_path = concatenate(var_command_dir, ":", (cmd_path && *cmd_path) ? 956 cmd_path : ROOT_PATH, (char *) 0); 957 check_setenv("PATH", cmd_path); 958 myfree(cmd_path); 959 } 960 961 /* check_shared_dir_status - check and claim shared directories */ 962 963 static void check_shared_dir_status(void) 964 { 965 struct stat st; 966 const SHARED_PATH *sp; 967 968 /* 969 * XXX Avoid false conflicts with meta_directory. This usually overlaps 970 * with other directories, typcally config_directory, shlib_directory or 971 * daemon_directory. 972 */ 973 for (sp = shared_dir_table; sp->param_name; ++sp) { 974 if (sp->param_value[0][0] != '/') /* "no" or other special */ 975 continue; 976 if (stat(sp->param_value[0], &st) < 0) 977 msg_fatal("%s = '%s': directory not found: %m", 978 sp->param_name, sp->param_value[0]); 979 if (!S_ISDIR(st.st_mode)) 980 msg_fatal("%s = '%s' is not a directory", 981 sp->param_name, sp->param_value[0]); 982 if (strcmp(sp->param_name, VAR_META_DIR) == 0) 983 continue; 984 register_claim(var_config_dir, sp->param_name, sp->param_value[0]); 985 } 986 } 987 988 /* check_safe_name - allow instance or group name with only "safe" characters */ 989 990 static int check_safe_name(const char *s) 991 { 992 #define SAFE_PUNCT "!@%-_=+:./" 993 if (*s == 0) 994 return (0); 995 for (; *s; ++s) { 996 if (!ISALNUM(*s) && !strchr(SAFE_PUNCT, *s)) 997 return (0); 998 } 999 return (1); 1000 } 1001 1002 /* check_name_assignments - Check validity of assigned instance or group name */ 1003 1004 static void check_name_assignments(NAME_ASSIGNMENT *assignment) 1005 { 1006 1007 /* 1008 * Syntax check the assigned instance name. This name is also used to 1009 * generate directory pathnames, so we must not allow "/" characters. 1010 * 1011 * The value "" will clear the name and is always valid. The command-line 1012 * parser has already converted "-" into "", to simplify implementation. 1013 */ 1014 if (assignment->name && *assignment->name) { 1015 if (!check_safe_name(assignment->name)) 1016 msg_fatal("Unsafe characters in new instance name: '%s'", 1017 assignment->name); 1018 if (strchr(assignment->name, '/')) 1019 msg_fatal("Illegal '/' character in new instance name: '%s'", 1020 assignment->name); 1021 if (NEED_NAME_PREFIX(assignment->name)) 1022 msg_fatal("New instance name must start with '%s'", 1023 NAME_PREFIX); 1024 } 1025 1026 /* 1027 * Syntax check the assigned group name. 1028 */ 1029 if (assignment->gname && *assignment->gname) { 1030 if (!check_safe_name(assignment->gname)) 1031 msg_fatal("Unsafe characters in '-G %s'", assignment->gname); 1032 } 1033 } 1034 1035 /* do_name_assignments - assign instance/group names */ 1036 1037 static int do_name_assignments(INSTANCE *target, NAME_ASSIGNMENT *assignment) 1038 { 1039 int export_flags = 0; 1040 1041 /* 1042 * The command-line parser has already converted "-" into "", to simplify 1043 * implementation. 1044 */ 1045 if (assignment->name 1046 && strcmp(assignment->name, target->name ? target->name : "")) { 1047 register_claim(target->config_dir, VAR_MULTI_NAME, assignment->name); 1048 if (target->name) 1049 myfree(target->name); 1050 target->name = SAVE_INSTANCE_NAME(assignment->name); 1051 export_flags |= EXP_FLAG_MULTI_NAME; 1052 } 1053 if (assignment->gname 1054 && strcmp(assignment->gname, target->gname ? target->gname : "")) { 1055 if (target->gname) 1056 myfree(target->gname); 1057 target->gname = SAVE_INSTANCE_NAME(assignment->gname); 1058 export_flags |= EXP_FLAG_MULTI_GROUP; 1059 } 1060 return (export_flags); 1061 } 1062 1063 /* make_private_path - generate secondary pathname using primary as template */ 1064 1065 static char *make_private_path(const char *param_name, 1066 const char *primary_value, 1067 NAME_ASSIGNMENT *assignment) 1068 { 1069 char *path; 1070 char *base; 1071 char *end; 1072 1073 /* 1074 * The command-line parser has already converted "-" into "", to simplify 1075 * implementation. 1076 */ 1077 if (assignment->name == 0 || *assignment->name == 0) 1078 msg_fatal("Missing %s parameter value", param_name); 1079 1080 if (*primary_value != '/') 1081 msg_fatal("Invalid default %s parameter value: '%s': " 1082 "specify an absolute pathname", 1083 param_name, primary_value); 1084 1085 base = mystrdup(primary_value); 1086 if ((end = strrchr(base, '/')) != 0) { 1087 /* Drop trailing slashes */ 1088 if (end[1] == '\0') { 1089 while (--end > base && *end == '/') 1090 *end = '\0'; 1091 end = strrchr(base, '/'); 1092 } 1093 /* Drop last path component */ 1094 while (end > base && *end == '/') 1095 *end-- = '\0'; 1096 } 1097 path = concatenate(base[1] ? base : "", "/", 1098 assignment->name, (char *) 0); 1099 myfree(base); 1100 return (path); 1101 } 1102 1103 /* assign_new_parameter - assign new instance private name=value */ 1104 1105 static void assign_new_parameter(INSTANCE *new, int edit_cmd, 1106 const char *arg) 1107 { 1108 char *saved_arg; 1109 char *name; 1110 char *value; 1111 char *end; 1112 char **target = 0; 1113 1114 /* 1115 * With "import", only config_directory is specified on the command line 1116 * (either explicitly as config_directory=/path/name, or implicitly as 1117 * instance name). The other private directory pathnames are taken from 1118 * the existing instance's main.cf file. 1119 * 1120 * With "create", all private pathname parameters are specified on the 1121 * command line, or generated from an instance name. 1122 */ 1123 saved_arg = mystrdup(arg); 1124 if (split_nameval(saved_arg, &name, &value)) 1125 msg_fatal("Malformed parameter setting '%s'", arg); 1126 1127 if (strcmp(VAR_CONFIG_DIR, name) == 0) { 1128 target = &new->config_dir; 1129 } else if (edit_cmd != EDIT_CMD_IMPORT) { 1130 if (strcmp(VAR_QUEUE_DIR, name) == 0) { 1131 target = &new->queue_dir; 1132 } else if (strcmp(VAR_DATA_DIR, name) == 0) { 1133 target = &new->data_dir; 1134 } 1135 } 1136 if (target == 0) 1137 msg_fatal("Parameter '%s' not valid with action %s", 1138 name, EDIT_CMD_STR(edit_cmd)); 1139 1140 /* 1141 * Extract and assign the parameter value. We do a limited number of 1142 * checks here. Conflicts between instances are checked by the caller. 1143 * More checks may be implemented in the helper script if inspired. 1144 */ 1145 if (*value != '/') 1146 msg_fatal("Parameter setting '%s' is not an absolute path", name); 1147 1148 /* Tolerate+trim trailing "/" from readline completion */ 1149 for (end = value + strlen(value) - 1; end > value && *end == '/'; --end) 1150 *end = 0; 1151 1152 /* No checks here for "/." or other shoot-foot silliness. */ 1153 if (end == value) 1154 msg_fatal("Parameter setting '%s' is the root directory", name); 1155 1156 if (*target) 1157 myfree(*target); 1158 *target = mystrdup(value); 1159 1160 /* 1161 * Cleanup. 1162 */ 1163 myfree(saved_arg); 1164 } 1165 1166 /* assign_new_parameters - initialize new instance private parameters */ 1167 1168 static void assign_new_parameters(INSTANCE *new, int edit_cmd, 1169 char **argv, NAME_ASSIGNMENT *assignment) 1170 { 1171 const char *owner; 1172 1173 /* 1174 * Sanity check the explicit parameter settings. More stringent checks 1175 * may take place in the helper script. 1176 */ 1177 while (*argv) 1178 assign_new_parameter(new, edit_cmd, *argv++); 1179 1180 /* 1181 * Initialize any missing private directory pathnames, using the primary 1182 * configuration directory parameter values as a template, and using the 1183 * assigned instance name to fill in the blanks. 1184 * 1185 * When importing an existing instance, load private directory pathnames 1186 * from its main.cf file. 1187 */ 1188 if (new->config_dir == 0) 1189 new->config_dir = 1190 make_private_path(VAR_CONFIG_DIR, var_config_dir, assignment); 1191 /* Needed for better-quality error message. */ 1192 if ((owner = IS_CLAIMED_BY(new->config_dir)) != 0) 1193 msg_fatal("new %s=%s is already in use by instance %s=%s", 1194 VAR_CONFIG_DIR, new->config_dir, owner, new->config_dir); 1195 if (edit_cmd != EDIT_CMD_IMPORT) { 1196 if (new->queue_dir == 0) 1197 new->queue_dir = 1198 make_private_path(VAR_QUEUE_DIR, var_queue_dir, assignment); 1199 if (new->data_dir == 0) 1200 new->data_dir = 1201 make_private_path(VAR_DATA_DIR, var_data_dir, assignment); 1202 } else { 1203 load_instance(new); 1204 } 1205 } 1206 1207 /* export_helper_environment - update environment settings for helper command */ 1208 1209 static void export_helper_environment(INSTANCE *target, int export_flags) 1210 { 1211 ARGV *import_env; 1212 VSTRING *multi_dirs; 1213 const SHARED_PATH *sp; 1214 RING *entry; 1215 1216 /* 1217 * Environment import filter, to enforce consistent behavior whether this 1218 * command is started by hand, or at system boot time. This is necessary 1219 * because some shell scripts use environment settings to override 1220 * main.cf settings. 1221 */ 1222 import_env = mail_parm_split(VAR_IMPORT_ENVIRON, var_import_environ); 1223 clean_env(import_env->argv); 1224 argv_free(import_env); 1225 1226 /* 1227 * Prepend $command_directory: to PATH. This supposedly ensures that 1228 * naive programs will execute commands from the right Postfix version. 1229 */ 1230 prepend_command_path(); 1231 1232 /* 1233 * The following ensures that Postfix's own programs will target the 1234 * primary instance. 1235 */ 1236 check_setenv(CONF_ENV_PATH, var_config_dir); 1237 1238 /* 1239 * Export the parameter settings that are shared between instances. 1240 */ 1241 for (sp = shared_dir_table; sp->param_name; ++sp) 1242 check_setenv(sp->param_name, sp->param_value[0]); 1243 1244 /* 1245 * Export the target instance's private directory locations. 1246 */ 1247 check_setenv(VAR_CONFIG_DIR, target->config_dir); 1248 check_setenv(VAR_QUEUE_DIR, target->queue_dir); 1249 check_setenv(VAR_DATA_DIR, target->data_dir); 1250 1251 /* 1252 * With operations that add or delete a secondary instance, we export the 1253 * modified multi_instance_directories parameter value for the primary 1254 * Postfix instance. 1255 */ 1256 if (export_flags & EXP_FLAG_MULTI_DIRS) { 1257 multi_dirs = vstring_alloc(100); 1258 FOREACH_SECONDARY_INSTANCE(entry) { 1259 if (VSTRING_LEN(multi_dirs) > 0) 1260 VSTRING_ADDCH(multi_dirs, ' '); 1261 vstring_strcat(multi_dirs, RING_TO_INSTANCE(entry)->config_dir); 1262 } 1263 check_setenv(VAR_MULTI_CONF_DIRS, STR(multi_dirs)); 1264 vstring_free(multi_dirs); 1265 } 1266 1267 /* 1268 * Export updates for the instance name and group. Empty value (or no 1269 * export) means don't update, "-" means clear. 1270 */ 1271 if (export_flags & EXP_FLAG_MULTI_NAME) 1272 check_setenv(VAR_MULTI_NAME, target->name && *target->name ? 1273 target->name : "-"); 1274 1275 if (export_flags & EXP_FLAG_MULTI_GROUP) 1276 check_setenv(VAR_MULTI_GROUP, target->gname && *target->gname ? 1277 target->gname : "-"); 1278 1279 /* 1280 * If we would implement enable/disable commands by exporting the updated 1281 * parameter value, then we could skip commands that have no effect, just 1282 * like we can skip "assign" commands that make no change. 1283 */ 1284 } 1285 1286 /* install_new_instance - install and return newly created instance */ 1287 1288 static INSTANCE *install_new_instance(int edit_cmd, char **argv, 1289 INST_SELECTION *selection, 1290 NAME_ASSIGNMENT *assignment, 1291 int *export_flags) 1292 { 1293 INSTANCE *new; 1294 1295 new = alloc_instance((char *) 0); 1296 check_name_assignments(assignment); 1297 assign_new_parameters(new, edit_cmd, argv, assignment); 1298 *export_flags |= 1299 (do_name_assignments(new, assignment) | EXP_FLAG_MULTI_DIRS); 1300 insert_instance(new, selection); 1301 return (new); 1302 } 1303 1304 /* update_instance - update existing instance, return export flags */ 1305 1306 static int update_instance(INSTANCE *target, NAME_ASSIGNMENT *assignment) 1307 { 1308 int export_flags; 1309 1310 check_name_assignments(assignment); 1311 export_flags = do_name_assignments(target, assignment); 1312 return (export_flags); 1313 } 1314 1315 /* select_existing_instance - return instance selected for management */ 1316 1317 static INSTANCE *select_existing_instance(INST_SELECTION *selection, 1318 int unlink_flag, 1319 int *export_flags) 1320 { 1321 INSTANCE *selected = 0; 1322 RING *entry; 1323 INSTANCE *ip; 1324 1325 #define DONT_UNLINK 0 1326 #define DO_UNLINK 1 1327 1328 if (selection->type != INST_SEL_NAME) 1329 msg_fatal("Select an instance via '-i name'"); 1330 1331 /* Find the selected instance and its predecessor */ 1332 FOREACH_INSTANCE(entry) { 1333 if (match_instance_selection(ip = RING_TO_INSTANCE(entry), selection)) { 1334 selected = ip; 1335 break; 1336 } 1337 } 1338 1339 if (selected == 0) 1340 msg_fatal("No instance named %s", selection->name); 1341 1342 if (unlink_flag) { 1343 /* Splice the target instance out of the list */ 1344 if (ring_pred(entry) == instance_hd) 1345 msg_fatal("Cannot remove the primary instance"); 1346 if (selected->enabled) 1347 msg_fatal("Cannot remove enabled instances"); 1348 ring_detach(entry); 1349 if (export_flags == 0) 1350 msg_panic("select_existing_instance: no export flags"); 1351 *export_flags |= EXP_FLAG_MULTI_DIRS; 1352 } 1353 return (selected); 1354 } 1355 1356 /* manage - create/destroy/... manage instances */ 1357 1358 static NORETURN manage(int edit_cmd, int argc, char **argv, 1359 INST_SELECTION *selection, 1360 NAME_ASSIGNMENT *assignment) 1361 { 1362 char *cmd; 1363 INSTANCE *target; 1364 int export_flags; 1365 1366 /* 1367 * Edit mode is not subject to iterator controls. 1368 */ 1369 #define NO_EXPORT_FLAGS ((int *) 0) 1370 export_flags = 0; 1371 1372 switch (edit_cmd) { 1373 case EDIT_CMD_INIT: 1374 target = create_primary_instance(); 1375 break; 1376 1377 case EDIT_CMD_CREATE: 1378 case EDIT_CMD_IMPORT: 1379 load_all_instances(); 1380 target = install_new_instance(edit_cmd, argv, selection, 1381 assignment, &export_flags); 1382 break; 1383 1384 case EDIT_CMD_ASSIGN: 1385 load_all_instances(); 1386 target = 1387 select_existing_instance(selection, DONT_UNLINK, NO_EXPORT_FLAGS); 1388 export_flags |= update_instance(target, assignment); 1389 if (export_flags == 0) 1390 exit(0); 1391 break; 1392 1393 case EDIT_CMD_DESTROY: 1394 case EDIT_CMD_DEPORT: 1395 load_all_instances(); 1396 target = select_existing_instance(selection, DO_UNLINK, &export_flags); 1397 break; 1398 1399 default: 1400 load_all_instances(); 1401 target = 1402 select_existing_instance(selection, DONT_UNLINK, NO_EXPORT_FLAGS); 1403 break; 1404 } 1405 1406 /* 1407 * Set up the helper script's process environment, and execute the helper 1408 * script. 1409 */ 1410 #define HELPER "postmulti-script" 1411 1412 export_helper_environment(target, export_flags); 1413 cmd = concatenate(var_daemon_dir, "/" HELPER, (char *) 0); 1414 execl(cmd, cmd, "-e", EDIT_CMD_STR(edit_cmd), (char *) 0); 1415 msg_fatal("%s: %m", cmd); 1416 } 1417 1418 /* run_user_command - execute external command with requested MAIL_CONFIG env */ 1419 1420 static int run_user_command(INSTANCE *ip, int iter_cmd, int iter_flags, 1421 char **argv) 1422 { 1423 WAIT_STATUS_T status; 1424 int pid; 1425 int wpid; 1426 1427 /* 1428 * Set up a process environment. The postfix(1) command needs MAIL_CONFIG 1429 * (or the equivalent command-line option); it overrides everything else. 1430 * 1431 * postmulti(1) typically runs various Postfix utilities (postsuper, ...) in 1432 * the context of one or more instances. It can also run various scripts 1433 * on the users PATH. So we can't clobber the user's PATH, but do want to 1434 * make sure that the utilities in $command_directory are always found in 1435 * the right place (or at all). 1436 */ 1437 switch (pid = fork()) { 1438 case -1: 1439 msg_warn("fork %s: %m", argv[0]); 1440 return -1; 1441 case 0: 1442 check_setenv(CONF_ENV_PATH, ip->config_dir); 1443 if (iter_cmd != ITER_CMD_POSTFIX) { 1444 check_setenv(VAR_DAEMON_DIR, var_daemon_dir); 1445 check_setenv(VAR_COMMAND_DIR, var_command_dir); 1446 check_setenv(VAR_CONFIG_DIR, ip->config_dir); 1447 check_setenv(VAR_QUEUE_DIR, ip->queue_dir); 1448 check_setenv(VAR_DATA_DIR, ip->data_dir); 1449 check_setenv(VAR_MULTI_NAME, ip->name ? ip->name : ""); 1450 check_setenv(VAR_MULTI_GROUP, ip->gname ? ip->gname : ""); 1451 check_setenv(VAR_MULTI_ENABLE, ip->enabled ? 1452 CONFIG_BOOL_YES : CONFIG_BOOL_NO); 1453 prepend_command_path(); 1454 } 1455 1456 /* 1457 * Replace: postfix -- start ... With: postfix -- check ... 1458 */ 1459 if (iter_cmd == ITER_CMD_POSTFIX 1460 && (iter_flags & ITER_FLAG_CHECK_DISABLED) && !ip->enabled) 1461 argv[2] = "check"; 1462 1463 execvp(argv[0], argv); 1464 msg_fatal("execvp %s: %m", argv[0]); 1465 default: 1466 do { 1467 wpid = waitpid(pid, &status, 0); 1468 } while (wpid == -1 && errno == EINTR); 1469 return (wpid == -1 ? -1 : 1470 WIFEXITED(status) ? WEXITSTATUS(status) : 1); 1471 } 1472 } 1473 1474 /* word_in_list - look up command in start, stop, or control list */ 1475 1476 static int word_in_list(char *cmdlist, const char *cmd) 1477 { 1478 char *saved; 1479 char *cp; 1480 char *elem; 1481 1482 cp = saved = mystrdup(cmdlist); 1483 while ((elem = mystrtok(&cp, CHARS_COMMA_SP)) != 0 && strcmp(elem, cmd) != 0) 1484 /* void */ ; 1485 myfree(saved); 1486 return (elem != 0); 1487 } 1488 1489 /* iterate_postfix_command - execute postfix(1) command */ 1490 1491 static int iterate_postfix_command(int iter_cmd, int argc, char **argv, 1492 INST_SELECTION *selection) 1493 { 1494 int exit_status; 1495 char *cmd; 1496 ARGV *my_argv; 1497 int iter_flags; 1498 1499 /* 1500 * Override the iterator controls. 1501 */ 1502 if (word_in_list(var_multi_start_cmds, argv[0])) { 1503 iter_flags = ITER_FLAG_CHECK_DISABLED; 1504 } else if (word_in_list(var_multi_stop_cmds, argv[0])) { 1505 iter_flags = ITER_FLAG_SKIP_DISABLED | ITER_FLAG_REVERSE; 1506 } else if (word_in_list(var_multi_cntrl_cmds, argv[0])) { 1507 iter_flags = ITER_FLAG_SKIP_DISABLED; 1508 } else { 1509 iter_flags = 0; 1510 } 1511 1512 /* 1513 * Override the command line in a straightforward manner: prepend 1514 * "postfix --" to the command arguments. Other overrides (environment, 1515 * start -> check) are implemented below the iterator. 1516 */ 1517 #define POSTFIX_CMD "postfix" 1518 1519 my_argv = argv_alloc(argc + 2); 1520 cmd = concatenate(var_command_dir, "/" POSTFIX_CMD, (char *) 0); 1521 argv_add(my_argv, cmd, "--", (char *) 0); 1522 myfree(cmd); 1523 while (*argv) 1524 argv_add(my_argv, *argv++, (char *) 0); 1525 1526 /* 1527 * Execute the command for all applicable Postfix instances. 1528 */ 1529 exit_status = 1530 iterate_command(iter_cmd, iter_flags, my_argv->argv, selection); 1531 1532 argv_free(my_argv); 1533 return (exit_status); 1534 } 1535 1536 /* list_instances - list all selected instances */ 1537 1538 static void list_instances(int iter_flags, INST_SELECTION *selection) 1539 { 1540 RING *entry; 1541 INSTANCE *ip; 1542 1543 /* 1544 * Iterate over the selected instances. 1545 */ 1546 FOREACH_ITERATOR_INSTANCE(iter_flags, entry) { 1547 ip = RING_TO_INSTANCE(entry); 1548 if (match_instance_selection(ip, selection)) 1549 vstream_printf("%-15s %-15s %-9s %s\n", 1550 ip->name ? ip->name : "-", 1551 ip->gname ? ip->gname : "-", 1552 ip->enabled ? "y" : "n", 1553 ip->config_dir); 1554 } 1555 if (vstream_fflush(VSTREAM_OUT)) 1556 msg_fatal("error writing output: %m"); 1557 } 1558 1559 /* iterate_command - execute command for selected instances */ 1560 1561 static int iterate_command(int iter_cmd, int iter_flags, char **argv, 1562 INST_SELECTION *selection) 1563 { 1564 int exit_status = 0; 1565 int matched = 0; 1566 RING *entry; 1567 INSTANCE *ip; 1568 1569 /* 1570 * Iterate over the selected instances. 1571 */ 1572 FOREACH_ITERATOR_INSTANCE(iter_flags, entry) { 1573 ip = RING_TO_INSTANCE(entry); 1574 if ((iter_flags & ITER_FLAG_SKIP_DISABLED) && !ip->enabled) 1575 continue; 1576 if (!match_instance_selection(ip, selection)) 1577 continue; 1578 matched = 1; 1579 1580 /* Run the requested command */ 1581 if (run_user_command(ip, iter_cmd, iter_flags, argv) != 0) 1582 exit_status = 1; 1583 } 1584 if (matched == 0) 1585 msg_fatal("No matching instances"); 1586 1587 return (exit_status); 1588 } 1589 1590 /* iterate - Iterate over all or selected instances */ 1591 1592 static NORETURN iterate(int iter_cmd, int iter_flags, int argc, char **argv, 1593 INST_SELECTION *selection) 1594 { 1595 int exit_status; 1596 1597 /* 1598 * In iterator mode, no selection means wild-card selection. 1599 */ 1600 if (selection->type == INST_SEL_NONE) 1601 selection->type = INST_SEL_ALL; 1602 1603 /* 1604 * Load the in-memory instance table from main.cf files. 1605 */ 1606 load_all_instances(); 1607 1608 /* 1609 * Iterate over the selected instances. 1610 */ 1611 switch (iter_cmd) { 1612 case ITER_CMD_POSTFIX: 1613 exit_status = iterate_postfix_command(iter_cmd, argc, argv, selection); 1614 break; 1615 case ITER_CMD_LIST: 1616 list_instances(iter_flags, selection); 1617 exit_status = 0; 1618 break; 1619 case ITER_CMD_GENERIC: 1620 exit_status = iterate_command(iter_cmd, iter_flags, argv, selection); 1621 break; 1622 default: 1623 msg_panic("iterate: unknown mode: %d", iter_cmd); 1624 } 1625 exit(exit_status); 1626 } 1627 1628 static NORETURN usage(const char *progname) 1629 { 1630 msg_fatal("Usage:" 1631 "%s -l [-v] [-a] [-g group] [-i instance] | " 1632 "%s -p [-v] [-a] [-g group] [-i instance] command... | " 1633 "%s -x [-v] [-a] [-i name] [-g group] command... | " 1634 "%s -e action [-v] [-a] [-i name] [-g group] [-I name] " 1635 "[-G group] [param=value ...]", 1636 progname, progname, progname, progname); 1637 } 1638 1639 MAIL_VERSION_STAMP_DECLARE; 1640 1641 /* main - iterate commands over multiple instance or manage instances */ 1642 1643 int main(int argc, char **argv) 1644 { 1645 int fd; 1646 struct stat st; 1647 char *slash; 1648 char *config_dir; 1649 int ch; 1650 static const CONFIG_STR_TABLE str_table[] = { 1651 VAR_MULTI_START_CMDS, DEF_MULTI_START_CMDS, &var_multi_start_cmds, 0, 0, 1652 VAR_MULTI_STOP_CMDS, DEF_MULTI_STOP_CMDS, &var_multi_stop_cmds, 0, 0, 1653 VAR_MULTI_CNTRL_CMDS, DEF_MULTI_CNTRL_CMDS, &var_multi_cntrl_cmds, 0, 0, 1654 0, 1655 }; 1656 int instance_select_count = 0; 1657 int command_mode_count = 0; 1658 INST_SELECTION selection; 1659 NAME_ASSIGNMENT assignment; 1660 int iter_flags = ITER_FLAG_DEFAULT; 1661 int cmd_mode = 0; 1662 int code; 1663 1664 selection.type = INST_SEL_NONE; 1665 assignment.name = assignment.gname = 0; 1666 1667 /* 1668 * Fingerprint executables and core dumps. 1669 */ 1670 MAIL_VERSION_STAMP_ALLOCATE; 1671 1672 /* 1673 * Be consistent with file permissions. 1674 */ 1675 umask(022); 1676 1677 /* 1678 * To minimize confusion, make sure that the standard file descriptors 1679 * are open before opening anything else. XXX Work around for 44BSD where 1680 * fstat can return EBADF on an open file descriptor. 1681 */ 1682 for (fd = 0; fd < 3; fd++) 1683 if (fstat(fd, &st) == -1 1684 && (close(fd), open("/dev/null", O_RDWR, 0)) != fd) 1685 msg_fatal("open /dev/null: %m"); 1686 1687 /* 1688 * Set up diagnostics. XXX What if stdin is the system console during 1689 * boot time? It seems a bad idea to log startup errors to the console. 1690 * This is UNIX, a system that can run without hand holding. 1691 */ 1692 if ((slash = strrchr(argv[0], '/')) != 0 && slash[1]) 1693 argv[0] = slash + 1; 1694 if (isatty(STDERR_FILENO)) 1695 msg_vstream_init(argv[0], VSTREAM_ERR); 1696 maillog_client_init(argv[0], MAILLOG_CLIENT_FLAG_LOGWRITER_FALLBACK); 1697 1698 /* 1699 * Check the Postfix library version as soon as we enable logging. 1700 */ 1701 MAIL_VERSION_CHECK; 1702 1703 /* 1704 * Process main.cf parameters. This is done before the GETOPT() loop to 1705 * improve logging. This assumes that no command-line option can affect 1706 * parameter processing. 1707 */ 1708 mail_conf_read(); 1709 get_mail_conf_str_table(str_table); 1710 maillog_client_init(argv[0], MAILLOG_CLIENT_FLAG_LOGWRITER_FALLBACK); 1711 1712 if ((config_dir = getenv(CONF_ENV_PATH)) != 0 1713 && strcmp(config_dir, DEF_CONFIG_DIR) != 0) 1714 msg_fatal("Non-default configuration directory: %s=%s", 1715 CONF_ENV_PATH, config_dir); 1716 1717 /* 1718 * Parse switches. Move the above mail_conf_read() block after this loop, 1719 * if any command-line option can affect parameter processing. 1720 */ 1721 while ((ch = GETOPT(argc, argv, "ae:g:i:G:I:lpRvx")) > 0) { 1722 switch (ch) { 1723 default: 1724 usage(argv[0]); 1725 /* NOTREACHED */ 1726 case 'a': 1727 if (selection.type != INST_SEL_ALL) 1728 instance_select_count++; 1729 selection.type = INST_SEL_ALL; 1730 break; 1731 case 'e': 1732 if ((code = EDIT_CMD_CODE(optarg)) < 0) 1733 msg_fatal("Invalid '-e' edit action '%s'. Specify '%s', " 1734 "'%s', '%s', '%s', '%s', '%s', '%s' or '%s'", 1735 optarg, 1736 EDIT_CMD_STR(EDIT_CMD_CREATE), 1737 EDIT_CMD_STR(EDIT_CMD_DESTROY), 1738 EDIT_CMD_STR(EDIT_CMD_IMPORT), 1739 EDIT_CMD_STR(EDIT_CMD_DEPORT), 1740 EDIT_CMD_STR(EDIT_CMD_ENABLE), 1741 EDIT_CMD_STR(EDIT_CMD_DISABLE), 1742 EDIT_CMD_STR(EDIT_CMD_ASSIGN), 1743 EDIT_CMD_STR(EDIT_CMD_INIT)); 1744 if (cmd_mode != code) 1745 command_mode_count++; 1746 cmd_mode = code; 1747 break; 1748 case 'g': 1749 instance_select_count++; 1750 selection.type = INST_SEL_GROUP; 1751 selection.name = optarg; 1752 break; 1753 case 'i': 1754 instance_select_count++; 1755 selection.type = INST_SEL_NAME; 1756 selection.name = optarg; 1757 break; 1758 case 'G': 1759 if (assignment.gname != 0) 1760 msg_fatal("Specify at most one '-G' option"); 1761 assignment.gname = strcmp(optarg, "-") == 0 ? "" : optarg; 1762 break; 1763 case 'I': 1764 if (assignment.name != 0) 1765 msg_fatal("Specify at most one '-I' option"); 1766 assignment.name = strcmp(optarg, "-") == 0 ? "" : optarg; 1767 break; 1768 case 'l': 1769 if (cmd_mode != ITER_CMD_LIST) 1770 command_mode_count++; 1771 cmd_mode = ITER_CMD_LIST; 1772 break; 1773 case 'p': 1774 if (cmd_mode != ITER_CMD_POSTFIX) 1775 command_mode_count++; 1776 cmd_mode = ITER_CMD_POSTFIX; 1777 break; 1778 case 'R': 1779 iter_flags ^= ITER_FLAG_REVERSE; 1780 break; 1781 case 'v': 1782 msg_verbose++; 1783 check_setenv(CONF_ENV_VERB, ""); 1784 break; 1785 case 'x': 1786 if (cmd_mode != ITER_CMD_GENERIC) 1787 command_mode_count++; 1788 cmd_mode = ITER_CMD_GENERIC; 1789 break; 1790 } 1791 } 1792 1793 /* 1794 * Report missing arguments, or wrong arguments in the wrong context. 1795 */ 1796 if (instance_select_count > 1) 1797 msg_fatal("Specity no more than one of '-a', '-g', '-i'"); 1798 1799 if (command_mode_count != 1) 1800 msg_fatal("Specify exactly one of '-e', '-l', '-p', '-x'"); 1801 1802 if (cmd_mode == ITER_CMD_LIST && argc > optind) 1803 msg_fatal("Command not allowed with '-l'"); 1804 1805 if (cmd_mode == ITER_CMD_POSTFIX || cmd_mode == ITER_CMD_GENERIC) 1806 if (argc == optind) 1807 msg_fatal("Command required with '-p' or '-x' option"); 1808 1809 if (cmd_mode == ITER_CMD_POSTFIX || (cmd_mode & EDIT_CMD_MASK_ALL)) 1810 if (iter_flags != ITER_FLAG_DEFAULT) 1811 msg_fatal("The '-p' and '-e' options preclude the use of '-R'"); 1812 1813 if ((cmd_mode & EDIT_CMD_MASK_ASSIGN) == 0 1814 && (assignment.name || assignment.gname)) { 1815 if ((cmd_mode & EDIT_CMD_MASK_ALL) == 0) 1816 msg_fatal("Cannot assign instance name or group without '-e %s'", 1817 EDIT_CMD_STR(EDIT_CMD_ASSIGN)); 1818 else 1819 msg_fatal("Cannot assign instance name or group with '-e %s'", 1820 EDIT_CMD_STR(cmd_mode)); 1821 } 1822 if (cmd_mode & EDIT_CMD_MASK_ALL) { 1823 if (cmd_mode == EDIT_CMD_ASSIGN 1824 && (assignment.name == 0 && assignment.gname == 0)) 1825 msg_fatal("Specify new instance name or group with '-e %s'", 1826 EDIT_CMD_STR(cmd_mode)); 1827 1828 if ((cmd_mode & ~EDIT_CMD_MASK_ADD) != 0 && argc > optind) 1829 msg_fatal("Parameter overrides not valid with '-e %s'", 1830 EDIT_CMD_STR(cmd_mode)); 1831 } 1832 1833 /* 1834 * Sanity checks. 1835 */ 1836 check_shared_dir_status(); 1837 1838 /* 1839 * Iterate over selected instances, or manipulate one instance. 1840 */ 1841 if (cmd_mode & ITER_CMD_MASK_ALL) 1842 iterate(cmd_mode, iter_flags, argc - optind, argv + optind, &selection); 1843 else 1844 manage(cmd_mode, argc - optind, argv + optind, &selection, &assignment); 1845 } 1846