summaryrefslogtreecommitdiff
path: root/usr/src/cmd/sendmail
diff options
context:
space:
mode:
authorstevel@tonic-gate <none@none>2005-06-14 00:00:00 -0700
committerstevel@tonic-gate <none@none>2005-06-14 00:00:00 -0700
commit7c478bd95313f5f23a4c958a745db2134aa03244 (patch)
treec871e58545497667cbb4b0a4f2daf204743e1fe7 /usr/src/cmd/sendmail
downloadillumos-joyent-7c478bd95313f5f23a4c958a745db2134aa03244.tar.gz
OpenSolaris Launch
Diffstat (limited to 'usr/src/cmd/sendmail')
-rw-r--r--usr/src/cmd/sendmail/LICENSE81
-rw-r--r--usr/src/cmd/sendmail/Makefile66
-rw-r--r--usr/src/cmd/sendmail/Makefile.cmd30
-rw-r--r--usr/src/cmd/sendmail/READ_ME60
-rw-r--r--usr/src/cmd/sendmail/aux/Makefile130
-rw-r--r--usr/src/cmd/sendmail/aux/aliasadm.c218
-rw-r--r--usr/src/cmd/sendmail/aux/editmap.c424
-rw-r--r--usr/src/cmd/sendmail/aux/etrn.pl275
-rw-r--r--usr/src/cmd/sendmail/aux/mail.local.c1215
-rw-r--r--usr/src/cmd/sendmail/aux/mailcompat.c340
-rw-r--r--usr/src/cmd/sendmail/aux/mailq.c62
-rw-r--r--usr/src/cmd/sendmail/aux/mailstats.c370
-rw-r--r--usr/src/cmd/sendmail/aux/makemap.c530
-rw-r--r--usr/src/cmd/sendmail/aux/mconnect.c240
-rw-r--r--usr/src/cmd/sendmail/aux/nisedit.c533
-rw-r--r--usr/src/cmd/sendmail/aux/nisplus.c451
-rw-r--r--usr/src/cmd/sendmail/aux/nisplus.h124
-rw-r--r--usr/src/cmd/sendmail/aux/praliases.c399
-rw-r--r--usr/src/cmd/sendmail/aux/rfc2047.c289
-rw-r--r--usr/src/cmd/sendmail/aux/smrsh.c442
-rw-r--r--usr/src/cmd/sendmail/aux/vacation.c1048
-rw-r--r--usr/src/cmd/sendmail/cf/Makefile181
-rw-r--r--usr/src/cmd/sendmail/cf/README4186
-rw-r--r--usr/src/cmd/sendmail/cf/cf/Makefile129
-rw-r--r--usr/src/cmd/sendmail/cf/cf/sendmail.mc33
-rw-r--r--usr/src/cmd/sendmail/cf/cf/submit.mc27
-rw-r--r--usr/src/cmd/sendmail/cf/domain/generic.m428
-rw-r--r--usr/src/cmd/sendmail/cf/domain/solaris-antispam.m447
-rw-r--r--usr/src/cmd/sendmail/cf/domain/solaris-generic.m450
-rw-r--r--usr/src/cmd/sendmail/cf/feature/accept_unqualified_senders.m417
-rw-r--r--usr/src/cmd/sendmail/cf/feature/accept_unresolvable_domains.m417
-rw-r--r--usr/src/cmd/sendmail/cf/feature/access_db.m445
-rw-r--r--usr/src/cmd/sendmail/cf/feature/allmasquerade.m426
-rw-r--r--usr/src/cmd/sendmail/cf/feature/always_add_domain.m423
-rw-r--r--usr/src/cmd/sendmail/cf/feature/bestmx_is_local.m452
-rw-r--r--usr/src/cmd/sendmail/cf/feature/bitdomain.m426
-rw-r--r--usr/src/cmd/sendmail/cf/feature/blacklist_recipients.m419
-rw-r--r--usr/src/cmd/sendmail/cf/feature/compat_check.m434
-rw-r--r--usr/src/cmd/sendmail/cf/feature/conncontrol.m437
-rw-r--r--usr/src/cmd/sendmail/cf/feature/delay_checks.m426
-rw-r--r--usr/src/cmd/sendmail/cf/feature/dnsbl.m434
-rw-r--r--usr/src/cmd/sendmail/cf/feature/domaintable.m426
-rw-r--r--usr/src/cmd/sendmail/cf/feature/enhdnsbl.m445
-rw-r--r--usr/src/cmd/sendmail/cf/feature/generics_entire_domain.m416
-rw-r--r--usr/src/cmd/sendmail/cf/feature/genericstable.m426
-rw-r--r--usr/src/cmd/sendmail/cf/feature/greet_pause.m445
-rw-r--r--usr/src/cmd/sendmail/cf/feature/ldap_routing.m447
-rw-r--r--usr/src/cmd/sendmail/cf/feature/limited_masquerade.m420
-rw-r--r--usr/src/cmd/sendmail/cf/feature/local_lmtp.m429
-rw-r--r--usr/src/cmd/sendmail/cf/feature/local_no_masquerade.m420
-rw-r--r--usr/src/cmd/sendmail/cf/feature/local_procmail.m437
-rw-r--r--usr/src/cmd/sendmail/cf/feature/lookupdotdomain.m423
-rw-r--r--usr/src/cmd/sendmail/cf/feature/loose_relay_check.m417
-rw-r--r--usr/src/cmd/sendmail/cf/feature/mailertable.m426
-rw-r--r--usr/src/cmd/sendmail/cf/feature/masquerade_entire_domain.m420
-rw-r--r--usr/src/cmd/sendmail/cf/feature/masquerade_envelope.m420
-rw-r--r--usr/src/cmd/sendmail/cf/feature/msp.m479
-rw-r--r--usr/src/cmd/sendmail/cf/feature/mtamark.m434
-rw-r--r--usr/src/cmd/sendmail/cf/feature/no_default_msa.m417
-rw-r--r--usr/src/cmd/sendmail/cf/feature/nocanonify.m424
-rw-r--r--usr/src/cmd/sendmail/cf/feature/notsticky.m422
-rw-r--r--usr/src/cmd/sendmail/cf/feature/nouucp.m427
-rw-r--r--usr/src/cmd/sendmail/cf/feature/nullclient.m439
-rw-r--r--usr/src/cmd/sendmail/cf/feature/preserve_local_plus_detail.m417
-rw-r--r--usr/src/cmd/sendmail/cf/feature/preserve_luser_host.m421
-rw-r--r--usr/src/cmd/sendmail/cf/feature/promiscuous_relay.m420
-rw-r--r--usr/src/cmd/sendmail/cf/feature/queuegroup.m428
-rw-r--r--usr/src/cmd/sendmail/cf/feature/ratecontrol.m437
-rw-r--r--usr/src/cmd/sendmail/cf/feature/redirect.m426
-rw-r--r--usr/src/cmd/sendmail/cf/feature/relay_based_on_MX.m421
-rw-r--r--usr/src/cmd/sendmail/cf/feature/relay_entire_domain.m417
-rw-r--r--usr/src/cmd/sendmail/cf/feature/relay_hosts_only.m417
-rw-r--r--usr/src/cmd/sendmail/cf/feature/relay_local_from.m421
-rw-r--r--usr/src/cmd/sendmail/cf/feature/relay_mail_from.m424
-rw-r--r--usr/src/cmd/sendmail/cf/feature/smrsh.m426
-rw-r--r--usr/src/cmd/sendmail/cf/feature/stickyhost.m420
-rw-r--r--usr/src/cmd/sendmail/cf/feature/use_client_ptr.m422
-rw-r--r--usr/src/cmd/sendmail/cf/feature/use_ct_file.m425
-rw-r--r--usr/src/cmd/sendmail/cf/feature/use_cw_file.m426
-rw-r--r--usr/src/cmd/sendmail/cf/feature/uucpdomain.m426
-rw-r--r--usr/src/cmd/sendmail/cf/feature/virtuser_entire_domain.m416
-rw-r--r--usr/src/cmd/sendmail/cf/feature/virtusertable.m426
-rw-r--r--usr/src/cmd/sendmail/cf/m4/cf.m430
-rw-r--r--usr/src/cmd/sendmail/cf/m4/cfhead.m4317
-rw-r--r--usr/src/cmd/sendmail/cf/m4/proto.m42943
-rw-r--r--usr/src/cmd/sendmail/cf/m4/version.m419
-rw-r--r--usr/src/cmd/sendmail/cf/mailer/local.m494
-rw-r--r--usr/src/cmd/sendmail/cf/mailer/procmail.m435
-rw-r--r--usr/src/cmd/sendmail/cf/mailer/smtp.m4123
-rw-r--r--usr/src/cmd/sendmail/cf/mailer/uucp.m4158
-rw-r--r--usr/src/cmd/sendmail/cf/ostype/solaris2.m427
-rw-r--r--usr/src/cmd/sendmail/cf/ostype/solaris2.ml.m427
-rw-r--r--usr/src/cmd/sendmail/cf/ostype/solaris2.pre5.m427
-rw-r--r--usr/src/cmd/sendmail/cf/ostype/solaris8.m426
-rw-r--r--usr/src/cmd/sendmail/cf/sh/check-hostname.sh214
-rw-r--r--usr/src/cmd/sendmail/cf/sh/check-permissions.sh121
-rw-r--r--usr/src/cmd/sendmail/cf/sh/makeinfo.sh96
-rw-r--r--usr/src/cmd/sendmail/db/LICENSE118
-rw-r--r--usr/src/cmd/sendmail/db/Makefile142
-rw-r--r--usr/src/cmd/sendmail/db/btree/bt_compare.c195
-rw-r--r--usr/src/cmd/sendmail/db/btree/bt_conv.c94
-rw-r--r--usr/src/cmd/sendmail/db/btree/bt_curadj.c272
-rw-r--r--usr/src/cmd/sendmail/db/btree/bt_cursor.c1913
-rw-r--r--usr/src/cmd/sendmail/db/btree/bt_delete.c589
-rw-r--r--usr/src/cmd/sendmail/db/btree/bt_open.c310
-rw-r--r--usr/src/cmd/sendmail/db/btree/bt_page.c317
-rw-r--r--usr/src/cmd/sendmail/db/btree/bt_put.c831
-rw-r--r--usr/src/cmd/sendmail/db/btree/bt_rec.c903
-rw-r--r--usr/src/cmd/sendmail/db/btree/bt_recno.c1356
-rw-r--r--usr/src/cmd/sendmail/db/btree/bt_rsearch.c391
-rw-r--r--usr/src/cmd/sendmail/db/btree/bt_search.c368
-rw-r--r--usr/src/cmd/sendmail/db/btree/bt_split.c978
-rw-r--r--usr/src/cmd/sendmail/db/btree/bt_stat.c198
-rw-r--r--usr/src/cmd/sendmail/db/btree/btree_auto.c1508
-rw-r--r--usr/src/cmd/sendmail/db/clib/strsep.c100
-rw-r--r--usr/src/cmd/sendmail/db/config.h179
-rw-r--r--usr/src/cmd/sendmail/db/db.h1050
-rw-r--r--usr/src/cmd/sendmail/db/db/db.c785
-rw-r--r--usr/src/cmd/sendmail/db/db/db_am.c432
-rw-r--r--usr/src/cmd/sendmail/db/db/db_appinit.c734
-rw-r--r--usr/src/cmd/sendmail/db/db/db_apprec.c245
-rw-r--r--usr/src/cmd/sendmail/db/db/db_auto.c1357
-rw-r--r--usr/src/cmd/sendmail/db/db/db_byteorder.c70
-rw-r--r--usr/src/cmd/sendmail/db/db/db_conv.c255
-rw-r--r--usr/src/cmd/sendmail/db/db/db_dispatch.c317
-rw-r--r--usr/src/cmd/sendmail/db/db/db_dup.c947
-rw-r--r--usr/src/cmd/sendmail/db/db/db_err.c211
-rw-r--r--usr/src/cmd/sendmail/db/db/db_iface.c490
-rw-r--r--usr/src/cmd/sendmail/db/db/db_join.c273
-rw-r--r--usr/src/cmd/sendmail/db/db/db_log2.c69
-rw-r--r--usr/src/cmd/sendmail/db/db/db_overflow.c407
-rw-r--r--usr/src/cmd/sendmail/db/db/db_pr.c831
-rw-r--r--usr/src/cmd/sendmail/db/db/db_rec.c611
-rw-r--r--usr/src/cmd/sendmail/db/db/db_region.c870
-rw-r--r--usr/src/cmd/sendmail/db/db/db_ret.c153
-rw-r--r--usr/src/cmd/sendmail/db/db/db_salloc.c301
-rw-r--r--usr/src/cmd/sendmail/db/db/db_shash.c126
-rw-r--r--usr/src/cmd/sendmail/db/db_int.h409
-rw-r--r--usr/src/cmd/sendmail/db/dbm/dbm.c485
-rw-r--r--usr/src/cmd/sendmail/db/hash/hash.c1336
-rw-r--r--usr/src/cmd/sendmail/db/hash/hash_auto.c1554
-rw-r--r--usr/src/cmd/sendmail/db/hash/hash_conv.c117
-rw-r--r--usr/src/cmd/sendmail/db/hash/hash_dup.c656
-rw-r--r--usr/src/cmd/sendmail/db/hash/hash_func.c226
-rw-r--r--usr/src/cmd/sendmail/db/hash/hash_page.c1929
-rw-r--r--usr/src/cmd/sendmail/db/hash/hash_rec.c977
-rw-r--r--usr/src/cmd/sendmail/db/hash/hash_stat.c44
-rw-r--r--usr/src/cmd/sendmail/db/hsearch/hsearch.c142
-rw-r--r--usr/src/cmd/sendmail/db/include/btree.h263
-rw-r--r--usr/src/cmd/sendmail/db/include/btree_auto.h135
-rw-r--r--usr/src/cmd/sendmail/db/include/btree_ext.h132
-rw-r--r--usr/src/cmd/sendmail/db/include/clib_ext.h59
-rw-r--r--usr/src/cmd/sendmail/db/include/common_ext.h34
-rw-r--r--usr/src/cmd/sendmail/db/include/db_am.h86
-rw-r--r--usr/src/cmd/sendmail/db/include/db_auto.h111
-rw-r--r--usr/src/cmd/sendmail/db/include/db_dispatch.h77
-rw-r--r--usr/src/cmd/sendmail/db/include/db_ext.h132
-rw-r--r--usr/src/cmd/sendmail/db/include/db_join.h25
-rw-r--r--usr/src/cmd/sendmail/db/include/db_page.h512
-rw-r--r--usr/src/cmd/sendmail/db/include/db_shash.h106
-rw-r--r--usr/src/cmd/sendmail/db/include/db_swap.h105
-rw-r--r--usr/src/cmd/sendmail/db/include/hash.h199
-rw-r--r--usr/src/cmd/sendmail/db/include/hash_auto.h140
-rw-r--r--usr/src/cmd/sendmail/db/include/hash_ext.h130
-rw-r--r--usr/src/cmd/sendmail/db/include/lock.h197
-rw-r--r--usr/src/cmd/sendmail/db/include/lock_ext.h20
-rw-r--r--usr/src/cmd/sendmail/db/include/log.h205
-rw-r--r--usr/src/cmd/sendmail/db/include/log_auto.h26
-rw-r--r--usr/src/cmd/sendmail/db/include/log_ext.h26
-rw-r--r--usr/src/cmd/sendmail/db/include/mp.h299
-rw-r--r--usr/src/cmd/sendmail/db/include/mp_ext.h21
-rw-r--r--usr/src/cmd/sendmail/db/include/mutex_ext.h7
-rw-r--r--usr/src/cmd/sendmail/db/include/os.h26
-rw-r--r--usr/src/cmd/sendmail/db/include/os_ext.h40
-rw-r--r--usr/src/cmd/sendmail/db/include/os_jump.h42
-rw-r--r--usr/src/cmd/sendmail/db/include/queue.h282
-rw-r--r--usr/src/cmd/sendmail/db/include/shqueue.h342
-rw-r--r--usr/src/cmd/sendmail/db/include/txn.h148
-rw-r--r--usr/src/cmd/sendmail/db/include/txn_auto.h51
-rw-r--r--usr/src/cmd/sendmail/db/include/txn_ext.h41
-rw-r--r--usr/src/cmd/sendmail/db/include/xa.h182
-rw-r--r--usr/src/cmd/sendmail/db/include/xa_ext.h15
-rw-r--r--usr/src/cmd/sendmail/db/lock/lock.c1034
-rw-r--r--usr/src/cmd/sendmail/db/lock/lock_conflict.c39
-rw-r--r--usr/src/cmd/sendmail/db/lock/lock_deadlock.c502
-rw-r--r--usr/src/cmd/sendmail/db/lock/lock_region.c745
-rw-r--r--usr/src/cmd/sendmail/db/lock/lock_util.c152
-rw-r--r--usr/src/cmd/sendmail/db/log/log.c546
-rw-r--r--usr/src/cmd/sendmail/db/log/log_archive.c417
-rw-r--r--usr/src/cmd/sendmail/db/log/log_auto.c231
-rw-r--r--usr/src/cmd/sendmail/db/log/log_compare.c42
-rw-r--r--usr/src/cmd/sendmail/db/log/log_findckp.c140
-rw-r--r--usr/src/cmd/sendmail/db/log/log_get.c329
-rw-r--r--usr/src/cmd/sendmail/db/log/log_put.c602
-rw-r--r--usr/src/cmd/sendmail/db/log/log_rec.c446
-rw-r--r--usr/src/cmd/sendmail/db/log/log_register.c208
-rw-r--r--usr/src/cmd/sendmail/db/mp/mp_bh.c590
-rw-r--r--usr/src/cmd/sendmail/db/mp/mp_fget.c351
-rw-r--r--usr/src/cmd/sendmail/db/mp/mp_fopen.c560
-rw-r--r--usr/src/cmd/sendmail/db/mp/mp_fput.c151
-rw-r--r--usr/src/cmd/sendmail/db/mp/mp_fset.c83
-rw-r--r--usr/src/cmd/sendmail/db/mp/mp_open.c221
-rw-r--r--usr/src/cmd/sendmail/db/mp/mp_pr.c304
-rw-r--r--usr/src/cmd/sendmail/db/mp/mp_region.c331
-rw-r--r--usr/src/cmd/sendmail/db/mp/mp_sync.c551
-rw-r--r--usr/src/cmd/sendmail/db/mutex/mutex.c314
-rw-r--r--usr/src/cmd/sendmail/db/os/os_abs.c31
-rw-r--r--usr/src/cmd/sendmail/db/os/os_alloc.c219
-rw-r--r--usr/src/cmd/sendmail/db/os/os_config.c153
-rw-r--r--usr/src/cmd/sendmail/db/os/os_dir.c101
-rw-r--r--usr/src/cmd/sendmail/db/os/os_fid.c76
-rw-r--r--usr/src/cmd/sendmail/db/os/os_fsync.c59
-rw-r--r--usr/src/cmd/sendmail/db/os/os_map.c447
-rw-r--r--usr/src/cmd/sendmail/db/os/os_oflags.c94
-rw-r--r--usr/src/cmd/sendmail/db/os/os_open.c152
-rw-r--r--usr/src/cmd/sendmail/db/os/os_rpath.c49
-rw-r--r--usr/src/cmd/sendmail/db/os/os_rw.c136
-rw-r--r--usr/src/cmd/sendmail/db/os/os_seek.c52
-rw-r--r--usr/src/cmd/sendmail/db/os/os_sleep.c59
-rw-r--r--usr/src/cmd/sendmail/db/os/os_spin.c107
-rw-r--r--usr/src/cmd/sendmail/db/os/os_stat.c99
-rw-r--r--usr/src/cmd/sendmail/db/os/os_tmpdir.c115
-rw-r--r--usr/src/cmd/sendmail/db/os/os_unlink.c39
-rw-r--r--usr/src/cmd/sendmail/db/txn/txn.c1046
-rw-r--r--usr/src/cmd/sendmail/db/txn/txn_auto.c621
-rw-r--r--usr/src/cmd/sendmail/db/txn/txn_rec.c296
-rw-r--r--usr/src/cmd/sendmail/db/xa/xa.c681
-rw-r--r--usr/src/cmd/sendmail/db/xa/xa_db.c308
-rw-r--r--usr/src/cmd/sendmail/db/xa/xa_map.c307
-rw-r--r--usr/src/cmd/sendmail/include/libmilter/mfapi.h497
-rw-r--r--usr/src/cmd/sendmail/include/libmilter/mfdef.h105
-rw-r--r--usr/src/cmd/sendmail/include/libmilter/milter.h57
-rw-r--r--usr/src/cmd/sendmail/include/libsmdb/smdb.h362
-rw-r--r--usr/src/cmd/sendmail/include/sendmail/mailstats.h41
-rw-r--r--usr/src/cmd/sendmail/include/sendmail/pathnames.h64
-rw-r--r--usr/src/cmd/sendmail/include/sendmail/sendmail.h137
-rw-r--r--usr/src/cmd/sendmail/include/sm/assert.h115
-rw-r--r--usr/src/cmd/sendmail/include/sm/bdb.h49
-rw-r--r--usr/src/cmd/sendmail/include/sm/bitops.h59
-rw-r--r--usr/src/cmd/sendmail/include/sm/cdefs.h149
-rw-r--r--usr/src/cmd/sendmail/include/sm/cf.h31
-rw-r--r--usr/src/cmd/sendmail/include/sm/clock.h82
-rw-r--r--usr/src/cmd/sendmail/include/sm/conf.h2967
-rw-r--r--usr/src/cmd/sendmail/include/sm/config.h189
-rw-r--r--usr/src/cmd/sendmail/include/sm/debug.h137
-rw-r--r--usr/src/cmd/sendmail/include/sm/errstring.h84
-rw-r--r--usr/src/cmd/sendmail/include/sm/exc.h188
-rw-r--r--usr/src/cmd/sendmail/include/sm/fdset.h27
-rw-r--r--usr/src/cmd/sendmail/include/sm/gen.h82
-rw-r--r--usr/src/cmd/sendmail/include/sm/heap.h103
-rw-r--r--usr/src/cmd/sendmail/include/sm/io.h391
-rw-r--r--usr/src/cmd/sendmail/include/sm/ldap.h129
-rw-r--r--usr/src/cmd/sendmail/include/sm/limits.h57
-rw-r--r--usr/src/cmd/sendmail/include/sm/mbdb.h45
-rw-r--r--usr/src/cmd/sendmail/include/sm/os/sm_os_sunos.h72
-rw-r--r--usr/src/cmd/sendmail/include/sm/path.h34
-rw-r--r--usr/src/cmd/sendmail/include/sm/rpool.h191
-rw-r--r--usr/src/cmd/sendmail/include/sm/sem.h61
-rw-r--r--usr/src/cmd/sendmail/include/sm/setjmp.h48
-rw-r--r--usr/src/cmd/sendmail/include/sm/shm.h45
-rw-r--r--usr/src/cmd/sendmail/include/sm/signal.h83
-rw-r--r--usr/src/cmd/sendmail/include/sm/string.h115
-rw-r--r--usr/src/cmd/sendmail/include/sm/sysexits.h111
-rw-r--r--usr/src/cmd/sendmail/include/sm/test.h48
-rw-r--r--usr/src/cmd/sendmail/include/sm/types.h67
-rw-r--r--usr/src/cmd/sendmail/include/sm/varargs.h47
-rw-r--r--usr/src/cmd/sendmail/include/sm/xtrap.h40
-rw-r--r--usr/src/cmd/sendmail/lib/Makefile78
-rw-r--r--usr/src/cmd/sendmail/lib/aliases77
-rw-r--r--usr/src/cmd/sendmail/lib/helpfile128
-rw-r--r--usr/src/cmd/sendmail/lib/local-host-names0
-rw-r--r--usr/src/cmd/sendmail/lib/smtp-sendmail151
-rw-r--r--usr/src/cmd/sendmail/lib/smtp-sendmail.xml177
-rw-r--r--usr/src/cmd/sendmail/lib/trusted-users1
-rw-r--r--usr/src/cmd/sendmail/libmilter/Makefile60
-rw-r--r--usr/src/cmd/sendmail/libmilter/Makefile.com66
-rw-r--r--usr/src/cmd/sendmail/libmilter/README420
-rw-r--r--usr/src/cmd/sendmail/libmilter/comm.c362
-rw-r--r--usr/src/cmd/sendmail/libmilter/engine.c1252
-rw-r--r--usr/src/cmd/sendmail/libmilter/handler.c68
-rw-r--r--usr/src/cmd/sendmail/libmilter/i386/Makefile29
-rw-r--r--usr/src/cmd/sendmail/libmilter/libmilter.h189
-rw-r--r--usr/src/cmd/sendmail/libmilter/listener.c955
-rw-r--r--usr/src/cmd/sendmail/libmilter/llib-lmilter32
-rw-r--r--usr/src/cmd/sendmail/libmilter/main.c245
-rw-r--r--usr/src/cmd/sendmail/libmilter/signal.c223
-rw-r--r--usr/src/cmd/sendmail/libmilter/sm_gethost.c150
-rw-r--r--usr/src/cmd/sendmail/libmilter/smfi.c650
-rw-r--r--usr/src/cmd/sendmail/libmilter/sparc/Makefile29
-rw-r--r--usr/src/cmd/sendmail/libsm/Makefile83
-rw-r--r--usr/src/cmd/sendmail/libsm/assert.c189
-rw-r--r--usr/src/cmd/sendmail/libsm/cf.c102
-rw-r--r--usr/src/cmd/sendmail/libsm/clock.c642
-rw-r--r--usr/src/cmd/sendmail/libsm/clrerr.c41
-rw-r--r--usr/src/cmd/sendmail/libsm/config.c253
-rw-r--r--usr/src/cmd/sendmail/libsm/debug.c396
-rw-r--r--usr/src/cmd/sendmail/libsm/errstring.c285
-rw-r--r--usr/src/cmd/sendmail/libsm/exc.c671
-rw-r--r--usr/src/cmd/sendmail/libsm/fclose.c151
-rw-r--r--usr/src/cmd/sendmail/libsm/feof.c44
-rw-r--r--usr/src/cmd/sendmail/libsm/ferror.c43
-rw-r--r--usr/src/cmd/sendmail/libsm/fflush.c153
-rw-r--r--usr/src/cmd/sendmail/libsm/fget.c112
-rw-r--r--usr/src/cmd/sendmail/libsm/findfp.c427
-rw-r--r--usr/src/cmd/sendmail/libsm/flags.c65
-rw-r--r--usr/src/cmd/sendmail/libsm/fopen.c376
-rw-r--r--usr/src/cmd/sendmail/libsm/fpos.c155
-rw-r--r--usr/src/cmd/sendmail/libsm/fprintf.c57
-rw-r--r--usr/src/cmd/sendmail/libsm/fpurge.c55
-rw-r--r--usr/src/cmd/sendmail/libsm/fput.c54
-rw-r--r--usr/src/cmd/sendmail/libsm/fread.c102
-rw-r--r--usr/src/cmd/sendmail/libsm/fscanf.c57
-rw-r--r--usr/src/cmd/sendmail/libsm/fseek.c338
-rw-r--r--usr/src/cmd/sendmail/libsm/fvwrite.c281
-rw-r--r--usr/src/cmd/sendmail/libsm/fvwrite.h32
-rw-r--r--usr/src/cmd/sendmail/libsm/fwalk.c63
-rw-r--r--usr/src/cmd/sendmail/libsm/fwrite.c69
-rw-r--r--usr/src/cmd/sendmail/libsm/get.c48
-rw-r--r--usr/src/cmd/sendmail/libsm/glue.h29
-rw-r--r--usr/src/cmd/sendmail/libsm/heap.c822
-rw-r--r--usr/src/cmd/sendmail/libsm/ldap.c1334
-rw-r--r--usr/src/cmd/sendmail/libsm/local.h330
-rw-r--r--usr/src/cmd/sendmail/libsm/makebuf.c156
-rw-r--r--usr/src/cmd/sendmail/libsm/match.c139
-rw-r--r--usr/src/cmd/sendmail/libsm/mbdb.c776
-rw-r--r--usr/src/cmd/sendmail/libsm/niprop.c215
-rw-r--r--usr/src/cmd/sendmail/libsm/path.c17
-rw-r--r--usr/src/cmd/sendmail/libsm/put.c82
-rw-r--r--usr/src/cmd/sendmail/libsm/refill.c301
-rw-r--r--usr/src/cmd/sendmail/libsm/rewind.c46
-rw-r--r--usr/src/cmd/sendmail/libsm/rpool.c526
-rw-r--r--usr/src/cmd/sendmail/libsm/sem.c203
-rw-r--r--usr/src/cmd/sendmail/libsm/setvbuf.c192
-rw-r--r--usr/src/cmd/sendmail/libsm/shm.c143
-rw-r--r--usr/src/cmd/sendmail/libsm/signal.c342
-rw-r--r--usr/src/cmd/sendmail/libsm/sm_os.h8
-rw-r--r--usr/src/cmd/sendmail/libsm/smstdio.c365
-rw-r--r--usr/src/cmd/sendmail/libsm/snprintf.c88
-rw-r--r--usr/src/cmd/sendmail/libsm/sscanf.c104
-rw-r--r--usr/src/cmd/sendmail/libsm/stdio.c521
-rw-r--r--usr/src/cmd/sendmail/libsm/strcasecmp.c108
-rw-r--r--usr/src/cmd/sendmail/libsm/strdup.c168
-rw-r--r--usr/src/cmd/sendmail/libsm/strerror.c62
-rw-r--r--usr/src/cmd/sendmail/libsm/strexit.c129
-rw-r--r--usr/src/cmd/sendmail/libsm/string.c58
-rw-r--r--usr/src/cmd/sendmail/libsm/stringf.c88
-rw-r--r--usr/src/cmd/sendmail/libsm/strio.c492
-rw-r--r--usr/src/cmd/sendmail/libsm/strl.c326
-rw-r--r--usr/src/cmd/sendmail/libsm/strrevcmp.c103
-rw-r--r--usr/src/cmd/sendmail/libsm/strto.c256
-rw-r--r--usr/src/cmd/sendmail/libsm/t-event.c90
-rw-r--r--usr/src/cmd/sendmail/libsm/t-exc.c147
-rw-r--r--usr/src/cmd/sendmail/libsm/t-float.c74
-rw-r--r--usr/src/cmd/sendmail/libsm/t-fopen.c40
-rw-r--r--usr/src/cmd/sendmail/libsm/t-heap.c66
-rw-r--r--usr/src/cmd/sendmail/libsm/t-match.c49
-rw-r--r--usr/src/cmd/sendmail/libsm/t-path.c37
-rw-r--r--usr/src/cmd/sendmail/libsm/t-rpool.c71
-rw-r--r--usr/src/cmd/sendmail/libsm/t-scanf.c61
-rw-r--r--usr/src/cmd/sendmail/libsm/t-sem.c346
-rw-r--r--usr/src/cmd/sendmail/libsm/t-shm.c278
-rw-r--r--usr/src/cmd/sendmail/libsm/t-smstdio.c77
-rw-r--r--usr/src/cmd/sendmail/libsm/t-string.c48
-rw-r--r--usr/src/cmd/sendmail/libsm/t-strio.c35
-rw-r--r--usr/src/cmd/sendmail/libsm/t-strl.c138
-rw-r--r--usr/src/cmd/sendmail/libsm/t-strrevcmp.c55
-rw-r--r--usr/src/cmd/sendmail/libsm/test.c157
-rw-r--r--usr/src/cmd/sendmail/libsm/ungetc.c183
-rw-r--r--usr/src/cmd/sendmail/libsm/vasprintf.c106
-rw-r--r--usr/src/cmd/sendmail/libsm/vfprintf.c1110
-rw-r--r--usr/src/cmd/sendmail/libsm/vfscanf.c877
-rw-r--r--usr/src/cmd/sendmail/libsm/vprintf.c41
-rw-r--r--usr/src/cmd/sendmail/libsm/vsnprintf.c80
-rw-r--r--usr/src/cmd/sendmail/libsm/wbuf.c90
-rw-r--r--usr/src/cmd/sendmail/libsm/wsetup.c86
-rw-r--r--usr/src/cmd/sendmail/libsm/xtrap.c23
-rw-r--r--usr/src/cmd/sendmail/libsmdb/Makefile64
-rw-r--r--usr/src/cmd/sendmail/libsmdb/smdb.c551
-rw-r--r--usr/src/cmd/sendmail/libsmdb/smdb1.c577
-rw-r--r--usr/src/cmd/sendmail/libsmdb/smdb2.c700
-rw-r--r--usr/src/cmd/sendmail/libsmdb/smndbm.c629
-rw-r--r--usr/src/cmd/sendmail/libsmutil/Makefile63
-rw-r--r--usr/src/cmd/sendmail/libsmutil/cf.c78
-rw-r--r--usr/src/cmd/sendmail/libsmutil/debug.c17
-rw-r--r--usr/src/cmd/sendmail/libsmutil/err.c65
-rw-r--r--usr/src/cmd/sendmail/libsmutil/lockfile.c84
-rw-r--r--usr/src/cmd/sendmail/libsmutil/safefile.c985
-rw-r--r--usr/src/cmd/sendmail/libsmutil/snprintf.c62
-rw-r--r--usr/src/cmd/sendmail/src/Makefile104
-rw-r--r--usr/src/cmd/sendmail/src/READ_ME92
-rw-r--r--usr/src/cmd/sendmail/src/alias.c1016
-rw-r--r--usr/src/cmd/sendmail/src/arpadate.c205
-rw-r--r--usr/src/cmd/sendmail/src/bf.c863
-rw-r--r--usr/src/cmd/sendmail/src/bf.h34
-rw-r--r--usr/src/cmd/sendmail/src/collect.c1124
-rw-r--r--usr/src/cmd/sendmail/src/conf.c6323
-rw-r--r--usr/src/cmd/sendmail/src/conf.h224
-rw-r--r--usr/src/cmd/sendmail/src/control.c439
-rw-r--r--usr/src/cmd/sendmail/src/convtime.c204
-rw-r--r--usr/src/cmd/sendmail/src/daemon.c4459
-rw-r--r--usr/src/cmd/sendmail/src/deliver.c6283
-rw-r--r--usr/src/cmd/sendmail/src/domain.c1171
-rw-r--r--usr/src/cmd/sendmail/src/envelope.c1259
-rw-r--r--usr/src/cmd/sendmail/src/err.c1163
-rw-r--r--usr/src/cmd/sendmail/src/headers.c2166
-rw-r--r--usr/src/cmd/sendmail/src/macro.c600
-rw-r--r--usr/src/cmd/sendmail/src/main.c4400
-rw-r--r--usr/src/cmd/sendmail/src/map.c7781
-rw-r--r--usr/src/cmd/sendmail/src/mci.c1554
-rw-r--r--usr/src/cmd/sendmail/src/milter.c4146
-rw-r--r--usr/src/cmd/sendmail/src/mime.c1259
-rw-r--r--usr/src/cmd/sendmail/src/parseaddr.c3237
-rw-r--r--usr/src/cmd/sendmail/src/queue.c8818
-rw-r--r--usr/src/cmd/sendmail/src/ratectrl.c536
-rw-r--r--usr/src/cmd/sendmail/src/readcf.c4460
-rw-r--r--usr/src/cmd/sendmail/src/recipient.c2044
-rw-r--r--usr/src/cmd/sendmail/src/savemail.c1692
-rw-r--r--usr/src/cmd/sendmail/src/sendmail.h2609
-rw-r--r--usr/src/cmd/sendmail/src/sfsasl.c799
-rw-r--r--usr/src/cmd/sendmail/src/sfsasl.h25
-rw-r--r--usr/src/cmd/sendmail/src/sm_os.h8
-rw-r--r--usr/src/cmd/sendmail/src/sm_resolve.c452
-rw-r--r--usr/src/cmd/sendmail/src/sm_resolve.h144
-rw-r--r--usr/src/cmd/sendmail/src/srvrsmtp.c4688
-rw-r--r--usr/src/cmd/sendmail/src/stab.c477
-rw-r--r--usr/src/cmd/sendmail/src/stats.c200
-rw-r--r--usr/src/cmd/sendmail/src/statusd_shm.h46
-rw-r--r--usr/src/cmd/sendmail/src/sun_compat.c92
-rw-r--r--usr/src/cmd/sendmail/src/sysexits.c169
-rw-r--r--usr/src/cmd/sendmail/src/sysexits.h97
-rw-r--r--usr/src/cmd/sendmail/src/timers.h35
-rw-r--r--usr/src/cmd/sendmail/src/tls.c1613
-rw-r--r--usr/src/cmd/sendmail/src/trace.c226
-rw-r--r--usr/src/cmd/sendmail/src/udb.c1314
-rw-r--r--usr/src/cmd/sendmail/src/usersmtp.c3356
-rw-r--r--usr/src/cmd/sendmail/src/util.c2882
-rw-r--r--usr/src/cmd/sendmail/src/version.c20
437 files changed, 186711 insertions, 0 deletions
diff --git a/usr/src/cmd/sendmail/LICENSE b/usr/src/cmd/sendmail/LICENSE
new file mode 100644
index 0000000000..3f7cbdf64b
--- /dev/null
+++ b/usr/src/cmd/sendmail/LICENSE
@@ -0,0 +1,81 @@
+# ident "%Z%%M% %I% %E% SMI"
+
+ SENDMAIL LICENSE
+
+The following license terms and conditions apply, unless a different
+license is obtained from Sendmail, Inc., 6425 Christie Ave, Fourth Floor,
+Emeryville, CA 94608, USA, or by electronic mail at license@sendmail.com.
+
+License Terms:
+
+Use, Modification and Redistribution (including distribution of any
+modified or derived work) in source and binary forms is permitted only if
+each of the following conditions is met:
+
+1. Redistributions qualify as "freeware" or "Open Source Software" under
+ one of the following terms:
+
+ (a) Redistributions are made at no charge beyond the reasonable cost of
+ materials and delivery.
+
+ (b) Redistributions are accompanied by a copy of the Source Code or by an
+ irrevocable offer to provide a copy of the Source Code for up to three
+ years at the cost of materials and delivery. Such redistributions
+ must allow further use, modification, and redistribution of the Source
+ Code under substantially the same terms as this license. For the
+ purposes of redistribution "Source Code" means the complete compilable
+ and linkable source code of sendmail including all modifications.
+
+2. Redistributions of source code must retain the copyright notices as they
+ appear in each source code file, these license terms, and the
+ disclaimer/limitation of liability set forth as paragraph 6 below.
+
+3. Redistributions in binary form must reproduce the Copyright Notice,
+ these license terms, and the disclaimer/limitation of liability set
+ forth as paragraph 6 below, in the documentation and/or other materials
+ provided with the distribution. For the purposes of binary distribution
+ the "Copyright Notice" refers to the following language:
+ "Copyright (c) 1998-2004 Sendmail, Inc. All rights reserved."
+
+4. Neither the name of Sendmail, Inc. nor the University of California nor
+ the names of their contributors may be used to endorse or promote
+ products derived from this software without specific prior written
+ permission. The name "sendmail" is a trademark of Sendmail, Inc.
+
+5. All redistributions must comply with the conditions imposed by the
+ University of California on certain embedded code, whose copyright
+ notice and conditions for redistribution are as follows:
+
+ (a) Copyright (c) 1988, 1993 The Regents of the University of
+ California. All rights reserved.
+
+ (b) Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ (i) Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ (ii) Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+
+ (iii) Neither the name of the University nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+6. Disclaimer/Limitation of Liability: THIS SOFTWARE IS PROVIDED BY
+ SENDMAIL, INC. AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
+ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+ NO EVENT SHALL SENDMAIL, INC., THE REGENTS OF THE UNIVERSITY OF
+ CALIFORNIA OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+$Revision: 8.13 $, Last updated $Date: 2004/05/11 23:57:57 $
diff --git a/usr/src/cmd/sendmail/Makefile b/usr/src/cmd/sendmail/Makefile
new file mode 100644
index 0000000000..dc60aae67e
--- /dev/null
+++ b/usr/src/cmd/sendmail/Makefile
@@ -0,0 +1,66 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License, Version 1.0 only
+# (the "License"). You may not use this file except in compliance
+# with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+#
+#ident "%Z%%M% %I% %E% SMI"
+#
+# Copyright 2004 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# cmd/sendmail/Makefile
+#
+# Makefile for top level sendmail source directory
+#
+
+# static libraries (db, libsm, libsmdb, libsmutil) must be built before
+# src and aux
+SUBDIRS= cf lib db libsm libsmdb libsmutil .WAIT src aux
+
+include ../Makefile.cmd
+
+ROOTDIRS= $(ROOT)/var/spool/mqueue $(ROOT)/var/spool/clientmqueue
+$(ROOTDIRS):= OWNER = root
+$(ROOTDIRS):= DIRMODE = 0750
+$(ROOT)/var/spool/clientmqueue:= OWNER = smmsp
+$(ROOT)/var/spool/clientmqueue:= GROUP = smmsp
+$(ROOT)/var/spool/clientmqueue:= DIRMODE = 0770
+
+all:= TARGET= all
+install:= TARGET= install
+clean:= TARGET= clean
+clobber:= TARGET= clobber
+lint:= TARGET= lint
+
+.KEEP_STATE:
+
+all clean clobber lint: $(SUBDIRS)
+
+install: $(ROOTDIRS) $(SUBDIRS) $(ROOTMANIFEST) $(ROOTSVCSH)
+
+.PARALLEL: $(SUBDIRS)
+
+$(ROOTDIRS):
+ $(INS.dir)
+
+$(SUBDIRS): FRC
+ @cd $@; pwd; $(MAKE) $(TARGET)
+
+FRC:
diff --git a/usr/src/cmd/sendmail/Makefile.cmd b/usr/src/cmd/sendmail/Makefile.cmd
new file mode 100644
index 0000000000..f924656d19
--- /dev/null
+++ b/usr/src/cmd/sendmail/Makefile.cmd
@@ -0,0 +1,30 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License, Version 1.0 only
+# (the "License"). You may not use this file except in compliance
+# with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+#
+#ident "%Z%%M% %I% %E% SMI"
+#
+# Copyright 2005 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+
+RLS_DEF= -DSOLARIS=21100
+DBMDEF= -DNDBM -DNEWDB -DNIS -DNISPLUS -DUSERDB -DMAP_REGEX -DLDAPMAP
diff --git a/usr/src/cmd/sendmail/READ_ME b/usr/src/cmd/sendmail/READ_ME
new file mode 100644
index 0000000000..3ca31ed10c
--- /dev/null
+++ b/usr/src/cmd/sendmail/READ_ME
@@ -0,0 +1,60 @@
+#
+# Copyright 2005 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License, Version 1.0 only
+# (the "License"). You may not use this file except in compliance
+# with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+#
+#ident "%Z%%M% %I% %E% SMI"; from UCB 4.1 7/25/83
+
+ S E N D M A I L
+
+The first thing you should look at is the documentation. The two
+critical documents are "Sendmail - An Internetwork Router" and
+"Sendmail - Installation and Operation Guide". Read both of these
+before proceeding with your installation. If you have read these
+before, you should still read through the second one again anyhow,
+since the installation procedures change occasionally.
+
+A brief tour:
+ Makefile A Makefile that will install Sendmail on Solaris.
+ Makefile.cmd Some Makefile variable definitions included by subdirs.
+ READ_ME This file.
+ aux Useful programs and shell scripts for debugging and
+ administering sendmail and the network mail environment.
+ Also, some C source files for peculiar configurations.
+ db The Berkeley Database, built for linking with aux/makemap
+ and src/sendmail .
+ cf The sources and "object" for the configuration files.
+ include Various headers files.
+ lib Master copies of files used by sendmail, e.g. the aliases
+ file.
+ libsm Sendmail general library.
+ libsmdb Sendmail database library; for use with makemap and
+ praliases
+ libsmutil Sendmail utility library; various general-purpose routines
+ needed by sendmail and its auxiliary programs.
+ src The source for the sendmail program itself.
+
+Sendmail is fully supported by Sun Microsystems, like any other part of
+Solaris. Contact Sun technical support if you have problems.
+
+Sendmail was originally written by Eric Allman of Sendmail, Inc.
diff --git a/usr/src/cmd/sendmail/aux/Makefile b/usr/src/cmd/sendmail/aux/Makefile
new file mode 100644
index 0000000000..58699b1dfb
--- /dev/null
+++ b/usr/src/cmd/sendmail/aux/Makefile
@@ -0,0 +1,130 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License, Version 1.0 only
+# (the "License"). You may not use this file except in compliance
+# with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+#
+#ident "%Z%%M% %I% %E% SMI"
+#
+# Copyright 1990 - 2002 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# cmd/sendmail/aux/Makefile
+#
+include ../../Makefile.cmd
+include ../Makefile.cmd
+
+PROG= mailstats mconnect vacation mailcompat aliasadm praliases mailq
+
+LIBPROG= mail.local smrsh
+
+USRSBINPROG= etrn makemap editmap
+
+# $(PROG) by default
+CLOBBERFILES= $(LIBPROG) $(USRSBINPROG)
+
+OBJS= $(PROG:%=%.o) nisplus.o nisedit.o
+
+.PARALLEL: $(OBJS) $(PROG) $(LIBPROG) $(USRSBINPROG)
+
+SRCS= $(PROG:%=%.c) nisplus.c nisedit.c
+
+aliasadm := LDLIBS += -lnsl
+editmap := LDLIBS += -lldap
+mail.local := LDLIBS += -lsocket -lnsl -lmail -lldap
+mailq := LDLIBS += -lsecdb
+mailstats := LDLIBS += -lldap
+makemap := LDLIBS += -lldap
+mconnect := LDLIBS += -lsocket -lnsl
+praliases := LDLIBS += -lldap
+smrsh := LDLIBS += -lldap
+vacation := LDLIBS += -lldap
+$(ROOTBIN)/mailq := FILEMODE = 4555
+
+INCPATH= -I../src -I../db -I../include
+
+ENVDEF= $(RLS_DEF) -DNOT_SENDMAIL
+SUNENVDEF= -DSUN_EXTENSIONS -DUSE_VENDOR_CF_PATH
+DBMDEF= -DNDBM -DNEWDB
+
+CPPFLAGS = $(INCPATH) $(ENVDEF) $(SUNENVDEF) $(DBMDEF) $(CPPFLAGS.master)
+
+.KEEP_STATE:
+
+all: $(PROG) $(LIBPROG) $(USRSBINPROG)
+
+convtime.o: ../src/convtime.c
+ $(COMPILE.c) ../src/convtime.c
+ $(POST_PROCESS_O)
+
+vacation: vacation.o convtime.o rfc2047.o ../libsm/libsm.a
+ $(LINK.c) vacation.o convtime.o rfc2047.o -o $@ $(LDLIBS) \
+ ../libsm/libsm.a
+ $(POST_PROCESS)
+
+aliasadm: aliasadm.o nisplus.o nisedit.o
+ $(LINK.c) aliasadm.o nisplus.o nisedit.o -o $@ $(LDLIBS)
+ $(POST_PROCESS)
+
+mail.local: mail.local.o ../libsmutil/libsmutil.a ../libsm/libsm.a
+ $(LINK.c) mail.local.o -o $@ $(LDLIBS) ../libsmutil/libsmutil.a \
+ ../libsm/libsm.a
+ $(POST_PROCESS)
+
+smrsh: smrsh.o ../libsm/libsm.a
+ $(LINK.c) smrsh.o -o $@ $(LDLIBS) ../libsm/libsm.a
+ $(POST_PROCESS)
+
+makemap: makemap.o ../libsmutil/libsmutil.a ../libsmdb/libsmdb.a \
+ ../db/libdb.a ../libsm/libsm.a
+ $(LINK.c) makemap.o -o $@ $(LDLIBS) ../libsmdb/libsmdb.a \
+ ../libsmutil/libsmutil.a ../db/libdb.a ../libsm/libsm.a
+ $(POST_PROCESS)
+
+editmap: editmap.o ../libsmutil/libsmutil.a ../libsmdb/libsmdb.a \
+ ../db/libdb.a ../libsm/libsm.a
+ $(LINK.c) editmap.o -o $@ $(LDLIBS) ../libsmdb/libsmdb.a \
+ ../libsmutil/libsmutil.a ../db/libdb.a ../libsm/libsm.a
+ $(POST_PROCESS)
+
+praliases: praliases.o ../libsmutil/libsmutil.a ../libsmdb/libsmdb.a \
+ ../libsm/libsm.a
+ $(LINK.c) praliases.o -o $@ $(LDLIBS) ../libsmdb/libsmdb.a \
+ ../libsmutil/libsmutil.a ../db/libdb.a ../libsm/libsm.a
+ $(POST_PROCESS)
+
+mailstats: mailstats.o ../libsmutil/libsmutil.a ../libsm/libsm.a
+ $(LINK.c) mailstats.o -o $@ $(LDLIBS) ../libsmutil/libsmutil.a \
+ ../libsm/libsm.a
+ $(POST_PROCESS)
+
+etrn%: etrn%.pl
+ $(CP) -f $< $@
+ $(CHMOD) +x $@
+
+install: all $(ROOTPROG) $(ROOTLIB)/mail.local $(ROOTLIB)/smrsh \
+ $(ROOTUSRSBIN)/makemap $(ROOTUSRSBIN)/etrn $(ROOTUSRSBIN)/editmap
+
+clean:
+ $(RM) $(OBJS) *.o
+
+lint: lint_PROG
+
+
+include ../../Makefile.targ
diff --git a/usr/src/cmd/sendmail/aux/aliasadm.c b/usr/src/cmd/sendmail/aux/aliasadm.c
new file mode 100644
index 0000000000..21b1a5a963
--- /dev/null
+++ b/usr/src/cmd/sendmail/aux/aliasadm.c
@@ -0,0 +1,218 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ * Copyright 2005 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include "nisplus.h"
+
+#define DEFAULT_ALIAS_MAP "mail_aliases.org_dir"
+
+static FILE *fp = NULL;
+static char *domain, *alias_map;
+static char *match_arg;
+
+static struct nis_mailias alias = {NULL, NULL, NULL, NULL};
+
+typedef enum t_mode {
+ AA_NONE,
+ AA_ADD,
+ AA_CHANGE,
+ AA_DELETE,
+ AA_EDIT,
+ AA_MATCH,
+ AA_LIST,
+ AA_INIT
+} t_mode;
+
+static t_mode mode;
+
+static void argparse();
+static void usage();
+
+int
+main(argc, argv)
+ int argc;
+ char **argv;
+{
+ nis_result *res;
+
+ print_comments = TRUE;
+
+ alias_map = DEFAULT_ALIAS_MAP;
+
+ if ((domain = nis_local_directory()) == NULL) {
+ fprintf(stderr, "Can't get current domain\n");
+ exit(-1);
+ }
+
+ argparse(argc, argv);
+
+ switch (mode) {
+ case AA_ADD:
+ nis_mailias_add(alias, alias_map, domain);
+ break;
+ case AA_CHANGE:
+ nis_mailias_change(alias, alias_map, domain);
+ break;
+ case AA_DELETE:
+ nis_mailias_delete(alias, alias_map, domain);
+ break;
+ case AA_MATCH:
+ res = nis_mailias_match(match_arg,
+ alias_map, domain, ALIAS_COL);
+ if (res->status == SUCCESS) {
+ int i;
+ for (i = 0; i < res->objects.objects_len; i++)
+ mailias_print(fp? fp: stdout,
+ (&res->objects.objects_val[0])+i);
+ }
+ break;
+ case AA_LIST:
+ nis_mailias_list(fp? fp: stdout, alias_map, domain);
+ break;
+ case AA_INIT:
+ nis_mailias_init(alias_map, domain);
+ break;
+ case AA_EDIT:
+ nis_mailias_edit(fp, alias_map, domain);
+ break;
+ case AA_NONE:
+ default:
+ usage(argv[0]);
+ exit(-1);
+ break;
+ }
+ return (0);
+}
+
+static void
+argparse(argc, argv)
+ int argc;
+ char **argv;
+{
+ int c;
+ int narg;
+ int ind;
+
+ mode = AA_NONE;
+
+ while ((c = getopt(argc, argv, "D:M:f:a:c:d:m:leIn")) != EOF) {
+ /*
+ * optind doesn't seem to be recognized as an extern int
+ * (which it is). For now, cast it.
+ */
+ ind = (int)optind;
+ switch (c) {
+ case 'a':
+ mode = AA_ADD;
+ narg = argc - ind + 1;
+ if (narg < 2) {
+ usage(argv[0]);
+ fprintf(stderr, "Invalid argument\n");
+ exit(-1);
+ }
+ alias.name = strdup(optarg);
+ alias.expn = strdup(argv[ind]);
+ if (narg >= 3 && *argv[ind + 1] != '-')
+ alias.comments = strdup(argv[ind + 1]);
+ if (narg >= 4 && *argv[ind + 1] != '-' &&
+ *argv[ind + 2] != '-') {
+ alias.options = strdup(argv[ind + 2]);
+ }
+ break;
+ case 'c':
+ mode = AA_CHANGE;
+ narg = argc - ind + 1;
+ if (narg < 2) {
+ usage(argv[0]);
+ fprintf(stderr, "Invalid argument\n");
+ exit(-1);
+ }
+ alias.name = optarg;
+ alias.expn = strdup(argv[ind]);
+ if (narg >= 3 && *argv[ind + 1] != '-')
+ alias.comments = strdup(argv[ind + 1]);
+ if (narg >= 4 && *argv[ind + 1] != '-' &&
+ *argv[ind + 2] != '-') {
+ alias.options = strdup(argv[ind + 2]);
+ }
+ break;
+ case 'D':
+ domain = strdup(optarg);
+ break;
+ case 'd':
+ mode = AA_DELETE;
+ alias.name = strdup(optarg);
+ break;
+ case 'M':
+ alias_map = strdup(optarg);
+ break;
+ case 'm':
+
+ mode = AA_MATCH;
+ match_arg = strdup(optarg);
+ break;
+ case 'n':
+ print_comments = FALSE;
+ break;
+ case 'f':
+ fp = fopen(optarg, "a+");
+ if (fp == NULL) {
+ fprintf(stderr, "%s:", optarg);
+ perror("Can not open:");
+ exit(-1);
+ }
+ break;
+ case 'e':
+ mode = AA_EDIT;
+ break;
+ case 'l':
+ mode = AA_LIST;
+ break;
+ case 'I':
+ mode = AA_INIT;
+ break;
+ default:
+ fprintf(stderr, "Invalid argument\n");
+ usage(argv[0]);
+ exit(-1);
+ break;
+ }
+ }
+}
+
+static void
+usage(pname)
+ char *pname;
+{
+ fprintf(stderr,
+ "usage:\t%s -a alias expansion [comments] [options]\n", pname);
+ fprintf(stderr, "\t%s -c alias expansion [comments] [options]\n",
+ pname);
+ fprintf(stderr, "\t%s -e\n", pname);
+ fprintf(stderr, "\t%s -d alias\n", pname);
+ fprintf(stderr, "\t%s -m alias\n", pname);
+ fprintf(stderr, "\t%s -l\n", pname);
+}
diff --git a/usr/src/cmd/sendmail/aux/editmap.c b/usr/src/cmd/sendmail/aux/editmap.c
new file mode 100644
index 0000000000..09ba089003
--- /dev/null
+++ b/usr/src/cmd/sendmail/aux/editmap.c
@@ -0,0 +1,424 @@
+/*
+ * Copyright (c) 1998-2002, 2004 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1992 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+#ifndef lint
+SM_UNUSED(static char copyright[]) =
+"@(#) Copyright (c) 1998-2001 Sendmail, Inc. and its suppliers.\n\
+ All rights reserved.\n\
+ Copyright (c) 1992 Eric P. Allman. All rights reserved.\n\
+ Copyright (c) 1992, 1993\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* ! lint */
+
+#ifndef lint
+SM_UNUSED(static char id[]) = "@(#)$Id: editmap.c,v 1.24 2004/08/03 18:40:10 ca Exp $";
+#endif /* ! lint */
+
+
+#include <sys/types.h>
+#ifndef ISC_UNIX
+# include <sys/file.h>
+#endif /* ! ISC_UNIX */
+#include <ctype.h>
+#include <stdlib.h>
+#include <unistd.h>
+#ifdef EX_OK
+# undef EX_OK /* unistd.h may have another use for this */
+#endif /* EX_OK */
+#include <sysexits.h>
+#include <assert.h>
+#include <sendmail/sendmail.h>
+#include <sendmail/pathnames.h>
+#include <libsmdb/smdb.h>
+
+uid_t RealUid;
+gid_t RealGid;
+char *RealUserName;
+uid_t RunAsUid;
+uid_t RunAsGid;
+char *RunAsUserName;
+int Verbose = 2;
+bool DontInitGroups = false;
+uid_t TrustedUid = 0;
+BITMAP256 DontBlameSendmail;
+
+#define BUFSIZE 1024
+#define ISSEP(c) (isascii(c) && isspace(c))
+
+
+static void usage __P((char *));
+
+static void
+usage(progname)
+ char *progname;
+{
+ fprintf(stderr,
+ "Usage: %s [-C cffile] [-N] [-f] [-q|-u|-x] maptype mapname key [ \"value ...\" ]\n",
+ progname);
+ exit(EX_USAGE);
+}
+
+int
+main(argc, argv)
+ int argc;
+ char **argv;
+{
+ char *progname;
+ char *cfile;
+ bool verbose = false;
+ bool query = false;
+ bool update = false;
+ bool remove = false;
+ bool inclnull = false;
+ bool foldcase = true;
+ unsigned int nops = 0;
+ int exitstat;
+ int opt;
+ char *typename = NULL;
+ char *mapname = NULL;
+ char *keyname = NULL;
+ char *value = NULL;
+ int mode;
+ int smode;
+ int putflags = 0;
+ long sff = SFF_ROOTOK|SFF_REGONLY;
+ struct passwd *pw;
+ SMDB_DATABASE *database;
+ SMDB_DBENT db_key, db_val;
+ SMDB_DBPARAMS params;
+ SMDB_USER_INFO user_info;
+#if HASFCHOWN
+ FILE *cfp;
+ char buf[MAXLINE];
+#endif /* HASFCHOWN */
+ static char rnamebuf[MAXNAME]; /* holds RealUserName */
+ extern char *optarg;
+ extern int optind;
+
+ memset(&params, '\0', sizeof params);
+ params.smdbp_cache_size = 1024 * 1024;
+
+ progname = strrchr(argv[0], '/');
+ if (progname != NULL)
+ progname++;
+ else
+ progname = argv[0];
+ cfile = _PATH_SENDMAILCF;
+
+ clrbitmap(DontBlameSendmail);
+ RunAsUid = RealUid = getuid();
+ RunAsGid = RealGid = getgid();
+ pw = getpwuid(RealUid);
+ if (pw != NULL)
+ (void) sm_strlcpy(rnamebuf, pw->pw_name, sizeof rnamebuf);
+ else
+ (void) sm_snprintf(rnamebuf, sizeof rnamebuf,
+ "Unknown UID %d", (int) RealUid);
+ RunAsUserName = RealUserName = rnamebuf;
+ user_info.smdbu_id = RunAsUid;
+ user_info.smdbu_group_id = RunAsGid;
+ (void) sm_strlcpy(user_info.smdbu_name, RunAsUserName,
+ SMDB_MAX_USER_NAME_LEN);
+
+#define OPTIONS "C:fquxvN"
+ while ((opt = getopt(argc, argv, OPTIONS)) != -1)
+ {
+ switch (opt)
+ {
+ case 'C':
+ cfile = optarg;
+ break;
+
+ case 'f':
+ foldcase = false;
+ break;
+
+ case 'q':
+ query = true;
+ nops++;
+ break;
+
+ case 'u':
+ update = true;
+ nops++;
+ break;
+
+ case 'x':
+ remove = true;
+ nops++;
+ break;
+
+ case 'v':
+ verbose = true;
+ break;
+
+ case 'N':
+ inclnull = true;
+ break;
+
+ default:
+ usage(progname);
+ assert(0); /* NOTREACHED */
+ }
+ }
+
+ if (!bitnset(DBS_WRITEMAPTOSYMLINK, DontBlameSendmail))
+ sff |= SFF_NOSLINK;
+ if (!bitnset(DBS_WRITEMAPTOHARDLINK, DontBlameSendmail))
+ sff |= SFF_NOHLINK;
+ if (!bitnset(DBS_LINKEDMAPINWRITABLEDIR, DontBlameSendmail))
+ sff |= SFF_NOWLINK;
+
+ argc -= optind;
+ argv += optind;
+ if ((nops != 1) ||
+ (query && argc != 3) ||
+ (remove && argc != 3) ||
+ (update && argc <= 3))
+ {
+ usage(progname);
+ assert(0); /* NOTREACHED */
+ }
+
+ typename = argv[0];
+ mapname = argv[1];
+ keyname = argv[2];
+ if (update)
+ value = argv[3];
+
+ if (foldcase)
+ {
+ char *p;
+
+ for (p = keyname; *p != '\0'; p++)
+ {
+ if (isascii(*p) && isupper(*p))
+ *p = tolower(*p);
+ }
+ }
+
+
+#if HASFCHOWN
+ /* Find TrustedUser value in sendmail.cf */
+ if ((cfp = fopen(cfile, "r")) == NULL)
+ {
+ fprintf(stderr, "%s: %s: %s\n", progname,
+ cfile, sm_errstring(errno));
+ exit(EX_NOINPUT);
+ }
+ while (fgets(buf, sizeof(buf), cfp) != NULL)
+ {
+ register char *b;
+
+ if ((b = strchr(buf, '\n')) != NULL)
+ *b = '\0';
+
+ b = buf;
+ switch (*b++)
+ {
+ case 'O': /* option */
+ if (strncasecmp(b, " TrustedUser", 12) == 0 &&
+ !(isascii(b[12]) && isalnum(b[12])))
+ {
+ b = strchr(b, '=');
+ if (b == NULL)
+ continue;
+ while (isascii(*++b) && isspace(*b))
+ continue;
+ if (isascii(*b) && isdigit(*b))
+ TrustedUid = atoi(b);
+ else
+ {
+ TrustedUid = 0;
+ pw = getpwnam(b);
+ if (pw == NULL)
+ fprintf(stderr,
+ "TrustedUser: unknown user %s\n", b);
+ else
+ TrustedUid = pw->pw_uid;
+ }
+
+# ifdef UID_MAX
+ if (TrustedUid > UID_MAX)
+ {
+ fprintf(stderr,
+ "TrustedUser: uid value (%ld) > UID_MAX (%ld)",
+ (long) TrustedUid,
+ (long) UID_MAX);
+ TrustedUid = 0;
+ }
+# endif /* UID_MAX */
+ break;
+ }
+
+
+ default:
+ continue;
+ }
+ }
+ (void) fclose(cfp);
+#endif /* HASFCHOWN */
+
+ if (query)
+ {
+ mode = O_RDONLY;
+ smode = S_IRUSR;
+ }
+ else
+ {
+ mode = O_RDWR | O_CREAT;
+ sff |= SFF_CREAT|SFF_NOTEXCL;
+ smode = S_IWUSR;
+ }
+
+ params.smdbp_num_elements = 4096;
+
+ errno = smdb_open_database(&database, mapname, mode, smode, sff,
+ typename, &user_info, &params);
+ if (errno != SMDBE_OK)
+ {
+ char *hint;
+
+ if (errno == SMDBE_UNSUPPORTED_DB_TYPE &&
+ (hint = smdb_db_definition(typename)) != NULL)
+ fprintf(stderr,
+ "%s: Need to recompile with -D%s for %s support\n",
+ progname, hint, typename);
+ else
+ fprintf(stderr,
+ "%s: error opening type %s map %s: %s\n",
+ progname, typename, mapname,
+ sm_errstring(errno));
+ exit(EX_CANTCREAT);
+ }
+
+ (void) database->smdb_sync(database, 0);
+
+ if (geteuid() == 0 && TrustedUid != 0)
+ {
+ errno = database->smdb_set_owner(database, TrustedUid, -1);
+ if (errno != SMDBE_OK)
+ {
+ fprintf(stderr,
+ "WARNING: ownership change on %s failed %s",
+ mapname, sm_errstring(errno));
+ }
+ }
+
+ exitstat = EX_OK;
+ if (query)
+ {
+ memset(&db_key, '\0', sizeof db_key);
+ memset(&db_val, '\0', sizeof db_val);
+
+ db_key.data = keyname;
+ db_key.size = strlen(keyname);
+ if (inclnull)
+ db_key.size++;
+
+ errno = database->smdb_get(database, &db_key, &db_val, 0);
+ if (errno != SMDBE_OK)
+ {
+ /* XXX - Need to distinguish between not found */
+ fprintf(stderr,
+ "%s: couldn't find key %s in map %s\n",
+ progname, keyname, mapname);
+ exitstat = EX_UNAVAILABLE;
+ }
+ else
+ {
+ printf("%.*s\n", (int) db_val.size,
+ (char *) db_val.data);
+ }
+ }
+ else if (update)
+ {
+ memset(&db_key, '\0', sizeof db_key);
+ memset(&db_val, '\0', sizeof db_val);
+
+ db_key.data = keyname;
+ db_key.size = strlen(keyname);
+ if (inclnull)
+ db_key.size++;
+ db_val.data = value;
+ db_val.size = strlen(value);
+ if (inclnull)
+ db_val.size++;
+
+ errno = database->smdb_put(database, &db_key, &db_val,
+ putflags);
+ if (errno != SMDBE_OK)
+ {
+ fprintf(stderr,
+ "%s: error updating (%s, %s) in map %s: %s\n",
+ progname, keyname, value, mapname,
+ sm_errstring(errno));
+ exitstat = EX_IOERR;
+ }
+ }
+ else if (remove)
+ {
+ memset(&db_key, '\0', sizeof db_key);
+ memset(&db_val, '\0', sizeof db_val);
+
+ db_key.data = keyname;
+ db_key.size = strlen(keyname);
+ if (inclnull)
+ db_key.size++;
+
+ errno = database->smdb_del(database, &db_key, 0);
+
+ switch (errno)
+ {
+ case SMDBE_NOT_FOUND:
+ fprintf(stderr,
+ "%s: key %s doesn't exist in map %s\n",
+ progname, keyname, mapname);
+ /* Don't set exitstat */
+ break;
+ case SMDBE_OK:
+ /* All's well */
+ break;
+ default:
+ fprintf(stderr,
+ "%s: couldn't remove key %s in map %s (error)\n",
+ progname, keyname, mapname);
+ exitstat = EX_IOERR;
+ break;
+ }
+ }
+ else
+ {
+ assert(0); /* NOT REACHED */
+ }
+
+ /*
+ ** Now close the database.
+ */
+
+ errno = database->smdb_close(database);
+ if (errno != SMDBE_OK)
+ {
+ fprintf(stderr, "%s: close(%s): %s\n",
+ progname, mapname, sm_errstring(errno));
+ exitstat = EX_IOERR;
+ }
+ smdb_free_database(database);
+
+ exit(exitstat);
+ /* NOTREACHED */
+ return exitstat;
+}
diff --git a/usr/src/cmd/sendmail/aux/etrn.pl b/usr/src/cmd/sendmail/aux/etrn.pl
new file mode 100644
index 0000000000..160c94fa30
--- /dev/null
+++ b/usr/src/cmd/sendmail/aux/etrn.pl
@@ -0,0 +1,275 @@
+#!/usr/perl5/bin/perl -w
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License, Version 1.0 only
+# (the "License"). You may not use this file except in compliance
+# with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+#
+# Copyright (c) 1996-2000 by John T. Beck <john@beck.org>
+# All rights reserved.
+#
+# Copyright 2003 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+#ident "%Z%%M% %I% %E% SMI"
+
+require 5.6.1; # minimal Perl version required
+use strict;
+use warnings;
+use English;
+
+use Socket;
+use Getopt::Std;
+our ($opt_v, $opt_b);
+
+# system requirements:
+# must have 'hostname' program.
+
+my $port = 'smtp';
+select(STDERR);
+
+chop(my $name = `hostname || uname -n`);
+
+my ($hostname) = (gethostbyname($name))[0];
+
+my $usage = "Usage: $PROGRAM_NAME [-bv] host [args]";
+getopts('bv');
+my $verbose = $opt_v;
+my $boot_check = $opt_b;
+my $server = shift(@ARGV);
+my @hosts = @ARGV;
+die $usage unless $server;
+my @cwfiles = ();
+my $alarm_action = "";
+
+if (!@hosts) {
+ push(@hosts, $hostname);
+
+ open(CF, "</etc/mail/sendmail.cf") ||
+ die "open /etc/mail/sendmail.cf: $ERRNO";
+ while (<CF>){
+ # look for a line starting with "Fw"
+ if (/^Fw.*$/) {
+ my $cwfile = $ARG;
+ chop($cwfile);
+ my $optional = /^Fw-o/;
+ # extract the file name
+ $cwfile =~ s,^Fw[^/]*,,;
+
+ # strip the options after the filename
+ $cwfile =~ s/ [^ ]+$//;
+
+ if (-r $cwfile) {
+ push (@cwfiles, $cwfile);
+ } else {
+ die "$cwfile is not readable" unless $optional;
+ }
+ }
+ # look for a line starting with "Cw"
+ if (/^Cw(.*)$/) {
+ my @cws = split (' ', $1);
+ while (@cws) {
+ my $thishost = shift(@cws);
+ push(@hosts, $thishost)
+ unless $thishost =~ "$hostname|localhost";
+ }
+ }
+ }
+ close(CF);
+
+ for my $cwfile (@cwfiles) {
+ if (open(CW, "<$cwfile")) {
+ while (<CW>) {
+ next if /^\#/;
+ my $thishost = $ARG;
+ chop($thishost);
+ push(@hosts, $thishost)
+ unless $thishost =~ $hostname;
+ }
+ close(CW);
+ } else {
+ die "open $cwfile: $ERRNO";
+ }
+ }
+ # Do this automatically if no client hosts are specified.
+ $boot_check = "yes";
+}
+
+my ($proto) = (getprotobyname('tcp'))[2];
+($port) = (getservbyname($port, 'tcp'))[2]
+ unless $port =~ /^\d+/;
+
+if ($boot_check) {
+ # first connect to localhost to verify that we can accept connections
+ print "verifying that localhost is accepting SMTP connections\n"
+ if ($verbose);
+ my $localhost_ok = 0;
+ ($name, my $laddr) = (gethostbyname('localhost'))[0, 4];
+ (!defined($name)) && die "gethostbyname failed, unknown host $server";
+
+ # get a connection
+ my $sinl = sockaddr_in($port, $laddr);
+ my $save_errno = 0;
+ for (my $num_tries = 1; $num_tries < 5; $num_tries++) {
+ socket(S, &PF_INET, &SOCK_STREAM, $proto)
+ || die "socket: $ERRNO";
+ if (connect(S, $sinl)) {
+ &alarm("sending 'quit' to $server");
+ print S "quit\n";
+ alarm(0);
+ $localhost_ok = 1;
+ close(S);
+ alarm(0);
+ last;
+ }
+ print STDERR "localhost connect failed ($num_tries)\n";
+ $save_errno = $ERRNO;
+ sleep(1 << $num_tries);
+ close(S);
+ alarm(0);
+ }
+ if (! $localhost_ok) {
+ die "could not connect to localhost: $save_errno\n";
+ }
+}
+
+# look it up
+
+($name, my $thataddr) = (gethostbyname($server))[0, 4];
+(!defined($name)) && die "gethostbyname failed, unknown host $server";
+
+# get a connection
+my $sinr = sockaddr_in($port, $thataddr);
+socket(S, &PF_INET, &SOCK_STREAM, $proto)
+ || die "socket: $ERRNO";
+print "server = $server\n" if (defined($verbose));
+&alarm("connect to $server");
+if (! connect(S, $sinr)) {
+ die "cannot connect to $server: $ERRNO\n";
+}
+alarm(0);
+select((select(S), $OUTPUT_AUTOFLUSH = 1)[0]); # don't buffer output to S
+
+# read the greeting
+&alarm("greeting with $server");
+while (<S>) {
+ alarm(0);
+ print if $verbose;
+ if (/^(\d+)([- ])/) {
+ # SMTP's initial greeting response code is 220.
+ if ($1 != 220) {
+ &alarm("giving up after bad response from $server");
+ &read_response($2, $verbose);
+ alarm(0);
+ print STDERR "$server: NOT 220 greeting: $ARG"
+ if ($verbose);
+ }
+ last if ($2 eq " ");
+ } else {
+ print STDERR "$server: NOT 220 greeting: $ARG"
+ if ($verbose);
+ close(S);
+ }
+ &alarm("greeting with $server");
+}
+alarm(0);
+
+&alarm("sending ehlo to $server");
+&ps("ehlo $hostname");
+my $etrn_support = 0;
+while (<S>) {
+ if (/^250([- ])ETRN(.+)$/) {
+ $etrn_support = 1;
+ }
+ print if $verbose;
+ last if /^\d+ /;
+}
+alarm(0);
+
+if ($etrn_support) {
+ print "ETRN supported\n" if ($verbose);
+ &alarm("sending etrn to $server");
+ while (@hosts) {
+ $server = shift(@hosts);
+ &ps("etrn $server");
+ while (<S>) {
+ print if $verbose;
+ last if /^\d+ /;
+ }
+ sleep(1);
+ }
+} else {
+ print "\nETRN not supported\n\n"
+}
+
+&alarm("sending 'quit' to $server");
+&ps("quit");
+while (<S>) {
+ print if $verbose;
+ last if /^\d+ /;
+}
+close(S);
+alarm(0);
+
+select(STDOUT);
+exit(0);
+
+# print to the server (also to stdout, if -v)
+sub ps
+{
+ my ($p) = @_;
+ print ">>> $p\n" if $verbose;
+ print S "$p\n";
+}
+
+sub alarm
+{
+ ($alarm_action) = @_;
+ alarm(10);
+ $SIG{ALRM} = 'handle_alarm';
+}
+
+sub handle_alarm
+{
+ &giveup($alarm_action);
+}
+
+sub giveup
+{
+ my $reason = @_;
+ (my $pk, my $file, my $line);
+ ($pk, $file, $line) = caller;
+
+ print "Timed out during $reason\n" if $verbose;
+ exit(1);
+}
+
+# read the rest of the current smtp daemon's response (and toss it away)
+sub read_response
+{
+ (my $done, $verbose) = @_;
+ (my @resp);
+ print my $s if $verbose;
+ while (($done eq "-") && ($s = <S>) && ($s =~ /^\d+([- ])/)) {
+ print $s if $verbose;
+ $done = $1;
+ push(@resp, $s);
+ }
+ return @resp;
+}
diff --git a/usr/src/cmd/sendmail/aux/mail.local.c b/usr/src/cmd/sendmail/aux/mail.local.c
new file mode 100644
index 0000000000..9d73be258b
--- /dev/null
+++ b/usr/src/cmd/sendmail/aux/mail.local.c
@@ -0,0 +1,1215 @@
+/*
+ * Copyright (c) 1998 Sendmail, Inc. All rights reserved.
+ * Copyright (c) 1990, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level
+ * of the sendmail distribution.
+ */
+
+/*
+ * Copyright 1994-2002 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef lint
+static char copyright[] =
+"@(#) Copyright (c) 1990, 1993, 1994\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#ifndef lint
+static char sccsid[] = "@(#)mail.local.c 8.83 (Berkeley) 12/17/98";
+static char sccsi2[] = "%W% (Sun) %G%";
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/file.h>
+
+#include <netinet/in.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <ctype.h>
+#include <string.h>
+#include <sysexits.h>
+#include <time.h>
+#include <unistd.h>
+#include <maillock.h>
+#include <grp.h>
+
+#ifdef __STDC__
+#include <stdarg.h>
+#else
+#include <varargs.h>
+#endif
+
+#include <syslog.h>
+
+#include <sysexits.h>
+#include <ctype.h>
+
+#include <sm/conf.h>
+#include <sendmail/pathnames.h>
+
+/*
+** If you don't have flock, you could try using lockf instead.
+*/
+
+#ifdef LDA_USE_LOCKF
+# define flock(a, b) lockf(a, b, 0)
+# ifdef LOCK_EX
+# undef LOCK_EX
+# endif /* LOCK_EX */
+# define LOCK_EX F_LOCK
+#endif /* LDA_USE_LOCKF */
+
+#ifndef LOCK_EX
+# include <sys/file.h>
+#endif /* ! LOCK_EX */
+
+#ifndef MAILER_DAEMON
+# define MAILER_DAEMON "MAILER-DAEMON"
+#endif
+
+typedef int bool;
+
+#define FALSE 0
+#define TRUE 1
+
+bool EightBitMime = TRUE; /* advertise 8BITMIME in LMTP */
+static int eval = EX_OK; /* sysexits.h error value. */
+static int lmtpmode = 0;
+bool bouncequota = FALSE; /* permanent error when over quota */
+
+#define _PATH_MAILDIR "/var/mail"
+#define _PATH_LOCTMP "/tmp/local.XXXXXX"
+#define _PATH_LOCHTMP "/tmp/lochd.XXXXXX"
+#define FALSE 0
+#define TRUE 1
+#define MAXLINE 2048
+
+static void deliver(int, int, char *, bool);
+static void e_to_sys(int);
+static void err(const char *fmt, ...);
+static void notifybiff(char *);
+static void store(char *, int);
+static void usage(void);
+static void vwarn();
+static void warn(const char *fmt, ...);
+static void mailerr(const char *, const char *, ...);
+static void sigterm_handler();
+
+static char unix_from_line[MAXLINE];
+static int ulen;
+static int content_length;
+static int bfd, hfd; /* temp file */
+static uid_t src_uid, targ_uid, saved_uid;
+static int sigterm_caught;
+
+int
+main(argc, argv)
+ int argc;
+ char *argv[];
+{
+ struct passwd *pw;
+ int ch;
+ uid_t uid;
+ char *from;
+ struct group *grpptr;
+ void dolmtp();
+
+ openlog("mail.local", 0, LOG_MAIL);
+
+ from = NULL;
+ pw = NULL;
+ sigterm_caught = FALSE;
+
+ (void) sigset(SIGTERM, sigterm_handler);
+
+ while ((ch = getopt(argc, argv, "7bdf:r:l")) != EOF)
+ switch (ch) {
+ case '7': /* Do not advertise 8BITMIME */
+ EightBitMime = FALSE;
+ break;
+
+ case 'b': /* bounce mail when over quota. */
+ bouncequota = TRUE;
+ break;
+
+ case 'd': /* Backward compatible. */
+ break;
+ case 'f':
+ case 'r': /* Backward compatible. */
+ if (from != NULL) {
+ warn("multiple -f options");
+ usage();
+ }
+ from = optarg;
+ break;
+ case 'l':
+ lmtpmode++;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ notifybiff(NULL); /* initialize biff structures */
+
+ /*
+ * We expect sendmail will invoke us with saved id 0
+ * We then do setgid and setuid defore delivery
+ * setgid to mail group
+ */
+ if ((grpptr = getgrnam("mail")) != NULL)
+ (void) setgid(grpptr->gr_gid);
+ saved_uid = geteuid();
+
+ if (lmtpmode) {
+ if (saved_uid != 0) {
+ warn("only super-user can use -l option");
+ exit(EX_CANTCREAT);
+ }
+ dolmtp(bouncequota);
+ }
+
+ if (!*argv)
+ usage();
+
+ /*
+ * If from not specified, use the name from getlogin() if the
+ * uid matches, otherwise, use the name from the password file
+ * corresponding to the uid.
+ */
+ uid = getuid();
+ if (!from && (!(from = getlogin()) ||
+ !(pw = getpwnam(from)) || pw->pw_uid != uid))
+ from = (pw = getpwuid(uid)) ? pw->pw_name : "???";
+ src_uid = pw ? pw->pw_uid : uid;
+
+ /*
+ * There is no way to distinguish the error status of one delivery
+ * from the rest of the deliveries. So, if we failed hard on one
+ * or more deliveries, but had no failures on any of the others, we
+ * return a hard failure. If we failed temporarily on one or more
+ * deliveries, we return a temporary failure regardless of the other
+ * failures. This results in the delivery being reattempted later
+ * at the expense of repeated failures and multiple deliveries.
+ */
+
+ for (store(from, 0); *argv; ++argv)
+ deliver(hfd, bfd, *argv, bouncequota);
+ return (eval);
+}
+
+void
+sigterm_handler()
+{
+ sigterm_caught = TRUE;
+ (void) sigignore(SIGTERM);
+}
+
+char *
+parseaddr(s)
+ char *s;
+{
+ char *p;
+ int len;
+
+ if (*s++ != '<')
+ return NULL;
+
+ p = s;
+
+ /* at-domain-list */
+ while (*p == '@') {
+ p++;
+ if (*p == '[') {
+ p++;
+ while (isascii(*p) &&
+ (isalnum(*p) || *p == '.' ||
+ *p == '-' || *p == ':'))
+ p++;
+ if (*p++ != ']')
+ return NULL;
+ } else {
+ while ((isascii(*p) && isalnum(*p)) ||
+ strchr(".-_", *p))
+ p++;
+ }
+ if (*p == ',' && p[1] == '@')
+ p++;
+ else if (*p == ':' && p[1] != '@')
+ p++;
+ else
+ return NULL;
+ }
+
+ s = p;
+
+ /* local-part */
+ if (*p == '\"') {
+ p++;
+ while (*p && *p != '\"') {
+ if (*p == '\\') {
+ if (!*++p)
+ return NULL;
+ }
+ p++;
+ }
+ if (!*p++)
+ return NULL;
+ } else {
+ while (*p && *p != '@' && *p != '>') {
+ if (*p == '\\') {
+ if (!*++p)
+ return NULL;
+ } else {
+ if (*p <= ' ' || (*p & 128) ||
+ strchr("<>()[]\\,;:\"", *p))
+ return NULL;
+ }
+ p++;
+ }
+ }
+
+ /* @domain */
+ if (*p == '@') {
+ p++;
+ if (*p == '[') {
+ p++;
+ while (isascii(*p) &&
+ (isalnum(*p) || *p == '.' ||
+ *p == '-' || *p == ':'))
+ p++;
+ if (*p++ != ']')
+ return NULL;
+ } else {
+ while ((isascii(*p) && isalnum(*p)) ||
+ strchr(".-_", *p))
+ p++;
+ }
+ }
+
+ if (*p++ != '>')
+ return NULL;
+ if (*p && *p != ' ')
+ return NULL;
+ len = p - s - 1;
+
+ if (*s == '\0' || len <= 0)
+ {
+ s = MAILER_DAEMON;
+ len = strlen(s);
+ }
+
+ p = malloc(len + 1);
+ if (p == NULL) {
+ printf("421 4.3.0 memory exhausted\r\n");
+ exit(EX_TEMPFAIL);
+ }
+
+ strncpy(p, s, len);
+ p[len] = '\0';
+ return p;
+}
+
+char *
+process_recipient(addr)
+ char *addr;
+{
+ if (getpwnam(addr) == NULL) {
+ return "550 5.1.1 user unknown";
+ }
+
+ return NULL;
+}
+
+#define RCPT_GROW 30
+
+void
+dolmtp(bouncequota)
+ bool bouncequota;
+{
+ char *return_path = NULL;
+ char **rcpt_addr = NULL;
+ int rcpt_num = 0;
+ int rcpt_alloc = 0;
+ bool gotlhlo = FALSE;
+ char myhostname[MAXHOSTNAMELEN];
+ char buf[4096];
+ char *err;
+ char *p;
+ int i;
+
+ gethostname(myhostname, sizeof myhostname - 1);
+
+ printf("220 %s LMTP ready\r\n", myhostname);
+ for (;;) {
+ if (sigterm_caught) {
+ for (; rcpt_num > 0; rcpt_num--)
+ printf("451 4.3.0 shutting down\r\n");
+ exit(EX_OK);
+ }
+ fflush(stdout);
+ if (fgets(buf, sizeof(buf)-1, stdin) == NULL) {
+ exit(EX_OK);
+ }
+ p = buf + strlen(buf) - 1;
+ if (p >= buf && *p == '\n')
+ *p-- = '\0';
+ if (p >= buf && *p == '\r')
+ *p-- = '\0';
+
+ switch (buf[0]) {
+
+ case 'd':
+ case 'D':
+ if (strcasecmp(buf, "data") == 0) {
+ if (rcpt_num == 0) {
+ printf("503 5.5.1 No recipients\r\n");
+ continue;
+ }
+ store(return_path, rcpt_num);
+ if (bfd == -1 || hfd == -1)
+ continue;
+
+ for (i = 0; i < rcpt_num; i++) {
+ p = strchr(rcpt_addr[i], '+');
+ if (p != NULL)
+ *p++ = '\0';
+ deliver(hfd, bfd, rcpt_addr[i],
+ bouncequota);
+ }
+ close(bfd);
+ close(hfd);
+ goto rset;
+ }
+ goto syntaxerr;
+ /* NOTREACHED */
+ break;
+
+ case 'l':
+ case 'L':
+ if (strncasecmp(buf, "lhlo ", 5) == 0)
+ {
+ /* check for duplicate per RFC 1651 4.2 */
+ if (gotlhlo)
+ {
+ printf("503 %s Duplicate LHLO\r\n",
+ myhostname);
+ continue;
+ }
+ gotlhlo = TRUE;
+ printf("250-%s\r\n", myhostname);
+ if (EightBitMime)
+ printf("250-8BITMIME\r\n");
+ printf("250-ENHANCEDSTATUSCODES\r\n");
+ printf("250 PIPELINING\r\n");
+ continue;
+ }
+ goto syntaxerr;
+ /* NOTREACHED */
+ break;
+
+ case 'm':
+ case 'M':
+ if (strncasecmp(buf, "mail ", 5) == 0) {
+ if (return_path != NULL) {
+ printf("503 5.5.1 Nested MAIL command\r\n");
+ continue;
+ }
+ if (strncasecmp(buf+5, "from:", 5) != 0 ||
+ ((return_path = parseaddr(buf+10)) == NULL)) {
+ printf("501 5.5.4 Syntax error in parameters\r\n");
+ continue;
+ }
+ printf("250 2.5.0 ok\r\n");
+ continue;
+ }
+ goto syntaxerr;
+
+ case 'n':
+ case 'N':
+ if (strcasecmp(buf, "noop") == 0) {
+ printf("250 2.0.0 ok\r\n");
+ continue;
+ }
+ goto syntaxerr;
+
+ case 'q':
+ case 'Q':
+ if (strcasecmp(buf, "quit") == 0) {
+ printf("221 2.0.0 bye\r\n");
+ exit(EX_OK);
+ }
+ goto syntaxerr;
+
+ case 'r':
+ case 'R':
+ if (strncasecmp(buf, "rcpt ", 5) == 0) {
+ if (return_path == NULL) {
+ printf("503 5.5.1 Need MAIL command\r\n");
+ continue;
+ }
+ if (rcpt_num >= rcpt_alloc) {
+ rcpt_alloc += RCPT_GROW;
+ rcpt_addr = (char **)
+ realloc((char *)rcpt_addr,
+ rcpt_alloc * sizeof(char **));
+ if (rcpt_addr == NULL) {
+ printf("421 4.3.0 memory exhausted\r\n");
+ exit(EX_TEMPFAIL);
+ }
+ }
+ if (strncasecmp(buf+5, "to:", 3) != 0 ||
+ ((rcpt_addr[rcpt_num] = parseaddr(buf+8)) == NULL)) {
+ printf("501 5.5.4 Syntax error in parameters\r\n");
+ continue;
+ }
+ if ((err = process_recipient(rcpt_addr[rcpt_num])) != NULL) {
+ printf("%s\r\n", err);
+ continue;
+ }
+ rcpt_num++;
+ printf("250 2.1.5 ok\r\n");
+ continue;
+ }
+ else if (strcasecmp(buf, "rset") == 0) {
+ printf("250 2.0.0 ok\r\n");
+
+ rset:
+ while (rcpt_num > 0) {
+ free(rcpt_addr[--rcpt_num]);
+ }
+ if (return_path != NULL)
+ free(return_path);
+ return_path = NULL;
+ continue;
+ }
+ goto syntaxerr;
+
+ case 'v':
+ case 'V':
+ if (strncasecmp(buf, "vrfy ", 5) == 0) {
+ printf("252 2.3.3 try RCPT to attempt delivery\r\n");
+ continue;
+ }
+ goto syntaxerr;
+
+ default:
+ syntaxerr:
+ printf("500 5.5.2 Syntax error\r\n");
+ continue;
+ }
+ }
+}
+
+static void
+store(from, lmtprcpts)
+ char *from;
+ int lmtprcpts;
+{
+ FILE *fp = NULL;
+ time_t tval;
+ bool fullline = TRUE; /* current line is terminated */
+ bool prevfl; /* previous line was terminated */
+ char line[MAXLINE];
+ FILE *bfp, *hfp;
+ char *btn, *htn;
+ int in_header_section;
+
+ bfd = -1;
+ hfd = -1;
+ btn = strdup(_PATH_LOCTMP);
+ if ((bfd = mkstemp(btn)) == -1 || (bfp = fdopen(bfd, "w+")) == NULL) {
+ if (bfd != -1)
+ (void) close(bfd);
+ if (lmtprcpts) {
+ printf("451 4.3.0 unable to open temporary file\r\n");
+ return;
+ } else {
+ mailerr("451 4.3.0", "unable to open temporary file");
+ exit(eval);
+ }
+ }
+ (void) unlink(btn);
+ free(btn);
+
+ if (lmtpmode) {
+ printf("354 go ahead\r\n");
+ fflush(stdout);
+ }
+
+ htn = strdup(_PATH_LOCHTMP);
+ if ((hfd = mkstemp(htn)) == -1 || (hfp = fdopen(hfd, "w+")) == NULL) {
+ if (hfd != -1)
+ (void) close(hfd);
+ e_to_sys(errno);
+ err("unable to open temporary file");
+ }
+ (void) unlink(htn);
+ free(htn);
+
+ in_header_section = TRUE;
+ content_length = 0;
+ fp = hfp;
+
+ line[0] = '\0';
+ while (fgets(line, sizeof(line), stdin) != (char *)NULL)
+ {
+ size_t line_len = 0;
+ int peek;
+
+ prevfl = fullline; /* preserve state of previous line */
+ while (line[line_len] != '\n' && line_len < sizeof(line) - 2)
+ line_len++;
+ line_len++;
+
+ /* Check for dot-stuffing */
+ if (prevfl && lmtprcpts && line[0] == '.')
+ {
+ if (line[1] == '\n' ||
+ (line[1] == '\r' && line[2] == '\n'))
+ goto lmtpdot;
+ memcpy(line, line + 1, line_len);
+ line_len--;
+ }
+
+ /* Check to see if we have the full line from fgets() */
+ fullline = FALSE;
+ if (line_len > 0)
+ {
+ if (line[line_len - 1] == '\n')
+ {
+ if (line_len >= 2 &&
+ line[line_len - 2] == '\r')
+ {
+ line[line_len - 2] = '\n';
+ line[line_len - 1] = '\0';
+ line_len--;
+ }
+ fullline = TRUE;
+ }
+ else if (line[line_len - 1] == '\r')
+ {
+ /* Did we just miss the CRLF? */
+ peek = fgetc(stdin);
+ if (peek == '\n')
+ {
+ line[line_len - 1] = '\n';
+ fullline = TRUE;
+ }
+ else
+ (void) ungetc(peek, stdin);
+ }
+ }
+ else
+ fullline = TRUE;
+
+ if (prevfl && line[0] == '\n' && in_header_section) {
+ in_header_section = FALSE;
+ if (fflush(fp) == EOF || ferror(fp)) {
+ if (lmtprcpts) {
+ while (lmtprcpts--)
+ printf("451 4.3.0 temporary file write error\r\n");
+ fclose(fp);
+ return;
+ } else {
+ mailerr("451 4.3.0",
+ "temporary file write error");
+ fclose(fp);
+ exit(eval);
+ }
+ }
+ fp = bfp;
+ continue;
+ }
+
+ if (in_header_section) {
+ if (strncasecmp("Content-Length:", line, 15) == 0) {
+ continue; /* skip this header */
+ }
+ } else
+ content_length += strlen(line);
+ (void) fwrite(line, sizeof(char), line_len, fp);
+ if (ferror(fp)) {
+ if (lmtprcpts) {
+ while (lmtprcpts--)
+ printf("451 4.3.0 temporary file write error\r\n");
+ fclose(fp);
+ return;
+ } else {
+ mailerr("451 4.3.0",
+ "temporary file write error");
+ fclose(fp);
+ exit(eval);
+ }
+ }
+ }
+ if (sigterm_caught) {
+ if (lmtprcpts)
+ while (lmtprcpts--)
+ printf("451 4.3.0 shutting down\r\n");
+ else
+ mailerr("451 4.3.0", "shutting down");
+ fclose(fp);
+ exit(eval);
+ }
+
+ if (lmtprcpts) {
+ /* Got a premature EOF -- toss message and exit */
+ exit(EX_OK);
+ }
+
+ /* If message not newline terminated, need an extra. */
+ if (!strchr(line, '\n')) {
+ (void) putc('\n', fp);
+ content_length++;
+ }
+
+ lmtpdot:
+
+ /* Output a newline; note, empty messages are allowed. */
+ (void) putc('\n', fp);
+
+ if (fflush(fp) == EOF || ferror(fp)) {
+ if (lmtprcpts) {
+ while (lmtprcpts--) {
+ printf("451 4.3.0 temporary file write error\r\n");
+ }
+ fclose(fp);
+ return;
+ } else {
+ mailerr("451 4.3.0", "temporary file write error");
+ fclose(fp);
+ exit(eval);
+ }
+ }
+
+ (void) time(&tval);
+ (void) snprintf(unix_from_line, sizeof (unix_from_line), "From %s %s",
+ from, ctime(&tval));
+ ulen = strlen(unix_from_line);
+}
+
+static void
+deliver(hfd, bfd, name, bouncequota)
+ int hfd;
+ int bfd;
+ char *name;
+ bool bouncequota;
+{
+ struct stat fsb, sb;
+ int mbfd = -1, nr, nw = 0, off;
+ char biffmsg[100], buf[8*1024], path[MAXPATHLEN];
+ off_t curoff, cursize;
+ int len;
+ struct passwd *pw = NULL;
+
+ /*
+ * Disallow delivery to unknown names -- special mailboxes
+ * can be handled in the sendmail aliases file.
+ */
+ if ((pw = getpwnam(name)) == NULL) {
+ eval = EX_TEMPFAIL;
+ mailerr("451 4.3.0", "cannot lookup name: %s", name);
+ return;
+ }
+ endpwent();
+
+ if (sigterm_caught) {
+ mailerr("451 4.3.0", "shutting down");
+ return;
+ }
+
+ /* mailbox may be NFS mounted, seteuid to user */
+ targ_uid = pw->pw_uid;
+ (void) seteuid(targ_uid);
+
+ if ((saved_uid != 0) && (src_uid != targ_uid)) {
+ /*
+ * If saved_uid == 0 (root), anything is OK; this is
+ * as it should be. But to prevent a random user from
+ * calling "mail.local foo" in an attempt to hijack
+ * foo's mail-box, make sure src_uid == targ_uid o/w.
+ */
+ warn("%s: wrong owner (is %d, should be %d)",
+ name, src_uid, targ_uid);
+ eval = EX_CANTCREAT;
+ return;
+ }
+
+ path[0] = '\0';
+ (void) snprintf(path, sizeof (path), "%s/%s", _PATH_MAILDIR, name);
+
+ /*
+ * If the mailbox is linked or a symlink, fail. There's an obvious
+ * race here, that the file was replaced with a symbolic link after
+ * the lstat returned, but before the open. We attempt to detect
+ * this by comparing the original stat information and information
+ * returned by an fstat of the file descriptor returned by the open.
+ *
+ * NB: this is a symptom of a larger problem, that the mail spooling
+ * directory is writeable by the wrong users. If that directory is
+ * writeable, system security is compromised for other reasons, and
+ * it cannot be fixed here.
+ *
+ * If we created the mailbox, set the owner/group. If that fails,
+ * just return. Another process may have already opened it, so we
+ * can't unlink it. Historically, binmail set the owner/group at
+ * each mail delivery. We no longer do this, assuming that if the
+ * ownership or permissions were changed there was a reason.
+ *
+ * XXX
+ * open(2) should support flock'ing the file.
+ */
+tryagain:
+ /* should check lock status, but... maillock return no value */
+ maillock(name, 10);
+
+ if (sigterm_caught) {
+ mailerr("451 4.3.0", "shutting down");
+ goto err0;
+ }
+
+ if (lstat(path, &sb)) {
+ mbfd = open(path, O_APPEND|O_CREAT|O_EXCL|O_WRONLY,
+ S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP);
+ if (mbfd != -1)
+ (void) fchmod(mbfd, 0660);
+
+
+ if (mbfd == -1) {
+ if (errno == EEXIST) {
+ mailunlock();
+ goto tryagain;
+ }
+ }
+ } else if (sb.st_nlink != 1) {
+ mailerr("550 5.2.0", "%s: too many links", path);
+ goto err0;
+ } else if (!S_ISREG(sb.st_mode)) {
+ mailerr("550 5.2.0", "%s: irregular file", path);
+ goto err0;
+ } else {
+ mbfd = open(path, O_APPEND|O_WRONLY, 0);
+ if (mbfd != -1 &&
+ (fstat(mbfd, &fsb) || fsb.st_nlink != 1 ||
+ S_ISLNK(fsb.st_mode) || sb.st_dev != fsb.st_dev ||
+ sb.st_ino != fsb.st_ino)) {
+ eval = EX_TEMPFAIL;
+ mailerr("550 5.2.0",
+ "%s: fstat: file changed after open", path);
+ goto err1;
+ }
+ }
+
+ if (mbfd == -1) {
+ mailerr("450 4.2.0", "%s: %s", path, strerror(errno));
+ goto err0;
+ }
+
+ if (sigterm_caught) {
+ mailerr("451 4.3.0", "shutting down");
+ goto err0;
+ }
+
+ /* Get the starting offset of the new message for biff. */
+ curoff = lseek(mbfd, (off_t)0, SEEK_END);
+ (void) snprintf(biffmsg, sizeof (biffmsg), "%s@%ld\n", name, curoff);
+
+ /* Copy the message into the file. */
+ if (lseek(hfd, (off_t)0, SEEK_SET) == (off_t)-1) {
+ mailerr("450 4.2.0", "temporary file: %s", strerror(errno));
+ goto err1;
+ }
+ /* Copy the message into the file. */
+ if (lseek(bfd, (off_t)0, SEEK_SET) == (off_t)-1) {
+ mailerr("450 4.2.0", "temporary file: %s", strerror(errno));
+ goto err1;
+ }
+ if ((write(mbfd, unix_from_line, ulen)) != ulen) {
+ mailerr("450 4.2.0", "temporary file: %s", strerror(errno));
+ goto err2;
+ }
+
+ if (sigterm_caught) {
+ mailerr("451 4.3.0", "shutting down");
+ goto err2;
+ }
+
+ while ((nr = read(hfd, buf, sizeof (buf))) > 0)
+ for (off = 0; off < nr; nr -= nw, off += nw)
+ if ((nw = write(mbfd, buf + off, nr)) < 0)
+ {
+#ifdef EDQUOT
+ if (errno == EDQUOT && bouncequota)
+ mailerr("552 5.2.2", "%s: %s",
+ path, sm_errstring(errno));
+#endif /* EDQUOT */
+ mailerr("450 4.2.0", "%s: %s",
+ path, sm_errstring(errno));
+ goto err2;
+ }
+ if (nr < 0) {
+ mailerr("450 4.2.0", "temporary file: %s", strerror(errno));
+ goto err2;
+ }
+
+ if (sigterm_caught) {
+ mailerr("451 4.3.0", "shutting down");
+ goto err2;
+ }
+
+ (void) snprintf(buf, sizeof (buf), "Content-Length: %d\n\n",
+ content_length);
+ len = strlen(buf);
+ if (write(mbfd, buf, len) != len) {
+ mailerr("450 4.2.0", "temporary file: %s", strerror(errno));
+ goto err2;
+ }
+
+ if (sigterm_caught) {
+ mailerr("451 4.3.0", "shutting down");
+ goto err2;
+ }
+
+ while ((nr = read(bfd, buf, sizeof (buf))) > 0) {
+ for (off = 0; off < nr; nr -= nw, off += nw)
+ if ((nw = write(mbfd, buf + off, nr)) < 0) {
+ mailerr("450 4.2.0", "temporary file: %s",
+ strerror(errno));
+ goto err2;
+ }
+ if (sigterm_caught) {
+ mailerr("451 4.3.0", "shutting down");
+ goto err2;
+ }
+ }
+ if (nr < 0) {
+ mailerr("450 4.2.0", "temporary file: %s", strerror(errno));
+ goto err2;
+ }
+
+ /* Flush to disk, don't wait for update. */
+ if (fsync(mbfd)) {
+ mailerr("450 4.2.0", "temporary file: %s", strerror(errno));
+err2: if (mbfd >= 0)
+ (void)ftruncate(mbfd, curoff);
+err1: (void)close(mbfd);
+err0: mailunlock();
+ (void)seteuid(saved_uid);
+ return;
+ }
+
+ /*
+ ** Save the current size so if the close() fails below
+ ** we can make sure no other process has changed the mailbox
+ ** between the failed close and the re-open()/re-lock().
+ ** If something else has changed the size, we shouldn't
+ ** try to truncate it as we may do more harm then good
+ ** (e.g., truncate a later message delivery).
+ */
+
+ if (fstat(mbfd, &sb) < 0)
+ cursize = 0;
+ else
+ cursize = sb.st_size;
+
+ /* Close and check -- NFS doesn't write until the close. */
+ if (close(mbfd))
+ {
+#ifdef EDQUOT
+ if (errno == EDQUOT && bouncequota)
+ mailerr("552 5.2.2", "%s: %s", path,
+ sm_errstring(errno));
+ else
+#endif /* EDQUOT */
+ mailerr("450 4.2.0", "%s: %s", path, sm_errstring(errno));
+ mbfd = open(path, O_WRONLY, 0);
+ if (mbfd < 0 ||
+ cursize == 0
+ || flock(mbfd, LOCK_EX) < 0 ||
+ fstat(mbfd, &sb) < 0 ||
+ sb.st_size != cursize ||
+ sb.st_nlink != 1 ||
+ !S_ISREG(sb.st_mode) ||
+ sb.st_dev != fsb.st_dev ||
+ sb.st_ino != fsb.st_ino ||
+ sb.st_uid != fsb.st_uid)
+ {
+ /* Don't use a bogus file */
+ if (mbfd >= 0)
+ {
+ (void) close(mbfd);
+ mbfd = -1;
+ }
+ }
+
+ /* Attempt to truncate back to pre-write size */
+ goto err2;
+ } else
+ notifybiff(biffmsg);
+
+ mailunlock();
+
+ (void)seteuid(saved_uid);
+
+ if (lmtpmode) {
+ printf("250 2.1.5 %s OK\r\n", name);
+ }
+}
+
+static void
+notifybiff(msg)
+ char *msg;
+{
+ static struct sockaddr_in addr;
+ static int f = -1;
+ struct hostent *hp;
+ struct servent *sp;
+ int len;
+
+ if (msg == NULL) {
+ /* Be silent if biff service not available. */
+ if ((sp = getservbyname("biff", "udp")) == NULL)
+ return;
+ if ((hp = gethostbyname("localhost")) == NULL) {
+ warn("localhost: %s", strerror(errno));
+ return;
+ }
+ addr.sin_family = hp->h_addrtype;
+ (void) memmove(&addr.sin_addr, hp->h_addr, hp->h_length);
+ addr.sin_port = sp->s_port;
+ return;
+ }
+
+ if (addr.sin_family == 0)
+ return; /* did not initialize */
+
+ if (f < 0 && (f = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
+ warn("socket: %s", strerror(errno));
+ return;
+ }
+ len = strlen(msg) + 1;
+ if (sendto(f, msg, len, 0, (struct sockaddr *)&addr, sizeof (addr))
+ != len)
+ warn("sendto biff: %s", strerror(errno));
+}
+
+static void
+usage()
+{
+ eval = EX_USAGE;
+ err("usage: mail.local [-l] [-f from] user ...");
+}
+
+static void
+/*VARARGS2*/
+#ifdef __STDC__
+mailerr(const char *hdr, const char *fmt, ...)
+#else
+mailerr(hdr, fmt, va_alist)
+ const char *hdr;
+ const char *fmt;
+ va_dcl
+#endif
+{
+ va_list ap;
+
+#ifdef __STDC__
+ va_start(ap, fmt);
+#else
+ va_start(ap);
+#endif
+ if (lmtpmode)
+ {
+ if (hdr != NULL)
+ printf("%s ", hdr);
+ vprintf(fmt, ap);
+ printf("\r\n");
+ }
+ else
+ {
+ e_to_sys(errno);
+ vwarn(fmt, ap);
+ }
+}
+
+static void
+/*VARARGS1*/
+#ifdef __STDC__
+err(const char *fmt, ...)
+#else
+err(fmt, va_alist)
+ const char *fmt;
+ va_dcl
+#endif
+{
+ va_list ap;
+
+#ifdef __STDC__
+ va_start(ap, fmt);
+#else
+ va_start(ap);
+#endif
+ vwarn(fmt, ap);
+ va_end(ap);
+
+ exit(eval);
+}
+
+static void
+/*VARARGS1*/
+#ifdef __STDC__
+warn(const char *fmt, ...)
+#else
+warn(fmt, va_alist)
+ const char *fmt;
+ va_dcl
+#endif
+{
+ va_list ap;
+
+#ifdef __STDC__
+ va_start(ap, fmt);
+#else
+ va_start(ap);
+#endif
+ vwarn(fmt, ap);
+ va_end(ap);
+}
+
+static void
+vwarn(fmt, ap)
+ const char *fmt;
+ va_list ap;
+{
+ /*
+ * Log the message to stderr.
+ *
+ * Don't use LOG_PERROR as an openlog() flag to do this,
+ * it's not portable enough.
+ */
+ if (eval != EX_USAGE)
+ (void) fprintf(stderr, "mail.local: ");
+ (void) vfprintf(stderr, fmt, ap);
+ (void) fprintf(stderr, "\n");
+
+ /* Log the message to syslog. */
+ vsyslog(LOG_ERR, fmt, ap);
+}
+
+/*
+ * e_to_sys --
+ * Guess which errno's are temporary. Gag me.
+ */
+static void
+e_to_sys(num)
+ int num;
+{
+ /* Temporary failures override hard errors. */
+ if (eval == EX_TEMPFAIL)
+ return;
+
+ switch (num) /* Hopefully temporary errors. */
+ {
+#ifdef EDQUOT
+ case EDQUOT: /* Disc quota exceeded */
+ if (bouncequota)
+ {
+ eval = EX_UNAVAILABLE;
+ break;
+ }
+ /* FALLTHROUGH */
+#endif /* EDQUOT */
+#ifdef EAGAIN
+ case EAGAIN: /* Resource temporarily unavailable */
+#endif
+#ifdef EBUSY
+ case EBUSY: /* Device busy */
+#endif
+#ifdef EPROCLIM
+ case EPROCLIM: /* Too many processes */
+#endif
+#ifdef EUSERS
+ case EUSERS: /* Too many users */
+#endif
+#ifdef ECONNABORTED
+ case ECONNABORTED: /* Software caused connection abort */
+#endif
+#ifdef ECONNREFUSED
+ case ECONNREFUSED: /* Connection refused */
+#endif
+#ifdef ECONNRESET
+ case ECONNRESET: /* Connection reset by peer */
+#endif
+#ifdef EDEADLK
+ case EDEADLK: /* Resource deadlock avoided */
+#endif
+#ifdef EFBIG
+ case EFBIG: /* File too large */
+#endif
+#ifdef EHOSTDOWN
+ case EHOSTDOWN: /* Host is down */
+#endif
+#ifdef EHOSTUNREACH
+ case EHOSTUNREACH: /* No route to host */
+#endif
+#ifdef EMFILE
+ case EMFILE: /* Too many open files */
+#endif
+#ifdef ENETDOWN
+ case ENETDOWN: /* Network is down */
+#endif
+#ifdef ENETRESET
+ case ENETRESET: /* Network dropped connection on reset */
+#endif
+#ifdef ENETUNREACH
+ case ENETUNREACH: /* Network is unreachable */
+#endif
+#ifdef ENFILE
+ case ENFILE: /* Too many open files in system */
+#endif
+#ifdef ENOBUFS
+ case ENOBUFS: /* No buffer space available */
+#endif
+#ifdef ENOMEM
+ case ENOMEM: /* Cannot allocate memory */
+#endif
+#ifdef ENOSPC
+ case ENOSPC: /* No space left on device */
+#endif
+#ifdef EROFS
+ case EROFS: /* Read-only file system */
+#endif
+#ifdef ESTALE
+ case ESTALE: /* Stale NFS file handle */
+#endif
+#ifdef ETIMEDOUT
+ case ETIMEDOUT: /* Connection timed out */
+#endif
+#if defined(EWOULDBLOCK) && (EWOULDBLOCK != EAGAIN)
+ case EWOULDBLOCK: /* Operation would block. */
+#endif
+ eval = EX_TEMPFAIL;
+ break;
+ default:
+ eval = EX_UNAVAILABLE;
+ break;
+ }
+}
diff --git a/usr/src/cmd/sendmail/aux/mailcompat.c b/usr/src/cmd/sendmail/aux/mailcompat.c
new file mode 100644
index 0000000000..8b244b455a
--- /dev/null
+++ b/usr/src/cmd/sendmail/aux/mailcompat.c
@@ -0,0 +1,340 @@
+/*
+ * Copyright 2005 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ *
+ * Copyright (c) 1983, 1984, 1986, 1986, 1987, 1988, 1989 AT&T
+ * All Rights Reserved
+ */
+
+/*
+ * Vacation
+ * Copyright (c) 1983 Eric P. Allman
+ * Berkeley, California
+ *
+ * Copyright (c) 1983 Regents of the University of California.
+ * All rights reserved. The Berkeley software License Agreement
+ * specifies the terms and conditions for redistribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <pwd.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sysexits.h>
+#include <string.h>
+#include <ctype.h>
+#include "conf.h"
+
+/*
+ * MAILCOMPAT -- Deliver mail to a user's mailbox with the "From's" stuffed.
+ */
+
+typedef int bool;
+
+#define FALSE 0
+#define TRUE 1
+
+bool Debug = FALSE;
+char *myname; /* person who is to have their mail filtered */
+char *homedir; /* home directory of said person */
+char *AliasList[MAXLINE]; /* list of aliases to allow */
+char *fromp;
+char *fromuser;
+int AliasCount = 0;
+
+static char *newstr();
+
+int ask(char *);
+int sendmessage(char *);
+void AutoInstall(void);
+void usrerr(const char *, ...);
+
+int
+main(argc, argv)
+ int argc;
+ char **argv;
+{
+ register char *p;
+ struct passwd *pw;
+ extern char *getfrom();
+
+ /* process arguments */
+ while (--argc > 0 && (p = *++argv) != NULL && *p == '-')
+ {
+ switch (*++p)
+ {
+ case 'd': /* debug */
+ Debug = TRUE;
+ break;
+ default:
+ usrerr("Unknown flag -%s", p);
+ exit(EX_USAGE);
+ }
+ }
+
+ /* verify recipient argument */
+ if (argc != 1)
+ {
+ if (argc == 0)
+ AutoInstall();
+ else
+ usrerr("Usage: mailcompat username (or) mailcompat -r");
+ exit(EX_USAGE);
+ }
+
+ myname = p;
+ /* find user's home directory */
+ pw = getpwnam(myname);
+ if (pw == NULL)
+ {
+ usrerr("user: %s look up failed, name services outage ?", myname);
+ exit(EX_TEMPFAIL);
+ }
+ homedir = newstr(pw->pw_dir);
+
+ /* read message from standard input (just from line) */
+ fromuser = getfrom(&fromp);
+ return (sendmessage(fromuser));
+}
+
+/*
+** sendmessage -- read message from standard input do the from stuffing
+** and forward to /bin/mail, Being sure to delete any
+** content-length headers (/bin/mail recalculates them).
+**
+**
+** Parameters:
+** none.
+**
+**
+** Side Effects:
+** Reads first line from standard input.
+*/
+
+#define L_HEADER "Content-Length:"
+#define LL_HEADER 15
+
+int
+sendmessage(from)
+char *from;
+{
+ static char line[MAXLINE];
+ static char command[MAXLINE];
+ bool in_body = FALSE;
+ FILE *mail_fp;
+ static char user_name[L_cuserid];
+
+ if (from == NULL)
+ from = cuserid(user_name);
+
+ snprintf(command, sizeof (command), "/bin/mail -f %s -d %s", from,
+ myname);
+ mail_fp = popen(command, "w");
+
+ /* read the line */
+ while (fgets(line, sizeof line, stdin) != NULL)
+ {
+ if (line[0] == (char)'\n') /* end of mail headers */
+ in_body = TRUE;
+ if (in_body && (strncmp(line, "From ", 5) == 0))
+ fprintf(mail_fp, ">");
+ if (in_body || (strncasecmp(line, L_HEADER, LL_HEADER) != 0))
+ fputs(line, mail_fp);
+ }
+ return (pclose(mail_fp));
+}
+
+char *
+getfrom(shortp)
+char **shortp;
+{
+ static char line[MAXLINE];
+ register char *p, *start, *at, *bang;
+ char saveat;
+
+ /* read the from line */
+ if (fgets(line, sizeof line, stdin) == NULL ||
+ strncmp(line, "From ", 5) != NULL)
+ {
+ usrerr("No initial From line");
+ exit(EX_USAGE);
+ }
+
+ /* find the end of the sender address and terminate it */
+ start = &line[5];
+ p = strchr(start, ' ');
+ if (p == NULL)
+ {
+ usrerr("Funny From line '%s'", line);
+ exit(EX_USAGE);
+ }
+ *p = '\0';
+
+ /*
+ * Strip all but the rightmost UUCP host
+ * to prevent loops due to forwarding.
+ * Start searching leftward from the leftmost '@'.
+ * a!b!c!d yields a short name of c!d
+ * a!b!c!d@e yields a short name of c!d@e
+ * e@a!b!c yields the same short name
+ */
+#ifdef VDEBUG
+printf("start='%s'\n", start);
+#endif /* VDEBUG */
+ *shortp = start; /* assume whole addr */
+ if ((at = strchr(start, '@')) == NULL) /* leftmost '@' */
+ at = p; /* if none, use end of addr */
+ saveat = *at;
+ *at = '\0';
+ if ((bang = strrchr(start, '!')) != NULL) { /* rightmost '!' */
+ char *bang2;
+ *bang = '\0';
+ if ((bang2 = strrchr(start, '!')) != NULL) /* 2nd rightmost '!' */
+ *shortp = bang2 + 1; /* move past ! */
+ *bang = '!';
+ }
+ *at = saveat;
+#ifdef VDEBUG
+printf("place='%s'\n", *shortp);
+#endif /* VDEBUG */
+
+ /* return the sender address */
+ return newstr(start);
+}
+
+/*
+** USRERR -- print user error
+**
+** Parameters:
+** f -- format.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** none.
+*/
+
+void
+usrerr(const char *f, ...)
+{
+ va_list alist;
+
+ va_start(alist, f);
+ (void) fprintf(stderr, "mailcompat: ");
+ (void) vfprintf(stderr, f, alist);
+ (void) fprintf(stderr, "\n");
+ va_end(alist);
+}
+
+/*
+** NEWSTR -- copy a string
+**
+** Parameters:
+** s -- the string to copy.
+**
+** Returns:
+** A copy of the string.
+**
+** Side Effects:
+** none.
+*/
+
+char *
+newstr(s)
+ char *s;
+{
+ char *p;
+ size_t psize = strlen(s) + 1;
+
+ p = malloc(psize);
+ if (p == NULL)
+ {
+ usrerr("newstr: cannot alloc memory");
+ exit(EX_OSERR);
+ }
+ strlcpy(p, s, psize);
+ return (p);
+}
+
+/*
+ * When invoked with no arguments, we fall into an automatic installation
+ * mode, stepping the user through a default installation.
+ */
+void
+AutoInstall()
+{
+ char forward[MAXLINE];
+ char line[MAXLINE];
+ static char user_name[L_cuserid];
+ FILE *f;
+
+ myname = cuserid(user_name);
+ homedir = getenv("HOME");
+ if (homedir == NULL) {
+ usrerr("Home directory unknown");
+ exit(EX_CONFIG);
+ }
+
+ printf("This program can be used to store your mail in a format\n");
+ printf("that you can read with SunOS 4.X based mail readers\n");
+ (void) strlcpy(forward, homedir, sizeof (forward));
+ (void) strlcat(forward, "/.forward", sizeof (forward));
+ f = fopen(forward, "r");
+ if (f) {
+ printf("You have a .forward file in your home directory");
+ printf(" containing:\n");
+ while (fgets(line, MAXLINE, f))
+ printf(" %s", line);
+ fclose(f);
+ if (!ask("Would you like to remove it and disable the mailcompat feature"))
+ exit(0);
+ if (unlink(forward))
+ perror("Error removing .forward file:");
+ else
+ printf("Back to normal reception of mail.\n");
+ exit(0);
+ }
+
+ printf("To enable the mailcompat feature a \".forward\" ");
+ printf("file is created.\n");
+ if (!ask("Would you like to enable the mailcompat feature")) {
+ printf("OK, mailcompat feature NOT enabled.\n");
+ exit(0);
+ }
+ f = fopen(forward, "w");
+ if (f == NULL) {
+ perror("Error opening .forward file");
+ exit(EX_USAGE);
+ }
+ fprintf(f, "\"|/usr/bin/mailcompat %s\"\n", myname);
+ fclose(f);
+ printf("Mailcompat feature ENABLED.");
+ printf("Run mailcompat with no arguments to remove it\n");
+}
+
+
+/*
+ * Ask the user a question until we get a reasonable answer
+ */
+int
+ask(prompt)
+ char *prompt;
+{
+ char line[MAXLINE];
+
+ for (;;) {
+ printf("%s? ", prompt);
+ fflush(stdout);
+ fgets(line, sizeof (line), stdin);
+ if (line[0] == 'y' || line[0] == 'Y')
+ return (TRUE);
+ if (line[0] == 'n' || line[0] == 'N')
+ return (FALSE);
+ printf("Please reply \"yes\" or \"no\" (\'y\' or \'n\')\n");
+ }
+ /* NOTREACHED */
+}
diff --git a/usr/src/cmd/sendmail/aux/mailq.c b/usr/src/cmd/sendmail/aux/mailq.c
new file mode 100644
index 0000000000..11ff6130c9
--- /dev/null
+++ b/usr/src/cmd/sendmail/aux/mailq.c
@@ -0,0 +1,62 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ * Copyright 2005 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <auth_attr.h>
+#include <secdb.h>
+#include <pwd.h>
+#include <unistd.h>
+#include <sysexits.h>
+#include <errno.h>
+#include <auth_list.h>
+
+#define _PATH_SENDMAIL_BIN "/usr/lib/sendmail"
+
+int
+main(int argc, char *argv[], char *envp[])
+{
+ struct passwd *pw = getpwuid(getuid());
+ char **newargv;
+ int j;
+
+ if (pw && chkauthattr(MAILQ_AUTH, pw->pw_name)) {
+ newargv = (char **)malloc((argc + 1) * sizeof (char *));
+ if (newargv == NULL)
+ exit(EX_UNAVAILABLE);
+ newargv[0] = _PATH_SENDMAIL_BIN;
+ newargv[1] = "-bp";
+ for (j = 1; j <= argc; j++)
+ newargv[j + 1] = argv[j];
+ (void) execve(_PATH_SENDMAIL_BIN, newargv, envp);
+ perror("Cannot exec " _PATH_SENDMAIL_BIN);
+ exit(EX_OSERR);
+ }
+ (void) fputs("No authorization to run mailq; "
+ "see mailq(1) for details.\n", stderr);
+ return (EX_NOPERM);
+}
diff --git a/usr/src/cmd/sendmail/aux/mailstats.c b/usr/src/cmd/sendmail/aux/mailstats.c
new file mode 100644
index 0000000000..f7870aa7de
--- /dev/null
+++ b/usr/src/cmd/sendmail/aux/mailstats.c
@@ -0,0 +1,370 @@
+/*
+ * Copyright (c) 1998-2002 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+
+SM_IDSTR(copyright,
+"@(#) Copyright (c) 1998-2002 Sendmail, Inc. and its suppliers.\n\
+ All rights reserved.\n\
+ Copyright (c) 1988, 1993\n\
+ The Regents of the University of California. All rights reserved.\n")
+
+SM_IDSTR(id, "@(#)$Id: mailstats.c,v 8.100 2002/06/27 23:24:06 gshapiro Exp $")
+
+#include <unistd.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+#include <time.h>
+#ifdef EX_OK
+# undef EX_OK /* unistd.h may have another use for this */
+#endif /* EX_OK */
+#include <sysexits.h>
+
+#include <sm/errstring.h>
+#include <sm/limits.h>
+#include <sendmail/sendmail.h>
+#include <sendmail/mailstats.h>
+#include <sendmail/pathnames.h>
+
+
+#define MNAMELEN 20 /* max length of mailer name */
+
+int
+main(argc, argv)
+ int argc;
+ char **argv;
+{
+ register int i;
+ int mno;
+ int save_errno;
+ int ch, fd;
+ char *sfile;
+ char *cfile;
+ SM_FILE_T *cfp;
+ bool mnames;
+ bool progmode;
+ bool trunc;
+ long frmsgs = 0, frbytes = 0, tomsgs = 0, tobytes = 0, rejmsgs = 0;
+ long dismsgs = 0;
+ long quarmsgs = 0;
+ time_t now;
+ char mtable[MAXMAILERS][MNAMELEN + 1];
+ char sfilebuf[MAXPATHLEN];
+ char buf[MAXLINE];
+ struct statistics stats;
+ extern char *ctime();
+ extern char *optarg;
+ extern int optind;
+
+ cfile = getcfname(0, 0, SM_GET_SENDMAIL_CF, NULL);
+ sfile = NULL;
+ mnames = true;
+ progmode = false;
+ trunc = false;
+ while ((ch = getopt(argc, argv, "cC:f:opP")) != -1)
+ {
+ switch (ch)
+ {
+ case 'c':
+ cfile = getcfname(0, 0, SM_GET_SUBMIT_CF, NULL);
+ break;
+
+ case 'C':
+ cfile = optarg;
+ break;
+
+ case 'f':
+ sfile = optarg;
+ break;
+
+ case 'o':
+ mnames = false;
+ break;
+
+ case 'p':
+ trunc = true;
+ /* FALLTHROUGH */
+
+ case 'P':
+ progmode = true;
+ break;
+
+ case '?':
+ default:
+ usage:
+ (void) sm_io_fputs(smioerr, SM_TIME_DEFAULT,
+ "usage: mailstats [-C cffile] [-c] [-P] [-f stfile] [-o] [-p]\n");
+ exit(EX_USAGE);
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 0)
+ goto usage;
+
+ if ((cfp = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, cfile, SM_IO_RDONLY,
+ NULL)) == NULL)
+ {
+ save_errno = errno;
+ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, "mailstats: ");
+ errno = save_errno;
+ sm_perror(cfile);
+ exit(EX_NOINPUT);
+ }
+
+ mno = 0;
+ (void) sm_strlcpy(mtable[mno++], "prog", MNAMELEN + 1);
+ (void) sm_strlcpy(mtable[mno++], "*file*", MNAMELEN + 1);
+ (void) sm_strlcpy(mtable[mno++], "*include*", MNAMELEN + 1);
+
+ while (sm_io_fgets(cfp, SM_TIME_DEFAULT, buf, sizeof(buf)) != NULL)
+ {
+ register char *b;
+ char *s;
+ register char *m;
+
+ b = strchr(buf, '#');
+ if (b == NULL)
+ b = strchr(buf, '\n');
+ if (b == NULL)
+ b = &buf[strlen(buf)];
+ while (isascii(*--b) && isspace(*b))
+ continue;
+ *++b = '\0';
+
+ b = buf;
+ switch (*b++)
+ {
+ case 'M': /* mailer definition */
+ break;
+
+ case 'O': /* option -- see if .st file */
+ if (sm_strncasecmp(b, " StatusFile", 11) == 0 &&
+ !(isascii(b[11]) && isalnum(b[11])))
+ {
+ /* new form -- find value */
+ b = strchr(b, '=');
+ if (b == NULL)
+ continue;
+ while (isascii(*++b) && isspace(*b))
+ continue;
+ }
+ else if (*b++ != 'S')
+ {
+ /* something else boring */
+ continue;
+ }
+
+ /* this is the S or StatusFile option -- save it */
+ if (sm_strlcpy(sfilebuf, b, sizeof sfilebuf) >=
+ sizeof sfilebuf)
+ {
+ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ "StatusFile filename too long: %.30s...\n",
+ b);
+ exit(EX_CONFIG);
+ }
+ if (sfile == NULL)
+ sfile = sfilebuf;
+
+ default:
+ continue;
+ }
+
+ if (mno >= MAXMAILERS)
+ {
+ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ "Too many mailers defined, %d max.\n",
+ MAXMAILERS);
+ exit(EX_SOFTWARE);
+ }
+ m = mtable[mno];
+ s = m + MNAMELEN; /* is [MNAMELEN + 1] */
+ while (*b != ',' && !(isascii(*b) && isspace(*b)) &&
+ *b != '\0' && m < s)
+ *m++ = *b++;
+ *m = '\0';
+ for (i = 0; i < mno; i++)
+ {
+ if (strcmp(mtable[i], mtable[mno]) == 0)
+ break;
+ }
+ if (i == mno)
+ mno++;
+ }
+ (void) sm_io_close(cfp, SM_TIME_DEFAULT);
+ for (; mno < MAXMAILERS; mno++)
+ mtable[mno][0] = '\0';
+
+ if (sfile == NULL)
+ {
+ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ "mailstats: no statistics file located\n");
+ exit(EX_OSFILE);
+ }
+
+ fd = open(sfile, O_RDONLY, 0600);
+ if ((fd < 0) || (i = read(fd, &stats, sizeof stats)) < 0)
+ {
+ save_errno = errno;
+ (void) sm_io_fputs(smioerr, SM_TIME_DEFAULT, "mailstats: ");
+ errno = save_errno;
+ sm_perror(sfile);
+ exit(EX_NOINPUT);
+ }
+ if (i == 0)
+ {
+ (void) sleep(1);
+ if ((i = read(fd, &stats, sizeof stats)) < 0)
+ {
+ save_errno = errno;
+ (void) sm_io_fputs(smioerr, SM_TIME_DEFAULT,
+ "mailstats: ");
+ errno = save_errno;
+ sm_perror(sfile);
+ exit(EX_NOINPUT);
+ }
+ else if (i == 0)
+ {
+ memset((ARBPTR_T) &stats, '\0', sizeof stats);
+ (void) time(&stats.stat_itime);
+ }
+ }
+ if (i != 0)
+ {
+ if (stats.stat_magic != STAT_MAGIC)
+ {
+ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ "mailstats: incorrect magic number in %s\n",
+ sfile);
+ exit(EX_OSERR);
+ }
+ else if (stats.stat_version != STAT_VERSION)
+ {
+ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ "mailstats version (%d) incompatible with %s version (%d)\n",
+ STAT_VERSION, sfile,
+ stats.stat_version);
+
+ exit(EX_OSERR);
+ }
+ else if (i != sizeof stats || stats.stat_size != sizeof(stats))
+ {
+ (void) sm_io_fputs(smioerr, SM_TIME_DEFAULT,
+ "mailstats: file size changed.\n");
+ exit(EX_OSERR);
+ }
+ }
+
+ if (progmode)
+ {
+ (void) time(&now);
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%ld %ld\n",
+ (long) stats.stat_itime, (long) now);
+ }
+ else
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Statistics from %s",
+ ctime(&stats.stat_itime));
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ " M msgsfr bytes_from msgsto bytes_to msgsrej msgsdis");
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, " msgsqur");
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%s\n",
+ mnames ? " Mailer" : "");
+ }
+ for (i = 0; i < MAXMAILERS; i++)
+ {
+ if (stats.stat_nf[i] || stats.stat_nt[i] ||
+ stats.stat_nq[i] ||
+ stats.stat_nr[i] || stats.stat_nd[i])
+ {
+ char *format;
+
+ if (progmode)
+ format = "%2d %8ld %10ld %8ld %10ld %6ld %6ld";
+ else
+ format = "%2d %8ld %10ldK %8ld %10ldK %6ld %6ld";
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ format, i,
+ stats.stat_nf[i],
+ stats.stat_bf[i],
+ stats.stat_nt[i],
+ stats.stat_bt[i],
+ stats.stat_nr[i],
+ stats.stat_nd[i]);
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ " %6ld", stats.stat_nq[i]);
+ if (mnames)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ " %s",
+ mtable[i]);
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "\n");
+ frmsgs += stats.stat_nf[i];
+ frbytes += stats.stat_bf[i];
+ tomsgs += stats.stat_nt[i];
+ tobytes += stats.stat_bt[i];
+ rejmsgs += stats.stat_nr[i];
+ dismsgs += stats.stat_nd[i];
+ quarmsgs += stats.stat_nq[i];
+ }
+ }
+ if (progmode)
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ " T %8ld %10ld %8ld %10ld %6ld %6ld",
+ frmsgs, frbytes, tomsgs, tobytes, rejmsgs,
+ dismsgs);
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ " %6ld", quarmsgs);
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "\n");
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ " C %8ld %8ld %6ld\n",
+ stats.stat_cf, stats.stat_ct,
+ stats.stat_cr);
+ (void) close(fd);
+ if (trunc)
+ {
+ fd = open(sfile, O_RDWR | O_TRUNC, 0600);
+ if (fd >= 0)
+ (void) close(fd);
+ }
+ }
+ else
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "=============================================================");
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "========");
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "\n");
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ " T %8ld %10ldK %8ld %10ldK %6ld %6ld",
+ frmsgs, frbytes, tomsgs, tobytes, rejmsgs,
+ dismsgs);
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ " %6ld", quarmsgs);
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "\n");
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ " C %8ld %10s %8ld %10s %6ld\n",
+ stats.stat_cf, "", stats.stat_ct, "",
+ stats.stat_cr);
+ }
+ exit(EX_OK);
+ /* NOTREACHED */
+ return EX_OK;
+}
diff --git a/usr/src/cmd/sendmail/aux/makemap.c b/usr/src/cmd/sendmail/aux/makemap.c
new file mode 100644
index 0000000000..03a7375eb9
--- /dev/null
+++ b/usr/src/cmd/sendmail/aux/makemap.c
@@ -0,0 +1,530 @@
+/*
+ * Copyright (c) 1998-2002, 2004 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1992 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+
+SM_IDSTR(copyright,
+"@(#) Copyright (c) 1998-2002, 2004 Sendmail, Inc. and its suppliers.\n\
+ All rights reserved.\n\
+ Copyright (c) 1992 Eric P. Allman. All rights reserved.\n\
+ Copyright (c) 1992, 1993\n\
+ The Regents of the University of California. All rights reserved.\n")
+
+SM_IDSTR(id, "@(#)$Id: makemap.c,v 8.177 2004/08/03 23:57:24 ca Exp $")
+
+
+#include <sys/types.h>
+#ifndef ISC_UNIX
+# include <sys/file.h>
+#endif /* ! ISC_UNIX */
+#include <ctype.h>
+#include <stdlib.h>
+#include <unistd.h>
+#ifdef EX_OK
+# undef EX_OK /* unistd.h may have another use for this */
+#endif /* EX_OK */
+#include <sysexits.h>
+#include <sendmail/sendmail.h>
+#include <sendmail/pathnames.h>
+#include <libsmdb/smdb.h>
+
+uid_t RealUid;
+gid_t RealGid;
+char *RealUserName;
+uid_t RunAsUid;
+uid_t RunAsGid;
+char *RunAsUserName;
+int Verbose = 2;
+bool DontInitGroups = false;
+uid_t TrustedUid = 0;
+BITMAP256 DontBlameSendmail;
+
+#define BUFSIZE 1024
+#define ISSEP(c) (sep == '\0' ? isascii(c) && isspace(c) : (c) == sep)
+
+static void usage __P((char *));
+
+static void
+usage(progname)
+ char *progname;
+{
+ sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ "Usage: %s [-C cffile] [-N] [-c cachesize] [-D commentchar]\n",
+ progname);
+ sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ " %*s [-d] [-e] [-f] [-l] [-o] [-r] [-s] [-t delimiter]\n",
+ (int) strlen(progname), "");
+ sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ " %*s [-u] [-v] type mapname\n",
+ (int) strlen(progname), "");
+ exit(EX_USAGE);
+}
+
+int
+main(argc, argv)
+ int argc;
+ char **argv;
+{
+ char *progname;
+ char *cfile;
+ bool inclnull = false;
+ bool notrunc = false;
+ bool allowreplace = false;
+ bool allowempty = false;
+ bool verbose = false;
+ bool foldcase = true;
+ bool unmake = false;
+ char sep = '\0';
+ char comment = '#';
+ int exitstat;
+ int opt;
+ char *typename = NULL;
+ char *mapname = NULL;
+ unsigned int lineno;
+ int st;
+ int mode;
+ int smode;
+ int putflags = 0;
+ long sff = SFF_ROOTOK|SFF_REGONLY;
+ struct passwd *pw;
+ SMDB_DATABASE *database;
+ SMDB_CURSOR *cursor;
+ SMDB_DBENT db_key, db_val;
+ SMDB_DBPARAMS params;
+ SMDB_USER_INFO user_info;
+ char ibuf[BUFSIZE];
+#if HASFCHOWN
+ SM_FILE_T *cfp;
+ char buf[MAXLINE];
+#endif /* HASFCHOWN */
+ static char rnamebuf[MAXNAME]; /* holds RealUserName */
+ extern char *optarg;
+ extern int optind;
+
+ memset(&params, '\0', sizeof params);
+ params.smdbp_cache_size = 1024 * 1024;
+
+ progname = strrchr(argv[0], '/');
+ if (progname != NULL)
+ progname++;
+ else
+ progname = argv[0];
+ cfile = getcfname(0, 0, SM_GET_SENDMAIL_CF, NULL);
+
+ clrbitmap(DontBlameSendmail);
+ RunAsUid = RealUid = getuid();
+ RunAsGid = RealGid = getgid();
+ pw = getpwuid(RealUid);
+ if (pw != NULL)
+ (void) sm_strlcpy(rnamebuf, pw->pw_name, sizeof rnamebuf);
+ else
+ (void) sm_snprintf(rnamebuf, sizeof rnamebuf,
+ "Unknown UID %d", (int) RealUid);
+ RunAsUserName = RealUserName = rnamebuf;
+ user_info.smdbu_id = RunAsUid;
+ user_info.smdbu_group_id = RunAsGid;
+ (void) sm_strlcpy(user_info.smdbu_name, RunAsUserName,
+ SMDB_MAX_USER_NAME_LEN);
+
+#define OPTIONS "C:D:Nc:deflorst:uv"
+ while ((opt = getopt(argc, argv, OPTIONS)) != -1)
+ {
+ switch (opt)
+ {
+ case 'C':
+ cfile = optarg;
+ break;
+
+ case 'N':
+ inclnull = true;
+ break;
+
+ case 'c':
+ params.smdbp_cache_size = atol(optarg);
+ break;
+
+ case 'd':
+ params.smdbp_allow_dup = true;
+ break;
+
+ case 'e':
+ allowempty = true;
+ break;
+
+ case 'f':
+ foldcase = false;
+ break;
+
+ case 'D':
+ comment = *optarg;
+ break;
+
+ case 'l':
+ smdb_print_available_types();
+ exit(EX_OK);
+ break;
+
+ case 'o':
+ notrunc = true;
+ break;
+
+ case 'r':
+ allowreplace = true;
+ break;
+
+ case 's':
+ setbitn(DBS_MAPINUNSAFEDIRPATH, DontBlameSendmail);
+ setbitn(DBS_WRITEMAPTOHARDLINK, DontBlameSendmail);
+ setbitn(DBS_WRITEMAPTOSYMLINK, DontBlameSendmail);
+ setbitn(DBS_LINKEDMAPINWRITABLEDIR, DontBlameSendmail);
+ break;
+
+ case 't':
+ if (optarg == NULL || *optarg == '\0')
+ {
+ sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ "Invalid separator\n");
+ break;
+ }
+ sep = *optarg;
+ break;
+
+ case 'u':
+ unmake = true;
+ break;
+
+ case 'v':
+ verbose = true;
+ break;
+
+ default:
+ usage(progname);
+ /* NOTREACHED */
+ }
+ }
+
+ if (!bitnset(DBS_WRITEMAPTOSYMLINK, DontBlameSendmail))
+ sff |= SFF_NOSLINK;
+ if (!bitnset(DBS_WRITEMAPTOHARDLINK, DontBlameSendmail))
+ sff |= SFF_NOHLINK;
+ if (!bitnset(DBS_LINKEDMAPINWRITABLEDIR, DontBlameSendmail))
+ sff |= SFF_NOWLINK;
+
+ argc -= optind;
+ argv += optind;
+ if (argc != 2)
+ {
+ usage(progname);
+ /* NOTREACHED */
+ }
+ else
+ {
+ typename = argv[0];
+ mapname = argv[1];
+ }
+
+#if HASFCHOWN
+ /* Find TrustedUser value in sendmail.cf */
+ if ((cfp = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, cfile, SM_IO_RDONLY,
+ NULL)) == NULL)
+ {
+ sm_io_fprintf(smioerr, SM_TIME_DEFAULT, "makemap: %s: %s",
+ cfile, sm_errstring(errno));
+ exit(EX_NOINPUT);
+ }
+ while (sm_io_fgets(cfp, SM_TIME_DEFAULT, buf, sizeof(buf)) != NULL)
+ {
+ register char *b;
+
+ if ((b = strchr(buf, '\n')) != NULL)
+ *b = '\0';
+
+ b = buf;
+ switch (*b++)
+ {
+ case 'O': /* option */
+ if (strncasecmp(b, " TrustedUser", 12) == 0 &&
+ !(isascii(b[12]) && isalnum(b[12])))
+ {
+ b = strchr(b, '=');
+ if (b == NULL)
+ continue;
+ while (isascii(*++b) && isspace(*b))
+ continue;
+ if (isascii(*b) && isdigit(*b))
+ TrustedUid = atoi(b);
+ else
+ {
+ TrustedUid = 0;
+ pw = getpwnam(b);
+ if (pw == NULL)
+ (void) sm_io_fprintf(smioerr,
+ SM_TIME_DEFAULT,
+ "TrustedUser: unknown user %s\n", b);
+ else
+ TrustedUid = pw->pw_uid;
+ }
+
+# ifdef UID_MAX
+ if (TrustedUid > UID_MAX)
+ {
+ (void) sm_io_fprintf(smioerr,
+ SM_TIME_DEFAULT,
+ "TrustedUser: uid value (%ld) > UID_MAX (%ld)",
+ (long) TrustedUid,
+ (long) UID_MAX);
+ TrustedUid = 0;
+ }
+# endif /* UID_MAX */
+ break;
+ }
+
+
+ default:
+ continue;
+ }
+ }
+ (void) sm_io_close(cfp, SM_TIME_DEFAULT);
+#endif /* HASFCHOWN */
+
+ if (!params.smdbp_allow_dup && !allowreplace)
+ putflags = SMDBF_NO_OVERWRITE;
+
+ if (unmake)
+ {
+ mode = O_RDONLY;
+ smode = S_IRUSR;
+ }
+ else
+ {
+ mode = O_RDWR;
+ if (!notrunc)
+ {
+ mode |= O_CREAT|O_TRUNC;
+ sff |= SFF_CREAT;
+ }
+ smode = S_IWUSR;
+ }
+
+ params.smdbp_num_elements = 4096;
+
+ errno = smdb_open_database(&database, mapname, mode, smode, sff,
+ typename, &user_info, &params);
+ if (errno != SMDBE_OK)
+ {
+ char *hint;
+
+ if (errno == SMDBE_UNSUPPORTED_DB_TYPE &&
+ (hint = smdb_db_definition(typename)) != NULL)
+ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ "%s: Need to recompile with -D%s for %s support\n",
+ progname, hint, typename);
+ else
+ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ "%s: error opening type %s map %s: %s\n",
+ progname, typename, mapname,
+ sm_errstring(errno));
+ exit(EX_CANTCREAT);
+ }
+
+ (void) database->smdb_sync(database, 0);
+
+ if (!unmake && geteuid() == 0 && TrustedUid != 0)
+ {
+ errno = database->smdb_set_owner(database, TrustedUid, -1);
+ if (errno != SMDBE_OK)
+ {
+ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ "WARNING: ownership change on %s failed %s",
+ mapname, sm_errstring(errno));
+ }
+ }
+
+ /*
+ ** Copy the data
+ */
+
+ exitstat = EX_OK;
+ if (unmake)
+ {
+ errno = database->smdb_cursor(database, &cursor, 0);
+ if (errno != SMDBE_OK)
+ {
+
+ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ "%s: cannot make cursor for type %s map %s\n",
+ progname, typename, mapname);
+ exit(EX_SOFTWARE);
+ }
+
+ memset(&db_key, '\0', sizeof db_key);
+ memset(&db_val, '\0', sizeof db_val);
+
+ for (lineno = 0; ; lineno++)
+ {
+ errno = cursor->smdbc_get(cursor, &db_key, &db_val,
+ SMDB_CURSOR_GET_NEXT);
+ if (errno != SMDBE_OK)
+ break;
+
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "%.*s\t%.*s\n",
+ (int) db_key.size,
+ (char *) db_key.data,
+ (int) db_val.size,
+ (char *)db_val.data);
+
+ }
+ (void) cursor->smdbc_close(cursor);
+ }
+ else
+ {
+ lineno = 0;
+ while (sm_io_fgets(smioin, SM_TIME_DEFAULT, ibuf, sizeof ibuf)
+ != NULL)
+ {
+ register char *p;
+
+ lineno++;
+
+ /*
+ ** Parse the line.
+ */
+
+ p = strchr(ibuf, '\n');
+ if (p != NULL)
+ *p = '\0';
+ else if (!sm_io_eof(smioin))
+ {
+ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ "%s: %s: line %u: line too long (%ld bytes max)\n",
+ progname, mapname, lineno,
+ (long) sizeof ibuf);
+ exitstat = EX_DATAERR;
+ continue;
+ }
+
+ if (ibuf[0] == '\0' || ibuf[0] == comment)
+ continue;
+ if (sep == '\0' && isascii(ibuf[0]) && isspace(ibuf[0]))
+ {
+ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ "%s: %s: line %u: syntax error (leading space)\n",
+ progname, mapname, lineno);
+ exitstat = EX_DATAERR;
+ continue;
+ }
+
+ memset(&db_key, '\0', sizeof db_key);
+ memset(&db_val, '\0', sizeof db_val);
+ db_key.data = ibuf;
+
+ for (p = ibuf; *p != '\0' && !(ISSEP(*p)); p++)
+ {
+ if (foldcase && isascii(*p) && isupper(*p))
+ *p = tolower(*p);
+ }
+ db_key.size = p - ibuf;
+ if (inclnull)
+ db_key.size++;
+
+ if (*p != '\0')
+ *p++ = '\0';
+ while (*p != '\0' && ISSEP(*p))
+ p++;
+ if (!allowempty && *p == '\0')
+ {
+ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ "%s: %s: line %u: no RHS for LHS %s\n",
+ progname, mapname, lineno,
+ (char *) db_key.data);
+ exitstat = EX_DATAERR;
+ continue;
+ }
+
+ db_val.data = p;
+ db_val.size = strlen(p);
+ if (inclnull)
+ db_val.size++;
+
+ /*
+ ** Do the database insert.
+ */
+
+ if (verbose)
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "key=`%s', val=`%s'\n",
+ (char *) db_key.data,
+ (char *) db_val.data);
+ }
+
+ errno = database->smdb_put(database, &db_key, &db_val,
+ putflags);
+ switch (errno)
+ {
+ case SMDBE_KEY_EXIST:
+ st = 1;
+ break;
+
+ case 0:
+ st = 0;
+ break;
+
+ default:
+ st = -1;
+ break;
+ }
+
+ if (st < 0)
+ {
+ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ "%s: %s: line %u: key %s: put error: %s\n",
+ progname, mapname, lineno,
+ (char *) db_key.data,
+ sm_errstring(errno));
+ exitstat = EX_IOERR;
+ }
+ else if (st > 0)
+ {
+ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ "%s: %s: line %u: key %s: duplicate key\n",
+ progname, mapname,
+ lineno,
+ (char *) db_key.data);
+ exitstat = EX_DATAERR;
+ }
+ }
+ }
+
+ /*
+ ** Now close the database.
+ */
+
+ errno = database->smdb_close(database);
+ if (errno != SMDBE_OK)
+ {
+ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ "%s: close(%s): %s\n",
+ progname, mapname, sm_errstring(errno));
+ exitstat = EX_IOERR;
+ }
+ smdb_free_database(database);
+
+ exit(exitstat);
+
+ /* NOTREACHED */
+ return exitstat;
+}
diff --git a/usr/src/cmd/sendmail/aux/mconnect.c b/usr/src/cmd/sendmail/aux/mconnect.c
new file mode 100644
index 0000000000..3a4f1fa0e1
--- /dev/null
+++ b/usr/src/cmd/sendmail/aux/mconnect.c
@@ -0,0 +1,240 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ * Copyright 2005 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+ * mconnect.c - A program to test out SMTP connections.
+ * Usage: mconnect [host]
+ * ... SMTP dialog
+ * ^C or ^D or QUIT
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <ctype.h>
+#include <string.h>
+#include <strings.h>
+#include <sgtty.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <arpa/nameser.h>
+#include <arpa/inet.h>
+#include <errno.h>
+
+union bigsockaddr
+{
+ struct sockaddr sa; /* general version */
+ struct sockaddr_in sin; /* INET family */
+ struct sockaddr_in6 sin6; /* INET/IPv6 */
+};
+
+static struct sgttyb TtyBuf;
+static int raw = 0;
+
+/* ARGSUSED */
+static void
+finis(sig)
+ int sig;
+{
+ if (raw)
+ (void) ioctl(0, TIOCSETP, &TtyBuf);
+ exit(0);
+}
+
+int
+main(argc, argv)
+ int argc;
+ char **argv;
+{
+ union bigsockaddr SendmailAddress;
+ register int s;
+ char *host = NULL;
+ int pid;
+ int on = 1;
+ struct servent *sp;
+ char buf[1000];
+ register FILE *f;
+ register struct hostent *hp;
+ in_port_t port = 0;
+ int err;
+ char buf6[INET6_ADDRSTRLEN];
+ int addr_num = 0;
+ int addrlen;
+
+ (void) ioctl(0, TIOCGETP, &TtyBuf);
+ (void) signal(SIGINT, finis);
+
+ while (--argc > 0)
+ {
+ register char *p;
+
+ p = *++argv;
+ if (*p == '-')
+ {
+ switch (*++p)
+ {
+ case 'h': /* host */
+ break;
+
+ case 'p': /* port */
+ port = htons(atoi(*++argv));
+ argc--;
+ break;
+
+ case 'r': /* raw connection */
+ raw = 1;
+ break;
+ }
+ } else if (host == NULL)
+ host = p;
+ }
+ if (host == NULL)
+ host = "localhost";
+
+ bzero(&SendmailAddress, sizeof (SendmailAddress));
+ hp = getipnodebyname(host, AF_INET6, AI_DEFAULT|AI_ALL, &err);
+ if (hp == NULL)
+ {
+ (void) fprintf(stderr, "mconnect: unknown host %s\r\n", host);
+ exit(0);
+ }
+
+ if (port == 0) {
+ sp = getservbyname("smtp", "tcp");
+ if (sp != NULL)
+ port = sp->s_port;
+ }
+
+ for (;;) {
+ bcopy(hp->h_addr_list[addr_num],
+ &SendmailAddress.sin6.sin6_addr, IN6ADDRSZ);
+ if (IN6_IS_ADDR_V4MAPPED(&SendmailAddress.sin6.sin6_addr)) {
+ SendmailAddress.sa.sa_family = AF_INET;
+ SendmailAddress.sin.sin_port = port;
+ bcopy(&hp->h_addr_list[addr_num][IN6ADDRSZ - INADDRSZ],
+ &SendmailAddress.sin.sin_addr, INADDRSZ);
+ addrlen = sizeof (struct sockaddr_in);
+ } else {
+ SendmailAddress.sa.sa_family = AF_INET6;
+ SendmailAddress.sin6.sin6_port = port;
+ addrlen = sizeof (struct sockaddr_in6);
+ }
+
+ s = socket(SendmailAddress.sa.sa_family, SOCK_STREAM, 0);
+ if (s < 0)
+ {
+ perror("socket");
+ exit(-1);
+ }
+ (void) setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (char *)&on,
+ sizeof (on));
+ if (SendmailAddress.sa.sa_family == AF_INET)
+ (void) printf("connecting to host %s (%s), port %d\r\n",
+ host, inet_ntoa(SendmailAddress.sin.sin_addr),
+ ntohs(SendmailAddress.sin.sin_port));
+ else
+ (void) printf("connecting to host %s (%s), port %d\r\n",
+ host, inet_ntop(AF_INET6,
+ SendmailAddress.sin6.sin6_addr.s6_addr,
+ buf6, sizeof (buf6)),
+ ntohs(SendmailAddress.sin6.sin6_port));
+ if (connect(s, (struct sockaddr *)&SendmailAddress,
+ addrlen) >= 0)
+ break;
+ if (hp->h_addr_list[++addr_num] != NULL) {
+ (void) printf("connect failed (%s), next address ...\n",
+ strerror(errno));
+ bcopy(hp->h_addr_list[addr_num],
+ &SendmailAddress.sin6.sin6_addr, IN6ADDRSZ);
+ if (IN6_IS_ADDR_V4MAPPED(
+ &SendmailAddress.sin6.sin6_addr)) {
+ SendmailAddress.sa.sa_family = AF_INET;
+ bcopy(&hp->h_addr_list[addr_num]
+ [IN6ADDRSZ - INADDRSZ],
+ &SendmailAddress.sin.sin_addr,
+ INADDRSZ);
+ addrlen = sizeof (struct sockaddr_in);
+ } else {
+ SendmailAddress.sa.sa_family = AF_INET6;
+ addrlen = sizeof (struct sockaddr_in6);
+ }
+ continue;
+ }
+ perror("connect");
+ exit(-1);
+ }
+
+ if (raw) {
+ TtyBuf.sg_flags &= ~CRMOD;
+ (void) ioctl(0, TIOCSETP, &TtyBuf);
+ TtyBuf.sg_flags |= CRMOD;
+ }
+
+ /* good connection, fork both sides */
+ (void) printf("connection open\n");
+ pid = fork();
+ if (pid < 0)
+ {
+ perror("fork");
+ exit(-1);
+ }
+ if (pid == 0)
+ {
+ /* child -- standard input to sendmail */
+ int c;
+
+ f = fdopen(s, "w");
+ while ((c = fgetc(stdin)) >= 0)
+ {
+ if (!raw && c == '\n')
+ (void) fputc('\r', f);
+ (void) fputc(c, f);
+ if (c == '\n')
+ (void) fflush(f);
+ }
+ (void) shutdown(s, 1);
+ (void) sleep(10);
+ }
+ else
+ {
+ /* parent -- sendmail to standard output */
+ f = fdopen(s, "r");
+ while (fgets(buf, sizeof (buf), f) != NULL)
+ {
+ (void) fputs(buf, stdout);
+ (void) fflush(stdout);
+ }
+ (void) kill(pid, SIGTERM);
+ }
+ if (raw)
+ (void) ioctl(0, TIOCSETP, &TtyBuf);
+ return (0);
+}
diff --git a/usr/src/cmd/sendmail/aux/nisedit.c b/usr/src/cmd/sendmail/aux/nisedit.c
new file mode 100644
index 0000000000..25720ec27d
--- /dev/null
+++ b/usr/src/cmd/sendmail/aux/nisedit.c
@@ -0,0 +1,533 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+/*
+ * Copyright 1991-2002 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include "nisplus.h"
+
+#include <pwd.h>
+#include <search.h>
+
+/*
+ * These are pointers to trees built from the "old" and "new" versions
+ * of the alias database when we run in edit mode.
+ */
+
+static void *oroot = NULL; /* pointer to the search table built from nis */
+static void *nroot = NULL; /* pointer to the table built from the editor */
+
+static int get_mailias();
+static void *mailias_parse_file(FILE *fp, nis_name map, nis_name domain);
+
+/*
+ * mailias_cmp returns 0 if the the ALIAS_COL ONLY of
+ * an nis alias table entry are the same. Otherwise it
+ * it returns -1 if obj1 is lexicographically less than obj2
+ * it returns 1 if obj1 is lexicographically greater than obj2
+ * N.B. This needs to be a function instead of a macro because it's
+ * passed to the tsearch and tfind family of routines.
+ */
+static int
+mailias_cmp(obj1, obj2)
+ const void *obj1, *obj2;
+{
+ char *s1, *s2;
+/* basically strcmp inlined for performance */
+ s1 = ALIAS(((nis_object *) obj1));
+ s2 = ALIAS(((nis_object *) obj2));
+
+ while (*s1 == *s2++)
+ if (*s1++ == '\0')
+ return (0);
+ return (*s1 - *--s2);
+}
+
+/*
+ * mailias_eq returns 1 if ALL columns of two alias objects are
+ * the same. Otherwise it returns 0.
+ */
+static int
+mailias_eq(obj1, obj2)
+ nis_object *obj1, *obj2;
+{
+ char *s1, *s2;
+
+ /* check ALIAS column */
+ s1 = ALIAS(obj1);
+ s2 = ALIAS(obj2);
+ if ((s1 != NULL) && (s2 != NULL)) {
+ if (strcmp(s1, s2) != 0)
+ return (FALSE);
+ } else
+ if (!((s1 == NULL) && (s2 == NULL)))
+ return (FALSE);
+
+ /* check EXPN column */
+ s1 = EXPN(obj1);
+ s2 = EXPN(obj2);
+ if ((s1 != NULL) && (s2 != NULL)) {
+ if (strcmp(s1, s2) != 0)
+ return (FALSE);
+ } else
+ if (!((s1 == NULL) && (s2 == NULL)))
+ return (FALSE);
+
+ /* check COMMENTS column */
+ s1 = COMMENTS(obj1);
+ s2 = COMMENTS(obj2);
+ if ((s1 != NULL) && (s2 != NULL)) {
+ if (strcmp(s1, s2) != 0)
+ return (FALSE);
+ } else
+ if (!((s1 == NULL) && (s2 == NULL)))
+ return (FALSE);
+
+ /* check OPTIONS column */
+ s1 = OPTIONS(obj1);
+ s2 = OPTIONS(obj2);
+ if ((s1 != NULL) && (s2 != NULL)) {
+ if (strcmp(s1, s2) != 0)
+ return (FALSE);
+ } else
+ if (!((s1 == NULL) && (s2 == NULL)))
+ return (FALSE);
+
+ /* Done */
+ return (TRUE);
+}
+
+/*
+ * nis_mailias_list(fp, map, domain)
+ * prints the nis alias map contained in "map" in the domain "domain"
+ * to the file pointed to by "fp" in format which is human readable.
+ * The format is "alias: value #options#comments
+ * Note that unless the -n flag was specified (which turns off the
+ * print_comments flag) this format will NOT be compatible with the
+ * /etc/mail/aliases file.
+ */
+
+void
+nis_mailias_list(fp, map, domain)
+ FILE *fp;
+ nis_name map;
+ nis_name domain;
+{
+ nis_result *res;
+ int i;
+ char qmap[NIS_MAXNAMELEN]; /* fully qualified map name */
+
+ (void) snprintf(qmap, sizeof (qmap), "%s.%s", map, domain);
+
+ res = nis_list(qmap, ALL_RESULTS|FOLLOW_PATH, NULL, NULL);
+ if (res->status == NIS_SUCCESS) {
+ qsort((void *) res->objects.objects_val,
+ (unsigned)res->objects.objects_len,
+ sizeof (nis_object),
+ mailias_cmp);
+ for (i = 0; i < res->objects.objects_len; i++) {
+ mailias_print(fp, &res->objects.objects_val[i]);
+ }
+ }
+}
+
+/*
+ * check_for_deletes(obj, order, level)
+ *
+ * when we walk the tree containing the original version of the aliases
+ * we call this routine. If obj is in the New aliases database we do
+ * nothing. If obj is NOT found in the new aliases database we
+ * delete that alias from the nis aliases database.
+ */
+
+/* ARGSUSED2 */
+static void
+check_for_deletes(obj, order, level)
+ const void *obj;
+ VISIT order;
+ int level;
+{
+ nis_mailias a;
+ nis_object *old;
+
+ /*
+ * need to check objects only once otherwise we might try and delete
+ * a deleted object
+ */
+ if (order != preorder && order != leaf)
+ return;
+
+ old = *((nis_object **)obj);
+
+ /* N.B. obj is really a (nis_object) ** */
+ if (tfind((void *)old, &nroot, mailias_cmp) != NULL)
+ return;
+ /* fall through and delete the object */
+ a.name = ALIAS(old);
+ a.expn = EXPN(old);
+ a.comments = COMMENTS(old);
+ a.options = OPTIONS(old);
+#ifdef DEBUG
+ fprintf(stderr, "deleting ");
+ mailias_print(stderr, old);
+#endif
+ nis_mailias_delete(a, old->zo_name, old->zo_domain);
+}
+
+/*
+ * check_for_changes(obj, order, level)
+ *
+ * when we walk the tree containing the version of the aliases the user created
+ * we call this routine. If obj is the same as in the old aliases database
+ * we do nothing. If obj is NOT found in the old aliases database we
+ * add that alias to the nis aliases database. If the obj is found in the
+ * old database but has been changed we change (using the nis_change
+ * operations) that alias in the database
+ */
+
+/* ARGSUSED2 */
+static void
+check_for_changes(obj, order, level)
+ const void *obj;
+ VISIT order;
+ int level;
+{
+ nis_object *old, *new;
+ nis_object **result;
+ nis_mailias a;
+
+ /*
+ * need to check objects only once otherwise we might try and change
+ * or add an object that has already been changed or added.
+ */
+ if (order != preorder && order != leaf)
+ return;
+
+ new = *((nis_object **)obj);
+ /* N.B. obj is really a (nis_object) ** */
+ result = (nis_object **) tfind((void *)new, &oroot, mailias_cmp);
+ old = (result ? *result : NULL);
+
+ if (old == NULL) {
+ a.name = ALIAS(new);
+ a.expn = EXPN(new);
+ a.comments = COMMENTS(new);
+ a.options = OPTIONS(new);
+#ifdef DEBUG
+ fprintf(stderr, "adding ");
+ mailias_print(stderr, new);
+#endif
+ nis_mailias_add(a, new->zo_name, new->zo_domain);
+ return;
+ }
+ if (mailias_eq(old, new)) {
+ /* alias entry was not changed */
+#ifdef DEBUG
+ fprintf(stderr, "unchanged ");
+ mailias_print(stderr, new);
+#endif
+ return;
+ }
+ /* i.e. the alias entry has changed */
+ a.name = ALIAS(new);
+ a.expn = EXPN(new);
+ a.comments = COMMENTS(new);
+ a.options = OPTIONS(new);
+#ifdef DEBUG
+ fprintf(stderr, "changing ");
+ mailias_print(stderr, new);
+#endif
+ nis_mailias_change(a, new->zo_name, new->zo_domain);
+}
+
+/*
+ * nis_mailias_edit(FILE *fp, nis_name map, nis_man domain)
+ *
+ * Edit's the alias map "map" in domain "domain". If fp is non-NULL
+ * The file pointed to by fp contains the new alias map and no
+ * editor is invoked.
+ * The method used is to build two search trees and compare them.
+ * The first tree pointed to by oroot contains the original alias map.
+ * the second tree pointed to by nroot contains the desired aliases map.
+ * The old tree is walked first, if any of the aliases in the old tree are
+ * missing in the new tree those aliases are deleted. The new tree is
+ * then walked. If the aliases are added or changed the apropriate
+ * mailias_function is called.
+ */
+
+void
+nis_mailias_edit(fp, map, domain)
+ FILE *fp;
+ nis_name map;
+ nis_name domain;
+{
+ nis_result *res;
+ char qmap[NIS_MAXNAMELEN]; /* fully qualified map name */
+ int i;
+ char *editor, *cmd, *tmpfname;
+ size_t cmdsize;
+
+ void *key;
+
+ if (!check_table(map, domain)) {
+ fprintf(stderr, "Alias table %s.%s does not exist\n",
+ map, domain);
+ exit(-1);
+ }
+
+ (void) snprintf(qmap, sizeof (qmap), "%s.%s", map, domain);
+ res = nis_list(qmap, ALL_RESULTS|FOLLOW_PATH, NULL, NULL);
+ if (res->status == NIS_SUCCESS) {
+ qsort((void *) res->objects.objects_val,
+ (unsigned)res->objects.objects_len,
+ sizeof (nis_object),
+ mailias_cmp);
+ }
+ if (fp == NULL) {
+ tmpfname = tmpnam(NULL);
+ fp = fopen(tmpfname, "w");
+ /* print the aliases into a temporary file */
+ if (res -> status == NIS_SUCCESS) {
+ for (i = 0; i < res->objects.objects_len; i++) {
+ mailias_print(fp, &res->objects.objects_val[i]);
+ }
+ }
+ fclose(fp);
+ /* invoke the users editor on that file */
+ editor = getenv("VISUAL");
+ if (editor == NULL)
+ editor = getenv("EDITOR");
+ if (editor == NULL)
+ editor = "/usr/bin/vi";
+ cmdsize = strlen(editor) + strlen(tmpfname) + 2;
+ cmd = (char *)malloc(cmdsize);
+ if (cmd == NULL) {
+ perror(NULL);
+ exit(-1);
+ }
+ (void) snprintf(cmd, cmdsize, "%s %s", editor, tmpfname);
+ system(cmd);
+ fp = fopen(tmpfname, "r");
+ nroot = mailias_parse_file(fp, map, domain);
+ fclose(fp);
+ unlink(tmpfname);
+ } else {
+ /* they used the -f command option to read from a file */
+ nroot = mailias_parse_file(fp, map, domain);
+ }
+
+ /* If there were entries in the existing map */
+ if (res -> status == NIS_SUCCESS) {
+ for (i = 0; i < res->objects.objects_len; i++) {
+ key = (void *) &(res->objects.objects_val[i]);
+ (void) tsearch(key, &oroot, mailias_cmp);
+ }
+ twalk(oroot, check_for_deletes);
+ }
+ twalk(nroot, check_for_changes);
+}
+
+/*
+ * mailias_parse_file(fp, map, domain)
+ *
+ * Returns a pointer to a tsearch(3) style tree which contains nis mail
+ * alias objects.
+ */
+
+extern void *
+mailias_parse_file(fp, map, domain)
+ FILE *fp;
+ nis_name map;
+ nis_name domain;
+{
+ char lbuf[4*NIS_MAXNAMELEN + MAXLINE];
+ int skipping = FALSE;
+ int next_char;
+ char *lp = NULL; /* pointer to an alias line */
+ char *p;
+ nis_mailias a;
+ nis_object *obj;
+ void *root = NULL;
+
+
+ while (fgets(lbuf, sizeof (lbuf), fp) != NULL) {
+ /* This used to be a strchr but was inlined for performance */
+ for (p = lbuf; *p != '\n' && *p != '\0'; p++)
+ ;
+ *p = '\0'; /* get rid of \n in the string */
+ switch (lbuf[0]) {
+ case '#':
+ case '\0':
+ skipping = FALSE;
+ continue;
+
+ case ' ':
+ case '\t':
+ if (!skipping)
+ fprintf(stderr,
+ "Non-continuation line starts with space\n");
+ skipping = TRUE;
+ continue;
+ }
+ skipping = FALSE;
+
+ /*
+ * Find and Read Any lines that start with whitespace
+ * (continuation lines)
+ */
+ lp = strdup(lbuf);
+ while ((next_char = getc(fp)) != EOF) {
+ if (next_char == '\t' || next_char == ' ') {
+ size_t lpsize = strlen(p) + strlen(lbuf);
+ /* Read a Continuation line */
+ ungetc(next_char, fp);
+ (void) fgets(lbuf, sizeof (lbuf), fp);
+ p = lp;
+ lp = (char *)malloc(lpsize);
+ if (p != NULL) {
+ (void) strlcpy(lp, p, lpsize);
+ free(p);
+ }
+
+ /*
+ * used to be a strchr,
+ * inlined for performance
+ */
+ for (p = lbuf; *p != '\n' && *p != '\0'; p++)
+ ;
+ *p = '\0';
+ (void) strlcat(lp, lbuf, lpsize);
+ } else {
+ ungetc(next_char, fp);
+ break;
+ }
+ }
+
+ /* Find The Alias Name */
+ a.name = "";
+ a.expn = "";
+ a.comments = "";
+ a.options = "";
+
+ (void) get_mailias(&a, lp);
+ if (lp != NULL)
+ free(lp);
+
+
+ if (strcmp(a.name, "") != 0) {
+ obj = mailias_make_entry(a, map, domain);
+ (void) tsearch((void *) obj, &root, mailias_cmp);
+ }
+ }
+ return (root);
+}
+
+/*
+ * get_mailias(a, lbuf)
+ *
+ * put's the mail alias from lbuf into the alias structure a.
+ * Note that this ISN'T the same format as the /etc/alias file.
+ * In /etc/aliases #'s (which are used here to separate fields)
+ * are perfectly legal on the right hand side of an alias (although
+ * sendmail won't handle that sort of alias)
+ */
+static int
+get_mailias(a, lbuf)
+ nis_mailias *a;
+ char *lbuf;
+{
+ char *ap; /* pointer to the end of the alias field */
+ char *ep; /* pointer to the end of the expansion field */
+ char *cp; /* pointer to the end of the comment field */
+ int cdepth = 0; /* if > 0 we're in a user name comment */
+ /* (in parens) */
+
+ /* Find the ":", all aliases should be of the form alias: value */
+ /* There used to be a strchr here that was inlined for performance */
+ for (ap = lbuf; *ap != ':' && *ap != '\0'; ap++)
+ ;
+ if (ap == '\0') {
+ fprintf(stderr, "Warning, missing colon in alias\n");
+ return (FALSE);
+ }
+
+ /* Allocate space and copy the alias into a->name */
+ a->name = (char *)malloc((ap - lbuf) + 1);
+ if (a->name == NULL) {
+ perror(NULL);
+ return (FALSE);
+ }
+ strncpy(a->name, lbuf, ap - lbuf);
+ a->name[ap - lbuf] = '\0'; /* terminate the string with NUL */
+
+ /* Read the alias value */
+ for (ep = ap; (*ep != '#' || cdepth > 0) && *ep != '\0'; ep++) {
+ if (*ep == '(')
+ cdepth++;
+ if (*ep == ')' && (cdepth > 0))
+ cdepth--;
+ }
+ a->expn = (char *)malloc((ep - ap) + 1);
+ if (a->expn == NULL) {
+ perror(NULL);
+ return (FALSE);
+ }
+ strncpy(a->expn, ap + 1, ep - (ap + 1)); /* this eliminates the : */
+ a->expn[ep - (ap + 1)] = '\0';
+
+ /* i.e. there are no comments or options */
+ if (*ep == '\0')
+ return (TRUE);
+
+ /* Read the comments field value */
+ cdepth = 0;
+ for (cp = ep + 1; (*cp != '#' || cdepth > 0) && *cp != '\0'; cp++) {
+ /*
+ * Note that notion of comments in parens inside a comment
+ * field is a bit strange. It's really just a way to
+ * embed "#"'s in your comments
+ */
+ if (*cp == '(')
+ cdepth++;
+ if (*cp == ')' && (cdepth > 0))
+ cdepth--;
+ }
+ a->comments = (char *)malloc(cp - ep);
+ if (a->comments == NULL) {
+ perror(NULL);
+ return (FALSE);
+ }
+
+ /* this eliminates the # */
+ strncpy(a->comments, ep + 1, cp - (ep + 1));
+ a->comments[cp - (ep + 1)] = '\0';
+
+ /* i.e. there are no options */
+ if (*cp == '\0')
+ return (TRUE);
+
+ a->options = strdup(cp + 1); /* make sure to skip the # */
+ return (TRUE);
+}
diff --git a/usr/src/cmd/sendmail/aux/nisplus.c b/usr/src/cmd/sendmail/aux/nisplus.c
new file mode 100644
index 0000000000..9fc1ad4df4
--- /dev/null
+++ b/usr/src/cmd/sendmail/aux/nisplus.c
@@ -0,0 +1,451 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+/*
+ * Copyright 1994-2002 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include "nisplus.h"
+
+#include <ctype.h>
+#include <pwd.h>
+#include <search.h>
+#include <nsswitch.h>
+
+static void space_rm();
+static nis_object *mailias_make_table(nis_name name, nis_name domain);
+
+/*
+ * Mailias_print(FILE *fp, nis_object *obj)
+ * takes an nis_object * which points to an nis mail alias
+ * object and prints to the file pointed to by fp.
+ */
+
+void
+mailias_print(fp, obj)
+ FILE *fp;
+ nis_object *obj;
+{
+ char *val;
+ char *c, *o;
+
+ /*
+ * Need to print out an empty comments field if there's no
+ * comments but there are OPTIONS
+ */
+
+ if (obj->zo_data.zo_type != NIS_ENTRY_OBJ)
+ return;
+
+ if (obj->EN_len != MAILIAS_COLS) {
+ fprintf(fp, "warning: alias map has %d cols should have %d\n",
+ obj->EN_len, MAILIAS_COLS);
+ return;
+ }
+
+ val = ALIAS(obj);
+ fprintf(fp, "%s: ", val? val: "");
+
+ val = EXPN(obj);
+ fprintf(fp, "%s", val? val: "");
+
+ c = COMMENTS(obj) ? COMMENTS(obj) : "";
+ o = OPTIONS(obj) ? OPTIONS(obj) : "";
+
+ if (print_comments && !(*c == '\0' && *o == '\0')) {
+ fprintf(fp, "#%s#%s", c, o);
+ }
+ fprintf(fp, "\n");
+}
+
+nis_result *
+nis_mailias_match(name, map, domain, field)
+ char *name;
+ nis_name domain;
+ nis_name map;
+ int field;
+{
+ nis_result *res;
+
+ char qbuf[MAXLINE + NIS_MAXNAMELEN];
+
+ name = strdup(name);
+ space_rm(name);
+
+ if (!check_table(map, domain)) {
+ res = (nis_result *)malloc(sizeof (nis_result));
+ if (res == NULL) {
+ perror(NULL);
+ exit(-1);
+ }
+ res->status = NIS_NOTFOUND;
+ free(name);
+ return (res);
+ }
+ switch (field) {
+ case EXPANSION_COL:
+ (void) snprintf(qbuf, sizeof (qbuf), "[expansion=%s],%s.%s",
+ name, map, domain);
+ break;
+ case ALIAS_COL:
+ default:
+ (void) snprintf(qbuf, sizeof (qbuf), "[alias=%s],%s.%s", name,
+ map, domain);
+ break;
+ }
+ res = nis_list(qbuf, FOLLOW_LINKS | FOLLOW_PATH, NULL, NULL);
+ free(name);
+ return (res);
+}
+
+/*
+ * nis_mailias_init(nis_name domain, nis_name map)
+ * initialiases an alias map in the given domain and gives the map
+ * the name passed to it in the second parameter. This is usually used
+ * with the current domain and on the map mail_aliases
+ */
+void
+nis_mailias_init(map, domain)
+ nis_name domain;
+ nis_name map;
+{
+ nis_object *ntable;
+ nis_result *res;
+ char name_buf[NIS_MAXNAMELEN];
+
+ if (check_table(map, domain)) {
+ fprintf(stderr, "Alias table %s.%s already exists\n",
+ map, domain);
+ exit(-1);
+ }
+
+ ntable = mailias_make_table(map, domain);
+ (void) snprintf(name_buf, sizeof (name_buf), "%s.%s", map, domain);
+ res = nis_add(name_buf, ntable);
+ if (res->status != NIS_SUCCESS) {
+ fprintf(stderr, "NIS alias map creation failed: %d\n",
+ res->status);
+ exit(-1);
+ }
+}
+
+/*
+ * nis_mailias_add( nis_mailias a, nis_name alias_map, nis_name domain)
+ * adds the alias entry a to the map alias_map in domain "domain".
+ * e.g. nis_mailias_add( a, "mail_aliases", "Eng.Sun.COM.");
+ */
+void
+nis_mailias_add(a, alias_map, domain)
+ nis_mailias a;
+ nis_name alias_map;
+ nis_name domain;
+{
+ nis_result *res;
+ nis_object *a_entry;
+ char name_buf[MAXLINE + NIS_MAXNAMELEN];
+
+ space_rm(a.name);
+
+ res = nis_mailias_match(a.name, alias_map, domain, ALIAS_COL);
+ if (res->status == NIS_SUCCESS) {
+ fprintf(stderr, "alias %s: is already in the map\n", a.name);
+ exit(-1);
+ }
+ a_entry = mailias_make_entry(a, alias_map, domain);
+ (void) snprintf(name_buf, sizeof (name_buf), "%s.%s", alias_map,
+ domain);
+ res = nis_add_entry(name_buf, a_entry, 0);
+ if (res->status != NIS_SUCCESS) {
+ fprintf(stderr, "NIS alias add failed: %d\n", res->status);
+ exit(-1);
+ }
+}
+
+
+/*
+ * nis_mailias_delete( nis_mailias a, nis_name alias_map, nis_name domain)
+ * removes the alias entry a from the table alias_map in domain "domain".
+ * e.g. nis_mailias_delete( a, "mail_aliases", "Eng.Sun.COM.");
+ */
+void
+nis_mailias_delete(a, alias_map, domain)
+ nis_mailias a;
+ nis_name alias_map;
+ nis_name domain;
+{
+ nis_result *res;
+ char name_buf[MAXLINE + NIS_MAXNAMELEN];
+
+ space_rm(a.name);
+
+ res = nis_mailias_match(a.name, alias_map, domain, ALIAS_COL);
+ if (res->status != NIS_SUCCESS) {
+ fprintf(stderr, "alias %s: not found in the map\n", a.name);
+ exit(-1);
+ }
+
+ (void) snprintf(name_buf, sizeof (name_buf), "%s.%s", alias_map,
+ domain);
+ res = nis_remove_entry(name_buf, NIS_RES_OBJECT(res), 0);
+ if (res->status != NIS_SUCCESS) {
+ fprintf(stderr, "NIS alias remove failed: %d\n", res->status);
+ exit(-1);
+ }
+
+}
+
+/*
+ * nis_mailias_change( nis_mailias a, nis_name alias_map, nis_name domain)
+ * changes the alias entry a in the table alias_map in domain "domain".
+ * e.g. nis_mailias_change( a, "mail_aliases", "Eng.Sun.COM.");
+ */
+void
+nis_mailias_change(a, alias_map, domain)
+ nis_mailias a;
+ nis_name alias_map;
+ nis_name domain;
+{
+ nis_result *res;
+ char name_buf[MAXLINE + NIS_MAXNAMELEN];
+ char *val;
+
+ space_rm(a.name);
+
+ res = nis_mailias_match(a.name, alias_map, domain, ALIAS_COL);
+ if (res->status != NIS_SUCCESS) {
+ fprintf(stderr, "alias %s: not found in the map\n", a.name);
+ exit(-1);
+ }
+
+ space_rm(a.expn);
+
+ val = a.expn ? a.expn : "";
+ NIS_RES_OBJECT(res)->EN_col(EXPANSION_COL) = val;
+ NIS_RES_OBJECT(res)->EN_col_len(EXPANSION_COL) = strlen(val) + 1;
+ NIS_RES_OBJECT(res)->EN_col_flags(EXPANSION_COL) = EN_MODIFIED;
+
+ val = a.comments ? a.comments: "";
+
+ NIS_RES_OBJECT(res)->EN_col(COMMENTS_COL) = val;
+ NIS_RES_OBJECT(res)->EN_col_len(COMMENTS_COL) = strlen(val) + 1;
+ NIS_RES_OBJECT(res)->EN_col_flags(COMMENTS_COL) = EN_MODIFIED;
+
+ val = a.options ? a.options : "";
+ NIS_RES_OBJECT(res)->EN_col(OPTIONS_COL) = val;
+ NIS_RES_OBJECT(res)->EN_col_len(OPTIONS_COL) = strlen(val) + 1;
+ NIS_RES_OBJECT(res)->EN_col_flags(OPTIONS_COL) = EN_MODIFIED;
+
+ (void) snprintf(name_buf, sizeof (name_buf), "[alias=%s],%s.%s", a.name,
+ alias_map, domain);
+ res = nis_modify_entry(name_buf, NIS_RES_OBJECT(res), 0);
+ if (res->status != NIS_SUCCESS) {
+ fprintf(stderr, "NIS alias modify failed: %d\n", res->status);
+ exit(-1);
+ }
+}
+
+/*
+ * Makes and Nis_alias table entry object, given 4 (possibly NULL)
+ * values to put in the columns.
+ */
+nis_object *
+mailias_make_entry(a, name, domain)
+ nis_mailias a;
+ nis_name name;
+ nis_name domain;
+{
+ nis_object *ret;
+ char name_buf[NIS_MAXNAMELEN];
+
+ space_rm(a.name);
+
+ ret = (nis_object *)malloc(sizeof (nis_object));
+ if (ret == NULL) {
+ perror(NULL);
+ exit(-1);
+ }
+ ret->zo_name = name;
+ (void) snprintf(name_buf, sizeof (name_buf), "%s.%s",
+ (getpwuid(getuid())->pw_name), domain);
+ ret->zo_owner = strdup(name_buf);
+ ret->zo_group = "";
+ ret->zo_domain = domain;
+ ret->zo_access = DEFAULT_RIGHTS;
+ ret->zo_ttl = 24 * 60 * 60;
+
+ ret->zo_data.zo_type = NIS_ENTRY_OBJ;
+ ret->EN_data.en_type = TABLE_TYPE;
+ ret->EN_len = MAILIAS_COLS;
+
+ ret->EN_colp = (entry_col *)malloc(MAILIAS_COLS *
+ sizeof (struct entry_col));
+ if (ret->EN_colp == NULL) {
+ perror(NULL);
+ exit(-1);
+ }
+ ret->EN_col(ALIAS_COL) = a.name ? a.name : "";
+ ret->EN_col_len(ALIAS_COL) = strlen(ret->EN_col(ALIAS_COL)) + 1;
+
+ space_rm(a.expn);
+
+ ret->EN_col(EXPANSION_COL) = a.expn ? a.expn : "";
+ ret->EN_col_len(EXPANSION_COL) =
+ strlen(ret->EN_col(EXPANSION_COL)) + 1;
+
+ ret->EN_col(COMMENTS_COL) = a.comments ? a.comments : "";
+ ret->EN_col_len(COMMENTS_COL) = strlen(ret->EN_col(COMMENTS_COL)) + 1;
+
+ ret->EN_col(OPTIONS_COL) = a.options ? a.options : "";
+ ret->EN_col_len(OPTIONS_COL) = strlen(ret->EN_col(OPTIONS_COL)) + 1;
+
+ return (ret);
+}
+
+/*
+ * Makes and Nis_alias table entry object, given 4 (possibly NULL)
+ * values to put in the columns.
+ */
+nis_object *
+mailias_make_table(name, domain)
+ nis_name name;
+ nis_name domain;
+{
+ nis_object *ret;
+ char name_buf[NIS_MAXNAMELEN];
+
+ ret = (nis_object *)malloc(sizeof (nis_object));
+ if (ret == NULL) {
+ perror(NULL);
+ exit(-1);
+ }
+ ret->zo_name = strdup(name);
+ (void) snprintf(name_buf, sizeof (name_buf), "%s.%s",
+ (getpwuid(getuid())->pw_name), domain);
+ ret->zo_owner = strdup(name_buf);
+ ret->zo_group = "";
+ ret->zo_domain = strdup(domain);
+ ret->zo_access = DEFAULT_RIGHTS;
+ ret->zo_ttl = 24 * 60 * 60;
+
+ ret->zo_data.zo_type = NIS_TABLE_OBJ;
+ ret->TA_data.ta_type = strdup(TABLE_TYPE);
+ ret->TA_data.ta_maxcol = MAILIAS_COLS;
+
+ ret->TA_data.ta_sep = ' ';
+ ret->TA_data.ta_cols.ta_cols_len = MAILIAS_COLS;
+ ret->TA_data.ta_path = "";
+ ret->TA_data.ta_cols.ta_cols_val =
+ (struct table_col *)malloc(sizeof (table_col) * MAILIAS_COLS);
+ if (ret->TA_data.ta_cols.ta_cols_val == NULL) {
+ perror(NULL);
+ exit(-1);
+ }
+
+ ret->TA_val(ALIAS_COL).tc_name = "alias";
+ ret->TA_val(ALIAS_COL).tc_flags = TA_SEARCHABLE;
+ ret->TA_val(ALIAS_COL).tc_rights = DEFAULT_RIGHTS;
+
+ ret->TA_val(EXPANSION_COL).tc_name = "expansion";
+ ret->TA_val(EXPANSION_COL).tc_flags = TA_SEARCHABLE;
+ ret->TA_val(EXPANSION_COL).tc_rights = DEFAULT_RIGHTS;
+
+ ret->TA_val(COMMENTS_COL).tc_name = "comments";
+ ret->TA_val(COMMENTS_COL).tc_flags = 0;
+ ret->TA_val(COMMENTS_COL).tc_rights = DEFAULT_RIGHTS;
+
+ ret->TA_val(OPTIONS_COL).tc_name = "options";
+ ret->TA_val(OPTIONS_COL).tc_flags = 0;
+ ret->TA_val(OPTIONS_COL).tc_rights = DEFAULT_RIGHTS;
+
+ return (ret);
+}
+
+#define UNINIT 3
+
+/*
+ * Check to see if the aliases database is initialized/installed in
+ * the proper format.
+ *
+ */
+int
+check_table(nis_name mapname, nis_name domain)
+{
+ nis_result *res = NULL;
+ static int succeeded = UNINIT;
+
+ char qbuf[MAXLINE + NIS_MAXNAMELEN];
+
+ if (succeeded != UNINIT)
+ return (succeeded);
+
+ (void) snprintf(qbuf, sizeof (qbuf), "%s.%s", mapname, domain);
+ while (res == NULL || res->status != NIS_SUCCESS) {
+ res = nis_lookup(qbuf, FOLLOW_LINKS);
+
+ switch (res->status) {
+
+ case NIS_SUCCESS:
+ case NIS_TRYAGAIN:
+ case NIS_RPCERROR:
+ break;
+ default: /* all other nisplus errors */
+ succeeded = FALSE;
+ return (FALSE);
+ };
+ sleep(2); /* try not overwhelm hosed server */
+ }
+
+ if (NIS_RES_NUMOBJ(res) != 1 &&
+ (NIS_RES_OBJECT(res)->zo_data.zo_type != NIS_TABLE_OBJ)) {
+ succeeded = FALSE;
+ return (FALSE);
+ }
+ succeeded = TRUE;
+ return (TRUE);
+
+}
+
+/*
+ * space_rm -- takes a pointer to a string as its input. It removes
+ * all spaces and tabs from that string, except for those
+ * inside double quotes.
+ */
+
+void
+space_rm(char *str)
+{
+ char *t1;
+ int in_comment = FALSE;
+
+ t1 = str;
+ while (*str != '\0') {
+ if (*str == '"')
+ in_comment = !in_comment;
+ if (in_comment || !isspace(*str))
+ *t1++ = *str++;
+ else
+ str++;
+ }
+ *t1 = '\0';
+}
diff --git a/usr/src/cmd/sendmail/aux/nisplus.h b/usr/src/cmd/sendmail/aux/nisplus.h
new file mode 100644
index 0000000000..0997fae6ca
--- /dev/null
+++ b/usr/src/cmd/sendmail/aux/nisplus.h
@@ -0,0 +1,124 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+/*
+ * Copyright (c) 1994, 1997, 1998 by Sun Microsystems, Inc.
+ * All rights reserved.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#undef NIS /* confict in nis.h */
+#undef T_UNSPEC /* symbol conflict in nis.h -> ... -> sys/tiuser.h */
+
+#include <rpcsvc/nis.h>
+#include <rpcsvc/nislib.h>
+
+/*
+ * Defines for Nisplus alias handling functions
+ */
+
+#define TABLE_TYPE "mail_aliases"
+
+/*
+ * Operating modes
+ */
+
+struct nis_mailias
+{
+ char *name;
+ char *expn;
+ char *comments;
+ char *options;
+};
+
+typedef struct nis_mailias nis_mailias;
+
+#define FORWARD 0
+#define REVERSE 1
+
+#define MAILIAS_COLS 4 /* Number of cols in a mailias entry */
+/*
+ * These are the the columns in the NIS+ Table.
+ */
+#define ALIAS_COL 0 /* the name of the alias */
+#define EXPANSION_COL 1 /* what the alias expands to */
+#define COMMENTS_COL 2 /* Human readable comments */
+/*
+ * Options column,
+ * This consists of a list of
+ * VARIABLE=VALUE, or VARIABLE names
+ */
+#define OPTIONS_COL 3
+
+#define MAXLINE 2048 /* max line length */
+
+#define EN_len zo_data.objdata_u.en_data.en_cols.en_cols_len
+#define EN_colp zo_data.objdata_u.en_data.en_cols.en_cols_val
+#define EN_col_len(col) \
+ zo_data.objdata_u.en_data.en_cols.en_cols_val[(col)].ec_value.ec_value_len
+#define EN_col_flags(col) \
+ zo_data.objdata_u.en_data.en_cols.en_cols_val[(col)].ec_flags
+#define EN_col(col) \
+ zo_data.objdata_u.en_data.en_cols.en_cols_val[(col)].ec_value.ec_value_val
+
+/*
+ * Macros which extract the Alias, Expansion, Comments, or Options column
+ * of an nis alias table object
+ */
+#define ALIAS(obj) ((obj)->EN_col(ALIAS_COL))
+#define EXPN(obj) ((obj)->EN_col(EXPANSION_COL))
+#define COMMENTS(obj) ((obj)->EN_col(COMMENTS_COL))
+#define OPTIONS(obj) ((obj)->EN_col(OPTIONS_COL))
+
+#define TA_val(col) zo_data.objdata_u.ta_data.ta_cols.ta_cols_val[(col)]
+
+extern int check_table(nis_name mapname, nis_name domain);
+
+extern void nis_mailias_add(nis_mailias a, nis_name alias_map, nis_name domain);
+
+extern void nis_mailias_change(nis_mailias a, nis_name alias_map,
+ nis_name domain);
+
+extern void nis_mailias_delete(nis_mailias a, nis_name alias_map,
+ nis_name domain);
+
+extern void nis_mailias_edit(FILE *fp, nis_name map, nis_name domain);
+
+extern void nis_mailias_init(nis_name map, nis_name domain);
+
+extern void nis_mailias_list(FILE *fp, nis_name map, nis_name domain);
+
+extern nis_result *nis_mailias_match(char *name, nis_name map,
+ nis_name domain, int qtype);
+
+extern nis_object *mailias_make_entry(struct nis_mailias a,
+ nis_name map, nis_name domain);
+
+extern void mailias_print(FILE *fp, nis_object *obj);
+
+extern int print_comments; /* Tells us whether to print comments and OPTIONS */
+int print_comments;
diff --git a/usr/src/cmd/sendmail/aux/praliases.c b/usr/src/cmd/sendmail/aux/praliases.c
new file mode 100644
index 0000000000..b44be6721f
--- /dev/null
+++ b/usr/src/cmd/sendmail/aux/praliases.c
@@ -0,0 +1,399 @@
+/*
+ * Copyright (c) 1998-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+
+SM_IDSTR(copyright,
+"@(#) Copyright (c) 1998-2001 Sendmail, Inc. and its suppliers.\n\
+ All rights reserved.\n\
+ Copyright (c) 1983 Eric P. Allman. All rights reserved.\n\
+ Copyright (c) 1988, 1993\n\
+ The Regents of the University of California. All rights reserved.\n")
+
+SM_IDSTR(id, "@(#)$Id: praliases.c,v 8.91 2001/03/29 21:15:53 rodney Exp $")
+
+#include <sys/types.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <unistd.h>
+#ifdef EX_OK
+# undef EX_OK /* unistd.h may have another use for this */
+#endif /* EX_OK */
+#include <sysexits.h>
+
+
+#ifndef NOT_SENDMAIL
+# define NOT_SENDMAIL
+#endif /* ! NOT_SENDMAIL */
+#include <sendmail/sendmail.h>
+#include <sendmail/pathnames.h>
+#include <libsmdb/smdb.h>
+
+static void praliases __P((char *, int, char **));
+
+uid_t RealUid;
+gid_t RealGid;
+char *RealUserName;
+uid_t RunAsUid;
+uid_t RunAsGid;
+char *RunAsUserName;
+int Verbose = 2;
+bool DontInitGroups = false;
+uid_t TrustedUid = 0;
+BITMAP256 DontBlameSendmail;
+
+# define DELIMITERS " ,/"
+# define PATH_SEPARATOR ':'
+
+int
+main(argc, argv)
+ int argc;
+ char **argv;
+{
+ char *cfile;
+ char *filename = NULL;
+ SM_FILE_T *cfp;
+ int ch;
+ char afilebuf[MAXLINE];
+ char buf[MAXLINE];
+ struct passwd *pw;
+ static char rnamebuf[MAXNAME];
+ extern char *optarg;
+ extern int optind;
+
+ clrbitmap(DontBlameSendmail);
+ RunAsUid = RealUid = getuid();
+ RunAsGid = RealGid = getgid();
+ pw = getpwuid(RealUid);
+ if (pw != NULL)
+ {
+ if (strlen(pw->pw_name) > MAXNAME - 1)
+ pw->pw_name[MAXNAME] = 0;
+ sm_snprintf(rnamebuf, sizeof rnamebuf, "%s", pw->pw_name);
+ }
+ else
+ (void) sm_snprintf(rnamebuf, sizeof rnamebuf,
+ "Unknown UID %d", (int) RealUid);
+ RunAsUserName = RealUserName = rnamebuf;
+
+ cfile = getcfname(0, 0, SM_GET_SENDMAIL_CF, NULL);
+ while ((ch = getopt(argc, argv, "C:f:")) != -1)
+ {
+ switch ((char)ch) {
+ case 'C':
+ cfile = optarg;
+ break;
+ case 'f':
+ filename = optarg;
+ break;
+ case '?':
+ default:
+ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ "usage: praliases [-C cffile] [-f aliasfile]\n");
+ exit(EX_USAGE);
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (filename != NULL)
+ {
+ praliases(filename, argc, argv);
+ exit(EX_OK);
+ }
+
+ if ((cfp = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, cfile, SM_IO_RDONLY,
+ NULL)) == NULL)
+ {
+ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ "praliases: %s: %s\n", cfile,
+ sm_errstring(errno));
+ exit(EX_NOINPUT);
+ }
+
+ while (sm_io_fgets(cfp, SM_TIME_DEFAULT, buf, sizeof(buf)) != NULL)
+ {
+ register char *b, *p;
+
+ b = strchr(buf, '\n');
+ if (b != NULL)
+ *b = '\0';
+
+ b = buf;
+ switch (*b++)
+ {
+ case 'O': /* option -- see if alias file */
+ if (sm_strncasecmp(b, " AliasFile", 10) == 0 &&
+ !(isascii(b[10]) && isalnum(b[10])))
+ {
+ /* new form -- find value */
+ b = strchr(b, '=');
+ if (b == NULL)
+ continue;
+ while (isascii(*++b) && isspace(*b))
+ continue;
+ }
+ else if (*b++ != 'A')
+ {
+ /* something else boring */
+ continue;
+ }
+
+ /* this is the A or AliasFile option -- save it */
+ if (sm_strlcpy(afilebuf, b, sizeof afilebuf) >=
+ sizeof afilebuf)
+ {
+ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ "praliases: AliasFile filename too long: %.30s\n",
+ b);
+ (void) sm_io_close(cfp, SM_TIME_DEFAULT);
+ exit(EX_CONFIG);
+ }
+ b = afilebuf;
+
+ for (p = b; p != NULL; )
+ {
+ while (isascii(*p) && isspace(*p))
+ p++;
+ if (*p == '\0')
+ break;
+ b = p;
+
+ p = strpbrk(p, DELIMITERS);
+
+ /* find end of spec */
+ if (p != NULL)
+ {
+ bool quoted = false;
+
+ for (; *p != '\0'; p++)
+ {
+ /*
+ ** Don't break into a quoted
+ ** string.
+ */
+
+ if (*p == '"')
+ quoted = !quoted;
+ else if (*p == ',' && !quoted)
+ break;
+ }
+
+ /* No more alias specs follow */
+ if (*p == '\0')
+ {
+ /* chop trailing whitespace */
+ while (isascii(*p) &&
+ isspace(*p) &&
+ p > b)
+ p--;
+ *p = '\0';
+ p = NULL;
+ }
+ }
+
+ if (p != NULL)
+ {
+ char *e = p - 1;
+
+ /* chop trailing whitespace */
+ while (isascii(*e) &&
+ isspace(*e) &&
+ e > b)
+ e--;
+ *++e = '\0';
+ *p++ = '\0';
+ }
+ praliases(b, argc, argv);
+ }
+
+ default:
+ continue;
+ }
+ }
+ (void) sm_io_close(cfp, SM_TIME_DEFAULT);
+ exit(EX_OK);
+ /* NOTREACHED */
+ return EX_OK;
+}
+
+static void
+praliases(filename, argc, argv)
+ char *filename;
+ int argc;
+ char **argv;
+{
+ int result;
+ char *colon;
+ char *db_name;
+ char *db_type;
+ SMDB_DATABASE *database = NULL;
+ SMDB_CURSOR *cursor = NULL;
+ SMDB_DBENT db_key, db_value;
+ SMDB_DBPARAMS params;
+ SMDB_USER_INFO user_info;
+
+ colon = strchr(filename, PATH_SEPARATOR);
+ if (colon == NULL)
+ {
+ db_name = filename;
+ db_type = SMDB_TYPE_DEFAULT;
+ }
+ else
+ {
+ *colon = '\0';
+ db_name = colon + 1;
+ db_type = filename;
+ }
+
+ /* clean off arguments */
+ for (;;)
+ {
+ while (isascii(*db_name) && isspace(*db_name))
+ db_name++;
+
+ if (*db_name != '-')
+ break;
+ while (*db_name != '\0' &&
+ !(isascii(*db_name) && isspace(*db_name)))
+ db_name++;
+ }
+
+ /* Skip non-file based DB types */
+ if (db_type != NULL && *db_type != '\0')
+ {
+ if (db_type != SMDB_TYPE_DEFAULT &&
+ strcmp(db_type, "hash") != 0 &&
+ strcmp(db_type, "btree") != 0 &&
+ strcmp(db_type, "dbm") != 0)
+ {
+ sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ "praliases: Skipping non-file based alias type %s\n",
+ db_type);
+ return;
+ }
+ }
+
+ if (*db_name == '\0' || (db_type != NULL && *db_type == '\0'))
+ {
+ if (colon != NULL)
+ *colon = ':';
+ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ "praliases: illegal alias specification: %s\n", filename);
+ goto fatal;
+ }
+
+ memset(&params, '\0', sizeof params);
+ params.smdbp_cache_size = 1024 * 1024;
+
+ user_info.smdbu_id = RunAsUid;
+ user_info.smdbu_group_id = RunAsGid;
+ (void) sm_strlcpy(user_info.smdbu_name, RunAsUserName,
+ SMDB_MAX_USER_NAME_LEN);
+
+ result = smdb_open_database(&database, db_name, O_RDONLY, 0,
+ SFF_ROOTOK, db_type, &user_info, &params);
+ if (result != SMDBE_OK)
+ {
+ sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ "praliases: %s: open: %s\n",
+ db_name, sm_errstring(result));
+ goto fatal;
+ }
+
+ if (argc == 0)
+ {
+ memset(&db_key, '\0', sizeof db_key);
+ memset(&db_value, '\0', sizeof db_value);
+
+ result = database->smdb_cursor(database, &cursor, 0);
+ if (result != SMDBE_OK)
+ {
+ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ "praliases: %s: set cursor: %s\n", db_name,
+ sm_errstring(result));
+ goto fatal;
+ }
+
+ while ((result = cursor->smdbc_get(cursor, &db_key, &db_value,
+ SMDB_CURSOR_GET_NEXT)) ==
+ SMDBE_OK)
+ {
+#if 0
+ /* skip magic @:@ entry */
+ if (db_key.size == 2 &&
+ db_key.data[0] == '@' &&
+ db_key.data[1] == '\0' &&
+ db_value.size == 2 &&
+ db_value.data[0] == '@' &&
+ db_value.data[1] == '\0')
+ continue;
+#endif /* 0 */
+
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "%.*s:%.*s\n",
+ (int) db_key.size,
+ (char *) db_key.data,
+ (int) db_value.size,
+ (char *) db_value.data);
+ }
+
+ if (result != SMDBE_OK && result != SMDBE_LAST_ENTRY)
+ {
+ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ "praliases: %s: get value at cursor: %s\n",
+ db_name, sm_errstring(result));
+ goto fatal;
+ }
+ }
+ else for (; *argv != NULL; ++argv)
+ {
+ int get_res;
+
+ memset(&db_key, '\0', sizeof db_key);
+ memset(&db_value, '\0', sizeof db_value);
+ db_key.data = *argv;
+ db_key.size = strlen(*argv);
+ get_res = database->smdb_get(database, &db_key, &db_value, 0);
+ if (get_res == SMDBE_NOT_FOUND)
+ {
+ db_key.size++;
+ get_res = database->smdb_get(database, &db_key,
+ &db_value, 0);
+ }
+ if (get_res == SMDBE_OK)
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "%.*s:%.*s\n",
+ (int) db_key.size,
+ (char *) db_key.data,
+ (int) db_value.size,
+ (char *) db_value.data);
+ }
+ else
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "%s: No such key\n",
+ (char *)db_key.data);
+ }
+
+ fatal:
+ if (cursor != NULL)
+ (void) cursor->smdbc_close(cursor);
+ if (database != NULL)
+ (void) database->smdb_close(database);
+ if (colon != NULL)
+ *colon = ':';
+ return;
+}
diff --git a/usr/src/cmd/sendmail/aux/rfc2047.c b/usr/src/cmd/sendmail/aux/rfc2047.c
new file mode 100644
index 0000000000..8092a9a587
--- /dev/null
+++ b/usr/src/cmd/sendmail/aux/rfc2047.c
@@ -0,0 +1,289 @@
+/*
+ * rfc2047.c -- decode RFC-2047 header format
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#ifndef lint
+static char sccsi2[] = "%W% (Sun) %G%";
+#endif
+
+/*
+ * Copyright (c) 1997-1998 Richard Coleman
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following two
+ * paragraphs appear in all copies of this software.
+ *
+ * In no event shall Richard Coleman be liable to any party for direct,
+ * indirect, special, incidental, or consequential damages arising out of
+ * the use of this software and its documentation, even if Richard Coleman
+ * has been advised of the possibility of such damage.
+ *
+ * Richard Coleman specifically disclaims any warranties, including, but
+ * not limited to, the implied warranties of merchantability and fitness
+ * for a particular purpose. The software provided hereunder is on an "as
+ * is" basis, and Richard Coleman has no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ */
+
+/*
+ * Parts of this code were derived from metamail, which is ...
+ *
+ * Copyright (c) 1991 Bell Communications Research, Inc. (Bellcore)
+ *
+ * Permission to use, copy, modify, and distribute this material
+ * for any purpose and without fee is hereby granted, provided
+ * that the above copyright notice and this permission notice
+ * appear in all copies, and that the name of Bellcore not be
+ * used in advertising or publicity pertaining to this
+ * material without the specific, prior written permission
+ * of an authorized representative of Bellcore. BELLCORE
+ * MAKES NO REPRESENTATIONS ABOUT THE ACCURACY OR SUITABILITY
+ * OF THIS MATERIAL FOR ANY PURPOSE. IT IS PROVIDED "AS IS",
+ * WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
+ */
+
+/*
+ * Copyright (c) 1998, by Sun Microsystems, Inc.
+ * All rights reserved.
+ */
+
+#include <string.h>
+
+typedef int bool;
+
+#define FALSE 0
+#define TRUE 1
+
+static signed char hexindex[] = {
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
+ -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
+};
+
+static signed char index_64[128] = {
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
+ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
+ -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1
+};
+
+#define char64(c) (((unsigned char) (c) > 127) ? -1 : \
+ index_64[(unsigned char) (c)])
+
+static int
+unqp(unsigned char byte1, unsigned char byte2)
+{
+ if (hexindex[byte1] == -1 || hexindex[byte2] == -1)
+ return (-1);
+ return (hexindex[byte1] << 4 | hexindex[byte2]);
+}
+
+/* Check if character is linear whitespace */
+#define is_lws(c) ((c) == ' ' || (c) == '\t' || (c) == '\n')
+
+/*
+ * Decode the string as a RFC-2047 header field
+ */
+
+bool
+decode_rfc2047(char *str, char *dst, char *charset)
+{
+ char *p, *q, *pp;
+ char *startofmime, *endofmime;
+ int c, quoted_printable;
+ bool encoding_found = FALSE; /* did we decode anything? */
+ bool between_encodings = FALSE; /* are we between two encodings? */
+ bool equals_pending = FALSE; /* is there a '=' pending? */
+ int whitespace = 0; /* how much whitespace between encodings? */
+
+ if (str == NULL)
+ return (FALSE);
+
+ /*
+ * Do a quick and dirty check for the '=' character.
+ * This should quickly eliminate many cases.
+ */
+ if (!strchr(str, '='))
+ return (FALSE);
+
+ for (p = str, q = dst; *p; p++) {
+ /*
+ * If we had an '=' character pending from
+ * last iteration, then add it first.
+ */
+ if (equals_pending) {
+ *q++ = '=';
+ equals_pending = FALSE;
+ between_encodings = FALSE; /* we added non-WS text */
+ }
+
+ if (*p != '=') {
+ /* count linear whitespace while between encodings */
+ if (between_encodings && is_lws(*p))
+ whitespace++;
+ else
+ between_encodings = FALSE; /* non-WS added */
+ *q++ = *p;
+ continue;
+ }
+
+ equals_pending = TRUE; /* we have a '=' pending */
+
+ /* Check for initial =? */
+ if (*p == '=' && p[1] && p[1] == '?' && p[2]) {
+ startofmime = p + 2;
+
+ /* Scan ahead for the next '?' character */
+ for (pp = startofmime; *pp && *pp != '?'; pp++)
+ ;
+
+ if (!*pp)
+ continue;
+
+ strncpy(charset, startofmime, pp - startofmime);
+ charset[pp - startofmime] = '\0';
+
+ startofmime = pp + 1;
+
+ /* Check for valid encoding type */
+ if (*startofmime != 'B' && *startofmime != 'b' &&
+ *startofmime != 'Q' && *startofmime != 'q')
+ continue;
+
+ /* Is encoding quoted printable or base64? */
+ quoted_printable = (*startofmime == 'Q' ||
+ *startofmime == 'q');
+ startofmime++;
+
+ /* Check for next '?' character */
+ if (*startofmime != '?')
+ continue;
+ startofmime++;
+
+ /*
+ * Scan ahead for the ending ?=
+ *
+ * While doing this, we will also check if encoded
+ * word has any embedded linear whitespace.
+ */
+ endofmime = NULL;
+ for (pp = startofmime; *pp && *(pp+1); pp++) {
+ if (is_lws(*pp))
+ break;
+ else if (*pp == '?' && pp[1] == '=') {
+ endofmime = pp;
+ break;
+ }
+ }
+ if (is_lws(*pp) || endofmime == NULL)
+ continue;
+
+ /*
+ * We've found an encoded word, so we can drop
+ * the '=' that was pending
+ */
+ equals_pending = FALSE;
+
+ /*
+ * If we are between two encoded words separated only
+ * by linear whitespace, then we ignore the whitespace.
+ * We will roll back the buffer the number of whitespace
+ * characters we've seen since last encoded word.
+ */
+ if (between_encodings)
+ q -= whitespace;
+
+ /* Now decode the text */
+ if (quoted_printable) {
+ for (pp = startofmime; pp < endofmime; pp++) {
+ if (*pp == '=') {
+ c = unqp(pp[1], pp[2]);
+ if (c == -1)
+ continue;
+ if (c != 0)
+ *q++ = c;
+ pp += 2;
+ } else if (*pp == '_')
+ *q++ = ' ';
+ else
+ *q++ = *pp;
+ }
+ } else {
+ /* base64 */
+ int c1, c2, c3, c4;
+
+ pp = startofmime;
+ while (pp < endofmime) {
+ /* 6 + 2 bits */
+ while ((pp < endofmime) &&
+ ((c1 = char64(*pp)) == -1)) {
+ pp++;
+ }
+ if (pp < endofmime)
+ pp++;
+ while ((pp < endofmime) &&
+ ((c2 = char64(*pp)) == -1)) {
+ pp++;
+ }
+ if (pp < endofmime && c1 != -1 &&
+ c2 != -1) {
+ *q++ = (c1 << 2) | (c2 >> 4);
+ pp++;
+ }
+ /* 4 + 4 bits */
+ while ((pp < endofmime) &&
+ ((c3 = char64(*pp)) == -1)) {
+ pp++;
+ }
+ if (pp < endofmime && c2 != -1 &&
+ c3 != -1) {
+ *q++ = ((c2 & 0xF) << 4) |
+ (c3 >> 2);
+ pp++;
+ }
+ /* 2 + 6 bits */
+ while ((pp < endofmime) &&
+ ((c4 = char64(*pp)) == -1)) {
+ pp++;
+ }
+ if (pp < endofmime && c3 != -1 &&
+ c4 != -1) {
+ *q++ = ((c3 & 0x3) << 6) | (c4);
+ pp++;
+ }
+ }
+ }
+
+ /*
+ * Now that we are done decoding this particular
+ * encoded word, advance string to trailing '='.
+ */
+ p = endofmime + 1;
+
+ encoding_found = TRUE; /* found (>= 1) encoded word */
+ between_encodings = TRUE; /* just decoded something */
+ whitespace = 0; /* re-initialize amount of whitespace */
+ }
+ }
+
+ /* If an equals was pending at end of string, add it now. */
+ if (equals_pending)
+ *q++ = '=';
+ *q = '\0';
+
+ return (encoding_found);
+}
diff --git a/usr/src/cmd/sendmail/aux/smrsh.c b/usr/src/cmd/sendmail/aux/smrsh.c
new file mode 100644
index 0000000000..32d9afa8eb
--- /dev/null
+++ b/usr/src/cmd/sendmail/aux/smrsh.c
@@ -0,0 +1,442 @@
+/*
+ * Copyright (c) 1998-2004 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1993 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+
+SM_IDSTR(copyright,
+"@(#) Copyright (c) 1998-2004 Sendmail, Inc. and its suppliers.\n\
+ All rights reserved.\n\
+ Copyright (c) 1993 Eric P. Allman. All rights reserved.\n\
+ Copyright (c) 1993\n\
+ The Regents of the University of California. All rights reserved.\n")
+
+SM_IDSTR(id, "@(#)$Id: smrsh.c,v 8.65 2004/08/06 18:54:22 ca Exp $")
+
+/*
+** SMRSH -- sendmail restricted shell
+**
+** This is a patch to get around the prog mailer bugs in most
+** versions of sendmail.
+**
+** Use this in place of /bin/sh in the "prog" mailer definition
+** in your sendmail.cf file. You then create CMDDIR (owned by
+** root, mode 755) and put links to any programs you want
+** available to prog mailers in that directory. This should
+** include things like "vacation" and "procmail", but not "sed"
+** or "sh".
+**
+** Leading pathnames are stripped from program names so that
+** existing .forward files that reference things like
+** "/usr/bin/vacation" will continue to work.
+**
+** The following characters are completely illegal:
+** < > ^ & ` ( ) \n \r
+** The following characters are sometimes illegal:
+** | &
+** This is more restrictive than strictly necessary.
+**
+** To use this, add FEATURE(`smrsh') to your .mc file.
+**
+** This can be used on any version of sendmail.
+**
+** In loving memory of RTM. 11/02/93.
+*/
+
+#include <unistd.h>
+#include <sm/io.h>
+#include <sm/limits.h>
+#include <sm/string.h>
+#include <sys/file.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#ifdef EX_OK
+# undef EX_OK
+#endif /* EX_OK */
+#include <sysexits.h>
+#include <syslog.h>
+#include <stdlib.h>
+
+#include <sm/conf.h>
+#include <sm/errstring.h>
+
+/* directory in which all commands must reside */
+#ifndef CMDDIR
+# ifdef SMRSH_CMDDIR
+# define CMDDIR SMRSH_CMDDIR
+# else /* SMRSH_CMDDIR */
+# define CMDDIR "/usr/adm/sm.bin"
+# endif /* SMRSH_CMDDIR */
+#endif /* ! CMDDIR */
+
+/* characters disallowed in the shell "-c" argument */
+#define SPECIALS "<|>^();&`$\r\n"
+
+/* default search path */
+#ifndef PATH
+# ifdef SMRSH_PATH
+# define PATH SMRSH_PATH
+# else /* SMRSH_PATH */
+# define PATH "/bin:/usr/bin:/usr/ucb"
+# endif /* SMRSH_PATH */
+#endif /* ! PATH */
+
+char newcmdbuf[1000];
+char *prg, *par;
+
+static void addcmd __P((char *, bool, size_t));
+
+/*
+** ADDCMD -- add a string to newcmdbuf, check for overflow
+**
+** Parameters:
+** s -- string to add
+** cmd -- it's a command: prepend CMDDIR/
+** len -- length of string to add
+**
+** Side Effects:
+** changes newcmdbuf or exits with a failure.
+**
+*/
+
+static void
+addcmd(s, cmd, len)
+ char *s;
+ bool cmd;
+ size_t len;
+{
+ if (s == NULL || *s == '\0')
+ return;
+
+ /* enough space for s (len) and CMDDIR + "/" and '\0'? */
+ if (sizeof newcmdbuf - strlen(newcmdbuf) <=
+ len + 1 + (cmd ? (strlen(CMDDIR) + 1) : 0))
+ {
+ (void)sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ "%s: command too long: %s\n", prg, par);
+#ifndef DEBUG
+ syslog(LOG_WARNING, "command too long: %.40s", par);
+#endif /* ! DEBUG */
+ exit(EX_UNAVAILABLE);
+ }
+ if (cmd)
+ (void) sm_strlcat2(newcmdbuf, CMDDIR, "/", sizeof newcmdbuf);
+ (void) strncat(newcmdbuf, s, len);
+}
+
+int
+main(argc, argv)
+ int argc;
+ char **argv;
+{
+ register char *p;
+ register char *q;
+ register char *r;
+ register char *cmd;
+ int isexec;
+ int save_errno;
+ char *newenv[2];
+ char pathbuf[1000];
+ char specialbuf[32];
+ struct stat st;
+
+#ifndef DEBUG
+# ifndef LOG_MAIL
+ openlog("smrsh", 0);
+# else /* ! LOG_MAIL */
+ openlog("smrsh", LOG_ODELAY|LOG_CONS, LOG_MAIL);
+# endif /* ! LOG_MAIL */
+#endif /* ! DEBUG */
+
+ (void) sm_strlcpyn(pathbuf, sizeof pathbuf, 2, "PATH=", PATH);
+ newenv[0] = pathbuf;
+ newenv[1] = NULL;
+
+ /*
+ ** Do basic argv usage checking
+ */
+
+ prg = argv[0];
+
+ if (argc != 3 || strcmp(argv[1], "-c") != 0)
+ {
+ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ "Usage: %s -c command\n", prg);
+#ifndef DEBUG
+ syslog(LOG_ERR, "usage");
+#endif /* ! DEBUG */
+ exit(EX_USAGE);
+ }
+
+ par = argv[2];
+
+ /*
+ ** Disallow special shell syntax. This is overly restrictive,
+ ** but it should shut down all attacks.
+ ** Be sure to include 8-bit versions, since many shells strip
+ ** the address to 7 bits before checking.
+ */
+
+ if (strlen(SPECIALS) * 2 >= sizeof specialbuf)
+ {
+#ifndef DEBUG
+ syslog(LOG_ERR, "too many specials: %.40s", SPECIALS);
+#endif /* ! DEBUG */
+ exit(EX_UNAVAILABLE);
+ }
+ (void) sm_strlcpy(specialbuf, SPECIALS, sizeof specialbuf);
+ for (p = specialbuf; *p != '\0'; p++)
+ *p |= '\200';
+ (void) sm_strlcat(specialbuf, SPECIALS, sizeof specialbuf);
+
+ /*
+ ** Do a quick sanity check on command line length.
+ */
+
+ if (strlen(par) > (sizeof newcmdbuf - sizeof CMDDIR - 2))
+ {
+ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ "%s: command too long: %s\n", prg, par);
+#ifndef DEBUG
+ syslog(LOG_WARNING, "command too long: %.40s", par);
+#endif /* ! DEBUG */
+ exit(EX_UNAVAILABLE);
+ }
+
+ q = par;
+ newcmdbuf[0] = '\0';
+ isexec = false;
+
+ while (*q != '\0')
+ {
+ /*
+ ** Strip off a leading pathname on the command name. For
+ ** example, change /usr/ucb/vacation to vacation.
+ */
+
+ /* strip leading spaces */
+ while (*q != '\0' && isascii(*q) && isspace(*q))
+ q++;
+ if (*q == '\0')
+ {
+ if (isexec)
+ {
+ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ "%s: missing command to exec\n",
+ prg);
+#ifndef DEBUG
+ syslog(LOG_CRIT, "uid %d: missing command to exec", (int) getuid());
+#endif /* ! DEBUG */
+ exit(EX_UNAVAILABLE);
+ }
+ break;
+ }
+
+ /* find the end of the command name */
+ p = strpbrk(q, " \t");
+ if (p == NULL)
+ cmd = &q[strlen(q)];
+ else
+ {
+ *p = '\0';
+ cmd = p;
+ }
+ /* search backwards for last / (allow for 0200 bit) */
+ while (cmd > q)
+ {
+ if ((*--cmd & 0177) == '/')
+ {
+ cmd++;
+ break;
+ }
+ }
+ /* cmd now points at final component of path name */
+
+ /* allow a few shell builtins */
+ if (strcmp(q, "exec") == 0 && p != NULL)
+ {
+ addcmd("exec ", false, strlen("exec "));
+
+ /* test _next_ arg */
+ q = ++p;
+ isexec = true;
+ continue;
+ }
+ else if (strcmp(q, "exit") == 0 || strcmp(q, "echo") == 0)
+ {
+ addcmd(cmd, false, strlen(cmd));
+
+ /* test following chars */
+ }
+ else
+ {
+ char cmdbuf[MAXPATHLEN];
+
+ /*
+ ** Check to see if the command name is legal.
+ */
+
+ if (sm_strlcpyn(cmdbuf, sizeof cmdbuf, 3, CMDDIR,
+ "/", cmd) >= sizeof cmdbuf)
+ {
+ /* too long */
+ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ "%s: \"%s\" not available for sendmail programs (filename too long)\n",
+ prg, cmd);
+ if (p != NULL)
+ *p = ' ';
+#ifndef DEBUG
+ syslog(LOG_CRIT, "uid %d: attempt to use \"%s\" (filename too long)",
+ (int) getuid(), cmd);
+#endif /* ! DEBUG */
+ exit(EX_UNAVAILABLE);
+ }
+
+#ifdef DEBUG
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Trying %s\n", cmdbuf);
+#endif /* DEBUG */
+ if (stat(cmdbuf, &st) < 0)
+ {
+ /* can't stat it */
+ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ "%s: \"%s\" not available for sendmail programs (stat failed)\n",
+ prg, cmd);
+ if (p != NULL)
+ *p = ' ';
+#ifndef DEBUG
+ syslog(LOG_CRIT, "uid %d: attempt to use \"%s\" (stat failed)",
+ (int) getuid(), cmd);
+#endif /* ! DEBUG */
+ exit(EX_UNAVAILABLE);
+ }
+ if (!S_ISREG(st.st_mode)
+#ifdef S_ISLNK
+ && !S_ISLNK(st.st_mode)
+#endif /* S_ISLNK */
+ )
+ {
+ /* can't stat it */
+ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ "%s: \"%s\" not available for sendmail programs (not a file)\n",
+ prg, cmd);
+ if (p != NULL)
+ *p = ' ';
+#ifndef DEBUG
+ syslog(LOG_CRIT, "uid %d: attempt to use \"%s\" (not a file)",
+ (int) getuid(), cmd);
+#endif /* ! DEBUG */
+ exit(EX_UNAVAILABLE);
+ }
+ if (access(cmdbuf, X_OK) < 0)
+ {
+ /* oops.... crack attack possiblity */
+ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ "%s: \"%s\" not available for sendmail programs\n",
+ prg, cmd);
+ if (p != NULL)
+ *p = ' ';
+#ifndef DEBUG
+ syslog(LOG_CRIT, "uid %d: attempt to use \"%s\"",
+ (int) getuid(), cmd);
+#endif /* ! DEBUG */
+ exit(EX_UNAVAILABLE);
+ }
+
+ /*
+ ** Create the actual shell input.
+ */
+
+ addcmd(cmd, true, strlen(cmd));
+ }
+ isexec = false;
+
+ if (p != NULL)
+ *p = ' ';
+ else
+ break;
+
+ r = strpbrk(p, specialbuf);
+ if (r == NULL)
+ {
+ addcmd(p, false, strlen(p));
+ break;
+ }
+#if ALLOWSEMI
+ if (*r == ';')
+ {
+ addcmd(p, false, r - p + 1);
+ q = r + 1;
+ continue;
+ }
+#endif /* ALLOWSEMI */
+ if ((*r == '&' && *(r + 1) == '&') ||
+ (*r == '|' && *(r + 1) == '|'))
+ {
+ addcmd(p, false, r - p + 2);
+ q = r + 2;
+ continue;
+ }
+
+ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ "%s: cannot use %c in command\n", prg, *r);
+#ifndef DEBUG
+ syslog(LOG_CRIT, "uid %d: attempt to use %c in command: %s",
+ (int) getuid(), *r, par);
+#endif /* ! DEBUG */
+ exit(EX_UNAVAILABLE);
+ }
+ if (isexec)
+ {
+ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ "%s: missing command to exec\n", prg);
+#ifndef DEBUG
+ syslog(LOG_CRIT, "uid %d: missing command to exec",
+ (int) getuid());
+#endif /* ! DEBUG */
+ exit(EX_UNAVAILABLE);
+ }
+ /* make sure we created something */
+ if (newcmdbuf[0] == '\0')
+ {
+ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ "Usage: %s -c command\n", prg);
+#ifndef DEBUG
+ syslog(LOG_ERR, "usage");
+#endif /* ! DEBUG */
+ exit(EX_USAGE);
+ }
+
+ /*
+ ** Now invoke the shell
+ */
+
+#ifdef DEBUG
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%s\n", newcmdbuf);
+#endif /* DEBUG */
+ (void) execle("/bin/sh", "/bin/sh", "-c", newcmdbuf,
+ (char *)NULL, newenv);
+ save_errno = errno;
+#ifndef DEBUG
+ syslog(LOG_CRIT, "Cannot exec /bin/sh: %s", sm_errstring(errno));
+#endif /* ! DEBUG */
+ errno = save_errno;
+ sm_perror("/bin/sh");
+ exit(EX_OSFILE);
+ /* NOTREACHED */
+ return EX_OSFILE;
+}
diff --git a/usr/src/cmd/sendmail/aux/vacation.c b/usr/src/cmd/sendmail/aux/vacation.c
new file mode 100644
index 0000000000..648cfdab84
--- /dev/null
+++ b/usr/src/cmd/sendmail/aux/vacation.c
@@ -0,0 +1,1048 @@
+/*
+ * Copyright 2005 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ *
+ * Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T
+ * All Rights Reserved
+ */
+
+/*
+ * Vacation
+ * Copyright (c) 1983 Eric P. Allman
+ * Berkeley, California
+ *
+ * Copyright (c) 1983 Regents of the University of California.
+ * All rights reserved. The Berkeley software License Agreement
+ * specifies the terms and conditions for redistribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#ifndef lint
+static char SccsId[] = "%W% %E% SMI";
+#endif /* not lint */
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sysexits.h>
+#include <pwd.h>
+#include <ndbm.h>
+#include <string.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <strings.h>
+#include <errno.h>
+
+/*
+ * VACATION -- return a message to the sender when on vacation.
+ *
+ * This program could be invoked as a message receiver
+ * when someone is on vacation. It returns a message
+ * specified by the user to whoever sent the mail, taking
+ * care not to return a message too often to prevent
+ * "I am on vacation" loops.
+ *
+ * For best operation, this program should run setuid to
+ * root or uucp or someone else that sendmail will believe
+ * a -f flag from. Otherwise, the user must be careful
+ * to include a header on his .vacation.msg file.
+ *
+ * Positional Parameters:
+ * the user to collect the vacation message from.
+ *
+ * Flag Parameters:
+ * -I initialize the database.
+ * -d turn on debugging.
+ * -tT set the timeout to T. messages arriving more
+ * often than T will be ignored to avoid loops.
+ *
+ * Side Effects:
+ * A message is sent back to the sender.
+ *
+ * Author:
+ * Eric Allman
+ * UCB/INGRES
+ */
+
+#define MAXLINE 256 /* max size of a line */
+
+#define ONEWEEK (60L*60L*24L*7L)
+#define MsgFile "/.vacation.msg"
+#define FilterFile "/.vacation.filter"
+#define DbFileBase "/.vacation"
+#define _PATH_TMP "/tmp/vacation.XXXXXX"
+
+typedef int bool;
+
+#define FALSE 0
+#define TRUE 1
+
+static time_t Timeout = ONEWEEK; /* timeout between notices per user */
+static DBM *db;
+static bool Debug = FALSE;
+static bool AnswerAll = FALSE; /* default: answer if in To:/Cc: only */
+static char *Subject = NULL; /* subject in message header */
+static char *EncodedSubject = NULL; /* subject in message header */
+static char Charset[MAXLINE]; /* for use in reply message */
+static char *AliasList[MAXLINE]; /* list of aliases to allow */
+static int AliasCount = 0;
+static char *myname; /* name of person "on vacation" */
+static char *homedir; /* home directory of said person */
+
+extern time_t convtime(char *, char);
+extern bool decode_rfc2047(char *, char *, char *);
+
+static bool ask(char *);
+static bool junkmail(char *);
+static bool filter_ok(char *, char *);
+static bool knows(char *);
+static bool sameword(char *, char *);
+static char *getfrom(char **);
+static char *newstr(char *);
+static void AutoInstall();
+static void initialize(char *);
+static void sendmessage(char *, char *, char *);
+static void setknows(char *);
+
+void usrerr(const char *, ...);
+
+int
+main(argc, argv)
+ int argc;
+ char **argv;
+{
+ char *from;
+ char *p, *at, *c;
+ struct passwd *pw;
+ char *shortfrom;
+ char buf[MAXLINE];
+ char *message_file = MsgFile;
+ char *db_file_base = DbFileBase;
+ char *filter_file = FilterFile;
+ char *sender;
+ bool sender_oob = FALSE;
+ bool initialize_only = FALSE;
+
+ /* process arguments */
+ while (--argc > 0 && (p = *++argv) != NULL && *p == '-')
+ {
+ switch (*++p)
+ {
+ case 'a': /* add this to list of acceptable aliases */
+ AliasList[AliasCount++] = argv[1];
+ if (argc > 0) {
+ argc--; argv++;
+ }
+ break;
+
+ case 'd': /* debug */
+ Debug = TRUE;
+ break;
+
+ case 'e': /* alternate filter file */
+ filter_file = argv[1];
+ if (argc > 0) {
+ argc--; argv++;
+ }
+ break;
+
+ case 'f': /* alternate database file name base */
+ db_file_base = argv[1];
+ if (argc > 0) {
+ argc--; argv++;
+ }
+ break;
+
+ case 'I': /* initialize */
+ initialize_only = TRUE;
+ break;
+
+ case 'j': /* answer all mail, even if not in To/Cc */
+ AnswerAll = TRUE;
+ break;
+
+ case 'm': /* alternate message file */
+ message_file = argv[1];
+ if (argc > 0) {
+ argc--; argv++;
+ }
+ break;
+
+ case 's': /* sender: use this instead of getfrom() */
+ sender = argv[1];
+ sender_oob = TRUE;
+ if (argc > 0) {
+ argc--; argv++;
+ }
+ break;
+
+ case 't': /* set timeout */
+ Timeout = convtime(++p, 'w');
+ break;
+
+ default:
+ usrerr("Unknown flag -%s", p);
+ exit(EX_USAGE);
+ }
+ }
+
+ if (initialize_only)
+ {
+ initialize(db_file_base);
+ exit(EX_OK);
+ }
+
+ /* verify recipient argument */
+ if (argc == 0)
+ AutoInstall();
+
+ if (argc != 1)
+ {
+ usrerr("Usage: vacation username (or) vacation -I");
+ exit(EX_USAGE);
+ }
+
+ myname = p;
+ Charset[0] = '\0';
+
+ /* find user's home directory */
+ pw = getpwnam(myname);
+ if (pw == NULL)
+ {
+ usrerr("user %s look up failed, name services outage ?",
+ myname);
+ exit(EX_TEMPFAIL);
+ }
+ homedir = newstr(pw->pw_dir);
+
+ (void) snprintf(buf, sizeof (buf), "%s%s%s", homedir,
+ (db_file_base[0] == '/') ? "" : "/", db_file_base);
+ if (!(db = dbm_open(buf, O_RDWR, 0))) {
+ usrerr("%s: %s\n", buf, strerror(errno));
+ exit(EX_DATAERR);
+ }
+
+ if (sender_oob)
+ {
+ at = strchr(sender, '@');
+ if (at != NULL)
+ for (c = at + 1; *c; c++)
+ *c = (char)tolower((char)*c);
+ from = sender;
+ shortfrom = sender;
+ }
+ else
+ /* read message from standard input (just from line) */
+ from = getfrom(&shortfrom);
+
+ /* check if junk mail or this person is already informed */
+ if (!junkmail(shortfrom) && filter_ok(shortfrom, filter_file) &&
+ !knows(shortfrom))
+ {
+ /* mark this person as knowing */
+ setknows(shortfrom);
+
+ /* send the message back */
+ (void) strlcpy(buf, homedir, sizeof (buf));
+ if (message_file[0] != '/')
+ (void) strlcat(buf, "/", sizeof (buf));
+ (void) strlcat(buf, message_file, sizeof (buf));
+ if (Debug)
+ printf("Sending %s to %s\n", buf, from);
+ else
+ {
+ sendmessage(buf, from, myname);
+ /*NOTREACHED*/
+ }
+ }
+ return (EX_OK);
+}
+
+/*
+ * GETFROM -- read message from standard input and return sender
+ *
+ * Parameters:
+ * none.
+ *
+ * Returns:
+ * pointer to the sender address.
+ *
+ * Side Effects:
+ * Reads first line from standard input.
+ */
+
+static char *
+getfrom(shortp)
+char **shortp;
+{
+ static char line[MAXLINE];
+ char *p, *start, *at, *bang, *c;
+ char saveat;
+
+ /* read the from line */
+ if (fgets(line, sizeof (line), stdin) == NULL ||
+ strncmp(line, "From ", 5) != NULL)
+ {
+ usrerr("No initial From line");
+ exit(EX_PROTOCOL);
+ }
+
+ /* find the end of the sender address and terminate it */
+ start = &line[5];
+ p = strchr(start, ' ');
+ if (p == NULL)
+ {
+ usrerr("Funny From line '%s'", line);
+ exit(EX_PROTOCOL);
+ }
+ *p = '\0';
+
+ /*
+ * Strip all but the rightmost UUCP host
+ * to prevent loops due to forwarding.
+ * Start searching leftward from the leftmost '@'.
+ * a!b!c!d yields a short name of c!d
+ * a!b!c!d@e yields a short name of c!d@e
+ * e@a!b!c yields the same short name
+ */
+#ifdef VDEBUG
+printf("start='%s'\n", start);
+#endif /* VDEBUG */
+ *shortp = start; /* assume whole addr */
+ if ((at = strchr(start, '@')) == NULL) /* leftmost '@' */
+ at = p; /* if none, use end of addr */
+ saveat = *at;
+ *at = '\0';
+ if ((bang = strrchr(start, '!')) != NULL) { /* rightmost '!' */
+ char *bang2;
+ *bang = '\0';
+ /* 2nd rightmost '!' */
+ if ((bang2 = strrchr(start, '!')) != NULL)
+ *shortp = bang2 + 1; /* move past ! */
+ *bang = '!';
+ }
+ *at = saveat;
+#ifdef VDEBUG
+printf("place='%s'\n", *shortp);
+#endif /* VDEBUG */
+ for (c = at + 1; *c; c++)
+ *c = (char)tolower((char)*c);
+
+ /* return the sender address */
+ return (start);
+}
+
+/*
+ * JUNKMAIL -- read the header and tell us if this is junk/bulk mail.
+ *
+ * Parameters:
+ * from -- the Return-Path of the sender. We assume that
+ * anything from "*-REQUEST@*" is bulk mail.
+ *
+ * Returns:
+ * TRUE -- if this is junk or bulk mail (that is, if the
+ * sender shouldn't receive a response).
+ * FALSE -- if the sender deserves a response.
+ *
+ * Side Effects:
+ * May read the header from standard input. When this
+ * returns the position on stdin is undefined.
+ */
+
+static bool
+junkmail(from)
+ char *from;
+{
+ register char *p;
+ char buf[MAXLINE+1];
+ bool inside, onlist;
+
+ /* test for inhuman sender */
+ p = strrchr(from, '@');
+ if (p != NULL)
+ {
+ *p = '\0';
+ if (sameword(&p[-8], "-REQUEST") ||
+ sameword(&p[-10], "Postmaster") ||
+ sameword(&p[-13], "MAILER-DAEMON"))
+ {
+ *p = '@';
+ return (TRUE);
+ }
+ *p = '@';
+ }
+
+#define Delims " \n\t:,:;()<>@!"
+
+ /* read the header looking for "interesting" lines */
+ inside = FALSE;
+ onlist = FALSE;
+ while (fgets(buf, MAXLINE, stdin) != NULL && buf[0] != '\n')
+ {
+ if (buf[0] != ' ' && buf[0] != '\t' && strchr(buf, ':') == NULL)
+ return (FALSE); /* no header found */
+
+ p = strtok(buf, Delims);
+ if (p == NULL)
+ continue;
+
+ if (sameword(p, "To") || sameword(p, "Cc"))
+ {
+ inside = TRUE;
+ p = strtok((char *)NULL, Delims);
+ if (p == NULL)
+ continue;
+
+ } else /* continuation line? */
+ if (inside)
+ inside = (buf[0] == ' ' || buf[0] == '\t');
+
+ if (inside) {
+ int i;
+
+ do {
+ if (sameword(p, myname))
+ onlist = TRUE; /* I am on the list */
+
+ for (i = 0; i < AliasCount; i++)
+ if (sameword(p, AliasList[i]))
+ onlist = TRUE; /* alias on list */
+
+ } while (p = strtok((char *)NULL, Delims));
+ continue;
+ }
+
+ if (sameword(p, "Precedence"))
+ {
+ /* find the value of this field */
+ p = strtok((char *)NULL, Delims);
+ if (p == NULL)
+ continue;
+
+ /* see if it is "junk" or "bulk" */
+ p[4] = '\0';
+ if (sameword(p, "junk") || sameword(p, "bulk"))
+ return (TRUE);
+ }
+
+ if (sameword(p, "Subject"))
+ {
+ char *decoded_subject;
+
+ Subject = newstr(buf+9);
+ if (p = strrchr(Subject, '\n'))
+ *p = '\0';
+ EncodedSubject = newstr(Subject);
+ decoded_subject = newstr(Subject);
+ if (decode_rfc2047(Subject, decoded_subject, Charset))
+ Subject = decoded_subject;
+ else
+ Charset[0] = '\0';
+ if (Debug)
+ printf("Subject=%s\n", Subject);
+ }
+ }
+ if (AnswerAll)
+ return (FALSE);
+ else
+ return (!onlist);
+}
+
+/*
+ * FILTER_OK -- see if the Return-Path is in the filter file.
+ * Note that a non-existent filter file means everything
+ * is OK, but an empty file means nothing is OK.
+ *
+ * Parameters:
+ * from -- the Return-Path of the sender.
+ *
+ * Returns:
+ * TRUE -- if this is in the filter file
+ * (sender should receive a response).
+ * FALSE -- if the sender does not deserve a response.
+ */
+
+static bool
+filter_ok(from, filter_file)
+ char *from;
+ char *filter_file;
+{
+ char file[MAXLINE];
+ char line[MAXLINE];
+ size_t line_len, from_len;
+ bool result = FALSE;
+ FILE *f;
+
+ from_len = strlen(from);
+ (void) strlcpy(file, homedir, sizeof (file));
+ if (filter_file[0] != '/')
+ (void) strlcat(file, "/", sizeof (file));
+ (void) strlcat(file, filter_file, sizeof (file));
+ f = fopen(file, "r");
+ if (f == NULL) {
+ /*
+ * If the file does not exist, then there is no filter to
+ * apply, so we simply return TRUE.
+ */
+ if (Debug)
+ (void) printf("%s does not exist, filter ok.\n",
+ file);
+ return (TRUE);
+ }
+ while (fgets(line, MAXLINE, f)) {
+ line_len = strlen(line);
+ /* zero out trailing newline */
+ if (line[line_len - 1] == '\n')
+ line[--line_len] = '\0';
+ /* skip blank lines */
+ if (line_len == 0)
+ continue;
+ /* skip comment lines */
+ if (line[0] == '#')
+ continue;
+ if (strchr(line, '@') != NULL) {
+ /* @ => full address */
+ if (strcasecmp(line, from) == 0) {
+ result = TRUE;
+ if (Debug)
+ (void) printf("filter match on %s\n",
+ line);
+ break;
+ }
+ } else {
+ /* no @ => domain */
+ if (from_len <= line_len)
+ continue;
+ /*
+ * Make sure the last part of from is the domain line
+ * and that the character immediately preceding is an
+ * '@' or a '.', otherwise we could get false positives
+ * from e.g. twinsun.com for sun.com .
+ */
+ if (strncasecmp(&from[from_len - line_len], line,
+ line_len) == 0 &&
+ (from[from_len - line_len -1] == '@' ||
+ from[from_len - line_len -1] == '.')) {
+ result = TRUE;
+ if (Debug)
+ (void) printf("filter match on %s\n",
+ line);
+ break;
+ }
+ }
+ }
+ (void) fclose(f);
+ if (Debug && !result)
+ (void) printf("no filter match\n");
+ return (result);
+}
+
+/*
+ * KNOWS -- predicate telling if user has already been informed.
+ *
+ * Parameters:
+ * user -- the user who sent this message.
+ *
+ * Returns:
+ * TRUE if 'user' has already been informed that the
+ * recipient is on vacation.
+ * FALSE otherwise.
+ *
+ * Side Effects:
+ * none.
+ */
+
+static bool
+knows(user)
+ char *user;
+{
+ datum key, data;
+ time_t now, then;
+
+ (void) time(&now);
+ key.dptr = user;
+ key.dsize = strlen(user) + 1;
+ data = dbm_fetch(db, key);
+ if (data.dptr == NULL)
+ return (FALSE);
+
+ bcopy(data.dptr, (char *)&then, sizeof (then));
+ if (then + Timeout < now)
+ return (FALSE);
+ if (Debug)
+ printf("User %s already knows\n", user);
+ return (TRUE);
+}
+
+/*
+ * SETKNOWS -- set that this user knows about the vacation.
+ *
+ * Parameters:
+ * user -- the user who should be marked.
+ *
+ * Returns:
+ * none.
+ *
+ * Side Effects:
+ * The dbm file is updated as appropriate.
+ */
+
+static void
+setknows(user)
+ char *user;
+{
+ datum key, data;
+ time_t now;
+
+ key.dptr = user;
+ key.dsize = strlen(user) + 1;
+ (void) time(&now);
+ data.dptr = (char *)&now;
+ data.dsize = sizeof (now);
+ dbm_store(db, key, data, DBM_REPLACE);
+}
+
+static bool
+any8bitchars(line)
+ char *line;
+{
+ char *c;
+
+ for (c = line; *c; c++)
+ if (*c & 0x80)
+ return (TRUE);
+ return (FALSE);
+}
+
+/*
+ * SENDMESSAGE -- send a message to a particular user.
+ *
+ * Parameters:
+ * msgf -- filename containing the message.
+ * user -- user who should receive it.
+ *
+ * Returns:
+ * none.
+ *
+ * Side Effects:
+ * sends mail to 'user' using /usr/lib/sendmail.
+ */
+
+static void
+sendmessage(msgf, user, myname)
+ char *msgf;
+ char *user;
+ char *myname;
+{
+ FILE *f, *fpipe, *tmpf;
+ char line[MAXLINE];
+ char *p, *tmpf_name;
+ int i, pipefd[2], tmpfd;
+ bool seen8bitchars = FALSE;
+ bool in_header = TRUE;
+
+ /* find the message to send */
+ f = fopen(msgf, "r");
+ if (f == NULL)
+ {
+ f = fopen("/etc/mail/vacation.def", "r");
+ if (f == NULL)
+ usrerr("No message to send");
+ exit(EX_OSFILE);
+ }
+
+ if (pipe(pipefd) < 0) {
+ usrerr("pipe() failed");
+ exit(EX_OSERR);
+ }
+ i = fork();
+ if (i < 0) {
+ usrerr("fork() failed");
+ exit(EX_OSERR);
+ }
+ if (i == 0) {
+ dup2(pipefd[0], 0);
+ close(pipefd[0]);
+ close(pipefd[1]);
+ fclose(f);
+ execl("/usr/lib/sendmail", "sendmail", "-eq", "-f", myname,
+ "--", user, NULL);
+ usrerr("can't exec /usr/lib/sendmail");
+ exit(EX_OSERR);
+ }
+ close(pipefd[0]);
+ fpipe = fdopen(pipefd[1], "w");
+ if (fpipe == NULL) {
+ usrerr("fdopen() failed");
+ exit(EX_OSERR);
+ }
+ fprintf(fpipe, "To: %s\n", user);
+ fputs("Auto-Submitted: auto-replied\n", fpipe);
+ fputs("X-Mailer: vacation %I%\n", fpipe);
+
+ /*
+ * We used to write directly to the pipe. But now we need to know
+ * what character set to use, and we need to examine the entire
+ * message to determine this. So write to a temp file first.
+ */
+ tmpf_name = strdup(_PATH_TMP);
+ if (tmpf_name == NULL) {
+ usrerr("newstr: cannot alloc memory");
+ exit(EX_OSERR);
+ }
+ tmpfd = -1;
+ tmpfd = mkstemp(tmpf_name);
+ if (tmpfd == -1) {
+ usrerr("can't open temp file %s", tmpf_name);
+ exit(EX_OSERR);
+ }
+ tmpf = fdopen(tmpfd, "w");
+ if (tmpf == NULL) {
+ usrerr("can't open temp file %s", tmpf_name);
+ exit(EX_OSERR);
+ }
+ while (fgets(line, MAXLINE, f)) {
+ /*
+ * Check for a line with no ':' character. If it's just \n,
+ * we're at the end of the headers and all is fine. Or if
+ * it starts with white-space, then it's a continuation header.
+ * Otherwise, it's the start of the body, which means the
+ * header/body separator was skipped. So output it.
+ */
+ if (in_header && line[0] != '\0' && strchr(line, ':') == NULL) {
+ if (line[0] == '\n')
+ in_header = FALSE;
+ else if (!isspace(line[0])) {
+ in_header = FALSE;
+ fputs("\n", tmpf);
+ }
+ }
+ p = strchr(line, '$');
+ if (p && strncmp(p, "$SUBJECT", 8) == 0) {
+ *p = '\0';
+ seen8bitchars |= any8bitchars(line);
+ fputs(line, tmpf);
+ if (Subject) {
+ if (in_header)
+ fputs(EncodedSubject, tmpf);
+ else {
+ seen8bitchars |= any8bitchars(Subject);
+ fputs(Subject, tmpf);
+ }
+ }
+ seen8bitchars |= any8bitchars(p+8);
+ fputs(p+8, tmpf);
+ continue;
+ }
+ seen8bitchars |= any8bitchars(line);
+ fputs(line, tmpf);
+ }
+ fclose(f);
+ fclose(tmpf);
+
+ /*
+ * If we haven't seen a funky Subject with Charset, use the default.
+ * If we have and it's us-ascii, 8-bit chars in the message file will
+ * still result in iso-8859-1.
+ */
+ if (Charset[0] == '\0')
+ (void) strlcpy(Charset, (seen8bitchars) ? "iso-8859-1" :
+ "us-ascii", sizeof (Charset));
+ else if ((strcasecmp(Charset, "us-ascii") == 0) && seen8bitchars)
+ (void) strlcpy(Charset, "iso-8859-1", sizeof (Charset));
+ if (Debug)
+ printf("Charset is %s\n", Charset);
+ fprintf(fpipe, "Content-Type: text/plain; charset=%s\n", Charset);
+ fputs("Mime-Version: 1.0\n", fpipe);
+
+ /*
+ * Now read back in from the temp file and write to the pipe.
+ */
+ tmpf = fopen(tmpf_name, "r");
+ if (tmpf == NULL) {
+ usrerr("can't open temp file %s", tmpf_name);
+ exit(EX_OSERR);
+ }
+ while (fgets(line, MAXLINE, tmpf))
+ fputs(line, fpipe);
+ fclose(fpipe);
+ fclose(tmpf);
+ (void) unlink(tmpf_name);
+ free(tmpf_name);
+}
+
+/*
+ * INITIALIZE -- initialize the database before leaving for vacation
+ *
+ * Parameters:
+ * none.
+ *
+ * Returns:
+ * none.
+ *
+ * Side Effects:
+ * Initializes the files .vacation.{pag,dir} in the
+ * caller's home directory.
+ */
+
+static void
+initialize(db_file_base)
+ char *db_file_base;
+{
+ char *homedir;
+ char buf[MAXLINE];
+ DBM *db;
+
+ setgid(getgid());
+ setuid(getuid());
+ homedir = getenv("HOME");
+ if (homedir == NULL) {
+ usrerr("No home!");
+ exit(EX_NOUSER);
+ }
+ (void) snprintf(buf, sizeof (buf), "%s%s%s", homedir,
+ (db_file_base[0] == '/') ? "" : "/", db_file_base);
+
+ if (!(db = dbm_open(buf, O_WRONLY|O_CREAT|O_TRUNC, 0644))) {
+ usrerr("%s: %s\n", buf, strerror(errno));
+ exit(EX_DATAERR);
+ }
+ dbm_close(db);
+}
+
+/*
+ * USRERR -- print user error
+ *
+ * Parameters:
+ * f -- format.
+ *
+ * Returns:
+ * none.
+ *
+ * Side Effects:
+ * none.
+ */
+
+/* PRINTFLIKE1 */
+void
+usrerr(const char *f, ...)
+{
+ va_list alist;
+
+ va_start(alist, f);
+ (void) fprintf(stderr, "vacation: ");
+ (void) vfprintf(stderr, f, alist);
+ (void) fprintf(stderr, "\n");
+ va_end(alist);
+}
+
+/*
+ * NEWSTR -- copy a string
+ *
+ * Parameters:
+ * s -- the string to copy.
+ *
+ * Returns:
+ * A copy of the string.
+ *
+ * Side Effects:
+ * none.
+ */
+
+static char *
+newstr(s)
+ char *s;
+{
+ char *p;
+ size_t s_sz = strlen(s);
+
+ p = malloc(s_sz + 1);
+ if (p == NULL)
+ {
+ usrerr("newstr: cannot alloc memory");
+ exit(EX_OSERR);
+ }
+ (void) strlcpy(p, s, s_sz + 1);
+ return (p);
+}
+
+/*
+ * SAMEWORD -- return TRUE if the words are the same
+ *
+ * Ignores case.
+ *
+ * Parameters:
+ * a, b -- the words to compare.
+ *
+ * Returns:
+ * TRUE if a & b match exactly (modulo case)
+ * FALSE otherwise.
+ *
+ * Side Effects:
+ * none.
+ */
+
+static bool
+sameword(a, b)
+ register char *a, *b;
+{
+ char ca, cb;
+
+ do
+ {
+ ca = *a++;
+ cb = *b++;
+ if (isascii(ca) && isupper(ca))
+ ca = ca - 'A' + 'a';
+ if (isascii(cb) && isupper(cb))
+ cb = cb - 'A' + 'a';
+ } while (ca != '\0' && ca == cb);
+ return (ca == cb);
+}
+
+/*
+ * When invoked with no arguments, we fall into an automatic installation
+ * mode, stepping the user through a default installation.
+ */
+
+static void
+AutoInstall()
+{
+ char file[MAXLINE];
+ char forward[MAXLINE];
+ char cmd[MAXLINE];
+ char line[MAXLINE];
+ char *editor;
+ FILE *f;
+ struct passwd *pw;
+ extern mode_t umask(mode_t cmask);
+
+ umask(022);
+ pw = getpwuid(getuid());
+ if (pw == NULL) {
+ usrerr("User ID unknown");
+ exit(EX_NOUSER);
+ }
+ myname = strdup(pw->pw_name);
+ if (myname == NULL) {
+ usrerr("Out of memory");
+ exit(EX_OSERR);
+ }
+ homedir = getenv("HOME");
+ if (homedir == NULL) {
+ usrerr("Home directory unknown");
+ exit(EX_NOUSER);
+ }
+
+ printf("This program can be used to answer your mail automatically\n");
+ printf("when you go away on vacation.\n");
+ (void) strlcpy(file, homedir, sizeof (file));
+ (void) strlcat(file, MsgFile, sizeof (file));
+ do {
+ f = fopen(file, "r");
+ if (f) {
+ printf("You have a message file in %s.\n", file);
+ if (ask("Would you like to see it")) {
+ (void) snprintf(cmd, sizeof (cmd),
+ "/usr/bin/more %s", file);
+ system(cmd);
+ }
+ if (ask("Would you like to edit it"))
+ f = NULL;
+ } else {
+ printf("You need to create a message file"
+ " in %s first.\n", file);
+ f = fopen(file, "w");
+ if (f == NULL) {
+ usrerr("Cannot open %s", file);
+ exit(EX_CANTCREAT);
+ }
+ fprintf(f, "Subject: away from my mail\n");
+ fprintf(f, "\nI will not be reading my mail"
+ " for a while.\n");
+ fprintf(f, "Your mail regarding \"$SUBJECT\" will"
+ " be read when I return.\n");
+ fclose(f);
+ f = NULL;
+ }
+ if (f == NULL) {
+ editor = getenv("VISUAL");
+ if (editor == NULL)
+ editor = getenv("EDITOR");
+ if (editor == NULL)
+ editor = "/usr/bin/vi";
+ (void) snprintf(cmd, sizeof (cmd), "%s %s", editor,
+ file);
+ printf("Please use your editor (%s)"
+ " to edit this file.\n", editor);
+ system(cmd);
+ }
+ } while (f == NULL);
+ fclose(f);
+ (void) strlcpy(forward, homedir, sizeof (forward));
+ (void) strlcat(forward, "/.forward", sizeof (forward));
+ f = fopen(forward, "r");
+ if (f) {
+ printf("You have a .forward file"
+ " in your home directory containing:\n");
+ while (fgets(line, MAXLINE, f))
+ printf(" %s", line);
+ fclose(f);
+ if (!ask("Would you like to remove it and"
+ " disable the vacation feature"))
+ exit(EX_OK);
+ if (unlink(forward))
+ perror("Error removing .forward file:");
+ else
+ printf("Back to normal reception of mail.\n");
+ exit(EX_OK);
+ }
+
+ printf("To enable the vacation feature"
+ " a \".forward\" file is created.\n");
+ if (!ask("Would you like to enable the vacation feature")) {
+ printf("OK, vacation feature NOT enabled.\n");
+ exit(EX_OK);
+ }
+ f = fopen(forward, "w");
+ if (f == NULL) {
+ perror("Error opening .forward file");
+ exit(EX_CANTCREAT);
+ }
+ fprintf(f, "\\%s, \"|/usr/bin/vacation %s\"\n", myname, myname);
+ fclose(f);
+ printf("Vacation feature ENABLED."
+ " Please remember to turn it off when\n");
+ printf("you get back from vacation. Bon voyage.\n");
+
+ initialize(DbFileBase);
+ exit(EX_OK);
+}
+
+
+/*
+ * Ask the user a question until we get a reasonable answer
+ */
+
+static bool
+ask(prompt)
+ char *prompt;
+{
+ char line[MAXLINE];
+ char *res;
+
+ for (;;) {
+ printf("%s? ", prompt);
+ fflush(stdout);
+ res = fgets(line, sizeof (line), stdin);
+ if (res == NULL)
+ return (FALSE);
+ if (res[0] == 'y' || res[0] == 'Y')
+ return (TRUE);
+ if (res[0] == 'n' || res[0] == 'N')
+ return (FALSE);
+ printf("Please reply \"yes\" or \"no\" (\'y\' or \'n\')\n");
+ }
+}
diff --git a/usr/src/cmd/sendmail/cf/Makefile b/usr/src/cmd/sendmail/cf/Makefile
new file mode 100644
index 0000000000..fc2cfcfcd9
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/Makefile
@@ -0,0 +1,181 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License, Version 1.0 only
+# (the "License"). You may not use this file except in compliance
+# with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+#
+# Copyright 2004 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# ident "%Z%%M% %I% %E% SMI"
+#
+# cmd/sendmail/cf/Makefile
+#
+
+include ../../Makefile.cmd
+
+CFS= sendmail.cf submit.cf
+
+SUBCFS= cf/sendmail.cf cf/submit.cf
+
+COMMONM4FILES= m4/version.m4 m4/cf.m4 m4/cfhead.m4 m4/proto.m4 \
+ ostype/solaris8.m4 domain/solaris-generic.m4 \
+ mailer/local.m4 mailer/smtp.m4
+
+ROOTETCMAIL = $(ROOTETC)/mail
+
+ROOTETCMAILFILES=$(CFS:%=$(ROOTETCMAIL)/%)
+
+ROOTETCMAILCF = $(ROOTETCMAIL)/cf
+
+BUILDPARTS = $(ROOTETCMAILCF)/README \
+ $(ROOTETCMAILCF)/cf/Makefile \
+ $(ROOTETCMAILCF)/cf/sendmail.cf \
+ $(ROOTETCMAILCF)/cf/sendmail.mc \
+ $(ROOTETCMAILCF)/cf/submit.cf \
+ $(ROOTETCMAILCF)/cf/submit.mc \
+ $(ROOTETCMAILCF)/domain/generic.m4 \
+ $(ROOTETCMAILCF)/domain/solaris-antispam.m4 \
+ $(ROOTETCMAILCF)/domain/solaris-generic.m4 \
+ $(ROOTETCMAILCF)/feature/accept_unqualified_senders.m4 \
+ $(ROOTETCMAILCF)/feature/accept_unresolvable_domains.m4 \
+ $(ROOTETCMAILCF)/feature/access_db.m4 \
+ $(ROOTETCMAILCF)/feature/allmasquerade.m4 \
+ $(ROOTETCMAILCF)/feature/always_add_domain.m4 \
+ $(ROOTETCMAILCF)/feature/bestmx_is_local.m4 \
+ $(ROOTETCMAILCF)/feature/bitdomain.m4 \
+ $(ROOTETCMAILCF)/feature/blacklist_recipients.m4 \
+ $(ROOTETCMAILCF)/feature/compat_check.m4 \
+ $(ROOTETCMAILCF)/feature/conncontrol.m4 \
+ $(ROOTETCMAILCF)/feature/delay_checks.m4 \
+ $(ROOTETCMAILCF)/feature/dnsbl.m4 \
+ $(ROOTETCMAILCF)/feature/domaintable.m4 \
+ $(ROOTETCMAILCF)/feature/enhdnsbl.m4 \
+ $(ROOTETCMAILCF)/feature/generics_entire_domain.m4 \
+ $(ROOTETCMAILCF)/feature/genericstable.m4 \
+ $(ROOTETCMAILCF)/feature/greet_pause.m4 \
+ $(ROOTETCMAILCF)/feature/ldap_routing.m4 \
+ $(ROOTETCMAILCF)/feature/limited_masquerade.m4 \
+ $(ROOTETCMAILCF)/feature/local_lmtp.m4 \
+ $(ROOTETCMAILCF)/feature/local_no_masquerade.m4 \
+ $(ROOTETCMAILCF)/feature/local_procmail.m4 \
+ $(ROOTETCMAILCF)/feature/lookupdotdomain.m4 \
+ $(ROOTETCMAILCF)/feature/loose_relay_check.m4 \
+ $(ROOTETCMAILCF)/feature/mailertable.m4 \
+ $(ROOTETCMAILCF)/feature/masquerade_entire_domain.m4 \
+ $(ROOTETCMAILCF)/feature/masquerade_envelope.m4 \
+ $(ROOTETCMAILCF)/feature/msp.m4 \
+ $(ROOTETCMAILCF)/feature/mtamark.m4 \
+ $(ROOTETCMAILCF)/feature/no_default_msa.m4 \
+ $(ROOTETCMAILCF)/feature/nocanonify.m4 \
+ $(ROOTETCMAILCF)/feature/notsticky.m4 \
+ $(ROOTETCMAILCF)/feature/nouucp.m4 \
+ $(ROOTETCMAILCF)/feature/nullclient.m4 \
+ $(ROOTETCMAILCF)/feature/preserve_local_plus_detail.m4 \
+ $(ROOTETCMAILCF)/feature/preserve_luser_host.m4 \
+ $(ROOTETCMAILCF)/feature/promiscuous_relay.m4 \
+ $(ROOTETCMAILCF)/feature/queuegroup.m4 \
+ $(ROOTETCMAILCF)/feature/ratecontrol.m4 \
+ $(ROOTETCMAILCF)/feature/redirect.m4 \
+ $(ROOTETCMAILCF)/feature/relay_based_on_MX.m4 \
+ $(ROOTETCMAILCF)/feature/relay_entire_domain.m4 \
+ $(ROOTETCMAILCF)/feature/relay_hosts_only.m4 \
+ $(ROOTETCMAILCF)/feature/relay_local_from.m4 \
+ $(ROOTETCMAILCF)/feature/relay_mail_from.m4 \
+ $(ROOTETCMAILCF)/feature/smrsh.m4 \
+ $(ROOTETCMAILCF)/feature/stickyhost.m4 \
+ $(ROOTETCMAILCF)/feature/use_client_ptr.m4 \
+ $(ROOTETCMAILCF)/feature/use_ct_file.m4 \
+ $(ROOTETCMAILCF)/feature/use_cw_file.m4 \
+ $(ROOTETCMAILCF)/feature/uucpdomain.m4 \
+ $(ROOTETCMAILCF)/feature/virtuser_entire_domain.m4 \
+ $(ROOTETCMAILCF)/feature/virtusertable.m4 \
+ $(ROOTETCMAILCF)/m4/cf.m4 \
+ $(ROOTETCMAILCF)/m4/cfhead.m4 \
+ $(ROOTETCMAILCF)/m4/proto.m4 \
+ $(ROOTETCMAILCF)/m4/version.m4 \
+ $(ROOTETCMAILCF)/mailer/local.m4 \
+ $(ROOTETCMAILCF)/mailer/procmail.m4 \
+ $(ROOTETCMAILCF)/mailer/smtp.m4 \
+ $(ROOTETCMAILCF)/mailer/uucp.m4 \
+ $(ROOTETCMAILCF)/ostype/solaris2.m4 \
+ $(ROOTETCMAILCF)/ostype/solaris2.ml.m4 \
+ $(ROOTETCMAILCF)/ostype/solaris2.pre5.m4 \
+ $(ROOTETCMAILCF)/ostype/solaris8.m4 \
+ $(ROOTETCMAILCF)/sh/makeinfo.sh
+
+SCRIPTS= $(ROOTUSRSBIN)/check-hostname \
+ $(ROOTUSRSBIN)/check-permissions
+
+.PARALLEL: $(BUILDPARTS) $(CFS)
+
+FILEMODE= 444
+M4FLAGS= -DSUN_HIDE_INTERNAL_DETAILS
+
+$(BUILDPARTS) := OWNER=root
+$(BUILDPARTS) := GROUP=mail
+
+.KEEP_STATE:
+
+all: $(CFS)
+
+%.cf: cf/%.cf
+ $(CP) $< $@
+
+cf/%.cf: cf/%.mc $(COMMONM4FILES)
+ cd cf; pwd; $(M4) $(M4FLAGS) ../m4/cf.m4 $(<F) > $(@F)
+
+install: all $(ROOTETCMAILFILES) $(BUILDPARTS) $(SCRIPTS) .WAIT links
+
+clean:
+ $(RM) $(CFS) $(SUBCFS)
+
+clobber: clean
+
+lint:
+
+$(ROOTETCMAIL)/%.cf: %.cf
+ $(INS.file)
+
+$(ROOTETCMAILCF)/%: %
+ $(INS.file)
+
+$(ROOTUSRSBIN)/check-%: sh/check-%.sh
+ $(INS.rename)
+ $(CHMOD) +x $@
+
+links:
+ $(RM) $(ROOTLIB)/mail
+ $(SYMLINK) ../../etc/mail/cf $(ROOTLIB)/mail
+ $(RM) $(ROOTETCMAILCF)/sh/check-hostname
+ $(SYMLINK) ../../../../usr/sbin/check-hostname $(ROOTETCMAILCF)/sh/check-hostname
+ $(RM) $(ROOTETCMAILCF)/sh/check-permissions
+ $(SYMLINK) ../../../../usr/sbin/check-permissions $(ROOTETCMAILCF)/sh/check-permissions
+ $(RM) $(ROOTETCMAIL)/main.cf
+ $(SYMLINK) sendmail.cf $(ROOTETCMAIL)/main.cf
+ $(RM) $(ROOTETCMAIL)/subsidiary.cf
+ $(SYMLINK) sendmail.cf $(ROOTETCMAIL)/subsidiary.cf
+ $(RM) $(ROOTETCMAILCF)/cf/main.cf
+ $(SYMLINK) sendmail.cf $(ROOTETCMAILCF)/cf/main.cf
+ $(RM) $(ROOTETCMAILCF)/cf/main.mc
+ $(SYMLINK) sendmail.mc $(ROOTETCMAILCF)/cf/main.mc
+ $(RM) $(ROOTETCMAILCF)/cf/subsidiary.cf
+ $(SYMLINK) sendmail.cf $(ROOTETCMAILCF)/cf/subsidiary.cf
+ $(RM) $(ROOTETCMAILCF)/cf/subsidiary.mc
+ $(SYMLINK) sendmail.mc $(ROOTETCMAILCF)/cf/subsidiary.mc
diff --git a/usr/src/cmd/sendmail/cf/README b/usr/src/cmd/sendmail/cf/README
new file mode 100644
index 0000000000..d4efd7b14c
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/README
@@ -0,0 +1,4186 @@
+
+ SENDMAIL CONFIGURATION FILES
+
+This document describes the sendmail configuration files. It
+explains how to create a sendmail.cf file for use with sendmail.
+It also describes how to set options for sendmail which are explained
+in the Sendmail Installation and Operation guide, which can be found
+on-line at http://www.sendmail.org/%7Eca/email/doc8.12/op.html .
+Recall this URL throughout this document when references to
+doc/op/op.* are made.
+
+Table of Content:
+
+INTRODUCTION AND EXAMPLE
+A BRIEF INTRODUCTION TO M4
+FILE LOCATIONS
+OSTYPE
+DOMAINS
+MAILERS
+FEATURES
+HACKS
+SITE CONFIGURATION
+USING UUCP MAILERS
+TWEAKING RULESETS
+MASQUERADING AND RELAYING
+USING LDAP FOR ALIASES, MAPS, AND CLASSES
+LDAP ROUTING
+ANTI-SPAM CONFIGURATION CONTROL
+CONNECTION CONTROL
+STARTTLS
+ADDING NEW MAILERS OR RULESETS
+ADDING NEW MAIL FILTERS
+QUEUE GROUP DEFINITIONS
+NON-SMTP BASED CONFIGURATIONS
+WHO AM I?
+ACCEPTING MAIL FOR MULTIPLE NAMES
+USING MAILERTABLES
+USING USERDB TO MAP FULL NAMES
+MISCELLANEOUS SPECIAL FEATURES
+SECURITY NOTES
+TWEAKING CONFIGURATION OPTIONS
+MESSAGE SUBMISSION PROGRAM
+FORMAT OF FILES AND MAPS
+DIRECTORY LAYOUT
+ADMINISTRATIVE DETAILS
+
+
++--------------------------+
+| INTRODUCTION AND EXAMPLE |
++--------------------------+
+
+Configuration files are contained in the subdirectory "cf", with a
+suffix ".mc". They must be run through "m4" to produce a ".cf" file.
+You must pre-load "cf.m4":
+
+ m4 ${CFDIR}/m4/cf.m4 config.mc > config.cf
+
+Alternatively, you can simply:
+
+ cd ${CFDIR}/cf
+ /usr/ccs/bin/make config.cf
+
+where ${CFDIR} is the root of the cf directory and config.mc is the
+name of your configuration file. If you are running a version of M4
+that understands the __file__ builtin (versions of GNU m4 >= 0.75 do
+this, but the versions distributed with 4.4BSD and derivatives do not)
+or the -I flag (ditto), then ${CFDIR} can be in an arbitrary directory.
+For "traditional" versions, ${CFDIR} ***MUST*** be "..", or you MUST
+use -D_CF_DIR_=/path/to/cf/dir/ -- note the trailing slash! For example:
+
+ m4 -D_CF_DIR_=${CFDIR}/ ${CFDIR}/m4/cf.m4 config.mc > config.cf
+
+Let's examine a typical .mc file:
+
+ divert(-1)
+ #
+ # Copyright (c) 1998-2004 Sendmail, Inc. and its suppliers.
+ # All rights reserved.
+ # Copyright (c) 1983 Eric P. Allman. All rights reserved.
+ # Copyright (c) 1988, 1993
+ # The Regents of the University of California. All rights reserved.
+ #
+ # By using this file, you agree to the terms and conditions set
+ # forth in the LICENSE file which can be found at the top level of
+ # the sendmail distribution.
+ #
+
+ #
+ # This is a Berkeley-specific configuration file for HP-UX 9.x.
+ # It applies only to the Computer Science Division at Berkeley,
+ # and should not be used elsewhere. It is provided on the sendmail
+ # distribution as a sample only. To create your own configuration
+ # file, create an appropriate domain file in ../domain, change the
+ # `DOMAIN' macro below to reference that file, and copy the result
+ # to a name of your own choosing.
+ #
+ divert(0)
+
+The divert(-1) will delete the crud in the resulting output file.
+The copyright notice can be replaced by whatever your lawyers require;
+our lawyers require the one that is included in these files. A copyleft
+is a copyright by another name. The divert(0) restores regular output.
+
+ VERSIONID(`<SCCS or RCS version id>')
+
+VERSIONID is a macro that stuffs the version information into the
+resulting file. You could use SCCS, RCS, CVS, something else, or
+omit it completely. This is not the same as the version id included
+in SMTP greeting messages -- this is defined in m4/version.m4.
+
+ OSTYPE(`hpux9')dnl
+
+You must specify an OSTYPE to properly configure things such as the
+pathname of the help and status files, the flags needed for the local
+mailer, and other important things. If you omit it, you will get an
+error when you try to build the configuration. Look at the ostype
+directory for the list of known operating system types.
+
+ DOMAIN(`CS.Berkeley.EDU')dnl
+
+This example is specific to the Computer Science Division at Berkeley.
+You can use "DOMAIN(`generic')" to get a sufficiently bland definition
+that may well work for you, or you can create a customized domain
+definition appropriate for your environment.
+
+ MAILER(`local')
+ MAILER(`smtp')
+
+These describe the mailers used at the default CS site. The local
+mailer is always included automatically. Beware: MAILER declarations
+should only be followed by LOCAL_* sections. The general rules are
+that the order should be:
+
+ VERSIONID
+ OSTYPE
+ DOMAIN
+ FEATURE
+ local macro definitions
+ MAILER
+ LOCAL_CONFIG
+ LOCAL_RULE_*
+ LOCAL_RULESETS
+
+There are a few exceptions to this rule. Local macro definitions which
+influence a FEATURE() should be done before that feature. For example,
+a define(`PROCMAIL_MAILER_PATH', ...) should be done before
+FEATURE(`local_procmail').
+
+
++----------------------------+
+| A BRIEF INTRODUCTION TO M4 |
++----------------------------+
+
+Sendmail uses the M4 macro processor to ``compile'' the configuration
+files. The most important thing to know is that M4 is stream-based,
+that is, it doesn't understand about lines. For this reason, in some
+places you may see the word ``dnl'', which stands for ``delete
+through newline''; essentially, it deletes all characters starting
+at the ``dnl'' up to and including the next newline character. In
+most cases sendmail uses this only to avoid lots of unnecessary
+blank lines in the output.
+
+Other important directives are define(A, B) which defines the macro
+``A'' to have value ``B''. Macros are expanded as they are read, so
+one normally quotes both values to prevent expansion. For example,
+
+ define(`SMART_HOST', `smart.foo.com')
+
+One word of warning: M4 macros are expanded even in lines that appear
+to be comments. For example, if you have
+
+ # See FEATURE(`foo') above
+
+it will not do what you expect, because the FEATURE(`foo') will be
+expanded. This also applies to
+
+ # And then define the $X macro to be the return address
+
+because ``define'' is an M4 keyword. If you want to use them, surround
+them with directed quotes, `like this'.
+
+Since m4 uses single quotes (opening "`" and closing "'") to quote
+arguments, those quotes can't be used in arguments. For example,
+it is not possible to define a rejection message containing a single
+quote. Usually there are simple workarounds by changing those
+messages; in the worst case it might be ok to change the value
+directly in the generated .cf file, which however is not advised.
+
++----------------+
+| FILE LOCATIONS |
++----------------+
+
+sendmail 8.9 has introduced a new configuration directory for sendmail
+related files, /etc/mail. The new files available for sendmail 8.9 --
+the class {R} /etc/mail/relay-domains and the access database
+/etc/mail/access -- take advantage of this new directory. Beginning with
+8.10, all files will use this directory by default (some options may be
+set by OSTYPE() files). This new directory should help to restore
+uniformity to sendmail's file locations.
+
+Below is a table of some of the common changes:
+
+Old filename New filename
+------------ ------------
+/etc/bitdomain /etc/mail/bitdomain
+/etc/domaintable /etc/mail/domaintable
+/etc/genericstable /etc/mail/genericstable
+/etc/uudomain /etc/mail/uudomain
+/etc/virtusertable /etc/mail/virtusertable
+/etc/userdb /etc/mail/userdb
+
+/etc/aliases /etc/mail/aliases
+/etc/sendmail/aliases /etc/mail/aliases
+/etc/ucbmail/aliases /etc/mail/aliases
+/usr/adm/sendmail/aliases /etc/mail/aliases
+/usr/lib/aliases /etc/mail/aliases
+/usr/lib/mail/aliases /etc/mail/aliases
+/usr/ucblib/aliases /etc/mail/aliases
+
+/etc/sendmail.cw /etc/mail/local-host-names
+/etc/mail/sendmail.cw /etc/mail/local-host-names
+/etc/sendmail/sendmail.cw /etc/mail/local-host-names
+
+/etc/sendmail.ct /etc/mail/trusted-users
+
+/etc/sendmail.oE /etc/mail/error-header
+
+/etc/sendmail.hf /etc/mail/helpfile
+/etc/mail/sendmail.hf /etc/mail/helpfile
+/usr/ucblib/sendmail.hf /etc/mail/helpfile
+/etc/ucbmail/sendmail.hf /etc/mail/helpfile
+/usr/lib/sendmail.hf /etc/mail/helpfile
+/usr/share/lib/sendmail.hf /etc/mail/helpfile
+/usr/share/misc/sendmail.hf /etc/mail/helpfile
+/share/misc/sendmail.hf /etc/mail/helpfile
+
+/etc/service.switch /etc/mail/service.switch
+
+/etc/sendmail.st /etc/mail/statistics
+/etc/mail/sendmail.st /etc/mail/statistics
+/etc/mailer/sendmail.st /etc/mail/statistics
+/etc/sendmail/sendmail.st /etc/mail/statistics
+/usr/lib/sendmail.st /etc/mail/statistics
+/usr/ucblib/sendmail.st /etc/mail/statistics
+
+Note that all of these paths actually use a new m4 macro MAIL_SETTINGS_DIR
+to create the pathnames. The default value of this variable is
+`/etc/mail/'. If you set this macro to a different value, you MUST include
+a trailing slash.
+
+Notice: all filenames used in a .mc (or .cf) file should be absolute
+(starting at the root, i.e., with '/'). Relative filenames most
+likely cause surprises during operations (unless otherwise noted).
+
+
++--------+
+| OSTYPE |
++--------+
+
+You MUST define an operating system environment, or the configuration
+file build will puke. There are several environments available; look
+at the "ostype" directory for the current list. This macro changes
+things like the location of the alias file and queue directory. Some
+of these files are identical to one another.
+
+It is IMPERATIVE that the OSTYPE occur before any MAILER definitions.
+In general, the OSTYPE macro should go immediately after any version
+information, and MAILER definitions should always go last.
+
+Operating system definitions are usually easy to write. They may define
+the following variables (everything defaults, so an ostype file may be
+empty). Unfortunately, the list of configuration-supported systems is
+not as broad as the list of source-supported systems, since many of
+the source contributors do not include corresponding ostype files.
+
+ALIAS_FILE [/etc/mail/aliases] The location of the text version
+ of the alias file(s). It can be a comma-separated
+ list of names (but be sure you quote values with
+ commas in them -- for example, use
+ define(`ALIAS_FILE', `a,b')
+ to get "a" and "b" both listed as alias files;
+ otherwise the define() primitive only sees "a").
+HELP_FILE [/etc/mail/helpfile] The name of the file
+ containing information printed in response to
+ the SMTP HELP command.
+QUEUE_DIR [/var/spool/mqueue] The directory containing
+ queue files. To use multiple queues, supply
+ a value ending with an asterisk. For
+ example, /var/spool/mqueue/qd* will use all of the
+ directories or symbolic links to directories
+ beginning with 'qd' in /var/spool/mqueue as queue
+ directories. The names 'qf', 'df', and 'xf' are
+ reserved as specific subdirectories for the
+ corresponding queue file types as explained in
+ doc/op/op.me. See also QUEUE GROUP DEFINITIONS.
+MSP_QUEUE_DIR [/var/spool/clientmqueue] The directory containing
+ queue files for the MSP (Mail Submission Program).
+STATUS_FILE [/etc/mail/statistics] The file containing status
+ information.
+LOCAL_MAILER_PATH [/bin/mail] The program used to deliver local mail.
+LOCAL_MAILER_FLAGS [Prmn9] The flags used by the local mailer. The
+ flags lsDFMAw5:/|@q are always included.
+LOCAL_MAILER_ARGS [mail -d $u] The arguments passed to deliver local
+ mail.
+LOCAL_MAILER_MAX [undefined] If defined, the maximum size of local
+ mail that you are willing to accept.
+LOCAL_MAILER_MAXMSGS [undefined] If defined, the maximum number of
+ messages to deliver in a single connection. Only
+ useful for LMTP local mailers.
+LOCAL_MAILER_CHARSET [undefined] If defined, messages containing 8-bit data
+ that ARRIVE from an address that resolves to the
+ local mailer and which are converted to MIME will be
+ labeled with this character set.
+LOCAL_MAILER_EOL [undefined] If defined, the string to use as the
+ end of line for the local mailer.
+LOCAL_MAILER_DSN_DIAGNOSTIC_CODE
+ [X-Unix] The DSN Diagnostic-Code value for the
+ local mailer. This should be changed with care.
+LOCAL_SHELL_PATH [/bin/sh] The shell used to deliver piped email.
+LOCAL_SHELL_FLAGS [eu9] The flags used by the shell mailer. The
+ flags lsDFM are always included.
+LOCAL_SHELL_ARGS [sh -c $u] The arguments passed to deliver "prog"
+ mail.
+LOCAL_SHELL_DIR [$z:/] The directory search path in which the
+ shell should run.
+LOCAL_MAILER_QGRP [undefined] The queue group for the local mailer.
+SMTP_MAILER_FLAGS [undefined] Flags added to SMTP mailer. Default
+ flags are `mDFMuX' for all SMTP-based mailers; the
+ "esmtp" mailer adds `a'; "smtp8" adds `8'; and
+ "dsmtp" adds `%'.
+RELAY_MAILER_FLAGS [undefined] Flags added to the relay mailer. Default
+ flags are `mDFMuX' for all SMTP-based mailers; the
+ relay mailer adds `a8'. If this is not defined,
+ then SMTP_MAILER_FLAGS is used.
+SMTP_MAILER_MAX [undefined] The maximum size of messages that will
+ be transported using the smtp, smtp8, esmtp, or dsmtp
+ mailers.
+SMTP_MAILER_MAXMSGS [undefined] If defined, the maximum number of
+ messages to deliver in a single connection for the
+ smtp, smtp8, esmtp, or dsmtp mailers.
+SMTP_MAILER_MAXRCPTS [undefined] If defined, the maximum number of
+ recipients to deliver in a single connection for the
+ smtp, smtp8, esmtp, or dsmtp mailers.
+SMTP_MAILER_ARGS [TCP $h] The arguments passed to the smtp mailer.
+ About the only reason you would want to change this
+ would be to change the default port.
+ESMTP_MAILER_ARGS [TCP $h] The arguments passed to the esmtp mailer.
+SMTP8_MAILER_ARGS [TCP $h] The arguments passed to the smtp8 mailer.
+DSMTP_MAILER_ARGS [TCP $h] The arguments passed to the dsmtp mailer.
+RELAY_MAILER_ARGS [TCP $h] The arguments passed to the relay mailer.
+SMTP_MAILER_QGRP [undefined] The queue group for the smtp mailer.
+ESMTP_MAILER_QGRP [undefined] The queue group for the esmtp mailer.
+SMTP8_MAILER_QGRP [undefined] The queue group for the smtp8 mailer.
+DSMTP_MAILER_QGRP [undefined] The queue group for the dsmtp mailer.
+RELAY_MAILER_QGRP [undefined] The queue group for the relay mailer.
+RELAY_MAILER_MAXMSGS [undefined] If defined, the maximum number of
+ messages to deliver in a single connection for the
+ relay mailer.
+SMTP_MAILER_CHARSET [undefined] If defined, messages containing 8-bit data
+ that ARRIVE from an address that resolves to one of
+ the SMTP mailers and which are converted to MIME will
+ be labeled with this character set.
+UUCP_MAILER_PATH [/usr/bin/uux] The program used to send UUCP mail.
+UUCP_MAILER_FLAGS [undefined] Flags added to UUCP mailer. Default
+ flags are `DFMhuU' (and `m' for uucp-new mailer,
+ minus `U' for uucp-dom mailer).
+UUCP_MAILER_ARGS [uux - -r -z -a$g -gC $h!rmail ($u)] The arguments
+ passed to the UUCP mailer.
+UUCP_MAILER_MAX [100000] The maximum size message accepted for
+ transmission by the UUCP mailers.
+UUCP_MAILER_CHARSET [undefined] If defined, messages containing 8-bit data
+ that ARRIVE from an address that resolves to one of
+ the UUCP mailers and which are converted to MIME will
+ be labeled with this character set.
+UUCP_MAILER_QGRP [undefined] The queue group for the UUCP mailers.
+PROCMAIL_MAILER_PATH [/usr/local/bin/procmail] The path to the procmail
+ program. This is also used by
+ FEATURE(`local_procmail').
+PROCMAIL_MAILER_FLAGS [SPhnu9] Flags added to Procmail mailer. Flags
+ DFM are always set. This is NOT used by
+ FEATURE(`local_procmail'); tweak LOCAL_MAILER_FLAGS
+ instead.
+PROCMAIL_MAILER_ARGS [procmail -Y -m $h $f $u] The arguments passed to
+ the Procmail mailer. This is NOT used by
+ FEATURE(`local_procmail'); tweak LOCAL_MAILER_ARGS
+ instead.
+PROCMAIL_MAILER_MAX [undefined] If set, the maximum size message that
+ will be accepted by the procmail mailer.
+PROCMAIL_MAILER_QGRP [undefined] The queue group for the procmail mailer.
+confEBINDIR [/usr/libexec] The directory for executables.
+ Currently used for FEATURE(`local_lmtp') and
+ FEATURE(`smrsh').
+LOCAL_PROG_QGRP [undefined] The queue group for the prog mailer.
+
+Note: to tweak Name_MAILER_FLAGS use the macro MODIFY_MAILER_FLAGS:
+MODIFY_MAILER_FLAGS(`Name', `change') where Name is the first part of
+the macro Name_MAILER_FLAGS and change can be: flags that should
+be used directly (thus overriding the default value), or if it
+starts with `+' (`-') then those flags are added to (removed from)
+the default value. Example:
+
+ MODIFY_MAILER_FLAGS(`LOCAL', `+e')
+
+will add the flag `e' to LOCAL_MAILER_FLAGS. Notice: there are
+several smtp mailers all of which are manipulated individually.
+See the section MAILERS for the available mailer names.
+WARNING: The FEATUREs local_lmtp and local_procmail set LOCAL_MAILER_FLAGS
+unconditionally, i.e., without respecting any definitions in an
+OSTYPE setting.
+
+
++---------+
+| DOMAINS |
++---------+
+
+You will probably want to collect domain-dependent defines into one
+file, referenced by the DOMAIN macro. For example, the Berkeley
+domain file includes definitions for several internal distinguished
+hosts:
+
+UUCP_RELAY The host that will accept UUCP-addressed email.
+ If not defined, all UUCP sites must be directly
+ connected.
+BITNET_RELAY The host that will accept BITNET-addressed email.
+ If not defined, the .BITNET pseudo-domain won't work.
+DECNET_RELAY The host that will accept DECNET-addressed email.
+ If not defined, the .DECNET pseudo-domain and addresses
+ of the form node::user will not work.
+FAX_RELAY The host that will accept mail to the .FAX pseudo-domain.
+ The "fax" mailer overrides this value.
+LOCAL_RELAY The site that will handle unqualified names -- that
+ is, names without an @domain extension.
+ Normally MAIL_HUB is preferred for this function.
+ LOCAL_RELAY is mostly useful in conjunction with
+ FEATURE(`stickyhost') -- see the discussion of
+ stickyhost below. If not set, they are assumed to
+ belong on this machine. This allows you to have a
+ central site to store a company- or department-wide
+ alias database. This only works at small sites,
+ and only with some user agents.
+LUSER_RELAY The site that will handle lusers -- that is, apparently
+ local names that aren't local accounts or aliases. To
+ specify a local user instead of a site, set this to
+ ``local:username''.
+
+Any of these can be either ``mailer:hostname'' (in which case the
+mailer is the internal mailer name, such as ``uucp-new'' and the hostname
+is the name of the host as appropriate for that mailer) or just a
+``hostname'', in which case a default mailer type (usually ``relay'',
+a variant on SMTP) is used. WARNING: if you have a wildcard MX
+record matching your domain, you probably want to define these to
+have a trailing dot so that you won't get the mail diverted back
+to yourself.
+
+The domain file can also be used to define a domain name, if needed
+(using "DD<domain>") and set certain site-wide features. If all hosts
+at your site masquerade behind one email name, you could also use
+MASQUERADE_AS here.
+
+You do not have to define a domain -- in particular, if you are a
+single machine sitting off somewhere, it is probably more work than
+it's worth. This is just a mechanism for combining "domain dependent
+knowledge" into one place.
+
+
++---------+
+| MAILERS |
++---------+
+
+There are fewer mailers supported in this version than the previous
+version, owing mostly to a simpler world. As a general rule, put the
+MAILER definitions last in your .mc file.
+
+local The local and prog mailers. You will almost always
+ need these; the only exception is if you relay ALL
+ your mail to another site. This mailer is included
+ automatically.
+
+smtp The Simple Mail Transport Protocol mailer. This does
+ not hide hosts behind a gateway or another other
+ such hack; it assumes a world where everyone is
+ running the name server. This file actually defines
+ five mailers: "smtp" for regular (old-style) SMTP to
+ other servers, "esmtp" for extended SMTP to other
+ servers, "smtp8" to do SMTP to other servers without
+ converting 8-bit data to MIME (essentially, this is
+ your statement that you know the other end is 8-bit
+ clean even if it doesn't say so), "dsmtp" to do on
+ demand delivery, and "relay" for transmission to the
+ RELAY_HOST, LUSER_RELAY, or MAIL_HUB.
+
+uucp The UNIX-to-UNIX Copy Program mailer. Actually, this
+ defines two mailers, "uucp-old" (a.k.a. "uucp") and
+ "uucp-new" (a.k.a. "suucp"). The latter is for when you
+ know that the UUCP mailer at the other end can handle
+ multiple recipients in one transfer. If the smtp mailer
+ is included in your configuration, two other mailers
+ ("uucp-dom" and "uucp-uudom") are also defined [warning: you
+ MUST specify MAILER(`smtp') before MAILER(`uucp')]. When you
+ include the uucp mailer, sendmail looks for all names in
+ class {U} and sends them to the uucp-old mailer; all
+ names in class {Y} are sent to uucp-new; and all
+ names in class {Z} are sent to uucp-uudom. Note that
+ this is a function of what version of rmail runs on
+ the receiving end, and hence may be out of your control.
+ See the section below describing UUCP mailers in more
+ detail.
+
+procmail An interface to procmail (does not come with sendmail).
+ This is designed to be used in mailertables. For example,
+ a common question is "how do I forward all mail for a given
+ domain to a single person?". If you have this mailer
+ defined, you could set up a mailertable reading:
+
+ host.com procmail:/etc/procmailrcs/host.com
+
+ with the file /etc/procmailrcs/host.com reading:
+
+ :0 # forward mail for host.com
+ ! -oi -f $1 person@other.host
+
+ This would arrange for (anything)@host.com to be sent
+ to person@other.host. In a procmail script, $1 is the
+ name of the sender and $2 is the name of the recipient.
+ If you use this with FEATURE(`local_procmail'), the FEATURE
+ should be listed first.
+
+ Of course there are other ways to solve this particular
+ problem, e.g., a catch-all entry in a virtusertable.
+
+The local mailer accepts addresses of the form "user+detail", where
+the "+detail" is not used for mailbox matching but is available
+to certain local mail programs (in particular, see
+FEATURE(`local_procmail')). For example, "eric", "eric+sendmail", and
+"eric+sww" all indicate the same user, but additional arguments <null>,
+"sendmail", and "sww" may be provided for use in sorting mail.
+
+
++----------+
+| FEATURES |
++----------+
+
+Special features can be requested using the "FEATURE" macro. For
+example, the .mc line:
+
+ FEATURE(`use_cw_file')
+
+tells sendmail that you want to have it read an /etc/mail/local-host-names
+file to get values for class {w}. A FEATURE may contain up to 9
+optional parameters -- for example:
+
+ FEATURE(`mailertable', `dbm /usr/lib/mailertable')
+
+The default database map type for the table features can be set with
+
+ define(`DATABASE_MAP_TYPE', `dbm')
+
+which would set it to use ndbm databases. The default is the Berkeley DB
+hash database format. Note that you must still declare a database map type
+if you specify an argument to a FEATURE. DATABASE_MAP_TYPE is only used
+if no argument is given for the FEATURE. It must be specified before any
+feature that uses a map.
+
+Also, features which can take a map definition as an argument can also take
+the special keyword `LDAP'. If that keyword is used, the map will use the
+LDAP definition described in the ``USING LDAP FOR ALIASES, MAPS, AND
+CLASSES'' section below.
+
+Available features are:
+
+use_cw_file Read the file /etc/mail/local-host-names file to get
+ alternate names for this host. This might be used if you
+ were on a host that MXed for a dynamic set of other hosts.
+ If the set is static, just including the line "Cw<name1>
+ <name2> ..." (where the names are fully qualified domain
+ names) is probably superior. The actual filename can be
+ overridden by redefining confCW_FILE.
+
+use_ct_file Read the file /etc/mail/trusted-users file to get the
+ names of users that will be ``trusted'', that is, able to
+ set their envelope from address using -f without generating
+ a warning message. The actual filename can be overridden
+ by redefining confCT_FILE.
+
+redirect Reject all mail addressed to "address.REDIRECT" with
+ a ``551 User has moved; please try <address>'' message.
+ If this is set, you can alias people who have left
+ to their new address with ".REDIRECT" appended.
+
+nouucp Don't route UUCP addresses. This feature takes one
+ parameter:
+ `reject': reject addresses which have "!" in the local
+ part unless it originates from a system
+ that is allowed to relay.
+ `nospecial': don't do anything special with "!".
+ Warnings: 1. See the notice in the anti-spam section.
+ 2. don't remove "!" from OperatorChars if `reject' is
+ given as parameter.
+
+nocanonify Don't pass addresses to $[ ... $] for canonification
+ by default, i.e., host/domain names are considered canonical,
+ except for unqualified names, which must not be used in this
+ mode (violation of the standard). It can be changed by
+ setting the DaemonPortOptions modifiers (M=). That is,
+ FEATURE(`nocanonify') will be overridden by setting the
+ 'c' flag. Conversely, if FEATURE(`nocanonify') is not used,
+ it can be emulated by setting the 'C' flag
+ (DaemonPortOptions=Modifiers=C). This would generally only
+ be used by sites that only act as mail gateways or which have
+ user agents that do full canonification themselves. You may
+ also want to use
+ "define(`confBIND_OPTS', `-DNSRCH -DEFNAMES')" to turn off
+ the usual resolver options that do a similar thing.
+
+ An exception list for FEATURE(`nocanonify') can be
+ specified with CANONIFY_DOMAIN or CANONIFY_DOMAIN_FILE,
+ i.e., a list of domains which are nevertheless passed to
+ $[ ... $] for canonification. This is useful to turn on
+ canonification for local domains, e.g., use
+ CANONIFY_DOMAIN(`my.domain my') to canonify addresses
+ which end in "my.domain" or "my".
+ Another way to require canonification in the local
+ domain is CANONIFY_DOMAIN(`$=m').
+
+ A trailing dot is added to addresses with more than
+ one component in it such that other features which
+ expect a trailing dot (e.g., virtusertable) will
+ still work.
+
+ If `canonify_hosts' is specified as parameter, i.e.,
+ FEATURE(`nocanonify', `canonify_hosts'), then
+ addresses which have only a hostname, e.g.,
+ <user@host>, will be canonified (and hopefully fully
+ qualified), too.
+
+stickyhost This feature is sometimes used with LOCAL_RELAY,
+ although it can be used for a different effect with
+ MAIL_HUB.
+
+ When used without MAIL_HUB, email sent to
+ "user@local.host" are marked as "sticky" -- that
+ is, the local addresses aren't matched against UDB,
+ don't go through ruleset 5, and are not forwarded to
+ the LOCAL_RELAY (if defined).
+
+ With MAIL_HUB, mail addressed to "user@local.host"
+ is forwarded to the mail hub, with the envelope
+ address still remaining "user@local.host".
+ Without stickyhost, the envelope would be changed
+ to "user@mail_hub", in order to protect against
+ mailing loops.
+
+mailertable Include a "mailer table" which can be used to override
+ routing for particular domains (which are not in class {w},
+ i.e. local host names). The argument of the FEATURE may be
+ the key definition. If none is specified, the definition
+ used is:
+
+ hash /etc/mail/mailertable
+
+ Keys in this database are fully qualified domain names
+ or partial domains preceded by a dot -- for example,
+ "vangogh.CS.Berkeley.EDU" or ".CS.Berkeley.EDU". As a
+ special case of the latter, "." matches any domain not
+ covered by other keys. Values must be of the form:
+ mailer:domain
+ where "mailer" is the internal mailer name, and "domain"
+ is where to send the message. These maps are not
+ reflected into the message header. As a special case,
+ the forms:
+ local:user
+ will forward to the indicated user using the local mailer,
+ local:
+ will forward to the original user in the e-mail address
+ using the local mailer, and
+ error:code message
+ error:D.S.N:code message
+ will give an error message with the indicated SMTP reply
+ code and message, where D.S.N is an RFC 1893 compliant
+ error code.
+
+domaintable Include a "domain table" which can be used to provide
+ domain name mapping. Use of this should really be
+ limited to your own domains. It may be useful if you
+ change names (e.g., your company changes names from
+ oldname.com to newname.com). The argument of the
+ FEATURE may be the key definition. If none is specified,
+ the definition used is:
+
+ hash /etc/mail/domaintable
+
+ The key in this table is the domain name; the value is
+ the new (fully qualified) domain. Anything in the
+ domaintable is reflected into headers; that is, this
+ is done in ruleset 3.
+
+bitdomain Look up bitnet hosts in a table to try to turn them into
+ internet addresses. The table can be built using the
+ bitdomain program contributed by John Gardiner Myers.
+ The argument of the FEATURE may be the key definition; if
+ none is specified, the definition used is:
+
+ hash /etc/mail/bitdomain
+
+ Keys are the bitnet hostname; values are the corresponding
+ internet hostname.
+
+uucpdomain Similar feature for UUCP hosts. The default map definition
+ is:
+
+ hash /etc/mail/uudomain
+
+ At the moment there is no automagic tool to build this
+ database.
+
+always_add_domain
+ Include the local host domain even on locally delivered
+ mail. Normally it is not added on unqualified names.
+ However, if you use a shared message store but do not use
+ the same user name space everywhere, you may need the host
+ name on local names. An optional argument specifies
+ another domain to be added than the local.
+
+allmasquerade If masquerading is enabled (using MASQUERADE_AS), this
+ feature will cause recipient addresses to also masquerade
+ as being from the masquerade host. Normally they get
+ the local hostname. Although this may be right for
+ ordinary users, it can break local aliases. For example,
+ if you send to "localalias", the originating sendmail will
+ find that alias and send to all members, but send the
+ message with "To: localalias@masqueradehost". Since that
+ alias likely does not exist, replies will fail. Use this
+ feature ONLY if you can guarantee that the ENTIRE
+ namespace on your masquerade host supersets all the
+ local entries.
+
+limited_masquerade
+ Normally, any hosts listed in class {w} are masqueraded. If
+ this feature is given, only the hosts listed in class {M} (see
+ below: MASQUERADE_DOMAIN) are masqueraded. This is useful
+ if you have several domains with disjoint namespaces hosted
+ on the same machine.
+
+masquerade_entire_domain
+ If masquerading is enabled (using MASQUERADE_AS) and
+ MASQUERADE_DOMAIN (see below) is set, this feature will
+ cause addresses to be rewritten such that the masquerading
+ domains are actually entire domains to be hidden. All
+ hosts within the masquerading domains will be rewritten
+ to the masquerade name (used in MASQUERADE_AS). For example,
+ if you have:
+
+ MASQUERADE_AS(`masq.com')
+ MASQUERADE_DOMAIN(`foo.org')
+ MASQUERADE_DOMAIN(`bar.com')
+
+ then *foo.org and *bar.com are converted to masq.com. Without
+ this feature, only foo.org and bar.com are masqueraded.
+
+ NOTE: only domains within your jurisdiction and
+ current hierarchy should be masqueraded using this.
+
+local_no_masquerade
+ This feature prevents the local mailer from masquerading even
+ if MASQUERADE_AS is used. MASQUERADE_AS will only have effect
+ on addresses of mail going outside the local domain.
+
+masquerade_envelope
+ If masquerading is enabled (using MASQUERADE_AS) or the
+ genericstable is in use, this feature will cause envelope
+ addresses to also masquerade as being from the masquerade
+ host. Normally only the header addresses are masqueraded.
+
+genericstable This feature will cause unqualified addresses (i.e., without
+ a domain) and addresses with a domain listed in class {G}
+ to be looked up in a map and turned into another ("generic")
+ form, which can change both the domain name and the user name.
+ Notice: if you use an MSP (as it is default starting with
+ 8.12), the MTA will only receive qualified addresses from the
+ MSP (as required by the RFCs). Hence you need to add your
+ domain to class {G}. This feature is similar to the userdb
+ functionality. The same types of addresses as for
+ masquerading are looked up, i.e., only header sender
+ addresses unless the allmasquerade and/or masquerade_envelope
+ features are given. Qualified addresses must have the domain
+ part in class {G}; entries can be added to this class by the
+ macros GENERICS_DOMAIN or GENERICS_DOMAIN_FILE (analogously
+ to MASQUERADE_DOMAIN and MASQUERADE_DOMAIN_FILE, see below).
+
+ The argument of FEATURE(`genericstable') may be the map
+ definition; the default map definition is:
+
+ hash /etc/mail/genericstable
+
+ The key for this table is either the full address, the domain
+ (with a leading @; the localpart is passed as first argument)
+ or the unqualified username (tried in the order mentioned);
+ the value is the new user address. If the new user address
+ does not include a domain, it will be qualified in the standard
+ manner, i.e., using $j or the masquerade name. Note that the
+ address being looked up must be fully qualified. For local
+ mail, it is necessary to use FEATURE(`always_add_domain')
+ for the addresses to be qualified.
+ The "+detail" of an address is passed as %1, so entries like
+
+ old+*@foo.org new+%1@example.com
+ gen+*@foo.org %1@example.com
+
+ and other forms are possible.
+
+generics_entire_domain
+ If the genericstable is enabled and GENERICS_DOMAIN or
+ GENERICS_DOMAIN_FILE is used, this feature will cause
+ addresses to be searched in the map if their domain
+ parts are subdomains of elements in class {G}.
+
+virtusertable A domain-specific form of aliasing, allowing multiple
+ virtual domains to be hosted on one machine. For example,
+ if the virtuser table contained:
+
+ info@foo.com foo-info
+ info@bar.com bar-info
+ joe@bar.com error:nouser 550 No such user here
+ jax@bar.com error:5.7.0:550 Address invalid
+ @baz.org jane@example.net
+
+ then mail addressed to info@foo.com will be sent to the
+ address foo-info, mail addressed to info@bar.com will be
+ delivered to bar-info, and mail addressed to anyone at baz.org
+ will be sent to jane@example.net, mail to joe@bar.com will
+ be rejected with the specified error message, and mail to
+ jax@bar.com will also have a RFC 1893 compliant error code
+ 5.7.0.
+
+ The username from the original address is passed
+ as %1 allowing:
+
+ @foo.org %1@example.com
+
+ meaning someone@foo.org will be sent to someone@example.com.
+ Additionally, if the local part consists of "user+detail"
+ then "detail" is passed as %2 and "+detail" is passed as %3
+ when a match against user+* is attempted, so entries like
+
+ old+*@foo.org new+%2@example.com
+ gen+*@foo.org %2@example.com
+ +*@foo.org %1%3@example.com
+ X++@foo.org Z%3@example.com
+ @bar.org %1%3
+
+ and other forms are possible. Note: to preserve "+detail"
+ for a default case (@domain) %1%3 must be used as RHS.
+ There are two wildcards after "+": "+" matches only a non-empty
+ detail, "*" matches also empty details, e.g., user+@foo.org
+ matches +*@foo.org but not ++@foo.org. This can be used
+ to ensure that the parameters %2 and %3 are not empty.
+
+ All the host names on the left hand side (foo.com, bar.com,
+ and baz.org) must be in class {w} or class {VirtHost}. The
+ latter can be defined by the macros VIRTUSER_DOMAIN or
+ VIRTUSER_DOMAIN_FILE (analogously to MASQUERADE_DOMAIN and
+ MASQUERADE_DOMAIN_FILE, see below). If VIRTUSER_DOMAIN or
+ VIRTUSER_DOMAIN_FILE is used, then the entries of class
+ {VirtHost} are added to class {R}, i.e., relaying is allowed
+ to (and from) those domains. The default map definition is:
+
+ hash /etc/mail/virtusertable
+
+ A new definition can be specified as the second argument of
+ the FEATURE macro, such as
+
+ FEATURE(`virtusertable', `dbm /etc/mail/virtusers')
+
+virtuser_entire_domain
+ If the virtusertable is enabled and VIRTUSER_DOMAIN or
+ VIRTUSER_DOMAIN_FILE is used, this feature will cause
+ addresses to be searched in the map if their domain
+ parts are subdomains of elements in class {VirtHost}.
+
+ldap_routing Implement LDAP-based e-mail recipient routing according to
+ the Internet Draft draft-lachman-laser-ldap-mail-routing-01.
+ This provides a method to re-route addresses with a
+ domain portion in class {LDAPRoute} to either a
+ different mail host or a different address. Hosts can
+ be added to this class using LDAPROUTE_DOMAIN and
+ LDAPROUTE_DOMAIN_FILE (analogously to MASQUERADE_DOMAIN and
+ MASQUERADE_DOMAIN_FILE, see below).
+
+ See the LDAP ROUTING section below for more information.
+
+nodns If you aren't running DNS at your site (for example,
+ you are UUCP-only connected). It's hard to consider
+ this a "feature", but hey, it had to go somewhere.
+ Actually, as of 8.7 this is a no-op -- remove "dns" from
+ the hosts service switch entry instead.
+
+nullclient This is a special case -- it creates a configuration file
+ containing nothing but support for forwarding all mail to a
+ central hub via a local SMTP-based network. The argument
+ is the name of that hub.
+
+ The only other feature that should be used in conjunction
+ with this one is FEATURE(`nocanonify'). No mailers
+ should be defined. No aliasing or forwarding is done.
+
+local_lmtp Use an LMTP capable local mailer. The argument to this
+ feature is the pathname of an LMTP capable mailer. By
+ default, mail.local is used. This is expected to be the
+ mail.local which came with the 8.9 distribution which is
+ LMTP capable. The path to mail.local is set by the
+ confEBINDIR m4 variable -- making the default
+ LOCAL_MAILER_PATH /usr/libexec/mail.local.
+ If a different LMTP capable mailer is used, its pathname
+ can be specified as second parameter and the arguments
+ passed to it (A=) as third parameter, e.g.,
+
+ FEATURE(`local_lmtp', `/usr/local/bin/lmtp', `lmtp')
+
+ WARNING: This feature sets LOCAL_MAILER_FLAGS unconditionally,
+ i.e., without respecting any definitions in an OSTYPE setting.
+
+local_procmail Use procmail or another delivery agent as the local mailer.
+ The argument to this feature is the pathname of the
+ delivery agent, which defaults to PROCMAIL_MAILER_PATH.
+ Note that this does NOT use PROCMAIL_MAILER_FLAGS or
+ PROCMAIL_MAILER_ARGS for the local mailer; tweak
+ LOCAL_MAILER_FLAGS and LOCAL_MAILER_ARGS instead, or
+ specify the appropriate parameters. When procmail is used,
+ the local mailer can make use of the
+ "user+indicator@local.host" syntax; normally the +indicator
+ is just tossed, but by default it is passed as the -a
+ argument to procmail.
+
+ This feature can take up to three arguments:
+
+ 1. Path to the mailer program
+ [default: /usr/local/bin/procmail]
+ 2. Argument vector including name of the program
+ [default: procmail -Y -a $h -d $u]
+ 3. Flags for the mailer [default: SPfhn9]
+
+ Empty arguments cause the defaults to be taken.
+ Note that if you are on a system with a broken
+ setreuid() call, you may need to add -f $f to the procmail
+ argument vector to pass the proper sender to procmail.
+
+ For example, this allows it to use the maildrop
+ (http://www.flounder.net/~mrsam/maildrop/) mailer instead
+ by specifying:
+
+ FEATURE(`local_procmail', `/usr/local/bin/maildrop',
+ `maildrop -d $u')
+
+ or scanmails using:
+
+ FEATURE(`local_procmail', `/usr/local/bin/scanmails')
+
+ WARNING: This feature sets LOCAL_MAILER_FLAGS unconditionally,
+ i.e., without respecting any definitions in an OSTYPE setting.
+
+bestmx_is_local Accept mail as though locally addressed for any host that
+ lists us as the best possible MX record. This generates
+ additional DNS traffic, but should be OK for low to
+ medium traffic hosts. The argument may be a set of
+ domains, which will limit the feature to only apply to
+ these domains -- this will reduce unnecessary DNS
+ traffic. THIS FEATURE IS FUNDAMENTALLY INCOMPATIBLE WITH
+ WILDCARD MX RECORDS!!! If you have a wildcard MX record
+ that matches your domain, you cannot use this feature.
+
+smrsh Use the SendMail Restricted SHell (smrsh) provided
+ with the distribution instead of /bin/sh for mailing
+ to programs. This improves the ability of the local
+ system administrator to control what gets run via
+ e-mail. If an argument is provided it is used as the
+ pathname to smrsh; otherwise, the path defined by
+ confEBINDIR is used for the smrsh binary -- by default,
+ /usr/libexec/smrsh is assumed.
+
+promiscuous_relay
+ By default, the sendmail configuration files do not permit
+ mail relaying (that is, accepting mail from outside your
+ local host (class {w}) and sending it to another host than
+ your local host). This option sets your site to allow
+ mail relaying from any site to any site. In almost all
+ cases, it is better to control relaying more carefully
+ with the access map, class {R}, or authentication. Domains
+ can be added to class {R} by the macros RELAY_DOMAIN or
+ RELAY_DOMAIN_FILE (analogously to MASQUERADE_DOMAIN and
+ MASQUERADE_DOMAIN_FILE, see below).
+
+relay_entire_domain
+ This option allows any host in your domain as defined by
+ class {m} to use your server for relaying. Notice: make
+ sure that your domain is not just a top level domain,
+ e.g., com. This can happen if you give your host a name
+ like example.com instead of host.example.com.
+
+relay_hosts_only
+ By default, names that are listed as RELAY in the access
+ db and class {R} are treated as domain names, not host names.
+ For example, if you specify ``foo.com'', then mail to or
+ from foo.com, abc.foo.com, or a.very.deep.domain.foo.com
+ will all be accepted for relaying. This feature changes
+ the behaviour to lookup individual host names only.
+
+relay_based_on_MX
+ Turns on the ability to allow relaying based on the MX
+ records of the host portion of an incoming recipient; that
+ is, if an MX record for host foo.com points to your site,
+ you will accept and relay mail addressed to foo.com. See
+ description below for more information before using this
+ feature. Also, see the KNOWNBUGS entry regarding bestmx
+ map lookups.
+
+ FEATURE(`relay_based_on_MX') does not necessarily allow
+ routing of these messages which you expect to be allowed,
+ if route address syntax (or %-hack syntax) is used. If
+ this is a problem, add entries to the access-table or use
+ FEATURE(`loose_relay_check').
+
+relay_mail_from
+ Allows relaying if the mail sender is listed as RELAY in
+ the access map. If an optional argument `domain' (this
+ is the literal word `domain', not a placeholder) is given,
+ relaying can be allowed just based on the domain portion
+ of the sender address. This feature should only be used if
+ absolutely necessary as the sender address can be easily
+ forged. Use of this feature requires the "From:" tag to
+ be used for the key in the access map; see the discussion
+ of tags and FEATURE(`relay_mail_from') in the section on
+ anti-spam configuration control.
+
+relay_local_from
+ Allows relaying if the domain portion of the mail sender
+ is a local host. This should only be used if absolutely
+ necessary as it opens a window for spammers. Specifically,
+ they can send mail to your mail server that claims to be
+ from your domain (either directly or via a routed address),
+ and you will go ahead and relay it out to arbitrary hosts
+ on the Internet.
+
+accept_unqualified_senders
+ Normally, MAIL FROM: commands in the SMTP session will be
+ refused if the connection is a network connection and the
+ sender address does not include a domain name. If your
+ setup sends local mail unqualified (i.e., MAIL FROM: <joe>),
+ you will need to use this feature to accept unqualified
+ sender addresses. Setting the DaemonPortOptions modifier
+ 'u' overrides the default behavior, i.e., unqualified
+ addresses are accepted even without this FEATURE.
+ If this FEATURE is not used, the DaemonPortOptions modifier
+ 'f' can be used to enforce fully qualified addresses.
+
+accept_unresolvable_domains
+ Normally, MAIL FROM: commands in the SMTP session will be
+ refused if the host part of the argument to MAIL FROM:
+ cannot be located in the host name service (e.g., an A or
+ MX record in DNS). If you are inside a firewall that has
+ only a limited view of the Internet host name space, this
+ could cause problems. In this case you probably want to
+ use this feature to accept all domains on input, even if
+ they are unresolvable.
+
+access_db Turns on the access database feature. The access db gives
+ you the ability to allow or refuse to accept mail from
+ specified domains for administrative reasons. Moreover,
+ it can control the behavior of sendmail in various situations.
+ By default, the access database specification is:
+
+ hash -T<TMPF> /etc/mail/access
+
+ See the anti-spam configuration control section for further
+ important information about this feature. Notice:
+ "-T<TMPF>" is meant literal, do not replace it by anything.
+
+blacklist_recipients
+ Turns on the ability to block incoming mail for certain
+ recipient usernames, hostnames, or addresses. For
+ example, you can block incoming mail to user nobody,
+ host foo.mydomain.com, or guest@bar.mydomain.com.
+ These specifications are put in the access db as
+ described in the anti-spam configuration control section
+ later in this document.
+
+delay_checks The rulesets check_mail and check_relay will not be called
+ when a client connects or issues a MAIL command, respectively.
+ Instead, those rulesets will be called by the check_rcpt
+ ruleset; they will be skipped under certain circumstances.
+ See "Delay all checks" in the anti-spam configuration control
+ section. Note: this feature is incompatible to the versions
+ in 8.10 and 8.11.
+
+use_client_ptr If this feature is enabled then check_relay will override
+ its first argument with $&{client_ptr}. This is useful for
+ rejections based on the unverified hostname of client,
+ which turns on the same behavior as in earlier sendmail
+ versions when delay_checks was not in use. See doc/op/op.*
+ about check_relay, {client_name}, and {client_ptr}.
+
+dnsbl Turns on rejection of hosts found in an DNS based rejection
+ list. If an argument is provided it is used as the domain
+ in which blocked hosts are listed; otherwise it defaults to
+ blackholes.mail-abuse.org. An explanation for an DNS based
+ rejection list can be found at http://mail-abuse.org/rbl/.
+ A second argument can be used to change the default error
+ message. Without that second argument, the error message
+ will be
+ Rejected: IP-ADDRESS listed at SERVER
+ where IP-ADDRESS and SERVER are replaced by the appropriate
+ information. By default, temporary lookup failures are
+ ignored. This behavior can be changed by specifying a
+ third argument, which must be either `t' or a full error
+ message. See the anti-spam configuration control section for
+ an example. The dnsbl feature can be included several times
+ to query different DNS based rejection lists. See also
+ enhdnsbl for an enhanced version.
+
+ Set the DNSBL_MAP mc option to change the default map
+ definition from `host'. Set the DNSBL_MAP_OPT mc option
+ to add additional options to the map specification used.
+
+ Some DNS based rejection lists cause failures if asked
+ for AAAA records. If your sendmail version is compiled
+ with IPv6 support (NETINET6) and you experience this
+ problem, add
+
+ define(`DNSBL_MAP', `dns -R A')
+
+ before the first use of this feature. Alternatively you
+ can use enhdnsbl instead (see below). Moreover, this
+ statement can be used to reduce the number of DNS retries,
+ e.g.,
+
+ define(`DNSBL_MAP', `dns -R A -r2')
+
+ See below (EDNSBL_TO) for an explanation.
+
+ NOTE: The default DNS blacklist, blackholes.mail-abuse.org,
+ is a service offered by the Mail Abuse Prevention System
+ (MAPS). As of July 31, 2001, MAPS is a subscription
+ service, so using that network address won't work if you
+ haven't subscribed. Contact MAPS to subscribe
+ (http://mail-abuse.org/).
+
+enhdnsbl Enhanced version of dnsbl (see above). Further arguments
+ (up to 5) can be used to specify specific return values
+ from lookups. Temporary lookup failures are ignored unless
+ a third argument is given, which must be either `t' or a full
+ error message. By default, any successful lookup will
+ generate an error. Otherwise the result of the lookup is
+ compared with the supplied argument(s), and only if a match
+ occurs an error is generated. For example,
+
+ FEATURE(`enhdnsbl', `dnsbl.example.com', `', `t', `127.0.0.2.')
+
+ will reject the e-mail if the lookup returns the value
+ ``127.0.0.2.'', or generate a 451 response if the lookup
+ temporarily failed. The arguments can contain metasymbols
+ as they are allowed in the LHS of rules. As the example
+ shows, the default values are also used if an empty argument,
+ i.e., `', is specified. This feature requires that sendmail
+ has been compiled with the flag DNSMAP (see sendmail/README).
+
+ Set the EDNSBL_TO mc option to change the DNS retry count
+ from the default value of 5, this can be very useful when
+ a DNS server is not responding, which in turn may cause
+ clients to time out (an entry stating
+
+ did not issue MAIL/EXPN/VRFY/ETRN
+
+ will be logged).
+
+ratecontrol Enable simple ruleset to do connection rate control
+ checking. This requires entries in access_db of the form
+
+ ClientRate:IP.ADD.RE.SS LIMIT
+
+ The RHS specifies the maximum number of connections
+ (an integer number) over the time interval defined
+ by ConnectionRateWindowSize, where 0 means unlimited.
+
+ Take the following example:
+
+ ClientRate:10.1.2.3 4
+ ClientRate:127.0.0.1 0
+ ClientRate: 10
+
+ 10.1.2.3 can only make up to 4 connections, the
+ general limit it 10, and 127.0.0.1 can make an unlimited
+ number of connections per ConnectionRateWindowSize.
+
+ See also CONNECTION CONTROL.
+
+conncontrol Enable a simple check of the number of incoming SMTP
+ connections. This requires entries in access_db of the
+ form
+
+ ClientConn:IP.ADD.RE.SS LIMIT
+
+ The RHS specifies the maximum number of open connections
+ (an integer number).
+
+ Take the following example:
+
+ ClientConn:10.1.2.3 4
+ ClientConn:127.0.0.1 0
+ ClientConn: 10
+
+ 10.1.2.3 can only have up to 4 open connections, the
+ general limit it 10, and 127.0.0.1 does not have any
+ explicit limit.
+
+ See also CONNECTION CONTROL.
+
+mtamark Experimental support for "Marking Mail Transfer Agents in
+ Reverse DNS with TXT RRs" (MTAMark), see
+ draft-stumpf-dns-mtamark-01. Optional arguments are:
+
+ 1. Error message, default:
+
+ 550 Rejected: $&{client_addr} not listed as MTA
+
+ 2. Temporary lookup failures are ignored unless a second
+ argument is given, which must be either `t' or a full
+ error message.
+
+ 3. Lookup prefix, default: _perm._smtp._srv. This should
+ not be changed unless the draft changes it.
+
+ Example:
+
+ FEATURE(`mtamark', `', `t')
+
+lookupdotdomain Look up also .domain in the access map. This allows to
+ match only subdomains. It does not work well with
+ FEATURE(`relay_hosts_only'), because most lookups for
+ subdomains are suppressed by the latter feature.
+
+loose_relay_check
+ Normally, if % addressing is used for a recipient, e.g.
+ user%site@othersite, and othersite is in class {R}, the
+ check_rcpt ruleset will strip @othersite and recheck
+ user@site for relaying. This feature changes that
+ behavior. It should not be needed for most installations.
+
+preserve_luser_host
+ Preserve the name of the recipient host if LUSER_RELAY is
+ used. Without this option, the domain part of the
+ recipient address will be replaced by the host specified as
+ LUSER_RELAY. This feature only works if the hostname is
+ passed to the mailer (see mailer triple in op.me). Note
+ that in the default configuration the local mailer does not
+ receive the hostname, i.e., the mailer triple has an empty
+ hostname.
+
+preserve_local_plus_detail
+ Preserve the +detail portion of the address when passing
+ address to local delivery agent. Disables alias and
+ .forward +detail stripping (e.g., given user+detail, only
+ that address will be looked up in the alias file; user+* and
+ user will not be looked up). Only use if the local
+ delivery agent in use supports +detail addressing.
+
+compat_check Enable ruleset check_compat to look up pairs of addresses
+ with the Compat: tag -- Compat:sender<@>recipient -- in the
+ access map. Valid values for the RHS include
+ DISCARD silently discard recipient
+ TEMP: return a temporary error
+ ERROR: return a permanent error
+ In the last two cases, a 4xy/5xy SMTP reply code should
+ follow the colon.
+
+no_default_msa Don't generate the default MSA daemon, i.e.,
+ DAEMON_OPTIONS(`Port=587,Name=MSA,M=E')
+ To define a MSA daemon with other parameters, use this
+ FEATURE and introduce new settings via DAEMON_OPTIONS().
+
+msp Defines config file for Message Submission Program.
+ See cf/submit.mc for how
+ to use it. An optional argument can be used to override
+ the default of `[localhost]' to use as host to send all
+ e-mails to. Note that MX records will be used if the
+ specified hostname is not in square brackets (e.g.,
+ [hostname]). If `MSA' is specified as second argument then
+ port 587 is used to contact the server. Example:
+
+ FEATURE(`msp', `', `MSA')
+
+ Some more hints about possible changes can be found below
+ in the section MESSAGE SUBMISSION PROGRAM.
+
+ Note: Due to many problems, submit.mc uses
+
+ FEATURE(`msp', `[127.0.0.1]')
+
+ by default. If you have a machine with IPv6 only,
+ change it to
+
+ FEATURE(`msp', `[IPv6:::1]')
+
+ If you want to continue using '[localhost]', (the behavior
+ up to 8.12.6), use
+
+ FEATURE(`msp')
+
+queuegroup A simple example how to select a queue group based
+ on the full e-mail address or the domain of the
+ recipient. Selection is done via entries in the
+ access map using the tag QGRP:, for example:
+
+ QGRP:example.com main
+ QGRP:friend@some.org others
+ QGRP:my.domain local
+
+ where "main", "others", and "local" are names of
+ queue groups. If an argument is specified, it is used
+ as default queue group.
+
+ Note: please read the warning in doc/op/op.me about
+ queue groups and possible queue manipulations.
+
+greet_pause Adds the greet_pause ruleset which enables open proxy
+ and SMTP slamming protection. The feature can take an
+ argument specifying the milliseconds to wait:
+
+ FEATURE(`greet_pause', `5000') dnl 5 seconds
+
+ If FEATURE(`access_db') is enabled, an access database
+ lookup with the GreetPause tag is done using client
+ hostname, domain, IP address, or subnet to determine the
+ pause time:
+
+ GreetPause:my.domain 0
+ GreetPause:example.com 5000
+ GreetPause:10.1.2 2000
+ GreetPause:127.0.0.1 0
+
+ When using FEATURE(`access_db'), the optional
+ FEATURE(`greet_pause') argument becomes the default if
+ nothing is found in the access database. A ruleset called
+ Local_greet_pause can be used for local modifications, e.g.,
+
+ LOCAL_RULESETS
+ SLocal_greet_pause
+ R$* $: $&{daemon_flags}
+ R$* a $* $# 0
+
++--------------------+
+| USING UUCP MAILERS |
++--------------------+
+
+It's hard to get UUCP mailers right because of the extremely ad hoc
+nature of UUCP addressing. These config files are really designed
+for domain-based addressing, even for UUCP sites.
+
+There are four UUCP mailers available. The choice of which one to
+use is partly a matter of local preferences and what is running at
+the other end of your UUCP connection. Unlike good protocols that
+define what will go over the wire, UUCP uses the policy that you
+should do what is right for the other end; if they change, you have
+to change. This makes it hard to do the right thing, and discourages
+people from updating their software. In general, if you can avoid
+UUCP, please do.
+
+The major choice is whether to go for a domainized scheme or a
+non-domainized scheme. This depends entirely on what the other
+end will recognize. If at all possible, you should encourage the
+other end to go to a domain-based system -- non-domainized addresses
+don't work entirely properly.
+
+The four mailers are:
+
+ uucp-old (obsolete name: "uucp")
+ This is the oldest, the worst (but the closest to UUCP) way of
+ sending messages across UUCP connections. It does bangify
+ everything and prepends $U (your UUCP name) to the sender's
+ address (which can already be a bang path itself). It can
+ only send to one address at a time, so it spends a lot of
+ time copying duplicates of messages. Avoid this if at all
+ possible.
+
+ uucp-new (obsolete name: "suucp")
+ The same as above, except that it assumes that in one rmail
+ command you can specify several recipients. It still has a
+ lot of other problems.
+
+ uucp-dom
+ This UUCP mailer keeps everything as domain addresses.
+ Basically, it uses the SMTP mailer rewriting rules. This mailer
+ is only included if MAILER(`smtp') is specified before
+ MAILER(`uucp').
+
+ Unfortunately, a lot of UUCP mailer transport agents require
+ bangified addresses in the envelope, although you can use
+ domain-based addresses in the message header. (The envelope
+ shows up as the From_ line on UNIX mail.) So....
+
+ uucp-uudom
+ This is a cross between uucp-new (for the envelope addresses)
+ and uucp-dom (for the header addresses). It bangifies the
+ envelope sender (From_ line in messages) without adding the
+ local hostname, unless there is no host name on the address
+ at all (e.g., "wolf") or the host component is a UUCP host name
+ instead of a domain name ("somehost!wolf" instead of
+ "some.dom.ain!wolf"). This is also included only if MAILER(`smtp')
+ is also specified earlier.
+
+Examples:
+
+On host grasp.insa-lyon.fr (UUCP host name "grasp"), the following
+summarizes the sender rewriting for various mailers.
+
+Mailer sender rewriting in the envelope
+------ ------ -------------------------
+uucp-{old,new} wolf grasp!wolf
+uucp-dom wolf wolf@grasp.insa-lyon.fr
+uucp-uudom wolf grasp.insa-lyon.fr!wolf
+
+uucp-{old,new} wolf@fr.net grasp!fr.net!wolf
+uucp-dom wolf@fr.net wolf@fr.net
+uucp-uudom wolf@fr.net fr.net!wolf
+
+uucp-{old,new} somehost!wolf grasp!somehost!wolf
+uucp-dom somehost!wolf somehost!wolf@grasp.insa-lyon.fr
+uucp-uudom somehost!wolf grasp.insa-lyon.fr!somehost!wolf
+
+If you are using one of the domainized UUCP mailers, you really want
+to convert all UUCP addresses to domain format -- otherwise, it will
+do it for you (and probably not the way you expected). For example,
+if you have the address foo!bar!baz (and you are not sending to foo),
+the heuristics will add the @uucp.relay.name or @local.host.name to
+this address. However, if you map foo to foo.host.name first, it
+will not add the local hostname. You can do this using the uucpdomain
+feature.
+
+
++-------------------+
+| TWEAKING RULESETS |
++-------------------+
+
+For more complex configurations, you can define special rules.
+The macro LOCAL_RULE_3 introduces rules that are used in canonicalizing
+the names. Any modifications made here are reflected in the header.
+
+A common use is to convert old UUCP addresses to SMTP addresses using
+the UUCPSMTP macro. For example:
+
+ LOCAL_RULE_3
+ UUCPSMTP(`decvax', `decvax.dec.com')
+ UUCPSMTP(`research', `research.att.com')
+
+will cause addresses of the form "decvax!user" and "research!user"
+to be converted to "user@decvax.dec.com" and "user@research.att.com"
+respectively.
+
+This could also be used to look up hosts in a database map:
+
+ LOCAL_RULE_3
+ R$* < @ $+ > $* $: $1 < @ $(hostmap $2 $) > $3
+
+This map would be defined in the LOCAL_CONFIG portion, as shown below.
+
+Similarly, LOCAL_RULE_0 can be used to introduce new parsing rules.
+For example, new rules are needed to parse hostnames that you accept
+via MX records. For example, you might have:
+
+ LOCAL_RULE_0
+ R$+ <@ host.dom.ain.> $#uucp $@ cnmat $: $1 < @ host.dom.ain.>
+
+You would use this if you had installed an MX record for cnmat.Berkeley.EDU
+pointing at this host; this rule catches the message and forwards it on
+using UUCP.
+
+You can also tweak rulesets 1 and 2 using LOCAL_RULE_1 and LOCAL_RULE_2.
+These rulesets are normally empty.
+
+A similar macro is LOCAL_CONFIG. This introduces lines added after the
+boilerplate option setting but before rulesets. Do not declare rulesets in
+the LOCAL_CONFIG section. It can be used to declare local database maps or
+whatever. For example:
+
+ LOCAL_CONFIG
+ Khostmap hash /etc/mail/hostmap
+ Kyplocal nis -m hosts.byname
+
+
++---------------------------+
+| MASQUERADING AND RELAYING |
++---------------------------+
+
+You can have your host masquerade as another using
+
+ MASQUERADE_AS(`host.domain')
+
+This causes mail being sent to be labeled as coming from the
+indicated host.domain, rather than $j. One normally masquerades as
+one of one's own subdomains (for example, it's unlikely that
+Berkeley would choose to masquerade as an MIT site). This
+behaviour is modified by a plethora of FEATUREs; in particular, see
+masquerade_envelope, allmasquerade, limited_masquerade, and
+masquerade_entire_domain.
+
+The masquerade name is not normally canonified, so it is important
+that it be your One True Name, that is, fully qualified and not a
+CNAME. However, if you use a CNAME, the receiving side may canonify
+it for you, so don't think you can cheat CNAME mapping this way.
+
+Normally the only addresses that are masqueraded are those that come
+from this host (that is, are either unqualified or in class {w}, the list
+of local domain names). You can augment this list, which is realized
+by class {M} using
+
+ MASQUERADE_DOMAIN(`otherhost.domain')
+
+The effect of this is that although mail to user@otherhost.domain
+will not be delivered locally, any mail including any user@otherhost.domain
+will, when relayed, be rewritten to have the MASQUERADE_AS address.
+This can be a space-separated list of names.
+
+If these names are in a file, you can use
+
+ MASQUERADE_DOMAIN_FILE(`filename')
+
+to read the list of names from the indicated file (i.e., to add
+elements to class {M}).
+
+To exempt hosts or subdomains from being masqueraded, you can use
+
+ MASQUERADE_EXCEPTION(`host.domain')
+
+This can come handy if you want to masquerade a whole domain
+except for one (or a few) host(s). If these names are in a file,
+you can use
+
+ MASQUERADE_EXCEPTION_FILE(`filename')
+
+Normally only header addresses are masqueraded. If you want to
+masquerade the envelope as well, use
+
+ FEATURE(`masquerade_envelope')
+
+There are always users that need to be "exposed" -- that is, their
+internal site name should be displayed instead of the masquerade name.
+Root is an example (which has been "exposed" by default prior to 8.10).
+You can add users to this list using
+
+ EXPOSED_USER(`usernames')
+
+This adds users to class {E}; you could also use
+
+ EXPOSED_USER_FILE(`filename')
+
+You can also arrange to relay all unqualified names (that is, names
+without @host) to a relay host. For example, if you have a central
+email server, you might relay to that host so that users don't have
+to have .forward files or aliases. You can do this using
+
+ define(`LOCAL_RELAY', `mailer:hostname')
+
+The ``mailer:'' can be omitted, in which case the mailer defaults to
+"relay". There are some user names that you don't want relayed, perhaps
+because of local aliases. A common example is root, which may be
+locally aliased. You can add entries to this list using
+
+ LOCAL_USER(`usernames')
+
+This adds users to class {L}; you could also use
+
+ LOCAL_USER_FILE(`filename')
+
+If you want all incoming mail sent to a centralized hub, as for a
+shared /var/spool/mail scheme, use
+
+ define(`MAIL_HUB', `mailer:hostname')
+
+Again, ``mailer:'' defaults to "relay". If you define both LOCAL_RELAY
+and MAIL_HUB _AND_ you have FEATURE(`stickyhost'), unqualified names will
+be sent to the LOCAL_RELAY and other local names will be sent to MAIL_HUB.
+Note: there is a (long standing) bug which keeps this combination from
+working for addresses of the form user+detail.
+Names in class {L} will be delivered locally, so you MUST have aliases or
+.forward files for them.
+
+For example, if you are on machine mastodon.CS.Berkeley.EDU and you have
+FEATURE(`stickyhost'), the following combinations of settings will have the
+indicated effects:
+
+email sent to.... eric eric@mastodon.CS.Berkeley.EDU
+
+LOCAL_RELAY set to mail.CS.Berkeley.EDU (delivered locally)
+mail.CS.Berkeley.EDU (no local aliasing) (aliasing done)
+
+MAIL_HUB set to mammoth.CS.Berkeley.EDU mammoth.CS.Berkeley.EDU
+mammoth.CS.Berkeley.EDU (aliasing done) (aliasing done)
+
+Both LOCAL_RELAY and mail.CS.Berkeley.EDU mammoth.CS.Berkeley.EDU
+MAIL_HUB set as above (no local aliasing) (aliasing done)
+
+If you do not have FEATURE(`stickyhost') set, then LOCAL_RELAY and
+MAIL_HUB act identically, with MAIL_HUB taking precedence.
+
+If you want all outgoing mail to go to a central relay site, define
+SMART_HOST as well. Briefly:
+
+ LOCAL_RELAY applies to unqualified names (e.g., "eric").
+ MAIL_HUB applies to names qualified with the name of the
+ local host (e.g., "eric@mastodon.CS.Berkeley.EDU").
+ SMART_HOST applies to names qualified with other hosts or
+ bracketed addresses (e.g., "eric@mastodon.CS.Berkeley.EDU"
+ or "eric@[127.0.0.1]").
+
+However, beware that other relays (e.g., UUCP_RELAY, BITNET_RELAY,
+DECNET_RELAY, and FAX_RELAY) take precedence over SMART_HOST, so if you
+really want absolutely everything to go to a single central site you will
+need to unset all the other relays -- or better yet, find or build a
+minimal config file that does this.
+
+For duplicate suppression to work properly, the host name is best
+specified with a terminal dot:
+
+ define(`MAIL_HUB', `host.domain.')
+ note the trailing dot ---^
+
+
++-------------------------------------------+
+| USING LDAP FOR ALIASES, MAPS, AND CLASSES |
++-------------------------------------------+
+
+LDAP can be used for aliases, maps, and classes by either specifying your
+own LDAP map specification or using the built-in default LDAP map
+specification. The built-in default specifications all provide lookups
+which match against either the machine's fully qualified hostname (${j}) or
+a "cluster". The cluster allows you to share LDAP entries among a large
+number of machines without having to enter each of the machine names into
+each LDAP entry. To set the LDAP cluster name to use for a particular
+machine or set of machines, set the confLDAP_CLUSTER m4 variable to a
+unique name. For example:
+
+ define(`confLDAP_CLUSTER', `Servers')
+
+Here, the word `Servers' will be the cluster name. As an example, assume
+that smtp.sendmail.org, etrn.sendmail.org, and mx.sendmail.org all belong
+to the Servers cluster.
+
+Some of the LDAP LDIF examples below show use of the Servers cluster.
+Every entry must have either a sendmailMTAHost or sendmailMTACluster
+attribute or it will be ignored. Be careful as mixing clusters and
+individual host records can have surprising results (see the CAUTION
+sections below).
+
+See the file cf/sendmail.schema for the actual LDAP schemas. Note that
+this schema (and therefore the lookups and examples below) is experimental
+at this point as it has had little public review. Therefore, it may change
+in future versions. Feedback via sendmail@sendmail.org is encouraged.
+
+-------
+Aliases
+-------
+
+The ALIAS_FILE (O AliasFile) option can be set to use LDAP for alias
+lookups. To use the default schema, simply use:
+
+ define(`ALIAS_FILE', `ldap:')
+
+By doing so, you will use the default schema which expands to a map
+declared as follows:
+
+ ldap -k (&(objectClass=sendmailMTAAliasObject)
+ (sendmailMTAAliasGrouping=aliases)
+ (|(sendmailMTACluster=${sendmailMTACluster})
+ (sendmailMTAHost=$j))
+ (sendmailMTAKey=%0))
+ -v sendmailMTAAliasValue,sendmailMTAAliasSearch:FILTER:sendmailMTAAliasObject,sendmailMTAAliasURL:URL:sendmailMTAAliasObject
+
+
+NOTE: The macros shown above ${sendmailMTACluster} and $j are not actually
+used when the binary expands the `ldap:' token as the AliasFile option is
+not actually macro-expanded when read from the sendmail.cf file.
+
+Example LDAP LDIF entries might be:
+
+ dn: sendmailMTAKey=sendmail-list, dc=sendmail, dc=org
+ objectClass: sendmailMTA
+ objectClass: sendmailMTAAlias
+ objectClass: sendmailMTAAliasObject
+ sendmailMTAAliasGrouping: aliases
+ sendmailMTAHost: etrn.sendmail.org
+ sendmailMTAKey: sendmail-list
+ sendmailMTAAliasValue: ca@example.org
+ sendmailMTAAliasValue: eric
+ sendmailMTAAliasValue: gshapiro@example.com
+
+ dn: sendmailMTAKey=owner-sendmail-list, dc=sendmail, dc=org
+ objectClass: sendmailMTA
+ objectClass: sendmailMTAAlias
+ objectClass: sendmailMTAAliasObject
+ sendmailMTAAliasGrouping: aliases
+ sendmailMTAHost: etrn.sendmail.org
+ sendmailMTAKey: owner-sendmail-list
+ sendmailMTAAliasValue: eric
+
+ dn: sendmailMTAKey=postmaster, dc=sendmail, dc=org
+ objectClass: sendmailMTA
+ objectClass: sendmailMTAAlias
+ objectClass: sendmailMTAAliasObject
+ sendmailMTAAliasGrouping: aliases
+ sendmailMTACluster: Servers
+ sendmailMTAKey: postmaster
+ sendmailMTAAliasValue: eric
+
+Here, the aliases sendmail-list and owner-sendmail-list will be available
+only on etrn.sendmail.org but the postmaster alias will be available on
+every machine in the Servers cluster (including etrn.sendmail.org).
+
+CAUTION: aliases are additive so that entries like these:
+
+ dn: sendmailMTAKey=bob, dc=sendmail, dc=org
+ objectClass: sendmailMTA
+ objectClass: sendmailMTAAlias
+ objectClass: sendmailMTAAliasObject
+ sendmailMTAAliasGrouping: aliases
+ sendmailMTACluster: Servers
+ sendmailMTAKey: bob
+ sendmailMTAAliasValue: eric
+
+ dn: sendmailMTAKey=bobetrn, dc=sendmail, dc=org
+ objectClass: sendmailMTA
+ objectClass: sendmailMTAAlias
+ objectClass: sendmailMTAAliasObject
+ sendmailMTAAliasGrouping: aliases
+ sendmailMTAHost: etrn.sendmail.org
+ sendmailMTAKey: bob
+ sendmailMTAAliasValue: gshapiro
+
+would mean that on all of the hosts in the cluster, mail to bob would go to
+eric EXCEPT on etrn.sendmail.org in which case it would go to BOTH eric and
+gshapiro.
+
+If you prefer not to use the default LDAP schema for your aliases, you can
+specify the map parameters when setting ALIAS_FILE. For example:
+
+ define(`ALIAS_FILE', `ldap:-k (&(objectClass=mailGroup)(mail=%0)) -v mgrpRFC822MailMember')
+
+----
+Maps
+----
+
+FEATURE()'s which take an optional map definition argument (e.g., access,
+mailertable, virtusertable, etc.) can instead take the special keyword
+`LDAP', e.g.:
+
+ FEATURE(`access_db', `LDAP')
+ FEATURE(`virtusertable', `LDAP')
+
+When this keyword is given, that map will use LDAP lookups consisting of
+the objectClass sendmailMTAClassObject, the attribute sendmailMTAMapName
+with the map name, a search attribute of sendmailMTAKey, and the value
+attribute sendmailMTAMapValue.
+
+The values for sendmailMTAMapName are:
+
+ FEATURE() sendmailMTAMapName
+ --------- ------------------
+ access_db access
+ authinfo authinfo
+ bitdomain bitdomain
+ domaintable domain
+ genericstable generics
+ mailertable mailer
+ uucpdomain uucpdomain
+ virtusertable virtuser
+
+For example, FEATURE(`mailertable', `LDAP') would use the map definition:
+
+ Kmailertable ldap -k (&(objectClass=sendmailMTAMapObject)
+ (sendmailMTAMapName=mailer)
+ (|(sendmailMTACluster=${sendmailMTACluster})
+ (sendmailMTAHost=$j))
+ (sendmailMTAKey=%0))
+ -1 -v sendmailMTAMapValue,sendmailMTAMapSearch:FILTER:sendmailMTAMapObject,sendmailMTAMapURL:URL:sendmailMTAMapObject
+
+An example LDAP LDIF entry using this map might be:
+
+ dn: sendmailMTAMapName=mailer, dc=sendmail, dc=org
+ objectClass: sendmailMTA
+ objectClass: sendmailMTAMap
+ sendmailMTACluster: Servers
+ sendmailMTAMapName: mailer
+
+ dn: sendmailMTAKey=example.com, sendmailMTAMapName=mailer, dc=sendmail, dc=org
+ objectClass: sendmailMTA
+ objectClass: sendmailMTAMap
+ objectClass: sendmailMTAMapObject
+ sendmailMTAMapName: mailer
+ sendmailMTACluster: Servers
+ sendmailMTAKey: example.com
+ sendmailMTAMapValue: relay:[smtp.example.com]
+
+CAUTION: If your LDAP database contains the record above and *ALSO* a host
+specific record such as:
+
+ dn: sendmailMTAKey=example.com@etrn, sendmailMTAMapName=mailer, dc=sendmail, dc=org
+ objectClass: sendmailMTA
+ objectClass: sendmailMTAMap
+ objectClass: sendmailMTAMapObject
+ sendmailMTAMapName: mailer
+ sendmailMTAHost: etrn.sendmail.org
+ sendmailMTAKey: example.com
+ sendmailMTAMapValue: relay:[mx.example.com]
+
+then these entries will give unexpected results. When the lookup is done
+on etrn.sendmail.org, the effect is that there is *NO* match at all as maps
+require a single match. Since the host etrn.sendmail.org is also in the
+Servers cluster, LDAP would return two answers for the example.com map key
+in which case sendmail would treat this as no match at all.
+
+If you prefer not to use the default LDAP schema for your maps, you can
+specify the map parameters when using the FEATURE(). For example:
+
+ FEATURE(`access_db', `ldap:-1 -k (&(objectClass=mapDatabase)(key=%0)) -v value')
+
+-------
+Classes
+-------
+
+Normally, classes can be filled via files or programs. As of 8.12, they
+can also be filled via map lookups using a new syntax:
+
+ F{ClassName}mapkey@mapclass:mapspec
+
+mapkey is optional and if not provided the map key will be empty. This can
+be used with LDAP to read classes from LDAP. Note that the lookup is only
+done when sendmail is initially started. Use the special value `@LDAP' to
+use the default LDAP schema. For example:
+
+ RELAY_DOMAIN_FILE(`@LDAP')
+
+would put all of the attribute sendmailMTAClassValue values of LDAP records
+with objectClass sendmailMTAClass and an attribute sendmailMTAClassName of
+'R' into class $={R}. In other words, it is equivalent to the LDAP map
+specification:
+
+ F{R}@ldap:-k (&(objectClass=sendmailMTAClass)
+ (sendmailMTAClassName=R)
+ (|(sendmailMTACluster=${sendmailMTACluster})
+ (sendmailMTAHost=$j)))
+ -v sendmailMTAClassValue,sendmailMTAClassSearch:FILTER:sendmailMTAClass,sendmailMTAClassURL:URL:sendmailMTAClass
+
+NOTE: The macros shown above ${sendmailMTACluster} and $j are not actually
+used when the binary expands the `@LDAP' token as class declarations are
+not actually macro-expanded when read from the sendmail.cf file.
+
+This can be used with class related commands such as RELAY_DOMAIN_FILE(),
+MASQUERADE_DOMAIN_FILE(), etc:
+
+ Command sendmailMTAClassName
+ ------- --------------------
+ CANONIFY_DOMAIN_FILE() Canonify
+ EXPOSED_USER_FILE() E
+ GENERICS_DOMAIN_FILE() G
+ LDAPROUTE_DOMAIN_FILE() LDAPRoute
+ LDAPROUTE_EQUIVALENT_FILE() LDAPRouteEquiv
+ LOCAL_USER_FILE() L
+ MASQUERADE_DOMAIN_FILE() M
+ MASQUERADE_EXCEPTION_FILE() N
+ RELAY_DOMAIN_FILE() R
+ VIRTUSER_DOMAIN_FILE() VirtHost
+
+You can also add your own as any 'F'ile class of the form:
+
+ F{ClassName}@LDAP
+ ^^^^^^^^^
+will use "ClassName" for the sendmailMTAClassName.
+
+An example LDAP LDIF entry would look like:
+
+ dn: sendmailMTAClassName=R, dc=sendmail, dc=org
+ objectClass: sendmailMTA
+ objectClass: sendmailMTAClass
+ sendmailMTACluster: Servers
+ sendmailMTAClassName: R
+ sendmailMTAClassValue: sendmail.org
+ sendmailMTAClassValue: example.com
+ sendmailMTAClassValue: 10.56.23
+
+CAUTION: If your LDAP database contains the record above and *ALSO* a host
+specific record such as:
+
+ dn: sendmailMTAClassName=R@etrn.sendmail.org, dc=sendmail, dc=org
+ objectClass: sendmailMTA
+ objectClass: sendmailMTAClass
+ sendmailMTAHost: etrn.sendmail.org
+ sendmailMTAClassName: R
+ sendmailMTAClassValue: example.com
+
+the result will be similar to the aliases caution above. When the lookup
+is done on etrn.sendmail.org, $={R} would contain all of the entries (from
+both the cluster match and the host match). In other words, the effective
+is additive.
+
+If you prefer not to use the default LDAP schema for your classes, you can
+specify the map parameters when using the class command. For example:
+
+ VIRTUSER_DOMAIN_FILE(`@ldap:-k (&(objectClass=virtHosts)(host=*)) -v host')
+
+Remember, macros can not be used in a class declaration as the binary does
+not expand them.
+
+
++--------------+
+| LDAP ROUTING |
++--------------+
+
+FEATURE(`ldap_routing') can be used to implement the IETF Internet Draft
+LDAP Schema for Intranet Mail Routing
+(draft-lachman-laser-ldap-mail-routing-01). This feature enables
+LDAP-based rerouting of a particular address to either a different host
+or a different address. The LDAP lookup is first attempted on the full
+address (e.g., user@example.com) and then on the domain portion
+(e.g., @example.com). Be sure to setup your domain for LDAP routing using
+LDAPROUTE_DOMAIN(), e.g.:
+
+ LDAPROUTE_DOMAIN(`example.com')
+
+Additionally, you can specify equivalent domains for LDAP routing using
+LDAPROUTE_EQUIVALENT() and LDAPROUTE_EQUIVALENT_FILE(). 'Equivalent'
+hostnames are mapped to $M (the masqueraded hostname for the server) before
+the LDAP query. For example, if the mail is addressed to
+user@host1.example.com, normally the LDAP lookup would only be done for
+'user@host1.example.com' and '@host1.example.com'. However, if
+LDAPROUTE_EQUIVALENT(`host1.example.com') is used, the lookups would also be
+done on 'user@example.com' and '@example.com' after attempting the
+host1.example.com lookups.
+
+By default, the feature will use the schemas as specified in the draft
+and will not reject addresses not found by the LDAP lookup. However,
+this behavior can be changed by giving additional arguments to the FEATURE()
+command:
+
+ FEATURE(`ldap_routing', <mailHost>, <mailRoutingAddress>, <bounce>,
+ <detail>, <nodomain>, <tempfail>)
+
+where <mailHost> is a map definition describing how to lookup an alternative
+mail host for a particular address; <mailRoutingAddress> is a map definition
+describing how to lookup an alternative address for a particular address;
+the <bounce> argument, if present and not the word "passthru", dictates
+that mail should be bounced if neither a mailHost nor mailRoutingAddress
+is found, if set to "sendertoo", the sender will be rejected if not
+found in LDAP; and <detail> indicates what actions to take if the address
+contains +detail information -- `strip' tries the lookup with the +detail
+and if no matches are found, strips the +detail and tries the lookup again;
+`preserve', does the same as `strip' but if a mailRoutingAddress match is
+found, the +detail information is copied to the new address; the <nodomain>
+argument, if present, will prevent the @domain lookup if the full
+address is not found in LDAP; the <tempfail> argument, if set to
+"tempfail", instructs the rules to give an SMTP 4XX temporary
+error if the LDAP server gives the MTA a temporary failure, or if set to
+"queue" (the default), the MTA will locally queue the mail.
+
+The default <mailHost> map definition is:
+
+ ldap -1 -T<TMPF> -v mailHost -k (&(objectClass=inetLocalMailRecipient)
+ (mailLocalAddress=%0))
+
+The default <mailRoutingAddress> map definition is:
+
+ ldap -1 -T<TMPF> -v mailRoutingAddress
+ -k (&(objectClass=inetLocalMailRecipient)
+ (mailLocalAddress=%0))
+
+Note that neither includes the LDAP server hostname (-h server) or base DN
+(-b o=org,c=COUNTRY), both necessary for LDAP queries. It is presumed that
+your .mc file contains a setting for the confLDAP_DEFAULT_SPEC option with
+these settings. If this is not the case, the map definitions should be
+changed as described above. The "-T<TMPF>" is required in any user
+specified map definition to catch temporary errors.
+
+The following possibilities exist as a result of an LDAP lookup on an
+address:
+
+ mailHost is mailRoutingAddress is Results in
+ ----------- --------------------- ----------
+ set to a set mail delivered to
+ "local" host mailRoutingAddress
+
+ set to a not set delivered to
+ "local" host original address
+
+ set to a set mailRoutingAddress
+ remote host relayed to mailHost
+
+ set to a not set original address
+ remote host relayed to mailHost
+
+ not set set mail delivered to
+ mailRoutingAddress
+
+ not set not set delivered to
+ original address *OR*
+ bounced as unknown user
+
+The term "local" host above means the host specified is in class {w}. If
+the result would mean sending the mail to a different host, that host is
+looked up in the mailertable before delivery.
+
+Note that the last case depends on whether the third argument is given
+to the FEATURE() command. The default is to deliver the message to the
+original address.
+
+The LDAP entries should be set up with an objectClass of
+inetLocalMailRecipient and the address be listed in a mailLocalAddress
+attribute. If present, there must be only one mailHost attribute and it
+must contain a fully qualified host name as its value. Similarly, if
+present, there must be only one mailRoutingAddress attribute and it must
+contain an RFC 822 compliant address. Some example LDAP records (in LDIF
+format):
+
+ dn: uid=tom, o=example.com, c=US
+ objectClass: inetLocalMailRecipient
+ mailLocalAddress: tom@example.com
+ mailRoutingAddress: thomas@mailhost.example.com
+
+This would deliver mail for tom@example.com to thomas@mailhost.example.com.
+
+ dn: uid=dick, o=example.com, c=US
+ objectClass: inetLocalMailRecipient
+ mailLocalAddress: dick@example.com
+ mailHost: eng.example.com
+
+This would relay mail for dick@example.com to the same address but redirect
+the mail to MX records listed for the host eng.example.com (unless the
+mailertable overrides).
+
+ dn: uid=harry, o=example.com, c=US
+ objectClass: inetLocalMailRecipient
+ mailLocalAddress: harry@example.com
+ mailHost: mktmail.example.com
+ mailRoutingAddress: harry@mkt.example.com
+
+This would relay mail for harry@example.com to the MX records listed for
+the host mktmail.example.com using the new address harry@mkt.example.com
+when talking to that host.
+
+ dn: uid=virtual.example.com, o=example.com, c=US
+ objectClass: inetLocalMailRecipient
+ mailLocalAddress: @virtual.example.com
+ mailHost: server.example.com
+ mailRoutingAddress: virtual@example.com
+
+This would send all mail destined for any username @virtual.example.com to
+the machine server.example.com's MX servers and deliver to the address
+virtual@example.com on that relay machine.
+
+
++---------------------------------+
+| ANTI-SPAM CONFIGURATION CONTROL |
++---------------------------------+
+
+The primary anti-spam features available in sendmail are:
+
+* Relaying is denied by default.
+* Better checking on sender information.
+* Access database.
+* Header checks.
+
+Relaying (transmission of messages from a site outside your host (class
+{w}) to another site except yours) is denied by default. Note that this
+changed in sendmail 8.9; previous versions allowed relaying by default.
+If you really want to revert to the old behaviour, you will need to use
+FEATURE(`promiscuous_relay'). You can allow certain domains to relay
+through your server by adding their domain name or IP address to class
+{R} using RELAY_DOMAIN() and RELAY_DOMAIN_FILE() or via the access database
+(described below). Note that IPv6 addresses must be prefaced with "IPv6:".
+The file consists (like any other file based class) of entries listed on
+separate lines, e.g.,
+
+ sendmail.org
+ 128.32
+ IPv6:2002:c0a8:02c7
+ IPv6:2002:c0a8:51d2::23f4
+ host.mydomain.com
+ [UNIX:localhost]
+
+Notice: the last entry allows relaying for connections via a UNIX
+socket to the MTA/MSP. This might be necessary if your configuration
+doesn't allow relaying by other means in that case, e.g., by having
+localhost.$m in class {R} (make sure $m is not just a top level
+domain).
+
+If you use
+
+ FEATURE(`relay_entire_domain')
+
+then any host in any of your local domains (that is, class {m})
+will be relayed (that is, you will accept mail either to or from any
+host in your domain).
+
+You can also allow relaying based on the MX records of the host
+portion of an incoming recipient address by using
+
+ FEATURE(`relay_based_on_MX')
+
+For example, if your server receives a recipient of user@domain.com
+and domain.com lists your server in its MX records, the mail will be
+accepted for relay to domain.com. This feature may cause problems
+if MX lookups for the recipient domain are slow or time out. In that
+case, mail will be temporarily rejected. It is usually better to
+maintain a list of hosts/domains for which the server acts as relay.
+Note also that this feature will stop spammers from using your host
+to relay spam but it will not stop outsiders from using your server
+as a relay for their site (that is, they set up an MX record pointing
+to your mail server, and you will relay mail addressed to them
+without any prior arrangement). Along the same lines,
+
+ FEATURE(`relay_local_from')
+
+will allow relaying if the sender specifies a return path (i.e.
+MAIL FROM: <user@domain>) domain which is a local domain. This is a
+dangerous feature as it will allow spammers to spam using your mail
+server by simply specifying a return address of user@your.domain.com.
+It should not be used unless absolutely necessary.
+A slightly better solution is
+
+ FEATURE(`relay_mail_from')
+
+which allows relaying if the mail sender is listed as RELAY in the
+access map. If an optional argument `domain' (this is the literal
+word `domain', not a placeholder) is given, the domain portion of
+the mail sender is also checked to allowing relaying. This option
+only works together with the tag From: for the LHS of the access
+map entries. This feature allows spammers to abuse your mail server
+by specifying a return address that you enabled in your access file.
+This may be harder to figure out for spammers, but it should not
+be used unless necessary. Instead use STARTTLS to
+allow relaying for roaming users.
+
+
+If source routing is used in the recipient address (e.g.,
+RCPT TO: <user%site.com@othersite.com>), sendmail will check
+user@site.com for relaying if othersite.com is an allowed relay host
+in either class {R}, class {m} if FEATURE(`relay_entire_domain') is used,
+or the access database if FEATURE(`access_db') is used. To prevent
+the address from being stripped down, use:
+
+ FEATURE(`loose_relay_check')
+
+If you think you need to use this feature, you probably do not. This
+should only be used for sites which have no control over the addresses
+that they provide a gateway for. Use this FEATURE with caution as it
+can allow spammers to relay through your server if not setup properly.
+
+NOTICE: It is possible to relay mail through a system which the anti-relay
+rules do not prevent: the case of a system that does use FEATURE(`nouucp',
+`nospecial') (system A) and relays local messages to a mail hub (e.g., via
+LOCAL_RELAY or LUSER_RELAY) (system B). If system B doesn't use
+FEATURE(`nouucp') at all, addresses of the form
+<example.net!user@local.host> would be relayed to <user@example.net>.
+System A doesn't recognize `!' as an address separator and therefore
+forwards it to the mail hub which in turns relays it because it came from
+a trusted local host. So if a mailserver allows UUCP (bang-format)
+addresses, all systems from which it allows relaying should do the same
+or reject those addresses.
+
+As of 8.9, sendmail will refuse mail if the MAIL FROM: parameter has
+an unresolvable domain (i.e., one that DNS, your local name service,
+or special case rules in ruleset 3 cannot locate). This also applies
+to addresses that use domain literals, e.g., <user@[1.2.3.4]>, if the
+IP address can't be mapped to a host name. If you want to continue
+to accept such domains, e.g., because you are inside a firewall that
+has only a limited view of the Internet host name space (note that you
+will not be able to return mail to them unless you have some "smart
+host" forwarder), use
+
+ FEATURE(`accept_unresolvable_domains')
+
+Alternatively, you can allow specific addresses by adding them to
+the access map, e.g.,
+
+ From:unresolvable.domain OK
+ From:[1.2.3.4] OK
+ From:[1.2.4] OK
+
+Notice: domains which are temporarily unresolvable are (temporarily)
+rejected with a 451 reply code. If those domains should be accepted
+(which is discouraged) then you can use
+
+ LOCAL_CONFIG
+ C{ResOk}TEMP
+
+sendmail will also refuse mail if the MAIL FROM: parameter is not
+fully qualified (i.e., contains a domain as well as a user). If you
+want to continue to accept such senders, use
+
+ FEATURE(`accept_unqualified_senders')
+
+Setting the DaemonPortOptions modifier 'u' overrides the default behavior,
+i.e., unqualified addresses are accepted even without this FEATURE. If
+this FEATURE is not used, the DaemonPortOptions modifier 'f' can be used
+to enforce fully qualified domain names.
+
+An ``access'' database can be created to accept or reject mail from
+selected domains. For example, you may choose to reject all mail
+originating from known spammers. To enable such a database, use
+
+ FEATURE(`access_db')
+
+Notice: the access database is applied to the envelope addresses
+and the connection information, not to the header.
+
+The FEATURE macro can accept as second parameter the key file
+definition for the database; for example
+
+ FEATURE(`access_db', `hash -T<TMPF> /etc/mail/access_map')
+
+Notice: If a second argument is specified it must contain the option
+`-T<TMPF>' as shown above. The optional third and fourth parameters
+may be `skip' or `lookupdotdomain'. The former enables SKIP as
+value part (see below), the latter is another way to enable the
+feature of the same name (see above).
+
+Remember, since /etc/mail/access is a database, after creating the text
+file as described below, you must use makemap to create the database
+map. For example:
+
+ makemap hash /etc/mail/access < /etc/mail/access
+
+The table itself uses e-mail addresses, domain names, and network
+numbers as keys. Note that IPv6 addresses must be prefaced with "IPv6:".
+For example,
+
+ From:spammer@aol.com REJECT
+ From:cyberspammer.com REJECT
+ Connect:cyberspammer.com REJECT
+ Connect:TLD REJECT
+ Connect:192.168.212 REJECT
+ Connect:IPv6:2002:c0a8:02c7 RELAY
+ Connect:IPv6:2002:c0a8:51d2::23f4 REJECT
+
+would refuse mail from spammer@aol.com, any user from cyberspammer.com
+(or any host within the cyberspammer.com domain), any host in the entire
+top level domain TLD, 192.168.212.* network, and the IPv6 address
+2002:c0a8:51d2::23f4. It would allow relay for the IPv6 network
+2002:c0a8:02c7::/48.
+
+Entries in the access map should be tagged according to their type.
+Three tags are available:
+
+ Connect: connection information (${client_addr}, ${client_name})
+ From: envelope sender
+ To: envelope recipient
+
+Notice: untagged entries are deprecated.
+
+If the required item is looked up in a map, it will be tried first
+with the corresponding tag in front, then (as fallback to enable
+backward compatibility) without any tag, unless the specific feature
+requires a tag. For example,
+
+ From:spammer@some.dom REJECT
+ To:friend.domain RELAY
+ Connect:friend.domain OK
+ Connect:from.domain RELAY
+ From:good@another.dom OK
+ From:another.dom REJECT
+
+This would deny mails from spammer@some.dom but you could still
+send mail to that address even if FEATURE(`blacklist_recipients')
+is enabled. Your system will allow relaying to friend.domain, but
+not from it (unless enabled by other means). Connections from that
+domain will be allowed even if it ends up in one of the DNS based
+rejection lists. Relaying is enabled from from.domain but not to
+it (since relaying is based on the connection information for
+outgoing relaying, the tag Connect: must be used; for incoming
+relaying, which is based on the recipient address, To: must be
+used). The last two entries allow mails from good@another.dom but
+reject mail from all other addresses with another.dom as domain
+part.
+
+
+The value part of the map can contain:
+
+ OK Accept mail even if other rules in the running
+ ruleset would reject it, for example, if the domain
+ name is unresolvable. "Accept" does not mean
+ "relay", but at most acceptance for local
+ recipients. That is, OK allows less than RELAY.
+ RELAY Accept mail addressed to the indicated domain or
+ received from the indicated domain for relaying
+ through your SMTP server. RELAY also serves as
+ an implicit OK for the other checks.
+ REJECT Reject the sender or recipient with a general
+ purpose message.
+ DISCARD Discard the message completely using the
+ $#discard mailer. If it is used in check_compat,
+ it affects only the designated recipient, not
+ the whole message as it does in all other cases.
+ This should only be used if really necessary.
+ SKIP This can only be used for host/domain names
+ and IP addresses/nets. It will abort the current
+ search for this entry without accepting or rejecting
+ it but causing the default action.
+ ### any text where ### is an RFC 821 compliant error code and
+ "any text" is a message to return for the command.
+ The string should be quoted to avoid surprises,
+ e.g., sendmail may remove spaces otherwise.
+ This type is deprecated, use one of the two
+ ERROR: entries below instead.
+ ERROR:### any text
+ as above, but useful to mark error messages as such.
+ ERROR:D.S.N:### any text
+ where D.S.N is an RFC 1893 compliant error code
+ and the rest as above.
+ QUARANTINE:any text
+ Quarantine the message using the given text as the
+ quarantining reason.
+
+For example:
+
+ From:cyberspammer.com ERROR:"550 We don't accept mail from spammers"
+ From:okay.cyberspammer.com OK
+ Connect:sendmail.org RELAY
+ To:sendmail.org RELAY
+ Connect:128.32 RELAY
+ Connect:128.32.2 SKIP
+ Connect:IPv6:1:2:3:4:5:6:7 RELAY
+ Connect:suspicious.example.com QUARANTINE:Mail from suspicious host
+ Connect:[127.0.0.3] OK
+ Connect:[IPv6:1:2:3:4:5:6:7:8] OK
+
+would accept mail from okay.cyberspammer.com, but would reject mail
+from all other hosts at cyberspammer.com with the indicated message.
+It would allow relaying mail from and to any hosts in the sendmail.org
+domain, and allow relaying from the IPv6 1:2:3:4:5:6:7:* network
+and from the 128.32.*.* network except for the 128.32.2.* network,
+which shows how SKIP is useful to exempt subnets/subdomains. The
+last two entries are for checks against ${client_name} if the IP
+address doesn't resolve to a hostname (or is considered as "may be
+forged"). That is, using square brackets means these are host
+names, not network numbers.
+
+Warning: if you change the RFC 821 compliant error code from the default
+value of 550, then you should probably also change the RFC 1893 compliant
+error code to match it. For example, if you use
+
+ To:user@example.com ERROR:450 mailbox full
+
+the error returned would be "450 5.0.0 mailbox full" which is wrong.
+Use "ERROR:4.2.2:450 mailbox full" instead.
+
+Note, UUCP users may need to add hostname.UUCP to the access database
+or class {R}.
+
+If you also use:
+
+ FEATURE(`relay_hosts_only')
+
+then the above example will allow relaying for sendmail.org, but not
+hosts within the sendmail.org domain. Note that this will also require
+hosts listed in class {R} to be fully qualified host names.
+
+You can also use the access database to block sender addresses based on
+the username portion of the address. For example:
+
+ From:FREE.STEALTH.MAILER@ ERROR:550 Spam not accepted
+
+Note that you must include the @ after the username to signify that
+this database entry is for checking only the username portion of the
+sender address.
+
+If you use:
+
+ FEATURE(`blacklist_recipients')
+
+then you can add entries to the map for local users, hosts in your
+domains, or addresses in your domain which should not receive mail:
+
+ To:badlocaluser@ ERROR:550 Mailbox disabled for badlocaluser
+ To:host.my.TLD ERROR:550 That host does not accept mail
+ To:user@other.my.TLD ERROR:550 Mailbox disabled for this recipient
+
+This would prevent a recipient of badlocaluser in any of the local
+domains (class {w}), any user at host.my.TLD, and the single address
+user@other.my.TLD from receiving mail. Please note: a local username
+must be now tagged with an @ (this is consistent with the check of
+the sender address, and hence it is possible to distinguish between
+hostnames and usernames). Enabling this feature will keep you from
+sending mails to all addresses that have an error message or REJECT
+as value part in the access map. Taking the example from above:
+
+ spammer@aol.com REJECT
+ cyberspammer.com REJECT
+
+Mail can't be sent to spammer@aol.com or anyone at cyberspammer.com.
+That's why tagged entries should be used.
+
+There are several DNS based blacklists, the first of which was
+the RBL (``Realtime Blackhole List'') run by the MAPS project,
+see http://mail-abuse.org/. These are databases of spammers
+maintained in DNS. To use such a database, specify
+
+ FEATURE(`dnsbl')
+
+This will cause sendmail to reject mail from any site in the original
+Realtime Blackhole List database. This default DNS blacklist,
+blackholes.mail-abuse.org, is a service offered by the Mail Abuse
+Prevention System (MAPS). As of July 31, 2001, MAPS is a subscription
+service, so using that network address won't work if you haven't
+subscribed. Contact MAPS to subscribe (http://mail-abuse.org/).
+
+You can specify an alternative RBL server to check by specifying an
+argument to the FEATURE. The default error message is
+
+ Rejected: IP-ADDRESS listed at SERVER
+
+where IP-ADDRESS and SERVER are replaced by the appropriate
+information. A second argument can be used to specify a different
+text. By default, temporary lookup failures are ignored and hence
+cause the connection not to be rejected by the DNS based rejection
+list. This behavior can be changed by specifying a third argument,
+which must be either `t' or a full error message. For example:
+
+ FEATURE(`dnsbl', `dnsbl.example.com', `',
+ `"451 Temporary lookup failure for " $&{client_addr} " in dnsbl.example.com"')
+
+If `t' is used, the error message is:
+
+ 451 Temporary lookup failure of IP-ADDRESS at SERVER
+
+where IP-ADDRESS and SERVER are replaced by the appropriate
+information.
+
+This FEATURE can be included several times to query different
+DNS based rejection lists, e.g., the dial-up user list (see
+http://mail-abuse.org/dul/).
+
+Notice: to avoid checking your own local domains against those
+blacklists, use the access_db feature and add:
+
+ Connect:10.1 OK
+ Connect:127.0.0.1 RELAY
+
+to the access map, where 10.1 is your local network. You may
+want to use "RELAY" instead of "OK" to allow also relaying
+instead of just disabling the DNS lookups in the blacklists.
+
+
+The features described above make use of the check_relay, check_mail,
+and check_rcpt rulesets. Note that check_relay checks the SMTP
+client hostname and IP address when the connection is made to your
+server. It does not check if a mail message is being relayed to
+another server. That check is done in check_rcpt. If you wish to
+include your own checks, you can put your checks in the rulesets
+Local_check_relay, Local_check_mail, and Local_check_rcpt. For
+example if you wanted to block senders with all numeric usernames
+(i.e. 2312343@bigisp.com), you would use Local_check_mail and the
+regex map:
+
+ LOCAL_CONFIG
+ Kallnumbers regex -a@MATCH ^[0-9]+$
+
+ LOCAL_RULESETS
+ SLocal_check_mail
+ # check address against various regex checks
+ R$* $: $>Parse0 $>3 $1
+ R$+ < @ bigisp.com. > $* $: $(allnumbers $1 $)
+ R@MATCH $#error $: 553 Header Error
+
+These rules are called with the original arguments of the corresponding
+check_* ruleset. If the local ruleset returns $#OK, no further checking
+is done by the features described above and the mail is accepted. If
+the local ruleset resolves to a mailer (such as $#error or $#discard),
+the appropriate action is taken. Other results starting with $# are
+interpreted by sendmail and may lead to unspecified behavior. Note: do
+NOT create a mailer with the name OK. Return values that do not start
+with $# are ignored, i.e., normal processing continues.
+
+Delay all checks
+----------------
+
+By using FEATURE(`delay_checks') the rulesets check_mail and check_relay
+will not be called when a client connects or issues a MAIL command,
+respectively. Instead, those rulesets will be called by the check_rcpt
+ruleset; they will be skipped if a sender has been authenticated using
+a "trusted" mechanism, i.e., one that is defined via TRUST_AUTH_MECH().
+If check_mail returns an error then the RCPT TO command will be rejected
+with that error. If it returns some other result starting with $# then
+check_relay will be skipped. If the sender address (or a part of it) is
+listed in the access map and it has a RHS of OK or RELAY, then check_relay
+will be skipped. This has an interesting side effect: if your domain is
+my.domain and you have
+
+ my.domain RELAY
+
+in the access map, then any e-mail with a sender address of
+<user@my.domain> will not be rejected by check_relay even though
+it would match the hostname or IP address. This allows spammers
+to get around DNS based blacklist by faking the sender address. To
+avoid this problem you have to use tagged entries:
+
+ To:my.domain RELAY
+ Connect:my.domain RELAY
+
+if you need those entries at all (class {R} may take care of them).
+
+FEATURE(`delay_checks') can take an optional argument:
+
+ FEATURE(`delay_checks', `friend')
+ enables spamfriend test
+ FEATURE(`delay_checks', `hater')
+ enables spamhater test
+
+If such an argument is given, the recipient will be looked up in the
+access map (using the tag Spam:). If the argument is `friend', then
+the default behavior is to apply the other rulesets and make a SPAM
+friend the exception. The rulesets check_mail and check_relay will be
+skipped only if the recipient address is found and has RHS FRIEND. If
+the argument is `hater', then the default behavior is to skip the rulesets
+check_mail and check_relay and make a SPAM hater the exception. The
+other two rulesets will be applied only if the recipient address is
+found and has RHS HATER.
+
+This allows for simple exceptions from the tests, e.g., by activating
+the friend option and having
+
+ Spam:abuse@ FRIEND
+
+in the access map, mail to abuse@localdomain will get through (where
+"localdomain" is any domain in class {w}). It is also possible to
+specify a full address or an address with +detail:
+
+ Spam:abuse@my.domain FRIEND
+ Spam:me+abuse@ FRIEND
+ Spam:spam.domain FRIEND
+
+Note: The required tag has been changed in 8.12 from To: to Spam:.
+This change is incompatible to previous versions. However, you can
+(for now) simply add the new entries to the access map, the old
+ones will be ignored. As soon as you removed the old entries from
+the access map, specify a third parameter (`n') to this feature and
+the backward compatibility rules will not be in the generated .cf
+file.
+
+Header Checks
+-------------
+
+You can also reject mail on the basis of the contents of headers.
+This is done by adding a ruleset call to the 'H' header definition command
+in sendmail.cf. For example, this can be used to check the validity of
+a Message-ID: header:
+
+ LOCAL_CONFIG
+ HMessage-Id: $>CheckMessageId
+
+ LOCAL_RULESETS
+ SCheckMessageId
+ R< $+ @ $+ > $@ OK
+ R$* $#error $: 553 Header Error
+
+The alternative format:
+
+ HSubject: $>+CheckSubject
+
+that is, $>+ instead of $>, gives the full Subject: header including
+comments to the ruleset (comments in parentheses () are stripped
+by default).
+
+A default ruleset for headers which don't have a specific ruleset
+defined for them can be given by:
+
+ H*: $>CheckHdr
+
+Notice:
+1. All rules act on tokens as explained in doc/op/op.{me,ps,txt}.
+That may cause problems with simple header checks due to the
+tokenization. It might be simpler to use a regex map and apply it
+to $&{currHeader}.
+2. There are no default rulesets coming with this distribution of
+sendmail. You can either write your own or you can search the
+WWW for examples, e.g., http://www.digitalanswers.org/check_local/
+3. When using a default ruleset for headers, the name of the header
+currently being checked can be found in the $&{hdr_name} macro.
+
+After all of the headers are read, the check_eoh ruleset will be called for
+any final header-related checks. The ruleset is called with the number of
+headers and the size of all of the headers in bytes separated by $|. One
+example usage is to reject messages which do not have a Message-Id:
+header. However, the Message-Id: header is *NOT* a required header and is
+not a guaranteed spam indicator. This ruleset is an example and should
+probably not be used in production.
+
+ LOCAL_CONFIG
+ Kstorage macro
+ HMessage-Id: $>CheckMessageId
+
+ LOCAL_RULESETS
+ SCheckMessageId
+ # Record the presence of the header
+ R$* $: $(storage {MessageIdCheck} $@ OK $) $1
+ R< $+ @ $+ > $@ OK
+ R$* $#error $: 553 Header Error
+
+ Scheck_eoh
+ # Check the macro
+ R$* $: < $&{MessageIdCheck} >
+ # Clear the macro for the next message
+ R$* $: $(storage {MessageIdCheck} $) $1
+ # Has a Message-Id: header
+ R< $+ > $@ OK
+ # Allow missing Message-Id: from local mail
+ R$* $: < $&{client_name} >
+ R< > $@ OK
+ R< $=w > $@ OK
+ # Otherwise, reject the mail
+ R$* $#error $: 553 Header Error
+
+
++--------------------+
+| CONNECTION CONTROL |
++--------------------+
+
+The features ratecontrol and conncontrol allow to establish connection
+limits per client IP address or net. These features can limit the
+rate of connections (connections per time unit) or the number of
+incoming SMTP connections, respectively. If enabled, appropriate
+rulesets are called at the end of check_relay, i.e., after DNS
+blacklists and generic access_db operations. The features require
+FEATURE(`access_db') to be listed earlier in the mc file.
+
+Note: FEATURE(`delay_checks') delays those connection control checks
+after a recipient address has been received, hence making these
+connection control features less useful. To run the checks as early
+as possible, specify the parameter `nodelay', e.g.,
+
+ FEATURE(`ratecontrol', `nodelay')
+
+In that case, FEATURE(`delay_checks') has no effect on connection
+control (and it must be specified earlier in the mc file).
+
+An optional second argument `terminate' specifies whether the
+rulesets should return the error code 421 which will cause
+sendmail to terminate the session with that error if it is
+returned from check_relay, i.e., not delayed as explained in
+the previous paragraph. Example:
+
+ FEATURE(`ratecontrol', `nodelay', `terminate')
+
+
++----------+
+| STARTTLS |
++----------+
+
+In this text, cert will be used as an abbreviation for X.509 certificate,
+DN (CN) is the distinguished (common) name of a cert, and CA is a
+certification authority, which signs (issues) certs.
+
+For STARTTLS to be offered by sendmail you need to set at least
+these variables (the file names and paths are just examples):
+
+ define(`confCACERT_PATH', `/etc/mail/certs/')
+ define(`confCACERT', `/etc/mail/certs/CA.cert.pem')
+ define(`confSERVER_CERT', `/etc/mail/certs/my.cert.pem')
+ define(`confSERVER_KEY', `/etc/mail/certs/my.key.pem')
+
+On systems which do not have the compile flag HASURANDOM set (see
+sendmail/README) you also must set confRAND_FILE.
+
+See doc/op/op.{me,ps,txt} for more information about these options,
+especially the sections ``Certificates for STARTTLS'' and ``PRNG for
+STARTTLS''.
+
+Macros related to STARTTLS are:
+
+${cert_issuer} holds the DN of the CA (the cert issuer).
+${cert_subject} holds the DN of the cert (called the cert subject).
+${cn_issuer} holds the CN of the CA (the cert issuer).
+${cn_subject} holds the CN of the cert (called the cert subject).
+${tls_version} the TLS/SSL version used for the connection, e.g., TLSv1,
+ TLSv1/SSLv3, SSLv3, SSLv2.
+${cipher} the cipher used for the connection, e.g., EDH-DSS-DES-CBC3-SHA,
+ EDH-RSA-DES-CBC-SHA, DES-CBC-MD5, DES-CBC3-SHA.
+${cipher_bits} the keylength (in bits) of the symmetric encryption algorithm
+ used for the connection.
+${verify} holds the result of the verification of the presented cert.
+ Possible values are:
+ OK verification succeeded.
+ NO no cert presented.
+ NOT no cert requested.
+ FAIL cert presented but could not be verified,
+ e.g., the cert of the signing CA is missing.
+ NONE STARTTLS has not been performed.
+ TEMP temporary error occurred.
+ PROTOCOL protocol error occurred (SMTP level).
+ SOFTWARE STARTTLS handshake failed.
+${server_name} the name of the server of the current outgoing SMTP
+ connection.
+${server_addr} the address of the server of the current outgoing SMTP
+ connection.
+
+Relaying
+--------
+
+SMTP STARTTLS can allow relaying for remote SMTP clients which have
+successfully authenticated themselves. If the verification of the cert
+failed (${verify} != OK), relaying is subject to the usual rules.
+Otherwise the DN of the issuer is looked up in the access map using the
+tag CERTISSUER. If the resulting value is RELAY, relaying is allowed.
+If it is SUBJECT, the DN of the cert subject is looked up next in the
+access map using the tag CERTSUBJECT. If the value is RELAY, relaying
+is allowed.
+
+To make things a bit more flexible (or complicated), the values for
+${cert_issuer} and ${cert_subject} can be optionally modified by regular
+expressions defined in the m4 variables _CERT_REGEX_ISSUER_ and
+_CERT_REGEX_SUBJECT_, respectively. To avoid problems with those macros in
+rulesets and map lookups, they are modified as follows: each non-printable
+character and the characters '<', '>', '(', ')', '"', '+', ' ' are replaced
+by their HEX value with a leading '+'. For example:
+
+/C=US/ST=California/O=endmail.org/OU=private/CN=Darth Mail (Cert)/Email=
+darth+cert@endmail.org
+
+is encoded as:
+
+/C=US/ST=California/O=endmail.org/OU=private/CN=
+Darth+20Mail+20+28Cert+29/Email=darth+2Bcert@endmail.org
+
+(line breaks have been inserted for readability).
+
+The macros which are subject to this encoding are ${cert_subject},
+${cert_issuer}, ${cn_subject}, and ${cn_issuer}.
+
+Examples:
+
+To allow relaying for everyone who can present a cert signed by
+
+/C=US/ST=California/O=endmail.org/OU=private/CN=
+Darth+20Mail+20+28Cert+29/Email=darth+2Bcert@endmail.org
+
+simply use:
+
+CertIssuer:/C=US/ST=California/O=endmail.org/OU=private/CN=
+Darth+20Mail+20+28Cert+29/Email=darth+2Bcert@endmail.org RELAY
+
+To allow relaying only for a subset of machines that have a cert signed by
+
+/C=US/ST=California/O=endmail.org/OU=private/CN=
+Darth+20Mail+20+28Cert+29/Email=darth+2Bcert@endmail.org
+
+use:
+
+CertIssuer:/C=US/ST=California/O=endmail.org/OU=private/CN=
+Darth+20Mail+20+28Cert+29/Email=darth+2Bcert@endmail.org SUBJECT
+CertSubject:/C=US/ST=California/O=endmail.org/OU=private/CN=
+DeathStar/Email=deathstar@endmail.org RELAY
+
+Notes:
+- line breaks have been inserted after "CN=" for readability,
+ each tagged entry must be one (long) line in the access map.
+- if OpenSSL 0.9.7 or newer is used then the "Email=" part of a DN
+ is replaced by "emailAddress=".
+
+Of course it is also possible to write a simple ruleset that allows
+relaying for everyone who can present a cert that can be verified, e.g.,
+
+LOCAL_RULESETS
+SLocal_check_rcpt
+R$* $: $&{verify}
+ROK $# OK
+
+Allowing Connections
+--------------------
+
+The rulesets tls_server, tls_client, and tls_rcpt are used to decide whether
+an SMTP connection is accepted (or should continue).
+
+tls_server is called when sendmail acts as client after a STARTTLS command
+(should) have been issued. The parameter is the value of ${verify}.
+
+tls_client is called when sendmail acts as server, after a STARTTLS command
+has been issued, and from check_mail. The parameter is the value of
+${verify} and STARTTLS or MAIL, respectively.
+
+Both rulesets behave the same. If no access map is in use, the connection
+will be accepted unless ${verify} is SOFTWARE, in which case the connection
+is always aborted. For tls_server/tls_client, ${client_name}/${server_name}
+is looked up in the access map using the tag TLS_Srv/TLS_Clt, which is done
+with the ruleset LookUpDomain. If no entry is found, ${client_addr}
+(${server_addr}) is looked up in the access map (same tag, ruleset
+LookUpAddr). If this doesn't result in an entry either, just the tag is
+looked up in the access map (included the trailing colon). Notice:
+requiring that e-mail is sent to a server only encrypted, e.g., via
+
+TLS_Srv:secure.domain ENCR:112
+
+doesn't necessarily mean that e-mail sent to that domain is encrypted.
+If the domain has multiple MX servers, e.g.,
+
+secure.domain. IN MX 10 mail.secure.domain.
+secure.domain. IN MX 50 mail.other.domain.
+
+then mail to user@secure.domain may go unencrypted to mail.other.domain.
+tls_rcpt can be used to address this problem.
+
+tls_rcpt is called before a RCPT TO: command is sent. The parameter is the
+current recipient. This ruleset is only defined if FEATURE(`access_db')
+is selected. A recipient address user@domain is looked up in the access
+map in four formats: TLS_Rcpt:user@domain, TLS_Rcpt:user@, TLS_Rcpt:domain,
+and TLS_Rcpt:; the first match is taken.
+
+The result of the lookups is then used to call the ruleset TLS_connection,
+which checks the requirement specified by the RHS in the access map against
+the actual parameters of the current TLS connection, esp. ${verify} and
+${cipher_bits}. Legal RHSs in the access map are:
+
+VERIFY verification must have succeeded
+VERIFY:bits verification must have succeeded and ${cipher_bits} must
+ be greater than or equal bits.
+ENCR:bits ${cipher_bits} must be greater than or equal bits.
+
+The RHS can optionally be prefixed by TEMP+ or PERM+ to select a temporary
+or permanent error. The default is a temporary error code (403 4.7.0)
+unless the macro TLS_PERM_ERR is set during generation of the .cf file.
+
+If a certain level of encryption is required, then it might also be
+possible that this level is provided by the security layer from a SASL
+algorithm, e.g., DIGEST-MD5.
+
+Furthermore, there can be a list of extensions added. Such a list
+starts with '+' and the items are separated by '++'. Allowed
+extensions are:
+
+CN:name name must match ${cn_subject}
+CN ${server_name} must match ${cn_subject}
+CS:name name must match ${cert_subject}
+CI:name name must match ${cert_issuer}
+
+Example: e-mail sent to secure.example.com should only use an encrypted
+connection. E-mail received from hosts within the laptop.example.com domain
+should only be accepted if they have been authenticated. The host which
+receives e-mail for darth@endmail.org must present a cert that uses the
+CN smtp.endmail.org.
+
+TLS_Srv:secure.example.com ENCR:112
+TLS_Clt:laptop.example.com PERM+VERIFY:112
+TLS_Rcpt:darth@endmail.org ENCR:112+CN:smtp.endmail.org
+
+
+Disabling STARTTLS And Setting SMTP Server Features
+---------------------------------------------------
+
+By default STARTTLS is used whenever possible. However, there are
+some broken MTAs that don't properly implement STARTTLS. To be able
+to send to (or receive from) those MTAs, the ruleset try_tls
+(srv_features) can be used that work together with the access map.
+Entries for the access map must be tagged with Try_TLS (Srv_Features)
+and refer to the hostname or IP address of the connecting system.
+A default case can be specified by using just the tag. For example,
+the following entries in the access map:
+
+ Try_TLS:broken.server NO
+ Srv_Features:my.domain v
+ Srv_Features: V
+
+will turn off STARTTLS when sending to broken.server (or any host
+in that domain), and request a client certificate during the TLS
+handshake only for hosts in my.domain. The valid entries on the RHS
+for Srv_Features are listed in the Sendmail Installation and
+Operations Guide.
+
+
+Received: Header
+----------------
+
+The Received: header reveals whether STARTTLS has been used. It contains an
+extra line:
+
+(version=${tls_version} cipher=${cipher} bits=${cipher_bits} verify=${verify})
+
+
++--------------------------------+
+| ADDING NEW MAILERS OR RULESETS |
++--------------------------------+
+
+Sometimes you may need to add entirely new mailers or rulesets. They
+should be introduced with the constructs MAILER_DEFINITIONS and
+LOCAL_RULESETS respectively. For example:
+
+ MAILER_DEFINITIONS
+ Mmymailer, ...
+ ...
+
+ LOCAL_RULESETS
+ Smyruleset
+ ...
+
+Note: you don't need to add a name for the ruleset, it is implicitly
+defined by using the appropriate macro.
+
+
++-------------------------+
+| ADDING NEW MAIL FILTERS |
++-------------------------+
+
+Sendmail supports mail filters to filter incoming SMTP messages according
+to the "Sendmail Mail Filter API" documentation. These filters can be
+configured in your mc file using the two commands:
+
+ MAIL_FILTER(`name', `equates')
+ INPUT_MAIL_FILTER(`name', `equates')
+
+The first command, MAIL_FILTER(), simply defines a filter with the given
+name and equates. For example:
+
+ MAIL_FILTER(`archive', `S=local:/var/run/archivesock, F=R')
+
+This creates the equivalent sendmail.cf entry:
+
+ Xarchive, S=local:/var/run/archivesock, F=R
+
+The INPUT_MAIL_FILTER() command performs the same actions as MAIL_FILTER
+but also populates the m4 variable `confINPUT_MAIL_FILTERS' with the name
+of the filter such that the filter will actually be called by sendmail.
+
+For example, the two commands:
+
+ INPUT_MAIL_FILTER(`archive', `S=local:/var/run/archivesock, F=R')
+ INPUT_MAIL_FILTER(`spamcheck', `S=inet:2525@localhost, F=T')
+
+are equivalent to the three commands:
+
+ MAIL_FILTER(`archive', `S=local:/var/run/archivesock, F=R')
+ MAIL_FILTER(`spamcheck', `S=inet:2525@localhost, F=T')
+ define(`confINPUT_MAIL_FILTERS', `archive, spamcheck')
+
+In general, INPUT_MAIL_FILTER() should be used unless you need to define
+more filters than you want to use for `confINPUT_MAIL_FILTERS'.
+
+Note that setting `confINPUT_MAIL_FILTERS' after any INPUT_MAIL_FILTER()
+commands will clear the list created by the prior INPUT_MAIL_FILTER()
+commands.
+
+
++-------------------------+
+| QUEUE GROUP DEFINITIONS |
++-------------------------+
+
+In addition to the queue directory (which is the default queue group
+called "mqueue"), sendmail can deal with multiple queue groups, which
+are collections of queue directories with the same behaviour. Queue
+groups can be defined using the command:
+
+ QUEUE_GROUP(`name', `equates')
+
+For details about queue groups, please see doc/op/op.{me,ps,txt}.
+
++-------------------------------+
+| NON-SMTP BASED CONFIGURATIONS |
++-------------------------------+
+
+These configuration files are designed primarily for use by
+SMTP-based sites. They may not be well tuned for UUCP-only or
+UUCP-primarily nodes (the latter is defined as a small local net
+connected to the rest of the world via UUCP). However, there is
+one hook to handle some special cases.
+
+You can define a ``smart host'' that understands a richer address syntax
+using:
+
+ define(`SMART_HOST', `mailer:hostname')
+
+In this case, the ``mailer:'' defaults to "relay". Any messages that
+can't be handled using the usual UUCP rules are passed to this host.
+
+If you are on a local SMTP-based net that connects to the outside
+world via UUCP, you can use LOCAL_NET_CONFIG to add appropriate rules.
+For example:
+
+ define(`SMART_HOST', `uucp-new:uunet')
+ LOCAL_NET_CONFIG
+ R$* < @ $* .$m. > $* $#smtp $@ $2.$m. $: $1 < @ $2.$m. > $3
+
+This will cause all names that end in your domain name ($m) to be sent
+via SMTP; anything else will be sent via uucp-new (smart UUCP) to uunet.
+If you have FEATURE(`nocanonify'), you may need to omit the dots after
+the $m. If you are running a local DNS inside your domain which is
+not otherwise connected to the outside world, you probably want to
+use:
+
+ define(`SMART_HOST', `smtp:fire.wall.com')
+ LOCAL_NET_CONFIG
+ R$* < @ $* . > $* $#smtp $@ $2. $: $1 < @ $2. > $3
+
+That is, send directly only to things you found in your DNS lookup;
+anything else goes through SMART_HOST.
+
+You may need to turn off the anti-spam rules in order to accept
+UUCP mail with FEATURE(`promiscuous_relay') and
+FEATURE(`accept_unresolvable_domains').
+
+
++-----------+
+| WHO AM I? |
++-----------+
+
+Normally, the $j macro is automatically defined to be your fully
+qualified domain name (FQDN). Sendmail does this by getting your
+host name using gethostname and then calling gethostbyname on the
+result. For example, in some environments gethostname returns
+only the root of the host name (such as "foo"); gethostbyname is
+supposed to return the FQDN ("foo.bar.com"). In some (fairly rare)
+cases, gethostbyname may fail to return the FQDN. In this case
+you MUST define confDOMAIN_NAME to be your fully qualified domain
+name. This is usually done using:
+
+ Dmbar.com
+ define(`confDOMAIN_NAME', `$w.$m')dnl
+
+
++-----------------------------------+
+| ACCEPTING MAIL FOR MULTIPLE NAMES |
++-----------------------------------+
+
+If your host is known by several different names, you need to augment
+class {w}. This is a list of names by which your host is known, and
+anything sent to an address using a host name in this list will be
+treated as local mail. You can do this in two ways: either create the
+file /etc/mail/local-host-names containing a list of your aliases (one per
+line), and use ``FEATURE(`use_cw_file')'' in the .mc file, or add
+``LOCAL_DOMAIN(`alias.host.name')''. Be sure you use the fully-qualified
+name of the host, rather than a short name.
+
+If you want to have different address in different domains, take
+a look at the virtusertable feature, which is also explained at
+http://www.sendmail.org/virtual-hosting.html
+
+
++--------------------+
+| USING MAILERTABLES |
++--------------------+
+
+To use FEATURE(`mailertable'), you will have to create an external
+database containing the routing information for various domains.
+For example, a mailertable file in text format might be:
+
+ .my.domain xnet:%1.my.domain
+ uuhost1.my.domain uucp-new:uuhost1
+ .bitnet smtp:relay.bit.net
+
+This should normally be stored in /etc/mail/mailertable. The actual
+database version of the mailertable is built using:
+
+ makemap hash /etc/mail/mailertable < /etc/mail/mailertable
+
+The semantics are simple. Any LHS entry that does not begin with
+a dot matches the full host name indicated. LHS entries beginning
+with a dot match anything ending with that domain name (including
+the leading dot) -- that is, they can be thought of as having a
+leading ".+" regular expression pattern for a non-empty sequence of
+characters. Matching is done in order of most-to-least qualified
+-- for example, even though ".my.domain" is listed first in the
+above example, an entry of "uuhost1.my.domain" will match the second
+entry since it is more explicit. Note: e-mail to "user@my.domain"
+does not match any entry in the above table. You need to have
+something like:
+
+ my.domain esmtp:host.my.domain
+
+The RHS should always be a "mailer:host" pair. The mailer is the
+configuration name of a mailer (that is, an M line in the
+sendmail.cf file). The "host" will be the hostname passed to
+that mailer. In domain-based matches (that is, those with leading
+dots) the "%1" may be used to interpolate the wildcarded part of
+the host name. For example, the first line above sends everything
+addressed to "anything.my.domain" to that same host name, but using
+the (presumably experimental) xnet mailer.
+
+In some cases you may want to temporarily turn off MX records,
+particularly on gateways. For example, you may want to MX
+everything in a domain to one machine that then forwards it
+directly. To do this, you might use the DNS configuration:
+
+ *.domain. IN MX 0 relay.machine
+
+and on relay.machine use the mailertable:
+
+ .domain smtp:[gateway.domain]
+
+The [square brackets] turn off MX records for this host only.
+If you didn't do this, the mailertable would use the MX record
+again, which would give you an MX loop. Note that the use of
+wildcard MX records is almost always a bad idea. Please avoid
+using them if possible.
+
+
++--------------------------------+
+| USING USERDB TO MAP FULL NAMES |
++--------------------------------+
+
+The user database was not originally intended for mapping full names
+to login names (e.g., Eric.Allman => eric), but some people are using
+it that way. (it is recommended that you set up aliases for this
+purpose instead -- since you can specify multiple alias files, this
+is fairly easy.) The intent was to locate the default maildrop at
+a site, but allow you to override this by sending to a specific host.
+
+If you decide to set up the user database in this fashion, it is
+imperative that you not use FEATURE(`stickyhost') -- otherwise,
+e-mail sent to Full.Name@local.host.name will be rejected.
+
+To build the internal form of the user database, use:
+
+ makemap btree /etc/mail/userdb < /etc/mail/userdb.txt
+
+As a general rule, it is an extremely bad idea to using full names
+as e-mail addresses, since they are not in any sense unique. For
+example, the UNIX software-development community has at least two
+well-known Peter Deutsches, and at one time Bell Labs had two
+Stephen R. Bournes with offices along the same hallway. Which one
+will be forced to suffer the indignity of being Stephen_R_Bourne_2?
+The less famous of the two, or the one that was hired later?
+
+Finger should handle full names (and be fuzzy). Mail should use
+handles, and not be fuzzy.
+
+
++--------------------------------+
+| MISCELLANEOUS SPECIAL FEATURES |
++--------------------------------+
+
+Plussed users
+ Sometimes it is convenient to merge configuration on a
+ centralized mail machine, for example, to forward all
+ root mail to a mail server. In this case it might be
+ useful to be able to treat the root addresses as a class
+ of addresses with subtle differences. You can do this
+ using plussed users. For example, a client might include
+ the alias:
+
+ root: root+client1@server
+
+ On the server, this will match an alias for "root+client1".
+ If that is not found, the alias "root+*" will be tried,
+ then "root".
+
+
++----------------+
+| SECURITY NOTES |
++----------------+
+
+A lot of sendmail security comes down to you. Sendmail 8 is much
+more careful about checking for security problems than previous
+versions, but there are some things that you still need to watch
+for. In particular:
+
+* Make sure the aliases file is not writable except by trusted
+ system personnel. This includes both the text and database
+ version.
+
+* Make sure that other files that sendmail reads, such as the
+ mailertable, are only writable by trusted system personnel.
+
+* The queue directory should not be world writable PARTICULARLY
+ if your system allows "file giveaways" (that is, if a non-root
+ user can chown any file they own to any other user).
+
+* If your system allows file giveaways, DO NOT create a publically
+ writable directory for forward files. This will allow anyone
+ to steal anyone else's e-mail. Instead, create a script that
+ copies the .forward file from users' home directories once a
+ night (if you want the non-NFS-mounted forward directory).
+
+* If your system allows file giveaways, you'll find that
+ sendmail is much less trusting of :include: files -- in
+ particular, you'll have to have /SENDMAIL/ANY/SHELL/ in
+ /etc/shells before they will be trusted (that is, before
+ files and programs listed in them will be honored).
+
+In general, file giveaways are a mistake -- if you can turn them
+off, do so.
+
+
++--------------------------------+
+| TWEAKING CONFIGURATION OPTIONS |
++--------------------------------+
+
+There are a large number of configuration options that don't normally
+need to be changed. However, if you feel you need to tweak them,
+you can define the following M4 variables. Note that some of these
+variables require formats that are defined in RFC 2821 or RFC 2822.
+Before changing them you need to make sure you do not violate those
+(and other relevant) RFCs.
+
+This list is shown in four columns: the name you define, the default
+value for that definition, the option or macro that is affected
+(either Ox for an option or Dx for a macro), and a brief description.
+
+Some options are likely to be deprecated in future versions -- that is,
+the option is only included to provide back-compatibility. These are
+marked with "*".
+
+Remember that these options are M4 variables, and hence may need to
+be quoted. In particular, arguments with commas will usually have to
+be ``double quoted, like this phrase'' to avoid having the comma
+confuse things. This is common for alias file definitions and for
+the read timeout.
+
+M4 Variable Name Configuration [Default] & Description
+================ ============= =======================
+confMAILER_NAME $n macro [MAILER-DAEMON] The sender name used
+ for internally generated outgoing
+ messages.
+confDOMAIN_NAME $j macro If defined, sets $j. This should
+ only be done if your system cannot
+ determine your local domain name,
+ and then it should be set to
+ $w.Foo.COM, where Foo.COM is your
+ domain name.
+confCF_VERSION $Z macro If defined, this is appended to the
+ configuration version name.
+confLDAP_CLUSTER ${sendmailMTACluster} macro
+ If defined, this is the LDAP
+ cluster to use for LDAP searches
+ as described above in ``USING LDAP
+ FOR ALIASES, MAPS, AND CLASSES''.
+confFROM_HEADER From: [$?x$x <$g>$|$g$.] The format of an
+ internally generated From: address.
+confRECEIVED_HEADER Received:
+ [$?sfrom $s $.$?_($?s$|from $.$_)
+ $.$?{auth_type}(authenticated)
+ $.by $j ($v/$Z)$?r with $r$. id $i$?u
+ for $u; $|;
+ $.$b]
+ The format of the Received: header
+ in messages passed through this host.
+ It is unwise to try to change this.
+confMESSAGEID_HEADER Message-Id: [<$t.$i@$j>] The format of an
+ internally generated Message-Id:
+ header.
+confCW_FILE Fw class [/etc/mail/local-host-names] Name
+ of file used to get the local
+ additions to class {w} (local host
+ names).
+confCT_FILE Ft class [/etc/mail/trusted-users] Name of
+ file used to get the local additions
+ to class {t} (trusted users).
+confCR_FILE FR class [/etc/mail/relay-domains] Name of
+ file used to get the local additions
+ to class {R} (hosts allowed to relay).
+confTRUSTED_USERS Ct class [no default] Names of users to add to
+ the list of trusted users. This list
+ always includes root, uucp, and daemon.
+ See also FEATURE(`use_ct_file').
+confTRUSTED_USER TrustedUser [no default] Trusted user for file
+ ownership and starting the daemon.
+ Not to be confused with
+ confTRUSTED_USERS (see above).
+confSMTP_MAILER - [esmtp] The mailer name used when
+ SMTP connectivity is required.
+ One of "smtp", "smtp8",
+ "esmtp", or "dsmtp".
+confUUCP_MAILER - [uucp-old] The mailer to be used by
+ default for bang-format recipient
+ addresses. See also discussion of
+ class {U}, class {Y}, and class {Z}
+ in the MAILER(`uucp') section.
+confLOCAL_MAILER - [local] The mailer name used when
+ local connectivity is required.
+ Almost always "local".
+confRELAY_MAILER - [relay] The default mailer name used
+ for relaying any mail (e.g., to a
+ BITNET_RELAY, a SMART_HOST, or
+ whatever). This can reasonably be
+ "uucp-new" if you are on a
+ UUCP-connected site.
+confSEVEN_BIT_INPUT SevenBitInput [False] Force input to seven bits?
+confEIGHT_BIT_HANDLING EightBitMode [pass8] 8-bit data handling
+confALIAS_WAIT AliasWait [10m] Time to wait for alias file
+ rebuild until you get bored and
+ decide that the apparently pending
+ rebuild failed.
+confMIN_FREE_BLOCKS MinFreeBlocks [100] Minimum number of free blocks on
+ queue filesystem to accept SMTP mail.
+ (Prior to 8.7 this was minfree/maxsize,
+ where minfree was the number of free
+ blocks and maxsize was the maximum
+ message size. Use confMAX_MESSAGE_SIZE
+ for the second value now.)
+confMAX_MESSAGE_SIZE MaxMessageSize [infinite] The maximum size of messages
+ that will be accepted (in bytes).
+confBLANK_SUB BlankSub [.] Blank (space) substitution
+ character.
+confCON_EXPENSIVE HoldExpensive [False] Avoid connecting immediately
+ to mailers marked expensive.
+confCHECKPOINT_INTERVAL CheckpointInterval
+ [10] Checkpoint queue files every N
+ recipients.
+confDELIVERY_MODE DeliveryMode [background] Default delivery mode.
+confERROR_MODE ErrorMode [print] Error message mode.
+confERROR_MESSAGE ErrorHeader [undefined] Error message header/file.
+confSAVE_FROM_LINES SaveFromLine Save extra leading From_ lines.
+confTEMP_FILE_MODE TempFileMode [0600] Temporary file mode.
+confMATCH_GECOS MatchGECOS [False] Match GECOS field.
+confMAX_HOP MaxHopCount [25] Maximum hop count.
+confIGNORE_DOTS* IgnoreDots [False; always False in -bs or -bd
+ mode] Ignore dot as terminator for
+ incoming messages?
+confBIND_OPTS ResolverOptions [undefined] Default options for DNS
+ resolver.
+confMIME_FORMAT_ERRORS* SendMimeErrors [True] Send error messages as MIME-
+ encapsulated messages per RFC 1344.
+confFORWARD_PATH ForwardPath [$z/.forward.$w:$z/.forward]
+ The colon-separated list of places to
+ search for .forward files. N.B.: see
+ the Security Notes section.
+confMCI_CACHE_SIZE ConnectionCacheSize
+ [2] Size of open connection cache.
+confMCI_CACHE_TIMEOUT ConnectionCacheTimeout
+ [5m] Open connection cache timeout.
+confHOST_STATUS_DIRECTORY HostStatusDirectory
+ [undefined] If set, host status is kept
+ on disk between sendmail runs in the
+ named directory tree. This need not be
+ a full pathname, in which case it is
+ interpreted relative to the queue
+ directory.
+confSINGLE_THREAD_DELIVERY SingleThreadDelivery
+ [False] If this option and the
+ HostStatusDirectory option are both
+ set, single thread deliveries to other
+ hosts. That is, don't allow any two
+ sendmails on this host to connect
+ simultaneously to any other single
+ host. This can slow down delivery in
+ some cases, in particular since a
+ cached but otherwise idle connection
+ to a host will prevent other sendmails
+ from connecting to the other host.
+confUSE_ERRORS_TO* UseErrorsTo [False] Use the Errors-To: header to
+ deliver error messages. This should
+ not be necessary because of general
+ acceptance of the envelope/header
+ distinction.
+confLOG_LEVEL LogLevel [9] Log level.
+confME_TOO MeToo [True] Include sender in group
+ expansions. This option is
+ deprecated and will be removed from
+ a future version.
+confCHECK_ALIASES CheckAliases [False] Check RHS of aliases when
+ running newaliases. Since this does
+ DNS lookups on every address, it can
+ slow down the alias rebuild process
+ considerably on large alias files.
+confOLD_STYLE_HEADERS* OldStyleHeaders [True] Assume that headers without
+ special chars are old style.
+confPRIVACY_FLAGS PrivacyOptions [authwarnings] Privacy flags.
+confCOPY_ERRORS_TO PostmasterCopy [undefined] Address for additional
+ copies of all error messages.
+confQUEUE_FACTOR QueueFactor [600000] Slope of queue-only function.
+confQUEUE_FILE_MODE QueueFileMode [undefined] Default permissions for
+ queue files (octal). If not set,
+ sendmail uses 0600 unless its real
+ and effective uid are different in
+ which case it uses 0644.
+confDONT_PRUNE_ROUTES DontPruneRoutes [False] Don't prune down route-addr
+ syntax addresses to the minimum
+ possible.
+confSAFE_QUEUE* SuperSafe [True] Commit all messages to disk
+ before forking.
+confTO_INITIAL Timeout.initial [5m] The timeout waiting for a response
+ on the initial connect.
+confTO_CONNECT Timeout.connect [0] The timeout waiting for an initial
+ connect() to complete. This can only
+ shorten connection timeouts; the kernel
+ silently enforces an absolute maximum
+ (which varies depending on the system).
+confTO_ICONNECT Timeout.iconnect
+ [undefined] Like Timeout.connect, but
+ applies only to the very first attempt
+ to connect to a host in a message.
+ This allows a single very fast pass
+ followed by more careful delivery
+ attempts in the future.
+confTO_ACONNECT Timeout.aconnect
+ [0] The overall timeout waiting for
+ all connection for a single delivery
+ attempt to succeed. If 0, no overall
+ limit is applied.
+confTO_HELO Timeout.helo [5m] The timeout waiting for a response
+ to a HELO or EHLO command.
+confTO_MAIL Timeout.mail [10m] The timeout waiting for a
+ response to the MAIL command.
+confTO_RCPT Timeout.rcpt [1h] The timeout waiting for a response
+ to the RCPT command.
+confTO_DATAINIT Timeout.datainit
+ [5m] The timeout waiting for a 354
+ response from the DATA command.
+confTO_DATABLOCK Timeout.datablock
+ [1h] The timeout waiting for a block
+ during DATA phase.
+confTO_DATAFINAL Timeout.datafinal
+ [1h] The timeout waiting for a response
+ to the final "." that terminates a
+ message.
+confTO_RSET Timeout.rset [5m] The timeout waiting for a response
+ to the RSET command.
+confTO_QUIT Timeout.quit [2m] The timeout waiting for a response
+ to the QUIT command.
+confTO_MISC Timeout.misc [2m] The timeout waiting for a response
+ to other SMTP commands.
+confTO_COMMAND Timeout.command [1h] In server SMTP, the timeout
+ waiting for a command to be issued.
+confTO_IDENT Timeout.ident [5s] The timeout waiting for a
+ response to an IDENT query.
+confTO_FILEOPEN Timeout.fileopen
+ [60s] The timeout waiting for a file
+ (e.g., :include: file) to be opened.
+confTO_LHLO Timeout.lhlo [2m] The timeout waiting for a response
+ to an LMTP LHLO command.
+confTO_STARTTLS Timeout.starttls
+ [1h] The timeout waiting for a
+ response to an SMTP STARTTLS command.
+confTO_CONTROL Timeout.control
+ [2m] The timeout for a complete
+ control socket transaction to complete.
+confTO_QUEUERETURN Timeout.queuereturn
+ [5d] The timeout before a message is
+ returned as undeliverable.
+confTO_QUEUERETURN_NORMAL
+ Timeout.queuereturn.normal
+ [undefined] As above, for normal
+ priority messages.
+confTO_QUEUERETURN_URGENT
+ Timeout.queuereturn.urgent
+ [undefined] As above, for urgent
+ priority messages.
+confTO_QUEUERETURN_NONURGENT
+ Timeout.queuereturn.non-urgent
+ [undefined] As above, for non-urgent
+ (low) priority messages.
+confTO_QUEUERETURN_DSN
+ Timeout.queuereturn.dsn
+ [undefined] As above, for delivery
+ status notification messages.
+confTO_QUEUEWARN Timeout.queuewarn
+ [4h] The timeout before a warning
+ message is sent to the sender telling
+ them that the message has been
+ deferred.
+confTO_QUEUEWARN_NORMAL Timeout.queuewarn.normal
+ [undefined] As above, for normal
+ priority messages.
+confTO_QUEUEWARN_URGENT Timeout.queuewarn.urgent
+ [undefined] As above, for urgent
+ priority messages.
+confTO_QUEUEWARN_NONURGENT
+ Timeout.queuewarn.non-urgent
+ [undefined] As above, for non-urgent
+ (low) priority messages.
+confTO_QUEUEWARN_DSN
+ Timeout.queuewarn.dsn
+ [undefined] As above, for delivery
+ status notification messages.
+confTO_HOSTSTATUS Timeout.hoststatus
+ [30m] How long information about host
+ statuses will be maintained before it
+ is considered stale and the host should
+ be retried. This applies both within
+ a single queue run and to persistent
+ information (see below).
+confTO_RESOLVER_RETRANS Timeout.resolver.retrans
+ [varies] Sets the resolver's
+ retransmission time interval (in
+ seconds). Sets both
+ Timeout.resolver.retrans.first and
+ Timeout.resolver.retrans.normal.
+confTO_RESOLVER_RETRANS_FIRST Timeout.resolver.retrans.first
+ [varies] Sets the resolver's
+ retransmission time interval (in
+ seconds) for the first attempt to
+ deliver a message.
+confTO_RESOLVER_RETRANS_NORMAL Timeout.resolver.retrans.normal
+ [varies] Sets the resolver's
+ retransmission time interval (in
+ seconds) for all resolver lookups
+ except the first delivery attempt.
+confTO_RESOLVER_RETRY Timeout.resolver.retry
+ [varies] Sets the number of times
+ to retransmit a resolver query.
+ Sets both
+ Timeout.resolver.retry.first and
+ Timeout.resolver.retry.normal.
+confTO_RESOLVER_RETRY_FIRST Timeout.resolver.retry.first
+ [varies] Sets the number of times
+ to retransmit a resolver query for
+ the first attempt to deliver a
+ message.
+confTO_RESOLVER_RETRY_NORMAL Timeout.resolver.retry.normal
+ [varies] Sets the number of times
+ to retransmit a resolver query for
+ all resolver lookups except the
+ first delivery attempt.
+confTIME_ZONE TimeZoneSpec [USE_SYSTEM] Time zone info -- can be
+ USE_SYSTEM to use the system's idea,
+ USE_TZ to use the user's TZ envariable,
+ or something else to force that value.
+confDEF_USER_ID DefaultUser [1:1] Default user id.
+confUSERDB_SPEC UserDatabaseSpec
+ [undefined] User database
+ specification.
+confFALLBACK_MX FallbackMXhost [undefined] Fallback MX host.
+confFALLBACK_SMARTHOST FallbackSmartHost
+ [undefined] Fallback smart host.
+confTRY_NULL_MX_LIST TryNullMXList [False] If this host is the best MX
+ for a host and other arrangements
+ haven't been made, try connecting
+ to the host directly; normally this
+ would be a config error.
+confQUEUE_LA QueueLA [varies] Load average at which
+ queue-only function kicks in.
+ Default values is (8 * numproc)
+ where numproc is the number of
+ processors online (if that can be
+ determined).
+confREFUSE_LA RefuseLA [varies] Load average at which
+ incoming SMTP connections are
+ refused. Default values is (12 *
+ numproc) where numproc is the
+ number of processors online (if
+ that can be determined).
+confREJECT_LOG_INTERVAL RejectLogInterval [3h] Log interval when
+ refusing connections for this long.
+confDELAY_LA DelayLA [0] Load average at which sendmail
+ will sleep for one second on most
+ SMTP commands and before accepting
+ connections. 0 means no limit.
+confMAX_ALIAS_RECURSION MaxAliasRecursion
+ [10] Maximum depth of alias recursion.
+confMAX_DAEMON_CHILDREN MaxDaemonChildren
+ [undefined] The maximum number of
+ children the daemon will permit. After
+ this number, connections will be
+ rejected. If not set or <= 0, there is
+ no limit.
+confMAX_HEADERS_LENGTH MaxHeadersLength
+ [32768] Maximum length of the sum
+ of all headers.
+confMAX_MIME_HEADER_LENGTH MaxMimeHeaderLength
+ [undefined] Maximum length of
+ certain MIME header field values.
+confCONNECTION_RATE_THROTTLE ConnectionRateThrottle
+ [undefined] The maximum number of
+ connections permitted per second per
+ daemon. After this many connections
+ are accepted, further connections
+ will be delayed. If not set or <= 0,
+ there is no limit.
+confCONNECTION_RATE_WINDOW_SIZE ConnectionRateWindowSize
+ [60s] Define the length of the
+ interval for which the number of
+ incoming connections is maintained.
+confWORK_RECIPIENT_FACTOR
+ RecipientFactor [30000] Cost of each recipient.
+confSEPARATE_PROC ForkEachJob [False] Run all deliveries in a
+ separate process.
+confWORK_CLASS_FACTOR ClassFactor [1800] Priority multiplier for class.
+confWORK_TIME_FACTOR RetryFactor [90000] Cost of each delivery attempt.
+confQUEUE_SORT_ORDER QueueSortOrder [Priority] Queue sort algorithm:
+ Priority, Host, Filename, Random,
+ Modification, or Time.
+confMIN_QUEUE_AGE MinQueueAge [0] The minimum amount of time a job
+ must sit in the queue between queue
+ runs. This allows you to set the
+ queue run interval low for better
+ responsiveness without trying all
+ jobs in each run.
+confDEF_CHAR_SET DefaultCharSet [unknown-8bit] When converting
+ unlabeled 8 bit input to MIME, the
+ character set to use by default.
+confSERVICE_SWITCH_FILE ServiceSwitchFile
+ [/etc/mail/service.switch] The file
+ to use for the service switch on
+ systems that do not have a
+ system-defined switch.
+confHOSTS_FILE HostsFile [/etc/hosts] The file to use when doing
+ "file" type access of hosts names.
+confDIAL_DELAY DialDelay [0s] If a connection fails, wait this
+ long and try again. Zero means "don't
+ retry". This is to allow "dial on
+ demand" connections to have enough time
+ to complete a connection.
+confNO_RCPT_ACTION NoRecipientAction
+ [none] What to do if there are no legal
+ recipient fields (To:, Cc: or Bcc:)
+ in the message. Legal values can
+ be "none" to just leave the
+ nonconforming message as is, "add-to"
+ to add a To: header with all the
+ known recipients (which may expose
+ blind recipients), "add-apparently-to"
+ to do the same but use Apparently-To:
+ instead of To: (strongly discouraged
+ in accordance with IETF standards),
+ "add-bcc" to add an empty Bcc:
+ header, or "add-to-undisclosed" to
+ add the header
+ ``To: undisclosed-recipients:;''.
+confSAFE_FILE_ENV SafeFileEnvironment
+ [undefined] If set, sendmail will do a
+ chroot() into this directory before
+ writing files.
+confCOLON_OK_IN_ADDR ColonOkInAddr [True unless Configuration Level > 6]
+ If set, colons are treated as a regular
+ character in addresses. If not set,
+ they are treated as the introducer to
+ the RFC 822 "group" syntax. Colons are
+ handled properly in route-addrs. This
+ option defaults on for V5 and lower
+ configuration files.
+confMAX_QUEUE_RUN_SIZE MaxQueueRunSize [0] If set, limit the maximum size of
+ any given queue run to this number of
+ entries. Essentially, this will stop
+ reading each queue directory after this
+ number of entries are reached; it does
+ _not_ pick the highest priority jobs,
+ so this should be as large as your
+ system can tolerate. If not set, there
+ is no limit.
+confMAX_QUEUE_CHILDREN MaxQueueChildren
+ [undefined] Limits the maximum number
+ of concurrent queue runners active.
+ This is to keep system resources used
+ within a reasonable limit. Relates to
+ Queue Groups and ForkEachJob.
+confMAX_RUNNERS_PER_QUEUE MaxRunnersPerQueue
+ [1] Only active when MaxQueueChildren
+ defined. Controls the maximum number
+ of queue runners (aka queue children)
+ active at the same time in a work
+ group. See also MaxQueueChildren.
+confDONT_EXPAND_CNAMES DontExpandCnames
+ [False] If set, $[ ... $] lookups that
+ do DNS based lookups do not expand
+ CNAME records. This currently violates
+ the published standards, but the IETF
+ seems to be moving toward legalizing
+ this. For example, if "FTP.Foo.ORG"
+ is a CNAME for "Cruft.Foo.ORG", then
+ with this option set a lookup of
+ "FTP" will return "FTP.Foo.ORG"; if
+ clear it returns "Cruft.FOO.ORG". N.B.
+ you may not see any effect until your
+ downstream neighbors stop doing CNAME
+ lookups as well.
+confFROM_LINE UnixFromLine [From $g $d] The From_ line used
+ when sending to files or programs.
+confSINGLE_LINE_FROM_HEADER SingleLineFromHeader
+ [False] From: lines that have
+ embedded newlines are unwrapped
+ onto one line.
+confALLOW_BOGUS_HELO AllowBogusHELO [False] Allow HELO SMTP command that
+ does not include a host name.
+confMUST_QUOTE_CHARS MustQuoteChars [.'] Characters to be quoted in a full
+ name phrase (@,;:\()[] are automatic).
+confOPERATORS OperatorChars [.:%@!^/[]+] Address operator
+ characters.
+confSMTP_LOGIN_MSG SmtpGreetingMessage
+ [$j Sendmail $v/$Z; $b]
+ The initial (spontaneous) SMTP
+ greeting message. The word "ESMTP"
+ will be inserted between the first and
+ second words to convince other
+ sendmails to try to speak ESMTP.
+confDONT_INIT_GROUPS DontInitGroups [False] If set, the initgroups(3)
+ routine will never be invoked. You
+ might want to do this if you are
+ running NIS and you have a large group
+ map, since this call does a sequential
+ scan of the map; in a large site this
+ can cause your ypserv to run
+ essentially full time. If you set
+ this, agents run on behalf of users
+ will only have their primary
+ (/etc/passwd) group permissions.
+confUNSAFE_GROUP_WRITES UnsafeGroupWrites
+ [False] If set, group-writable
+ :include: and .forward files are
+ considered "unsafe", that is, programs
+ and files cannot be directly referenced
+ from such files. World-writable files
+ are always considered unsafe.
+confCONNECT_ONLY_TO ConnectOnlyTo [undefined] override connection
+ address (for testing).
+confCONTROL_SOCKET_NAME ControlSocketName
+ [undefined] Control socket for daemon
+ management.
+confDOUBLE_BOUNCE_ADDRESS DoubleBounceAddress
+ [postmaster] If an error occurs when
+ sending an error message, send that
+ "double bounce" error message to this
+ address. If it expands to an empty
+ string, double bounces are dropped.
+confDEAD_LETTER_DROP DeadLetterDrop [undefined] Filename to save bounce
+ messages which could not be returned
+ to the user or sent to postmaster.
+ If not set, the queue file will
+ be renamed.
+confRRT_IMPLIES_DSN RrtImpliesDsn [False] Return-Receipt-To: header
+ implies DSN request.
+confRUN_AS_USER RunAsUser [undefined] If set, become this user
+ when reading and delivering mail.
+ Causes all file reads (e.g., .forward
+ and :include: files) to be done as
+ this user. Also, all programs will
+ be run as this user, and all output
+ files will be written as this user.
+confMAX_RCPTS_PER_MESSAGE MaxRecipientsPerMessage
+ [infinite] If set, allow no more than
+ the specified number of recipients in
+ an SMTP envelope. Further recipients
+ receive a 452 error code (i.e., they
+ are deferred for the next delivery
+ attempt).
+confBAD_RCPT_THROTTLE BadRcptThrottle [infinite] If set and the specified
+ number of recipients in a single SMTP
+ transaction have been rejected, sleep
+ for one second after each subsequent
+ RCPT command in that transaction.
+confDONT_PROBE_INTERFACES DontProbeInterfaces
+ [False] If set, sendmail will _not_
+ insert the names and addresses of any
+ local interfaces into class {w}
+ (list of known "equivalent" addresses).
+ If you set this, you must also include
+ some support for these addresses (e.g.,
+ in a mailertable entry) -- otherwise,
+ mail to addresses in this list will
+ bounce with a configuration error.
+ If set to "loopback" (without
+ quotes), sendmail will skip
+ loopback interfaces (e.g., "lo0").
+confPID_FILE PidFile [system dependent] Location of pid
+ file.
+confPROCESS_TITLE_PREFIX ProcessTitlePrefix
+ [undefined] Prefix string for the
+ process title shown on 'ps' listings.
+confDONT_BLAME_SENDMAIL DontBlameSendmail
+ [safe] Override sendmail's file
+ safety checks. This will definitely
+ compromise system security and should
+ not be used unless absolutely
+ necessary.
+confREJECT_MSG - [550 Access denied] The message
+ given if the access database contains
+ REJECT in the value portion.
+confRELAY_MSG - [550 Relaying denied] The message
+ given if an unauthorized relaying
+ attempt is rejected.
+confDF_BUFFER_SIZE DataFileBufferSize
+ [4096] The maximum size of a
+ memory-buffered data (df) file
+ before a disk-based file is used.
+confXF_BUFFER_SIZE XScriptFileBufferSize
+ [4096] The maximum size of a
+ memory-buffered transcript (xf)
+ file before a disk-based file is
+ used.
+confTLS_SRV_OPTIONS TLSSrvOptions If this option is 'V' no client
+ verification is performed, i.e.,
+ the server doesn't ask for a
+ certificate.
+confLDAP_DEFAULT_SPEC LDAPDefaultSpec [undefined] Default map
+ specification for LDAP maps. The
+ value should only contain LDAP
+ specific settings such as "-h host
+ -p port -d bindDN", etc. The
+ settings will be used for all LDAP
+ maps unless they are specified in
+ the individual map specification
+ ('K' command).
+confCACERT_PATH CACertPath [undefined] Path to directory
+ with certs of CAs.
+confCACERT CACertFile [undefined] File containing one CA
+ cert.
+confSERVER_CERT ServerCertFile [undefined] File containing the
+ cert of the server, i.e., this cert
+ is used when sendmail acts as
+ server.
+confSERVER_KEY ServerKeyFile [undefined] File containing the
+ private key belonging to the server
+ cert.
+confCLIENT_CERT ClientCertFile [undefined] File containing the
+ cert of the client, i.e., this cert
+ is used when sendmail acts as
+ client.
+confCLIENT_KEY ClientKeyFile [undefined] File containing the
+ private key belonging to the client
+ cert.
+confCRL CRLFile [undefined] File containing certificate
+ revocation status, useful for X.509v3
+ authentication. Note that CRL requires
+ at least OpenSSL version 0.9.7.
+confDH_PARAMETERS DHParameters [undefined] File containing the
+ DH parameters.
+confRAND_FILE RandFile [undefined] File containing random
+ data (use prefix file:) or the
+ name of the UNIX socket if EGD is
+ used (use prefix egd:). STARTTLS
+ requires this option if the compile
+ flag HASURANDOM is not set (see
+ sendmail/README).
+confNICE_QUEUE_RUN NiceQueueRun [undefined] If set, the priority of
+ queue runners is set the given value
+ (nice(3)).
+confDIRECT_SUBMISSION_MODIFIERS DirectSubmissionModifiers
+ [undefined] Defines {daemon_flags}
+ for direct submissions.
+confUSE_MSP UseMSP [false] Use as mail submission
+ program.
+confDELIVER_BY_MIN DeliverByMin [0] Minimum time for Deliver By
+ SMTP Service Extension (RFC 2852).
+confREQUIRES_DIR_FSYNC RequiresDirfsync [true] RequiresDirfsync can
+ be used to turn off the compile time
+ flag REQUIRES_DIR_FSYNC at runtime.
+ See sendmail/README for details.
+confSHARED_MEMORY_KEY SharedMemoryKey [0] Key for shared memory.
+confFAST_SPLIT FastSplit [1] If set to a value greater than
+ zero, the initial MX lookups on
+ addresses is suppressed when they
+ are sorted which may result in
+ faster envelope splitting. If the
+ mail is submitted directly from the
+ command line, then the value also
+ limits the number of processes to
+ deliver the envelopes.
+confMAILBOX_DATABASE MailboxDatabase [pw] Type of lookup to find
+ information about local mailboxes.
+confDEQUOTE_OPTS - [empty] Additional options for the
+ dequote map.
+confINPUT_MAIL_FILTERS InputMailFilters
+ A comma separated list of filters
+ which determines which filters and
+ the invocation sequence are
+ contacted for incoming SMTP
+ messages. If none are set, no
+ filters will be contacted.
+confMILTER_LOG_LEVEL Milter.LogLevel [9] Log level for input mail filter
+ actions, defaults to LogLevel.
+confMILTER_MACROS_CONNECT Milter.macros.connect
+ [j, _, {daemon_name}, {if_name},
+ {if_addr}] Macros to transmit to
+ milters when a session connection
+ starts.
+confMILTER_MACROS_HELO Milter.macros.helo
+ [{tls_version}, {cipher},
+ {cipher_bits}, {cert_subject},
+ {cert_issuer}] Macros to transmit to
+ milters after HELO/EHLO command.
+confMILTER_MACROS_ENVFROM Milter.macros.envfrom
+ [i, {auth_type}, {auth_authen},
+ {auth_ssf}, {auth_author},
+ {mail_mailer}, {mail_host},
+ {mail_addr}] Macros to transmit to
+ milters after MAIL FROM command.
+confMILTER_MACROS_ENVRCPT Milter.macros.envrcpt
+ [{rcpt_mailer}, {rcpt_host},
+ {rcpt_addr}] Macros to transmit to
+ milters after RCPT TO command.
+confMILTER_MACROS_EOM Milter.macros.eom
+ [{msg_id}] Macros to transmit to
+ milters after DATA command.
+
+
+See also the description of OSTYPE for some parameters that can be
+tweaked (generally pathnames to mailers).
+
+ClientPortOptions and DaemonPortOptions are special cases since multiple
+clients/daemons can be defined. This can be done via
+
+ CLIENT_OPTIONS(`field1=value1,field2=value2,...')
+ DAEMON_OPTIONS(`field1=value1,field2=value2,...')
+
+Note that multiple CLIENT_OPTIONS() commands (and therefore multiple
+ClientPortOptions settings) are allowed in order to give settings for each
+protocol family (e.g., one for Family=inet and one for Family=inet6). A
+restriction placed on one family only affects outgoing connections on that
+particular family.
+
+If DAEMON_OPTIONS is not used, then the default is
+
+ DAEMON_OPTIONS(`Port=smtp, Name=MTA')
+ DAEMON_OPTIONS(`Port=587, Name=MSA, M=E')
+
+If you use one DAEMON_OPTIONS macro, it will alter the parameters
+of the first of these. The second will still be defaulted; it
+represents a "Message Submission Agent" (MSA) as defined by RFC
+2476 (see below). To turn off the default definition for the MSA,
+use FEATURE(`no_default_msa') (see also FEATURES). If you use
+additional DAEMON_OPTIONS macros, they will add additional daemons.
+
+Example 1: To change the port for the SMTP listener, while
+still using the MSA default, use
+ DAEMON_OPTIONS(`Port=925, Name=MTA')
+
+Example 2: To change the port for the MSA daemon, while still
+using the default SMTP port, use
+ FEATURE(`no_default_msa')
+ DAEMON_OPTIONS(`Name=MTA')
+ DAEMON_OPTIONS(`Port=987, Name=MSA, M=E')
+
+Note that if the first of those DAEMON_OPTIONS lines were omitted, then
+there would be no listener on the standard SMTP port.
+
+Example 3: To listen on both IPv4 and IPv6 interfaces, use
+
+ DAEMON_OPTIONS(`Name=MTA-v4, Family=inet')
+ DAEMON_OPTIONS(`Name=MTA-v6, Family=inet6')
+
+A "Message Submission Agent" still uses all of the same rulesets for
+processing the message (and therefore still allows message rejection via
+the check_* rulesets). In accordance with the RFC, the MSA will ensure
+that all domains in envelope addresses are fully qualified if the message
+is relayed to another MTA. It will also enforce the normal address syntax
+rules and log error messages. Additionally, by using the M=a modifier you
+can require authentication before messages are accepted by the MSA.
+Notice: Do NOT use the 'a' modifier on a public accessible MTA! Finally,
+the M=E modifier shown above disables ETRN as required by RFC 2476.
+
+Mail filters can be defined using the INPUT_MAIL_FILTER() and MAIL_FILTER()
+commands:
+
+ INPUT_MAIL_FILTER(`sample', `S=local:/var/run/f1.sock')
+ MAIL_FILTER(`myfilter', `S=inet:3333@localhost')
+
+The INPUT_MAIL_FILTER() command causes the filter(s) to be called in the
+same order they were specified by also setting confINPUT_MAIL_FILTERS. A
+filter can be defined without adding it to the input filter list by using
+MAIL_FILTER() instead of INPUT_MAIL_FILTER() in your .mc file.
+Alternatively, you can reset the list of filters and their order by setting
+confINPUT_MAIL_FILTERS option after all INPUT_MAIL_FILTER() commands in
+your .mc file.
+
+
++----------------------------+
+| MESSAGE SUBMISSION PROGRAM |
++----------------------------+
+
+This section contains a list of caveats and
+a few hints how for those who want to tweak the default configuration
+for it (which is installed as submit.cf).
+
+Notice: do not add options/features to submit.mc unless you are
+absolutely sure you need them. Options you may want to change
+include:
+
+- confTRUSTED_USERS, FEATURE(`use_ct_file'), and confCT_FILE for
+ avoiding X-Authentication warnings.
+- confTIME_ZONE to change it from the default `USE_TZ'.
+- confDELIVERY_MODE is set to interactive in msp.m4 instead
+ of the default background mode.
+- FEATURE(stickyhost) and LOCAL_RELAY to send unqualified addresses
+ to the LOCAL_RELAY instead of the default relay.
+
+The MSP performs hostname canonicalization by default. Mail may end
+up for various DNS related reasons in the MSP queue. This problem
+can be minimized by using
+
+ FEATURE(`nocanonify', `canonify_hosts')
+ define(`confDIRECT_SUBMISSION_MODIFIERS', `C')
+
+See the discussion about nocanonify for possible side effects.
+
+Some things are not intended to work with the MSP. These include
+features that influence the delivery process (e.g., mailertable,
+aliases), or those that are only important for a SMTP server (e.g.,
+virtusertable, DaemonPortOptions, multiple queues). Moreover,
+relaxing certain restrictions (RestrictQueueRun, permissions on
+queue directory) or adding features (e.g., enabling prog/file mailer)
+can cause security problems.
+
+Other things don't work well with the MSP and require tweaking or
+workarounds.
+
+The file and the map created by makemap should be owned by smmsp,
+its group should be smmsp, and it should have mode 640.
+
+feature/msp.m4 defines almost all settings for the MSP. Most of
+those should not be changed at all. Some of the features and options
+can be overridden if really necessary. It is a bit tricky to do
+this, because it depends on the actual way the option is defined
+in feature/msp.m4. If it is directly defined (i.e., define()) then
+the modified value must be defined after
+
+ FEATURE(`msp')
+
+If it is conditionally defined (i.e., ifdef()) then the desired
+value must be defined before the FEATURE line in the .mc file.
+To see how the options are defined read feature/msp.m4.
+
+
++--------------------------+
+| FORMAT OF FILES AND MAPS |
++--------------------------+
+
+Files that define classes, i.e., F{classname}, consist of lines
+each of which contains a single element of the class. For example,
+/etc/mail/local-host-names may have the following content:
+
+my.domain
+another.domain
+
+Maps must be created using makemap(8) , e.g.,
+
+ makemap hash MAP < MAP
+
+In general, a text file from which a map is created contains lines
+of the form
+
+key value
+
+where 'key' and 'value' are also called LHS and RHS, respectively.
+By default, the delimiter between LHS and RHS is a non-empty sequence
+of white space characters.
+
+
++------------------+
+| DIRECTORY LAYOUT |
++------------------+
+
+Within this directory are several subdirectories, to wit:
+
+m4 General support routines. These are typically
+ very important and should not be changed without
+ very careful consideration.
+
+cf The configuration files themselves. They have
+ ".mc" suffixes, and must be run through m4 to
+ become complete. The resulting output should
+ have a ".cf" suffix.
+
+ostype Definitions describing a particular operating
+ system type. These should always be referenced
+ using the OSTYPE macro in the .mc file. Examples
+ include "bsd4.3", "bsd4.4", "sunos3.5", and
+ "sunos4.1".
+
+domain Definitions describing a particular domain, referenced
+ using the DOMAIN macro in the .mc file. These are
+ site dependent; for example, "CS.Berkeley.EDU.m4"
+ describes hosts in the CS.Berkeley.EDU subdomain.
+
+mailer Descriptions of mailers. These are referenced using
+ the MAILER macro in the .mc file.
+
+sh Shell files used when building the .cf file from the
+ .mc file in the cf subdirectory.
+
+feature These hold special orthogonal features that you might
+ want to include. They should be referenced using
+ the FEATURE macro.
+
+hack Local hacks. These can be referenced using the HACK
+ macro. They shouldn't be of more than voyeuristic
+ interest outside the .Berkeley.EDU domain, but who knows?
+
+siteconfig Site configuration -- e.g., tables of locally connected
+ UUCP sites.
+
+
++------------------------+
+| ADMINISTRATIVE DETAILS |
++------------------------+
+
+The following sections detail usage of certain internal parts of the
+sendmail.cf file. Read them carefully if you are trying to modify
+the current model. If you find the above descriptions adequate, these
+should be {boring, confusing, tedious, ridiculous} (pick one or more).
+
+RULESETS (* means built in to sendmail)
+
+ 0 * Parsing
+ 1 * Sender rewriting
+ 2 * Recipient rewriting
+ 3 * Canonicalization
+ 4 * Post cleanup
+ 5 * Local address rewrite (after aliasing)
+ 1x mailer rules (sender qualification)
+ 2x mailer rules (recipient qualification)
+ 3x mailer rules (sender header qualification)
+ 4x mailer rules (recipient header qualification)
+ 5x mailer subroutines (general)
+ 6x mailer subroutines (general)
+ 7x mailer subroutines (general)
+ 8x reserved
+ 90 Mailertable host stripping
+ 96 Bottom half of Ruleset 3 (ruleset 6 in old sendmail)
+ 97 Hook for recursive ruleset 0 call (ruleset 7 in old sendmail)
+ 98 Local part of ruleset 0 (ruleset 8 in old sendmail)
+
+
+MAILERS
+
+ 0 local, prog local and program mailers
+ 1 [e]smtp, relay SMTP channel
+ 2 uucp-* UNIX-to-UNIX Copy Program
+ 3 netnews Network News delivery
+ 4 fax Sam Leffler's HylaFAX software
+ 5 mail11 DECnet mailer
+
+
+MACROS
+
+ A
+ B Bitnet Relay
+ C DECnet Relay
+ D The local domain -- usually not needed
+ E reserved for X.400 Relay
+ F FAX Relay
+ G
+ H mail Hub (for mail clusters)
+ I
+ J
+ K
+ L Luser Relay
+ M Masquerade (who you claim to be)
+ N
+ O
+ P
+ Q
+ R Relay (for unqualified names)
+ S Smart Host
+ T
+ U my UUCP name (if you have a UUCP connection)
+ V UUCP Relay (class {V} hosts)
+ W UUCP Relay (class {W} hosts)
+ X UUCP Relay (class {X} hosts)
+ Y UUCP Relay (all other hosts)
+ Z Version number
+
+
+CLASSES
+
+ A
+ B domains that are candidates for bestmx lookup
+ C
+ D
+ E addresses that should not seem to come from $M
+ F hosts this system forward for
+ G domains that should be looked up in genericstable
+ H
+ I
+ J
+ K
+ L addresses that should not be forwarded to $R
+ M domains that should be mapped to $M
+ N host/domains that should not be mapped to $M
+ O operators that indicate network operations (cannot be in local names)
+ P top level pseudo-domains: BITNET, DECNET, FAX, UUCP, etc.
+ Q
+ R domains this system is willing to relay (pass anti-spam filters)
+ S
+ T
+ U locally connected UUCP hosts
+ V UUCP hosts connected to relay $V
+ W UUCP hosts connected to relay $W
+ X UUCP hosts connected to relay $X
+ Y locally connected smart UUCP hosts
+ Z locally connected domain-ized UUCP hosts
+ . the class containing only a dot
+ [ the class containing only a left bracket
+
+
+M4 DIVERSIONS
+
+ 1 Local host detection and resolution
+ 2 Local Ruleset 3 additions
+ 3 Local Ruleset 0 additions
+ 4 UUCP Ruleset 0 additions
+ 5 locally interpreted names (overrides $R)
+ 6 local configuration (at top of file)
+ 7 mailer definitions
+ 8 DNS based blacklists
+ 9 special local rulesets (1 and 2)
+
+$Revision: 8.694 $, Last updated $Date: 2005/03/23 21:41:09 $
+ident "%Z%%M% %I% %E% SMI"
diff --git a/usr/src/cmd/sendmail/cf/cf/Makefile b/usr/src/cmd/sendmail/cf/cf/Makefile
new file mode 100644
index 0000000000..575f8b760c
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/cf/Makefile
@@ -0,0 +1,129 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License, Version 1.0 only
+# (the "License"). You may not use this file except in compliance
+# with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+#
+# Makefile for configuration files.
+#
+# Copyright 2004 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# @(#)Makefile 8.15 (Berkeley) 3/29/98
+# %W% (Sun) %G%
+# ident "%Z%%M% %I% %E% SMI"
+#
+
+#
+# Create configuration files using "m4 ../m4/cf.m4 file.mc > file.cf"
+# of "/usr/ccs/bin/make file.cf"; these may be easier than tweaking the
+# Makefile. Also, updates will over-write any changes to the Makefile.
+# You do need to have a fairly modern M4 available (GNU m4 works). On
+# Solaris, use /usr/ccs/bin/m4.
+#
+
+M4= /usr/ccs/bin/m4
+CFDIR= ..
+MV= /usr/bin/mv
+RM= /usr/bin/rm -f
+
+.SUFFIXES: .mc .cf
+
+.mc.cf:
+ test ! -f $@ || $(MV) $@ $@.prev
+ $(M4) ${CFDIR}/m4/cf.m4 $*.mc > $@
+
+ALL= main.cf subsidiary.cf submit.cf
+
+all: $(ALL)
+
+clean cleandir:
+ $(RM) $(ALL) core
+
+depend install:
+
+# this is overkill, but....
+M4FILES=\
+ ${CFDIR}/domain/generic.m4 \
+ ${CFDIR}/domain/solaris-antispam.m4 \
+ ${CFDIR}/domain/solaris-generic.m4 \
+ ${CFDIR}/feature/accept_unqualified_senders.m4 \
+ ${CFDIR}/feature/accept_unresolvable_domains.m4 \
+ ${CFDIR}/feature/access_db.m4 \
+ ${CFDIR}/feature/allmasquerade.m4 \
+ ${CFDIR}/feature/always_add_domain.m4 \
+ ${CFDIR}/feature/bestmx_is_local.m4 \
+ ${CFDIR}/feature/bitdomain.m4 \
+ ${CFDIR}/feature/blacklist_recipients.m4 \
+ ${CFDIR}/feature/compat_check.m4 \
+ ${CFDIR}/feature/conncontrol.m4 \
+ ${CFDIR}/feature/delay_checks.m4 \
+ ${CFDIR}/feature/dnsbl.m4 \
+ ${CFDIR}/feature/domaintable.m4 \
+ ${CFDIR}/feature/enhdnsbl.m4 \
+ ${CFDIR}/feature/generics_entire_domain.m4 \
+ ${CFDIR}/feature/genericstable.m4 \
+ ${CFDIR}/feature/greet_pause.m4 \
+ ${CFDIR}/feature/ldap_routing.m4 \
+ ${CFDIR}/feature/limited_masquerade.m4 \
+ ${CFDIR}/feature/local_lmtp.m4 \
+ ${CFDIR}/feature/local_no_masquerade.m4 \
+ ${CFDIR}/feature/lookupdotdomain.m4 \
+ ${CFDIR}/feature/loose_relay_check.m4 \
+ ${CFDIR}/feature/mailertable.m4 \
+ ${CFDIR}/feature/masquerade_entire_domain.m4 \
+ ${CFDIR}/feature/masquerade_envelope.m4 \
+ ${CFDIR}/feature/msp.m4 \
+ ${CFDIR}/feature/mtamark.m4 \
+ ${CFDIR}/feature/no_default_msa.m4 \
+ ${CFDIR}/feature/nocanonify.m4 \
+ ${CFDIR}/feature/notsticky.m4 \
+ ${CFDIR}/feature/nouucp.m4 \
+ ${CFDIR}/feature/promiscuous_relay.m4 \
+ ${CFDIR}/feature/preserve_local_plus_detail.m4 \
+ ${CFDIR}/feature/preserve_luser_host.m4 \
+ ${CFDIR}/feature/queuegroup.m4 \
+ ${CFDIR}/feature/ratecontrol.m4 \
+ ${CFDIR}/feature/redirect.m4 \
+ ${CFDIR}/feature/relay_based_on_MX.m4 \
+ ${CFDIR}/feature/relay_entire_domain.m4 \
+ ${CFDIR}/feature/relay_hosts_only.m4 \
+ ${CFDIR}/feature/relay_local_from.m4 \
+ ${CFDIR}/feature/relay_mail_from.m4 \
+ ${CFDIR}/feature/smrsh.m4 \
+ ${CFDIR}/feature/stickyhost.m4 \
+ ${CFDIR}/feature/use_client_ptr.m4 \
+ ${CFDIR}/feature/use_ct_file.m4 \
+ ${CFDIR}/feature/use_cw_file.m4 \
+ ${CFDIR}/feature/uucpdomain.m4 \
+ ${CFDIR}/feature/virtuser_entire_domain.m4 \
+ ${CFDIR}/feature/virtusertable.m4 \
+ ${CFDIR}/m4/cf.m4 \
+ ${CFDIR}/m4/cfhead.m4 \
+ ${CFDIR}/m4/proto.m4 \
+ ${CFDIR}/m4/version.m4 \
+ ${CFDIR}/mailer/local.m4 \
+ ${CFDIR}/mailer/smtp.m4 \
+ ${CFDIR}/mailer/uucp.m4 \
+ ${CFDIR}/ostype/solaris2.m4 \
+ ${CFDIR}/ostype/solaris2.ml.m4 \
+ ${CFDIR}/ostype/solaris2.pre5.m4 \
+ ${CFDIR}/ostype/solaris8.m4
+
+$(ALL): $(M4FILES)
diff --git a/usr/src/cmd/sendmail/cf/cf/sendmail.mc b/usr/src/cmd/sendmail/cf/cf/sendmail.mc
new file mode 100644
index 0000000000..d912c665a1
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/cf/sendmail.mc
@@ -0,0 +1,33 @@
+divert(-1)
+#
+# Copyright (c) 1983 Eric P. Allman
+# Copyright (c) 1988, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# Copyright 2004 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# ident "%Z%%M% %I% %E% SMI"
+#
+# This is a configuration file for SunOS 5.8 (a.k.a. Solaris 8) and later
+# subsidiary machines. It has support for local and SMTP mail. The
+# confFALLBACK_SMARTHOST macro is enabled, which means that messages will
+# be sent to that host (which is set to mailhost.$m [$m is the local domain])
+# if MX records are unavailable. A short-cut rule is also defined, which
+# says if the recipient host is in the local domain, send to it directly
+# instead of the smart host.
+#
+# If you want to customize this further, copy it to a name appropriate
+# for your environment and do the modifications there.
+#
+
+divert(0)dnl
+VERSIONID(`%W% (Sun) %G%')
+OSTYPE(`solaris8')dnl
+DOMAIN(`solaris-generic')dnl
+define(`confFALLBACK_SMARTHOST', `mailhost$?m.$m$.')dnl
+MAILER(`local')dnl
+MAILER(`smtp')dnl
+
+LOCAL_NET_CONFIG
+R$* < @ $* .$m. > $* $#esmtp $@ $2.$m $: $1 < @ $2.$m. > $3
diff --git a/usr/src/cmd/sendmail/cf/cf/submit.mc b/usr/src/cmd/sendmail/cf/cf/submit.mc
new file mode 100644
index 0000000000..b1efb216d9
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/cf/submit.mc
@@ -0,0 +1,27 @@
+divert(-1)
+#
+# Copyright (c) 2001, 2002 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+#ident "%Z%%M% %I% %E% SMI"
+#
+
+#
+# This is the prototype file for a set-group-ID sm-msp sendmail that
+# acts as a initial mail submission program.
+#
+
+divert(0)dnl
+VERSIONID(`$Id: submit.mc,v 8.6.2.4 2002/12/29 03:54:34 ca Exp $')
+define(`confCF_VERSION', `Submit')dnl
+define(`__OSTYPE__',`')dnl dirty hack to keep proto.m4 from complaining
+define(`_USE_DECNET_SYNTAX_', `1')dnl support DECnet
+define(`confTIME_ZONE', `USE_TZ')dnl
+define(`confDONT_INIT_GROUPS', `True')dnl
+dnl
+dnl If you use IPv6 only, change [127.0.0.1] to [IPv6:::1]
+FEATURE(`msp', `[127.0.0.1]')dnl
diff --git a/usr/src/cmd/sendmail/cf/domain/generic.m4 b/usr/src/cmd/sendmail/cf/domain/generic.m4
new file mode 100644
index 0000000000..caa5a8845f
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/domain/generic.m4
@@ -0,0 +1,28 @@
+divert(-1)
+#
+# Copyright (c) 1998, 1999 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+# Copyright (c) 1983 Eric P. Allman. All rights reserved.
+# Copyright (c) 1988, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+#
+
+#
+# The following is a generic domain file. You should be able to
+# use it anywhere. If you want to customize it, copy it to a file
+# named with your domain and make the edits; then, copy the appropriate
+# .mc files and change `DOMAIN(generic)' to reference your updated domain
+# files.
+#
+divert(0)
+VERSIONID(`$Id: generic.m4,v 8.15 1999/04/04 00:51:09 ca Exp $')
+define(`confFORWARD_PATH', `$z/.forward.$w+$h:$z/.forward+$h:$z/.forward.$w:$z/.forward')dnl
+define(`confMAX_HEADERS_LENGTH', `32768')dnl
+FEATURE(`redirect')dnl
+FEATURE(`use_cw_file')dnl
+EXPOSED_USER(`root')
diff --git a/usr/src/cmd/sendmail/cf/domain/solaris-antispam.m4 b/usr/src/cmd/sendmail/cf/domain/solaris-antispam.m4
new file mode 100644
index 0000000000..74574820d5
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/domain/solaris-antispam.m4
@@ -0,0 +1,47 @@
+divert(-1)
+#
+# Copyright (c) 1983 Eric P. Allman
+# Copyright (c) 1988, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# Copyright (c) 1997-2001 by Sun Microsystems, Inc.
+# All rights reserved.
+#
+# ident "%Z%%M% %I% %E% SMI"
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# 3. All advertising materials mentioning features or use of this software
+# must display the following acknowledgement:
+# This product includes software developed by the University of
+# California, Berkeley and its contributors.
+# 4. Neither the name of the University nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+
+divert(0)
+VERSIONID(`%W% (Sun) %G%')
+define(`confFORWARD_PATH', `$z/.forward.$w+$h:$z/.forward+$h:$z/.forward.$w:$z/.forward')dnl
+FEATURE(`redirect')dnl
+FEATURE(`use_cw_file')dnl
+FEATURE(`use_ct_file')dnl
+EXPOSED_USER(`root')
diff --git a/usr/src/cmd/sendmail/cf/domain/solaris-generic.m4 b/usr/src/cmd/sendmail/cf/domain/solaris-generic.m4
new file mode 100644
index 0000000000..26fa9d8b0b
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/domain/solaris-generic.m4
@@ -0,0 +1,50 @@
+divert(-1)
+#
+# Copyright (c) 1983 Eric P. Allman
+# Copyright (c) 1988, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# Copyright (c) 1997-2001 by Sun Microsystems, Inc.
+# All rights reserved.
+#
+# ident "%Z%%M% %I% %E% SMI"
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# 3. All advertising materials mentioning features or use of this software
+# must display the following acknowledgement:
+# This product includes software developed by the University of
+# California, Berkeley and its contributors.
+# 4. Neither the name of the University nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+
+divert(0)
+VERSIONID(`%W% (Sun) %G%')
+define(`confFORWARD_PATH', `$z/.forward.$w+$h:$z/.forward+$h:$z/.forward.$w:$z/.forward')dnl
+FEATURE(`redirect')dnl
+FEATURE(`use_cw_file')dnl
+FEATURE(`use_ct_file')dnl
+FEATURE(`accept_unqualified_senders')dnl
+FEATURE(`accept_unresolvable_domains')dnl
+FEATURE(`relay_entire_domain')dnl
+EXPOSED_USER(`root')
diff --git a/usr/src/cmd/sendmail/cf/feature/accept_unqualified_senders.m4 b/usr/src/cmd/sendmail/cf/feature/accept_unqualified_senders.m4
new file mode 100644
index 0000000000..13f555918b
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/feature/accept_unqualified_senders.m4
@@ -0,0 +1,17 @@
+divert(-1)
+#
+# Copyright (c) 1998, 1999 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+#ident "%Z%%M% %I% %E% SMI"
+#
+
+divert(0)
+VERSIONID(`$Id: accept_unqualified_senders.m4,v 8.6 1999/02/07 07:26:07 gshapiro Exp $')
+divert(-1)
+
+define(`_ACCEPT_UNQUALIFIED_SENDERS_', 1)
diff --git a/usr/src/cmd/sendmail/cf/feature/accept_unresolvable_domains.m4 b/usr/src/cmd/sendmail/cf/feature/accept_unresolvable_domains.m4
new file mode 100644
index 0000000000..ccfa1638b5
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/feature/accept_unresolvable_domains.m4
@@ -0,0 +1,17 @@
+divert(-1)
+#
+# Copyright (c) 1998, 1999 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+#ident "%Z%%M% %I% %E% SMI"
+#
+
+divert(0)
+VERSIONID(`$Id: accept_unresolvable_domains.m4,v 8.10 1999/02/07 07:26:07 gshapiro Exp $')
+divert(-1)
+
+define(`_ACCEPT_UNRESOLVABLE_DOMAINS_', 1)
diff --git a/usr/src/cmd/sendmail/cf/feature/access_db.m4 b/usr/src/cmd/sendmail/cf/feature/access_db.m4
new file mode 100644
index 0000000000..49fda50ed3
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/feature/access_db.m4
@@ -0,0 +1,45 @@
+divert(-1)
+#
+# Copyright (c) 1998-2002, 2004 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+# ident "%Z%%M% %I% %E% SMI"
+#
+
+divert(0)
+VERSIONID(`$Id: access_db.m4,v 8.26 2004/06/24 18:10:02 ca Exp $')
+divert(-1)
+
+define(`_ACCESS_TABLE_', `')
+define(`_TAG_DELIM_', `:')dnl should be in OperatorChars
+ifelse(lower(_ARG2_),`skip',`define(`_ACCESS_SKIP_', `1')')
+ifelse(lower(_ARG2_),`lookupdotdomain',`define(`_LOOKUPDOTDOMAIN_', `1')')
+ifelse(lower(_ARG3_),`skip',`define(`_ACCESS_SKIP_', `1')')
+ifelse(lower(_ARG3_),`lookupdotdomain',`define(`_LOOKUPDOTDOMAIN_', `1')')
+define(`_ATMPF_', `<TMPF>')dnl
+dnl check whether arg contains -T`'_ATMPF_
+dnl unless it is a sequence map
+ifelse(defn(`_ARG_'), `', `',
+ defn(`_ARG_'), `LDAP', `',
+ `ifelse(index(_ARG_, `sequence '), `0', `',
+ `ifelse(index(_ARG_, _ATMPF_), `-1',
+ `errprint(`*** WARNING: missing -T'_ATMPF_` in argument of FEATURE(`access_db',' defn(`_ARG_')`)
+')
+ define(`_ABP_', index(_ARG_, ` '))
+ define(`_NARG_', `substr(_ARG_, 0, _ABP_) -T'_ATMPF_` substr(_ARG_, _ABP_)')
+ ')
+ ')
+ ')
+ifdef(`_GREET_PAUSE_',
+ `errprint(`*** WARNING: FEATURE(`greet_pause') before FEATURE(`access_db')
+ greet_pause will not use access_db!')')
+
+LOCAL_CONFIG
+# Access list database (for spam stomping)
+Kaccess ifelse(defn(`_ARG_'), `', DATABASE_MAP_TYPE -T`'_ATMPF_ MAIL_SETTINGS_DIR`access',
+ defn(`_ARG_'), `LDAP', `ldap -T`'_ATMPF_ -1 -v sendmailMTAMapValue,sendmailMTAMapSearch:FILTER:sendmailMTAMapObject,sendmailMTAMapURL:URL:sendmailMTAMapObject -k (&(objectClass=sendmailMTAMapObject)(|(sendmailMTACluster=${sendmailMTACluster})(sendmailMTAHost=$j))(sendmailMTAMapName=access)(sendmailMTAKey=%0))',
+ defn(`_NARG_'), `', `_ARG_', `_NARG_')
diff --git a/usr/src/cmd/sendmail/cf/feature/allmasquerade.m4 b/usr/src/cmd/sendmail/cf/feature/allmasquerade.m4
new file mode 100644
index 0000000000..5ddf089793
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/feature/allmasquerade.m4
@@ -0,0 +1,26 @@
+divert(-1)
+#
+# Copyright (c) 1998-2000 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+# Copyright (c) 1983 Eric P. Allman. All rights reserved.
+# Copyright (c) 1988, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+#ident "%Z%%M% %I% %E% SMI"
+#
+
+divert(0)
+VERSIONID(`$Id: allmasquerade.m4,v 8.13 2000/09/12 22:00:53 ca Exp $')
+divert(-1)
+
+ifdef(`_MAILER_local_',
+ `errprint(`*** MAILER(`local') must appear after FEATURE(`allmasquerade')')
+')dnl
+ifdef(`_MAILER_uucp_',
+ `errprint(`*** MAILER(`uucp') must appear after FEATURE(`allmasquerade')')
+')dnl
+define(`_ALL_MASQUERADE_', 1)
diff --git a/usr/src/cmd/sendmail/cf/feature/always_add_domain.m4 b/usr/src/cmd/sendmail/cf/feature/always_add_domain.m4
new file mode 100644
index 0000000000..b3395d503a
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/feature/always_add_domain.m4
@@ -0,0 +1,23 @@
+divert(-1)
+#
+# Copyright (c) 1998-2000 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+# Copyright (c) 1983 Eric P. Allman. All rights reserved.
+# Copyright (c) 1988, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+#ident "%Z%%M% %I% %E% SMI"
+#
+
+divert(0)
+VERSIONID(`$Id: always_add_domain.m4,v 8.11 2000/09/12 22:00:53 ca Exp $')
+divert(-1)
+
+ifdef(`_MAILER_local_',
+ `errprint(`*** MAILER(`local') must appear after FEATURE(`always_add_domain')')
+')dnl
+define(`_ALWAYS_ADD_DOMAIN_', ifelse(len(X`'_ARG_),`1',`',_ARG_))
diff --git a/usr/src/cmd/sendmail/cf/feature/bestmx_is_local.m4 b/usr/src/cmd/sendmail/cf/feature/bestmx_is_local.m4
new file mode 100644
index 0000000000..4bc1f6282e
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/feature/bestmx_is_local.m4
@@ -0,0 +1,52 @@
+divert(-1)
+#
+# Copyright (c) 1998-2000 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+# Copyright (c) 1983 Eric P. Allman. All rights reserved.
+# Copyright (c) 1988, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+#ident "%Z%%M% %I% %E% SMI"
+#
+
+divert(0)
+VERSIONID(`$Id: bestmx_is_local.m4,v 8.26 2000/09/17 17:30:00 gshapiro Exp $')
+divert(-1)
+
+define(`_BESTMX_IS_LOCAL_', _ARG_)
+
+LOCAL_CONFIG
+# turn on bestMX lookup table
+Kbestmx bestmx
+ifelse(defn(`_ARG_'), `', `dnl',`
+# limit bestmx to these domains
+CB`'_ARG_')
+
+LOCAL_NET_CONFIG
+
+# If we are the best MX for a site, then we want to accept
+# its mail as local. We assume we've already weeded out mail to
+# UUCP sites which are connected to us, which should also have
+# listed us as their best MX.
+#
+# Warning: this may generate a lot of extra DNS traffic -- a
+# lower cost method is to list all the expected best MX hosts
+# in $=w. This should be fine (and easier to administer) for
+# low to medium traffic hosts. If you use the limited bestmx
+# by passing in a set of possible domains it will improve things.
+
+ifelse(defn(`_ARG_'), `', `dnl
+# unlimited bestmx
+R$* < @ $* > $* $: $1 < @ $2 @@ $(bestmx $2 $) > $3',
+`dnl
+# limit bestmx to $=B
+R$* < @ $* $=B . > $* $: $1 < @ $2 $3 . @@ $(bestmx $2 $3 . $) > $4')
+R$* $=O $* < @ $* @@ $=w . > $* $@ $>Recurse $1 $2 $3
+R< @ $* @@ $=w . > : $* $@ $>Recurse $3
+dnl we cannot use _LOCAL_ here since it is defined too late
+R$* < @ $* @@ $=w . > $* $@ $>CanonLocal < $1 >
+R$* < @ $* @@ $* > $* $: $1 < @ $2 > $4
diff --git a/usr/src/cmd/sendmail/cf/feature/bitdomain.m4 b/usr/src/cmd/sendmail/cf/feature/bitdomain.m4
new file mode 100644
index 0000000000..ede4ea34bb
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/feature/bitdomain.m4
@@ -0,0 +1,26 @@
+divert(-1)
+#
+# Copyright (c) 1998, 1999, 2001-2002 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+# Copyright (c) 1983 Eric P. Allman. All rights reserved.
+# Copyright (c) 1988, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+# ident "%Z%%M% %I% %E% SMI"
+#
+
+divert(0)
+VERSIONID(`$Id: bitdomain.m4,v 8.30 2002/06/27 23:23:57 gshapiro Exp $')
+divert(-1)
+
+define(`_BITDOMAIN_TABLE_', `')
+
+LOCAL_CONFIG
+# BITNET mapping table
+Kbitdomain ifelse(defn(`_ARG_'), `', DATABASE_MAP_TYPE MAIL_SETTINGS_DIR`bitdomain',
+ defn(`_ARG_'), `LDAP', `ldap -1 -v sendmailMTAMapValue,sendmailMTAMapSearch:FILTER:sendmailMTAMapObject,sendmailMTAMapURL:URL:sendmailMTAMapObject -k (&(objectClass=sendmailMTAMapObject)(|(sendmailMTACluster=${sendmailMTACluster})(sendmailMTAHost=$j))(sendmailMTAMapName=bitdomain)(sendmailMTAKey=%0))',
+ `_ARG_')
diff --git a/usr/src/cmd/sendmail/cf/feature/blacklist_recipients.m4 b/usr/src/cmd/sendmail/cf/feature/blacklist_recipients.m4
new file mode 100644
index 0000000000..d6218d1194
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/feature/blacklist_recipients.m4
@@ -0,0 +1,19 @@
+divert(-1)
+#
+# Copyright (c) 1998, 1999 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+#
+
+divert(0)
+VERSIONID(`$Id: blacklist_recipients.m4,v 8.13 1999/04/02 02:25:13 gshapiro Exp $')
+divert(-1)
+
+ifdef(`_ACCESS_TABLE_',
+ `define(`_BLACKLIST_RCPT_', 1)',
+ `errprint(`*** ERROR: FEATURE(blacklist_recipients) requires FEATURE(access_db)
+')')
diff --git a/usr/src/cmd/sendmail/cf/feature/compat_check.m4 b/usr/src/cmd/sendmail/cf/feature/compat_check.m4
new file mode 100644
index 0000000000..520c7ad976
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/feature/compat_check.m4
@@ -0,0 +1,34 @@
+divert(-1)
+#
+# Copyright (c) 2000-2002 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+#ident "%Z%%M% %I% %E% SMI"
+#
+divert(0)
+VERSIONID(`$Id: compat_check.m4,v 1.4 2002/02/26 22:15:31 gshapiro Exp $')
+divert(-1)
+ifdef(`_ACCESS_TABLE_', `',
+`errprint(`FEATURE(`compat_check') requires FEATURE(`access_db')
+')')
+
+LOCAL_RULESETS
+Scheck_compat
+# look up the pair of addresses
+# (we use <@> as the separator. Note this in the map too!)
+R< $+ > $| $+ $: $1 $| $2
+R$+ $| < $+ > $: $1 $| $2
+R$+ $| $+ $: <$(access Compat:$1<@>$2 $:OK $)>
+R$* $| $* $@ ok
+# act on the result,
+# it must be one of the following... anything else will be allowed..
+dnl for consistency with the other two even though discard does not take a
+dnl reply code
+R< DISCARD:$* > $#discard $: $1 " - discarded by check_compat"
+R< DISCARD $* > $#discard $: $1 " - discarded by check_compat"
+R< TEMP:$* > $#error $@ TEMPFAIL $: $1 " error from check_compat. Try again later"
+R< ERROR:$* > $#error $@ UNAVAILABLE $: $1 " error from check_compat"
diff --git a/usr/src/cmd/sendmail/cf/feature/conncontrol.m4 b/usr/src/cmd/sendmail/cf/feature/conncontrol.m4
new file mode 100644
index 0000000000..d5e449aac9
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/feature/conncontrol.m4
@@ -0,0 +1,37 @@
+divert(-1)
+#
+# Copyright (c) 2003, 2004 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+# ident "%Z%%M% %I% %E% SMI"
+#
+
+divert(0)
+VERSIONID(`$Id: conncontrol.m4,v 1.4 2004/02/19 21:31:47 ca Exp $')
+
+divert(-1)
+ifdef(`_ACCESS_TABLE_', `
+ define(`_CONN_CONTROL_', `1')
+ ifelse(defn(`_ARG_'), `', `',
+ strcasecmp(defn(`_ARG_'), `nodelay'), `1',
+ `ifdef(`_DELAY_CHECKS_',
+ `
+ define(`_CONN_CONTROL_IMMEDIATE_', `1')
+ define(`_CONTROL_IMMEDIATE_', `1')
+ ',
+ `errprint(`*** ERROR: FEATURE(`conncontrol', `nodelay') requires FEATURE(`delay_checks')')'
+ )',
+ `errprint(`*** ERROR: unknown parameter '"defn(`_ARG_')"` for FEATURE(`conncontrol')')')
+ define(`_FFR_SRCHLIST_A', `1')
+ ifelse(len(X`'_ARG2_), `1', `',
+ _ARG2_, `terminate', `define(`_CONN_CONTROL_REPLY', `421')',
+ `errprint(`*** ERROR: FEATURE(`conncontrol'): unknown argument '"_ARG2_"
+)'
+ )
+ ', `errprint(`*** ERROR: FEATURE(`conncontrol') requires FEATURE(`access_db')
+')')
+ifdef(`_CONN_CONTROL_REPLY',,`define(`_CONN_CONTROL_REPLY', `452')')
diff --git a/usr/src/cmd/sendmail/cf/feature/delay_checks.m4 b/usr/src/cmd/sendmail/cf/feature/delay_checks.m4
new file mode 100644
index 0000000000..349c95381c
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/feature/delay_checks.m4
@@ -0,0 +1,26 @@
+divert(-1)
+#
+# Copyright (c) 1999-2000 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+#ident "%Z%%M% %I% %E% SMI"
+#
+
+divert(0)
+VERSIONID(`$Id: delay_checks.m4,v 8.8 2000/12/05 18:50:45 ca Exp $')
+divert(-1)
+
+define(`_DELAY_CHECKS_', 1)
+ifelse(defn(`_ARG_'), `', `',
+ lower(substr(_ARG_,0,1)), `f', `define(`_SPAM_FRIEND_', 1) define(`_SPAM_FH_', 1)',
+ lower(substr(_ARG_,0,1)), `h', `define(`_SPAM_HATER_', 1) define(`_SPAM_FH_', 1)',
+ `errprint(`*** ERROR: illegal argument _ARG_ for FEATURE(delay_checks)
+')
+ ')
+
+dnl be backward compatible by default
+ifelse(len(X`'_ARG2_), `1', `define(`_DELAY_COMPAT_8_10_', 1)', `')
diff --git a/usr/src/cmd/sendmail/cf/feature/dnsbl.m4 b/usr/src/cmd/sendmail/cf/feature/dnsbl.m4
new file mode 100644
index 0000000000..5a8b457375
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/feature/dnsbl.m4
@@ -0,0 +1,34 @@
+divert(-1)
+#
+# Copyright (c) 1998-2002 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+# ident "%Z%%M% %I% %E% SMI"
+#
+
+ifdef(`DNSBL_MAP', `', `define(`DNSBL_MAP', `dns -R A')')
+divert(0)
+ifdef(`_DNSBL_R_',`dnl',`dnl
+VERSIONID(`$Id: dnsbl.m4,v 8.29 2002/08/09 21:02:08 ca Exp $')
+define(`_DNSBL_R_',`')
+LOCAL_CONFIG
+# map for DNS based blacklist lookups
+Kdnsbl DNSBL_MAP -T<TMP>ifdef(`DNSBL_MAP_OPT',` DNSBL_MAP_OPT')')
+divert(-1)
+define(`_DNSBL_SRV_', `ifelse(len(X`'_ARG_),`1',`blackholes.mail-abuse.org',_ARG_)')dnl
+define(`_DNSBL_MSG_', `ifelse(len(X`'_ARG2_),`1',`"550 Rejected: " $`'&{client_addr} " listed at '_DNSBL_SRV_`"',`_ARG2_')')dnl
+define(`_DNSBL_MSG_TMP_', `ifelse(_ARG3_,`t',`"451 Temporary lookup failure of " $`'&{client_addr} " at '_DNSBL_SRV_`"',`_ARG3_')')dnl
+divert(8)
+# DNS based IP address spam list _DNSBL_SRV_
+R$* $: $&{client_addr}
+R$-.$-.$-.$- $: <?> $(dnsbl $4.$3.$2.$1._DNSBL_SRV_. $: OK $)
+R<?>OK $: OKSOFAR
+ifelse(len(X`'_ARG3_),`1',
+`R<?>$+<TMP> $: TMPOK',
+`R<?>$+<TMP> $#error $@ 4.7.1 $: _DNSBL_MSG_TMP_')
+R<?>$+ $#error $@ 5.7.1 $: _DNSBL_MSG_
+divert(-1)
diff --git a/usr/src/cmd/sendmail/cf/feature/domaintable.m4 b/usr/src/cmd/sendmail/cf/feature/domaintable.m4
new file mode 100644
index 0000000000..50b558e39f
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/feature/domaintable.m4
@@ -0,0 +1,26 @@
+divert(-1)
+#
+# Copyright (c) 1998, 1999, 2001-2002 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+# Copyright (c) 1983 Eric P. Allman. All rights reserved.
+# Copyright (c) 1988, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+# ident "%Z%%M% %I% %E% SMI"
+#
+
+divert(0)
+VERSIONID(`$Id: domaintable.m4,v 8.24 2002/06/27 23:23:57 gshapiro Exp $')
+divert(-1)
+
+define(`_DOMAIN_TABLE_', `')
+
+LOCAL_CONFIG
+# Domain table (adding domains)
+Kdomaintable ifelse(defn(`_ARG_'), `', DATABASE_MAP_TYPE MAIL_SETTINGS_DIR`domaintable',
+ defn(`_ARG_'), `LDAP', `ldap -1 -v sendmailMTAMapValue,sendmailMTAMapSearch:FILTER:sendmailMTAMapObject,sendmailMTAMapURL:URL:sendmailMTAMapObject -k (&(objectClass=sendmailMTAMapObject)(|(sendmailMTACluster=${sendmailMTACluster})(sendmailMTAHost=$j))(sendmailMTAMapName=domain)(sendmailMTAKey=%0))',
+ `_ARG_')
diff --git a/usr/src/cmd/sendmail/cf/feature/enhdnsbl.m4 b/usr/src/cmd/sendmail/cf/feature/enhdnsbl.m4
new file mode 100644
index 0000000000..d6bf5a36f6
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/feature/enhdnsbl.m4
@@ -0,0 +1,45 @@
+divert(-1)
+#
+# Copyright (c) 2000-2002 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+#ident "%Z%%M% %I% %E% SMI"
+#
+
+divert(0)
+ifdef(`_EDNSBL_R_',`dnl',`dnl
+VERSIONID(`$Id: enhdnsbl.m4,v 1.9 2002/05/19 21:27:29 gshapiro Exp $')
+LOCAL_CONFIG
+define(`_EDNSBL_R_',`')dnl
+# map for enhanced DNS based blacklist lookups
+Kednsbl dns -R A -a. -T<TMP> -r`'ifdef(`EDNSBL_TO',`EDNSBL_TO',`5')
+')
+divert(-1)
+define(`_EDNSBL_SRV_', `ifelse(len(X`'_ARG_),`1',`blackholes.mail-abuse.org',_ARG_)')dnl
+define(`_EDNSBL_MSG_', `ifelse(len(X`'_ARG2_),`1',`"550 Rejected: " $`'&{client_addr} " listed at '_EDNSBL_SRV_`"',`_ARG2_')')dnl
+define(`_EDNSBL_MSG_TMP_', `ifelse(_ARG3_,`t',`"451 Temporary lookup failure of " $`'&{client_addr} " at '_EDNSBL_SRV_`"',`_ARG3_')')dnl
+define(`_EDNSBL_MATCH_', `ifelse(len(X`'_ARG4_),`1',`$`'+',_ARG4_)')dnl
+divert(8)
+# DNS based IP address spam list _EDNSBL_SRV_
+R$* $: $&{client_addr}
+R$-.$-.$-.$- $: <?> $(ednsbl $4.$3.$2.$1._EDNSBL_SRV_. $: OK $)
+R<?>OK $: OKSOFAR
+ifelse(len(X`'_ARG3_),`1',
+`R<?>$+<TMP> $: TMPOK',
+`R<?>$+<TMP> $#error $@ 4.7.1 $: _EDNSBL_MSG_TMP_')
+R<?>_EDNSBL_MATCH_ $#error $@ 5.7.1 $: _EDNSBL_MSG_
+ifelse(len(X`'_ARG5_),`1',`dnl',
+`R<?>_ARG5_ $#error $@ 5.7.1 $: _EDNSBL_MSG_')
+ifelse(len(X`'_ARG6_),`1',`dnl',
+`R<?>_ARG6_ $#error $@ 5.7.1 $: _EDNSBL_MSG_')
+ifelse(len(X`'_ARG7_),`1',`dnl',
+`R<?>_ARG7_ $#error $@ 5.7.1 $: _EDNSBL_MSG_')
+ifelse(len(X`'_ARG8_),`1',`dnl',
+`R<?>_ARG8_ $#error $@ 5.7.1 $: _EDNSBL_MSG_')
+ifelse(len(X`'_ARG9_),`1',`dnl',
+`R<?>_ARG9_ $#error $@ 5.7.1 $: _EDNSBL_MSG_')
+divert(-1)
diff --git a/usr/src/cmd/sendmail/cf/feature/generics_entire_domain.m4 b/usr/src/cmd/sendmail/cf/feature/generics_entire_domain.m4
new file mode 100644
index 0000000000..fab586af11
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/feature/generics_entire_domain.m4
@@ -0,0 +1,16 @@
+divert(-1)
+#
+# Copyright (c) 1999 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+#
+
+divert(0)
+VERSIONID(`$Id: generics_entire_domain.m4,v 8.1 1999/03/16 00:43:05 ca Exp $')
+divert(-1)
+
+define(`_GENERICS_ENTIRE_DOMAIN_', 1)
diff --git a/usr/src/cmd/sendmail/cf/feature/genericstable.m4 b/usr/src/cmd/sendmail/cf/feature/genericstable.m4
new file mode 100644
index 0000000000..9403b1c229
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/feature/genericstable.m4
@@ -0,0 +1,26 @@
+divert(-1)
+#
+# Copyright (c) 1998, 1999, 2001-2002 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+# Copyright (c) 1983 Eric P. Allman. All rights reserved.
+# Copyright (c) 1988, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+# ident "%Z%%M% %I% %E% SMI"
+#
+
+divert(0)
+VERSIONID(`$Id: genericstable.m4,v 8.23 2002/06/27 23:23:57 gshapiro Exp $')
+divert(-1)
+
+define(`_GENERICS_TABLE_', `')
+
+LOCAL_CONFIG
+# Generics table (mapping outgoing addresses)
+Kgenerics ifelse(defn(`_ARG_'), `', DATABASE_MAP_TYPE MAIL_SETTINGS_DIR`genericstable',
+ defn(`_ARG_'), `LDAP', `ldap -1 -v sendmailMTAMapValue,sendmailMTAMapSearch:FILTER:sendmailMTAMapObject,sendmailMTAMapURL:URL:sendmailMTAMapObject -k (&(objectClass=sendmailMTAMapObject)(|(sendmailMTACluster=${sendmailMTACluster})(sendmailMTAHost=$j))(sendmailMTAMapName=generics)(sendmailMTAKey=%0))',
+ `_ARG_')
diff --git a/usr/src/cmd/sendmail/cf/feature/greet_pause.m4 b/usr/src/cmd/sendmail/cf/feature/greet_pause.m4
new file mode 100644
index 0000000000..a81bcdce56
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/feature/greet_pause.m4
@@ -0,0 +1,45 @@
+divert(-1)
+#
+# Copyright (c) 2004 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+# ident "%Z%%M% %I% %E% SMI"
+#
+
+divert(0)
+VERSIONID(`$Id: greet_pause.m4,v 1.4 2004/07/06 20:49:51 ca Exp $')
+divert(-1)
+
+ifelse(len(X`'_ARG_),`1',`ifdef(`_ACCESS_TABLE_', `',
+ `errprint(`*** ERROR: FEATURE(`greet_pause') requires FEATURE(`access_db')
+')')')
+
+define(`_GREET_PAUSE_', `')
+
+LOCAL_RULESETS
+######################################################################
+### greet_pause: lookup pause time before 220 greeting
+###
+### Parameters:
+### $1: {client_name}
+### $2: {client_addr}
+######################################################################
+SLocal_greet_pause
+Sgreet_pause
+R$* $: <$1><?> $| $>"Local_greet_pause" $1
+R<$*><?> $| $#$* $#$2
+R<$*><?> $| $* $: $1
+ifdef(`_ACCESS_TABLE_', `dnl
+R$+ $| $+ $: $>D < $1 > <?> <! GreetPause> < $2 >
+R $| $+ $: $>A < $1 > <?> <! GreetPause> <> empty client_name
+R<?> <$+> $: $>A < $1 > <?> <! GreetPause> <> no: another lookup
+ifelse(len(X`'_ARG_),`1',
+`R<?> <$*> $@',
+`R<?> <$*> $# _ARG_')
+R<$* <TMPF>> <$*> $@
+R<$+> <$*> $# $1',`dnl
+R$* $# _ARG_')
diff --git a/usr/src/cmd/sendmail/cf/feature/ldap_routing.m4 b/usr/src/cmd/sendmail/cf/feature/ldap_routing.m4
new file mode 100644
index 0000000000..82fa3e59fd
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/feature/ldap_routing.m4
@@ -0,0 +1,47 @@
+divert(-1)
+#
+# Copyright (c) 1999-2002, 2004 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+# ident "%Z%%M% %I% %E% SMI"
+#
+
+divert(0)
+VERSIONID(`$Id: ldap_routing.m4,v 8.14 2004/02/18 02:45:11 gshapiro Exp $')
+divert(-1)
+
+# Check first two arguments. If they aren't set, may need to warn in proto.m4
+ifelse(len(X`'_ARG1_), `1', `define(`_LDAP_ROUTING_WARN_', `yes')')
+ifelse(len(X`'_ARG2_), `1', `define(`_LDAP_ROUTING_WARN_', `yes')')
+ifelse(len(X`'_ARG5_), `1', `', `define(`_LDAP_ROUTE_NODOMAIN_', `yes')')
+
+# Check for third argument to indicate how to deal with non-existant
+# LDAP records
+ifelse(len(X`'_ARG3_), `1', `define(`_LDAP_ROUTING_', `_PASS_THROUGH_')',
+ _ARG3_, `passthru', `define(`_LDAP_ROUTING_', `_PASS_THROUGH_')',
+ _ARG3_, `sendertoo', `define(`_LDAP_ROUTING_', `_MUST_EXIST_')define(`_LDAP_SENDER_MUST_EXIST_')',
+ `define(`_LDAP_ROUTING_', `_MUST_EXIST_')')
+
+# Check for fourth argument to indicate how to deal with +detail info
+ifelse(len(X`'_ARG4_), `1', `',
+ _ARG4_, `strip', `define(`_LDAP_ROUTE_DETAIL_', `_STRIP_')',
+ _ARG4_, `preserve', `define(`_LDAP_ROUTE_DETAIL_', `_PRESERVE_')')
+
+# Check for sixth argument to indicate how to deal with tempfails
+ifelse(len(X`'_ARG6_), `1', `define(`_LDAP_ROUTE_MAPTEMP_', `_QUEUE_')',
+ _ARG6_, `tempfail', `define(`_LDAP_ROUTE_MAPTEMP_', `_TEMPFAIL_')',
+ _ARG6_, `queue', `define(`_LDAP_ROUTE_MAPTEMP_', `_QUEUE_')')
+
+LOCAL_CONFIG
+# LDAP routing maps
+Kldapmh ifelse(len(X`'_ARG1_), `1',
+ `ldap -1 -T<TMPF> -v mailHost -k (&(objectClass=inetLocalMailRecipient)(mailLocalAddress=%0))',
+ `_ARG1_')
+
+Kldapmra ifelse(len(X`'_ARG2_), `1',
+ `ldap -1 -T<TMPF> -v mailRoutingAddress -k (&(objectClass=inetLocalMailRecipient)(mailLocalAddress=%0))',
+ `_ARG2_')
diff --git a/usr/src/cmd/sendmail/cf/feature/limited_masquerade.m4 b/usr/src/cmd/sendmail/cf/feature/limited_masquerade.m4
new file mode 100644
index 0000000000..3b12d870b2
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/feature/limited_masquerade.m4
@@ -0,0 +1,20 @@
+divert(-1)
+#
+# Copyright (c) 1998, 1999 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+# Copyright (c) 1983 Eric P. Allman. All rights reserved.
+# Copyright (c) 1988, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+#ident "%Z%%M% %I% %E% SMI"
+#
+
+divert(0)
+VERSIONID(`$Id: limited_masquerade.m4,v 8.9 1999/02/07 07:26:09 gshapiro Exp $')
+divert(-1)
+
+define(`_LIMITED_MASQUERADE_', 1)
diff --git a/usr/src/cmd/sendmail/cf/feature/local_lmtp.m4 b/usr/src/cmd/sendmail/cf/feature/local_lmtp.m4
new file mode 100644
index 0000000000..6d01d44203
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/feature/local_lmtp.m4
@@ -0,0 +1,29 @@
+divert(-1)
+#
+# Copyright (c) 1998-2000, 2002 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+# ident "%Z%%M% %I% %E% SMI"
+#
+
+divert(0)
+VERSIONID(`$Id: local_lmtp.m4,v 8.17 2002/11/17 04:41:04 ca Exp $')
+divert(-1)
+
+ifdef(`_MAILER_local_',
+ `errprint(`*** FEATURE(local_lmtp) must occur before MAILER(local)
+')')dnl
+
+define(`LOCAL_MAILER_PATH',
+ ifelse(defn(`_ARG_'), `',
+ ifdef(`confEBINDIR', confEBINDIR, `/usr/libexec')`/mail.local',
+ _ARG_))
+define(`LOCAL_MAILER_FLAGS', `PSXmnz9')
+define(`LOCAL_MAILER_ARGS',
+ ifelse(len(X`'_ARG2_), `1', `mail.local -l', _ARG2_))
+define(`LOCAL_MAILER_DSN_DIAGNOSTIC_CODE', `SMTP')
+define(`_LOCAL_LMTP_', `1')
diff --git a/usr/src/cmd/sendmail/cf/feature/local_no_masquerade.m4 b/usr/src/cmd/sendmail/cf/feature/local_no_masquerade.m4
new file mode 100644
index 0000000000..39121350c5
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/feature/local_no_masquerade.m4
@@ -0,0 +1,20 @@
+divert(-1)
+#
+# Copyright (c) 2000 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+#ident "%Z%%M% %I% %E% SMI"
+#
+
+divert(0)
+VERSIONID(`$Id: local_no_masquerade.m4,v 1.2 2000/08/03 15:54:59 ca Exp $')
+divert(-1)
+
+ifdef(`_MAILER_local_',
+ `errprint(`*** MAILER(`local') must appear after FEATURE(`local_no_masquerade')')
+')dnl
+define(`_LOCAL_NO_MASQUERADE_', `1')
diff --git a/usr/src/cmd/sendmail/cf/feature/local_procmail.m4 b/usr/src/cmd/sendmail/cf/feature/local_procmail.m4
new file mode 100644
index 0000000000..6779a34fe4
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/feature/local_procmail.m4
@@ -0,0 +1,37 @@
+divert(-1)
+#
+# Copyright (c) 1998, 1999, 2002 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+# Copyright (c) 1994 Eric P. Allman. All rights reserved.
+# Copyright (c) 1988, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+#ident "%Z%%M% %I% %E% SMI"
+#
+
+divert(0)
+VERSIONID(`$Id: local_procmail.m4,v 8.21.42.1 2002/11/17 04:25:07 ca Exp $')
+divert(-1)
+
+ifdef(`_MAILER_local_',
+ `errprint(`*** FEATURE(local_procmail) must occur before MAILER(local)
+')')dnl
+
+define(`LOCAL_MAILER_PATH',
+ ifelse(defn(`_ARG_'), `',
+ ifdef(`PROCMAIL_MAILER_PATH',
+ PROCMAIL_MAILER_PATH,
+ `/usr/local/bin/procmail'),
+ _ARG_))
+define(`LOCAL_MAILER_ARGS',
+ ifelse(len(X`'_ARG2_), `1', `procmail -Y -a $h -d $u', _ARG2_))
+define(`LOCAL_MAILER_FLAGS',
+ ifelse(len(X`'_ARG3_), `1', `SPfhn9', _ARG3_))
+dnl local_procmail conflicts with local_lmtp but the latter might be
+dnl defined in an OS/ file (solaris8). Let's just undefine it.
+undefine(`_LOCAL_LMTP_')
+undefine(`LOCAL_MAILER_DSN_DIAGNOSTIC_CODE')
diff --git a/usr/src/cmd/sendmail/cf/feature/lookupdotdomain.m4 b/usr/src/cmd/sendmail/cf/feature/lookupdotdomain.m4
new file mode 100644
index 0000000000..1ecd368681
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/feature/lookupdotdomain.m4
@@ -0,0 +1,23 @@
+divert(-1)
+#
+# Copyright (c) 2000 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+#ident "%Z%%M% %I% %E% SMI"
+#
+
+divert(0)
+VERSIONID(`$Id: lookupdotdomain.m4,v 1.1 2000/04/13 22:32:49 ca Exp $')
+divert(-1)
+
+ifdef(`_ACCESS_TABLE_',
+ `define(`_LOOKUPDOTDOMAIN_')',
+ `errprint(`*** ERROR: FEATURE(`lookupdotdomain') requires FEATURE(`access_db')
+')')
+ifdef(`_RELAY_HOSTS_ONLY_',
+ `errprint(`*** WARNING: FEATURE(`lookupdotdomain') does not work well with FEATURE(`relay_hosts_only')
+')')
diff --git a/usr/src/cmd/sendmail/cf/feature/loose_relay_check.m4 b/usr/src/cmd/sendmail/cf/feature/loose_relay_check.m4
new file mode 100644
index 0000000000..f990ca18f4
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/feature/loose_relay_check.m4
@@ -0,0 +1,17 @@
+divert(-1)
+#
+# Copyright (c) 1998, 1999 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+#ident "%Z%%M% %I% %E% SMI"
+#
+
+divert(0)
+VERSIONID(`$Id: loose_relay_check.m4,v 8.6 1999/02/07 07:26:10 gshapiro Exp $')
+divert(-1)
+
+define(`_LOOSE_RELAY_CHECK_', 1)
diff --git a/usr/src/cmd/sendmail/cf/feature/mailertable.m4 b/usr/src/cmd/sendmail/cf/feature/mailertable.m4
new file mode 100644
index 0000000000..bf335ec98a
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/feature/mailertable.m4
@@ -0,0 +1,26 @@
+divert(-1)
+#
+# Copyright (c) 1998, 1999, 2001-2002 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+# Copyright (c) 1983 Eric P. Allman. All rights reserved.
+# Copyright (c) 1988, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+# ident "%Z%%M% %I% %E% SMI"
+#
+
+divert(0)
+VERSIONID(`$Id: mailertable.m4,v 8.25 2002/06/27 23:23:57 gshapiro Exp $')
+divert(-1)
+
+define(`_MAILER_TABLE_', `')
+
+LOCAL_CONFIG
+# Mailer table (overriding domains)
+Kmailertable ifelse(defn(`_ARG_'), `', DATABASE_MAP_TYPE MAIL_SETTINGS_DIR`mailertable',
+ defn(`_ARG_'), `LDAP', `ldap -1 -v sendmailMTAMapValue,sendmailMTAMapSearch:FILTER:sendmailMTAMapObject,sendmailMTAMapURL:URL:sendmailMTAMapObject -k (&(objectClass=sendmailMTAMapObject)(|(sendmailMTACluster=${sendmailMTACluster})(sendmailMTAHost=$j))(sendmailMTAMapName=mailer)(sendmailMTAKey=%0))',
+ `_ARG_')
diff --git a/usr/src/cmd/sendmail/cf/feature/masquerade_entire_domain.m4 b/usr/src/cmd/sendmail/cf/feature/masquerade_entire_domain.m4
new file mode 100644
index 0000000000..555190414d
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/feature/masquerade_entire_domain.m4
@@ -0,0 +1,20 @@
+divert(-1)
+#
+# Copyright (c) 1998, 1999 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+# Copyright (c) 1983 Eric P. Allman. All rights reserved.
+# Copyright (c) 1988, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+#ident "%Z%%M% %I% %E% SMI"
+#
+
+divert(0)
+VERSIONID(`$Id: masquerade_entire_domain.m4,v 8.9 1999/02/07 07:26:10 gshapiro Exp $')
+divert(-1)
+
+define(`_MASQUERADE_ENTIRE_DOMAIN_', 1)
diff --git a/usr/src/cmd/sendmail/cf/feature/masquerade_envelope.m4 b/usr/src/cmd/sendmail/cf/feature/masquerade_envelope.m4
new file mode 100644
index 0000000000..257cc9e71c
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/feature/masquerade_envelope.m4
@@ -0,0 +1,20 @@
+divert(-1)
+#
+# Copyright (c) 1998, 1999 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+# Copyright (c) 1983 Eric P. Allman. All rights reserved.
+# Copyright (c) 1988, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+#ident "%Z%%M% %I% %E% SMI"
+#
+
+divert(0)
+VERSIONID(`$Id: masquerade_envelope.m4,v 8.9 1999/02/07 07:26:10 gshapiro Exp $')
+divert(-1)
+
+define(`_MASQUERADE_ENVELOPE_', 1)
diff --git a/usr/src/cmd/sendmail/cf/feature/msp.m4 b/usr/src/cmd/sendmail/cf/feature/msp.m4
new file mode 100644
index 0000000000..f3639d3809
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/feature/msp.m4
@@ -0,0 +1,79 @@
+divert(-1)
+#
+# Copyright (c) 2000-2002, 2004 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+# ident "%Z%%M% %I% %E% SMI"
+#
+
+divert(0)dnl
+VERSIONID(`$Id: msp.m4,v 1.33 2004/02/09 22:32:38 ca Exp $')
+divert(-1)
+undefine(`ALIAS_FILE')
+define(`confDELIVERY_MODE', `i')
+define(`confUSE_MSP', `True')
+define(`confFORWARD_PATH', `')
+define(`confPRIVACY_FLAGS', `goaway,noetrn,restrictqrun')
+define(`confDONT_PROBE_INTERFACES', `True')
+dnl ---------------------------------------------
+dnl run as this user (even if called by root)
+ifdef(`confRUN_AS_USER',,`define(`confRUN_AS_USER', `smmsp')')
+ifdef(`confTRUSTED_USER',,`define(`confTRUSTED_USER',
+`ifelse(index(confRUN_AS_USER,`:'), -1, `confRUN_AS_USER',
+`substr(confRUN_AS_USER,0,index(confRUN_AS_USER,`:'))')')')
+dnl ---------------------------------------------
+dnl This queue directory must have the same group
+dnl as sendmail and it must be group-writable.
+dnl notice: do not test for QUEUE_DIR, it is set in some ostype/*.m4 files
+ifdef(`MSP_QUEUE_DIR',
+`define(`QUEUE_DIR', `MSP_QUEUE_DIR')',
+`define(`QUEUE_DIR', `/var/spool/clientmqueue')')
+define(`_MTA_HOST_', ifelse(defn(`_ARG_'), `', `[localhost]', `_ARG_'))
+define(`_MSP_FQHN_',`dnl used to qualify addresses
+ifdef(`MASQUERADE_NAME', ifdef(`_MASQUERADE_ENVELOPE_', `$M', `$j'), `$j')')
+ifelse(_ARG2_, `MSA', `define(`RELAY_MAILER_ARGS', `TCP $h 587')')
+dnl ---------------------------------------------
+ifdef(`confPID_FILE', `dnl',
+`define(`confPID_FILE', QUEUE_DIR`/sm-client.pid')')
+define(`confQUEUE_FILE_MODE', `0660')dnl
+ifdef(`STATUS_FILE',
+`define(`_F_',
+`define(`_b_', index(STATUS_FILE, `sendmail.st'))ifelse(_b_, `-1', `STATUS_FILE', `substr(STATUS_FILE, 0, _b_)sm-client.st')')
+define(`STATUS_FILE', _F_)
+undefine(`_b_') undefine(`_F_')',
+`define(`STATUS_FILE', QUEUE_DIR`/sm-client.st')')
+FEATURE(`no_default_msa')dnl
+ifelse(defn(`_DPO_'), `',
+`DAEMON_OPTIONS(`Name=NoMTA, Addr=127.0.0.1, M=E')dnl')
+define(`_DEF_LOCAL_MAILER_FLAGS', `')dnl
+define(`_DEF_LOCAL_SHELL_FLAGS', `')dnl
+define(`LOCAL_MAILER_PATH', `[IPC]')dnl
+define(`LOCAL_MAILER_FLAGS', `lmDFMuXkw5')dnl
+define(`LOCAL_MAILER_ARGS', `TCP $h')dnl
+define(`LOCAL_MAILER_DSN_DIAGNOSTIC_CODE', `SMTP')dnl
+define(`LOCAL_SHELL_PATH', `[IPC]')dnl
+define(`LOCAL_SHELL_FLAGS', `lmDFMuXk5')dnl
+define(`LOCAL_SHELL_ARGS', `TCP $h')dnl
+MODIFY_MAILER_FLAGS(`SMTP', `+k5')dnl
+MODIFY_MAILER_FLAGS(`ESMTP', `+k5')dnl
+MODIFY_MAILER_FLAGS(`DSMTP', `+k5')dnl
+MODIFY_MAILER_FLAGS(`SMTP8', `+k5')dnl
+MODIFY_MAILER_FLAGS(`RELAY', `+k')dnl
+MAILER(`local')dnl
+MAILER(`smtp')dnl
+
+LOCAL_CONFIG
+D{MTAHost}_MTA_HOST_
+
+LOCAL_RULESETS
+SLocal_localaddr
+R$+ $: $>ParseRecipient $1
+R$* < @ $+ > $* $#relay $@ ${MTAHost} $: $1 < @ $2 > $3
+ifdef(`_USE_DECNET_SYNTAX_',
+`# DECnet
+R$+ :: $+ $#relay $@ ${MTAHost} $: $1 :: $2', `dnl')
+R$* $#relay $@ ${MTAHost} $: $1 < @ _MSP_FQHN_ >
diff --git a/usr/src/cmd/sendmail/cf/feature/mtamark.m4 b/usr/src/cmd/sendmail/cf/feature/mtamark.m4
new file mode 100644
index 0000000000..32b458d80f
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/feature/mtamark.m4
@@ -0,0 +1,34 @@
+divert(-1)
+#
+# Copyright (c) 2004 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+# ident "%Z%%M% %I% %E% SMI"
+#
+
+divert(0)
+ifdef(`_MTAMARK_R',`dnl',`dnl
+VERSIONID(`$Id: mtamark.m4,v 1.1 2004/03/22 19:22:40 ca Exp $')
+LOCAL_CONFIG
+define(`_MTAMARK_R',`')dnl
+# map for MTA mark
+Kmtamark dns -R TXT -a. -T<TMP> -r`'ifdef(`MTAMARK_TO',`MTAMARK_TO',`5')
+')
+divert(-1)
+define(`_MTAMARK_RR_', `ifelse(len(X`'_ARG3_),`1',`_perm._smtp._srv',`_ARG3_')')dnl
+define(`_MTAMARK_MSG_', `ifelse(len(X`'_ARG_),`1',`"550 Rejected: " $`'&{client_addr} " not listed as MTA"',`_ARG_')')dnl
+define(`_MTAMARK_MSG_TMP_', `ifelse(_ARG2_,`t',`"451 Temporary lookup failure of " _MTAMARK_RR_.$`'&{client_addr}',`_ARG2_')')dnl
+divert(8)
+# DNS based IP MTA list
+R$* $: $&{client_addr}
+R$-.$-.$-.$- $: <?> $(mtamark _MTAMARK_RR_.$4.$3.$2.$1.in-addr.arpa. $: OK $)
+R<?>1. $: OKSOFAR
+R<?>0. $#error $@ 5.7.1 $: _MTAMARK_MSG_
+ifelse(len(X`'_ARG2_),`1',
+`R<?>$+<TMP> $: TMPOK',
+`R<?>$+<TMP> $#error $@ 4.7.1 $: _MTAMARK_MSG_TMP_')
+divert(-1)
diff --git a/usr/src/cmd/sendmail/cf/feature/no_default_msa.m4 b/usr/src/cmd/sendmail/cf/feature/no_default_msa.m4
new file mode 100644
index 0000000000..88852abadd
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/feature/no_default_msa.m4
@@ -0,0 +1,17 @@
+divert(-1)
+#
+# Copyright (c) 1999-2000 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+#ident "%Z%%M% %I% %E% SMI"
+#
+
+divert(0)
+VERSIONID(`$Id: no_default_msa.m4,v 8.2 2001/02/14 05:03:22 gshapiro Exp $')
+divert(-1)
+
+define(`_NO_MSA_', `1')
diff --git a/usr/src/cmd/sendmail/cf/feature/nocanonify.m4 b/usr/src/cmd/sendmail/cf/feature/nocanonify.m4
new file mode 100644
index 0000000000..05baa7a47b
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/feature/nocanonify.m4
@@ -0,0 +1,24 @@
+divert(-1)
+#
+# Copyright (c) 1998, 1999 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+# Copyright (c) 1983 Eric P. Allman. All rights reserved.
+# Copyright (c) 1988, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+#
+
+divert(0)
+VERSIONID(`$Id: nocanonify.m4,v 8.12 1999/08/28 00:42:01 ca Exp $')
+divert(-1)
+
+define(`_NO_CANONIFY_', 1)
+ifelse(defn(`_ARG_'), `', `',
+ strcasecmp(defn(`_ARG_'), `canonify_hosts'), `1',
+ `define(`_CANONIFY_HOSTS_', 1)',
+ `errprint(`*** ERROR: unknown parameter '"defn(`_ARG_')"` for FEATURE(`nocanonify')
+')')
diff --git a/usr/src/cmd/sendmail/cf/feature/notsticky.m4 b/usr/src/cmd/sendmail/cf/feature/notsticky.m4
new file mode 100644
index 0000000000..f9666228b6
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/feature/notsticky.m4
@@ -0,0 +1,22 @@
+divert(-1)
+#
+# Copyright (c) 1998, 1999 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+# Copyright (c) 1983 Eric P. Allman. All rights reserved.
+# Copyright (c) 1988, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+#ident "%Z%%M% %I% %E% SMI"
+#
+
+divert(0)
+VERSIONID(`$Id: notsticky.m4,v 8.11 1999/02/07 07:26:11 gshapiro Exp $')
+#
+# This is now the default. Use ``FEATURE(stickyhost)'' if you want
+# the old default behaviour.
+#
+divert(-1)
diff --git a/usr/src/cmd/sendmail/cf/feature/nouucp.m4 b/usr/src/cmd/sendmail/cf/feature/nouucp.m4
new file mode 100644
index 0000000000..a03104964d
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/feature/nouucp.m4
@@ -0,0 +1,27 @@
+divert(-1)
+#
+# Copyright (c) 1998, 1999 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+# Copyright (c) 1983 Eric P. Allman. All rights reserved.
+# Copyright (c) 1988, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+#
+
+divert(0)
+VERSIONID(`$Id: nouucp.m4,v 8.13 1999/11/24 18:37:07 ca Exp $')
+divert(-1)
+
+ifelse(defn(`_ARG_'), `',
+ `errprint(`*** ERROR: missing argument for FEATURE(nouucp):
+ use `reject' or `nospecial'. See cf/README.
+')define(`_NO_UUCP_', `e')',
+ substr(_ARG_,0,1), `r', `define(`_NO_UUCP_', `r')',
+ substr(_ARG_,0,1), `n', `define(`_NO_UUCP_', `n')',
+ `errprint(`*** ERROR: illegal argument _ARG_ for FEATURE(nouucp)
+')
+ ')
diff --git a/usr/src/cmd/sendmail/cf/feature/nullclient.m4 b/usr/src/cmd/sendmail/cf/feature/nullclient.m4
new file mode 100644
index 0000000000..ee3f5dd05e
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/feature/nullclient.m4
@@ -0,0 +1,39 @@
+divert(-1)
+#
+# Copyright (c) 1998-2000 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+# Copyright (c) 1983 Eric P. Allman. All rights reserved.
+# Copyright (c) 1988, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+#ident "%Z%%M% %I% %E% SMI"
+#
+ifelse(defn(`_ARG_'), `', `errprint(`Feature "nullclient" requires argument')',
+ `define(`_NULL_CLIENT_', _ARG_)')
+
+#
+# This is used only for relaying mail from a client to a hub when
+# that client does absolutely nothing else -- i.e., it is a "null
+# mailer". In this sense, it acts like the "R" option in Sun
+# sendmail.
+#
+
+divert(0)
+VERSIONID(`$Id: nullclient.m4,v 8.24 2000/09/17 17:30:00 gshapiro Exp $')
+divert(-1)
+
+undefine(`ALIAS_FILE')
+define(`MAIL_HUB', _NULL_CLIENT_)
+define(`SMART_HOST', _NULL_CLIENT_)
+define(`confFORWARD_PATH', `')
+ifdef(`confFROM_HEADER',, `define(`confFROM_HEADER', `<$g>')')
+define(`_DEF_LOCAL_MAILER_FLAGS', `lsDFM5q')
+MASQUERADE_AS(_NULL_CLIENT_)
+FEATURE(`allmasquerade')
+FEATURE(`masquerade_envelope')
+MAILER(`local')
+MAILER(`smtp')
diff --git a/usr/src/cmd/sendmail/cf/feature/preserve_local_plus_detail.m4 b/usr/src/cmd/sendmail/cf/feature/preserve_local_plus_detail.m4
new file mode 100644
index 0000000000..1d63994abe
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/feature/preserve_local_plus_detail.m4
@@ -0,0 +1,17 @@
+divert(-1)
+#
+# Copyright (c) 2000 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+#ident "%Z%%M% %I% %E% SMI"
+#
+
+divert(0)
+VERSIONID(`$Id: preserve_local_plus_detail.m4,v 8.1 2000/04/10 05:48:05 gshapiro Exp $')
+divert(-1)
+
+define(`_PRESERVE_LOCAL_PLUS_DETAIL_', `1')
diff --git a/usr/src/cmd/sendmail/cf/feature/preserve_luser_host.m4 b/usr/src/cmd/sendmail/cf/feature/preserve_luser_host.m4
new file mode 100644
index 0000000000..74b595452f
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/feature/preserve_luser_host.m4
@@ -0,0 +1,21 @@
+divert(-1)
+#
+# Copyright (c) 2000, 2002 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+#ident "%Z%%M% %I% %E% SMI"
+#
+
+divert(0)
+VERSIONID(`$Id: preserve_luser_host.m4,v 1.3 2002/04/14 13:22:58 ca Exp $')
+divert(-1)
+
+ifdef(`LUSER_RELAY', `',
+`errprint(`*** LUSER_RELAY should be defined before FEATURE(`preserve_luser_host')
+ ')')
+define(`_PRESERVE_LUSER_HOST_', `1')
+define(`_NEED_MACRO_MAP_', `1')
diff --git a/usr/src/cmd/sendmail/cf/feature/promiscuous_relay.m4 b/usr/src/cmd/sendmail/cf/feature/promiscuous_relay.m4
new file mode 100644
index 0000000000..193f32da34
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/feature/promiscuous_relay.m4
@@ -0,0 +1,20 @@
+divert(-1)
+#
+# Copyright (c) 1998-1999, 2001 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+#ident "%Z%%M% %I% %E% SMI"
+#
+
+divert(0)
+VERSIONID(`$Id: promiscuous_relay.m4,v 8.12 2001/02/06 17:14:35 ca Exp $')
+divert(-1)
+
+define(`_PROMISCUOUS_RELAY_', 1)
+errprint(`*** WARNING: FEATURE(`promiscuous_relay') configures your system as open
+ relay. Do NOT use it on a server that is connected to the Internet!
+')
diff --git a/usr/src/cmd/sendmail/cf/feature/queuegroup.m4 b/usr/src/cmd/sendmail/cf/feature/queuegroup.m4
new file mode 100644
index 0000000000..31b95c2577
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/feature/queuegroup.m4
@@ -0,0 +1,28 @@
+divert(-1)
+#
+# Copyright (c) 2001 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+#ident "%Z%%M% %I% %E% SMI"
+#
+
+divert(0)
+VERSIONID(`$Id: queuegroup.m4,v 1.4 2001/03/28 00:39:39 ca Exp $')
+divert(-1)
+
+ifdef(`_ACCESS_TABLE_', `',
+ `errprint(`*** ERROR: FEATURE(`queuegroup') requires FEATURE(`access_db')
+')')
+
+LOCAL_RULESETS
+Squeuegroup
+R< $+ > $1
+R $+ @ $+ $: $>SearchList <! qgrp> $| <F:$1@$2> <D:$2> <>
+ifelse(len(X`'_ARG_),`1',
+`R<?> $@',
+`R<?> $# _ARG_')
+R<$+> $# $1
diff --git a/usr/src/cmd/sendmail/cf/feature/ratecontrol.m4 b/usr/src/cmd/sendmail/cf/feature/ratecontrol.m4
new file mode 100644
index 0000000000..7551f041ec
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/feature/ratecontrol.m4
@@ -0,0 +1,37 @@
+divert(-1)
+#
+# Copyright (c) 2003, 2004 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+# ident "%Z%%M% %I% %E% SMI"
+#
+
+divert(0)
+VERSIONID(`$Id: ratecontrol.m4,v 1.5 2004/02/19 21:31:47 ca Exp $')
+
+divert(-1)
+ifdef(`_ACCESS_TABLE_', `
+ define(`_RATE_CONTROL_', `1')
+ ifelse(defn(`_ARG_'), `', `',
+ strcasecmp(defn(`_ARG_'), `nodelay'), `1',
+ `ifdef(`_DELAY_CHECKS_',
+ `
+ define(`_RATE_CONTROL_IMMEDIATE_', `1')
+ define(`_CONTROL_IMMEDIATE_', `1')
+ ',
+ `errprint(`*** ERROR: FEATURE(`ratecontrol', `nodelay') requires FEATURE(`delay_checks')')'
+ )',
+ `errprint(`*** ERROR: unknown parameter '"defn(`_ARG_')"` for FEATURE(`ratecontrol')')')
+ define(`_FFR_SRCHLIST_A', `1')
+ ifelse(len(X`'_ARG2_), `1', `',
+ _ARG2_, `terminate', `define(`_RATE_CONTROL_REPLY', `421')',
+ `errprint(`*** ERROR: FEATURE(`ratecontrol'): unknown argument '"_ARG2_"
+)'
+ )
+ ', `errprint(`*** ERROR: FEATURE(`ratecontrol') requires FEATURE(`access_db')
+')')
+ifdef(`_RATE_CONTROL_REPLY',,`define(`_RATE_CONTROL_REPLY', `452')')
diff --git a/usr/src/cmd/sendmail/cf/feature/redirect.m4 b/usr/src/cmd/sendmail/cf/feature/redirect.m4
new file mode 100644
index 0000000000..e167865efe
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/feature/redirect.m4
@@ -0,0 +1,26 @@
+divert(-1)
+#
+# Copyright (c) 1998, 1999 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+# Copyright (c) 1983 Eric P. Allman. All rights reserved.
+# Copyright (c) 1988, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+#
+
+divert(0)
+VERSIONID(`$Id: redirect.m4,v 8.15 1999/08/06 01:47:36 gshapiro Exp $')
+divert(-1)
+
+LOCAL_RULE_0
+# addresses sent to foo@host.REDIRECT will give a 551 error code
+R$* < @ $+ .REDIRECT. > $: $1 < @ $2 . REDIRECT . > < ${opMode} >
+R$* < @ $+ .REDIRECT. > <i> $: $1 < @ $2 . REDIRECT. >
+R$* < @ $+ .REDIRECT. > < $- > $#error $@ 5.1.1 $: "551 User has moved; please try " <$1@$2>
+
+LOCAL_CONFIG
+CPREDIRECT
diff --git a/usr/src/cmd/sendmail/cf/feature/relay_based_on_MX.m4 b/usr/src/cmd/sendmail/cf/feature/relay_based_on_MX.m4
new file mode 100644
index 0000000000..872680a480
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/feature/relay_based_on_MX.m4
@@ -0,0 +1,21 @@
+divert(-1)
+#
+# Copyright (c) 1998, 1999 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+#
+
+divert(0)
+VERSIONID(`$Id: relay_based_on_MX.m4,v 8.11 1999/04/02 02:25:13 gshapiro Exp $')
+divert(-1)
+
+define(`_RELAY_MX_SERVED_', 1)
+
+LOCAL_CONFIG
+# MX map (to allow relaying to hosts that we MX for)
+Kmxserved bestmx -z: -T<TEMP>
+
diff --git a/usr/src/cmd/sendmail/cf/feature/relay_entire_domain.m4 b/usr/src/cmd/sendmail/cf/feature/relay_entire_domain.m4
new file mode 100644
index 0000000000..4c8de63afa
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/feature/relay_entire_domain.m4
@@ -0,0 +1,17 @@
+divert(-1)
+#
+# Copyright (c) 1998, 1999 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+#ident "%Z%%M% %I% %E% SMI"
+#
+
+divert(0)
+VERSIONID(`$Id: relay_entire_domain.m4,v 8.10 1999/02/07 07:26:12 gshapiro Exp $')
+divert(-1)
+
+define(`_RELAY_ENTIRE_DOMAIN_', 1)
diff --git a/usr/src/cmd/sendmail/cf/feature/relay_hosts_only.m4 b/usr/src/cmd/sendmail/cf/feature/relay_hosts_only.m4
new file mode 100644
index 0000000000..ba4a86e94b
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/feature/relay_hosts_only.m4
@@ -0,0 +1,17 @@
+divert(-1)
+#
+# Copyright (c) 1998, 1999 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+#ident "%Z%%M% %I% %E% SMI"
+#
+
+divert(0)
+VERSIONID(`$Id: relay_hosts_only.m4,v 8.10 1999/02/07 07:26:12 gshapiro Exp $')
+divert(-1)
+
+define(`_RELAY_HOSTS_ONLY_', 1)
diff --git a/usr/src/cmd/sendmail/cf/feature/relay_local_from.m4 b/usr/src/cmd/sendmail/cf/feature/relay_local_from.m4
new file mode 100644
index 0000000000..8d5234dbf0
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/feature/relay_local_from.m4
@@ -0,0 +1,21 @@
+divert(-1)
+#
+# Copyright (c) 1998-1999, 2001 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+#ident "%Z%%M% %I% %E% SMI"
+#
+
+divert(0)
+VERSIONID(`$Id: relay_local_from.m4,v 8.6 2001/02/06 15:55:21 ca Exp $')
+divert(-1)
+
+define(`_RELAY_LOCAL_FROM_', 1)
+errprint(`*** WARNING: FEATURE(`relay_local_from') may cause your system to act as open
+ relay. Use SMTP AUTH or STARTTLS instead. If you cannot use those,
+ try FEATURE(`relay_mail_from').
+')
diff --git a/usr/src/cmd/sendmail/cf/feature/relay_mail_from.m4 b/usr/src/cmd/sendmail/cf/feature/relay_mail_from.m4
new file mode 100644
index 0000000000..9c5da14c27
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/feature/relay_mail_from.m4
@@ -0,0 +1,24 @@
+divert(-1)
+#
+# Copyright (c) 1999, 2001 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+#ident "%Z%%M% %I% %E% SMI"
+#
+
+divert(0)
+VERSIONID(`$Id: relay_mail_from.m4,v 8.3 2001/02/06 16:07:12 ca Exp $')
+divert(-1)
+
+ifdef(`_ACCESS_TABLE_',
+ `define(`_RELAY_DB_FROM_', 1)
+ ifelse(_ARG_,`domain',`define(`_RELAY_DB_FROM_DOMAIN_', 1)')',
+ `errprint(`*** ERROR: FEATURE(`relay_mail_from') requires FEATURE(`access_db')
+')')
+errprint(`*** WARNING: FEATURE(`relay_mail_from') may cause your system to act as open
+ relay. Use SMTP AUTH or STARTTLS instead.
+')
diff --git a/usr/src/cmd/sendmail/cf/feature/smrsh.m4 b/usr/src/cmd/sendmail/cf/feature/smrsh.m4
new file mode 100644
index 0000000000..2159ff8580
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/feature/smrsh.m4
@@ -0,0 +1,26 @@
+divert(-1)
+#
+# Copyright (c) 1998, 1999 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+# Copyright (c) 1983 Eric P. Allman. All rights reserved.
+# Copyright (c) 1988, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+#
+
+divert(0)
+VERSIONID(`$Id: smrsh.m4,v 8.14 1999/11/18 05:06:23 ca Exp $')
+divert(-1)
+
+ifdef(`_MAILER_local_',
+ `errprint(`*** FEATURE(smrsh) must occur before MAILER(local)
+')')dnl
+define(`LOCAL_SHELL_PATH',
+ ifelse(defn(`_ARG_'), `',
+ ifdef(`confEBINDIR', confEBINDIR, `/usr/libexec')`/smrsh',
+ _ARG_))
+_DEFIFNOT(`LOCAL_SHELL_ARGS', `smrsh -c $u')
diff --git a/usr/src/cmd/sendmail/cf/feature/stickyhost.m4 b/usr/src/cmd/sendmail/cf/feature/stickyhost.m4
new file mode 100644
index 0000000000..062bd45b25
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/feature/stickyhost.m4
@@ -0,0 +1,20 @@
+divert(-1)
+#
+# Copyright (c) 1998, 1999 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+# Copyright (c) 1983 Eric P. Allman. All rights reserved.
+# Copyright (c) 1988, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+#ident "%Z%%M% %I% %E% SMI"
+#
+
+divert(0)
+VERSIONID(`$Id: stickyhost.m4,v 8.9 1999/02/07 07:26:13 gshapiro Exp $')
+divert(-1)
+
+define(`_STICKY_LOCAL_DOMAIN_', 1)
diff --git a/usr/src/cmd/sendmail/cf/feature/use_client_ptr.m4 b/usr/src/cmd/sendmail/cf/feature/use_client_ptr.m4
new file mode 100644
index 0000000000..2b65b869d7
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/feature/use_client_ptr.m4
@@ -0,0 +1,22 @@
+divert(-1)
+#
+# Copyright (c) 2004 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+# ident "%Z%%M% %I% %E% SMI"
+#
+
+divert(0)
+VERSIONID(`$Id: use_client_ptr.m4,v 1.1 2004/04/20 22:27:14 ca Exp $')
+divert(-1)
+
+# if defined, check_relay will use {client_ptr} instead of whatever
+# is passed in as its first argument.
+
+define(`_USE_CLIENT_PTR_', `1')
+
+divert(0)
diff --git a/usr/src/cmd/sendmail/cf/feature/use_ct_file.m4 b/usr/src/cmd/sendmail/cf/feature/use_ct_file.m4
new file mode 100644
index 0000000000..629900025d
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/feature/use_ct_file.m4
@@ -0,0 +1,25 @@
+divert(-1)
+#
+# Copyright (c) 1998, 1999, 2001 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+# Copyright (c) 1983 Eric P. Allman. All rights reserved.
+# Copyright (c) 1988, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+#ident "%Z%%M% %I% %E% SMI"
+#
+
+divert(0)
+VERSIONID(`$Id: use_ct_file.m4,v 8.11 2001/08/26 20:58:57 gshapiro Exp $')
+divert(-1)
+
+# if defined, the sendmail.cf will read the /etc/mail/trusted-users file to
+# find the names of trusted users. There should only be a few of these.
+
+define(`_USE_CT_FILE_', `')
+
+divert(0)
diff --git a/usr/src/cmd/sendmail/cf/feature/use_cw_file.m4 b/usr/src/cmd/sendmail/cf/feature/use_cw_file.m4
new file mode 100644
index 0000000000..ebe981ac47
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/feature/use_cw_file.m4
@@ -0,0 +1,26 @@
+divert(-1)
+#
+# Copyright (c) 1998, 1999, 2001 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+# Copyright (c) 1983 Eric P. Allman. All rights reserved.
+# Copyright (c) 1988, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+#ident "%Z%%M% %I% %E% SMI"
+#
+
+divert(0)
+VERSIONID(`$Id: use_cw_file.m4,v 8.11 2001/08/26 20:58:57 gshapiro Exp $')
+divert(-1)
+
+# if defined, the sendmail.cf will read the /etc/mail/local-host-names file
+# to find alternate names for this host. Typically only used when several
+# hosts have been squashed into one another at high speed.
+
+define(`USE_CW_FILE', `')
+
+divert(0)
diff --git a/usr/src/cmd/sendmail/cf/feature/uucpdomain.m4 b/usr/src/cmd/sendmail/cf/feature/uucpdomain.m4
new file mode 100644
index 0000000000..cd78149409
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/feature/uucpdomain.m4
@@ -0,0 +1,26 @@
+divert(-1)
+#
+# Copyright (c) 1998, 1999, 2001-2002 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+# Copyright (c) 1983 Eric P. Allman. All rights reserved.
+# Copyright (c) 1988, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+# ident "%Z%%M% %I% %E% SMI"
+#
+
+divert(0)
+VERSIONID(`$Id: uucpdomain.m4,v 8.29 2002/06/27 23:23:57 gshapiro Exp $')
+divert(-1)
+
+define(`_UUDOMAIN_TABLE_', `')
+
+LOCAL_CONFIG
+# UUCP domain table
+Kuudomain ifelse(defn(`_ARG_'), `', DATABASE_MAP_TYPE MAIL_SETTINGS_DIR`uudomain',
+ defn(`_ARG_'), `LDAP', `ldap -1 -v sendmailMTAMapValue,sendmailMTAMapSearch:FILTER:sendmailMTAMapObject,sendmailMTAMapURL:URL:sendmailMTAMapObject -k (&(objectClass=sendmailMTAMapObject)(|(sendmailMTACluster=${sendmailMTACluster})(sendmailMTAHost=$j))(sendmailMTAMapName=uucpdomain)(sendmailMTAKey=%0))',
+ `_ARG_')
diff --git a/usr/src/cmd/sendmail/cf/feature/virtuser_entire_domain.m4 b/usr/src/cmd/sendmail/cf/feature/virtuser_entire_domain.m4
new file mode 100644
index 0000000000..5a1d9f0e9f
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/feature/virtuser_entire_domain.m4
@@ -0,0 +1,16 @@
+divert(-1)
+#
+# Copyright (c) 1999 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+#
+
+divert(0)
+VERSIONID(`$Id: virtuser_entire_domain.m4,v 8.2 1999/03/16 00:43:05 ca Exp $')
+divert(-1)
+
+define(`_VIRTUSER_ENTIRE_DOMAIN_', 1)
diff --git a/usr/src/cmd/sendmail/cf/feature/virtusertable.m4 b/usr/src/cmd/sendmail/cf/feature/virtusertable.m4
new file mode 100644
index 0000000000..f5a47262d6
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/feature/virtusertable.m4
@@ -0,0 +1,26 @@
+divert(-1)
+#
+# Copyright (c) 1998, 1999, 2001-2002 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+# Copyright (c) 1983 Eric P. Allman. All rights reserved.
+# Copyright (c) 1988, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+# ident "%Z%%M% %I% %E% SMI"
+#
+
+divert(0)
+VERSIONID(`$Id: virtusertable.m4,v 8.23 2002/06/27 23:23:57 gshapiro Exp $')
+divert(-1)
+
+define(`_VIRTUSER_TABLE_', `')
+
+LOCAL_CONFIG
+# Virtual user table (maps incoming users)
+Kvirtuser ifelse(defn(`_ARG_'), `', DATABASE_MAP_TYPE MAIL_SETTINGS_DIR`virtusertable',
+ defn(`_ARG_'), `LDAP', `ldap -1 -v sendmailMTAMapValue,sendmailMTAMapSearch:FILTER:sendmailMTAMapObject,sendmailMTAMapURL:URL:sendmailMTAMapObject -k (&(objectClass=sendmailMTAMapObject)(|(sendmailMTACluster=${sendmailMTACluster})(sendmailMTAHost=$j))(sendmailMTAMapName=virtuser)(sendmailMTAKey=%0))',
+ `_ARG_')
diff --git a/usr/src/cmd/sendmail/cf/m4/cf.m4 b/usr/src/cmd/sendmail/cf/m4/cf.m4
new file mode 100644
index 0000000000..a11085d24b
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/m4/cf.m4
@@ -0,0 +1,30 @@
+divert(-1)
+#
+# Copyright (c) 1998, 1999 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+# Copyright (c) 1983, 1995 Eric P. Allman. All rights reserved.
+# Copyright (c) 1988, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+#ident "%Z%%M% %I% %E% SMI"
+#
+
+#
+# This file is included so that multiple includes of cf.m4 will work
+#
+
+# figure out where the CF files live
+ifdef(`_CF_DIR_', `',
+ `ifelse(__file__, `__file__',
+ `define(`_CF_DIR_', `../')',
+ `define(`_CF_DIR_',
+ substr(__file__, 0, eval(len(__file__) - 8)))')')
+
+divert(0)dnl
+ifdef(`OSTYPE', `dnl',
+`include(_CF_DIR_`'m4/cfhead.m4)dnl
+VERSIONID(`$Id: cf.m4,v 8.32 1999/02/07 07:26:14 gshapiro Exp $')')
diff --git a/usr/src/cmd/sendmail/cf/m4/cfhead.m4 b/usr/src/cmd/sendmail/cf/m4/cfhead.m4
new file mode 100644
index 0000000000..742fa8c116
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/m4/cfhead.m4
@@ -0,0 +1,317 @@
+#
+# Copyright (c) 1998-2004 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+# Copyright (c) 1983, 1995 Eric P. Allman. All rights reserved.
+# Copyright (c) 1988, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# Copyright 1993, 1997-2004 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+#
+
+######################################################################
+######################################################################
+#####
+##### SENDMAIL CONFIGURATION FILE
+#####
+ifdef(`SUN_HIDE_INTERNAL_DETAILS',,
+ifdef(`__win32__', `dnl', `dnl
+ifdef(`TEMPFILE', `dnl', `define(`TEMPFILE', maketemp(/tmp/cfXXXXXX))dnl
+syscmd(sh _CF_DIR_`'sh/makeinfo.sh _CF_DIR_ > TEMPFILE)dnl
+include(TEMPFILE)dnl
+syscmd(rm -f TEMPFILE)dnl')')
+#####
+)dnl
+######################################################################
+#####
+##### DO NOT EDIT THIS FILE! Only edit the source .mc file.
+#####
+######################################################################
+######################################################################
+
+divert(-1)
+
+# ident "%Z%%M% %I% %E% SMI"
+
+changecom()
+undefine(`format')
+undefine(`hpux')
+ifdef(`pushdef', `',
+ `errprint(`You need a newer version of M4, at least as new as
+System V or GNU')
+ include(NoSuchFile)')
+define(`PUSHDIVERT', `pushdef(`__D__', divnum)divert($1)')
+define(`POPDIVERT', `divert(__D__)popdef(`__D__')')
+define(`OSTYPE',
+ `PUSHDIVERT(-1)
+ ifdef(`__OSTYPE__', `errprint(`duplicate OSTYPE'($1)
+)')
+ define(`__OSTYPE__', $1)
+ define(`_ARG_', $2)
+ include(_CF_DIR_`'ostype/$1.m4)POPDIVERT`'')
+## helpful functions
+define(`lower', `translit(`$1', `ABCDEFGHIJKLMNOPQRSTUVWXYZ', `abcdefghijklmnopqrstuvwx')')
+define(`strcasecmp', `ifelse(lower($1), lower($2), `1', `0')')
+## access to further arguments in FEATURE/HACK
+define(`_ACC_ARG_1_',`$1')
+define(`_ACC_ARG_2_',`$2')
+define(`_ACC_ARG_3_',`$3')
+define(`_ACC_ARG_4_',`$4')
+define(`_ACC_ARG_5_',`$5')
+define(`_ACC_ARG_6_',`$6')
+define(`_ACC_ARG_7_',`$7')
+define(`_ACC_ARG_8_',`$8')
+define(`_ACC_ARG_9_',`$9')
+define(`_ARG1_',`_ACC_ARG_1_(_ARGS_)')
+define(`_ARG2_',`_ACC_ARG_2_(_ARGS_)')
+define(`_ARG3_',`_ACC_ARG_3_(_ARGS_)')
+define(`_ARG4_',`_ACC_ARG_4_(_ARGS_)')
+define(`_ARG5_',`_ACC_ARG_5_(_ARGS_)')
+define(`_ARG6_',`_ACC_ARG_6_(_ARGS_)')
+define(`_ARG7_',`_ACC_ARG_7_(_ARGS_)')
+define(`_ARG8_',`_ACC_ARG_8_(_ARGS_)')
+define(`_ARG9_',`_ACC_ARG_9_(_ARGS_)')
+dnl define if not yet defined: if `$1' is not defined it will be `$2'
+define(`_DEFIFNOT',`ifdef(`$1',`',`define(`$1',`$2')')')
+dnl ----------------------------------------
+dnl add a char $2 to a string $1 if it is not there
+define(`_ADDCHAR_',`define(`_I_',`eval(index(`$1',`$2') >= 0)')`'ifelse(_I_,`1',`$1',`$1$2')')
+dnl ----
+dnl delete a char $2 from a string $1 if it is there
+define(`_DELCHAR_',`define(`_IDX_',`index(`$1',`$2')')`'define(`_I_',`eval(_IDX_ >= 0)')`'ifelse(_I_,`1',`substr(`$1',0,_IDX_)`'substr(`$1',eval(_IDX_+1))',`$1')')
+dnl ----
+dnl apply a macro to a whole string by recursion (one char at a time)
+dnl $1: macro
+dnl $2: first argument to macro
+dnl $3: list that is split up into characters
+define(`_AP_',`ifelse(`$3',`',`$2',`_AP_(`$1',$1(`$2',substr(`$3',0,1)),substr(`$3',1))')')
+dnl ----
+dnl MODIFY_MAILER_FLAGS: append tail of $2 to $1_MF_A/D_
+dnl A if head($2) = +
+dnl D if head($2) = -
+dnl $1_MF_ is set otherwise; set _A/D_ to `'
+define(`MODIFY_MAILER_FLAGS',`define(`_hd_',`substr(`$2',0,1)')define(`_tl_',`substr(`$2',1)')`'ifelse(_hd_,`+',`ifdef($1`'_MF_A_, `define($1`'_MF_A_,$1_MF_A_`'_tl_)', `define($1`'_MF_A_, _tl_)')',_hd_,`-',`ifdef($1`'_MF_D_, `define($1`'_MF_D_,$1_MF_D_`'_tl_)', `define($1`'_MF_D_,_tl_)')',`define($1`'_MF_,`$2')define($1`'_MF_A_,`')define($1`'_MF_D_,`')')')
+dnl ----
+dnl actually modify flags:
+dnl $1: flags (strings) to modify
+dnl $2: name of flags (just first part) to modify
+dnl WARNING: the order might be important: if someone adds and delete the
+dnl same characters, he does not deserve any better, does he?
+dnl this could be coded more efficiently... (do not apply the macro if _MF_A/D_ is undefined)
+define(`_MODMF_',`ifdef($2`'_MF_,`$2_MF_',`_AP_(`_ADDCHAR_',_AP_(`_DELCHAR_',$1,ifdef($2`'_MF_D_,`$2_MF_D_',`')),ifdef($2`'_MF_A_,`$2_MF_A_',`'))')')
+dnl usage:
+dnl MODIFY_MAILER_FLAGS(`LOCAL',`+FlaGs')dnl
+dnl in MAILER.m4: _MODMF_(LMF,`LOCAL')
+dnl ----------------------------------------
+define(`MAILER',
+`define(`_M_N_', `ifelse(`$2', `', `$1', `$2')')dnl
+ifdef(`_MAILER_DEFINED_', `', `define(`_MAILER_DEFINED_', `1')')dnl
+ifdef(_MAILER_`'_M_N_`'_,
+`errprint(`*** ERROR: MAILER('_M_N_`) already included
+')',
+`define(_MAILER_`'_M_N_`'_, `')define(`_ARG_', `$2')define(`_ARGS_', `shift($@)')PUSHDIVERT(7)include(_CF_DIR_`'mailer/$1.m4)POPDIVERT`'')')
+define(`DOMAIN', `PUSHDIVERT(-1)define(`_ARG_', `$2')include(_CF_DIR_`'domain/$1.m4)POPDIVERT`'')
+define(`FEATURE', `PUSHDIVERT(-1)ifdef(`_MAILER_DEFINED_',`errprint(`*** ERROR: FEATURE() should be before MAILER()
+')')define(`_ARG_', `$2')define(`_ARGS_', `shift($@)')include(_CF_DIR_`'feature/$1.m4)POPDIVERT`'')
+define(`HACK', `PUSHDIVERT(-1)define(`_ARG_', `$2')define(`_ARGS_', `shift($@)')include(_CF_DIR_`'hack/$1.m4)POPDIVERT`'')
+define(`_DPO_',`')
+define(`DAEMON_OPTIONS', `define(`_DPO_', defn(`_DPO_')
+O DaemonPortOptions=`$1')')
+define(`_CPO_',`')
+define(`CLIENT_OPTIONS', `define(`_CPO_', defn(`_CPO_')
+O ClientPortOptions=`$1')')
+define(`_MAIL_FILTERS_', `')
+define(`_MAIL_FILTERS_DEF', `')
+define(`MAIL_FILTER', `define(`_MAIL_FILTERS_', defn(`_MAIL_FILTERS_')
+X`'$1`, '`$2')
+define(`_MAIL_FILTERS_DEF', defn(`_MAIL_FILTERS_DEF')`X')')
+define(`INPUT_MAIL_FILTER', `MAIL_FILTER(`$1', `$2')
+ifelse(defn(`confINPUT_MAIL_FILTERS')X, `X',
+`define(`confINPUT_MAIL_FILTERS', $1)',
+`define(`confINPUT_MAIL_FILTERS', defn(`confINPUT_MAIL_FILTERS')`, '`$1')')')
+define(`_QUEUE_GROUP_', `')
+define(`QUEUE_GROUP', `define(`_QUEUE_GROUP_', defn(`_QUEUE_GROUP_')
+Q`'$1`, '`$2')')
+define(`CF_LEVEL', `10')dnl
+define(`VERSIONID', ``##### $1 #####'')
+define(`LOCAL_RULE_0', `divert(3)')
+dnl for UUCP...
+define(`LOCAL_UUCP', `divert(4)')
+define(`LOCAL_RULE_1',
+`divert(9)dnl
+#######################################
+### Ruleset 1 -- Sender Rewriting ###
+#######################################
+
+Ssender=1
+')
+define(`LOCAL_RULE_2',
+`divert(9)dnl
+##########################################
+### Ruleset 2 -- Recipient Rewriting ###
+##########################################
+
+Srecipient=2
+')
+define(`LOCAL_RULESETS',
+`divert(9)
+
+')
+define(`LOCAL_SRV_FEATURES',
+`define(`_LOCAL_SRV_FEATURES_')
+ifdef(`_MAILER_DEFINED_',,`errprint(`*** WARNING: MAILER() should be before LOCAL_SRV_FEATURES
+')')
+divert(9)
+SLocal_srv_features')
+define(`LOCAL_TRY_TLS',
+`define(`_LOCAL_TRY_TLS_')
+ifdef(`_MAILER_DEFINED_',,`errprint(`*** WARNING: MAILER() should be before LOCAL_TRY_TLS
+')')
+divert(9)
+SLocal_try_tls')
+define(`LOCAL_TLS_RCPT',
+`define(`_LOCAL_TLS_RCPT_')
+ifdef(`_MAILER_DEFINED_',,`errprint(`*** WARNING: MAILER() should be before LOCAL_TLS_RCPT
+')')
+divert(9)
+SLocal_tls_rcpt')
+define(`LOCAL_TLS_CLIENT',
+`define(`_LOCAL_TLS_CLIENT_')
+ifdef(`_MAILER_DEFINED_',,`errprint(`*** WARNING: MAILER() should be before LOCAL_TLS_CLIENT
+')')
+divert(9)
+SLocal_tls_client')
+define(`LOCAL_TLS_SERVER',
+`define(`_LOCAL_TLS_SERVER_')
+ifdef(`_MAILER_DEFINED_',,`errprint(`*** WARNING: MAILER() should be before LOCAL_TLS_SERVER
+')')
+divert(9)
+SLocal_tls_server')
+define(`LOCAL_RULE_3', `divert(2)')
+define(`LOCAL_CONFIG', `divert(6)')
+define(`MAILER_DEFINITIONS', `divert(7)')
+define(`LOCAL_NET_CONFIG', `define(`_LOCAL_RULES_', 1)divert(1)')
+define(`UUCPSMTP', `R DOL(*) < @ $1 .UUCP > DOL(*) DOL(1) < @ $2 > DOL(2)')
+define(`CONCAT', `$1$2$3$4$5$6$7')
+define(`DOL', ``$'$1')
+define(`SITECONFIG',
+`CONCAT(D, $3, $2)
+define(`_CLASS_$3_', `')dnl
+ifelse($3, U, C{w}$2 $2.UUCP, `dnl')
+define(`SITE', `ifelse(CONCAT($'2`, $3), SU,
+ CONCAT(CY, $'1`),
+ CONCAT(C, $3, $'1`))')
+sinclude(_CF_DIR_`'siteconfig/$1.m4)')
+define(`EXPOSED_USER', `PUSHDIVERT(5)C{E}$1
+POPDIVERT`'dnl`'')
+define(`EXPOSED_USER_FILE', `PUSHDIVERT(5)F{E}$1
+POPDIVERT`'dnl`'')
+define(`LOCAL_USER', `PUSHDIVERT(5)C{L}$1
+POPDIVERT`'dnl`'')
+define(`LOCAL_USER_FILE', `PUSHDIVERT(5)F{L}$1
+POPDIVERT`'dnl`'')
+define(`MASQUERADE_AS', `define(`MASQUERADE_NAME', $1)')
+define(`MASQUERADE_DOMAIN', `PUSHDIVERT(5)C{M}$1
+POPDIVERT`'dnl`'')
+define(`MASQUERADE_EXCEPTION', `PUSHDIVERT(5)C{N}$1
+POPDIVERT`'dnl`'')
+define(`MASQUERADE_DOMAIN_FILE', `PUSHDIVERT(5)F{M}$1
+POPDIVERT`'dnl`'')
+define(`MASQUERADE_EXCEPTION_FILE', `PUSHDIVERT(5)F{N}$1
+POPDIVERT`'dnl`'')
+define(`LOCAL_DOMAIN', `PUSHDIVERT(5)C{w}$1
+POPDIVERT`'dnl`'')
+define(`CANONIFY_DOMAIN', `PUSHDIVERT(5)C{Canonify}$1
+POPDIVERT`'dnl`'')
+define(`CANONIFY_DOMAIN_FILE', `PUSHDIVERT(5)F{Canonify}$1
+POPDIVERT`'dnl`'')
+define(`GENERICS_DOMAIN', `PUSHDIVERT(5)C{G}$1
+POPDIVERT`'dnl`'')
+define(`GENERICS_DOMAIN_FILE', `PUSHDIVERT(5)F{G}$1
+POPDIVERT`'dnl`'')
+define(`LDAPROUTE_DOMAIN', `PUSHDIVERT(5)C{LDAPRoute}$1
+POPDIVERT`'dnl`'')
+define(`LDAPROUTE_DOMAIN_FILE', `PUSHDIVERT(5)F{LDAPRoute}$1
+POPDIVERT`'dnl`'')
+define(`LDAPROUTE_EQUIVALENT', `PUSHDIVERT(5)C{LDAPRouteEquiv}$1
+POPDIVERT`'dnl`'')
+define(`LDAPROUTE_EQUIVALENT_FILE', `PUSHDIVERT(5)F{LDAPRouteEquiv}$1
+POPDIVERT`'dnl`'')
+define(`VIRTUSER_DOMAIN', `PUSHDIVERT(5)C{VirtHost}$1
+define(`_VIRTHOSTS_')
+POPDIVERT`'dnl`'')
+define(`VIRTUSER_DOMAIN_FILE', `PUSHDIVERT(5)F{VirtHost}$1
+define(`_VIRTHOSTS_')
+POPDIVERT`'dnl`'')
+define(`RELAY_DOMAIN', `PUSHDIVERT(5)C{R}$1
+POPDIVERT`'dnl`'')
+define(`RELAY_DOMAIN_FILE', `PUSHDIVERT(5)F{R}$1
+POPDIVERT`'dnl`'')
+define(`TRUST_AUTH_MECH', `_DEFIFNOT(`_USE_AUTH_',`1')PUSHDIVERT(5)C{TrustAuthMech}$1
+POPDIVERT`'dnl`'')
+define(`_OPTINS', `ifdef(`$1', `$2$1$3')')
+
+
+m4wrap(`include(_CF_DIR_`m4/proto.m4')')
+
+# default location for files
+ifdef(`MAIL_SETTINGS_DIR', , `define(`MAIL_SETTINGS_DIR', `/etc/mail/')')
+
+# set our default hashed database type
+define(`DATABASE_MAP_TYPE', `hash')
+
+# set up default values for options
+define(`ALIAS_FILE', `MAIL_SETTINGS_DIR`'aliases')
+define(`confMAILER_NAME', ``MAILER-DAEMON'')
+define(`confFROM_LINE', `From $g $d')
+define(`confOPERATORS', `.:%@!^/[]+')
+define(`confSMTP_LOGIN_MSG', `$j Sendmail $v/$Z; $b')
+define(`_REC_AUTH_', `$.$?{auth_type}(authenticated')
+define(`_REC_FULL_AUTH_', `$.$?{auth_type}(user=${auth_authen} $?{auth_author}author=${auth_author} $.mech=${auth_type}')
+define(`_REC_HDR_', `$?sfrom $s $.$?_($?s$|from $.$_)')
+define(`_REC_END_', `for $u; $|;
+ $.$b')
+define(`_REC_TLS_', `(version=${tls_version} cipher=${cipher} bits=${cipher_bits} verify=${verify})$.$?u')
+define(`_REC_BY_', `$.by $j ($v/$Z)$?r with $r$. id $i$?{tls_version}')
+define(`confRECEIVED_HEADER', `_REC_HDR_
+ _REC_AUTH_$?{auth_ssf} bits=${auth_ssf}$.)
+ _REC_BY_
+ _REC_TLS_
+ _REC_END_')
+define(`confSEVEN_BIT_INPUT', `False')
+define(`confALIAS_WAIT', `10')
+define(`confMIN_FREE_BLOCKS', `100')
+define(`confBLANK_SUB', `.')
+define(`confCON_EXPENSIVE', `False')
+define(`confDELIVERY_MODE', `background')
+define(`confTEMP_FILE_MODE', `0600')
+define(`confMCI_CACHE_SIZE', `2')
+define(`confMCI_CACHE_TIMEOUT', `5m')
+define(`confUSE_ERRORS_TO', `False')
+define(`confLOG_LEVEL', `9')
+define(`confCHECK_ALIASES', `False')
+define(`confOLD_STYLE_HEADERS', `True')
+define(`confPRIVACY_FLAGS', `authwarnings')
+define(`confSAFE_QUEUE', `True')
+define(`confTO_QUEUERETURN', `5d')
+define(`confTO_QUEUEWARN', `4h')
+define(`confTIME_ZONE', `USE_SYSTEM')
+define(`confCW_FILE', `MAIL_SETTINGS_DIR`'local-host-names')
+define(`confMIME_FORMAT_ERRORS', `True')
+define(`confFORWARD_PATH', `$z/.forward.$w:$z/.forward')
+define(`confCR_FILE', `-o MAIL_SETTINGS_DIR`'relay-domains')
+define(`confMILTER_MACROS_CONNECT', ``j, _, {daemon_name}, {if_name}, {if_addr}'')
+define(`confMILTER_MACROS_HELO', ``{tls_version}, {cipher}, {cipher_bits}, {cert_subject}, {cert_issuer}'')
+define(`confMILTER_MACROS_ENVFROM', ``i, {auth_type}, {auth_authen}, {auth_ssf}, {auth_author}, {mail_mailer}, {mail_host}, {mail_addr}'')
+define(`confMILTER_MACROS_ENVRCPT', ``{rcpt_mailer}, {rcpt_host}, {rcpt_addr}'')
+define(`confMILTER_MACROS_EOM', `{msg_id}')
+
+
+divert(0)dnl
+VERSIONID(`$Id: cfhead.m4,v 8.116 2004/01/28 22:02:22 ca Exp $')
+VERSIONID(`ident "%Z%%M% %I% %E% SMI"')
diff --git a/usr/src/cmd/sendmail/cf/m4/proto.m4 b/usr/src/cmd/sendmail/cf/m4/proto.m4
new file mode 100644
index 0000000000..3da71dfbbb
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/m4/proto.m4
@@ -0,0 +1,2943 @@
+divert(-1)
+#
+# Copyright (c) 1998-2004 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+# Copyright (c) 1983, 1995 Eric P. Allman. All rights reserved.
+# Copyright (c) 1988, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# Copyright 2004 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# ident "%Z%%M% %I% %E% SMI"
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+#
+divert(0)
+
+VERSIONID(`$Id: proto.m4,v 8.711 2004/08/04 21:29:55 ca Exp $')
+
+# level CF_LEVEL config file format
+V`'CF_LEVEL/ifdef(`VENDOR_NAME', `VENDOR_NAME', `Sun')
+divert(-1)
+
+dnl if MAILER(`local') not defined: do it ourself; be nice
+dnl maybe we should issue a warning?
+ifdef(`_MAILER_local_',`', `MAILER(local)')
+
+# do some sanity checking
+ifdef(`__OSTYPE__',,
+ `errprint(`*** ERROR: No system type defined (use OSTYPE macro)
+')')
+
+# pick our default mailers
+ifdef(`confSMTP_MAILER',, `define(`confSMTP_MAILER', `esmtp')')
+ifdef(`confLOCAL_MAILER',, `define(`confLOCAL_MAILER', `local')')
+ifdef(`confRELAY_MAILER',,
+ `define(`confRELAY_MAILER',
+ `ifdef(`_MAILER_smtp_', `relay',
+ `ifdef(`_MAILER_uucp', `uucp-new', `unknown')')')')
+ifdef(`confUUCP_MAILER',, `define(`confUUCP_MAILER', `uucp-old')')
+define(`_SMTP_', `confSMTP_MAILER')dnl for readability only
+define(`_LOCAL_', `confLOCAL_MAILER')dnl for readability only
+define(`_RELAY_', `confRELAY_MAILER')dnl for readability only
+define(`_UUCP_', `confUUCP_MAILER')dnl for readability only
+
+# back compatibility with old config files
+ifdef(`confDEF_GROUP_ID',
+`errprint(`*** confDEF_GROUP_ID is obsolete.
+ Use confDEF_USER_ID with a colon in the value instead.
+')')
+ifdef(`confREAD_TIMEOUT',
+`errprint(`*** confREAD_TIMEOUT is obsolete.
+ Use individual confTO_<timeout> parameters instead.
+')')
+ifdef(`confMESSAGE_TIMEOUT',
+ `define(`_ARG_', index(confMESSAGE_TIMEOUT, /))
+ ifelse(_ARG_, -1,
+ `define(`confTO_QUEUERETURN', confMESSAGE_TIMEOUT)',
+ `define(`confTO_QUEUERETURN',
+ substr(confMESSAGE_TIMEOUT, 0, _ARG_))
+ define(`confTO_QUEUEWARN',
+ substr(confMESSAGE_TIMEOUT, eval(_ARG_+1)))')')
+ifdef(`confMIN_FREE_BLOCKS', `ifelse(index(confMIN_FREE_BLOCKS, /), -1,,
+`errprint(`*** compound confMIN_FREE_BLOCKS is obsolete.
+ Use confMAX_MESSAGE_SIZE for the second part of the value.
+')')')
+
+
+# Sanity check on ldap_routing feature
+# If the user doesn't specify a new map, they better have given as a
+# default LDAP specification which has the LDAP base (and most likely the host)
+ifdef(`confLDAP_DEFAULT_SPEC',, `ifdef(`_LDAP_ROUTING_WARN_', `errprint(`
+WARNING: Using default FEATURE(ldap_routing) map definition(s)
+without setting confLDAP_DEFAULT_SPEC option.
+')')')dnl
+
+# clean option definitions below....
+define(`_OPTION', `ifdef(`$2', `O $1`'ifelse(defn(`$2'), `',, `=$2')', `#O $1`'ifelse(`$3', `',,`=$3')')')dnl
+
+dnl required to "rename" the check_* rulesets...
+define(`_U_',ifdef(`_DELAY_CHECKS_',`',`_'))
+dnl default relaying denied message
+ifdef(`confRELAY_MSG', `', `define(`confRELAY_MSG',
+ifdef(`_USE_AUTH_', `"550 Relaying denied. Proper authentication required."', `"550 Relaying denied"'))')
+ifdef(`confRCPTREJ_MSG', `', `define(`confRCPTREJ_MSG', `"550 Mailbox disabled for this recipient"')')
+define(`_CODE553', `553')
+divert(0)dnl
+
+# override file safeties - setting this option compromises system security,
+# addressing the actual file configuration problem is preferred
+# need to set this before any file actions are encountered in the cf file
+_OPTION(DontBlameSendmail, `confDONT_BLAME_SENDMAIL', `safe')
+
+# default LDAP map specification
+# need to set this now before any LDAP maps are defined
+_OPTION(LDAPDefaultSpec, `confLDAP_DEFAULT_SPEC', `-h localhost')
+
+##################
+# local info #
+##################
+
+# my LDAP cluster
+# need to set this before any LDAP lookups are done (including classes)
+ifdef(`confLDAP_CLUSTER', `D{sendmailMTACluster}`'confLDAP_CLUSTER', `#D{sendmailMTACluster}$m')
+
+Cwlocalhost
+ifdef(`USE_CW_FILE',
+`# file containing names of hosts for which we receive email
+Fw`'confCW_FILE',
+ `dnl')
+
+# my official domain name
+# ... `define' this only if sendmail cannot automatically determine your domain
+ifdef(`confDOMAIN_NAME', `Dj`'confDOMAIN_NAME', `#Dj$w.Foo.COM')
+
+# host/domain names ending with a token in class P are canonical
+CP.
+
+ifdef(`UUCP_RELAY',
+`# UUCP relay host
+DY`'UUCP_RELAY
+CPUUCP
+
+')dnl
+ifdef(`BITNET_RELAY',
+`# BITNET relay host
+DB`'BITNET_RELAY
+CPBITNET
+
+')dnl
+ifdef(`DECNET_RELAY',
+`define(`_USE_DECNET_SYNTAX_', 1)dnl
+# DECnet relay host
+DC`'DECNET_RELAY
+CPDECNET
+
+')dnl
+ifdef(`FAX_RELAY',
+`# FAX relay host
+DF`'FAX_RELAY
+CPFAX
+
+')dnl
+# "Smart" relay host (may be null)
+DS`'ifdef(`SMART_HOST', `SMART_HOST')
+
+ifdef(`LUSER_RELAY', `dnl
+# place to which unknown users should be forwarded
+Kuser user -m -a<>
+DL`'LUSER_RELAY',
+`dnl')
+
+# operators that cannot be in local usernames (i.e., network indicators)
+CO @ % ifdef(`_NO_UUCP_', `', `!')
+
+# a class with just dot (for identifying canonical names)
+C..
+
+# a class with just a left bracket (for identifying domain literals)
+C[[
+
+ifdef(`_ACCESS_TABLE_', `dnl
+# access_db acceptance class
+C{Accept}OK RELAY
+ifdef(`_DELAY_COMPAT_8_10_',`dnl
+ifdef(`_BLACKLIST_RCPT_',`dnl
+# possible access_db RHS for spam friends/haters
+C{SpamTag}SPAMFRIEND SPAMHATER')')',
+`dnl')
+
+dnl mark for "domain is ok" (resolved or accepted anyway)
+define(`_RES_OK_', `OKR')dnl
+ifdef(`_ACCEPT_UNRESOLVABLE_DOMAINS_',`dnl',`dnl
+# Resolve map (to check if a host exists in check_mail)
+Kresolve host -a<_RES_OK_> -T<TEMP>')
+C{ResOk}_RES_OK_
+
+ifdef(`_NEED_MACRO_MAP_', `dnl
+ifdef(`_MACRO_MAP_', `', `# macro storage map
+define(`_MACRO_MAP_', `1')dnl
+Kmacro macro')', `dnl')
+
+ifdef(`confCR_FILE', `dnl
+# Hosts for which relaying is permitted ($=R)
+FR`'confCR_FILE',
+`dnl')
+
+define(`TLS_SRV_TAG', `"TLS_Srv"')dnl
+define(`TLS_CLT_TAG', `"TLS_Clt"')dnl
+define(`TLS_RCPT_TAG', `"TLS_Rcpt"')dnl
+define(`TLS_TRY_TAG', `"Try_TLS"')dnl
+define(`SRV_FEAT_TAG', `"Srv_Features"')dnl
+dnl this may be useful in other contexts too
+ifdef(`_ARITH_MAP_', `', `# arithmetic map
+define(`_ARITH_MAP_', `1')dnl
+Karith arith')
+ifdef(`_ACCESS_TABLE_', `dnl
+ifdef(`_MACRO_MAP_', `', `# macro storage map
+define(`_MACRO_MAP_', `1')dnl
+Kmacro macro')
+# possible values for TLS_connection in access map
+C{Tls}VERIFY ENCR', `dnl')
+ifdef(`_CERT_REGEX_ISSUER_', `dnl
+# extract relevant part from cert issuer
+KCERTIssuer regex _CERT_REGEX_ISSUER_', `dnl')
+ifdef(`_CERT_REGEX_SUBJECT_', `dnl
+# extract relevant part from cert subject
+KCERTSubject regex _CERT_REGEX_SUBJECT_', `dnl')
+
+ifdef(`LOCAL_RELAY', `dnl
+# who I send unqualified names to if `FEATURE(stickyhost)' is used
+# (null means deliver locally)
+DR`'LOCAL_RELAY')
+
+ifdef(`MAIL_HUB', `dnl
+# who gets all local email traffic
+# ($R has precedence for unqualified names if `FEATURE(stickyhost)' is used)
+DH`'MAIL_HUB')
+
+# dequoting map
+Kdequote dequote`'ifdef(`confDEQUOTE_OPTS', ` confDEQUOTE_OPTS', `')
+
+divert(0)dnl # end of nullclient diversion
+# class E: names that should be exposed as from this host, even if we masquerade
+# class L: names that should be delivered locally, even if we have a relay
+# class M: domains that should be converted to $M
+# class N: domains that should not be converted to $M
+#CL root
+undivert(5)dnl
+ifdef(`_VIRTHOSTS_', `CR$={VirtHost}', `dnl')
+
+ifdef(`MASQUERADE_NAME', `dnl
+# who I masquerade as (null for no masquerading) (see also $=M)
+DM`'MASQUERADE_NAME')
+
+# my name for error messages
+ifdef(`confMAILER_NAME', `Dn`'confMAILER_NAME', `#DnMAILER-DAEMON')
+
+undivert(6)dnl LOCAL_CONFIG
+include(_CF_DIR_`m4/version.m4')
+
+###############
+# Options #
+###############
+ifdef(`confAUTO_REBUILD',
+`errprint(WARNING: `confAUTO_REBUILD' is no longer valid.
+ There was a potential for a denial of service attack if this is set.
+)')dnl
+
+# strip message body to 7 bits on input?
+_OPTION(SevenBitInput, `confSEVEN_BIT_INPUT', `False')
+
+# 8-bit data handling
+_OPTION(EightBitMode, `confEIGHT_BIT_HANDLING', `pass8')
+
+# wait for alias file rebuild (default units: minutes)
+_OPTION(AliasWait, `confALIAS_WAIT', `5m')
+
+# location of alias file
+_OPTION(AliasFile, `ALIAS_FILE', `MAIL_SETTINGS_DIR`'aliases')
+
+# minimum number of free blocks on filesystem
+_OPTION(MinFreeBlocks, `confMIN_FREE_BLOCKS', `100')
+
+# maximum message size
+_OPTION(MaxMessageSize, `confMAX_MESSAGE_SIZE', `0')
+
+# substitution for space (blank) characters
+_OPTION(BlankSub, `confBLANK_SUB', `_')
+
+# avoid connecting to "expensive" mailers on initial submission?
+_OPTION(HoldExpensive, `confCON_EXPENSIVE', `False')
+
+# checkpoint queue runs after every N successful deliveries
+_OPTION(CheckpointInterval, `confCHECKPOINT_INTERVAL', `10')
+
+# default delivery mode
+_OPTION(DeliveryMode, `confDELIVERY_MODE', `background')
+
+# error message header/file
+_OPTION(ErrorHeader, `confERROR_MESSAGE', `MAIL_SETTINGS_DIR`'error-header')
+
+# error mode
+_OPTION(ErrorMode, `confERROR_MODE', `print')
+
+# save Unix-style "From_" lines at top of header?
+_OPTION(SaveFromLine, `confSAVE_FROM_LINES', `False')
+
+# queue file mode (qf files)
+_OPTION(QueueFileMode, `confQUEUE_FILE_MODE', `0600')
+
+# temporary file mode
+_OPTION(TempFileMode, `confTEMP_FILE_MODE', `0600')
+
+# match recipients against GECOS field?
+_OPTION(MatchGECOS, `confMATCH_GECOS', `False')
+
+# maximum hop count
+_OPTION(MaxHopCount, `confMAX_HOP', `25')
+
+# location of help file
+O HelpFile=ifdef(`HELP_FILE', HELP_FILE, `MAIL_SETTINGS_DIR`'helpfile')
+
+# ignore dots as terminators in incoming messages?
+_OPTION(IgnoreDots, `confIGNORE_DOTS', `False')
+
+# name resolver options
+_OPTION(ResolverOptions, `confBIND_OPTS', `+AAONLY')
+
+# deliver MIME-encapsulated error messages?
+_OPTION(SendMimeErrors, `confMIME_FORMAT_ERRORS', `True')
+
+# Forward file search path
+_OPTION(ForwardPath, `confFORWARD_PATH', `/var/forward/$u:$z/.forward.$w:$z/.forward')
+
+# open connection cache size
+_OPTION(ConnectionCacheSize, `confMCI_CACHE_SIZE', `2')
+
+# open connection cache timeout
+_OPTION(ConnectionCacheTimeout, `confMCI_CACHE_TIMEOUT', `5m')
+
+# persistent host status directory
+_OPTION(HostStatusDirectory, `confHOST_STATUS_DIRECTORY', `.hoststat')
+
+# single thread deliveries (requires HostStatusDirectory)?
+_OPTION(SingleThreadDelivery, `confSINGLE_THREAD_DELIVERY', `False')
+
+# use Errors-To: header?
+_OPTION(UseErrorsTo, `confUSE_ERRORS_TO', `False')
+
+# log level
+_OPTION(LogLevel, `confLOG_LEVEL', `10')
+
+# send to me too, even in an alias expansion?
+_OPTION(MeToo, `confME_TOO', `True')
+
+# verify RHS in newaliases?
+_OPTION(CheckAliases, `confCHECK_ALIASES', `False')
+
+# default messages to old style headers if no special punctuation?
+_OPTION(OldStyleHeaders, `confOLD_STYLE_HEADERS', `False')
+
+# SMTP daemon options
+ifelse(defn(`confDAEMON_OPTIONS'), `', `dnl',
+`errprint(WARNING: `confDAEMON_OPTIONS' is no longer valid.
+ Use `DAEMON_OPTIONS()'; see cf/README.
+)'dnl
+`DAEMON_OPTIONS(`confDAEMON_OPTIONS')')
+ifelse(defn(`_DPO_'), `',
+`ifdef(`_NETINET6_', `O DaemonPortOptions=Name=MTA-v4, Family=inet
+O DaemonPortOptions=Name=MTA-v6, Family=inet6',`O DaemonPortOptions=Name=MTA')', `_DPO_')
+ifdef(`_NO_MSA_', `dnl', `O DaemonPortOptions=Port=587, Name=MSA, M=E')
+
+# SMTP client options
+ifelse(defn(`confCLIENT_OPTIONS'), `', `dnl',
+`errprint(WARNING: `confCLIENT_OPTIONS' is no longer valid. See cf/README for more information.
+)'dnl
+`CLIENT_OPTIONS(`confCLIENT_OPTIONS')')
+ifelse(defn(`_CPO_'), `',
+`#O ClientPortOptions=Family=inet, Address=0.0.0.0', `_CPO_')
+
+# Modifiers to `define' {daemon_flags} for direct submissions
+_OPTION(DirectSubmissionModifiers, `confDIRECT_SUBMISSION_MODIFIERS', `')
+
+# Use as mail submission program? See sendmail/SECURITY
+_OPTION(UseMSP, `confUSE_MSP', `')
+
+# privacy flags
+_OPTION(PrivacyOptions, `confPRIVACY_FLAGS', `authwarnings')
+
+# who (if anyone) should get extra copies of error messages
+_OPTION(PostmasterCopy, `confCOPY_ERRORS_TO', `Postmaster')
+
+# slope of queue-only function
+_OPTION(QueueFactor, `confQUEUE_FACTOR', `600000')
+
+# limit on number of concurrent queue runners
+_OPTION(MaxQueueChildren, `confMAX_QUEUE_CHILDREN', `')
+
+# maximum number of queue-runners per queue-grouping with multiple queues
+_OPTION(MaxRunnersPerQueue, `confMAX_RUNNERS_PER_QUEUE', `1')
+
+# priority of queue runners (nice(3))
+_OPTION(NiceQueueRun, `confNICE_QUEUE_RUN', `')
+
+# shall we sort the queue by hostname first?
+_OPTION(QueueSortOrder, `confQUEUE_SORT_ORDER', `priority')
+
+# minimum time in queue before retry
+_OPTION(MinQueueAge, `confMIN_QUEUE_AGE', `30m')
+
+# how many jobs can you process in the queue?
+_OPTION(MaxQueueRunSize, `confMAX_QUEUE_RUN_SIZE', `10000')
+
+# perform initial split of envelope without checking MX records
+_OPTION(FastSplit, `confFAST_SPLIT', `1')
+
+# queue directory
+O QueueDirectory=ifdef(`QUEUE_DIR', QUEUE_DIR, `/var/spool/mqueue')
+
+# key for shared memory; 0 to turn off
+_OPTION(SharedMemoryKey, `confSHARED_MEMORY_KEY', `0')
+
+ifdef(`confSHARED_MEMORY_KEY_FILE', `dnl
+# file to store key for shared memory (if SharedMemoryKey = -1)
+O SharedMemoryKeyFile=confSHARED_MEMORY_KEY_FILE')
+
+# timeouts (many of these)
+_OPTION(Timeout.initial, `confTO_INITIAL', `5m')
+_OPTION(Timeout.connect, `confTO_CONNECT', `5m')
+_OPTION(Timeout.aconnect, `confTO_ACONNECT', `0s')
+_OPTION(Timeout.iconnect, `confTO_ICONNECT', `5m')
+_OPTION(Timeout.helo, `confTO_HELO', `5m')
+_OPTION(Timeout.mail, `confTO_MAIL', `10m')
+_OPTION(Timeout.rcpt, `confTO_RCPT', `1h')
+_OPTION(Timeout.datainit, `confTO_DATAINIT', `5m')
+_OPTION(Timeout.datablock, `confTO_DATABLOCK', `1h')
+_OPTION(Timeout.datafinal, `confTO_DATAFINAL', `1h')
+_OPTION(Timeout.rset, `confTO_RSET', `5m')
+_OPTION(Timeout.quit, `confTO_QUIT', `2m')
+_OPTION(Timeout.misc, `confTO_MISC', `2m')
+_OPTION(Timeout.command, `confTO_COMMAND', `1h')
+_OPTION(Timeout.ident, `confTO_IDENT', `5s')
+_OPTION(Timeout.fileopen, `confTO_FILEOPEN', `60s')
+_OPTION(Timeout.control, `confTO_CONTROL', `2m')
+_OPTION(Timeout.queuereturn, `confTO_QUEUERETURN', `5d')
+_OPTION(Timeout.queuereturn.normal, `confTO_QUEUERETURN_NORMAL', `5d')
+_OPTION(Timeout.queuereturn.urgent, `confTO_QUEUERETURN_URGENT', `2d')
+_OPTION(Timeout.queuereturn.non-urgent, `confTO_QUEUERETURN_NONURGENT', `7d')
+_OPTION(Timeout.queuereturn.dsn, `confTO_QUEUERETURN_DSN', `5d')
+_OPTION(Timeout.queuewarn, `confTO_QUEUEWARN', `4h')
+_OPTION(Timeout.queuewarn.normal, `confTO_QUEUEWARN_NORMAL', `4h')
+_OPTION(Timeout.queuewarn.urgent, `confTO_QUEUEWARN_URGENT', `1h')
+_OPTION(Timeout.queuewarn.non-urgent, `confTO_QUEUEWARN_NONURGENT', `12h')
+_OPTION(Timeout.queuewarn.dsn, `confTO_QUEUEWARN_DSN', `4h')
+_OPTION(Timeout.hoststatus, `confTO_HOSTSTATUS', `30m')
+_OPTION(Timeout.resolver.retrans, `confTO_RESOLVER_RETRANS', `5s')
+_OPTION(Timeout.resolver.retrans.first, `confTO_RESOLVER_RETRANS_FIRST', `5s')
+_OPTION(Timeout.resolver.retrans.normal, `confTO_RESOLVER_RETRANS_NORMAL', `5s')
+_OPTION(Timeout.resolver.retry, `confTO_RESOLVER_RETRY', `4')
+_OPTION(Timeout.resolver.retry.first, `confTO_RESOLVER_RETRY_FIRST', `4')
+_OPTION(Timeout.resolver.retry.normal, `confTO_RESOLVER_RETRY_NORMAL', `4')
+_OPTION(Timeout.lhlo, `confTO_LHLO', `2m')
+_OPTION(Timeout.auth, `confTO_AUTH', `10m')
+_OPTION(Timeout.starttls, `confTO_STARTTLS', `1h')
+
+# time for DeliverBy; extension disabled if less than 0
+_OPTION(DeliverByMin, `confDELIVER_BY_MIN', `0')
+
+# should we not prune routes in route-addr syntax addresses?
+_OPTION(DontPruneRoutes, `confDONT_PRUNE_ROUTES', `False')
+
+# queue up everything before forking?
+_OPTION(SuperSafe, `confSAFE_QUEUE', `True')
+
+# status file
+O StatusFile=ifdef(`STATUS_FILE', `STATUS_FILE', `MAIL_SETTINGS_DIR`'statistics')
+
+# time zone handling:
+# if undefined, use system default
+# if defined but null, use TZ envariable passed in
+# if defined and non-null, use that info
+ifelse(confTIME_ZONE, `USE_SYSTEM', `#O TimeZoneSpec=',
+ confTIME_ZONE, `USE_TZ', `O TimeZoneSpec=',
+ `O TimeZoneSpec=confTIME_ZONE')
+
+# default UID (can be username or userid:groupid)
+_OPTION(DefaultUser, `confDEF_USER_ID', `mailnull')
+
+# list of locations of user database file (null means no lookup)
+_OPTION(UserDatabaseSpec, `confUSERDB_SPEC', `MAIL_SETTINGS_DIR`'userdb')
+
+# fallback MX host
+_OPTION(FallbackMXhost, `confFALLBACK_MX', `fall.back.host.net')
+
+# fallback smart host
+_OPTION(FallbackSmartHost, `confFALLBACK_SMARTHOST', `fall.back.host.net')
+
+# if we are the best MX host for a site, try it directly instead of config err
+_OPTION(TryNullMXList, `confTRY_NULL_MX_LIST', `False')
+
+# load average at which we just queue messages
+_OPTION(QueueLA, `confQUEUE_LA', `8')
+
+# load average at which we refuse connections
+_OPTION(RefuseLA, `confREFUSE_LA', `12')
+
+# log interval when refusing connections for this long
+_OPTION(RejectLogInterval, `confREJECT_LOG_INTERVAL', `3h')
+
+# load average at which we delay connections; 0 means no limit
+_OPTION(DelayLA, `confDELAY_LA', `0')
+
+# maximum number of children we allow at one time
+_OPTION(MaxDaemonChildren, `confMAX_DAEMON_CHILDREN', `0')
+
+# maximum number of new connections per second
+_OPTION(ConnectionRateThrottle, `confCONNECTION_RATE_THROTTLE', `0')
+
+# Width of the window
+_OPTION(ConnectionRateWindowSize, `confCONNECTION_RATE_WINDOW_SIZE', `60s')
+
+# work recipient factor
+_OPTION(RecipientFactor, `confWORK_RECIPIENT_FACTOR', `30000')
+
+# deliver each queued job in a separate process?
+_OPTION(ForkEachJob, `confSEPARATE_PROC', `False')
+
+# work class factor
+_OPTION(ClassFactor, `confWORK_CLASS_FACTOR', `1800')
+
+# work time factor
+_OPTION(RetryFactor, `confWORK_TIME_FACTOR', `90000')
+
+# default character set
+_OPTION(DefaultCharSet, `confDEF_CHAR_SET', `unknown-8bit')
+
+# service switch file (name hardwired on Solaris, Ultrix, OSF/1, others)
+_OPTION(ServiceSwitchFile, `confSERVICE_SWITCH_FILE', `MAIL_SETTINGS_DIR`'service.switch')
+
+# hosts file (normally /etc/hosts)
+_OPTION(HostsFile, `confHOSTS_FILE', `/etc/hosts')
+
+# dialup line delay on connection failure
+_OPTION(DialDelay, `confDIAL_DELAY', `10s')
+
+# action to take if there are no recipients in the message
+_OPTION(NoRecipientAction, `confNO_RCPT_ACTION', `add-to-undisclosed')
+
+# chrooted environment for writing to files
+_OPTION(SafeFileEnvironment, `confSAFE_FILE_ENV', `/arch')
+
+# are colons OK in addresses?
+_OPTION(ColonOkInAddr, `confCOLON_OK_IN_ADDR', `True')
+
+# shall I avoid expanding CNAMEs (violates protocols)?
+_OPTION(DontExpandCnames, `confDONT_EXPAND_CNAMES', `False')
+
+# SMTP initial login message (old $e macro)
+_OPTION(SmtpGreetingMessage, `confSMTP_LOGIN_MSG', `$j Sendmail $v ready at $b')
+
+# UNIX initial From header format (old $l macro)
+_OPTION(UnixFromLine, `confFROM_LINE', `From $g $d')
+
+# From: lines that have embedded newlines are unwrapped onto one line
+_OPTION(SingleLineFromHeader, `confSINGLE_LINE_FROM_HEADER', `False')
+
+# Allow HELO SMTP command that does not `include' a host name
+_OPTION(AllowBogusHELO, `confALLOW_BOGUS_HELO', `False')
+
+# Characters to be quoted in a full name phrase (@,;:\()[] are automatic)
+_OPTION(MustQuoteChars, `confMUST_QUOTE_CHARS', `.')
+
+# delimiter (operator) characters (old $o macro)
+_OPTION(OperatorChars, `confOPERATORS', `.:@[]')
+
+# shall I avoid calling initgroups(3) because of high NIS costs?
+_OPTION(DontInitGroups, `confDONT_INIT_GROUPS', `False')
+
+# are group-writable `:include:' and .forward files (un)trustworthy?
+# True (the default) means they are not trustworthy.
+_OPTION(UnsafeGroupWrites, `confUNSAFE_GROUP_WRITES', `True')
+ifdef(`confUNSAFE_GROUP_WRITES',
+`errprint(`WARNING: confUNSAFE_GROUP_WRITES is deprecated; use confDONT_BLAME_SENDMAIL.
+')')
+
+# where do errors that occur when sending errors get sent?
+_OPTION(DoubleBounceAddress, `confDOUBLE_BOUNCE_ADDRESS', `postmaster')
+
+# where to save bounces if all else fails
+_OPTION(DeadLetterDrop, `confDEAD_LETTER_DROP', `/var/tmp/dead.letter')
+
+# what user id do we assume for the majority of the processing?
+_OPTION(RunAsUser, `confRUN_AS_USER', `sendmail')
+
+# maximum number of recipients per SMTP envelope
+_OPTION(MaxRecipientsPerMessage, `confMAX_RCPTS_PER_MESSAGE', `0')
+
+# limit the rate recipients per SMTP envelope are accepted
+# once the threshold number of recipients have been rejected
+_OPTION(BadRcptThrottle, `confBAD_RCPT_THROTTLE', `0')
+
+# shall we get local names from our installed interfaces?
+_OPTION(DontProbeInterfaces, `confDONT_PROBE_INTERFACES', `False')
+
+# Return-Receipt-To: header implies DSN request
+_OPTION(RrtImpliesDsn, `confRRT_IMPLIES_DSN', `False')
+
+# override connection address (for testing)
+_OPTION(ConnectOnlyTo, `confCONNECT_ONLY_TO', `0.0.0.0')
+
+# Trusted user for file ownership and starting the daemon
+_OPTION(TrustedUser, `confTRUSTED_USER', `root')
+
+# Control socket for daemon management
+_OPTION(ControlSocketName, `confCONTROL_SOCKET_NAME', `/var/spool/mqueue/.control')
+
+# Maximum MIME header length to protect MUAs
+_OPTION(MaxMimeHeaderLength, `confMAX_MIME_HEADER_LENGTH', `2048/1024')
+
+# Maximum length of the sum of all headers
+_OPTION(MaxHeadersLength, `confMAX_HEADERS_LENGTH', `32768')
+
+# Maximum depth of alias recursion
+_OPTION(MaxAliasRecursion, `confMAX_ALIAS_RECURSION', `10')
+
+# location of pid file
+_OPTION(PidFile, `confPID_FILE', `/var/run/sendmail.pid')
+
+# Prefix string for the process title shown on 'ps' listings
+_OPTION(ProcessTitlePrefix, `confPROCESS_TITLE_PREFIX', `prefix')
+
+# Data file (df) memory-buffer file maximum size
+_OPTION(DataFileBufferSize, `confDF_BUFFER_SIZE', `4096')
+
+# Transcript file (xf) memory-buffer file maximum size
+_OPTION(XscriptFileBufferSize, `confXF_BUFFER_SIZE', `4096')
+
+# lookup type to find information about local mailboxes
+_OPTION(MailboxDatabase, `confMAILBOX_DATABASE', `pw')
+
+# override compile time flag REQUIRES_DIR_FSYNC
+_OPTION(RequiresDirfsync, `confREQUIRES_DIR_FSYNC', `true')
+
+# list of authentication mechanisms
+_OPTION(AuthMechanisms, `confAUTH_MECHANISMS', `EXTERNAL GSSAPI KERBEROS_V4 DIGEST-MD5 CRAM-MD5')
+
+# Authentication realm
+_OPTION(AuthRealm, `confAUTH_REALM', `')
+
+# default authentication information for outgoing connections
+_OPTION(DefaultAuthInfo, `confDEF_AUTH_INFO', `MAIL_SETTINGS_DIR`'default-auth-info')
+
+# SMTP AUTH flags
+_OPTION(AuthOptions, `confAUTH_OPTIONS', `')
+
+# SMTP AUTH maximum encryption strength
+_OPTION(AuthMaxBits, `confAUTH_MAX_BITS', `')
+
+# SMTP STARTTLS server options
+_OPTION(TLSSrvOptions, `confTLS_SRV_OPTIONS', `')
+
+# Input mail filters
+_OPTION(InputMailFilters, `confINPUT_MAIL_FILTERS', `')
+
+ifelse(len(X`'_MAIL_FILTERS_DEF), `1', `dnl', `dnl
+# Milter options
+_OPTION(Milter.LogLevel, `confMILTER_LOG_LEVEL', `')
+_OPTION(Milter.macros.connect, `confMILTER_MACROS_CONNECT', `')
+_OPTION(Milter.macros.helo, `confMILTER_MACROS_HELO', `')
+_OPTION(Milter.macros.envfrom, `confMILTER_MACROS_ENVFROM', `')
+_OPTION(Milter.macros.envrcpt, `confMILTER_MACROS_ENVRCPT', `')
+_OPTION(Milter.macros.eom, `confMILTER_MACROS_EOM', `')')
+
+# CA directory
+_OPTION(CACertPath, `confCACERT_PATH', `')
+# CA file
+_OPTION(CACertFile, `confCACERT', `')
+# Server Cert
+_OPTION(ServerCertFile, `confSERVER_CERT', `')
+# Server private key
+_OPTION(ServerKeyFile, `confSERVER_KEY', `')
+# Client Cert
+_OPTION(ClientCertFile, `confCLIENT_CERT', `')
+# Client private key
+_OPTION(ClientKeyFile, `confCLIENT_KEY', `')
+# File containing certificate revocation lists
+_OPTION(CRLFile, `confCRL', `')
+# DHParameters (only required if DSA/DH is used)
+_OPTION(DHParameters, `confDH_PARAMETERS', `')
+# Random data source (required for systems without /dev/urandom under OpenSSL)
+_OPTION(RandFile, `confRAND_FILE', `')
+
+############################
+`# QUEUE GROUP DEFINITIONS #'
+############################
+_QUEUE_GROUP_
+
+###########################
+# Message precedences #
+###########################
+
+Pfirst-class=0
+Pspecial-delivery=100
+Plist=-30
+Pbulk=-60
+Pjunk=-100
+
+#####################
+# Trusted users #
+#####################
+
+# this is equivalent to setting class "t"
+ifdef(`_USE_CT_FILE_', `', `#')Ft`'ifdef(`confCT_FILE', confCT_FILE, `MAIL_SETTINGS_DIR`'trusted-users')
+Troot
+Tdaemon
+ifdef(`_NO_UUCP_', `dnl', `Tuucp')
+ifdef(`confTRUSTED_USERS', `T`'confTRUSTED_USERS', `dnl')
+
+#########################
+# Format of headers #
+#########################
+
+ifdef(`confFROM_HEADER',, `define(`confFROM_HEADER', `$?x$x <$g>$|$g$.')')dnl
+ifdef(`confMESSAGEID_HEADER',, `define(`confMESSAGEID_HEADER', `<$t.$i@$j>')')dnl
+H?P?Return-Path: <$g>
+HReceived: confRECEIVED_HEADER
+H?D?Resent-Date: $a
+H?D?Date: $a
+H?F?Resent-From: confFROM_HEADER
+H?F?From: confFROM_HEADER
+H?x?Full-Name: $x
+# HPosted-Date: $a
+# H?l?Received-Date: $b
+H?M?Resent-Message-Id: confMESSAGEID_HEADER
+H?M?Message-Id: confMESSAGEID_HEADER
+
+#
+######################################################################
+######################################################################
+#####
+##### REWRITING RULES
+#####
+######################################################################
+######################################################################
+
+############################################
+### Ruleset 3 -- Name Canonicalization ###
+############################################
+Scanonify=3
+
+# handle null input (translate to <@> special case)
+R$@ $@ <@>
+
+# strip group: syntax (not inside angle brackets!) and trailing semicolon
+R$* $: $1 <@> mark addresses
+R$* < $* > $* <@> $: $1 < $2 > $3 unmark <addr>
+R@ $* <@> $: @ $1 unmark @host:...
+R$* [ IPv6 : $+ ] <@> $: $1 [ IPv6 : $2 ] unmark IPv6 addr
+R$* :: $* <@> $: $1 :: $2 unmark node::addr
+R:`include': $* <@> $: :`include': $1 unmark :`include':...
+R$* : $* [ $* ] $: $1 : $2 [ $3 ] <@> remark if leading colon
+R$* : $* <@> $: $2 strip colon if marked
+R$* <@> $: $1 unmark
+R$* ; $1 strip trailing semi
+R$* < $+ :; > $* $@ $2 :; <@> catch <list:;>
+R$* < $* ; > $1 < $2 > bogus bracketed semi
+
+# null input now results from list:; syntax
+R$@ $@ :; <@>
+
+# strip angle brackets -- note RFC733 heuristic to get innermost item
+R$* $: < $1 > housekeeping <>
+R$+ < $* > < $2 > strip excess on left
+R< $* > $+ < $1 > strip excess on right
+R<> $@ < @ > MAIL FROM:<> case
+R< $+ > $: $1 remove housekeeping <>
+
+ifdef(`_USE_DEPRECATED_ROUTE_ADDR_',`dnl
+# make sure <@a,@b,@c:user@d> syntax is easy to parse -- undone later
+R@ $+ , $+ @ $1 : $2 change all "," to ":"
+
+# localize and dispose of route-based addresses
+dnl XXX: IPv6 colon conflict
+ifdef(`NO_NETINET6', `dnl',
+`R@ [$+] : $+ $@ $>Canonify2 < @ [$1] > : $2 handle <route-addr>')
+R@ $+ : $+ $@ $>Canonify2 < @$1 > : $2 handle <route-addr>
+dnl',`dnl
+# strip route address <@a,@b,@c:user@d> -> <user@d>
+R@ $+ , $+ $2
+ifdef(`NO_NETINET6', `dnl',
+`R@ [ $* ] : $+ $2')
+R@ $+ : $+ $2
+dnl')
+
+# find focus for list syntax
+R $+ : $* ; @ $+ $@ $>Canonify2 $1 : $2 ; < @ $3 > list syntax
+R $+ : $* ; $@ $1 : $2; list syntax
+
+# find focus for @ syntax addresses
+R$+ @ $+ $: $1 < @ $2 > focus on domain
+R$+ < $+ @ $+ > $1 $2 < @ $3 > move gaze right
+R$+ < @ $+ > $@ $>Canonify2 $1 < @ $2 > already canonical
+
+dnl This is flagged as an error in S0; no need to silently fix it here.
+dnl # do some sanity checking
+dnl R$* < @ $~[ $* : $* > $* $1 < @ $2 $3 > $4 nix colons in addrs
+
+ifdef(`_NO_UUCP_', `dnl',
+`# convert old-style addresses to a domain-based address
+R$- ! $+ $@ $>Canonify2 $2 < @ $1 .UUCP > resolve uucp names
+R$+ . $- ! $+ $@ $>Canonify2 $3 < @ $1 . $2 > domain uucps
+R$+ ! $+ $@ $>Canonify2 $2 < @ $1 .UUCP > uucp subdomains
+')
+ifdef(`_USE_DECNET_SYNTAX_',
+`# convert node::user addresses into a domain-based address
+R$- :: $+ $@ $>Canonify2 $2 < @ $1 .DECNET > resolve DECnet names
+R$- . $- :: $+ $@ $>Canonify2 $3 < @ $1.$2 .DECNET > numeric DECnet addr
+',
+ `dnl')
+# if we have % signs, take the rightmost one
+R$* % $* $1 @ $2 First make them all @s.
+R$* @ $* @ $* $1 % $2 @ $3 Undo all but the last.
+R$* @ $* $@ $>Canonify2 $1 < @ $2 > Insert < > and finish
+
+# else we must be a local name
+R$* $@ $>Canonify2 $1
+
+
+################################################
+### Ruleset 96 -- bottom half of ruleset 3 ###
+################################################
+
+SCanonify2=96
+
+# handle special cases for local names
+R$* < @ localhost > $* $: $1 < @ $j . > $2 no domain at all
+R$* < @ localhost . $m > $* $: $1 < @ $j . > $2 local domain
+ifdef(`_NO_UUCP_', `dnl',
+`R$* < @ localhost . UUCP > $* $: $1 < @ $j . > $2 .UUCP domain')
+
+# check for IPv4/IPv6 domain literal
+R$* < @ [ $+ ] > $* $: $1 < @@ [ $2 ] > $3 mark [addr]
+R$* < @@ $=w > $* $: $1 < @ $j . > $3 self-literal
+R$* < @@ $+ > $* $@ $1 < @ $2 > $3 canon IP addr
+
+ifdef(`_DOMAIN_TABLE_', `dnl
+# look up domains in the domain table
+R$* < @ $+ > $* $: $1 < @ $(domaintable $2 $) > $3', `dnl')
+
+undivert(2)dnl LOCAL_RULE_3
+
+ifdef(`_BITDOMAIN_TABLE_', `dnl
+# handle BITNET mapping
+R$* < @ $+ .BITNET > $* $: $1 < @ $(bitdomain $2 $: $2.BITNET $) > $3', `dnl')
+
+ifdef(`_UUDOMAIN_TABLE_', `dnl
+# handle UUCP mapping
+R$* < @ $+ .UUCP > $* $: $1 < @ $(uudomain $2 $: $2.UUCP $) > $3', `dnl')
+
+ifdef(`_NO_UUCP_', `dnl',
+`ifdef(`UUCP_RELAY',
+`# pass UUCP addresses straight through
+R$* < @ $+ . UUCP > $* $@ $1 < @ $2 . UUCP . > $3',
+`# if really UUCP, handle it immediately
+ifdef(`_CLASS_U_',
+`R$* < @ $=U . UUCP > $* $@ $1 < @ $2 . UUCP . > $3', `dnl')
+ifdef(`_CLASS_V_',
+`R$* < @ $=V . UUCP > $* $@ $1 < @ $2 . UUCP . > $3', `dnl')
+ifdef(`_CLASS_W_',
+`R$* < @ $=W . UUCP > $* $@ $1 < @ $2 . UUCP . > $3', `dnl')
+ifdef(`_CLASS_X_',
+`R$* < @ $=X . UUCP > $* $@ $1 < @ $2 . UUCP . > $3', `dnl')
+ifdef(`_CLASS_Y_',
+`R$* < @ $=Y . UUCP > $* $@ $1 < @ $2 . UUCP . > $3', `dnl')
+
+ifdef(`_NO_CANONIFY_', `dnl', `dnl
+# try UUCP traffic as a local address
+R$* < @ $+ . UUCP > $* $: $1 < @ $[ $2 $] . UUCP . > $3
+R$* < @ $+ . . UUCP . > $* $@ $1 < @ $2 . > $3')
+')')
+# hostnames ending in class P are always canonical
+R$* < @ $* $=P > $* $: $1 < @ $2 $3 . > $4
+dnl apply the next rule only for hostnames not in class P
+dnl this even works for phrases in class P since . is in class P
+dnl which daemon flags are set?
+R$* < @ $* $~P > $* $: $&{daemon_flags} $| $1 < @ $2 $3 > $4
+dnl the other rules in this section only apply if the hostname
+dnl does not end in class P hence no further checks are done here
+dnl if this ever changes make sure the lookups are "protected" again!
+ifdef(`_NO_CANONIFY_', `dnl
+dnl do not canonify unless:
+dnl domain ends in class {Canonify} (this does not work if the intersection
+dnl with class P is non-empty)
+dnl or {daemon_flags} has c set
+# pass to name server to make hostname canonical if in class {Canonify}
+R$* $| $* < @ $* $={Canonify} > $* $: $2 < @ $[ $3 $4 $] > $5
+# pass to name server to make hostname canonical if requested
+R$* c $* $| $* < @ $* > $* $: $3 < @ $[ $4 $] > $5
+dnl trailing dot? -> do not apply _CANONIFY_HOSTS_
+R$* $| $* < @ $+ . > $* $: $2 < @ $3 . > $4
+# add a trailing dot to qualified hostnames so other rules will work
+R$* $| $* < @ $+.$+ > $* $: $2 < @ $3.$4 . > $5
+ifdef(`_CANONIFY_HOSTS_', `dnl
+dnl this should only apply to unqualified hostnames
+dnl but if a valid character inside an unqualified hostname is an OperatorChar
+dnl then $- does not work.
+# lookup unqualified hostnames
+R$* $| $* < @ $* > $* $: $2 < @ $[ $3 $] > $4', `dnl')', `dnl
+dnl _NO_CANONIFY_ is not set: canonify unless:
+dnl {daemon_flags} contains CC (do not canonify)
+dnl but add a trailing dot to qualified hostnames so other rules will work
+dnl should we do this for every hostname: even unqualified?
+R$* CC $* $| $* < @ $+.$+ > $* $: $3 < @ $4.$5 . > $6
+R$* CC $* $| $* $: $3
+ifdef(`_FFR_NOCANONIFY_HEADERS', `dnl
+# do not canonify header addresses
+R$* $| $* < @ $* $~P > $* $: $&{addr_type} $| $2 < @ $3 $4 > $5
+R$* h $* $| $* < @ $+.$+ > $* $: $3 < @ $4.$5 . > $6
+R$* h $* $| $* $: $3', `dnl')
+# pass to name server to make hostname canonical
+R$* $| $* < @ $* > $* $: $2 < @ $[ $3 $] > $4')
+dnl remove {daemon_flags} for other cases
+R$* $| $* $: $2
+
+# local host aliases and pseudo-domains are always canonical
+R$* < @ $=w > $* $: $1 < @ $2 . > $3
+ifdef(`_MASQUERADE_ENTIRE_DOMAIN_',
+`R$* < @ $* $=M > $* $: $1 < @ $2 $3 . > $4',
+`R$* < @ $=M > $* $: $1 < @ $2 . > $3')
+ifdef(`_VIRTUSER_TABLE_', `dnl
+dnl virtual hosts are also canonical
+ifdef(`_VIRTUSER_ENTIRE_DOMAIN_',
+`R$* < @ $* $={VirtHost} > $* $: $1 < @ $2 $3 . > $4',
+`R$* < @ $={VirtHost} > $* $: $1 < @ $2 . > $3')',
+`dnl')
+ifdef(`_GENERICS_TABLE_', `dnl
+dnl hosts for genericstable are also canonical
+ifdef(`_GENERICS_ENTIRE_DOMAIN_',
+`R$* < @ $* $=G > $* $: $1 < @ $2 $3 . > $4',
+`R$* < @ $=G > $* $: $1 < @ $2 . > $3')',
+`dnl')
+dnl remove superfluous dots (maybe repeatedly) which may have been added
+dnl by one of the rules before
+R$* < @ $* . . > $* $1 < @ $2 . > $3
+
+
+##################################################
+### Ruleset 4 -- Final Output Post-rewriting ###
+##################################################
+Sfinal=4
+
+R$+ :; <@> $@ $1 : handle <list:;>
+R$* <@> $@ handle <> and list:;
+
+# strip trailing dot off possibly canonical name
+R$* < @ $+ . > $* $1 < @ $2 > $3
+
+# eliminate internal code
+R$* < @ *LOCAL* > $* $1 < @ $j > $2
+
+# externalize local domain info
+R$* < $+ > $* $1 $2 $3 defocus
+R@ $+ : @ $+ : $+ @ $1 , @ $2 : $3 <route-addr> canonical
+R@ $* $@ @ $1 ... and exit
+
+ifdef(`_NO_UUCP_', `dnl',
+`# UUCP must always be presented in old form
+R$+ @ $- . UUCP $2!$1 u@h.UUCP => h!u')
+
+ifdef(`_USE_DECNET_SYNTAX_',
+`# put DECnet back in :: form
+R$+ @ $+ . DECNET $2 :: $1 u@h.DECNET => h::u',
+ `dnl')
+# delete duplicate local names
+R$+ % $=w @ $=w $1 @ $2 u%host@host => u@host
+
+
+
+##############################################################
+### Ruleset 97 -- recanonicalize and call ruleset zero ###
+### (used for recursive calls) ###
+##############################################################
+
+SRecurse=97
+R$* $: $>canonify $1
+R$* $@ $>parse $1
+
+
+######################################
+### Ruleset 0 -- Parse Address ###
+######################################
+
+Sparse=0
+
+R$* $: $>Parse0 $1 initial parsing
+R<@> $#_LOCAL_ $: <@> special case error msgs
+R$* $: $>ParseLocal $1 handle local hacks
+R$* $: $>Parse1 $1 final parsing
+
+#
+# Parse0 -- do initial syntax checking and eliminate local addresses.
+# This should either return with the (possibly modified) input
+# or return with a #error mailer. It should not return with a
+# #mailer other than the #error mailer.
+#
+
+SParse0
+R<@> $@ <@> special case error msgs
+R$* : $* ; <@> $#error $@ 5.1.3 $: "_CODE553 List:; syntax illegal for recipient addresses"
+R@ <@ $* > < @ $1 > catch "@@host" bogosity
+R<@ $+> $#error $@ 5.1.3 $: "_CODE553 User address required"
+R$+ <@> $#error $@ 5.1.3 $: "_CODE553 Hostname required"
+R$* $: <> $1
+dnl allow tricks like [host1]:[host2]
+R<> $* < @ [ $* ] : $+ > $* $1 < @ [ $2 ] : $3 > $4
+R<> $* < @ [ $* ] , $+ > $* $1 < @ [ $2 ] , $3 > $4
+dnl but no a@[b]c
+R<> $* < @ [ $* ] $+ > $* $#error $@ 5.1.2 $: "_CODE553 Invalid address"
+R<> $* < @ [ $+ ] > $* $1 < @ [ $2 ] > $3
+R<> $* <$* : $* > $* $#error $@ 5.1.3 $: "_CODE553 Colon illegal in host name part"
+R<> $* $1
+R$* < @ . $* > $* $#error $@ 5.1.2 $: "_CODE553 Invalid host name"
+R$* < @ $* .. $* > $* $#error $@ 5.1.2 $: "_CODE553 Invalid host name"
+dnl no a@b@
+R$* < @ $* @ > $* $#error $@ 5.1.2 $: "_CODE553 Invalid route address"
+dnl no a@b@c
+R$* @ $* < @ $* > $* $#error $@ 5.1.3 $: "_CODE553 Invalid route address"
+dnl comma only allowed before @; this check is not complete
+R$* , $~O $* $#error $@ 5.1.3 $: "_CODE553 Invalid route address"
+
+ifdef(`_STRICT_RFC821_', `# more RFC 821 checks
+R$* . < @ $* > $* $#error $@ 5.1.2 $: "_CODE553 Local part must not end with a dot"
+R. $* < @ $* > $* $#error $@ 5.1.2 $: "_CODE553 Local part must not begin with a dot"
+dnl', `dnl')
+
+# now delete the local info -- note $=O to find characters that cause forwarding
+R$* < @ > $* $@ $>Parse0 $>canonify $1 user@ => user
+R< @ $=w . > : $* $@ $>Parse0 $>canonify $2 @here:... -> ...
+R$- < @ $=w . > $: $(dequote $1 $) < @ $2 . > dequote "foo"@here
+R< @ $+ > $#error $@ 5.1.3 $: "_CODE553 User address required"
+R$* $=O $* < @ $=w . > $@ $>Parse0 $>canonify $1 $2 $3 ...@here -> ...
+R$- $: $(dequote $1 $) < @ *LOCAL* > dequote "foo"
+R< @ *LOCAL* > $#error $@ 5.1.3 $: "_CODE553 User address required"
+R$* $=O $* < @ *LOCAL* >
+ $@ $>Parse0 $>canonify $1 $2 $3 ...@*LOCAL* -> ...
+R$* < @ *LOCAL* > $: $1
+
+#
+# Parse1 -- the bottom half of ruleset 0.
+#
+
+SParse1
+ifdef(`_LDAP_ROUTING_', `dnl
+# handle LDAP routing for hosts in $={LDAPRoute}
+R$+ < @ $={LDAPRoute} . > $: $>LDAPExpand <$1 < @ $2 . >> <$1 @ $2> <>
+R$+ < @ $={LDAPRouteEquiv} . > $: $>LDAPExpand <$1 < @ $2 . >> <$1 @ $M> <>',
+`dnl')
+
+ifdef(`_MAILER_smtp_',
+`# handle numeric address spec
+dnl there is no check whether this is really an IP number
+R$* < @ [ $+ ] > $* $: $>ParseLocal $1 < @ [ $2 ] > $3 numeric internet spec
+R$* < @ [ $+ ] > $* $: $1 < @ [ $2 ] : $S > $3 Add smart host to path
+R$* < @ [ $+ ] : > $* $#_SMTP_ $@ [$2] $: $1 < @ [$2] > $3 no smarthost: send
+R$* < @ [ $+ ] : $- : $*> $* $#$3 $@ $4 $: $1 < @ [$2] > $5 smarthost with mailer
+R$* < @ [ $+ ] : $+ > $* $#_SMTP_ $@ $3 $: $1 < @ [$2] > $4 smarthost without mailer',
+ `dnl')
+
+ifdef(`_VIRTUSER_TABLE_', `dnl
+# handle virtual users
+ifdef(`_VIRTUSER_STOP_ONE_LEVEL_RECURSION_',`dnl
+dnl this is not a documented option
+dnl it stops looping in virtusertable mapping if input and output
+dnl are identical, i.e., if address A is mapped to A.
+dnl it does not deal with multi-level recursion
+# handle full domains in RHS of virtusertable
+R$+ < @ $+ > $: $(macro {RecipientAddress} $) $1 < @ $2 >
+R$+ < @ $+ > $: <?> $1 < @ $2 > $| $>final $1 < @ $2 >
+R<?> $+ $| $+ $: $1 $(macro {RecipientAddress} $@ $2 $)
+R<?> $+ $| $* $: $1',
+`dnl')
+R$+ $: <!> $1 Mark for lookup
+dnl input: <!> local<@domain>
+ifdef(`_VIRTUSER_ENTIRE_DOMAIN_',
+`R<!> $+ < @ $* $={VirtHost} . > $: < $(virtuser $1 @ $2 $3 $@ $1 $: @ $) > $1 < @ $2 $3 . >',
+`R<!> $+ < @ $={VirtHost} . > $: < $(virtuser $1 @ $2 $@ $1 $: @ $) > $1 < @ $2 . >')
+dnl input: <result-of-lookup | @> local<@domain> | <!> local<@domain>
+R<!> $+ < @ $=w . > $: < $(virtuser $1 @ $2 $@ $1 $: @ $) > $1 < @ $2 . >
+dnl if <@> local<@domain>: no match but try lookup
+dnl user+detail: try user++@domain if detail not empty
+R<@> $+ + $+ < @ $* . >
+ $: < $(virtuser $1 + + @ $3 $@ $1 $@ $2 $@ +$2 $: @ $) > $1 + $2 < @ $3 . >
+dnl user+detail: try user+*@domain
+R<@> $+ + $* < @ $* . >
+ $: < $(virtuser $1 + * @ $3 $@ $1 $@ $2 $@ +$2 $: @ $) > $1 + $2 < @ $3 . >
+dnl user+detail: try user@domain
+R<@> $+ + $* < @ $* . >
+ $: < $(virtuser $1 @ $3 $@ $1 $@ $2 $@ +$2 $: @ $) > $1 + $2 < @ $3 . >
+dnl try default entry: @domain
+dnl ++@domain
+R<@> $+ + $+ < @ $+ . > $: < $(virtuser + + @ $3 $@ $1 $@ $2 $@ +$2 $: @ $) > $1 + $2 < @ $3 . >
+dnl +*@domain
+R<@> $+ + $* < @ $+ . > $: < $(virtuser + * @ $3 $@ $1 $@ $2 $@ +$2 $: @ $) > $1 + $2 < @ $3 . >
+dnl @domain if +detail exists
+dnl if no match, change marker to prevent a second @domain lookup
+R<@> $+ + $* < @ $+ . > $: < $(virtuser @ $3 $@ $1 $@ $2 $@ +$2 $: ! $) > $1 + $2 < @ $3 . >
+dnl without +detail
+R<@> $+ < @ $+ . > $: < $(virtuser @ $2 $@ $1 $: @ $) > $1 < @ $2 . >
+dnl no match
+R<@> $+ $: $1
+dnl remove mark
+R<!> $+ $: $1
+R< error : $-.$-.$- : $+ > $* $#error $@ $1.$2.$3 $: $4
+R< error : $- $+ > $* $#error $@ $(dequote $1 $) $: $2
+ifdef(`_VIRTUSER_STOP_ONE_LEVEL_RECURSION_',`dnl
+# check virtuser input address against output address, if same, skip recursion
+R< $+ > $+ < @ $+ > $: < $1 > $2 < @ $3 > $| $1
+# it is the same: stop now
+R< $+ > $+ < @ $+ > $| $&{RecipientAddress} $: $>ParseLocal $>Parse0 $>canonify $1
+R< $+ > $+ < @ $+ > $| $* $: < $1 > $2 < @ $3 >
+dnl', `dnl')
+dnl this is not a documented option
+dnl it performs no looping at all for virtusertable
+ifdef(`_NO_VIRTUSER_RECURSION_',
+`R< $+ > $+ < @ $+ > $: $>ParseLocal $>Parse0 $>canonify $1',
+`R< $+ > $+ < @ $+ > $: $>Recurse $1')
+dnl', `dnl')
+
+# short circuit local delivery so forwarded email works
+ifdef(`_MAILER_usenet_', `dnl
+R$+ . USENET < @ $=w . > $#usenet $@ usenet $: $1 handle usenet specially', `dnl')
+
+
+ifdef(`_STICKY_LOCAL_DOMAIN_',
+`R$+ < @ $=w . > $: < $H > $1 < @ $2 . > first try hub
+R< $+ > $+ < $+ > $>MailerToTriple < $1 > $2 < $3 > yep ....
+dnl $H empty (but @$=w.)
+R< > $+ + $* < $+ > $#_LOCAL_ $: $1 + $2 plussed name?
+R< > $+ < $+ > $#_LOCAL_ $: @ $1 nope, local address',
+`R$=L < @ $=w . > $#_LOCAL_ $: @ $1 special local names
+R$+ < @ $=w . > $#_LOCAL_ $: $1 regular local name')
+
+ifdef(`_MAILER_TABLE_', `dnl
+# not local -- try mailer table lookup
+R$* <@ $+ > $* $: < $2 > $1 < @ $2 > $3 extract host name
+R< $+ . > $* $: < $1 > $2 strip trailing dot
+R< $+ > $* $: < $(mailertable $1 $) > $2 lookup
+dnl it is $~[ instead of $- to avoid matches on IPv6 addresses
+R< $~[ : $* > $* $>MailerToTriple < $1 : $2 > $3 check -- resolved?
+R< $+ > $* $: $>Mailertable <$1> $2 try domain',
+`dnl')
+undivert(4)dnl UUCP rules from `MAILER(uucp)'
+
+ifdef(`_NO_UUCP_', `dnl',
+`# resolve remotely connected UUCP links (if any)
+ifdef(`_CLASS_V_',
+`R$* < @ $=V . UUCP . > $* $: $>MailerToTriple < $V > $1 <@$2.UUCP.> $3',
+ `dnl')
+ifdef(`_CLASS_W_',
+`R$* < @ $=W . UUCP . > $* $: $>MailerToTriple < $W > $1 <@$2.UUCP.> $3',
+ `dnl')
+ifdef(`_CLASS_X_',
+`R$* < @ $=X . UUCP . > $* $: $>MailerToTriple < $X > $1 <@$2.UUCP.> $3',
+ `dnl')')
+
+# resolve fake top level domains by forwarding to other hosts
+ifdef(`BITNET_RELAY',
+`R$*<@$+.BITNET.>$* $: $>MailerToTriple < $B > $1 <@$2.BITNET.> $3 user@host.BITNET',
+ `dnl')
+ifdef(`DECNET_RELAY',
+`R$*<@$+.DECNET.>$* $: $>MailerToTriple < $C > $1 <@$2.DECNET.> $3 user@host.DECNET',
+ `dnl')
+ifdef(`_MAILER_pop_',
+`R$+ < @ POP. > $#pop $: $1 user@POP',
+ `dnl')
+ifdef(`_MAILER_fax_',
+`R$+ < @ $+ .FAX. > $#fax $@ $2 $: $1 user@host.FAX',
+`ifdef(`FAX_RELAY',
+`R$*<@$+.FAX.>$* $: $>MailerToTriple < $F > $1 <@$2.FAX.> $3 user@host.FAX',
+ `dnl')')
+
+ifdef(`UUCP_RELAY',
+`# forward non-local UUCP traffic to our UUCP relay
+R$*<@$*.UUCP.>$* $: $>MailerToTriple < $Y > $1 <@$2.UUCP.> $3 uucp mail',
+`ifdef(`_MAILER_uucp_',
+`# forward other UUCP traffic straight to UUCP
+R$* < @ $+ .UUCP. > $* $#_UUCP_ $@ $2 $: $1 < @ $2 .UUCP. > $3 user@host.UUCP',
+ `dnl')')
+ifdef(`_MAILER_usenet_', `
+# addresses sent to net.group.USENET will get forwarded to a newsgroup
+R$+ . USENET $#usenet $@ usenet $: $1',
+ `dnl')
+
+ifdef(`_LOCAL_RULES_',
+`# figure out what should stay in our local mail system
+undivert(1)', `dnl')
+
+# pass names that still have a host to a smarthost (if defined)
+R$* < @ $* > $* $: $>MailerToTriple < $S > $1 < @ $2 > $3 glue on smarthost name
+
+# deal with other remote names
+ifdef(`_MAILER_smtp_',
+`R$* < @$* > $* $#_SMTP_ $@ $2 $: $1 < @ $2 > $3 user@host.domain',
+`R$* < @$* > $* $#error $@ 5.1.2 $: "_CODE553 Unrecognized host name " $2')
+
+# handle locally delivered names
+R$=L $#_LOCAL_ $: @ $1 special local names
+R$+ $#_LOCAL_ $: $1 regular local names
+
+###########################################################################
+### Ruleset 5 -- special rewriting after aliases have been expanded ###
+###########################################################################
+
+SLocal_localaddr
+Slocaladdr=5
+R$+ $: $1 $| $>"Local_localaddr" $1
+R$+ $| $#ok $@ $1 no change
+R$+ $| $#$* $#$2
+R$+ $| $* $: $1
+
+ifdef(`_PRESERVE_LUSER_HOST_', `dnl
+# Preserve rcpt_host in {Host}
+R$+ $: $1 $| $&h $| $&{Host} check h and {Host}
+R$+ $| $| $: $(macro {Host} $@ $) $1 no h or {Host}
+R$+ $| $| $+ $: $1 h not set, {Host} set
+R$+ $| +$* $| $* $: $1 h is +detail, {Host} set
+R$+ $| $* @ $+ $| $* $: $(macro {Host} $@ @$3 $) $1 set {Host} to host in h
+R$+ $| $+ $| $* $: $(macro {Host} $@ @$2 $) $1 set {Host} to h
+')dnl
+
+ifdef(`_FFR_5_', `dnl
+# Preserve host in a macro
+R$+ $: $(macro {LocalAddrHost} $) $1
+R$+ @ $+ $: $(macro {LocalAddrHost} $@ @ $2 $) $1')
+
+ifdef(`_PRESERVE_LOCAL_PLUS_DETAIL_', `', `dnl
+# deal with plussed users so aliases work nicely
+R$+ + * $#_LOCAL_ $@ $&h $: $1`'ifdef(`_FFR_5_', ` $&{LocalAddrHost}')
+R$+ + $* $#_LOCAL_ $@ + $2 $: $1 + *`'ifdef(`_FFR_5_', ` $&{LocalAddrHost}')
+')
+# prepend an empty "forward host" on the front
+R$+ $: <> $1
+
+ifdef(`LUSER_RELAY', `dnl
+# send unrecognized local users to a relay host
+ifdef(`_PRESERVE_LOCAL_PLUS_DETAIL_', `dnl
+R< > $+ + $* $: < ? $L > <+ $2> $(user $1 $) look up user+
+R< > $+ $: < ? $L > < > $(user $1 $) look up user
+R< ? $* > < $* > $+ <> $: < > $3 $2 found; strip $L
+R< ? $* > < $* > $+ $: < $1 > $3 $2 not found', `
+R< > $+ $: < $L > $(user $1 $) look up user
+R< $* > $+ <> $: < > $2 found; strip $L')
+ifdef(`_PRESERVE_LUSER_HOST_', `dnl
+R< $+ > $+ $: < $1 > $2 $&{Host}')
+dnl')
+
+ifdef(`MAIL_HUB', `dnl
+R< > $+ $: < $H > $1 try hub', `dnl')
+ifdef(`LOCAL_RELAY', `dnl
+R< > $+ $: < $R > $1 try relay', `dnl')
+ifdef(`_PRESERVE_LOCAL_PLUS_DETAIL_', `dnl
+R< > $+ $@ $1', `dnl
+R< > $+ $: < > < $1 <> $&h > nope, restore +detail
+ifdef(`_PRESERVE_LUSER_HOST_', `dnl
+R< > < $+ @ $+ <> + $* > $: < > < $1 + $3 @ $2 > check whether +detail')
+R< > < $+ <> + $* > $: < > < $1 + $2 > check whether +detail
+R< > < $+ <> $* > $: < > < $1 > else discard
+R< > < $+ + $* > $* < > < $1 > + $2 $3 find the user part
+R< > < $+ > + $* $#_LOCAL_ $@ $2 $: @ $1`'ifdef(`_FFR_5_', ` $&{LocalAddrHost}') strip the extra +
+R< > < $+ > $@ $1 no +detail
+R$+ $: $1 <> $&h add +detail back in
+ifdef(`_PRESERVE_LUSER_HOST_', `dnl
+R$+ @ $+ <> + $* $: $1 + $3 @ $2 check whether +detail')
+R$+ <> + $* $: $1 + $2 check whether +detail
+R$+ <> $* $: $1 else discard')
+R< local : $* > $* $: $>MailerToTriple < local : $1 > $2 no host extension
+R< error : $* > $* $: $>MailerToTriple < error : $1 > $2 no host extension
+ifdef(`_PRESERVE_LUSER_HOST_', `dnl
+dnl it is $~[ instead of $- to avoid matches on IPv6 addresses
+R< $~[ : $+ > $+ @ $+ $: $>MailerToTriple < $1 : $2 > $3 < @ $4 >')
+R< $~[ : $+ > $+ $: $>MailerToTriple < $1 : $2 > $3 < @ $2 >
+ifdef(`_PRESERVE_LUSER_HOST_', `dnl
+R< $+ > $+ @ $+ $@ $>MailerToTriple < $1 > $2 < @ $3 >')
+R< $+ > $+ $@ $>MailerToTriple < $1 > $2 < @ $1 >
+
+ifdef(`_MAILER_TABLE_', `dnl
+ifdef(`_LDAP_ROUTING_', `dnl
+###################################################################
+### Ruleset LDAPMailertable -- mailertable lookup for LDAP ###
+dnl input: <Domain> FullAddress
+###################################################################
+
+SLDAPMailertable
+R< $+ > $* $: < $(mailertable $1 $) > $2 lookup
+R< $~[ : $* > $* $>MailerToTriple < $1 : $2 > $3 check resolved?
+R< $+ > $* $: < $1 > $>Mailertable <$1> $2 try domain
+R< $+ > $#$* $#$2 found
+R< $+ > $* $#_RELAY_ $@ $1 $: $2 not found, direct relay',
+`dnl')
+
+###################################################################
+### Ruleset 90 -- try domain part of mailertable entry ###
+dnl input: LeftPartOfDomain <RightPartOfDomain> FullAddress
+###################################################################
+
+SMailertable=90
+dnl shift and check
+dnl %2 is not documented in cf/README
+R$* <$- . $+ > $* $: $1$2 < $(mailertable .$3 $@ $1$2 $@ $2 $) > $4
+dnl it is $~[ instead of $- to avoid matches on IPv6 addresses
+R$* <$~[ : $* > $* $>MailerToTriple < $2 : $3 > $4 check -- resolved?
+R$* < . $+ > $* $@ $>Mailertable $1 . <$2> $3 no -- strip & try again
+dnl is $2 always empty?
+R$* < $* > $* $: < $(mailertable . $@ $1$2 $) > $3 try "."
+R< $~[ : $* > $* $>MailerToTriple < $1 : $2 > $3 "." found?
+dnl return full address
+R< $* > $* $@ $2 no mailertable match',
+`dnl')
+
+###################################################################
+### Ruleset 95 -- canonify mailer:[user@]host syntax to triple ###
+dnl input: in general: <[mailer:]host> lp<@domain>rest
+dnl <> address -> address
+dnl <error:d.s.n:text> -> error
+dnl <error:keyword:text> -> error
+dnl <error:text> -> error
+dnl <mailer:user@host> lp<@domain>rest -> mailer host user
+dnl <mailer:host> address -> mailer host address
+dnl <localdomain> address -> address
+dnl <host> address -> relay host address
+###################################################################
+
+SMailerToTriple=95
+R< > $* $@ $1 strip off null relay
+R< error : $-.$-.$- : $+ > $* $#error $@ $1.$2.$3 $: $4
+R< error : $- : $+ > $* $#error $@ $(dequote $1 $) $: $2
+R< error : $+ > $* $#error $: $1
+R< local : $* > $* $>CanonLocal < $1 > $2
+dnl it is $~[ instead of $- to avoid matches on IPv6 addresses
+R< $~[ : $+ @ $+ > $*<$*>$* $# $1 $@ $3 $: $2<@$3> use literal user
+R< $~[ : $+ > $* $# $1 $@ $2 $: $3 try qualified mailer
+R< $=w > $* $@ $2 delete local host
+R< $+ > $* $#_RELAY_ $@ $1 $: $2 use unqualified mailer
+
+###################################################################
+### Ruleset CanonLocal -- canonify local: syntax ###
+dnl input: <user> address
+dnl <x> <@host> : rest -> Recurse rest
+dnl <x> p1 $=O p2 <@host> -> Recurse p1 $=O p2
+dnl <> user <@host> rest -> local user@host user
+dnl <> user -> local user user
+dnl <user@host> lp <@domain> rest -> <user> lp <@host> [cont]
+dnl <user> lp <@host> rest -> local lp@host user
+dnl <user> lp -> local lp user
+###################################################################
+
+SCanonLocal
+# strip local host from routed addresses
+R< $* > < @ $+ > : $+ $@ $>Recurse $3
+R< $* > $+ $=O $+ < @ $+ > $@ $>Recurse $2 $3 $4
+
+# strip trailing dot from any host name that may appear
+R< $* > $* < @ $* . > $: < $1 > $2 < @ $3 >
+
+# handle local: syntax -- use old user, either with or without host
+R< > $* < @ $* > $* $#_LOCAL_ $@ $1@$2 $: $1
+R< > $+ $#_LOCAL_ $@ $1 $: $1
+
+# handle local:user@host syntax -- ignore host part
+R< $+ @ $+ > $* < @ $* > $: < $1 > $3 < @ $4 >
+
+# handle local:user syntax
+R< $+ > $* <@ $* > $* $#_LOCAL_ $@ $2@$3 $: $1
+R< $+ > $* $#_LOCAL_ $@ $2 $: $1
+
+###################################################################
+### Ruleset 93 -- convert header names to masqueraded form ###
+###################################################################
+
+SMasqHdr=93
+
+ifdef(`_GENERICS_TABLE_', `dnl
+# handle generics database
+ifdef(`_GENERICS_ENTIRE_DOMAIN_',
+dnl if generics should be applied add a @ as mark
+`R$+ < @ $* $=G . > $: < $1@$2$3 > $1 < @ $2$3 . > @ mark',
+`R$+ < @ $=G . > $: < $1@$2 > $1 < @ $2 . > @ mark')
+R$+ < @ *LOCAL* > $: < $1@$j > $1 < @ *LOCAL* > @ mark
+dnl workspace: either user<@domain> or <user@domain> user <@domain> @
+dnl ignore the first case for now
+dnl if it has the mark lookup full address
+dnl broken: %1 is full address not just detail
+R< $+ > $+ < $* > @ $: < $(generics $1 $: @ $1 $) > $2 < $3 >
+dnl workspace: ... or <match|@user@domain> user <@domain>
+dnl no match, try user+detail@domain
+R<@$+ + $* @ $+> $+ < @ $+ >
+ $: < $(generics $1+*@$3 $@ $2 $:@$1 + $2@$3 $) > $4 < @ $5 >
+R<@$+ + $* @ $+> $+ < @ $+ >
+ $: < $(generics $1@$3 $: $) > $4 < @ $5 >
+dnl no match, remove mark
+R<@$+ > $+ < @ $+ > $: < > $2 < @ $3 >
+dnl no match, try @domain for exceptions
+R< > $+ < @ $+ . > $: < $(generics @$2 $@ $1 $: $) > $1 < @ $2 . >
+dnl workspace: ... or <match> user <@domain>
+dnl no match, try local part
+R< > $+ < @ $+ > $: < $(generics $1 $: $) > $1 < @ $2 >
+R< > $+ + $* < @ $+ > $: < $(generics $1+* $@ $2 $: $) > $1 + $2 < @ $3 >
+R< > $+ + $* < @ $+ > $: < $(generics $1 $: $) > $1 + $2 < @ $3 >
+R< $* @ $* > $* < $* > $@ $>canonify $1 @ $2 found qualified
+R< $+ > $* < $* > $: $>canonify $1 @ *LOCAL* found unqualified
+R< > $* $: $1 not found',
+`dnl')
+
+# do not masquerade anything in class N
+R$* < @ $* $=N . > $@ $1 < @ $2 $3 . >
+
+ifdef(`MASQUERADE_NAME', `dnl
+# special case the users that should be exposed
+R$=E < @ *LOCAL* > $@ $1 < @ $j . > leave exposed
+ifdef(`_MASQUERADE_ENTIRE_DOMAIN_',
+`R$=E < @ $* $=M . > $@ $1 < @ $2 $3 . >',
+`R$=E < @ $=M . > $@ $1 < @ $2 . >')
+ifdef(`_LIMITED_MASQUERADE_', `dnl',
+`R$=E < @ $=w . > $@ $1 < @ $2 . >')
+
+# handle domain-specific masquerading
+ifdef(`_MASQUERADE_ENTIRE_DOMAIN_',
+`R$* < @ $* $=M . > $* $: $1 < @ $2 $3 . @ $M > $4 convert masqueraded doms',
+`R$* < @ $=M . > $* $: $1 < @ $2 . @ $M > $3 convert masqueraded doms')
+ifdef(`_LIMITED_MASQUERADE_', `dnl',
+`R$* < @ $=w . > $* $: $1 < @ $2 . @ $M > $3')
+R$* < @ *LOCAL* > $* $: $1 < @ $j . @ $M > $2
+R$* < @ $+ @ > $* $: $1 < @ $2 > $3 $M is null
+R$* < @ $+ @ $+ > $* $: $1 < @ $3 . > $4 $M is not null
+dnl', `dnl no masquerading
+dnl just fix *LOCAL* leftovers
+R$* < @ *LOCAL* > $@ $1 < @ $j . >')
+
+###################################################################
+### Ruleset 94 -- convert envelope names to masqueraded form ###
+###################################################################
+
+SMasqEnv=94
+ifdef(`_MASQUERADE_ENVELOPE_',
+`R$+ $@ $>MasqHdr $1',
+`R$* < @ *LOCAL* > $* $: $1 < @ $j . > $2')
+
+###################################################################
+### Ruleset 98 -- local part of ruleset zero (can be null) ###
+###################################################################
+
+SParseLocal=98
+undivert(3)dnl LOCAL_RULE_0
+
+ifdef(`_LDAP_ROUTING_', `dnl
+######################################################################
+### LDAPExpand: Expand address using LDAP routing
+###
+### Parameters:
+### <$1> -- parsed address (user < @ domain . >) (pass through)
+### <$2> -- RFC822 address (user @ domain) (used for lookup)
+### <$3> -- +detail information
+###
+### Returns:
+### Mailer triplet ($#mailer $@ host $: address)
+### Parsed address (user < @ domain . >)
+######################################################################
+
+# SMTP operation modes
+C{SMTPOpModes} s d D
+
+SLDAPExpand
+# do the LDAP lookups
+R<$+><$+><$*> $: <$(ldapmra $2 $: $)> <$(ldapmh $2 $: $)> <$1> <$2> <$3>
+
+# look for temporary failures and...
+R<$* <TMPF>> <$*> <$+> <$+> <$*> $: $&{opMode} $| TMPF <$&{addr_type}> $| $3
+R<$*> <$* <TMPF>> <$+> <$+> <$*> $: $&{opMode} $| TMPF <$&{addr_type}> $| $3
+ifelse(_LDAP_ROUTE_MAPTEMP_, `_TEMPFAIL_', `dnl
+# ... temp fail RCPT SMTP commands
+R$={SMTPOpModes} $| TMPF <e r> $| $+ $#error $@ 4.3.0 $: "451 Temporary system failure. Please try again later."')
+# ... return original address for MTA to queue up
+R$* $| TMPF <$*> $| $+ $@ $3
+
+# if mailRoutingAddress and local or non-existant mailHost,
+# return the new mailRoutingAddress
+ifelse(_LDAP_ROUTE_DETAIL_, `_PRESERVE_', `dnl
+R<$+@$+> <$=w> <$+> <$+> <$*> $@ $>Parse0 $>canonify $1 $6 @ $2
+R<$+@$+> <> <$+> <$+> <$*> $@ $>Parse0 $>canonify $1 $5 @ $2')
+R<$+> <$=w> <$+> <$+> <$*> $@ $>Parse0 $>canonify $1
+R<$+> <> <$+> <$+> <$*> $@ $>Parse0 $>canonify $1
+
+
+# if mailRoutingAddress and non-local mailHost,
+# relay to mailHost with new mailRoutingAddress
+ifelse(_LDAP_ROUTE_DETAIL_, `_PRESERVE_', `dnl
+ifdef(`_MAILER_TABLE_', `dnl
+# check mailertable for host, relay from there
+R<$+@$+> <$+> <$+> <$+> <$*> $>LDAPMailertable <$3> $>canonify $1 $6 @ $2',
+`R<$+@$+> <$+> <$+> <$+> <$*> $#_RELAY_ $@ $3 $: $>canonify $1 $6 @ $2')')
+ifdef(`_MAILER_TABLE_', `dnl
+# check mailertable for host, relay from there
+R<$+> <$+> <$+> <$+> <$*> $>LDAPMailertable <$2> $>canonify $1',
+`R<$+> <$+> <$+> <$+> <$*> $#_RELAY_ $@ $2 $: $>canonify $1')
+
+# if no mailRoutingAddress and local mailHost,
+# return original address
+R<> <$=w> <$+> <$+> <$*> $@ $2
+
+
+# if no mailRoutingAddress and non-local mailHost,
+# relay to mailHost with original address
+ifdef(`_MAILER_TABLE_', `dnl
+# check mailertable for host, relay from there
+R<> <$+> <$+> <$+> <$*> $>LDAPMailertable <$1> $2',
+`R<> <$+> <$+> <$+> <$*> $#_RELAY_ $@ $1 $: $2')
+
+ifdef(`_LDAP_ROUTE_DETAIL_',
+`# if no mailRoutingAddress and no mailHost,
+# try without +detail
+R<> <> <$+> <$+ + $* @ $+> <> $@ $>LDAPExpand <$1> <$2 @ $4> <+$3>')dnl
+
+ifdef(`_LDAP_ROUTE_NODOMAIN_', `dnl', `
+# if still no mailRoutingAddress and no mailHost,
+# try @domain
+ifelse(_LDAP_ROUTE_DETAIL_, `_PRESERVE_', `dnl
+R<> <> <$+> <$+ + $* @ $+> <> $@ $>LDAPExpand <$1> <@ $4> <+$3>')
+R<> <> <$+> <$+ @ $+> <$*> $@ $>LDAPExpand <$1> <@ $3> <$4>')
+
+# if no mailRoutingAddress and no mailHost and this was a domain attempt,
+ifelse(_LDAP_ROUTING_, `_MUST_EXIST_', `dnl
+# user does not exist
+R<> <> <$+> <@ $+> <$*> $: <?> < $&{addr_type} > < $1 >
+# only give error for envelope recipient
+R<?> <e r> <$+> $#error $@ nouser $: "550 User unknown"
+ifdef(`_LDAP_SENDER_MUST_EXIST_', `dnl
+# and the sender too
+R<?> <e s> <$+> $#error $@ nouser $: "550 User unknown"')
+R<?> <$*> <$+> $@ $2',
+`dnl
+# return the original address
+R<> <> <$+> <@ $+> <$*> $@ $1')',
+`dnl')
+
+ifelse(substr(confDELIVERY_MODE,0,1), `d', `errprint(`WARNING: Antispam rules not available in deferred delivery mode.
+')')
+ifdef(`_ACCESS_TABLE_', `dnl', `divert(-1)')
+######################################################################
+### D: LookUpDomain -- search for domain in access database
+###
+### Parameters:
+### <$1> -- key (domain name)
+### <$2> -- default (what to return if not found in db)
+dnl must not be empty
+### <$3> -- mark (must be <(!|+) single-token>)
+### ! does lookup only with tag
+### + does lookup with and without tag
+### <$4> -- passthru (additional data passed unchanged through)
+dnl returns: <default> <passthru>
+dnl <result> <passthru>
+######################################################################
+
+SD
+dnl workspace <key> <default> <passthru> <mark>
+dnl lookup with tag (in front, no delimiter here)
+dnl 2 3 4 5
+R<$*> <$+> <$- $-> <$*> $: < $(access $4`'_TAG_DELIM_`'$1 $: ? $) > <$1> <$2> <$3 $4> <$5>
+dnl workspace <result-of-lookup|?> <key> <default> <passthru> <mark>
+dnl lookup without tag?
+dnl 1 2 3 4
+R<?> <$+> <$+> <+ $-> <$*> $: < $(access $1 $: ? $) > <$1> <$2> <+ $3> <$4>
+ifdef(`_LOOKUPDOTDOMAIN_', `dnl omit first component: lookup .rest
+dnl XXX apply this also to IP addresses?
+dnl currently it works the wrong way round for [1.2.3.4]
+dnl 1 2 3 4 5 6
+R<?> <$+.$+> <$+> <$- $-> <$*> $: < $(access $5`'_TAG_DELIM_`'.$2 $: ? $) > <$1.$2> <$3> <$4 $5> <$6>
+dnl 1 2 3 4 5
+R<?> <$+.$+> <$+> <+ $-> <$*> $: < $(access .$2 $: ? $) > <$1.$2> <$3> <+ $4> <$5>', `dnl')
+ifdef(`_ACCESS_SKIP_', `dnl
+dnl found SKIP: return <default> and <passthru>
+dnl 1 2 3 4 5
+R<SKIP> <$+> <$+> <$- $-> <$*> $@ <$2> <$5>', `dnl')
+dnl not found: IPv4 net (no check is done whether it is an IP number!)
+dnl 1 2 3 4 5 6
+R<?> <[$+.$-]> <$+> <$- $-> <$*> $@ $>D <[$1]> <$3> <$4 $5> <$6>
+ifdef(`NO_NETINET6', `dnl',
+`dnl not found: IPv6 net
+dnl (could be merged with previous rule if we have a class containing .:)
+dnl 1 2 3 4 5 6
+R<?> <[$+::$-]> <$+> <$- $-> <$*> $: $>D <[$1]> <$3> <$4 $5> <$6>
+R<?> <[$+:$-]> <$+> <$- $-> <$*> $: $>D <[$1]> <$3> <$4 $5> <$6>')
+dnl not found, but subdomain: try again
+dnl 1 2 3 4 5 6
+R<?> <$+.$+> <$+> <$- $-> <$*> $@ $>D <$2> <$3> <$4 $5> <$6>
+ifdef(`_FFR_LOOKUPTAG_', `dnl lookup Tag:
+dnl 1 2 3 4
+R<?> <$+> <$+> <! $-> <$*> $: < $(access $3`'_TAG_DELIM_ $: ? $) > <$1> <$2> <! $3> <$4>', `dnl')
+dnl not found, no subdomain: return <default> and <passthru>
+dnl 1 2 3 4 5
+R<?> <$+> <$+> <$- $-> <$*> $@ <$2> <$5>
+ifdef(`_ATMPF_', `dnl tempfail?
+dnl 2 3 4 5 6
+R<$* _ATMPF_> <$+> <$+> <$- $-> <$*> $@ <_ATMPF_> <$6>', `dnl')
+dnl return <result of lookup> and <passthru>
+dnl 2 3 4 5 6
+R<$*> <$+> <$+> <$- $-> <$*> $@ <$1> <$6>
+
+######################################################################
+### A: LookUpAddress -- search for host address in access database
+###
+### Parameters:
+### <$1> -- key (dot quadded host address)
+### <$2> -- default (what to return if not found in db)
+dnl must not be empty
+### <$3> -- mark (must be <(!|+) single-token>)
+### ! does lookup only with tag
+### + does lookup with and without tag
+### <$4> -- passthru (additional data passed through)
+dnl returns: <default> <passthru>
+dnl <result> <passthru>
+######################################################################
+
+SA
+dnl lookup with tag
+dnl 2 3 4 5
+R<$+> <$+> <$- $-> <$*> $: < $(access $4`'_TAG_DELIM_`'$1 $: ? $) > <$1> <$2> <$3 $4> <$5>
+dnl lookup without tag
+dnl 1 2 3 4
+R<?> <$+> <$+> <+ $-> <$*> $: < $(access $1 $: ? $) > <$1> <$2> <+ $3> <$4>
+dnl workspace <result-of-lookup|?> <key> <default> <mark> <passthru>
+ifdef(`_ACCESS_SKIP_', `dnl
+dnl found SKIP: return <default> and <passthru>
+dnl 1 2 3 4 5
+R<SKIP> <$+> <$+> <$- $-> <$*> $@ <$2> <$5>', `dnl')
+ifdef(`NO_NETINET6', `dnl',
+`dnl no match; IPv6: remove last part
+dnl 1 2 3 4 5 6
+R<?> <$+::$-> <$+> <$- $-> <$*> $@ $>A <$1> <$3> <$4 $5> <$6>
+R<?> <$+:$-> <$+> <$- $-> <$*> $@ $>A <$1> <$3> <$4 $5> <$6>')
+dnl no match; IPv4: remove last part
+dnl 1 2 3 4 5 6
+R<?> <$+.$-> <$+> <$- $-> <$*> $@ $>A <$1> <$3> <$4 $5> <$6>
+dnl no match: return default
+dnl 1 2 3 4 5
+R<?> <$+> <$+> <$- $-> <$*> $@ <$2> <$5>
+ifdef(`_ATMPF_', `dnl tempfail?
+dnl 2 3 4 5 6
+R<$* _ATMPF_> <$+> <$+> <$- $-> <$*> $@ <_ATMPF_> <$6>', `dnl')
+dnl match: return result
+dnl 2 3 4 5 6
+R<$*> <$+> <$+> <$- $-> <$*> $@ <$1> <$6>
+dnl endif _ACCESS_TABLE_
+divert(0)
+######################################################################
+### CanonAddr -- Convert an address into a standard form for
+### relay checking. Route address syntax is
+### crudely converted into a %-hack address.
+###
+### Parameters:
+### $1 -- full recipient address
+###
+### Returns:
+### parsed address, not in source route form
+dnl user%host%host<@domain>
+dnl host!user<@domain>
+######################################################################
+
+SCanonAddr
+R$* $: $>Parse0 $>canonify $1 make domain canonical
+ifdef(`_USE_DEPRECATED_ROUTE_ADDR_',`dnl
+R< @ $+ > : $* @ $* < @ $1 > : $2 % $3 change @ to % in src route
+R$* < @ $+ > : $* : $* $3 $1 < @ $2 > : $4 change to % hack.
+R$* < @ $+ > : $* $3 $1 < @ $2 >
+dnl')
+
+######################################################################
+### ParseRecipient -- Strip off hosts in $=R as well as possibly
+### $* $=m or the access database.
+### Check user portion for host separators.
+###
+### Parameters:
+### $1 -- full recipient address
+###
+### Returns:
+### parsed, non-local-relaying address
+######################################################################
+
+SParseRecipient
+dnl mark and canonify address
+R$* $: <?> $>CanonAddr $1
+dnl workspace: <?> localpart<@domain[.]>
+R<?> $* < @ $* . > <?> $1 < @ $2 > strip trailing dots
+dnl workspace: <?> localpart<@domain>
+R<?> $- < @ $* > $: <?> $(dequote $1 $) < @ $2 > dequote local part
+
+# if no $=O character, no host in the user portion, we are done
+R<?> $* $=O $* < @ $* > $: <NO> $1 $2 $3 < @ $4>
+dnl no $=O in localpart: return
+R<?> $* $@ $1
+
+dnl workspace: <NO> localpart<@domain>, where localpart contains $=O
+dnl mark everything which has an "authorized" domain with <RELAY>
+ifdef(`_RELAY_ENTIRE_DOMAIN_', `dnl
+# if we relay, check username portion for user%host so host can be checked also
+R<NO> $* < @ $* $=m > $: <RELAY> $1 < @ $2 $3 >', `dnl')
+dnl workspace: <(NO|RELAY)> localpart<@domain>, where localpart contains $=O
+dnl if mark is <NO> then change it to <RELAY> if domain is "authorized"
+
+dnl what if access map returns something else than RELAY?
+dnl we are only interested in RELAY entries...
+dnl other To: entries: blacklist recipient; generic entries?
+dnl if it is an error we probably do not want to relay anyway
+ifdef(`_RELAY_HOSTS_ONLY_',
+`R<NO> $* < @ $=R > $: <RELAY> $1 < @ $2 >
+ifdef(`_ACCESS_TABLE_', `dnl
+R<NO> $* < @ $+ > $: <$(access To:$2 $: NO $)> $1 < @ $2 >
+R<NO> $* < @ $+ > $: <$(access $2 $: NO $)> $1 < @ $2 >',`dnl')',
+`R<NO> $* < @ $* $=R > $: <RELAY> $1 < @ $2 $3 >
+ifdef(`_ACCESS_TABLE_', `dnl
+R<NO> $* < @ $+ > $: $>D <$2> <NO> <+ To> <$1 < @ $2 >>
+R<$+> <$+> $: <$1> $2',`dnl')')
+
+
+ifdef(`_RELAY_MX_SERVED_', `dnl
+dnl do "we" ($=w) act as backup MX server for the destination domain?
+R<NO> $* < @ $+ > $: <MX> < : $(mxserved $2 $) : > < $1 < @$2 > >
+R<MX> < : $* <TEMP> : > $* $#TEMP $@ 4.4.0 $: "450 Can not check MX records for recipient host " $1
+dnl yes: mark it as <RELAY>
+R<MX> < $* : $=w. : $* > < $+ > $: <RELAY> $4
+dnl no: put old <NO> mark back
+R<MX> < : $* : > < $+ > $: <NO> $2', `dnl')
+
+dnl do we relay to this recipient domain?
+R<RELAY> $* < @ $* > $@ $>ParseRecipient $1
+dnl something else
+R<$+> $* $@ $2
+
+
+######################################################################
+### check_relay -- check hostname/address on SMTP startup
+######################################################################
+
+ifdef(`_CONTROL_IMMEDIATE_',`dnl
+Scheck_relay
+ifdef(`_RATE_CONTROL_IMMEDIATE_',`dnl
+dnl workspace: ignored...
+R$* $: $>"RateControl" dummy', `dnl')
+ifdef(`_CONN_CONTROL_IMMEDIATE_',`dnl
+dnl workspace: ignored...
+R$* $: $>"ConnControl" dummy', `dnl')
+dnl')
+
+SLocal_check_relay
+Scheck`'_U_`'relay
+ifdef(`_USE_CLIENT_PTR_',`dnl
+R$* $| $* $: $&{client_ptr} $| $2', `dnl')
+R$* $: $1 $| $>"Local_check_relay" $1
+R$* $| $* $| $#$* $#$3
+R$* $| $* $| $* $@ $>"Basic_check_relay" $1 $| $2
+
+SBasic_check_relay
+# check for deferred delivery mode
+R$* $: < $&{deliveryMode} > $1
+R< d > $* $@ deferred
+R< $* > $* $: $2
+
+ifdef(`_ACCESS_TABLE_', `dnl
+dnl workspace: {client_name} $| {client_addr}
+R$+ $| $+ $: $>D < $1 > <?> <+ Connect> < $2 >
+dnl workspace: <result-of-lookup> <{client_addr}>
+dnl OR $| $+ if client_name is empty
+R $| $+ $: $>A < $1 > <?> <+ Connect> <> empty client_name
+dnl workspace: <result-of-lookup> <{client_addr}>
+R<?> <$+> $: $>A < $1 > <?> <+ Connect> <> no: another lookup
+dnl workspace: <result-of-lookup> (<>|<{client_addr}>)
+R<?> <$*> $: OK found nothing
+dnl workspace: <result-of-lookup> (<>|<{client_addr}>) | OK
+R<$={Accept}> <$*> $@ $1 return value of lookup
+R<REJECT> <$*> $#error ifdef(`confREJECT_MSG', `$: confREJECT_MSG', `$@ 5.7.1 $: "550 Access denied"')
+R<DISCARD> <$*> $#discard $: discard
+R<QUARANTINE:$+> <$*> $#error $@ quarantine $: $1
+dnl error tag
+R<ERROR:$-.$-.$-:$+> <$*> $#error $@ $1.$2.$3 $: $4
+R<ERROR:$+> <$*> $#error $: $1
+ifdef(`_ATMPF_', `R<$* _ATMPF_> <$*> $#error $@ 4.3.0 $: "451 Temporary system failure. Please try again later."', `dnl')
+dnl generic error from access map
+R<$+> <$*> $#error $: $1', `dnl')
+
+ifdef(`_RBL_',`dnl
+# DNS based IP address spam list
+dnl workspace: ignored...
+R$* $: $&{client_addr}
+R$-.$-.$-.$- $: <?> $(host $4.$3.$2.$1._RBL_. $: OK $)
+R<?>OK $: OKSOFAR
+R<?>$+ $#error $@ 5.7.1 $: "550 Rejected: " $&{client_addr} " listed at _RBL_"',
+`dnl')
+ifdef(`_RATE_CONTROL_',`dnl
+ifdef(`_RATE_CONTROL_IMMEDIATE_',`', `dnl
+dnl workspace: ignored...
+R$* $: $>"RateControl" dummy')', `dnl')
+ifdef(`_CONN_CONTROL_',`dnl
+ifdef(`_CONN_CONTROL_IMMEDIATE_',`',`dnl
+dnl workspace: ignored...
+R$* $: $>"ConnControl" dummy')', `dnl')
+undivert(8)
+
+######################################################################
+### check_mail -- check SMTP ``MAIL FROM:'' command argument
+######################################################################
+
+SLocal_check_mail
+Scheck`'_U_`'mail
+R$* $: $1 $| $>"Local_check_mail" $1
+R$* $| $#$* $#$2
+R$* $| $* $@ $>"Basic_check_mail" $1
+
+SBasic_check_mail
+# check for deferred delivery mode
+R$* $: < $&{deliveryMode} > $1
+R< d > $* $@ deferred
+R< $* > $* $: $2
+
+# authenticated?
+dnl done first: we can require authentication for every mail transaction
+dnl workspace: address as given by MAIL FROM: (sender)
+R$* $: $1 $| $>"tls_client" $&{verify} $| MAIL
+R$* $| $#$+ $#$2
+dnl undo damage: remove result of tls_client call
+R$* $| $* $: $1
+
+dnl workspace: address as given by MAIL FROM:
+R<> $@ <OK> we MUST accept <> (RFC 1123)
+ifdef(`_ACCEPT_UNQUALIFIED_SENDERS_',`dnl',`dnl
+dnl do some additional checks
+dnl no user@host
+dnl no user@localhost (if nonlocal sender)
+dnl this is a pretty simple canonification, it will not catch every case
+dnl just make sure the address has <> around it (which is required by
+dnl the RFC anyway, maybe we should complain if they are missing...)
+dnl dirty trick: if it is user@host, just add a dot: user@host. this will
+dnl not be modified by host lookups.
+R$+ $: <?> $1
+R<?><$+> $: <@> <$1>
+R<?>$+ $: <@> <$1>
+dnl workspace: <@> <address>
+dnl prepend daemon_flags
+R$* $: $&{daemon_flags} $| $1
+dnl workspace: ${daemon_flags} $| <@> <address>
+dnl do not allow these at all or only from local systems?
+R$* f $* $| <@> < $* @ $- > $: < ? $&{client_name} > < $3 @ $4 >
+dnl accept unqualified sender: change mark to avoid test
+R$* u $* $| <@> < $* > $: <?> < $3 >
+dnl workspace: ${daemon_flags} $| <@> <address>
+dnl or: <? ${client_name} > <address>
+dnl or: <?> <address>
+dnl remove daemon_flags
+R$* $| $* $: $2
+# handle case of @localhost on address
+R<@> < $* @ localhost > $: < ? $&{client_name} > < $1 @ localhost >
+R<@> < $* @ [127.0.0.1] >
+ $: < ? $&{client_name} > < $1 @ [127.0.0.1] >
+R<@> < $* @ localhost.$m >
+ $: < ? $&{client_name} > < $1 @ localhost.$m >
+ifdef(`_NO_UUCP_', `dnl',
+`R<@> < $* @ localhost.UUCP >
+ $: < ? $&{client_name} > < $1 @ localhost.UUCP >')
+dnl workspace: < ? $&{client_name} > <user@localhost|host>
+dnl or: <@> <address>
+dnl or: <?> <address> (thanks to u in ${daemon_flags})
+R<@> $* $: $1 no localhost as domain
+dnl workspace: < ? $&{client_name} > <user@localhost|host>
+dnl or: <address>
+dnl or: <?> <address> (thanks to u in ${daemon_flags})
+R<? $=w> $* $: $2 local client: ok
+R<? $+> <$+> $#error $@ 5.5.4 $: "_CODE553 Real domain name required for sender address"
+dnl remove <?> (happens only if ${client_name} == "" or u in ${daemon_flags})
+R<?> $* $: $1')
+dnl workspace: address (or <address>)
+R$* $: <?> $>CanonAddr $1 canonify sender address and mark it
+dnl workspace: <?> CanonicalAddress (i.e. address in canonical form localpart<@host>)
+dnl there is nothing behind the <@host> so no trailing $* needed
+R<?> $* < @ $+ . > <?> $1 < @ $2 > strip trailing dots
+# handle non-DNS hostnames (*.bitnet, *.decnet, *.uucp, etc)
+R<?> $* < @ $* $=P > $: <_RES_OK_> $1 < @ $2 $3 >
+dnl workspace <mark> CanonicalAddress where mark is ? or OK
+dnl A sender address with my local host name ($j) is safe
+R<?> $* < @ $j > $: <_RES_OK_> $1 < @ $j >
+ifdef(`_ACCEPT_UNRESOLVABLE_DOMAINS_',
+`R<?> $* < @ $+ > $: <_RES_OK_> $1 < @ $2 > ... unresolvable OK',
+`R<?> $* < @ $+ > $: <? $(resolve $2 $: $2 <PERM> $) > $1 < @ $2 >
+R<? $* <$->> $* < @ $+ >
+ $: <$2> $3 < @ $4 >')
+dnl workspace <mark> CanonicalAddress where mark is ?, _RES_OK_, PERM, TEMP
+dnl mark is ? iff the address is user (wo @domain)
+
+ifdef(`_ACCESS_TABLE_', `dnl
+# check sender address: user@address, user@, address
+dnl should we remove +ext from user?
+dnl workspace: <mark> CanonicalAddress where mark is: ?, _RES_OK_, PERM, TEMP
+R<$+> $+ < @ $* > $: @<$1> <$2 < @ $3 >> $| <F:$2@$3> <U:$2@> <D:$3>
+R<$+> $+ $: @<$1> <$2> $| <U:$2@>
+dnl workspace: @<mark> <CanonicalAddress> $| <@type:address> ....
+dnl $| is used as delimiter, otherwise false matches may occur: <user<@domain>>
+dnl will only return user<@domain when "reversing" the args
+R@ <$+> <$*> $| <$+> $: <@> <$1> <$2> $| $>SearchList <+ From> $| <$3> <>
+dnl workspace: <@><mark> <CanonicalAddress> $| <result>
+R<@> <$+> <$*> $| <$*> $: <$3> <$1> <$2> reverse result
+dnl workspace: <result> <mark> <CanonicalAddress>
+# retransform for further use
+dnl required form:
+dnl <ResultOfLookup|mark> CanonicalAddress
+R<?> <$+> <$*> $: <$1> $2 no match
+R<$+> <$+> <$*> $: <$1> $3 relevant result, keep it', `dnl')
+dnl workspace <ResultOfLookup|mark> CanonicalAddress
+dnl mark is ? iff the address is user (wo @domain)
+
+ifdef(`_ACCEPT_UNQUALIFIED_SENDERS_',`dnl',`dnl
+# handle case of no @domain on address
+dnl prepend daemon_flags
+R<?> $* $: $&{daemon_flags} $| <?> $1
+dnl accept unqualified sender: change mark to avoid test
+R$* u $* $| <?> $* $: <_RES_OK_> $3
+dnl remove daemon_flags
+R$* $| $* $: $2
+R<?> $* $: < ? $&{client_addr} > $1
+R<?> $* $@ <_RES_OK_> ...local unqualed ok
+R<? $+> $* $#error $@ 5.5.4 $: "_CODE553 Domain name required for sender address " $&f
+ ...remote is not')
+# check results
+R<?> $* $: @ $1 mark address: nothing known about it
+R<$={ResOk}> $* $@ <_RES_OK_> domain ok: stop
+R<TEMP> $* $#error $@ 4.1.8 $: "451 Domain of sender address " $&f " does not resolve"
+R<PERM> $* $#error $@ 5.1.8 $: "_CODE553 Domain of sender address " $&f " does not exist"
+ifdef(`_ACCESS_TABLE_', `dnl
+R<$={Accept}> $* $# $1 accept from access map
+R<DISCARD> $* $#discard $: discard
+R<QUARANTINE:$+> $* $#error $@ quarantine $: $1
+R<REJECT> $* $#error ifdef(`confREJECT_MSG', `$: confREJECT_MSG', `$@ 5.7.1 $: "550 Access denied"')
+dnl error tag
+R<ERROR:$-.$-.$-:$+> $* $#error $@ $1.$2.$3 $: $4
+R<ERROR:$+> $* $#error $: $1
+ifdef(`_ATMPF_', `R<_ATMPF_> $* $#error $@ 4.3.0 $: "451 Temporary system failure. Please try again later."', `dnl')
+dnl generic error from access map
+R<$+> $* $#error $: $1 error from access db',
+`dnl')
+
+######################################################################
+### check_rcpt -- check SMTP ``RCPT TO:'' command argument
+######################################################################
+
+SLocal_check_rcpt
+Scheck`'_U_`'rcpt
+R$* $: $1 $| $>"Local_check_rcpt" $1
+R$* $| $#$* $#$2
+R$* $| $* $@ $>"Basic_check_rcpt" $1
+
+SBasic_check_rcpt
+# empty address?
+R<> $#error $@ nouser $: "553 User address required"
+R$@ $#error $@ nouser $: "553 User address required"
+# check for deferred delivery mode
+R$* $: < $&{deliveryMode} > $1
+R< d > $* $@ deferred
+R< $* > $* $: $2
+
+ifdef(`_REQUIRE_QUAL_RCPT_', `dnl
+dnl this code checks for user@host where host is not a FQHN.
+dnl it is not activated.
+dnl notice: code to check for a recipient without a domain name is
+dnl available down below; look for the same macro.
+dnl this check is done here because the name might be qualified by the
+dnl canonicalization.
+# require fully qualified domain part?
+dnl very simple canonification: make sure the address is in < >
+R$+ $: <?> $1
+R<?> <$+> $: <@> <$1>
+R<?> $+ $: <@> <$1>
+R<@> < postmaster > $: postmaster
+R<@> < $* @ $+ . $+ > $: < $1 @ $2 . $3 >
+dnl prepend daemon_flags
+R<@> $* $: $&{daemon_flags} $| <@> $1
+dnl workspace: ${daemon_flags} $| <@> <address>
+dnl 'r'equire qual.rcpt: ok
+R$* r $* $| <@> < $+ @ $+ > $: < $3 @ $4 >
+dnl do not allow these at all or only from local systems?
+R$* r $* $| <@> < $* > $: < ? $&{client_name} > < $3 >
+R<?> < $* > $: <$1>
+R<? $=w> < $* > $: <$1>
+R<? $+> <$+> $#error $@ 5.5.4 $: "553 Fully qualified domain name required"
+dnl remove daemon_flags for other cases
+R$* $| <@> $* $: $2', `dnl')
+
+dnl ##################################################################
+dnl call subroutines for recipient and relay
+dnl possible returns from subroutines:
+dnl $#TEMP temporary failure
+dnl $#error permanent failure (or temporary if from access map)
+dnl $#other stop processing
+dnl RELAY RELAYing allowed
+dnl other otherwise
+######################################################################
+R$* $: $1 $| @ $>"Rcpt_ok" $1
+dnl temporary failure? remove mark @ and remember
+R$* $| @ $#TEMP $+ $: $1 $| T $2
+dnl error or ok (stop)
+R$* $| @ $#$* $#$2
+ifdef(`_PROMISCUOUS_RELAY_', `divert(-1)', `dnl')
+R$* $| @ RELAY $@ RELAY
+dnl something else: call check sender (relay)
+R$* $| @ $* $: O $| $>"Relay_ok" $1
+dnl temporary failure: call check sender (relay)
+R$* $| T $+ $: T $2 $| $>"Relay_ok" $1
+dnl temporary failure? return that
+R$* $| $#TEMP $+ $#error $2
+dnl error or ok (stop)
+R$* $| $#$* $#$2
+R$* $| RELAY $@ RELAY
+dnl something else: return previous temp failure
+R T $+ $| $* $#error $1
+# anything else is bogus
+R$* $#error $@ 5.7.1 $: confRELAY_MSG
+divert(0)
+
+######################################################################
+### Rcpt_ok: is the recipient ok?
+dnl input: recipient address (RCPT TO)
+dnl output: see explanation at call
+######################################################################
+SRcpt_ok
+ifdef(`_LOOSE_RELAY_CHECK_',`dnl
+R$* $: $>CanonAddr $1
+R$* < @ $* . > $1 < @ $2 > strip trailing dots',
+`R$* $: $>ParseRecipient $1 strip relayable hosts')
+
+ifdef(`_BESTMX_IS_LOCAL_',`dnl
+ifelse(_BESTMX_IS_LOCAL_, `', `dnl
+# unlimited bestmx
+R$* < @ $* > $* $: $1 < @ $2 @@ $(bestmx $2 $) > $3',
+`dnl
+# limit bestmx to $=B
+R$* < @ $* $=B > $* $: $1 < @ $2 $3 @@ $(bestmx $2 $3 $) > $4')
+R$* $=O $* < @ $* @@ $=w . > $* $@ $>"Rcpt_ok" $1 $2 $3
+R$* < @ $* @@ $=w . > $* $: $1 < @ $3 > $4
+R$* < @ $* @@ $* > $* $: $1 < @ $2 > $4')
+
+ifdef(`_BLACKLIST_RCPT_',`dnl
+ifdef(`_ACCESS_TABLE_', `dnl
+# blacklist local users or any host from receiving mail
+R$* $: <?> $1
+dnl user is now tagged with @ to be consistent with check_mail
+dnl and to distinguish users from hosts (com would be host, com@ would be user)
+R<?> $+ < @ $=w > $: <> <$1 < @ $2 >> $| <F:$1@$2> <U:$1@> <D:$2>
+R<?> $+ < @ $* > $: <> <$1 < @ $2 >> $| <F:$1@$2> <D:$2>
+R<?> $+ $: <> <$1> $| <U:$1@>
+dnl $| is used as delimiter, otherwise false matches may occur: <user<@domain>>
+dnl will only return user<@domain when "reversing" the args
+R<> <$*> $| <$+> $: <@> <$1> $| $>SearchList <+ To> $| <$2> <>
+R<@> <$*> $| <$*> $: <$2> <$1> reverse result
+R<?> <$*> $: @ $1 mark address as no match
+dnl we may have to filter here because otherwise some RHSs
+dnl would be interpreted as generic error messages...
+dnl error messages should be "tagged" by prefixing them with error: !
+dnl that would make a lot of things easier.
+R<$={Accept}> <$*> $: @ $2 mark address as no match
+ifdef(`_ACCESS_SKIP_', `dnl
+R<SKIP> <$*> $: @ $1 mark address as no match', `dnl')
+ifdef(`_DELAY_COMPAT_8_10_',`dnl
+dnl compatility with 8.11/8.10:
+dnl we have to filter these because otherwise they would be interpreted
+dnl as generic error message...
+dnl error messages should be "tagged" by prefixing them with error: !
+dnl that would make a lot of things easier.
+dnl maybe we should stop checks already here (if SPAM_xyx)?
+R<$={SpamTag}> <$*> $: @ $2 mark address as no match')
+R<REJECT> $* $#error $@ 5.2.1 $: confRCPTREJ_MSG
+R<DISCARD> $* $#discard $: discard
+R<QUARANTINE:$+> $* $#error $@ quarantine $: $1
+dnl error tag
+R<ERROR:$-.$-.$-:$+> $* $#error $@ $1.$2.$3 $: $4
+R<ERROR:$+> $* $#error $: $1
+ifdef(`_ATMPF_', `R<_ATMPF_> $* $#error $@ 4.3.0 $: "451 Temporary system failure. Please try again later."', `dnl')
+dnl generic error from access map
+R<$+> $* $#error $: $1 error from access db
+R@ $* $1 remove mark', `dnl')', `dnl')
+
+ifdef(`_PROMISCUOUS_RELAY_', `divert(-1)', `dnl')
+# authenticated via TLS?
+R$* $: $1 $| $>RelayTLS client authenticated?
+R$* $| $# $+ $# $2 error/ok?
+R$* $| $* $: $1 no
+
+R$* $: $1 $| $>"Local_Relay_Auth" $&{auth_type}
+dnl workspace: localpart<@domain> $| result of Local_Relay_Auth
+R$* $| $# $* $# $2
+dnl if Local_Relay_Auth returns NO then do not check $={TrustAuthMech}
+R$* $| NO $: $1
+R$* $| $* $: $1 $| $&{auth_type}
+dnl workspace: localpart<@domain> [ $| ${auth_type} ]
+dnl empty ${auth_type}?
+R$* $| $: $1
+dnl mechanism ${auth_type} accepted?
+dnl use $# to override further tests (delay_checks): see check_rcpt below
+R$* $| $={TrustAuthMech} $# RELAY
+dnl remove ${auth_type}
+R$* $| $* $: $1
+dnl workspace: localpart<@domain> | localpart
+ifelse(defn(`_NO_UUCP_'), `r',
+`R$* ! $* < @ $* > $: <REMOTE> $2 < @ BANG_PATH >
+R$* ! $* $: <REMOTE> $2 < @ BANG_PATH >', `dnl')
+# anything terminating locally is ok
+ifdef(`_RELAY_ENTIRE_DOMAIN_', `dnl
+R$+ < @ $* $=m > $@ RELAY', `dnl')
+R$+ < @ $=w > $@ RELAY
+ifdef(`_RELAY_HOSTS_ONLY_',
+`R$+ < @ $=R > $@ RELAY
+ifdef(`_ACCESS_TABLE_', `dnl
+R$+ < @ $+ > $: <$(access To:$2 $: ? $)> <$1 < @ $2 >>
+dnl workspace: <Result-of-lookup | ?> <localpart<@domain>>
+R<?> <$+ < @ $+ >> $: <$(access $2 $: ? $)> <$1 < @ $2 >>',`dnl')',
+`R$+ < @ $* $=R > $@ RELAY
+ifdef(`_ACCESS_TABLE_', `dnl
+ifdef(`_RELAY_FULL_ADDR_', `dnl
+R$+ < @ $+ > $: $1 < @ $2 > $| $>SearchList <+ To> $| <F:$1@$2> <D:$2> <F:$1@> <>
+R$+ < @ $+ > $| <$*> $: <$3> <$1 <@ $2>>
+R$+ < @ $+ > $| $* $: <$3> <$1 <@ $2>>',
+`R$+ < @ $+ > $: $>D <$2> <?> <+ To> <$1 < @ $2 >>')')')
+ifdef(`_ACCESS_TABLE_', `dnl
+dnl workspace: <Result-of-lookup | ?> <localpart<@domain>>
+R<RELAY> $* $@ RELAY
+ifdef(`_ATMPF_', `R<$* _ATMPF_> $* $#TEMP $@ 4.3.0 $: "451 Temporary system failure. Please try again later."', `dnl')
+R<$*> <$*> $: $2',`dnl')
+
+
+ifdef(`_RELAY_MX_SERVED_', `dnl
+# allow relaying for hosts which we MX serve
+R$+ < @ $+ > $: < : $(mxserved $2 $) : > $1 < @ $2 >
+dnl this must not necessarily happen if the client is checked first...
+R< : $* <TEMP> : > $* $#TEMP $@ 4.4.0 $: "450 Can not check MX records for recipient host " $1
+R<$* : $=w . : $*> $* $@ RELAY
+R< : $* : > $* $: $2',
+`dnl')
+
+# check for local user (i.e. unqualified address)
+R$* $: <?> $1
+R<?> $* < @ $+ > $: <REMOTE> $1 < @ $2 >
+# local user is ok
+dnl is it really? the standard requires user@domain, not just user
+dnl but we should accept it anyway (maybe making it an option:
+dnl RequireFQDN ?)
+dnl postmaster must be accepted without domain (DRUMS)
+ifdef(`_REQUIRE_QUAL_RCPT_', `dnl
+R<?> postmaster $@ OK
+# require qualified recipient?
+dnl prepend daemon_flags
+R<?> $+ $: $&{daemon_flags} $| <?> $1
+dnl workspace: ${daemon_flags} $| <?> localpart
+dnl do not allow these at all or only from local systems?
+dnl r flag? add client_name
+R$* r $* $| <?> $+ $: < ? $&{client_name} > <?> $3
+dnl no r flag: relay to local user (only local part)
+# no qualified recipient required
+R$* $| <?> $+ $@ RELAY
+dnl client_name is empty
+R<?> <?> $+ $@ RELAY
+dnl client_name is local
+R<? $=w> <?> $+ $@ RELAY
+dnl client_name is not local
+R<? $+> $+ $#error $@ 5.5.4 $: "553 Domain name required"', `dnl
+dnl no qualified recipient required
+R<?> $+ $@ RELAY')
+dnl it is a remote user: remove mark and then check client
+R<$+> $* $: $2
+dnl currently the recipient address is not used below
+
+######################################################################
+### Relay_ok: is the relay/sender ok?
+dnl input: ignored
+dnl output: see explanation at call
+######################################################################
+SRelay_ok
+# anything originating locally is ok
+# check IP address
+R$* $: $&{client_addr}
+R$@ $@ RELAY originated locally
+R0 $@ RELAY originated locally
+R127.0.0.1 $@ RELAY originated locally
+RIPv6:::1 $@ RELAY originated locally
+R$=R $* $@ RELAY relayable IP address
+ifdef(`_ACCESS_TABLE_', `dnl
+R$* $: $>A <$1> <?> <+ Connect> <$1>
+R<RELAY> $* $@ RELAY relayable IP address
+ifdef(`_FFR_REJECT_IP_IN_CHECK_RCPT_',`dnl
+dnl this will cause rejections in cases like:
+dnl Connect:My.Host.Domain RELAY
+dnl Connect:My.Net REJECT
+dnl since in check_relay client_name is checked before client_addr
+R<REJECT> $* $@ REJECT rejected IP address')
+ifdef(`_ATMPF_', `R<_ATMPF_> $* $#TEMP $@ 4.3.0 $: "451 Temporary system failure. Please try again later."', `dnl')
+R<$*> <$*> $: $2', `dnl')
+R$* $: [ $1 ] put brackets around it...
+R$=w $@ RELAY ... and see if it is local
+
+ifdef(`_RELAY_DB_FROM_', `define(`_RELAY_MAIL_FROM_', `1')')dnl
+ifdef(`_RELAY_LOCAL_FROM_', `define(`_RELAY_MAIL_FROM_', `1')')dnl
+ifdef(`_RELAY_MAIL_FROM_', `dnl
+dnl input: {client_addr} or something "broken"
+dnl just throw the input away; we do not need it.
+# check whether FROM is allowed to use system as relay
+R$* $: <?> $>CanonAddr $&f
+R<?> $+ < @ $+ . > <?> $1 < @ $2 > remove trailing dot
+ifdef(`_RELAY_LOCAL_FROM_', `dnl
+# check whether local FROM is ok
+R<?> $+ < @ $=w > $@ RELAY FROM local', `dnl')
+ifdef(`_RELAY_DB_FROM_', `dnl
+R<?> $+ < @ $+ > $: <@> $>SearchList <! From> $| <F:$1@$2> ifdef(`_RELAY_DB_FROM_DOMAIN_', ifdef(`_RELAY_HOSTS_ONLY_', `<E:$2>', `<D:$2>')) <>
+R<@> <RELAY> $@ RELAY RELAY FROM sender ok
+ifdef(`_ATMPF_', `R<@> <_ATMPF_> $#TEMP $@ 4.3.0 $: "451 Temporary system failure. Please try again later."', `dnl')
+', `dnl
+ifdef(`_RELAY_DB_FROM_DOMAIN_',
+`errprint(`*** ERROR: _RELAY_DB_FROM_DOMAIN_ requires _RELAY_DB_FROM_
+')',
+`dnl')
+dnl')', `dnl')
+dnl notice: the rulesets above do not leave a unique workspace behind.
+dnl it does not matter in this case because the following rule ignores
+dnl the input. otherwise these rules must "clean up" the workspace.
+
+# check client name: first: did it resolve?
+dnl input: ignored
+R$* $: < $&{client_resolve} >
+R<TEMP> $#TEMP $@ 4.4.0 $: "450 Relaying temporarily denied. Cannot resolve PTR record for " $&{client_addr}
+R<FORGED> $#error $@ 5.7.1 $: "550 Relaying denied. IP name possibly forged " $&{client_name}
+R<FAIL> $#error $@ 5.7.1 $: "550 Relaying denied. IP name lookup failed " $&{client_name}
+dnl ${client_resolve} should be OK, so go ahead
+R$* $: <@> $&{client_name}
+dnl should not be necessary since it has been done for client_addr already
+dnl this rule actually may cause a problem if {client_name} resolves to ""
+dnl however, this should not happen since the forward lookup should fail
+dnl and {client_resolve} should be TEMP or FAIL.
+dnl nevertheless, removing the rule doesn't hurt.
+dnl R<@> $@ RELAY
+dnl workspace: <@> ${client_name} (not empty)
+# pass to name server to make hostname canonical
+R<@> $* $=P $:<?> $1 $2
+R<@> $+ $:<?> $[ $1 $]
+dnl workspace: <?> ${client_name} (canonified)
+R$* . $1 strip trailing dots
+ifdef(`_RELAY_ENTIRE_DOMAIN_', `dnl
+R<?> $* $=m $@ RELAY', `dnl')
+R<?> $=w $@ RELAY
+ifdef(`_RELAY_HOSTS_ONLY_',
+`R<?> $=R $@ RELAY
+ifdef(`_ACCESS_TABLE_', `dnl
+R<?> $* $: <$(access Connect:$1 $: ? $)> <$1>
+R<?> <$*> $: <$(access $1 $: ? $)> <$1>',`dnl')',
+`R<?> $* $=R $@ RELAY
+ifdef(`_ACCESS_TABLE_', `dnl
+R<?> $* $: $>D <$1> <?> <+ Connect> <$1>',`dnl')')
+ifdef(`_ACCESS_TABLE_', `dnl
+R<RELAY> $* $@ RELAY
+ifdef(`_ATMPF_', `R<$* _ATMPF_> $* $#TEMP $@ 4.3.0 $: "451 Temporary system failure. Please try again later."', `dnl')
+R<$*> <$*> $: $2',`dnl')
+dnl end of _PROMISCUOUS_RELAY_
+divert(0)
+ifdef(`_DELAY_CHECKS_',`dnl
+# turn a canonical address in the form user<@domain>
+# qualify unqual. addresses with $j
+dnl it might have been only user (without <@domain>)
+SFullAddr
+R$* <@ $+ . > $1 <@ $2 >
+R$* <@ $* > $@ $1 <@ $2 >
+R$+ $@ $1 <@ $j >
+
+SDelay_TLS_Clt
+# authenticated?
+dnl code repeated here from Basic_check_mail
+dnl only called from check_rcpt in delay mode if checkrcpt returns $#
+R$* $: $1 $| $>"tls_client" $&{verify} $| MAIL
+R$* $| $#$+ $#$2
+dnl return result from checkrcpt
+R$* $| $* $# $1
+R$* $# $1
+
+SDelay_TLS_Clt2
+# authenticated?
+dnl code repeated here from Basic_check_mail
+dnl only called from check_rcpt in delay mode if stopping due to Friend/Hater
+R$* $: $1 $| $>"tls_client" $&{verify} $| MAIL
+R$* $| $#$+ $#$2
+dnl return result from friend/hater check
+R$* $| $* $@ $1
+R$* $@ $1
+
+# call all necessary rulesets
+Scheck_rcpt
+dnl this test should be in the Basic_check_rcpt ruleset
+dnl which is the correct DSN code?
+# R$@ $#error $@ 5.1.3 $: "553 Recipient address required"
+
+R$+ $: $1 $| $>checkrcpt $1
+dnl now we can simply stop checks by returning "$# xyz" instead of just "ok"
+dnl on error (or discard) stop now
+R$+ $| $#error $* $#error $2
+R$+ $| $#discard $* $#discard $2
+dnl otherwise call tls_client; see above
+R$+ $| $#$* $@ $>"Delay_TLS_Clt" $2
+R$+ $| $* $: <?> $>FullAddr $>CanonAddr $1
+ifdef(`_SPAM_FH_',
+`dnl lookup user@ and user@address
+ifdef(`_ACCESS_TABLE_', `',
+`errprint(`*** ERROR: FEATURE(`delay_checks', `argument') requires FEATURE(`access_db')
+')')dnl
+dnl one of the next two rules is supposed to match
+dnl this code has been copied from BLACKLIST... etc
+dnl and simplified by omitting some < >.
+R<?> $+ < @ $=w > $: <> $1 < @ $2 > $| <F: $1@$2 > <D: $2 > <U: $1@>
+R<?> $+ < @ $* > $: <> $1 < @ $2 > $| <F: $1@$2 > <D: $2 >
+dnl R<?> $@ something_is_very_wrong_here
+# lookup the addresses only with Spam tag
+R<> $* $| <$+> $: <@> $1 $| $>SearchList <! Spam> $| <$2> <>
+R<@> $* $| $* $: $2 $1 reverse result
+dnl', `dnl')
+ifdef(`_SPAM_FRIEND_',
+`# is the recipient a spam friend?
+ifdef(`_SPAM_HATER_',
+ `errprint(`*** ERROR: define either Hater or Friend -- not both.
+')', `dnl')
+R<FRIEND> $+ $@ $>"Delay_TLS_Clt2" SPAMFRIEND
+R<$*> $+ $: $2',
+`dnl')
+ifdef(`_SPAM_HATER_',
+`# is the recipient no spam hater?
+R<HATER> $+ $: $1 spam hater: continue checks
+R<$*> $+ $@ $>"Delay_TLS_Clt2" NOSPAMHATER everyone else: stop
+dnl',`dnl')
+dnl run further checks: check_mail
+dnl should we "clean up" $&f?
+ifdef(`_FFR_MAIL_MACRO',
+`R$* $: $1 $| $>checkmail $&{mail_from}',
+`R$* $: $1 $| $>checkmail <$&f>')
+dnl recipient (canonical format) $| result of checkmail
+R$* $| $#$* $#$2
+dnl run further checks: check_relay
+R$* $| $* $: $1 $| $>checkrelay $&{client_name} $| $&{client_addr}
+R$* $| $#$* $#$2
+R$* $| $* $: $1
+', `dnl')
+
+ifdef(`_ACCESS_TABLE_', `dnl', `divert(-1)')
+######################################################################
+### F: LookUpFull -- search for an entry in access database
+###
+### lookup of full key (which should be an address) and
+### variations if +detail exists: +* and without +detail
+###
+### Parameters:
+### <$1> -- key
+### <$2> -- default (what to return if not found in db)
+dnl must not be empty
+### <$3> -- mark (must be <(!|+) single-token>)
+### ! does lookup only with tag
+### + does lookup with and without tag
+### <$4> -- passthru (additional data passed unchanged through)
+dnl returns: <default> <passthru>
+dnl <result> <passthru>
+######################################################################
+
+SF
+dnl workspace: <key> <def> <o tag> <thru>
+dnl full lookup
+dnl 2 3 4 5
+R<$+> <$*> <$- $-> <$*> $: <$(access $4`'_TAG_DELIM_`'$1 $: ? $)> <$1> <$2> <$3 $4> <$5>
+dnl no match, try without tag
+dnl 1 2 3 4
+R<?> <$+> <$*> <+ $-> <$*> $: <$(access $1 $: ? $)> <$1> <$2> <+ $3> <$4>
+dnl no match, +detail: try +*
+dnl 1 2 3 4 5 6 7
+R<?> <$+ + $* @ $+> <$*> <$- $-> <$*>
+ $: <$(access $6`'_TAG_DELIM_`'$1+*@$3 $: ? $)> <$1+$2@$3> <$4> <$5 $6> <$7>
+dnl no match, +detail: try +* without tag
+dnl 1 2 3 4 5 6
+R<?> <$+ + $* @ $+> <$*> <+ $-> <$*>
+ $: <$(access $1+*@$3 $: ? $)> <$1+$2@$3> <$4> <+ $5> <$6>
+dnl no match, +detail: try without +detail
+dnl 1 2 3 4 5 6 7
+R<?> <$+ + $* @ $+> <$*> <$- $-> <$*>
+ $: <$(access $6`'_TAG_DELIM_`'$1@$3 $: ? $)> <$1+$2@$3> <$4> <$5 $6> <$7>
+dnl no match, +detail: try without +detail and without tag
+dnl 1 2 3 4 5 6
+R<?> <$+ + $* @ $+> <$*> <+ $-> <$*>
+ $: <$(access $1@$3 $: ? $)> <$1+$2@$3> <$4> <+ $5> <$6>
+dnl no match, return <default> <passthru>
+dnl 1 2 3 4 5
+R<?> <$+> <$*> <$- $-> <$*> $@ <$2> <$5>
+ifdef(`_ATMPF_', `dnl tempfail?
+dnl 2 3 4 5
+R<$+ _ATMPF_> <$*> <$- $-> <$*> $@ <_ATMPF_> <$5>', `dnl')
+dnl match, return <match> <passthru>
+dnl 2 3 4 5
+R<$+> <$*> <$- $-> <$*> $@ <$1> <$5>
+
+######################################################################
+### E: LookUpExact -- search for an entry in access database
+###
+### Parameters:
+### <$1> -- key
+### <$2> -- default (what to return if not found in db)
+dnl must not be empty
+### <$3> -- mark (must be <(!|+) single-token>)
+### ! does lookup only with tag
+### + does lookup with and without tag
+### <$4> -- passthru (additional data passed unchanged through)
+dnl returns: <default> <passthru>
+dnl <result> <passthru>
+######################################################################
+
+SE
+dnl 2 3 4 5
+R<$*> <$*> <$- $-> <$*> $: <$(access $4`'_TAG_DELIM_`'$1 $: ? $)> <$1> <$2> <$3 $4> <$5>
+dnl no match, try without tag
+dnl 1 2 3 4
+R<?> <$+> <$*> <+ $-> <$*> $: <$(access $1 $: ? $)> <$1> <$2> <+ $3> <$4>
+dnl no match, return default passthru
+dnl 1 2 3 4 5
+R<?> <$+> <$*> <$- $-> <$*> $@ <$2> <$5>
+ifdef(`_ATMPF_', `dnl tempfail?
+dnl 2 3 4 5
+R<$+ _ATMPF_> <$*> <$- $-> <$*> $@ <_ATMPF_> <$5>', `dnl')
+dnl match, return <match> <passthru>
+dnl 2 3 4 5
+R<$+> <$*> <$- $-> <$*> $@ <$1> <$5>
+
+######################################################################
+### U: LookUpUser -- search for an entry in access database
+###
+### lookup of key (which should be a local part) and
+### variations if +detail exists: +* and without +detail
+###
+### Parameters:
+### <$1> -- key (user@)
+### <$2> -- default (what to return if not found in db)
+dnl must not be empty
+### <$3> -- mark (must be <(!|+) single-token>)
+### ! does lookup only with tag
+### + does lookup with and without tag
+### <$4> -- passthru (additional data passed unchanged through)
+dnl returns: <default> <passthru>
+dnl <result> <passthru>
+######################################################################
+
+SU
+dnl user lookups are always with trailing @
+dnl 2 3 4 5
+R<$+> <$*> <$- $-> <$*> $: <$(access $4`'_TAG_DELIM_`'$1 $: ? $)> <$1> <$2> <$3 $4> <$5>
+dnl no match, try without tag
+dnl 1 2 3 4
+R<?> <$+> <$*> <+ $-> <$*> $: <$(access $1 $: ? $)> <$1> <$2> <+ $3> <$4>
+dnl do not remove the @ from the lookup:
+dnl it is part of the +detail@ which is omitted for the lookup
+dnl no match, +detail: try +*
+dnl 1 2 3 4 5 6
+R<?> <$+ + $* @> <$*> <$- $-> <$*>
+ $: <$(access $5`'_TAG_DELIM_`'$1+*@ $: ? $)> <$1+$2@> <$3> <$4 $5> <$6>
+dnl no match, +detail: try +* without tag
+dnl 1 2 3 4 5
+R<?> <$+ + $* @> <$*> <+ $-> <$*>
+ $: <$(access $1+*@ $: ? $)> <$1+$2@> <$3> <+ $4> <$5>
+dnl no match, +detail: try without +detail
+dnl 1 2 3 4 5 6
+R<?> <$+ + $* @> <$*> <$- $-> <$*>
+ $: <$(access $5`'_TAG_DELIM_`'$1@ $: ? $)> <$1+$2@> <$3> <$4 $5> <$6>
+dnl no match, +detail: try without +detail and without tag
+dnl 1 2 3 4 5
+R<?> <$+ + $* @> <$*> <+ $-> <$*>
+ $: <$(access $1@ $: ? $)> <$1+$2@> <$3> <+ $4> <$5>
+dnl no match, return <default> <passthru>
+dnl 1 2 3 4 5
+R<?> <$+> <$*> <$- $-> <$*> $@ <$2> <$5>
+ifdef(`_ATMPF_', `dnl tempfail?
+dnl 2 3 4 5
+R<$+ _ATMPF_> <$*> <$- $-> <$*> $@ <_ATMPF_> <$5>', `dnl')
+dnl match, return <match> <passthru>
+dnl 2 3 4 5
+R<$+> <$*> <$- $-> <$*> $@ <$1> <$5>
+
+######################################################################
+### SearchList: search a list of items in the access map
+### Parameters:
+### <exact tag> $| <mark:address> <mark:address> ... <>
+dnl maybe we should have a @ (again) in front of the mark to
+dnl avoid errorneous matches (with error messages?)
+dnl if we can make sure that tag is always a single token
+dnl then we can omit the delimiter $|, otherwise we need it
+dnl to avoid errorneous matchs (first rule: D: if there
+dnl is that mark somewhere in the list, it will be taken).
+dnl moreover, we can do some tricks to enforce lookup with
+dnl the tag only, e.g.:
+### where "exact" is either "+" or "!":
+### <+ TAG> lookup with and w/o tag
+### <! TAG> lookup with tag
+dnl Warning: + and ! should be in OperatorChars (otherwise there must be
+dnl a blank between them and the tag.
+### possible values for "mark" are:
+### D: recursive host lookup (LookUpDomain)
+dnl A: recursive address lookup (LookUpAddress) [not yet required]
+### E: exact lookup, no modifications
+### F: full lookup, try user+ext@domain and user@domain
+### U: user lookup, try user+ext and user (input must have trailing @)
+### return: <RHS of lookup> or <?> (not found)
+######################################################################
+
+# class with valid marks for SearchList
+dnl if A is activated: add it
+C{Src}E F D U ifdef(`_FFR_SRCHLIST_A', `A')
+SSearchList
+# just call the ruleset with the name of the tag... nice trick...
+dnl 2 3 4
+R<$+> $| <$={Src}:$*> <$*> $: <$1> $| <$4> $| $>$2 <$3> <?> <$1> <>
+dnl workspace: <o tag> $| <rest> $| <result of lookup> <>
+dnl no match and nothing left: return
+R<$+> $| <> $| <?> <> $@ <?>
+dnl no match but something left: continue
+R<$+> $| <$+> $| <?> <> $@ $>SearchList <$1> $| <$2>
+dnl match: return
+R<$+> $| <$*> $| <$+> <> $@ <$3>
+dnl return result from recursive invocation
+R<$+> $| <$+> $@ <$2>
+dnl endif _ACCESS_TABLE_
+divert(0)
+
+######################################################################
+### trust_auth: is user trusted to authenticate as someone else?
+###
+### Parameters:
+### $1: AUTH= parameter from MAIL command
+######################################################################
+
+dnl empty ruleset definition so it can be called
+SLocal_trust_auth
+Strust_auth
+R$* $: $&{auth_type} $| $1
+# required by RFC 2554 section 4.
+R$@ $| $* $#error $@ 5.7.1 $: "550 not authenticated"
+dnl seems to be useful...
+R$* $| $&{auth_authen} $@ identical
+R$* $| <$&{auth_authen}> $@ identical
+dnl call user supplied code
+R$* $| $* $: $1 $| $>"Local_trust_auth" $2
+R$* $| $#$* $#$2
+dnl default: error
+R$* $#error $@ 5.7.1 $: "550 " $&{auth_authen} " not allowed to act as " $&{auth_author}
+
+######################################################################
+### Relay_Auth: allow relaying based on authentication?
+###
+### Parameters:
+### $1: ${auth_type}
+######################################################################
+SLocal_Relay_Auth
+
+######################################################################
+### srv_features: which features to offer to a client?
+### (done in server)
+######################################################################
+Ssrv_features
+ifdef(`_LOCAL_SRV_FEATURES_', `dnl
+R$* $: $1 $| $>"Local_srv_features" $1
+R$* $| $#$* $#$2
+R$* $| $* $: $1', `dnl')
+ifdef(`_ACCESS_TABLE_', `dnl
+R$* $: $>D <$&{client_name}> <?> <! SRV_FEAT_TAG> <>
+R<?>$* $: $>A <$&{client_addr}> <?> <! SRV_FEAT_TAG> <>
+R<?>$* $: <$(access SRV_FEAT_TAG`'_TAG_DELIM_ $: ? $)>
+R<?>$* $@ OK
+ifdef(`_ATMPF_', `dnl tempfail?
+R<$* _ATMPF_>$* $#temp', `dnl')
+R<$+>$* $# $1')
+
+######################################################################
+### try_tls: try to use STARTTLS?
+### (done in client)
+######################################################################
+Stry_tls
+ifdef(`_LOCAL_TRY_TLS_', `dnl
+R$* $: $1 $| $>"Local_try_tls" $1
+R$* $| $#$* $#$2
+R$* $| $* $: $1', `dnl')
+ifdef(`_ACCESS_TABLE_', `dnl
+R$* $: $>D <$&{server_name}> <?> <! TLS_TRY_TAG> <>
+R<?>$* $: $>A <$&{server_addr}> <?> <! TLS_TRY_TAG> <>
+R<?>$* $: <$(access TLS_TRY_TAG`'_TAG_DELIM_ $: ? $)>
+R<?>$* $@ OK
+ifdef(`_ATMPF_', `dnl tempfail?
+R<$* _ATMPF_>$* $#error $@ 4.3.0 $: "451 Temporary system failure. Please try again later."', `dnl')
+R<NO>$* $#error $@ 5.7.1 $: "550 do not try TLS with " $&{server_name} " ["$&{server_addr}"]"')
+
+######################################################################
+### tls_rcpt: is connection with server "good" enough?
+### (done in client, per recipient)
+dnl called from deliver() before RCPT command
+###
+### Parameters:
+### $1: recipient
+######################################################################
+Stls_rcpt
+ifdef(`_LOCAL_TLS_RCPT_', `dnl
+R$* $: $1 $| $>"Local_tls_rcpt" $1
+R$* $| $#$* $#$2
+R$* $| $* $: $1', `dnl')
+ifdef(`_ACCESS_TABLE_', `dnl
+dnl store name of other side
+R$* $: $(macro {TLS_Name} $@ $&{server_name} $) $1
+dnl canonify recipient address
+R$+ $: <?> $>CanonAddr $1
+dnl strip trailing dots
+R<?> $+ < @ $+ . > <?> $1 <@ $2 >
+dnl full address?
+R<?> $+ < @ $+ > $: $1 <@ $2 > $| <F:$1@$2> <U:$1@> <D:$2> <E:>
+dnl only localpart?
+R<?> $+ $: $1 $| <U:$1@> <E:>
+dnl look it up
+dnl also look up a default value via E:
+R$* $| $+ $: $1 $| $>SearchList <! TLS_RCPT_TAG> $| $2 <>
+dnl found nothing: stop here
+R$* $| <?> $@ OK
+ifdef(`_ATMPF_', `dnl tempfail?
+R$* $| <$* _ATMPF_> $#error $@ 4.3.0 $: "451 Temporary system failure. Please try again later."', `dnl')
+dnl use the generic routine (for now)
+R$* $| <$+> $@ $>"TLS_connection" $&{verify} $| <$2>')
+
+######################################################################
+### tls_client: is connection with client "good" enough?
+### (done in server)
+###
+### Parameters:
+### ${verify} $| (MAIL|STARTTLS)
+######################################################################
+dnl MAIL: called from check_mail
+dnl STARTTLS: called from smtp() after STARTTLS has been accepted
+Stls_client
+ifdef(`_LOCAL_TLS_CLIENT_', `dnl
+R$* $: $1 $| $>"Local_tls_client" $1
+R$* $| $#$* $#$2
+R$* $| $* $: $1', `dnl')
+ifdef(`_ACCESS_TABLE_', `dnl
+dnl store name of other side
+R$* $: $(macro {TLS_Name} $@ $&{server_name} $) $1
+dnl ignore second arg for now
+dnl maybe use it to distinguish permanent/temporary error?
+dnl if MAIL: permanent (STARTTLS has not been offered)
+dnl if STARTTLS: temporary (offered but maybe failed)
+R$* $| $* $: $1 $| $>D <$&{client_name}> <?> <! TLS_CLT_TAG> <>
+R$* $| <?>$* $: $1 $| $>A <$&{client_addr}> <?> <! TLS_CLT_TAG> <>
+dnl do a default lookup: just TLS_CLT_TAG
+R$* $| <?>$* $: $1 $| <$(access TLS_CLT_TAG`'_TAG_DELIM_ $: ? $)>
+ifdef(`_ATMPF_', `dnl tempfail?
+R$* $| <$* _ATMPF_> $#error $@ 4.3.0 $: "451 Temporary system failure. Please try again later."', `dnl')
+R$* $@ $>"TLS_connection" $1', `dnl
+R$* $| $* $@ $>"TLS_connection" $1')
+
+######################################################################
+### tls_server: is connection with server "good" enough?
+### (done in client)
+###
+### Parameter:
+### ${verify}
+######################################################################
+dnl i.e. has the server been authenticated and is encryption active?
+dnl called from deliver() after STARTTLS command
+Stls_server
+ifdef(`_LOCAL_TLS_SERVER_', `dnl
+R$* $: $1 $| $>"Local_tls_server" $1
+R$* $| $#$* $#$2
+R$* $| $* $: $1', `dnl')
+ifdef(`_ACCESS_TABLE_', `dnl
+dnl store name of other side
+R$* $: $(macro {TLS_Name} $@ $&{server_name} $) $1
+R$* $: $1 $| $>D <$&{server_name}> <?> <! TLS_SRV_TAG> <>
+R$* $| <?>$* $: $1 $| $>A <$&{server_addr}> <?> <! TLS_SRV_TAG> <>
+dnl do a default lookup: just TLS_SRV_TAG
+R$* $| <?>$* $: $1 $| <$(access TLS_SRV_TAG`'_TAG_DELIM_ $: ? $)>
+ifdef(`_ATMPF_', `dnl tempfail?
+R$* $| <$* _ATMPF_> $#error $@ 4.3.0 $: "451 Temporary system failure. Please try again later."', `dnl')
+R$* $@ $>"TLS_connection" $1', `dnl
+R$* $@ $>"TLS_connection" $1')
+
+######################################################################
+### TLS_connection: is TLS connection "good" enough?
+###
+### Parameters:
+ifdef(`_ACCESS_TABLE_', `dnl
+### ${verify} $| <Requirement> [<>]', `dnl
+### ${verify}')
+### Requirement: RHS from access map, may be ? for none.
+dnl syntax for Requirement:
+dnl [(PERM|TEMP)+] (VERIFY[:bits]|ENCR:bits) [+extensions]
+dnl extensions: could be a list of further requirements
+dnl for now: CN:string {cn_subject} == string
+######################################################################
+STLS_connection
+ifdef(`_ACCESS_TABLE_', `dnl', `dnl use default error
+dnl deal with TLS handshake failures: abort
+RSOFTWARE $#error $@ ifdef(`TLS_PERM_ERR', `5.7.0', `4.7.0') $: "ifdef(`TLS_PERM_ERR', `503', `403') TLS handshake."
+divert(-1)')
+dnl common ruleset for tls_{client|server}
+dnl input: ${verify} $| <ResultOfLookup> [<>]
+dnl remove optional <>
+R$* $| <$*>$* $: $1 $| <$2>
+dnl workspace: ${verify} $| <ResultOfLookup>
+# create the appropriate error codes
+dnl permanent or temporary error?
+R$* $| <PERM + $={Tls} $*> $: $1 $| <503:5.7.0> <$2 $3>
+R$* $| <TEMP + $={Tls} $*> $: $1 $| <403:4.7.0> <$2 $3>
+dnl default case depends on TLS_PERM_ERR
+R$* $| <$={Tls} $*> $: $1 $| <ifdef(`TLS_PERM_ERR', `503:5.7.0', `403:4.7.0')> <$2 $3>
+dnl workspace: ${verify} $| [<SMTP:ESC>] <ResultOfLookup>
+# deal with TLS handshake failures: abort
+RSOFTWARE $| <$-:$+> $* $#error $@ $2 $: $1 " TLS handshake failed."
+dnl no <reply:dns> i.e. not requirements in the access map
+dnl use default error
+RSOFTWARE $| $* $#error $@ ifdef(`TLS_PERM_ERR', `5.7.0', `4.7.0') $: "ifdef(`TLS_PERM_ERR', `503', `403') TLS handshake failed."
+R$* $| <$*> <VERIFY> $: <$2> <VERIFY> <> $1
+dnl separate optional requirements
+R$* $| <$*> <VERIFY + $+> $: <$2> <VERIFY> <$3> $1
+R$* $| <$*> <$={Tls}:$->$* $: <$2> <$3:$4> <> $1
+dnl separate optional requirements
+R$* $| <$*> <$={Tls}:$- + $+>$* $: <$2> <$3:$4> <$5> $1
+dnl some other value in access map: accept
+dnl this also allows to override the default case (if used)
+R$* $| $* $@ OK
+# authentication required: give appropriate error
+# other side did authenticate (via STARTTLS)
+dnl workspace: <SMTP:ESC> <{VERIFY,ENCR}[:BITS]> <[extensions]> ${verify}
+dnl only verification required and it succeeded
+R<$*><VERIFY> <> OK $@ OK
+dnl verification required and it succeeded but extensions are given
+dnl change it to <SMTP:ESC> <REQ:0> <extensions>
+R<$*><VERIFY> <$+> OK $: <$1> <REQ:0> <$2>
+dnl verification required + some level of encryption
+R<$*><VERIFY:$-> <$*> OK $: <$1> <REQ:$2> <$3>
+dnl just some level of encryption required
+R<$*><ENCR:$-> <$*> $* $: <$1> <REQ:$2> <$3>
+dnl workspace:
+dnl 1. <SMTP:ESC> <VERIFY [:bits]> <[extensions]> {verify} (!= OK)
+dnl 2. <SMTP:ESC> <REQ:bits> <[extensions]>
+dnl verification required but ${verify} is not set (case 1.)
+R<$-:$+><VERIFY $*> <$*> $#error $@ $2 $: $1 " authentication required"
+R<$-:$+><VERIFY $*> <$*> FAIL $#error $@ $2 $: $1 " authentication failed"
+R<$-:$+><VERIFY $*> <$*> NO $#error $@ $2 $: $1 " not authenticated"
+R<$-:$+><VERIFY $*> <$*> NOT $#error $@ $2 $: $1 " no authentication requested"
+R<$-:$+><VERIFY $*> <$*> NONE $#error $@ $2 $: $1 " other side does not support STARTTLS"
+dnl some other value for ${verify}
+R<$-:$+><VERIFY $*> <$*> $+ $#error $@ $2 $: $1 " authentication failure " $4
+dnl some level of encryption required: get the maximum level (case 2.)
+R<$*><REQ:$-> <$*> $: <$1> <REQ:$2> <$3> $>max $&{cipher_bits} : $&{auth_ssf}
+dnl compare required bits with actual bits
+R<$*><REQ:$-> <$*> $- $: <$1> <$2:$4> <$3> $(arith l $@ $4 $@ $2 $)
+R<$-:$+><$-:$-> <$*> TRUE $#error $@ $2 $: $1 " encryption too weak " $4 " less than " $3
+dnl strength requirements fulfilled
+dnl TLS Additional Requirements Separator
+dnl this should be something which does not appear in the extensions itself
+dnl @ could be part of a CN, DN, etc...
+dnl use < > ? those are encoded in CN, DN, ...
+define(`_TLS_ARS_', `++')dnl
+dnl workspace:
+dnl <SMTP:ESC> <REQ:bits> <extensions> result-of-compare
+R<$-:$+><$-:$-> <$*> $* $: <$1:$2 _TLS_ARS_ $5>
+dnl workspace: <SMTP:ESC _TLS_ARS_ extensions>
+dnl continue: check extensions
+R<$-:$+ _TLS_ARS_ > $@ OK
+dnl split extensions into own list
+R<$-:$+ _TLS_ARS_ $+ > $: <$1:$2> <$3>
+R<$-:$+> < $+ _TLS_ARS_ $+ > <$1:$2> <$3> <$4>
+R<$-:$+> $+ $@ $>"TLS_req" $3 $| <$1:$2>
+
+######################################################################
+### TLS_req: check additional TLS requirements
+###
+### Parameters: [<list> <of> <req>] $| <$-:$+>
+### $-: SMTP reply code
+### $+: Enhanced Status Code
+dnl further requirements for this ruleset:
+dnl name of "other side" is stored is {TLS_name} (client/server_name)
+dnl
+dnl currently only CN[:common_name] is implemented
+dnl right now this is only a logical AND
+dnl i.e. all requirements must be true
+dnl how about an OR? CN must be X or CN must be Y or ..
+dnl use a macro to compute this as a trivial sequential
+dnl operations (no precedences etc)?
+######################################################################
+STLS_req
+dnl no additional requirements: ok
+R $| $+ $@ OK
+dnl require CN: but no CN specified: use name of other side
+R<CN> $* $| <$+> $: <CN:$&{TLS_Name}> $1 $| <$2>
+dnl match, check rest
+R<CN:$&{cn_subject}> $* $| <$+> $@ $>"TLS_req" $1 $| <$2>
+dnl CN does not match
+dnl 1 2 3 4
+R<CN:$+> $* $| <$-:$+> $#error $@ $4 $: $3 " CN " $&{cn_subject} " does not match " $1
+dnl cert subject
+R<CS:$&{cert_subject}> $* $| <$+> $@ $>"TLS_req" $1 $| <$2>
+dnl CS does not match
+dnl 1 2 3 4
+R<CS:$+> $* $| <$-:$+> $#error $@ $4 $: $3 " Cert Subject " $&{cert_subject} " does not match " $1
+dnl match, check rest
+R<CI:$&{cert_issuer}> $* $| <$+> $@ $>"TLS_req" $1 $| <$2>
+dnl CI does not match
+dnl 1 2 3 4
+R<CI:$+> $* $| <$-:$+> $#error $@ $4 $: $3 " Cert Issuer " $&{cert_issuer} " does not match " $1
+dnl return from recursive call
+ROK $@ OK
+
+######################################################################
+### max: return the maximum of two values separated by :
+###
+### Parameters: [$-]:[$-]
+######################################################################
+Smax
+R: $: 0
+R:$- $: $1
+R$-: $: $1
+R$-:$- $: $(arith l $@ $1 $@ $2 $) : $1 : $2
+RTRUE:$-:$- $: $2
+R$-:$-:$- $: $2
+dnl endif _ACCESS_TABLE_
+divert(0)
+
+######################################################################
+### RelayTLS: allow relaying based on TLS authentication
+###
+### Parameters:
+### none
+######################################################################
+SRelayTLS
+# authenticated?
+dnl we do not allow relaying for anyone who can present a cert
+dnl signed by a "trusted" CA. For example, even if we put verisigns
+dnl CA in CertPath so we can authenticate users, we do not allow
+dnl them to abuse our server (they might be easier to get hold of,
+dnl but anyway).
+dnl so here is the trick: if the verification succeeded
+dnl we look up the cert issuer in the access map
+dnl (maybe after extracting a part with a regular expression)
+dnl if this returns RELAY we relay without further questions
+dnl if it returns SUBJECT we perform a similar check on the
+dnl cert subject.
+ifdef(`_ACCESS_TABLE_', `dnl
+R$* $: <?> $&{verify}
+R<?> OK $: OK authenticated: continue
+R<?> $* $@ NO not authenticated
+ifdef(`_CERT_REGEX_ISSUER_', `dnl
+R$* $: $(CERTIssuer $&{cert_issuer} $)',
+`R$* $: $&{cert_issuer}')
+R$+ $: $(access CERTISSUER`'_TAG_DELIM_`'$1 $)
+dnl use $# to stop further checks (delay_check)
+RRELAY $# RELAY
+ifdef(`_CERT_REGEX_SUBJECT_', `dnl
+RSUBJECT $: <@> $(CERTSubject $&{cert_subject} $)',
+`RSUBJECT $: <@> $&{cert_subject}')
+R<@> $+ $: <@> $(access CERTSUBJECT`'_TAG_DELIM_`'$1 $)
+R<@> RELAY $# RELAY
+R$* $: NO', `dnl')
+
+######################################################################
+### authinfo: lookup authinfo in the access map
+###
+### Parameters:
+### $1: {server_name}
+### $2: {server_addr}
+dnl both are currently ignored
+dnl if it should be done via another map, we either need to restrict
+dnl functionality (it calls D and A) or copy those rulesets (or add another
+dnl parameter which I want to avoid, it's quite complex already)
+######################################################################
+dnl omit this ruleset if neither is defined?
+dnl it causes DefaultAuthInfo to be ignored
+dnl (which may be considered a good thing).
+Sauthinfo
+ifdef(`_AUTHINFO_TABLE_', `dnl
+R$* $: <$(authinfo AuthInfo:$&{server_name} $: ? $)>
+R<?> $: <$(authinfo AuthInfo:$&{server_addr} $: ? $)>
+R<?> $: <$(authinfo AuthInfo: $: ? $)>
+R<?> $@ no no authinfo available
+R<$*> $# $1
+dnl', `dnl
+ifdef(`_ACCESS_TABLE_', `dnl
+R$* $: $1 $| $>D <$&{server_name}> <?> <! AuthInfo> <>
+R$* $| <?>$* $: $1 $| $>A <$&{server_addr}> <?> <! AuthInfo> <>
+R$* $| <?>$* $: $1 $| <$(access AuthInfo`'_TAG_DELIM_ $: ? $)> <>
+R$* $| <?>$* $@ no no authinfo available
+R$* $| <$*> <> $# $2
+dnl', `dnl')')
+
+ifdef(`_RATE_CONTROL_',`dnl
+######################################################################
+### RateControl:
+### Parameters: ignored
+### return: $#error or OK
+######################################################################
+SRateControl
+ifdef(`_ACCESS_TABLE_', `dnl
+R$* $: <A:$&{client_addr}> <E:>
+dnl also look up a default value via E:
+R$+ $: $>SearchList <! ClientRate> $| $1 <>
+dnl found nothing: stop here
+R<?> $@ OK
+ifdef(`_ATMPF_', `dnl tempfail?
+R<$* _ATMPF_> $#error $@ 4.3.0 $: "451 Temporary system failure. Please try again later."', `dnl')
+dnl use the generic routine (for now)
+R<0> $@ OK no limit
+R<$+> $: <$1> $| $(arith l $@ $&{client_rate} $@ $1 $)
+dnl log this? Connection rate $&{client_rate} exceeds limit $1.
+R<$+> $| FALSE $#error $@ 4.3.2 $: _RATE_CONTROL_REPLY Connection rate limit exceeded.
+')')
+
+ifdef(`_CONN_CONTROL_',`dnl
+######################################################################
+### ConnControl:
+### Parameters: ignored
+### return: $#error or OK
+######################################################################
+SConnControl
+ifdef(`_ACCESS_TABLE_', `dnl
+R$* $: <A:$&{client_addr}> <E:>
+dnl also look up a default value via E:
+R$+ $: $>SearchList <! ClientConn> $| $1 <>
+dnl found nothing: stop here
+R<?> $@ OK
+ifdef(`_ATMPF_', `dnl tempfail?
+R<$* _ATMPF_> $#error $@ 4.3.0 $: "451 Temporary system failure. Please try again later."', `dnl')
+dnl use the generic routine (for now)
+R<0> $@ OK no limit
+R<$+> $: <$1> $| $(arith l $@ $&{client_connections} $@ $1 $)
+dnl log this: Open connections $&{client_connections} exceeds limit $1.
+R<$+> $| FALSE $#error $@ 4.3.2 $: _CONN_CONTROL_REPLY Too many open connections.
+')')
+
+undivert(9)dnl LOCAL_RULESETS
+#
+######################################################################
+######################################################################
+#####
+`##### MAIL FILTER DEFINITIONS'
+#####
+######################################################################
+######################################################################
+_MAIL_FILTERS_
+#
+######################################################################
+######################################################################
+#####
+`##### MAILER DEFINITIONS'
+#####
+######################################################################
+######################################################################
+undivert(7)dnl MAILER_DEFINITIONS
+
diff --git a/usr/src/cmd/sendmail/cf/m4/version.m4 b/usr/src/cmd/sendmail/cf/m4/version.m4
new file mode 100644
index 0000000000..9cd0d62f4a
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/m4/version.m4
@@ -0,0 +1,19 @@
+divert(-1)
+#
+# Copyright (c) 1998-2005 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+# Copyright (c) 1983 Eric P. Allman. All rights reserved.
+# Copyright (c) 1988, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+# ident "%Z%%M% %I% %E% SMI"
+#
+VERSIONID(`$Id: version.m4,v 8.132 2005/03/25 18:44:45 ca Exp $')
+#
+divert(0)
+# Configuration version number
+DZ8.13.4`'ifdef(`confCF_VERSION', `/confCF_VERSION')
diff --git a/usr/src/cmd/sendmail/cf/mailer/local.m4 b/usr/src/cmd/sendmail/cf/mailer/local.m4
new file mode 100644
index 0000000000..0a38cd7073
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/mailer/local.m4
@@ -0,0 +1,94 @@
+PUSHDIVERT(-1)
+#
+# Copyright (c) 1998-2000, 2004 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+# Copyright (c) 1983 Eric P. Allman. All rights reserved.
+# Copyright (c) 1988, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+# ident "%Z%%M% %I% %E% SMI"
+#
+_DEFIFNOT(`_DEF_LOCAL_MAILER_FLAGS', `lsDFMAw5:/|@q')
+_DEFIFNOT(`LOCAL_MAILER_FLAGS', `Prmn9')
+ifdef(`LOCAL_MAILER_PATH',, `define(`LOCAL_MAILER_PATH', /bin/mail)')
+ifdef(`LOCAL_MAILER_ARGS',, `define(`LOCAL_MAILER_ARGS', `mail -d $u')')
+ifdef(`LOCAL_MAILER_DSN_DIAGNOSTIC_CODE',, `define(`LOCAL_MAILER_DSN_DIAGNOSTIC_CODE', `X-Unix')')
+_DEFIFNOT(`_DEF_LOCAL_SHELL_FLAGS', `lsDFMoq')
+_DEFIFNOT(`LOCAL_SHELL_FLAGS', `eu9')
+ifdef(`LOCAL_SHELL_PATH',, `define(`LOCAL_SHELL_PATH', /bin/sh)')
+ifdef(`LOCAL_SHELL_ARGS',, `define(`LOCAL_SHELL_ARGS', `sh -c $u')')
+ifdef(`LOCAL_SHELL_DIR',, `define(`LOCAL_SHELL_DIR', `$z:/')')
+define(`LOCAL_RWR', `ifdef(`_LOCAL_LMTP_',
+`S=EnvFromSMTP/HdrFromL, R=EnvToL/HdrToL',
+`S=EnvFromL/HdrFromL, R=EnvToL/HdrToL')')
+define(`_LOCAL_QGRP', `ifelse(defn(`LOCAL_MAILER_QGRP'),`',`', ` Q=LOCAL_MAILER_QGRP,')')dnl
+define(`_PROG_QGRP', `ifelse(defn(`LOCAL_PROG_QGRP'),`',`', ` Q=LOCAL_PROG_QGRP,')')dnl
+POPDIVERT
+
+##################################################
+### Local and Program Mailer specification ###
+##################################################
+
+VERSIONID(`$Id: local.m4,v 8.59 2004/11/23 00:37:25 ca Exp $')
+
+#
+# Envelope sender rewriting
+#
+SEnvFromL
+R<@> $n errors to mailer-daemon
+R@ <@ $*> $n temporarily bypass Sun bogosity
+R$+ $: $>AddDomain $1 add local domain if needed
+ifdef(`_LOCAL_NO_MASQUERADE_', `dnl', `dnl
+R$* $: $>MasqEnv $1 do masquerading')
+
+#
+# Envelope recipient rewriting
+#
+SEnvToL
+R$+ < @ $* > $: $1 strip host part
+ifdef(`confUSERDB_SPEC', `dnl', `dnl
+R$+ + $* $: < $&{addr_type} > $1 + $2 mark with addr type
+R<e s> $+ + $* $: $1 remove +detail for sender
+R< $* > $+ $: $2 else remove mark')
+
+#
+# Header sender rewriting
+#
+SHdrFromL
+R<@> $n errors to mailer-daemon
+R@ <@ $*> $n temporarily bypass Sun bogosity
+R$+ $: $>AddDomain $1 add local domain if needed
+ifdef(`_LOCAL_NO_MASQUERADE_', `dnl', `dnl
+R$* $: $>MasqHdr $1 do masquerading')
+
+#
+# Header recipient rewriting
+#
+SHdrToL
+R$+ $: $>AddDomain $1 add local domain if needed
+ifdef(`_ALL_MASQUERADE_', `dnl
+ifdef(`_LOCAL_NO_MASQUERADE_', `dnl', `dnl
+R$* $: $>MasqHdr $1 do all-masquerading')',
+`R$* < @ *LOCAL* > $* $: $1 < @ $j . > $2')
+
+#
+# Common code to add local domain name (only if always-add-domain)
+#
+SAddDomain
+ifdef(`_ALWAYS_ADD_DOMAIN_', `dnl
+R$* < @ $* > $* $@ $1 < @ $2 > $3 already fully qualified
+ifelse(len(X`'_ALWAYS_ADD_DOMAIN_),`1',`
+R$+ $@ $1 < @ *LOCAL* > add local qualification',
+`R$+ $@ $1 < @ _ALWAYS_ADD_DOMAIN_ > add qualification')',
+`dnl')
+
+Mlocal, P=LOCAL_MAILER_PATH, F=_MODMF_(CONCAT(_DEF_LOCAL_MAILER_FLAGS, LOCAL_MAILER_FLAGS), `LOCAL'), LOCAL_RWR,_OPTINS(`LOCAL_MAILER_EOL', ` E=', `, ')
+ _OPTINS(`LOCAL_MAILER_MAX', `M=', `, ')_OPTINS(`LOCAL_MAILER_MAXMSGS', `m=', `, ')_OPTINS(`LOCAL_MAILER_MAXRCPTS', `r=', `, ')_OPTINS(`LOCAL_MAILER_CHARSET', `C=', `, ')T=DNS/RFC822/LOCAL_MAILER_DSN_DIAGNOSTIC_CODE,_LOCAL_QGRP
+ A=LOCAL_MAILER_ARGS
+Mprog, P=LOCAL_SHELL_PATH, F=_MODMF_(CONCAT(_DEF_LOCAL_SHELL_FLAGS, LOCAL_SHELL_FLAGS), `SHELL'), S=EnvFromL/HdrFromL, R=EnvToL/HdrToL, D=LOCAL_SHELL_DIR,
+ _OPTINS(`LOCAL_MAILER_MAX', `M=', `, ')T=X-Unix/X-Unix/X-Unix,_PROG_QGRP
+ A=LOCAL_SHELL_ARGS
diff --git a/usr/src/cmd/sendmail/cf/mailer/procmail.m4 b/usr/src/cmd/sendmail/cf/mailer/procmail.m4
new file mode 100644
index 0000000000..5646a15306
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/mailer/procmail.m4
@@ -0,0 +1,35 @@
+PUSHDIVERT(-1)
+#
+# Copyright (c) 1998-2001 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+# Copyright (c) 1983 Eric P. Allman. All rights reserved.
+# Copyright (c) 1988, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+#ident "%Z%%M% %I% %E% SMI"
+#
+
+ifdef(`PROCMAIL_MAILER_PATH',,
+ `ifdef(`PROCMAIL_PATH',
+ `define(`PROCMAIL_MAILER_PATH', PROCMAIL_PATH)',
+ `define(`PROCMAIL_MAILER_PATH', /usr/local/bin/procmail)')')
+_DEFIFNOT(`PROCMAIL_MAILER_FLAGS', `SPhnu9')
+ifdef(`PROCMAIL_MAILER_ARGS',,
+ `define(`PROCMAIL_MAILER_ARGS', `procmail -Y -m $h $f $u')')
+define(`_PROCMAIL_QGRP', `ifelse(defn(`PROCMAIL_MAILER_QGRP'),`',`', ` Q=PROCMAIL_MAILER_QGRP,')')dnl
+
+POPDIVERT
+
+######################*****##############
+### PROCMAIL Mailer specification ###
+##################*****##################
+
+VERSIONID(`$Id: procmail.m4,v 8.22 2001/11/12 23:11:34 ca Exp $')
+
+Mprocmail, P=PROCMAIL_MAILER_PATH, F=_MODMF_(CONCAT(`DFM', PROCMAIL_MAILER_FLAGS), `PROCMAIL'), S=EnvFromSMTP/HdrFromSMTP, R=EnvToSMTP/HdrFromSMTP,
+ ifdef(`PROCMAIL_MAILER_MAX', `M=PROCMAIL_MAILER_MAX, ')T=DNS/RFC822/X-Unix,_PROCMAIL_QGRP
+ A=PROCMAIL_MAILER_ARGS
diff --git a/usr/src/cmd/sendmail/cf/mailer/smtp.m4 b/usr/src/cmd/sendmail/cf/mailer/smtp.m4
new file mode 100644
index 0000000000..31e554e515
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/mailer/smtp.m4
@@ -0,0 +1,123 @@
+PUSHDIVERT(-1)
+#
+# Copyright (c) 1998-2001 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+# Copyright (c) 1983 Eric P. Allman. All rights reserved.
+# Copyright (c) 1988, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+#ident "%Z%%M% %I% %E% SMI"
+#
+_DEFIFNOT(`_DEF_SMTP_MAILER_FLAGS', `mDFMuX')
+_DEFIFNOT(`SMTP_MAILER_FLAGS',`')
+_DEFIFNOT(`RELAY_MAILER_FLAGS', `SMTP_MAILER_FLAGS')
+ifdef(`SMTP_MAILER_ARGS',, `define(`SMTP_MAILER_ARGS', `TCP $h')')
+ifdef(`ESMTP_MAILER_ARGS',, `define(`ESMTP_MAILER_ARGS', `TCP $h')')
+ifdef(`SMTP8_MAILER_ARGS',, `define(`SMTP8_MAILER_ARGS', `TCP $h')')
+ifdef(`DSMTP_MAILER_ARGS',, `define(`DSMTP_MAILER_ARGS', `TCP $h')')
+ifdef(`RELAY_MAILER_ARGS',, `define(`RELAY_MAILER_ARGS', `TCP $h')')
+define(`_SMTP_QGRP', `ifelse(defn(`SMTP_MAILER_QGRP'),`',`', ` Q=SMTP_MAILER_QGRP,')')dnl
+define(`_ESMTP_QGRP', `ifelse(defn(`ESMTP_MAILER_QGRP'),`',`', ` Q=ESMTP_MAILER_QGRP,')')dnl
+define(`_SMTP8_QGRP', `ifelse(defn(`SMTP8_MAILER_QGRP'),`',`', ` Q=SMTP8_MAILER_QGRP,')')dnl
+define(`_DSMTP_QGRP', `ifelse(defn(`DSMTP_MAILER_QGRP'),`',`', ` Q=DSMTP_MAILER_QGRP,')')dnl
+define(`_RELAY_QGRP', `ifelse(defn(`RELAY_MAILER_QGRP'),`',`', ` Q=RELAY_MAILER_QGRP,')')dnl
+POPDIVERT
+#####################################
+### SMTP Mailer specification ###
+#####################################
+
+VERSIONID(`$Id: smtp.m4,v 8.64 2001/04/03 01:52:54 gshapiro Exp $')
+
+#
+# common sender and masquerading recipient rewriting
+#
+SMasqSMTP
+R$* < @ $* > $* $@ $1 < @ $2 > $3 already fully qualified
+R$+ $@ $1 < @ *LOCAL* > add local qualification
+
+#
+# convert pseudo-domain addresses to real domain addresses
+#
+SPseudoToReal
+
+# pass <route-addr>s through
+R< @ $+ > $* $@ < @ $1 > $2 resolve <route-addr>
+
+# output fake domains as user%fake@relay
+ifdef(`BITNET_RELAY',
+`R$+ <@ $+ .BITNET. > $: $1 % $2 .BITNET < @ $B > user@host.BITNET
+R$+.BITNET <@ $~[ $*:$+ > $: $1 .BITNET < @ $4 > strip mailer: part',
+ `dnl')
+ifdef(`_NO_UUCP_', `dnl', `
+# do UUCP heuristics; note that these are shared with UUCP mailers
+R$+ < @ $+ .UUCP. > $: < $2 ! > $1 convert to UUCP form
+R$+ < @ $* > $* $@ $1 < @ $2 > $3 not UUCP form
+
+# leave these in .UUCP form to avoid further tampering
+R< $&h ! > $- ! $+ $@ $2 < @ $1 .UUCP. >
+R< $&h ! > $-.$+ ! $+ $@ $3 < @ $1.$2 >
+R< $&h ! > $+ $@ $1 < @ $&h .UUCP. >
+R< $+ ! > $+ $: $1 ! $2 < @ $Y > use UUCP_RELAY
+R$+ < @ $~[ $* : $+ > $@ $1 < @ $4 > strip mailer: part
+R$+ < @ > $: $1 < @ *LOCAL* > if no UUCP_RELAY')
+
+
+#
+# envelope sender rewriting
+#
+SEnvFromSMTP
+R$+ $: $>PseudoToReal $1 sender/recipient common
+R$* :; <@> $@ list:; special case
+R$* $: $>MasqSMTP $1 qualify unqual'ed names
+R$+ $: $>MasqEnv $1 do masquerading
+
+
+#
+# envelope recipient rewriting --
+# also header recipient if not masquerading recipients
+#
+SEnvToSMTP
+R$+ $: $>PseudoToReal $1 sender/recipient common
+R$+ $: $>MasqSMTP $1 qualify unqual'ed names
+R$* < @ *LOCAL* > $* $: $1 < @ $j . > $2
+
+#
+# header sender and masquerading header recipient rewriting
+#
+SHdrFromSMTP
+R$+ $: $>PseudoToReal $1 sender/recipient common
+R:; <@> $@ list:; special case
+
+# do special header rewriting
+R$* <@> $* $@ $1 <@> $2 pass null host through
+R< @ $* > $* $@ < @ $1 > $2 pass route-addr through
+R$* $: $>MasqSMTP $1 qualify unqual'ed names
+R$+ $: $>MasqHdr $1 do masquerading
+
+
+#
+# relay mailer header masquerading recipient rewriting
+#
+SMasqRelay
+R$+ $: $>MasqSMTP $1
+R$+ $: $>MasqHdr $1
+
+Msmtp, P=[IPC], F=_MODMF_(CONCAT(_DEF_SMTP_MAILER_FLAGS, SMTP_MAILER_FLAGS), `SMTP'), S=EnvFromSMTP/HdrFromSMTP, R=ifdef(`_ALL_MASQUERADE_', `EnvToSMTP/HdrFromSMTP', `EnvToSMTP'), E=\r\n, L=990,
+ _OPTINS(`SMTP_MAILER_MAX', `M=', `, ')_OPTINS(`SMTP_MAILER_MAXMSGS', `m=', `, ')_OPTINS(`SMTP_MAILER_MAXRCPTS', `r=', `, ')_OPTINS(`SMTP_MAILER_CHARSET', `C=', `, ')T=DNS/RFC822/SMTP,_SMTP_QGRP
+ A=SMTP_MAILER_ARGS
+Mesmtp, P=[IPC], F=_MODMF_(CONCAT(_DEF_SMTP_MAILER_FLAGS, `a', SMTP_MAILER_FLAGS), `ESMTP'), S=EnvFromSMTP/HdrFromSMTP, R=ifdef(`_ALL_MASQUERADE_', `EnvToSMTP/HdrFromSMTP', `EnvToSMTP'), E=\r\n, L=990,
+ _OPTINS(`SMTP_MAILER_MAX', `M=', `, ')_OPTINS(`SMTP_MAILER_MAXMSGS', `m=', `, ')_OPTINS(`SMTP_MAILER_MAXRCPTS', `r=', `, ')_OPTINS(`SMTP_MAILER_CHARSET', `C=', `, ')T=DNS/RFC822/SMTP,_ESMTP_QGRP
+ A=ESMTP_MAILER_ARGS
+Msmtp8, P=[IPC], F=_MODMF_(CONCAT(_DEF_SMTP_MAILER_FLAGS, `8', SMTP_MAILER_FLAGS), `SMTP8'), S=EnvFromSMTP/HdrFromSMTP, R=ifdef(`_ALL_MASQUERADE_', `EnvToSMTP/HdrFromSMTP', `EnvToSMTP'), E=\r\n, L=990,
+ _OPTINS(`SMTP_MAILER_MAX', `M=', `, ')_OPTINS(`SMTP_MAILER_MAXMSGS', `m=', `, ')_OPTINS(`SMTP_MAILER_MAXRCPTS', `r=', `, ')_OPTINS(`SMTP_MAILER_CHARSET', `C=', `, ')T=DNS/RFC822/SMTP,_SMTP8_QGRP
+ A=SMTP8_MAILER_ARGS
+Mdsmtp, P=[IPC], F=_MODMF_(CONCAT(_DEF_SMTP_MAILER_FLAGS, `a%', SMTP_MAILER_FLAGS), `DSMTP'), S=EnvFromSMTP/HdrFromSMTP, R=ifdef(`_ALL_MASQUERADE_', `EnvToSMTP/HdrFromSMTP', `EnvToSMTP'), E=\r\n, L=990,
+ _OPTINS(`SMTP_MAILER_MAX', `M=', `, ')_OPTINS(`SMTP_MAILER_MAXMSGS', `m=', `, ')_OPTINS(`SMTP_MAILER_MAXRCPTS', `r=', `, ')_OPTINS(`SMTP_MAILER_CHARSET', `C=', `, ')T=DNS/RFC822/SMTP,_DSMTP_QGRP
+ A=DSMTP_MAILER_ARGS
+Mrelay, P=[IPC], F=_MODMF_(CONCAT(_DEF_SMTP_MAILER_FLAGS, `a8', RELAY_MAILER_FLAGS), `RELAY'), S=EnvFromSMTP/HdrFromSMTP, R=ifdef(`_ALL_MASQUERADE_', `MasqSMTP/MasqRelay', `MasqSMTP'), E=\r\n, L=2040,
+ _OPTINS(`RELAY_MAILER_CHARSET', `C=', `, ')_OPTINS(`RELAY_MAILER_MAXMSGS', `m=', `, ')_OPTINS(`SMTP_MAILER_MAXRCPTS', `r=', `, ')T=DNS/RFC822/SMTP,_RELAY_QGRP
+ A=RELAY_MAILER_ARGS
diff --git a/usr/src/cmd/sendmail/cf/mailer/uucp.m4 b/usr/src/cmd/sendmail/cf/mailer/uucp.m4
new file mode 100644
index 0000000000..85cf5426be
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/mailer/uucp.m4
@@ -0,0 +1,158 @@
+PUSHDIVERT(-1)
+#
+# Copyright (c) 1998-2001 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+# Copyright (c) 1983 Eric P. Allman. All rights reserved.
+# Copyright (c) 1988, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+#ident "%Z%%M% %I% %E% SMI"
+#
+
+ifdef(`UUCP_MAILER_PATH',, `define(`UUCP_MAILER_PATH', /usr/bin/uux)')
+ifdef(`UUCP_MAILER_ARGS',, `define(`UUCP_MAILER_ARGS', `uux - -r -a$g -gC $h!rmail ($u)')')
+_DEFIFNOT(`UUCP_MAILER_FLAGS', `')
+ifdef(`UUCP_MAILER_MAX',,
+ `define(`UUCP_MAILER_MAX',
+ `ifdef(`UUCP_MAX_SIZE', `UUCP_MAX_SIZE', 100000)')')
+define(`_UUCP_QGRP', `ifelse(defn(`UUCP_MAILER_QGRP'),`',`', ` Q=UUCP_MAILER_QGRP,')')dnl
+POPDIVERT
+#####################################
+### UUCP Mailer specification ###
+#####################################
+
+VERSIONID(`$Id: uucp.m4,v 8.44 2001/08/24 19:49:08 ca Exp $')
+
+#
+# envelope and header sender rewriting
+#
+SFromU
+
+# handle error address as a special case
+R<@> $n errors to mailer-daemon
+
+# list:; syntax should disappear
+R:; <@> $@
+
+R$* < @ $* . > $* $1 < @ $2 > $3 strip trailing dots
+R$* < @ $=w > $1 strip local name
+R<@ $- . UUCP > : $+ $1 ! $2 convert to UUCP format
+R<@ $+ > : $+ $1 ! $2 convert to UUCP format
+R$* < @ $- . UUCP > $2 ! $1 convert to UUCP format
+R$* < @ $+ > $2 ! $1 convert to UUCP format
+R$&h ! $+ ! $+ $@ $1 ! $2 $h!...!user => ...!user
+R$&h ! $+ $@ $&h ! $1 $h!user => $h!user
+R$+ $: $U ! $1 prepend our name
+R! $+ $: $k ! $1 in case $U undefined
+
+#
+# envelope recipient rewriting
+#
+SEnvToU
+
+# list:; should disappear
+R:; <@> $@
+
+R$* < @ $* . > $* $1 < @ $2 > $3 strip trailing dots
+R$* < @ $=w > $1 strip local name
+R<@ $- . UUCP > : $+ $1 ! $2 convert to UUCP format
+R<@ $+ > : $+ $1 ! $2 convert to UUCP format
+R$* < @ $- . UUCP > $2 ! $1 convert to UUCP format
+R$* < @ $+ > $2 ! $1 convert to UUCP format
+
+#
+# header recipient rewriting
+#
+SHdrToU
+
+# list:; syntax should disappear
+R:; <@> $@
+
+R$* < @ $* . > $* $1 < @ $2 > $3 strip trailing dots
+R$* < @ $=w > $1 strip local name
+R<@ $- . UUCP > : $+ $1 ! $2 convert to UUCP format
+R<@ $+ > : $+ $1 ! $2 convert to UUCP format
+R$* < @ $- . UUCP > $2 ! $1 convert to UUCP format
+R$* < @ $+ > $2 ! $1 convert to UUCP format
+R$&h ! $+ ! $+ $@ $1 ! $2 $h!...!user => ...!user
+R$&h ! $+ $@ $&h ! $1 $h!user => $h!user
+R$+ $: $U ! $1 prepend our name
+R! $+ $: $k ! $1 in case $U undefined
+
+
+ifdef(`_MAILER_smtp_',
+`#
+# envelope sender rewriting for uucp-dom mailer
+#
+SEnvFromUD
+
+# handle error address as a special case
+R<@> $n errors to mailer-daemon
+
+# pass everything to standard SMTP mailer rewriting
+R$* $@ $>EnvFromSMTP $1
+
+#
+# envelope sender rewriting for uucp-uudom mailer
+#
+SEnvFromUUD
+
+# handle error address as a special case
+R<@> $n errors to mailer-daemon
+
+# do standard SMTP mailer rewriting
+R$* $: $>EnvFromSMTP $1
+
+R$* < @ $* . > $* $1 < @ $2 > $3 strip trailing dots
+R<@ $- . UUCP > : $+ $@ $1 ! $2 convert to UUCP format
+R<@ $+ > : $+ $@ $1 ! $2 convert to UUCP format
+R$* < @ $- . UUCP > $@ $2 ! $1 convert to UUCP format
+R$* < @ $+ > $@ $2 ! $1 convert to UUCP format',
+`errprint(`*** MAILER(`smtp') must appear before MAILER(`uucp')
+ if uucp-dom should be included.')
+')
+
+PUSHDIVERT(4)
+# resolve locally connected UUCP links
+R$* < @ $=Z . UUCP. > $* $#uucp-uudom $@ $2 $: $1 < @ $2 .UUCP. > $3
+R$* < @ $=Y . UUCP. > $* $#uucp-new $@ $2 $: $1 < @ $2 .UUCP. > $3
+R$* < @ $=U . UUCP. > $* $#uucp-old $@ $2 $: $1 < @ $2 .UUCP. > $3
+POPDIVERT
+
+#
+# There are innumerable variations on the UUCP mailer. It really
+# is rather absurd.
+#
+
+# old UUCP mailer (two names)
+Muucp, P=UUCP_MAILER_PATH, F=_MODMF_(CONCAT(`DFMhuUd', UUCP_MAILER_FLAGS), `UUCP'), S=FromU, R=EnvToU/HdrToU,
+ M=UUCP_MAILER_MAX, _OPTINS(`UUCP_MAILER_CHARSET', `C=', `, ')T=X-UUCP/X-UUCP/X-Unix,_UUCP_QGRP
+ A=UUCP_MAILER_ARGS
+Muucp-old, P=UUCP_MAILER_PATH, F=_MODMF_(CONCAT(`DFMhuUd', UUCP_MAILER_FLAGS), `UUCP'), S=FromU, R=EnvToU/HdrToU,
+ M=UUCP_MAILER_MAX, _OPTINS(`UUCP_MAILER_CHARSET', `C=', `, ')T=X-UUCP/X-UUCP/X-Unix,_UUCP_QGRP
+ A=UUCP_MAILER_ARGS
+
+# smart UUCP mailer (handles multiple addresses) (two names)
+Msuucp, P=UUCP_MAILER_PATH, F=_MODMF_(CONCAT(`mDFMhuUd', UUCP_MAILER_FLAGS), `UUCP'), S=FromU, R=EnvToU/HdrToU,
+ M=UUCP_MAILER_MAX, _OPTINS(`UUCP_MAILER_CHARSET', `C=', `, ')T=X-UUCP/X-UUCP/X-Unix,_UUCP_QGRP
+ A=UUCP_MAILER_ARGS
+Muucp-new, P=UUCP_MAILER_PATH, F=_MODMF_(CONCAT(`mDFMhuUd', UUCP_MAILER_FLAGS), `UUCP'), S=FromU, R=EnvToU/HdrToU,
+ M=UUCP_MAILER_MAX, _OPTINS(`UUCP_MAILER_CHARSET', `C=', `, ')T=X-UUCP/X-UUCP/X-Unix,_UUCP_QGRP
+ A=UUCP_MAILER_ARGS
+
+ifdef(`_MAILER_smtp_',
+`# domain-ized UUCP mailer
+Muucp-dom, P=UUCP_MAILER_PATH, F=_MODMF_(CONCAT(`mDFMhud', UUCP_MAILER_FLAGS), `UUCP'), S=EnvFromUD/HdrFromSMTP, R=ifdef(`_ALL_MASQUERADE_', `EnvToSMTP/HdrFromSMTP', `EnvToSMTP'),
+ M=UUCP_MAILER_MAX, _OPTINS(`UUCP_MAILER_CHARSET', `C=', `, ')T=X-UUCP/X-UUCP/X-Unix,_UUCP_QGRP
+ A=UUCP_MAILER_ARGS
+
+# domain-ized UUCP mailer with UUCP-style sender envelope
+Muucp-uudom, P=UUCP_MAILER_PATH, F=_MODMF_(CONCAT(`mDFMhud', UUCP_MAILER_FLAGS), `UUCP'), S=EnvFromUUD/HdrFromSMTP, R=ifdef(`_ALL_MASQUERADE_', `EnvToSMTP/HdrFromSMTP', `EnvToSMTP'),
+ M=UUCP_MAILER_MAX, _OPTINS(`UUCP_MAILER_CHARSET', `C=', `, ')T=X-UUCP/X-UUCP/X-Unix,_UUCP_QGRP
+ A=UUCP_MAILER_ARGS')
+
+
diff --git a/usr/src/cmd/sendmail/cf/ostype/solaris2.m4 b/usr/src/cmd/sendmail/cf/ostype/solaris2.m4
new file mode 100644
index 0000000000..6cf14846d9
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/ostype/solaris2.m4
@@ -0,0 +1,27 @@
+divert(-1)
+#
+# Copyright (c) 1998, 1999 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+# Copyright (c) 1983 Eric P. Allman. All rights reserved.
+# Copyright (c) 1988, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+#
+# This ostype file is suitable for use on Solaris 2.x systems that
+# have mail.local installed. It is my understanding that this is
+# standard as of Solaris 2.5.
+#
+
+divert(0)
+VERSIONID(`$Id: solaris2.m4,v 8.22 1999/09/24 21:43:53 ca Exp $')
+divert(-1)
+
+ifdef(`LOCAL_MAILER_PATH',, `define(`LOCAL_MAILER_PATH', `/usr/lib/mail.local')')
+_DEFIFNOT(`LOCAL_MAILER_FLAGS', `fSmn9')
+ifdef(`LOCAL_MAILER_ARGS',, `define(`LOCAL_MAILER_ARGS', `mail.local -d $u')')
+ifdef(`UUCP_MAILER_ARGS',, `define(`UUCP_MAILER_ARGS', `uux - -r -a$g $h!rmail ($u)')')
+define(`confEBINDIR', `/usr/lib')dnl
diff --git a/usr/src/cmd/sendmail/cf/ostype/solaris2.ml.m4 b/usr/src/cmd/sendmail/cf/ostype/solaris2.ml.m4
new file mode 100644
index 0000000000..72cb72923e
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/ostype/solaris2.ml.m4
@@ -0,0 +1,27 @@
+divert(-1)
+#
+# Copyright (c) 1998, 1999 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+# Copyright (c) 1983 Eric P. Allman. All rights reserved.
+# Copyright (c) 1988, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+#
+# This ostype file is suitable for use on Solaris 2.x systems that
+# have mail.local installed. It is my understanding that this is
+# standard as of Solaris 2.5.
+#
+
+divert(0)
+VERSIONID(`$Id: solaris2.ml.m4,v 8.14 1999/04/24 05:37:44 gshapiro Exp $')
+divert(-1)
+
+ifdef(`LOCAL_MAILER_PATH',, `define(`LOCAL_MAILER_PATH', `/usr/lib/mail.local')')
+_DEFIFNOT(`LOCAL_MAILER_FLAGS', `fSmn9')
+ifdef(`LOCAL_MAILER_ARGS',, `define(`LOCAL_MAILER_ARGS', `mail.local -d $u')')
+ifdef(`UUCP_MAILER_ARGS',, `define(`UUCP_MAILER_ARGS', `uux - -r -a$g $h!rmail ($u)')')
+define(`confEBINDIR', `/usr/lib')dnl
diff --git a/usr/src/cmd/sendmail/cf/ostype/solaris2.pre5.m4 b/usr/src/cmd/sendmail/cf/ostype/solaris2.pre5.m4
new file mode 100644
index 0000000000..f664712fd6
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/ostype/solaris2.pre5.m4
@@ -0,0 +1,27 @@
+divert(-1)
+#
+# Copyright (c) 1998, 1999 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+# Copyright (c) 1983 Eric P. Allman. All rights reserved.
+# Copyright (c) 1988, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+#ident "%Z%%M% %I% %E% SMI"
+#
+# This ostype file is suitable for use on Solaris 2.x systems that
+# use mail as local mailer which are usually version before 2.5.
+#
+
+
+divert(0)
+VERSIONID(`$Id: solaris2.pre5.m4,v 8.1 1999/09/25 08:17:44 ca Exp $')
+divert(-1)
+
+_DEFIFNOT(`LOCAL_MAILER_FLAGS', `SnE9')
+ifdef(`LOCAL_MAILER_ARGS',, `define(`LOCAL_MAILER_ARGS', `mail -f $g -d $u')')
+ifdef(`UUCP_MAILER_ARGS',, `define(`UUCP_MAILER_ARGS', `uux - -r -a$g $h!rmail ($u)')')
+define(`confEBINDIR', `/usr/lib')dnl
diff --git a/usr/src/cmd/sendmail/cf/ostype/solaris8.m4 b/usr/src/cmd/sendmail/cf/ostype/solaris8.m4
new file mode 100644
index 0000000000..c4bd431824
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/ostype/solaris8.m4
@@ -0,0 +1,26 @@
+divert(-1)
+#
+# Copyright (c) 2000 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+#ident "%Z%%M% %I% %E% SMI"
+#
+# This ostype file is suitable for use on Solaris 8 and later systems,
+# taking advantage of mail.local's LMTP support, the existence of
+# /var/run and support for IPv6, all of which where introduced in
+# Solaris 8.
+#
+
+divert(0)
+VERSIONID(`$Id: solaris8.m4,v 8.2 2000/08/23 16:10:49 gshapiro Exp $')
+divert(-1)
+
+ifdef(`UUCP_MAILER_ARGS',, `define(`UUCP_MAILER_ARGS', `uux - -r -a$g $h!rmail ($u)')')
+define(`confEBINDIR', `/usr/lib')dnl
+define(`confPID_FILE', `/var/run/sendmail.pid')dnl
+define(`_NETINET6_')dnl
+FEATURE(`local_lmtp')dnl
diff --git a/usr/src/cmd/sendmail/cf/sh/check-hostname.sh b/usr/src/cmd/sendmail/cf/sh/check-hostname.sh
new file mode 100644
index 0000000000..1354153be6
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/sh/check-hostname.sh
@@ -0,0 +1,214 @@
+#!/bin/sh --
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License, Version 1.0 only
+# (the "License"). You may not use this file except in compliance
+# with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+# Check hostname configuration as per the sendmail code.
+#
+# See http://www.sendmail.org/sun-specific/migration.html#FQHN for details.
+#
+# Copyright (c) 1997-2000 by Sun Microsystems, Inc.
+# All Rights Reserved.
+#
+# %W% (Sun) %G%
+# ident "%Z%%M% %I% %E% SMI"
+
+PATH=/bin:/usr/sbin
+
+# If $1 has a ".", accept it and exit.
+
+accept_if_fully_qualified() {
+ case $1 in
+ *.*)
+ echo "Hostname $myhostname OK: fully qualified as $1"
+ exit 0
+ ;;
+ esac
+}
+
+# Check the `getent hosts $1` output, skipping the 1st entry (IP address).
+
+check_gethostbyname() {
+ for host in `getent hosts $1 | awk '{for (f=2; f <= NF; f++) print $f}'`
+ do
+ accept_if_fully_qualified $host
+ done
+}
+
+# Parse /etc/hosts, looking for $1 as an entry by itself, and try to find
+# a long name on the same line. First kill all comments, then check for
+# $1 as a word by itself, then take just the first such line, then skip
+# its first entry (IP address).
+
+check_hosts_file() {
+ for entry in `sed -e 's/#.*$//' /etc/hosts | \
+ awk '/[ ]'$1'([ ]|$)/ \
+ {for (f=2; f <= NF; f++) print $f; exit}'`
+ do
+ accept_if_fully_qualified $entry
+ done
+}
+
+# Parse the output of `nslookup $1`, checking the Name and Aliases.
+
+check_dns() {
+ for host in `nslookup $1 2>/dev/null | \
+ awk '$1 == "Name:" || $1 == "Aliases:"{print $2}'`
+ do
+ accept_if_fully_qualified $host
+ done
+}
+
+# Check the `ypmatch $1 hosts` output, skipping the 1st entry (IP address).
+
+check_nis() {
+ for hst in `ypmatch $1 hosts | awk '{for (f=2; f <= NF; f++) print $f}'`
+ do
+ accept_if_fully_qualified $hst
+ done
+}
+
+# Check the `nismatch $1 hosts` output. Its output is different from ypmatch
+# and the hosts file. Field 1 is a cname (i.e., alias), field 2 is the
+# proper name, field 3 is the IP address and field 4 is comment.
+
+check_nisplus() {
+ for hst in `nismatch $1 hosts.org_dir | \
+ awk '{for (f=1; f <= 2; f++) print $f}'`
+ do
+ accept_if_fully_qualified $hst
+ done
+}
+
+# Recommend how to reconfigure to get $1.$2 as the FQHN.
+# $3 is the first entry for hosts in /etc/nsswitch.conf .
+
+suggest_fix_and_exit() {
+ myhost=$1
+ suggested_domain=$2
+ fhe=$3
+ myipaddr=`getent hosts $myhost | head -1 | awk '{print $1}'`
+
+ # aliases: skip the 1st & 2nd entries: IP address & canonical name
+
+ set -- '' '' '[ aliases ... ]'
+ set -- `grep "^$myipaddr[ ]" /etc/hosts 2>/dev/null`
+ result=$?
+ shift 2
+ echo "We recommend \c"
+ if [ "x$fhe" != "xfiles" ] ; then
+ echo "listing files first for hosts in /etc/nsswitch.conf"
+ echo "and then \c"
+ fi
+ if [ $result = 0 ] ; then
+ echo "changing the /etc/hosts entry:\n"
+ echo "$myipaddr $myhost $*\n"
+ echo "to:\n"
+ else
+ echo "adding the /etc/hosts entry:\n"
+ fi
+ echo "$myipaddr $myhost $myhost.$suggested_domain $*"
+ exit 0
+}
+
+# Fall back to the NIS[+] domain, minus the first label. If it is non-null,
+# use it but recommend against it. $2 is just informative, indicating whether
+# we're checking the NIS or NIS+ domain. $3 is to pass on.
+
+check_nis_domain() {
+ nisdomain=`domainname`
+ realdomain=`echo $nisdomain | sed 's/[^.]*\.//'`
+ if [ "x$realdomain" != "x" ] ; then
+ echo "Hostname $1 can be fully qualified using NIS$2 domain"
+ echo " $nisdomain"
+ echo "resulting in the name"
+ echo " $1.$realdomain"
+ echo "but this is bad practice.\n"
+ suggest_fix_and_exit $1 $realdomain $3
+ fi
+}
+
+# Goal: try to fully qualify `hostname` as sendmail would.
+# Algorithm (stop as soon as a name with a dot is found):
+# 1. gethostbyname (simulate with getent hosts)
+# 2. fall back to individual hosts: methods in nsswitch.conf, using
+# only those that are configured, in their configured order
+# * files (parse /etc/hosts directly)
+# * dns (parse nslookup output)
+# * nis (parse ypmatch output)
+# * nisplus (parse nismatch output)
+# 3. fall back to the NIS[+] domain name.
+# If none of the above succeed, give up. Recommend:
+# a. the domain entry in /etc/resolv.conf, if one exists
+# b. "pick.some.domain"
+
+myhostname=`hostname`
+
+check_gethostbyname $myhostname
+
+hosts_line=`sed -n -e 's/^hosts:\([^#]*\).*/\1/p' /etc/nsswitch.conf`
+first_hosts_entry=`echo $hosts_line | awk '{print $1}'`
+nis_domains=""
+
+for entry in $hosts_line
+do
+ case $entry in
+ files)
+ check_hosts_file $myhostname
+ ;;
+ dns)
+ check_dns $myhostname
+ ;;
+ nis)
+ check_nis $myhostname
+ nis_domains="$nis_domains nis"
+ ;;
+ nisplus)
+ check_nisplus $myhostname
+ nis_domains="$nis_domains nisplus"
+ ;;
+ esac
+done
+
+for entry in $nis_domains
+do
+ case $entry in
+ nis)
+ check_nis_domain $myhostname "" $first_hosts_entry
+ ;;
+ nisplus)
+ check_nis_domain $myhostname "+" $first_hosts_entry
+ ;;
+ esac
+done
+
+realdomain=`awk '$1 ~ /^domain/ {print $2}' 2>/dev/null < /etc/resolv.conf`
+case $realdomain in
+*.*)
+ # OK
+ ;;
+*)
+ realdomain="pick.some.domain"
+ ;;
+esac
+
+echo "Hostname $myhostname could not be fully qualified."
+suggest_fix_and_exit $myhostname $realdomain $first_hosts_entry
diff --git a/usr/src/cmd/sendmail/cf/sh/check-permissions.sh b/usr/src/cmd/sendmail/cf/sh/check-permissions.sh
new file mode 100644
index 0000000000..d7331d9ec3
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/sh/check-permissions.sh
@@ -0,0 +1,121 @@
+#!/bin/sh --
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License, Version 1.0 only
+# (the "License"). You may not use this file except in compliance
+# with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+# Check :include: aliases (in files configured in sendmail.cf) and .forward
+# files to make sure the files and their parent directory paths all have
+# proper permissions. And check the master alias file(s) too.
+#
+# See http://www.sendmail.org/sun-specific/migration.html#Security for details.
+#
+# Copyright (c) 1998-2000 by Sun Microsystems, Inc.
+# All Rights Reserved.
+#
+# %W% (Sun) %G%
+# ident "%Z%%M% %I% %E% SMI"
+
+PATH=/bin
+
+# Check the group- and world-writable bits on the given file.
+
+analyze() {
+ case "`ls -Lldn $1`" in
+ ?????w??w?*)
+ echo $2: $1 is group and world writable
+ bogus_dirs=true ;;
+ ????????w?*)
+ echo $2: $1 is world writable
+ bogus_dirs=true ;;
+ ?????w????*)
+ echo $2: $1 is group writable
+ bogus_dirs=true ;;
+ esac
+}
+
+# Break down the given file name into its components, and call analyze with
+# each of them. E.g., an argument of /usr/local/aliases/foo.list would call
+# analyze in turn with arguments:
+# * /usr/local/aliases/foo.list
+# * /usr/local/aliases
+# * /usr/local
+# * /usr
+
+break_down() {
+ for j in `echo $1 | \
+ awk '{
+ n = split($0, parts, "/");
+ for (i = n; i >= 2; i--){
+ string = "";
+ for (j = 2; j <= i; j++){
+ string = sprintf("%s/%s", string, parts[j]);
+ }
+ print string
+ }
+ }'` "/"
+ do
+ analyze $j $1
+ done
+}
+
+config=/etc/mail/sendmail.cf
+bogus_dirs=false
+
+afl1=`grep "^OA" $config | sed 's/^OA//' | sed 's/,/ /g' | sed 's/.*://'`
+afl2=`grep "^O AliasFile=" $config | sed 's/^O AliasFile=//' | \
+ sed 's/,/ /g' | sed 's/.*://'`
+
+# These should be OK themselves, but other packages may have screwed up the
+# permissions on /etc or /etc/mail . And best to check in case non-standard
+# alias paths are used.
+
+break_down $afl1 $afl2
+
+# Find all valid :include: files used in alias files configured in sendmail.cf
+
+for i in `sed 's/^[#].*$//' $afl1 $afl2 | \
+ grep :include: | \
+ sed 's/.*:include://' | \
+ sed 's/,.*$//'`
+do
+ break_down $i
+done
+
+# Check .forward files as well. If the argument "ALL" is given, do it for
+# everyone. If no argument to the script is given, just do it for the current
+# user. O/w, do it for all arguments.
+
+if [ $# -eq 0 ] ; then
+ arg=`who am i | awk '{print $1}'`
+elif [ $1 = "ALL" ] ; then
+ arg=""
+else
+ arg="$*"
+fi
+
+for i in `getent passwd $arg | nawk '{FS=":";print $6}'`
+do
+ if [ -f $i/.forward ] ; then
+ break_down $i/.forward
+ fi
+done
+
+$bogus_dirs || echo "No unsafe directories found."
diff --git a/usr/src/cmd/sendmail/cf/sh/makeinfo.sh b/usr/src/cmd/sendmail/cf/sh/makeinfo.sh
new file mode 100644
index 0000000000..0a1deead5c
--- /dev/null
+++ b/usr/src/cmd/sendmail/cf/sh/makeinfo.sh
@@ -0,0 +1,96 @@
+#!/bin/sh
+#
+# Copyright (c) 1983 Eric P. Allman
+# Copyright (c) 1988, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# Copyright (c) 1998
+# Sun Microsystems, Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# 3. All advertising materials mentioning features or use of this software
+# must display the following acknowledgement:
+# This product includes software developed by the University of
+# California, Berkeley and its contributors.
+# 4. Neither the name of the University nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# %W% (Berkeley+Sun) %G%
+# ident "%Z%%M% %I% %E% SMI"
+#
+
+if [ "x$1" = "x-q" ]
+then
+ quiet=1
+ shift
+else
+ quiet=0
+fi
+
+usewhoami=0
+usehostname=0
+for p in `echo $PATH | sed 's/:/ /g'`
+do
+ if [ "x$p" = "x" ]
+ then
+ p="."
+ fi
+ if [ -f $p/whoami ]
+ then
+ usewhoami=1
+ if [ $usehostname -ne 0 ]
+ then
+ break;
+ fi
+ fi
+ if [ -f $p/hostname ]
+ then
+ usehostname=1
+ if [ $usewhoami -ne 0 ]
+ then
+ break;
+ fi
+ fi
+done
+if [ $usewhoami -ne 0 ]
+then
+ user=`whoami`
+else
+ user=$LOGNAME
+fi
+
+if [ $usehostname -ne 0 ]
+then
+ host=`hostname`
+else
+ host=`uname -n`
+fi
+if [ $quiet -eq 1 ]
+then
+ echo '#####' built on `date`
+else
+ echo '#####' built by $user@$host on `date`
+ echo '#####' in `pwd` | sed 's/\/tmp_mnt//'
+ echo '#####' using $1 as configuration include directory | sed 's/\/tmp_mnt//'
+ echo "define(\`__HOST__', $host)dnl"
+fi
diff --git a/usr/src/cmd/sendmail/db/LICENSE b/usr/src/cmd/sendmail/db/LICENSE
new file mode 100644
index 0000000000..5ec662776b
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/LICENSE
@@ -0,0 +1,118 @@
+# ident "%Z%%M% %I% %E% SMI"
+
+/*-
+ * @(#)LICENSE 10.10 (Sleepycat) 1/12/99
+ */
+
+The following are the copyrights and redistribution conditions that apply to
+this copy of the Berkeley DB software. For a license to use, redistribute
+or sell Berkeley DB software under conditions other than those described here,
+or to purchase support for this software, please contact Sleepycat Software at
+one of the following addresses:
+
+ Sleepycat Software db@sleepycat.com
+ 394 E. Riding Dr. 510-526-3972
+ Carlisle, MA 01741 877-SLEEPYCAT (toll-free, USA only)
+ USA
+
+=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+/*
+ * Copyright (c) 1990, 1993, 1994, 1995, 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Redistributions in any form must be accompanied by information on
+ * how to obtain complete source code for the DB software and any
+ * accompanying software that uses the DB software. The source code
+ * must either be included in the distribution or be available for no
+ * more than the cost of distribution plus a nominal fee, and must be
+ * freely redistributable under reasonable conditions. For an
+ * executable file, complete source code means the source code for all
+ * modules it contains. It does not mean source code for modules or
+ * files that typically accompany the operating system on which the
+ * executable file runs, e.g., standard library modules or system
+ * header files.
+ *
+ * THIS SOFTWARE IS PROVIDED BY SLEEPYCAT SOFTWARE ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL SLEEPYCAT SOFTWARE BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+/*
+ * Copyright (c) 1990, 1993, 1994, 1995
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+/*
+ * Copyright (c) 1995, 1996
+ * The President and Fellows of Harvard University. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by Harvard University
+ * and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY HARVARD AND ITS CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL HARVARD OR ITS CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
diff --git a/usr/src/cmd/sendmail/db/Makefile b/usr/src/cmd/sendmail/db/Makefile
new file mode 100644
index 0000000000..ab70ef80a3
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/Makefile
@@ -0,0 +1,142 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License, Version 1.0 only
+# (the "License"). You may not use this file except in compliance
+# with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+#
+# Copyright 2004 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# ident "%Z%%M% %I% %E% SMI"
+#
+# cmd/sendmail/db/Makefile
+#
+
+include ../../Makefile.cmd
+
+srcdir= .
+
+CPPFLAGS = -I. -I$(srcdir)/include -D_REENTRANT $(CPPFLAGS.master)
+
+ARFLAGS=cq
+
+OBJS= bt_compare.o bt_conv.o bt_curadj.o bt_cursor.o bt_delete.o bt_open.o \
+ bt_page.o bt_put.o bt_rec.o bt_recno.o bt_rsearch.o bt_search.o \
+ bt_split.o bt_stat.o btree_auto.o db.o db_appinit.o db_am.o \
+ db_apprec.o db_auto.o db_byteorder.o db_conv.o db_dispatch.o db_dup.o \
+ db_err.o db_iface.o db_join.o db_log2.o db_overflow.o db_pr.o \
+ db_rec.o db_region.o db_ret.o db_salloc.o db_shash.o dbm.o hash.o \
+ hash_auto.o hash_conv.o hash_dup.o hash_func.o hash_page.o hash_rec.o \
+ hash_stat.o hsearch.o lock.o lock_conflict.o lock_deadlock.o \
+ lock_util.o lock_region.o log.o log_archive.o log_auto.o \
+ log_compare.o log_findckp.o log_get.o log_put.o log_rec.o \
+ log_register.o mp_bh.o mp_fget.o mp_fopen.o mp_fput.o mp_fset.o \
+ mp_open.o mp_pr.o mp_region.o mp_sync.o mutex.o os_abs.o os_alloc.o \
+ os_config.o os_dir.o os_fid.o os_fsync.o os_map.o os_oflags.o \
+ os_open.o os_rpath.o os_rw.o os_seek.o os_sleep.o os_spin.o os_stat.o \
+ os_tmpdir.o os_unlink.o txn.o txn_auto.o txn_rec.o xa.o xa_db.o \
+ xa_map.o strsep.o
+
+SRCS= $(OBJS:%.o=$(srcdir)/*/%.c)
+
+libdb= libdb.a
+
+.KEEP_STATE:
+all: $(libdb)
+
+.PARALLEL: $(OBJS)
+
+$(libdb): db.h $(OBJS)
+ $(RM) $@
+ $(AR) $(ARFLAGS) $@ $(OBJS)
+
+clean:
+ $(RM) $(OBJS) $(libdb)
+
+depend obj:
+
+install: all
+
+lint:
+ $(LINT.c) $(SRCS) $(LDLIBS)
+
+# DB files.
+db%.o: $(srcdir)/db/db%.c
+ $(COMPILE.c) $<
+ $(POST_PROCESS_O)
+
+# Btree source files.
+bt%.o: $(srcdir)/btree/bt%.c
+ $(COMPILE.c) $<
+ $(POST_PROCESS_O)
+
+# Hash source files.
+hash%.o: $(srcdir)/hash/hash%.c
+ $(COMPILE.c) $<
+ $(POST_PROCESS_O)
+
+# Lock source files.
+lock%.o: $(srcdir)/lock/lock%.c
+ $(COMPILE.c) $<
+ $(POST_PROCESS_O)
+
+# Log source files.
+log%.o: $(srcdir)/log/log%.c
+ $(COMPILE.c) $<
+ $(POST_PROCESS_O)
+
+# Mpool source files.
+mp_%.o: $(srcdir)/mp/mp_%.c
+ $(COMPILE.c) $<
+ $(POST_PROCESS_O)
+
+# Mutex source files.
+mutex%.o: $(srcdir)/mutex/mutex%.c
+ $(COMPILE.c) $<
+ $(POST_PROCESS_O)
+
+# Transaction source files.
+txn%.o: $(srcdir)/txn/txn%.c
+ $(COMPILE.c) $<
+ $(POST_PROCESS_O)
+
+# Transaction manager source files.
+xa%.o: $(srcdir)/xa/xa%.c
+ $(COMPILE.c) $<
+ $(POST_PROCESS_O)
+
+# Historic interfaces.
+hsearch%.o: $(srcdir)/hsearch/hsearch%.c
+ $(COMPILE.c) $<
+ $(POST_PROCESS_O)
+dbm%.o: $(srcdir)/dbm/dbm%.c
+ $(COMPILE.c) $<
+ $(POST_PROCESS_O)
+
+# OS specific source files.
+os_%.o: $(srcdir)/os/os_%.c
+ $(COMPILE.c) $<
+ $(POST_PROCESS_O)
+
+# Replacement source files.
+strsep%.o: $(srcdir)/clib/strsep%.c
+ $(COMPILE.c) $<
+ $(POST_PROCESS_O)
+
+include ../../Makefile.targ
diff --git a/usr/src/cmd/sendmail/db/btree/bt_compare.c b/usr/src/cmd/sendmail/db/btree/bt_compare.c
new file mode 100644
index 0000000000..c60f920612
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/btree/bt_compare.c
@@ -0,0 +1,195 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+/*
+ * Copyright (c) 1990, 1993, 1994, 1995, 1996
+ * Keith Bostic. All rights reserved.
+ */
+/*
+ * Copyright (c) 1990, 1993, 1994, 1995
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Mike Olson.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)bt_compare.c 10.14 (Sleepycat) 10/9/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <string.h>
+#endif
+
+#include "db_int.h"
+#include "db_page.h"
+#include "btree.h"
+
+/*
+ * __bam_cmp --
+ * Compare a key to a given record.
+ *
+ * PUBLIC: int __bam_cmp __P((DB *, const DBT *,
+ * PUBLIC: PAGE *, u_int32_t, int (*)(const DBT *, const DBT *)));
+ */
+int
+__bam_cmp(dbp, dbt, h, indx, func)
+ DB *dbp;
+ const DBT *dbt;
+ PAGE *h;
+ u_int32_t indx;
+ int (*func)__P((const DBT *, const DBT *));
+{
+ BINTERNAL *bi;
+ BKEYDATA *bk;
+ BOVERFLOW *bo;
+ DBT pg_dbt;
+ int ret;
+
+ /*
+ * Returns:
+ * < 0 if dbt is < page record
+ * = 0 if dbt is = page record
+ * > 0 if dbt is > page record
+ *
+ * !!!
+ * We do not clear the pg_dbt DBT even though it's likely to contain
+ * random bits. That should be okay, because the app's comparison
+ * routine had better not be looking at fields other than data/size.
+ * We don't clear it because we go through this path a lot and it's
+ * expensive.
+ */
+ if (TYPE(h) == P_LBTREE || TYPE(h) == P_DUPLICATE) {
+ bk = GET_BKEYDATA(h, indx);
+ if (B_TYPE(bk->type) == B_OVERFLOW)
+ bo = (BOVERFLOW *)bk;
+ else {
+ pg_dbt.data = bk->data;
+ pg_dbt.size = bk->len;
+ return (func(dbt, &pg_dbt));
+ }
+ } else {
+ /*
+ * The following code guarantees that the left-most key on an
+ * internal page at any level of the btree is less than any
+ * user specified key. This saves us from having to update the
+ * leftmost key on an internal page when the user inserts a new
+ * key in the tree smaller than anything we've seen before.
+ */
+ if (indx == 0 && h->prev_pgno == PGNO_INVALID)
+ return (1);
+
+ bi = GET_BINTERNAL(h, indx);
+ if (B_TYPE(bi->type) == B_OVERFLOW)
+ bo = (BOVERFLOW *)(bi->data);
+ else {
+ pg_dbt.data = bi->data;
+ pg_dbt.size = bi->len;
+ return (func(dbt, &pg_dbt));
+ }
+ }
+
+ /*
+ * Overflow.
+ *
+ * XXX
+ * We ignore __db_moff() errors, because we have no way of returning
+ * them.
+ */
+ (void) __db_moff(dbp,
+ dbt, bo->pgno, bo->tlen, func == __bam_defcmp ? NULL : func, &ret);
+ return (ret);
+}
+
+/*
+ * __bam_defcmp --
+ * Default comparison routine.
+ *
+ * PUBLIC: int __bam_defcmp __P((const DBT *, const DBT *));
+ */
+int
+__bam_defcmp(a, b)
+ const DBT *a, *b;
+{
+ size_t len;
+ u_int8_t *p1, *p2;
+
+ /*
+ * Returns:
+ * < 0 if a is < b
+ * = 0 if a is = b
+ * > 0 if a is > b
+ *
+ * XXX
+ * If a size_t doesn't fit into a long, or if the difference between
+ * any two characters doesn't fit into an int, this routine can lose.
+ * What we need is a signed integral type that's guaranteed to be at
+ * least as large as a size_t, and there is no such thing.
+ */
+ len = a->size > b->size ? b->size : a->size;
+ for (p1 = a->data, p2 = b->data; len--; ++p1, ++p2)
+ if (*p1 != *p2)
+ return ((long)*p1 - (long)*p2);
+ return ((long)a->size - (long)b->size);
+}
+
+/*
+ * __bam_defpfx --
+ * Default prefix routine.
+ *
+ * PUBLIC: size_t __bam_defpfx __P((const DBT *, const DBT *));
+ */
+size_t
+__bam_defpfx(a, b)
+ const DBT *a, *b;
+{
+ size_t cnt, len;
+ u_int8_t *p1, *p2;
+
+ cnt = 1;
+ len = a->size > b->size ? b->size : a->size;
+ for (p1 = a->data, p2 = b->data; len--; ++p1, ++p2, ++cnt)
+ if (*p1 != *p2)
+ return (cnt);
+
+ /*
+ * We know that a->size must be <= b->size, or they wouldn't be
+ * in this order.
+ */
+ return (a->size < b->size ? a->size + 1 : a->size);
+}
diff --git a/usr/src/cmd/sendmail/db/btree/bt_conv.c b/usr/src/cmd/sendmail/db/btree/bt_conv.c
new file mode 100644
index 0000000000..a3069082ae
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/btree/bt_conv.c
@@ -0,0 +1,94 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)bt_conv.c 10.7 (Sleepycat) 9/20/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+#endif
+
+#include "db_int.h"
+#include "db_page.h"
+#include "db_swap.h"
+#include "btree.h"
+
+/*
+ * __bam_pgin --
+ * Convert host-specific page layout from the host-independent format
+ * stored on disk.
+ *
+ * PUBLIC: int __bam_pgin __P((db_pgno_t, void *, DBT *));
+ */
+int
+__bam_pgin(pg, pp, cookie)
+ db_pgno_t pg;
+ void *pp;
+ DBT *cookie;
+{
+ DB_PGINFO *pginfo;
+
+ pginfo = (DB_PGINFO *)cookie->data;
+ if (!pginfo->needswap)
+ return (0);
+ return (pg == PGNO_METADATA ?
+ __bam_mswap(pp) : __db_pgin(pg, pginfo->db_pagesize, pp));
+}
+
+/*
+ * __bam_pgout --
+ * Convert host-specific page layout to the host-independent format
+ * stored on disk.
+ *
+ * PUBLIC: int __bam_pgout __P((db_pgno_t, void *, DBT *));
+ */
+int
+__bam_pgout(pg, pp, cookie)
+ db_pgno_t pg;
+ void *pp;
+ DBT *cookie;
+{
+ DB_PGINFO *pginfo;
+
+ pginfo = (DB_PGINFO *)cookie->data;
+ if (!pginfo->needswap)
+ return (0);
+ return (pg == PGNO_METADATA ?
+ __bam_mswap(pp) : __db_pgout(pg, pginfo->db_pagesize, pp));
+}
+
+/*
+ * __bam_mswap --
+ * Swap the bytes on the btree metadata page.
+ *
+ * PUBLIC: int __bam_mswap __P((PAGE *));
+ */
+int
+__bam_mswap(pg)
+ PAGE *pg;
+{
+ u_int8_t *p;
+
+ p = (u_int8_t *)pg;
+
+ /* Swap the meta-data information. */
+ SWAP32(p); /* lsn.file */
+ SWAP32(p); /* lsn.offset */
+ SWAP32(p); /* pgno */
+ SWAP32(p); /* magic */
+ SWAP32(p); /* version */
+ SWAP32(p); /* pagesize */
+ SWAP32(p); /* maxkey */
+ SWAP32(p); /* minkey */
+ SWAP32(p); /* free */
+ SWAP32(p); /* flags */
+
+ return (0);
+}
diff --git a/usr/src/cmd/sendmail/db/btree/bt_curadj.c b/usr/src/cmd/sendmail/db/btree/bt_curadj.c
new file mode 100644
index 0000000000..0e8fb00aef
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/btree/bt_curadj.c
@@ -0,0 +1,272 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)bt_curadj.c 10.69 (Sleepycat) 12/2/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <stdlib.h>
+#endif
+
+#include "db_int.h"
+#include "db_page.h"
+#include "btree.h"
+
+#ifdef DEBUG
+/*
+ * __bam_cprint --
+ * Display the current cursor list.
+ *
+ * PUBLIC: int __bam_cprint __P((DB *));
+ */
+int
+__bam_cprint(dbp)
+ DB *dbp;
+{
+ CURSOR *cp;
+ DBC *dbc;
+
+ DB_THREAD_LOCK(dbp);
+ for (dbc = TAILQ_FIRST(&dbp->active_queue);
+ dbc != NULL; dbc = TAILQ_NEXT(dbc, links)) {
+ cp = (CURSOR *)dbc->internal;
+ fprintf(stderr,
+ "%#0x->%#0x: page: %lu index: %lu dpage %lu dindex: %lu recno: %lu",
+ (u_int)dbc, (u_int)cp, (u_long)cp->pgno, (u_long)cp->indx,
+ (u_long)cp->dpgno, (u_long)cp->dindx, (u_long)cp->recno);
+ if (F_ISSET(cp, C_DELETED))
+ fprintf(stderr, " (deleted)");
+ fprintf(stderr, "\n");
+ }
+ DB_THREAD_UNLOCK(dbp);
+
+ return (0);
+}
+#endif /* DEBUG */
+
+/*
+ * __bam_ca_delete --
+ * Update the cursors when items are deleted and when already deleted
+ * items are overwritten. Return the number of relevant cursors found.
+ *
+ * PUBLIC: int __bam_ca_delete __P((DB *, db_pgno_t, u_int32_t, int));
+ */
+int
+__bam_ca_delete(dbp, pgno, indx, delete)
+ DB *dbp;
+ db_pgno_t pgno;
+ u_int32_t indx;
+ int delete;
+{
+ DBC *dbc;
+ CURSOR *cp;
+ int count; /* !!!: Has to contain max number of cursors. */
+
+ /* Recno is responsible for its own adjustments. */
+ if (dbp->type == DB_RECNO)
+ return (0);
+
+ /*
+ * Adjust the cursors. We don't have to review the cursors for any
+ * thread of control other than the current one, because we have the
+ * page write locked at this point, and any other thread of control
+ * had better be using a different locker ID, meaning only cursors in
+ * our thread of control can be on the page.
+ *
+ * It's possible for multiple cursors within the thread to have write
+ * locks on the same page, but, cursors within a thread must be single
+ * threaded, so all we're locking here is the cursor linked list.
+ */
+ DB_THREAD_LOCK(dbp);
+ for (count = 0, dbc = TAILQ_FIRST(&dbp->active_queue);
+ dbc != NULL; dbc = TAILQ_NEXT(dbc, links)) {
+ cp = (CURSOR *)dbc->internal;
+
+ if ((cp->pgno == pgno && cp->indx == indx) ||
+ (cp->dpgno == pgno && cp->dindx == indx)) {
+ if (delete)
+ F_SET(cp, C_DELETED);
+ else
+ F_CLR(cp, C_DELETED);
+ ++count;
+ }
+ }
+ DB_THREAD_UNLOCK(dbp);
+
+ return (count);
+}
+
+/*
+ * __bam_ca_di --
+ * Adjust the cursors during a delete or insert.
+ *
+ * PUBLIC: void __bam_ca_di __P((DB *, db_pgno_t, u_int32_t, int));
+ */
+void
+__bam_ca_di(dbp, pgno, indx, adjust)
+ DB *dbp;
+ db_pgno_t pgno;
+ u_int32_t indx;
+ int adjust;
+{
+ CURSOR *cp;
+ DBC *dbc;
+
+ /* Recno is responsible for its own adjustments. */
+ if (dbp->type == DB_RECNO)
+ return;
+
+ /*
+ * Adjust the cursors. See the comment in __bam_ca_delete().
+ */
+ DB_THREAD_LOCK(dbp);
+ for (dbc = TAILQ_FIRST(&dbp->active_queue);
+ dbc != NULL; dbc = TAILQ_NEXT(dbc, links)) {
+ cp = (CURSOR *)dbc->internal;
+ if (cp->pgno == pgno && cp->indx >= indx)
+ cp->indx += adjust;
+ if (cp->dpgno == pgno && cp->dindx >= indx)
+ cp->dindx += adjust;
+ }
+ DB_THREAD_UNLOCK(dbp);
+}
+
+/*
+ * __bam_ca_dup --
+ * Adjust the cursors when moving items from a leaf page to a duplicates
+ * page.
+ *
+ * PUBLIC: void __bam_ca_dup __P((DB *,
+ * PUBLIC: db_pgno_t, u_int32_t, u_int32_t, db_pgno_t, u_int32_t));
+ */
+void
+__bam_ca_dup(dbp, fpgno, first, fi, tpgno, ti)
+ DB *dbp;
+ db_pgno_t fpgno, tpgno;
+ u_int32_t first, fi, ti;
+{
+ CURSOR *cp;
+ DBC *dbc;
+
+ /* Recno is responsible for its own adjustments. */
+ if (dbp->type == DB_RECNO)
+ return;
+
+ /*
+ * Adjust the cursors. See the comment in __bam_ca_delete().
+ */
+ DB_THREAD_LOCK(dbp);
+ for (dbc = TAILQ_FIRST(&dbp->active_queue);
+ dbc != NULL; dbc = TAILQ_NEXT(dbc, links)) {
+ cp = (CURSOR *)dbc->internal;
+ /*
+ * Ignore matching entries that have already been moved,
+ * we move from the same location on the leaf page more
+ * than once.
+ */
+ if (cp->dpgno == PGNO_INVALID &&
+ cp->pgno == fpgno && cp->indx == fi) {
+ cp->indx = first;
+ cp->dpgno = tpgno;
+ cp->dindx = ti;
+ }
+ }
+ DB_THREAD_UNLOCK(dbp);
+}
+
+/*
+ * __bam_ca_rsplit --
+ * Adjust the cursors when doing reverse splits.
+ *
+ * PUBLIC: void __bam_ca_rsplit __P((DB *, db_pgno_t, db_pgno_t));
+ */
+void
+__bam_ca_rsplit(dbp, fpgno, tpgno)
+ DB *dbp;
+ db_pgno_t fpgno, tpgno;
+{
+ CURSOR *cp;
+ DBC *dbc;
+
+ /* Recno is responsible for its own adjustments. */
+ if (dbp->type == DB_RECNO)
+ return;
+
+ /*
+ * Adjust the cursors. See the comment in __bam_ca_delete().
+ */
+ DB_THREAD_LOCK(dbp);
+ for (dbc = TAILQ_FIRST(&dbp->active_queue);
+ dbc != NULL; dbc = TAILQ_NEXT(dbc, links)) {
+ cp = (CURSOR *)dbc->internal;
+ if (cp->pgno == fpgno)
+ cp->pgno = tpgno;
+ }
+ DB_THREAD_UNLOCK(dbp);
+}
+
+/*
+ * __bam_ca_split --
+ * Adjust the cursors when splitting a page.
+ *
+ * PUBLIC: void __bam_ca_split __P((DB *,
+ * PUBLIC: db_pgno_t, db_pgno_t, db_pgno_t, u_int32_t, int));
+ */
+void
+__bam_ca_split(dbp, ppgno, lpgno, rpgno, split_indx, cleft)
+ DB *dbp;
+ db_pgno_t ppgno, lpgno, rpgno;
+ u_int32_t split_indx;
+ int cleft;
+{
+ DBC *dbc;
+ CURSOR *cp;
+
+ /* Recno is responsible for its own adjustments. */
+ if (dbp->type == DB_RECNO)
+ return;
+
+ /*
+ * Adjust the cursors. See the comment in __bam_ca_delete().
+ *
+ * If splitting the page that a cursor was on, the cursor has to be
+ * adjusted to point to the same record as before the split. Most
+ * of the time we don't adjust pointers to the left page, because
+ * we're going to copy its contents back over the original page. If
+ * the cursor is on the right page, it is decremented by the number of
+ * records split to the left page.
+ */
+ DB_THREAD_LOCK(dbp);
+ for (dbc = TAILQ_FIRST(&dbp->active_queue);
+ dbc != NULL; dbc = TAILQ_NEXT(dbc, links)) {
+ cp = (CURSOR *)dbc->internal;
+ if (cp->pgno == ppgno)
+ if (cp->indx < split_indx) {
+ if (cleft)
+ cp->pgno = lpgno;
+ } else {
+ cp->pgno = rpgno;
+ cp->indx -= split_indx;
+ }
+ if (cp->dpgno == ppgno)
+ if (cp->dindx < split_indx) {
+ if (cleft)
+ cp->dpgno = lpgno;
+ } else {
+ cp->dpgno = rpgno;
+ cp->dindx -= split_indx;
+ }
+ }
+ DB_THREAD_UNLOCK(dbp);
+}
diff --git a/usr/src/cmd/sendmail/db/btree/bt_cursor.c b/usr/src/cmd/sendmail/db/btree/bt_cursor.c
new file mode 100644
index 0000000000..10bc095c9d
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/btree/bt_cursor.c
@@ -0,0 +1,1913 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)bt_cursor.c 10.81 (Sleepycat) 12/16/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#endif
+
+#include "db_int.h"
+#include "db_page.h"
+#include "btree.h"
+#include "shqueue.h"
+#include "db_shash.h"
+#include "lock.h"
+#include "lock_ext.h"
+
+static int __bam_c_close __P((DBC *));
+static int __bam_c_del __P((DBC *, u_int32_t));
+static int __bam_c_destroy __P((DBC *));
+static int __bam_c_first __P((DBC *, CURSOR *));
+static int __bam_c_get __P((DBC *, DBT *, DBT *, u_int32_t));
+static int __bam_c_getstack __P((DBC *, CURSOR *));
+static int __bam_c_last __P((DBC *, CURSOR *));
+static int __bam_c_next __P((DBC *, CURSOR *, int));
+static int __bam_c_physdel __P((DBC *, CURSOR *, PAGE *));
+static int __bam_c_prev __P((DBC *, CURSOR *));
+static int __bam_c_put __P((DBC *, DBT *, DBT *, u_int32_t));
+static void __bam_c_reset __P((CURSOR *));
+static int __bam_c_rget __P((DBC *, DBT *, u_int32_t));
+static int __bam_c_search __P((DBC *, CURSOR *, const DBT *, u_int32_t, int *));
+static int __bam_dsearch __P((DBC *, CURSOR *, DBT *, u_int32_t *));
+
+/* Discard the current page/lock held by a cursor. */
+#undef DISCARD
+#define DISCARD(dbc, cp) { \
+ if ((cp)->page != NULL) { \
+ (void)memp_fput((dbc)->dbp->mpf, (cp)->page, 0); \
+ (cp)->page = NULL; \
+ } \
+ if ((cp)->lock != LOCK_INVALID) { \
+ (void)__BT_TLPUT((dbc), (cp)->lock); \
+ (cp)->lock = LOCK_INVALID; \
+ } \
+}
+
+/* If the cursor references a deleted record. */
+#undef IS_CUR_DELETED
+#define IS_CUR_DELETED(cp) \
+ (((cp)->dpgno == PGNO_INVALID && \
+ B_DISSET(GET_BKEYDATA((cp)->page, \
+ (cp)->indx + O_INDX)->type)) || \
+ ((cp)->dpgno != PGNO_INVALID && \
+ B_DISSET(GET_BKEYDATA((cp)->page, (cp)->dindx)->type)))
+
+/* If the cursor and index combination references a deleted record. */
+#undef IS_DELETED
+#define IS_DELETED(cp, indx) \
+ (((cp)->dpgno == PGNO_INVALID && \
+ B_DISSET(GET_BKEYDATA((cp)->page, (indx) + O_INDX)->type)) || \
+ ((cp)->dpgno != PGNO_INVALID && \
+ B_DISSET(GET_BKEYDATA((cp)->page, (indx))->type)))
+
+/*
+ * Test to see if two cursors could point to duplicates of the same key,
+ * whether on-page or off-page. The leaf page numbers must be the same
+ * in both cases. In the case of off-page duplicates, the key indices
+ * on the leaf page will be the same. In the case of on-page duplicates,
+ * the duplicate page number must not be set, and the key index offsets
+ * must be the same. For the last test, as the saved copy of the cursor
+ * will not have a valid page pointer, we use the cursor's.
+ */
+#undef POSSIBLE_DUPLICATE
+#define POSSIBLE_DUPLICATE(cursor, saved_copy) \
+ ((cursor)->pgno == (saved_copy).pgno && \
+ ((cursor)->indx == (saved_copy).indx || \
+ ((cursor)->dpgno == PGNO_INVALID && \
+ (saved_copy).dpgno == PGNO_INVALID && \
+ (cursor)->page->inp[(cursor)->indx] == \
+ (cursor)->page->inp[(saved_copy).indx])))
+
+/*
+ * __bam_c_reset --
+ * Initialize internal cursor structure.
+ */
+static void
+__bam_c_reset(cp)
+ CURSOR *cp;
+{
+ cp->sp = cp->csp = cp->stack;
+ cp->esp = cp->stack + sizeof(cp->stack) / sizeof(cp->stack[0]);
+ cp->page = NULL;
+ cp->pgno = PGNO_INVALID;
+ cp->indx = 0;
+ cp->dpgno = PGNO_INVALID;
+ cp->dindx = 0;
+ cp->lock = LOCK_INVALID;
+ cp->mode = DB_LOCK_NG;
+ cp->recno = RECNO_OOB;
+ cp->flags = 0;
+}
+
+/*
+ * __bam_c_init --
+ * Initialize the access private portion of a cursor
+ *
+ * PUBLIC: int __bam_c_init __P((DBC *));
+ */
+int
+__bam_c_init(dbc)
+ DBC *dbc;
+{
+ DB *dbp;
+ CURSOR *cp;
+ int ret;
+
+ if ((ret = __os_calloc(1, sizeof(CURSOR), &cp)) != 0)
+ return (ret);
+
+ dbp = dbc->dbp;
+ cp->dbc = dbc;
+
+ /*
+ * Logical record numbers are always the same size, and we don't want
+ * to have to check for space every time we return one. Allocate it
+ * in advance.
+ */
+ if (dbp->type == DB_RECNO || F_ISSET(dbp, DB_BT_RECNUM)) {
+ if ((ret = __os_malloc(sizeof(db_recno_t),
+ NULL, &dbc->rkey.data)) != 0) {
+ __os_free(cp, sizeof(CURSOR));
+ return (ret);
+ }
+ dbc->rkey.ulen = sizeof(db_recno_t);
+ }
+
+ /* Initialize methods. */
+ dbc->internal = cp;
+ if (dbp->type == DB_BTREE) {
+ dbc->c_am_close = __bam_c_close;
+ dbc->c_am_destroy = __bam_c_destroy;
+ dbc->c_del = __bam_c_del;
+ dbc->c_get = __bam_c_get;
+ dbc->c_put = __bam_c_put;
+ } else {
+ dbc->c_am_close = __bam_c_close;
+ dbc->c_am_destroy = __bam_c_destroy;
+ dbc->c_del = __ram_c_del;
+ dbc->c_get = __ram_c_get;
+ dbc->c_put = __ram_c_put;
+ }
+
+ /* Initialize dynamic information. */
+ __bam_c_reset(cp);
+
+ return (0);
+}
+
+/*
+ * __bam_c_close --
+ * Close down the cursor from a single use.
+ */
+static int
+__bam_c_close(dbc)
+ DBC *dbc;
+{
+ CURSOR *cp;
+ DB *dbp;
+ int ret;
+
+ dbp = dbc->dbp;
+ cp = dbc->internal;
+ ret = 0;
+
+ /*
+ * If a cursor deleted a btree key, perform the actual deletion.
+ * (Recno keys are either deleted immediately or never deleted.)
+ */
+ if (dbp->type == DB_BTREE && F_ISSET(cp, C_DELETED))
+ ret = __bam_c_physdel(dbc, cp, NULL);
+
+ /* Discard any locks not acquired inside of a transaction. */
+ if (cp->lock != LOCK_INVALID) {
+ (void)__BT_TLPUT(dbc, cp->lock);
+ cp->lock = LOCK_INVALID;
+ }
+
+ /* Sanity checks. */
+#ifdef DIAGNOSTIC
+ if (cp->csp != cp->stack)
+ __db_err(dbp->dbenv, "btree cursor close: stack not empty");
+#endif
+
+ /* Initialize dynamic information. */
+ __bam_c_reset(cp);
+
+ return (ret);
+}
+
+/*
+ * __bam_c_destroy --
+ * Close a single cursor -- internal version.
+ */
+static int
+__bam_c_destroy(dbc)
+ DBC *dbc;
+{
+ /* Discard the structures. */
+ __os_free(dbc->internal, sizeof(CURSOR));
+
+ return (0);
+}
+
+/*
+ * __bam_c_del --
+ * Delete using a cursor.
+ */
+static int
+__bam_c_del(dbc, flags)
+ DBC *dbc;
+ u_int32_t flags;
+{
+ CURSOR *cp;
+ DB *dbp;
+ DB_LOCK lock;
+ PAGE *h;
+ db_pgno_t pgno;
+ db_indx_t indx;
+ int ret;
+
+ dbp = dbc->dbp;
+ cp = dbc->internal;
+ h = NULL;
+
+ DB_PANIC_CHECK(dbp);
+
+ /* Check for invalid flags. */
+ if ((ret = __db_cdelchk(dbp, flags,
+ F_ISSET(dbp, DB_AM_RDONLY), cp->pgno != PGNO_INVALID)) != 0)
+ return (ret);
+
+ /*
+ * If we are running CDB, this had better be either a write
+ * cursor or an immediate writer.
+ */
+ if (F_ISSET(dbp, DB_AM_CDB))
+ if (!F_ISSET(dbc, DBC_RMW | DBC_WRITER))
+ return (EINVAL);
+
+ DEBUG_LWRITE(dbc, dbc->txn, "bam_c_del", NULL, NULL, flags);
+
+ /* If already deleted, return failure. */
+ if (F_ISSET(cp, C_DELETED))
+ return (DB_KEYEMPTY);
+
+ /*
+ * We don't physically delete the record until the cursor moves,
+ * so we have to have a long-lived write lock on the page instead
+ * of a long-lived read lock. Note, we have to have a read lock
+ * to even get here, so we simply discard it.
+ */
+ if (F_ISSET(dbp, DB_AM_LOCKING) && cp->mode != DB_LOCK_WRITE) {
+ if ((ret = __bam_lget(dbc,
+ 0, cp->pgno, DB_LOCK_WRITE, &lock)) != 0)
+ goto err;
+ (void)__BT_TLPUT(dbc, cp->lock);
+ cp->lock = lock;
+ cp->mode = DB_LOCK_WRITE;
+ }
+
+ /*
+ * Acquire the underlying page (which may be different from the above
+ * page because it may be a duplicate page), and set the on-page and
+ * in-cursor delete flags. We don't need to lock it as we've already
+ * write-locked the page leading to it.
+ */
+ if (cp->dpgno == PGNO_INVALID) {
+ pgno = cp->pgno;
+ indx = cp->indx;
+ } else {
+ pgno = cp->dpgno;
+ indx = cp->dindx;
+ }
+
+ if ((ret = memp_fget(dbp->mpf, &pgno, 0, &h)) != 0)
+ goto err;
+
+ /* Log the change. */
+ if (DB_LOGGING(dbc) &&
+ (ret = __bam_cdel_log(dbp->dbenv->lg_info, dbc->txn, &LSN(h),
+ 0, dbp->log_fileid, PGNO(h), &LSN(h), indx)) != 0) {
+ (void)memp_fput(dbp->mpf, h, 0);
+ goto err;
+ }
+
+ /*
+ * Set the intent-to-delete flag on the page and update all cursors. */
+ if (cp->dpgno == PGNO_INVALID)
+ B_DSET(GET_BKEYDATA(h, indx + O_INDX)->type);
+ else
+ B_DSET(GET_BKEYDATA(h, indx)->type);
+ (void)__bam_ca_delete(dbp, pgno, indx, 1);
+
+ ret = memp_fput(dbp->mpf, h, DB_MPOOL_DIRTY);
+ h = NULL;
+
+ /*
+ * If the tree has record numbers, we have to adjust the counts.
+ *
+ * !!!
+ * This test is right -- we don't yet support duplicates and record
+ * numbers in the same tree, so ignore duplicates if DB_BT_RECNUM
+ * set.
+ */
+ if (F_ISSET(dbp, DB_BT_RECNUM)) {
+ if ((ret = __bam_c_getstack(dbc, cp)) != 0)
+ goto err;
+ if ((ret = __bam_adjust(dbc, -1)) != 0)
+ goto err;
+ (void)__bam_stkrel(dbc, 0);
+ }
+
+err: if (h != NULL)
+ (void)memp_fput(dbp->mpf, h, 0);
+ return (ret);
+}
+
+/*
+ * __bam_c_get --
+ * Get using a cursor (btree).
+ */
+static int
+__bam_c_get(dbc, key, data, flags)
+ DBC *dbc;
+ DBT *key, *data;
+ u_int32_t flags;
+{
+ CURSOR *cp, copy, start;
+ DB *dbp;
+ PAGE *h;
+ int exact, ret, tmp_rmw;
+
+ dbp = dbc->dbp;
+ cp = dbc->internal;
+
+ DB_PANIC_CHECK(dbp);
+
+ /* Check for invalid flags. */
+ if ((ret = __db_cgetchk(dbp,
+ key, data, flags, cp->pgno != PGNO_INVALID)) != 0)
+ return (ret);
+
+ /* Clear OR'd in additional bits so we can check for flag equality. */
+ tmp_rmw = 0;
+ if (LF_ISSET(DB_RMW)) {
+ if (!F_ISSET(dbp, DB_AM_CDB)) {
+ tmp_rmw = 1;
+ F_SET(dbc, DBC_RMW);
+ }
+ LF_CLR(DB_RMW);
+ }
+
+ DEBUG_LREAD(dbc, dbc->txn, "bam_c_get",
+ flags == DB_SET || flags == DB_SET_RANGE ? key : NULL, NULL, flags);
+
+ /*
+ * Return a cursor's record number. It has nothing to do with the
+ * cursor get code except that it's been rammed into the interface.
+ */
+ if (flags == DB_GET_RECNO) {
+ ret = __bam_c_rget(dbc, data, flags);
+ if (tmp_rmw)
+ F_CLR(dbc, DBC_RMW);
+ return (ret);
+ }
+
+ /*
+ * Initialize the cursor for a new retrieval. Clear the cursor's
+ * page pointer, it was set before this operation, and no longer
+ * has any meaning.
+ */
+ cp->page = NULL;
+ copy = *cp;
+ cp->lock = LOCK_INVALID;
+
+ switch (flags) {
+ case DB_CURRENT:
+ /* It's not possible to return a deleted record. */
+ if (F_ISSET(cp, C_DELETED)) {
+ ret = DB_KEYEMPTY;
+ goto err;
+ }
+
+ /* Acquire the current page. */
+ if ((ret = __bam_lget(dbc,
+ 0, cp->pgno, DB_LOCK_READ, &cp->lock)) == 0)
+ ret = memp_fget(dbp->mpf,
+ cp->dpgno == PGNO_INVALID ? &cp->pgno : &cp->dpgno,
+ 0, &cp->page);
+ if (ret != 0)
+ goto err;
+ break;
+ case DB_NEXT_DUP:
+ if (cp->pgno == PGNO_INVALID) {
+ ret = EINVAL;
+ goto err;
+ }
+ if ((ret = __bam_c_next(dbc, cp, 1)) != 0)
+ goto err;
+
+ /* Make sure we didn't go past the end of the duplicates. */
+ if (!POSSIBLE_DUPLICATE(cp, copy)) {
+ ret = DB_NOTFOUND;
+ goto err;
+ }
+ break;
+ case DB_NEXT:
+ if (cp->pgno != PGNO_INVALID) {
+ if ((ret = __bam_c_next(dbc, cp, 1)) != 0)
+ goto err;
+ break;
+ }
+ /* FALLTHROUGH */
+ case DB_FIRST:
+ if ((ret = __bam_c_first(dbc, cp)) != 0)
+ goto err;
+ break;
+ case DB_PREV:
+ if (cp->pgno != PGNO_INVALID) {
+ if ((ret = __bam_c_prev(dbc, cp)) != 0)
+ goto err;
+ break;
+ }
+ /* FALLTHROUGH */
+ case DB_LAST:
+ if ((ret = __bam_c_last(dbc, cp)) != 0)
+ goto err;
+ break;
+ case DB_SET:
+ if ((ret = __bam_c_search(dbc, cp, key, flags, &exact)) != 0)
+ goto err;
+
+ /*
+ * We cannot currently be referencing a deleted record, but we
+ * may be referencing off-page duplicates.
+ *
+ * If we're referencing off-page duplicates, move off-page.
+ * If we moved off-page, move to the next non-deleted record.
+ * If we moved to the next non-deleted record, check to make
+ * sure we didn't switch records because our current record
+ * had no non-deleted data items.
+ */
+ start = *cp;
+ if ((ret = __bam_dup(dbc, cp, cp->indx, 0)) != 0)
+ goto err;
+ if (cp->dpgno != PGNO_INVALID && IS_CUR_DELETED(cp)) {
+ if ((ret = __bam_c_next(dbc, cp, 0)) != 0)
+ goto err;
+ if (!POSSIBLE_DUPLICATE(cp, start)) {
+ ret = DB_NOTFOUND;
+ goto err;
+ }
+ }
+ break;
+ case DB_SET_RECNO:
+ if ((ret = __bam_c_search(dbc, cp, key, flags, &exact)) != 0)
+ goto err;
+ break;
+ case DB_GET_BOTH:
+ if (F_ISSET(dbc, DBC_CONTINUE | DBC_KEYSET)) {
+ /* Acquire the current page. */
+ if ((ret = memp_fget(dbp->mpf,
+ cp->dpgno == PGNO_INVALID ? &cp->pgno : &cp->dpgno,
+ 0, &cp->page)) != 0)
+ goto err;
+
+ /* If DBC_CONTINUE, move to the next item. */
+ if (F_ISSET(dbc, DBC_CONTINUE) &&
+ (ret = __bam_c_next(dbc, cp, 1)) != 0)
+ goto err;
+ } else {
+ if ((ret =
+ __bam_c_search(dbc, cp, key, flags, &exact)) != 0)
+ goto err;
+
+ /*
+ * We may be referencing a duplicates page. Move to
+ * the first duplicate.
+ */
+ if ((ret = __bam_dup(dbc, cp, cp->indx, 0)) != 0)
+ goto err;
+ }
+
+ /* Search for a matching entry. */
+ if ((ret = __bam_dsearch(dbc, cp, data, NULL)) != 0)
+ goto err;
+
+ /* Ignore deleted entries. */
+ if (IS_CUR_DELETED(cp)) {
+ ret = DB_NOTFOUND;
+ goto err;
+ }
+ break;
+ case DB_SET_RANGE:
+ if ((ret = __bam_c_search(dbc, cp, key, flags, &exact)) != 0)
+ goto err;
+
+ /*
+ * As we didn't require an exact match, the search function
+ * may have returned an entry past the end of the page. If
+ * so, move to the next entry.
+ */
+ if (cp->indx == NUM_ENT(cp->page) &&
+ (ret = __bam_c_next(dbc, cp, 0)) != 0)
+ goto err;
+
+ /*
+ * We may be referencing off-page duplicates, if so, move
+ * off-page.
+ */
+ if ((ret = __bam_dup(dbc, cp, cp->indx, 0)) != 0)
+ goto err;
+
+ /*
+ * We may be referencing a deleted record, if so, move to
+ * the next non-deleted record.
+ */
+ if (IS_CUR_DELETED(cp) && (ret = __bam_c_next(dbc, cp, 0)) != 0)
+ goto err;
+ break;
+ }
+
+ /*
+ * Return the key if the user didn't give us one. If we've moved to
+ * a duplicate page, we may no longer have a pointer to the main page,
+ * so we have to go get it. We know that it's already read-locked,
+ * however, so we don't have to acquire a new lock.
+ */
+ if (flags != DB_SET) {
+ if (cp->dpgno != PGNO_INVALID) {
+ if ((ret = memp_fget(dbp->mpf, &cp->pgno, 0, &h)) != 0)
+ goto err;
+ } else
+ h = cp->page;
+ ret = __db_ret(dbp,
+ h, cp->indx, key, &dbc->rkey.data, &dbc->rkey.ulen);
+ if (cp->dpgno != PGNO_INVALID)
+ (void)memp_fput(dbp->mpf, h, 0);
+ if (ret)
+ goto err;
+ }
+
+ /* Return the data. */
+ if ((ret = __db_ret(dbp, cp->page,
+ cp->dpgno == PGNO_INVALID ? cp->indx + O_INDX : cp->dindx,
+ data, &dbc->rdata.data, &dbc->rdata.ulen)) != 0)
+ goto err;
+
+ /*
+ * If the previous cursor record has been deleted, physically delete
+ * the entry from the page. We clear the deleted flag before we call
+ * the underlying delete routine so that, if an error occurs, and we
+ * restore the cursor, the deleted flag is cleared. This is because,
+ * if we manage to physically modify the page, and then restore the
+ * cursor, we might try to repeat the page modification when closing
+ * the cursor.
+ */
+ if (F_ISSET(&copy, C_DELETED)) {
+ F_CLR(&copy, C_DELETED);
+ if ((ret = __bam_c_physdel(dbc, &copy, cp->page)) != 0)
+ goto err;
+ }
+ F_CLR(cp, C_DELETED);
+
+ /* Release the previous lock, if any; the current lock is retained. */
+ if (copy.lock != LOCK_INVALID)
+ (void)__BT_TLPUT(dbc, copy.lock);
+
+ /* Release the current page. */
+ if ((ret = memp_fput(dbp->mpf, cp->page, 0)) != 0)
+ goto err;
+
+ if (0) {
+err: if (cp->page != NULL)
+ (void)memp_fput(dbp->mpf, cp->page, 0);
+ if (cp->lock != LOCK_INVALID)
+ (void)__BT_TLPUT(dbc, cp->lock);
+ *cp = copy;
+ }
+
+ /* Release temporary lock upgrade. */
+ if (tmp_rmw)
+ F_CLR(dbc, DBC_RMW);
+
+ return (ret);
+}
+
+/*
+ * __bam_dsearch --
+ * Search for a matching data item (or the first data item that's
+ * equal to or greater than the one we're searching for).
+ */
+static int
+__bam_dsearch(dbc, cp, data, iflagp)
+ DBC *dbc;
+ CURSOR *cp;
+ DBT *data;
+ u_int32_t *iflagp;
+{
+ DB *dbp;
+ CURSOR copy, last;
+ int cmp, ret;
+
+ dbp = dbc->dbp;
+
+ /*
+ * If iflagp is non-NULL, we're doing an insert.
+ *
+ * If the duplicates are off-page, use the duplicate search routine.
+ */
+ if (cp->dpgno != PGNO_INVALID) {
+ if ((ret = __db_dsearch(dbc, iflagp != NULL,
+ data, cp->dpgno, &cp->dindx, &cp->page, &cmp)) != 0)
+ return (ret);
+ cp->dpgno = cp->page->pgno;
+
+ if (iflagp == NULL) {
+ if (cmp != 0)
+ return (DB_NOTFOUND);
+ return (0);
+ }
+ *iflagp = DB_BEFORE;
+ return (0);
+ }
+
+ /* Otherwise, do the search ourselves. */
+ copy = *cp;
+ for (;;) {
+ /* Save the last interesting cursor position. */
+ last = *cp;
+
+ /* See if the data item matches the one we're looking for. */
+ if ((cmp = __bam_cmp(dbp, data, cp->page, cp->indx + O_INDX,
+ dbp->dup_compare == NULL ?
+ __bam_defcmp : dbp->dup_compare)) == 0) {
+ if (iflagp != NULL)
+ *iflagp = DB_AFTER;
+ return (0);
+ }
+
+ /*
+ * If duplicate entries are sorted, we're done if we find a
+ * page entry that sorts greater than the application item.
+ * If doing an insert, return success, otherwise DB_NOTFOUND.
+ */
+ if (dbp->dup_compare != NULL && cmp < 0) {
+ if (iflagp == NULL)
+ return (DB_NOTFOUND);
+ *iflagp = DB_BEFORE;
+ return (0);
+ }
+
+ /*
+ * Move to the next item. If we reach the end of the page and
+ * we're doing an insert, set the cursor to the last item and
+ * set the referenced memory location so callers know to insert
+ * after the item, instead of before it. If not inserting, we
+ * return DB_NOTFOUND.
+ */
+ if ((cp->indx += P_INDX) >= NUM_ENT(cp->page)) {
+ if (iflagp == NULL)
+ return (DB_NOTFOUND);
+ goto use_last;
+ }
+
+ /*
+ * Make sure we didn't go past the end of the duplicates. The
+ * error conditions are the same as above.
+ */
+ if (!POSSIBLE_DUPLICATE(cp, copy)) {
+ if (iflagp == NULL)
+ return (DB_NOTFOUND);
+use_last: *cp = last;
+ *iflagp = DB_AFTER;
+ return (0);
+ }
+ }
+ /* NOTREACHED */
+}
+
+/*
+ * __bam_c_rget --
+ * Return the record number for a cursor.
+ */
+static int
+__bam_c_rget(dbc, data, flags)
+ DBC *dbc;
+ DBT *data;
+ u_int32_t flags;
+{
+ CURSOR *cp;
+ DB *dbp;
+ DBT dbt;
+ db_recno_t recno;
+ int exact, ret;
+
+ COMPQUIET(flags, 0);
+ dbp = dbc->dbp;
+ cp = dbc->internal;
+
+ /* Get the page with the current item on it. */
+ if ((ret = memp_fget(dbp->mpf, &cp->pgno, 0, &cp->page)) != 0)
+ return (ret);
+
+ /* Get a copy of the key. */
+ memset(&dbt, 0, sizeof(DBT));
+ dbt.flags = DB_DBT_MALLOC | DB_DBT_INTERNAL;
+ if ((ret = __db_ret(dbp, cp->page, cp->indx, &dbt, NULL, NULL)) != 0)
+ goto err;
+
+ exact = 1;
+ if ((ret = __bam_search(dbc, &dbt,
+ F_ISSET(dbc, DBC_RMW) ? S_FIND_WR : S_FIND,
+ 1, &recno, &exact)) != 0)
+ goto err;
+
+ ret = __db_retcopy(data, &recno, sizeof(recno),
+ &dbc->rdata.data, &dbc->rdata.ulen, dbp->db_malloc);
+
+ /* Release the stack. */
+ __bam_stkrel(dbc, 0);
+
+err: (void)memp_fput(dbp->mpf, cp->page, 0);
+ __os_free(dbt.data, dbt.size);
+ return (ret);
+}
+
+/*
+ * __bam_c_put --
+ * Put using a cursor.
+ */
+static int
+__bam_c_put(dbc, key, data, flags)
+ DBC *dbc;
+ DBT *key, *data;
+ u_int32_t flags;
+{
+ CURSOR *cp, copy;
+ DB *dbp;
+ DBT dbt;
+ db_indx_t indx;
+ db_pgno_t pgno;
+ u_int32_t iiflags, iiop;
+ int exact, needkey, ret, stack;
+ void *arg;
+
+ dbp = dbc->dbp;
+ cp = dbc->internal;
+
+ DB_PANIC_CHECK(dbp);
+
+ DEBUG_LWRITE(dbc, dbc->txn, "bam_c_put",
+ flags == DB_KEYFIRST || flags == DB_KEYLAST ? key : NULL,
+ data, flags);
+
+ if ((ret = __db_cputchk(dbp, key, data, flags,
+ F_ISSET(dbp, DB_AM_RDONLY), cp->pgno != PGNO_INVALID)) != 0)
+ return (ret);
+
+ /*
+ * If we are running CDB, this had better be either a write
+ * cursor or an immediate writer. If it's a regular writer,
+ * that means we have an IWRITE lock and we need to upgrade
+ * it to a write lock.
+ */
+ if (F_ISSET(dbp, DB_AM_CDB)) {
+ if (!F_ISSET(dbc, DBC_RMW | DBC_WRITER))
+ return (EINVAL);
+
+ if (F_ISSET(dbc, DBC_RMW) &&
+ (ret = lock_get(dbp->dbenv->lk_info, dbc->locker,
+ DB_LOCK_UPGRADE, &dbc->lock_dbt, DB_LOCK_WRITE,
+ &dbc->mylock)) != 0)
+ return (EAGAIN);
+ }
+
+ if (0) {
+split: /*
+ * To split, we need a valid key for the page. Since it's a
+ * cursor, we have to build one.
+ *
+ * Acquire a copy of a key from the page.
+ */
+ if (needkey) {
+ memset(&dbt, 0, sizeof(DBT));
+ if ((ret = __db_ret(dbp, cp->page, indx,
+ &dbt, &dbc->rkey.data, &dbc->rkey.ulen)) != 0)
+ goto err;
+ arg = &dbt;
+ } else
+ arg = key;
+
+ /*
+ * Discard any locks and pinned pages (the locks are discarded
+ * even if we're running with transactions, as they lock pages
+ * that we're sorry we ever acquired). If stack is set and the
+ * cursor entries are valid, they point to the same entries as
+ * the stack, don't free them twice.
+ */
+ if (stack) {
+ (void)__bam_stkrel(dbc, 1);
+ stack = 0;
+ } else
+ DISCARD(dbc, cp);
+
+ /*
+ * Restore the cursor to its original value. This is necessary
+ * for two reasons. First, we are about to copy it in case of
+ * error, again. Second, we adjust cursors during the split,
+ * and we have to ensure this cursor is adjusted appropriately,
+ * along with all the other cursors.
+ */
+ *cp = copy;
+
+ if ((ret = __bam_split(dbc, arg)) != 0)
+ goto err;
+ }
+
+ /*
+ * Initialize the cursor for a new retrieval. Clear the cursor's
+ * page pointer, it was set before this operation, and no longer
+ * has any meaning.
+ */
+ cp->page = NULL;
+ copy = *cp;
+ cp->lock = LOCK_INVALID;
+
+ iiflags = needkey = ret = stack = 0;
+ switch (flags) {
+ case DB_AFTER:
+ case DB_BEFORE:
+ case DB_CURRENT:
+ needkey = 1;
+ if (cp->dpgno == PGNO_INVALID) {
+ pgno = cp->pgno;
+ indx = cp->indx;
+ } else {
+ pgno = cp->dpgno;
+ indx = cp->dindx;
+ }
+
+ /*
+ * !!!
+ * This test is right -- we don't yet support duplicates and
+ * record numbers in the same tree, so ignore duplicates if
+ * DB_BT_RECNUM set.
+ */
+ if (F_ISSET(dbp, DB_BT_RECNUM) &&
+ (flags != DB_CURRENT || F_ISSET(cp, C_DELETED))) {
+ /* Acquire a complete stack. */
+ if ((ret = __bam_c_getstack(dbc, cp)) != 0)
+ goto err;
+ cp->page = cp->csp->page;
+
+ stack = 1;
+ iiflags = BI_DOINCR;
+ } else {
+ /* Acquire the current page. */
+ if ((ret = __bam_lget(dbc,
+ 0, cp->pgno, DB_LOCK_WRITE, &cp->lock)) == 0)
+ ret = memp_fget(dbp->mpf, &pgno, 0, &cp->page);
+ if (ret != 0)
+ goto err;
+
+ iiflags = 0;
+ }
+
+ /*
+ * If the user has specified a duplicate comparison function,
+ * we return an error if DB_CURRENT was specified and the
+ * replacement data doesn't compare equal to the current data.
+ * This stops apps from screwing up the duplicate sort order.
+ */
+ if (flags == DB_CURRENT && dbp->dup_compare != NULL)
+ if (__bam_cmp(dbp, data,
+ cp->page, indx, dbp->dup_compare) != 0) {
+ ret = EINVAL;
+ goto err;
+ }
+
+ iiop = flags;
+ break;
+ case DB_KEYFIRST:
+ case DB_KEYLAST:
+ /*
+ * If we have a duplicate comparison function, we position to
+ * the first of any on-page duplicates, and use __bam_dsearch
+ * to search for the right slot. Otherwise, we position to
+ * the first/last of any on-page duplicates based on the flag
+ * value.
+ */
+ if ((ret = __bam_c_search(dbc, cp, key,
+ flags == DB_KEYFIRST || dbp->dup_compare != NULL ?
+ DB_KEYFIRST : DB_KEYLAST, &exact)) != 0)
+ goto err;
+ stack = 1;
+
+ /*
+ * If an exact match:
+ * If duplicates aren't supported, replace the current
+ * item. (When implementing the DB->put function, our
+ * caller has already checked the DB_NOOVERWRITE flag.)
+ *
+ * If there's a duplicate comparison function, find the
+ * correct slot for this duplicate item.
+ *
+ * If there's no duplicate comparison function, set the
+ * insert flag based on the argument flags.
+ *
+ * If there's no match, the search function returned the
+ * smallest slot greater than the key, use it.
+ */
+ if (exact) {
+ if (F_ISSET(dbp, DB_AM_DUP)) {
+ /*
+ * If at off-page duplicate page, move to the
+ * first or last entry -- if a comparison
+ * function was specified, start searching at
+ * the first entry. Otherwise, move based on
+ * the DB_KEYFIRST/DB_KEYLAST flags.
+ */
+ if ((ret = __bam_dup(dbc, cp, cp->indx,
+ dbp->dup_compare == NULL &&
+ flags != DB_KEYFIRST)) != 0)
+ goto err;
+
+ /*
+ * If there's a comparison function, search for
+ * the correct slot. Otherwise, set the insert
+ * flag based on the argment flag.
+ */
+ if (dbp->dup_compare == NULL)
+ iiop = flags == DB_KEYFIRST ?
+ DB_BEFORE : DB_AFTER;
+ else
+ if ((ret = __bam_dsearch(dbc,
+ cp, data, &iiop)) != 0)
+ goto err;
+ } else
+ iiop = DB_CURRENT;
+ iiflags = 0;
+ } else {
+ iiop = DB_BEFORE;
+ iiflags = BI_NEWKEY;
+ }
+
+ if (cp->dpgno == PGNO_INVALID) {
+ pgno = cp->pgno;
+ indx = cp->indx;
+ } else {
+ pgno = cp->dpgno;
+ indx = cp->dindx;
+ }
+ break;
+ }
+
+ ret = __bam_iitem(dbc, &cp->page, &indx, key, data, iiop, iiflags);
+
+ if (ret == DB_NEEDSPLIT)
+ goto split;
+ if (ret != 0)
+ goto err;
+
+ /*
+ * Reset any cursors referencing this item that might have the item
+ * marked for deletion.
+ */
+ if (iiop == DB_CURRENT) {
+ (void)__bam_ca_delete(dbp, pgno, indx, 0);
+
+ /*
+ * It's also possible that we are the cursor that had the
+ * item marked for deletion, in which case we want to make
+ * sure that we don't delete it because we had the delete
+ * flag set already.
+ */
+ if (cp->pgno == copy.pgno && cp->indx == copy.indx &&
+ cp->dpgno == copy.dpgno && cp->dindx == copy.dindx)
+ F_CLR(&copy, C_DELETED);
+ }
+
+ /*
+ * Update the cursor to point to the new entry. The new entry was
+ * stored on the current page, because we split pages until it was
+ * possible.
+ */
+ if (cp->dpgno == PGNO_INVALID)
+ cp->indx = indx;
+ else
+ cp->dindx = indx;
+
+ /*
+ * If the previous cursor record has been deleted, physically delete
+ * the entry from the page. We clear the deleted flag before we call
+ * the underlying delete routine so that, if an error occurs, and we
+ * restore the cursor, the deleted flag is cleared. This is because,
+ * if we manage to physically modify the page, and then restore the
+ * cursor, we might try to repeat the page modification when closing
+ * the cursor.
+ */
+ if (F_ISSET(&copy, C_DELETED)) {
+ F_CLR(&copy, C_DELETED);
+ if ((ret = __bam_c_physdel(dbc, &copy, cp->page)) != 0)
+ goto err;
+ }
+ F_CLR(cp, C_DELETED);
+
+ /* Release the previous lock, if any; the current lock is retained. */
+ if (copy.lock != LOCK_INVALID)
+ (void)__BT_TLPUT(dbc, copy.lock);
+
+ /*
+ * Discard any pages pinned in the tree and their locks, except for
+ * the leaf page, for which we only discard the pin, not the lock.
+ *
+ * Note, the leaf page participated in the stack we acquired, and so
+ * we have to adjust the stack as necessary. If there was only a
+ * single page on the stack, we don't have to free further stack pages.
+ */
+ if (stack && BT_STK_POP(cp) != NULL)
+ (void)__bam_stkrel(dbc, 0);
+
+ /* Release the current page. */
+ if ((ret = memp_fput(dbp->mpf, cp->page, 0)) != 0)
+ goto err;
+
+ if (0) {
+err: /* Discard any pinned pages. */
+ if (stack)
+ (void)__bam_stkrel(dbc, 0);
+ else
+ DISCARD(dbc, cp);
+ *cp = copy;
+ }
+
+ if (F_ISSET(dbp, DB_AM_CDB) && F_ISSET(dbc, DBC_RMW))
+ (void)__lock_downgrade(dbp->dbenv->lk_info, dbc->mylock,
+ DB_LOCK_IWRITE, 0);
+
+ return (ret);
+}
+
+/*
+ * __bam_c_first --
+ * Return the first record.
+ */
+static int
+__bam_c_first(dbc, cp)
+ DBC *dbc;
+ CURSOR *cp;
+{
+ DB *dbp;
+ db_pgno_t pgno;
+ int ret;
+
+ dbp = dbc->dbp;
+
+ /* Walk down the left-hand side of the tree. */
+ for (pgno = PGNO_ROOT;;) {
+ if ((ret =
+ __bam_lget(dbc, 0, pgno, DB_LOCK_READ, &cp->lock)) != 0)
+ return (ret);
+ if ((ret = memp_fget(dbp->mpf, &pgno, 0, &cp->page)) != 0)
+ return (ret);
+
+ /* If we find a leaf page, we're done. */
+ if (ISLEAF(cp->page))
+ break;
+
+ pgno = GET_BINTERNAL(cp->page, 0)->pgno;
+ DISCARD(dbc, cp);
+ }
+
+ cp->pgno = cp->page->pgno;
+ cp->indx = 0;
+ cp->dpgno = PGNO_INVALID;
+
+ /* Check for duplicates. */
+ if ((ret = __bam_dup(dbc, cp, cp->indx, 0)) != 0)
+ return (ret);
+
+ /* If on an empty page or a deleted record, move to the next one. */
+ if (NUM_ENT(cp->page) == 0 || IS_CUR_DELETED(cp))
+ if ((ret = __bam_c_next(dbc, cp, 0)) != 0)
+ return (ret);
+
+ return (0);
+}
+
+/*
+ * __bam_c_last --
+ * Return the last record.
+ */
+static int
+__bam_c_last(dbc, cp)
+ DBC *dbc;
+ CURSOR *cp;
+{
+ DB *dbp;
+ db_pgno_t pgno;
+ int ret;
+
+ dbp = dbc->dbp;
+
+ /* Walk down the right-hand side of the tree. */
+ for (pgno = PGNO_ROOT;;) {
+ if ((ret =
+ __bam_lget(dbc, 0, pgno, DB_LOCK_READ, &cp->lock)) != 0)
+ return (ret);
+ if ((ret = memp_fget(dbp->mpf, &pgno, 0, &cp->page)) != 0)
+ return (ret);
+
+ /* If we find a leaf page, we're done. */
+ if (ISLEAF(cp->page))
+ break;
+
+ pgno =
+ GET_BINTERNAL(cp->page, NUM_ENT(cp->page) - O_INDX)->pgno;
+ DISCARD(dbc, cp);
+ }
+
+ cp->pgno = cp->page->pgno;
+ cp->indx = NUM_ENT(cp->page) == 0 ? 0 : NUM_ENT(cp->page) - P_INDX;
+ cp->dpgno = PGNO_INVALID;
+
+ /* Check for duplicates. */
+ if ((ret = __bam_dup(dbc, cp, cp->indx, 1)) != 0)
+ return (ret);
+
+ /* If on an empty page or a deleted record, move to the next one. */
+ if (NUM_ENT(cp->page) == 0 || IS_CUR_DELETED(cp))
+ if ((ret = __bam_c_prev(dbc, cp)) != 0)
+ return (ret);
+
+ return (0);
+}
+
+/*
+ * __bam_c_next --
+ * Move to the next record.
+ */
+static int
+__bam_c_next(dbc, cp, initial_move)
+ DBC *dbc;
+ CURSOR *cp;
+ int initial_move;
+{
+ DB *dbp;
+ db_indx_t adjust, indx;
+ db_pgno_t pgno;
+ int ret;
+
+ dbp = dbc->dbp;
+
+ /*
+ * We're either moving through a page of duplicates or a btree leaf
+ * page.
+ */
+ if (cp->dpgno == PGNO_INVALID) {
+ adjust = dbp->type == DB_BTREE ? P_INDX : O_INDX;
+ pgno = cp->pgno;
+ indx = cp->indx;
+ } else {
+ adjust = O_INDX;
+ pgno = cp->dpgno;
+ indx = cp->dindx;
+ }
+ if (cp->page == NULL) {
+ if ((ret =
+ __bam_lget(dbc, 0, pgno, DB_LOCK_READ, &cp->lock)) != 0)
+ return (ret);
+ if ((ret = memp_fget(dbp->mpf, &pgno, 0, &cp->page)) != 0)
+ return (ret);
+ }
+
+ /*
+ * If at the end of the page, move to a subsequent page.
+ *
+ * !!!
+ * Check for >= NUM_ENT. If we're here as the result of a search that
+ * landed us on NUM_ENT, we'll increment indx before we test.
+ *
+ * !!!
+ * This code handles empty pages and pages with only deleted entries.
+ */
+ if (initial_move)
+ indx += adjust;
+ for (;;) {
+ if (indx >= NUM_ENT(cp->page)) {
+ /*
+ * If we're in a btree leaf page, we've reached the end
+ * of the tree. If we've reached the end of a page of
+ * duplicates, continue from the btree leaf page where
+ * we found this page of duplicates.
+ */
+ pgno = cp->page->next_pgno;
+ if (pgno == PGNO_INVALID) {
+ /* If in a btree leaf page, it's EOF. */
+ if (cp->dpgno == PGNO_INVALID)
+ return (DB_NOTFOUND);
+
+ /* Continue from the last btree leaf page. */
+ cp->dpgno = PGNO_INVALID;
+
+ adjust = P_INDX;
+ pgno = cp->pgno;
+ indx = cp->indx + P_INDX;
+ } else
+ indx = 0;
+
+ DISCARD(dbc, cp);
+ if ((ret = __bam_lget(dbc,
+ 0, pgno, DB_LOCK_READ, &cp->lock)) != 0)
+ return (ret);
+ if ((ret =
+ memp_fget(dbp->mpf, &pgno, 0, &cp->page)) != 0)
+ return (ret);
+ continue;
+ }
+
+ /* Ignore deleted records. */
+ if (IS_DELETED(cp, indx)) {
+ indx += adjust;
+ continue;
+ }
+
+ /*
+ * If we're not in a duplicates page, check to see if we've
+ * found a page of duplicates, in which case we move to the
+ * first entry.
+ */
+ if (cp->dpgno == PGNO_INVALID) {
+ cp->pgno = cp->page->pgno;
+ cp->indx = indx;
+
+ if ((ret = __bam_dup(dbc, cp, indx, 0)) != 0)
+ return (ret);
+ if (cp->dpgno != PGNO_INVALID) {
+ indx = cp->dindx;
+ adjust = O_INDX;
+ continue;
+ }
+ } else {
+ cp->dpgno = cp->page->pgno;
+ cp->dindx = indx;
+ }
+ break;
+ }
+ return (0);
+}
+
+/*
+ * __bam_c_prev --
+ * Move to the previous record.
+ */
+static int
+__bam_c_prev(dbc, cp)
+ DBC *dbc;
+ CURSOR *cp;
+{
+ DB *dbp;
+ db_indx_t indx, adjust;
+ db_pgno_t pgno;
+ int ret, set_indx;
+
+ dbp = dbc->dbp;
+
+ /*
+ * We're either moving through a page of duplicates or a btree leaf
+ * page.
+ */
+ if (cp->dpgno == PGNO_INVALID) {
+ adjust = dbp->type == DB_BTREE ? P_INDX : O_INDX;
+ pgno = cp->pgno;
+ indx = cp->indx;
+ } else {
+ adjust = O_INDX;
+ pgno = cp->dpgno;
+ indx = cp->dindx;
+ }
+ if (cp->page == NULL) {
+ if ((ret =
+ __bam_lget(dbc, 0, pgno, DB_LOCK_READ, &cp->lock)) != 0)
+ return (ret);
+ if ((ret = memp_fget(dbp->mpf, &pgno, 0, &cp->page)) != 0)
+ return (ret);
+ }
+
+ /*
+ * If at the beginning of the page, move to any previous one.
+ *
+ * !!!
+ * This code handles empty pages and pages with only deleted entries.
+ */
+ for (;;) {
+ if (indx == 0) {
+ /*
+ * If we're in a btree leaf page, we've reached the
+ * beginning of the tree. If we've reached the first
+ * of a page of duplicates, continue from the btree
+ * leaf page where we found this page of duplicates.
+ */
+ pgno = cp->page->prev_pgno;
+ if (pgno == PGNO_INVALID) {
+ /* If in a btree leaf page, it's SOF. */
+ if (cp->dpgno == PGNO_INVALID)
+ return (DB_NOTFOUND);
+
+ /* Continue from the last btree leaf page. */
+ cp->dpgno = PGNO_INVALID;
+
+ adjust = P_INDX;
+ pgno = cp->pgno;
+ indx = cp->indx;
+ set_indx = 0;
+ } else
+ set_indx = 1;
+
+ DISCARD(dbc, cp);
+ if ((ret = __bam_lget(dbc,
+ 0, pgno, DB_LOCK_READ, &cp->lock)) != 0)
+ return (ret);
+ if ((ret =
+ memp_fget(dbp->mpf, &pgno, 0, &cp->page)) != 0)
+ return (ret);
+
+ if (set_indx)
+ indx = NUM_ENT(cp->page);
+ if (indx == 0)
+ continue;
+ }
+
+ /* Ignore deleted records. */
+ indx -= adjust;
+ if (IS_DELETED(cp, indx))
+ continue;
+
+ /*
+ * If we're not in a duplicates page, check to see if we've
+ * found a page of duplicates, in which case we move to the
+ * last entry.
+ */
+ if (cp->dpgno == PGNO_INVALID) {
+ cp->pgno = cp->page->pgno;
+ cp->indx = indx;
+
+ if ((ret = __bam_dup(dbc, cp, indx, 1)) != 0)
+ return (ret);
+ if (cp->dpgno != PGNO_INVALID) {
+ indx = cp->dindx + O_INDX;
+ adjust = O_INDX;
+ continue;
+ }
+ } else {
+ cp->dpgno = cp->page->pgno;
+ cp->dindx = indx;
+ }
+ break;
+ }
+ return (0);
+}
+
+/*
+ * __bam_c_search --
+ * Move to a specified record.
+ */
+static int
+__bam_c_search(dbc, cp, key, flags, exactp)
+ DBC *dbc;
+ CURSOR *cp;
+ const DBT *key;
+ u_int32_t flags;
+ int *exactp;
+{
+ BTREE *t;
+ DB *dbp;
+ DB_LOCK lock;
+ PAGE *h;
+ db_recno_t recno;
+ db_indx_t indx;
+ u_int32_t sflags;
+ int cmp, needexact, ret;
+
+ dbp = dbc->dbp;
+ t = dbp->internal;
+
+ /* Find an entry in the database. */
+ switch (flags) {
+ case DB_SET_RECNO:
+ if ((ret = __ram_getno(dbc, key, &recno, 0)) != 0)
+ return (ret);
+ sflags = F_ISSET(dbc, DBC_RMW) ? S_FIND_WR : S_FIND;
+ needexact = *exactp = 1;
+ ret = __bam_rsearch(dbc, &recno, sflags, 1, exactp);
+ break;
+ case DB_SET:
+ case DB_GET_BOTH:
+ sflags = F_ISSET(dbc, DBC_RMW) ? S_FIND_WR : S_FIND;
+ needexact = *exactp = 1;
+ goto search;
+ case DB_SET_RANGE:
+ sflags = F_ISSET(dbc, DBC_RMW) ? S_FIND_WR : S_FIND;
+ needexact = *exactp = 0;
+ goto search;
+ case DB_KEYFIRST:
+ sflags = S_KEYFIRST;
+ goto fast_search;
+ case DB_KEYLAST:
+ sflags = S_KEYLAST;
+fast_search: needexact = *exactp = 0;
+ /*
+ * If the application has a history of inserting into the first
+ * or last pages of the database, we check those pages first to
+ * avoid doing a full search.
+ *
+ * Record numbers can't be fast-tracked, the entire tree has to
+ * be locked.
+ */
+ h = NULL;
+ lock = LOCK_INVALID;
+ if (F_ISSET(dbp, DB_BT_RECNUM))
+ goto search;
+
+ /* Check if the application has a history of sorted input. */
+ if (t->bt_lpgno == PGNO_INVALID)
+ goto search;
+
+ /*
+ * Lock and retrieve the page on which we did the last insert.
+ * It's okay if it doesn't exist, or if it's not the page type
+ * we expected, it just means that the world changed.
+ */
+ if (__bam_lget(dbc, 0, t->bt_lpgno, DB_LOCK_WRITE, &lock))
+ goto fast_miss;
+ if (memp_fget(dbp->mpf, &t->bt_lpgno, 0, &h))
+ goto fast_miss;
+ if (TYPE(h) != P_LBTREE)
+ goto fast_miss;
+ if (NUM_ENT(h) == 0)
+ goto fast_miss;
+
+ /*
+ * What we do here is test to see if we're at the beginning or
+ * end of the tree and if the new item sorts before/after the
+ * first/last page entry. We don't try and catch inserts into
+ * the middle of the tree (although we could, as long as there
+ * were two keys on the page and we saved both the index and
+ * the page number of the last insert).
+ */
+ if (h->next_pgno == PGNO_INVALID) {
+ indx = NUM_ENT(h) - P_INDX;
+ if ((cmp =
+ __bam_cmp(dbp, key, h, indx, t->bt_compare)) < 0)
+ goto try_begin;
+ if (cmp > 0) {
+ indx += P_INDX;
+ goto fast_hit;
+ }
+
+ /*
+ * Found a duplicate. If doing DB_KEYLAST, we're at
+ * the correct position, otherwise, move to the first
+ * of the duplicates.
+ */
+ if (flags == DB_KEYLAST)
+ goto fast_hit;
+ for (;
+ indx > 0 && h->inp[indx - P_INDX] == h->inp[indx];
+ indx -= P_INDX)
+ ;
+ goto fast_hit;
+ }
+try_begin: if (h->prev_pgno == PGNO_INVALID) {
+ indx = 0;
+ if ((cmp =
+ __bam_cmp(dbp, key, h, indx, t->bt_compare)) > 0)
+ goto fast_miss;
+ if (cmp < 0)
+ goto fast_hit;
+ /*
+ * Found a duplicate. If doing DB_KEYFIRST, we're at
+ * the correct position, otherwise, move to the last
+ * of the duplicates.
+ */
+ if (flags == DB_KEYFIRST)
+ goto fast_hit;
+ for (;
+ indx < (db_indx_t)(NUM_ENT(h) - P_INDX) &&
+ h->inp[indx] == h->inp[indx + P_INDX];
+ indx += P_INDX)
+ ;
+ goto fast_hit;
+ }
+ goto fast_miss;
+
+fast_hit: /* Set the exact match flag, we may have found a duplicate. */
+ *exactp = cmp == 0;
+
+ /* Enter the entry in the stack. */
+ BT_STK_CLR(cp);
+ BT_STK_ENTER(cp, h, indx, lock, ret);
+ break;
+
+fast_miss: if (h != NULL)
+ (void)memp_fput(dbp->mpf, h, 0);
+ if (lock != LOCK_INVALID)
+ (void)__BT_LPUT(dbc, lock);
+
+search: ret = __bam_search(dbc, key, sflags, 1, NULL, exactp);
+ break;
+ default: /* XXX: Impossible. */
+ abort();
+ /* NOTREACHED */
+ }
+ if (ret != 0)
+ return (ret);
+
+ /*
+ * Initialize the cursor to reference it. This has to be done
+ * before we return (even with DB_NOTFOUND) because we have to
+ * free the page(s) we locked in __bam_search.
+ */
+ cp->page = cp->csp->page;
+ cp->pgno = cp->csp->page->pgno;
+ cp->indx = cp->csp->indx;
+ cp->lock = cp->csp->lock;
+ cp->dpgno = PGNO_INVALID;
+
+ /*
+ * If we inserted a key into the first or last slot of the tree,
+ * remember where it was so we can do it more quickly next time.
+ */
+ if (flags == DB_KEYFIRST || flags == DB_KEYLAST)
+ t->bt_lpgno =
+ ((cp->page->next_pgno == PGNO_INVALID &&
+ cp->indx >= NUM_ENT(cp->page)) ||
+ (cp->page->prev_pgno == PGNO_INVALID && cp->indx == 0)) ?
+ cp->pgno : PGNO_INVALID;
+
+ /* If we need an exact match and didn't find one, we're done. */
+ if (needexact && *exactp == 0)
+ return (DB_NOTFOUND);
+
+ return (0);
+}
+
+/*
+ * __bam_dup --
+ * Check for an off-page duplicates entry, and if found, move to the
+ * first or last entry.
+ *
+ * PUBLIC: int __bam_dup __P((DBC *, CURSOR *, u_int32_t, int));
+ */
+int
+__bam_dup(dbc, cp, indx, last_dup)
+ DBC *dbc;
+ CURSOR *cp;
+ u_int32_t indx;
+ int last_dup;
+{
+ BOVERFLOW *bo;
+ DB *dbp;
+ db_pgno_t pgno;
+ int ret;
+
+ dbp = dbc->dbp;
+
+ /*
+ * Check for an overflow entry. If we find one, move to the
+ * duplicates page, and optionally move to the last record on
+ * that page.
+ *
+ * !!!
+ * We don't lock duplicates pages, we've already got the correct
+ * lock on the main page.
+ */
+ bo = GET_BOVERFLOW(cp->page, indx + O_INDX);
+ if (B_TYPE(bo->type) != B_DUPLICATE)
+ return (0);
+
+ pgno = bo->pgno;
+ if ((ret = memp_fput(dbp->mpf, cp->page, 0)) != 0)
+ return (ret);
+ cp->page = NULL;
+ if (last_dup) {
+ if ((ret = __db_dend(dbc, pgno, &cp->page)) != 0)
+ return (ret);
+ indx = NUM_ENT(cp->page) - O_INDX;
+ } else {
+ if ((ret = memp_fget(dbp->mpf, &pgno, 0, &cp->page)) != 0)
+ return (ret);
+ indx = 0;
+ }
+
+ /* Update the cursor's duplicate information. */
+ cp->dpgno = cp->page->pgno;
+ cp->dindx = indx;
+
+ return (0);
+}
+
+/*
+ * __bam_c_physdel --
+ * Actually do the cursor deletion.
+ */
+static int
+__bam_c_physdel(dbc, cp, h)
+ DBC *dbc;
+ CURSOR *cp;
+ PAGE *h;
+{
+ enum { DELETE_ITEM, DELETE_PAGE, NOTHING_FURTHER } cmd;
+ BOVERFLOW bo;
+ DB *dbp;
+ DBT dbt;
+ DB_LOCK lock;
+ db_indx_t indx;
+ db_pgno_t pgno, next_pgno, prev_pgno;
+ int delete_page, local_page, ret;
+
+ dbp = dbc->dbp;
+
+ delete_page = ret = 0;
+
+ /* Figure out what we're deleting. */
+ if (cp->dpgno == PGNO_INVALID) {
+ pgno = cp->pgno;
+ indx = cp->indx;
+ } else {
+ pgno = cp->dpgno;
+ indx = cp->dindx;
+ }
+
+ /*
+ * If the item is referenced by another cursor, set that cursor's
+ * delete flag and leave it up to it to do the delete.
+ *
+ * !!!
+ * This test for > 0 is a tricky. There are two ways that we can
+ * be called here. Either we are closing the cursor or we've moved
+ * off the page with the deleted entry. In the first case, we've
+ * already removed the cursor from the active queue, so we won't see
+ * it in __bam_ca_delete. In the second case, it will be on a different
+ * item, so we won't bother with it in __bam_ca_delete.
+ */
+ if (__bam_ca_delete(dbp, pgno, indx, 1) > 0)
+ return (0);
+
+ /*
+ * If this is concurrent DB, upgrade the lock if necessary.
+ */
+ if (F_ISSET(dbp, DB_AM_CDB) && F_ISSET(dbc, DBC_RMW) &&
+ (ret = lock_get(dbp->dbenv->lk_info,
+ dbc->locker, DB_LOCK_UPGRADE, &dbc->lock_dbt, DB_LOCK_WRITE,
+ &dbc->mylock)) != 0)
+ return (EAGAIN);
+
+ /*
+ * If we don't already have the page locked, get it and delete the
+ * items.
+ */
+ if ((h == NULL || h->pgno != pgno)) {
+ if ((ret = __bam_lget(dbc, 0, pgno, DB_LOCK_WRITE, &lock)) != 0)
+ return (ret);
+ if ((ret = memp_fget(dbp->mpf, &pgno, 0, &h)) != 0)
+ return (ret);
+ local_page = 1;
+ } else
+ local_page = 0;
+
+ /*
+ * If we're deleting a duplicate entry and there are other duplicate
+ * entries remaining, call the common code to do the work and fix up
+ * the parent page as necessary. Otherwise, do a normal btree delete.
+ *
+ * There are 5 possible cases:
+ *
+ * 1. It's not a duplicate item: do a normal btree delete.
+ * 2. It's a duplicate item:
+ * 2a: We delete an item from a page of duplicates, but there are
+ * more items on the page.
+ * 2b: We delete the last item from a page of duplicates, deleting
+ * the last duplicate.
+ * 2c: We delete the last item from a page of duplicates, but there
+ * is a previous page of duplicates.
+ * 2d: We delete the last item from a page of duplicates, but there
+ * is a following page of duplicates.
+ *
+ * In the case of:
+ *
+ * 1: There's nothing further to do.
+ * 2a: There's nothing further to do.
+ * 2b: Do the normal btree delete instead of a duplicate delete, as
+ * that deletes both the duplicate chain and the parent page's
+ * entry.
+ * 2c: There's nothing further to do.
+ * 2d: Delete the duplicate, and update the parent page's entry.
+ */
+ if (TYPE(h) == P_DUPLICATE) {
+ pgno = PGNO(h);
+ prev_pgno = PREV_PGNO(h);
+ next_pgno = NEXT_PGNO(h);
+
+ if (NUM_ENT(h) == 1 &&
+ prev_pgno == PGNO_INVALID && next_pgno == PGNO_INVALID)
+ cmd = DELETE_PAGE;
+ else {
+ cmd = DELETE_ITEM;
+
+ /* Delete the duplicate. */
+ if ((ret = __db_drem(dbc, &h, indx, __bam_free)) != 0)
+ goto err;
+
+ /*
+ * 2a: h != NULL, h->pgno == pgno
+ * 2b: We don't reach this clause, as the above test
+ * was true.
+ * 2c: h == NULL, prev_pgno != PGNO_INVALID
+ * 2d: h != NULL, next_pgno != PGNO_INVALID
+ *
+ * Test for 2a and 2c: if we didn't empty the current
+ * page or there was a previous page of duplicates, we
+ * don't need to touch the parent page.
+ */
+ if ((h != NULL && pgno == h->pgno) ||
+ prev_pgno != PGNO_INVALID)
+ cmd = NOTHING_FURTHER;
+ }
+
+ /*
+ * Release any page we're holding and its lock.
+ *
+ * !!!
+ * If there is no subsequent page in the duplicate chain, then
+ * __db_drem will have put page "h" and set it to NULL.
+ */
+ if (local_page) {
+ if (h != NULL)
+ (void)memp_fput(dbp->mpf, h, 0);
+ (void)__BT_TLPUT(dbc, lock);
+ local_page = 0;
+ }
+
+ if (cmd == NOTHING_FURTHER)
+ goto done;
+
+ /* Acquire the parent page and switch the index to its entry. */
+ if ((ret =
+ __bam_lget(dbc, 0, cp->pgno, DB_LOCK_WRITE, &lock)) != 0)
+ goto err;
+ if ((ret = memp_fget(dbp->mpf, &cp->pgno, 0, &h)) != 0) {
+ (void)__BT_TLPUT(dbc, lock);
+ goto err;
+ }
+ local_page = 1;
+ indx = cp->indx;
+
+ if (cmd == DELETE_PAGE)
+ goto btd;
+
+ /*
+ * Copy, delete, update, add-back the parent page's data entry.
+ *
+ * XXX
+ * This may be a performance/logging problem. We should add a
+ * log message which simply logs/updates a random set of bytes
+ * on a page, and use it instead of doing a delete/add pair.
+ */
+ indx += O_INDX;
+ bo = *GET_BOVERFLOW(h, indx);
+ (void)__db_ditem(dbc, h, indx, BOVERFLOW_SIZE);
+ bo.pgno = next_pgno;
+ memset(&dbt, 0, sizeof(dbt));
+ dbt.data = &bo;
+ dbt.size = BOVERFLOW_SIZE;
+ (void)__db_pitem(dbc, h, indx, BOVERFLOW_SIZE, &dbt, NULL);
+ (void)memp_fset(dbp->mpf, h, DB_MPOOL_DIRTY);
+ goto done;
+ }
+
+btd: /*
+ * If the page is going to be emptied, delete it. To delete a leaf
+ * page we need a copy of a key from the page. We use the 0th page
+ * index since it's the last key that the page held.
+ *
+ * We malloc the page information instead of using the return key/data
+ * memory because we've already set them -- the reason we've already
+ * set them is because we're (potentially) about to do a reverse split,
+ * which would make our saved page information useless.
+ *
+ * !!!
+ * The following operations to delete a page might deadlock. I think
+ * that's OK. The problem is if we're deleting an item because we're
+ * closing cursors because we've already deadlocked and want to call
+ * txn_abort(). If we fail due to deadlock, we leave a locked empty
+ * page in the tree, which won't be empty long because we're going to
+ * undo the delete.
+ */
+ if (NUM_ENT(h) == 2 && h->pgno != PGNO_ROOT) {
+ memset(&dbt, 0, sizeof(DBT));
+ dbt.flags = DB_DBT_MALLOC | DB_DBT_INTERNAL;
+ if ((ret = __db_ret(dbp, h, 0, &dbt, NULL, NULL)) != 0)
+ goto err;
+ delete_page = 1;
+ }
+
+ /*
+ * Do a normal btree delete.
+ *
+ * !!!
+ * Delete the key item first, otherwise the duplicate checks in
+ * __bam_ditem() won't work!
+ */
+ if ((ret = __bam_ditem(dbc, h, indx)) != 0)
+ goto err;
+ if ((ret = __bam_ditem(dbc, h, indx)) != 0)
+ goto err;
+
+ /* Discard any remaining locks/pages. */
+ if (local_page) {
+ (void)memp_fput(dbp->mpf, h, 0);
+ (void)__BT_TLPUT(dbc, lock);
+ local_page = 0;
+ }
+
+ /* Delete the page if it was emptied. */
+ if (delete_page)
+ ret = __bam_dpage(dbc, &dbt);
+
+err:
+done: if (delete_page)
+ __os_free(dbt.data, dbt.size);
+
+ if (local_page) {
+ /*
+ * It's possible for h to be NULL, as __db_drem may have
+ * been relinking pages by the time that it deadlocked.
+ */
+ if (h != NULL)
+ (void)memp_fput(dbp->mpf, h, 0);
+ (void)__BT_TLPUT(dbc, lock);
+ }
+
+ if (F_ISSET(dbp, DB_AM_CDB) && F_ISSET(dbc, DBC_RMW))
+ (void)__lock_downgrade(dbp->dbenv->lk_info, dbc->mylock,
+ DB_LOCK_IWRITE, 0);
+
+ return (ret);
+}
+
+/*
+ * __bam_c_getstack --
+ * Acquire a full stack for a cursor.
+ */
+static int
+__bam_c_getstack(dbc, cp)
+ DBC *dbc;
+ CURSOR *cp;
+{
+ DB *dbp;
+ DBT dbt;
+ PAGE *h;
+ db_pgno_t pgno;
+ int exact, ret;
+
+ dbp = dbc->dbp;
+ h = NULL;
+ memset(&dbt, 0, sizeof(DBT));
+ ret = 0;
+
+ /* Get the page with the current item on it. */
+ pgno = cp->pgno;
+ if ((ret = memp_fget(dbp->mpf, &pgno, 0, &h)) != 0)
+ return (ret);
+
+ /* Get a copy of a key from the page. */
+ dbt.flags = DB_DBT_MALLOC | DB_DBT_INTERNAL;
+ if ((ret = __db_ret(dbp, h, 0, &dbt, NULL, NULL)) != 0)
+ goto err;
+
+ /* Get a write-locked stack for that page. */
+ exact = 0;
+ ret = __bam_search(dbc, &dbt, S_KEYFIRST, 1, NULL, &exact);
+
+ /* We no longer need the key or the page. */
+err: if (h != NULL)
+ (void)memp_fput(dbp->mpf, h, 0);
+ if (dbt.data != NULL)
+ __os_free(dbt.data, dbt.size);
+ return (ret);
+}
diff --git a/usr/src/cmd/sendmail/db/btree/bt_delete.c b/usr/src/cmd/sendmail/db/btree/bt_delete.c
new file mode 100644
index 0000000000..d623bd8a6f
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/btree/bt_delete.c
@@ -0,0 +1,589 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+/*
+ * Copyright (c) 1990, 1993, 1994, 1995, 1996
+ * Keith Bostic. All rights reserved.
+ */
+/*
+ * Copyright (c) 1990, 1993, 1994, 1995
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Mike Olson.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)bt_delete.c 10.43 (Sleepycat) 12/7/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <string.h>
+#endif
+
+#include "db_int.h"
+#include "db_page.h"
+#include "btree.h"
+
+/*
+ * __bam_delete --
+ * Delete the items referenced by a key.
+ *
+ * PUBLIC: int __bam_delete __P((DB *, DB_TXN *, DBT *, u_int32_t));
+ */
+int
+__bam_delete(dbp, txn, key, flags)
+ DB *dbp;
+ DB_TXN *txn;
+ DBT *key;
+ u_int32_t flags;
+{
+ DBC *dbc;
+ DBT data;
+ u_int32_t f_init, f_next;
+ int ret, t_ret;
+
+ DB_PANIC_CHECK(dbp);
+
+ /* Check for invalid flags. */
+ if ((ret =
+ __db_delchk(dbp, key, flags, F_ISSET(dbp, DB_AM_RDONLY))) != 0)
+ return (ret);
+
+ /* Allocate a cursor. */
+ if ((ret = dbp->cursor(dbp, txn, &dbc, DB_WRITELOCK)) != 0)
+ return (ret);
+
+ DEBUG_LWRITE(dbc, txn, "bam_delete", key, NULL, flags);
+
+ /*
+ * Walk a cursor through the key/data pairs, deleting as we go. Set
+ * the DB_DBT_USERMEM flag, as this might be a threaded application
+ * and the flags checking will catch us. We don't actually want the
+ * keys or data, so request a partial of length 0.
+ */
+ memset(&data, 0, sizeof(data));
+ F_SET(&data, DB_DBT_USERMEM | DB_DBT_PARTIAL);
+
+ /* If locking, set read-modify-write flag. */
+ f_init = DB_SET;
+ f_next = DB_NEXT_DUP;
+ if (dbp->dbenv != NULL && dbp->dbenv->lk_info != NULL) {
+ f_init |= DB_RMW;
+ f_next |= DB_RMW;
+ }
+
+ /* Walk through the set of key/data pairs, deleting as we go. */
+ if ((ret = dbc->c_get(dbc, key, &data, f_init)) != 0)
+ goto err;
+ for (;;) {
+ if ((ret = dbc->c_del(dbc, 0)) != 0)
+ goto err;
+ if ((ret = dbc->c_get(dbc, key, &data, f_next)) != 0) {
+ if (ret == DB_NOTFOUND) {
+ ret = 0;
+ break;
+ }
+ goto err;
+ }
+ }
+
+err: /* Discard the cursor. */
+ if ((t_ret = dbc->c_close(dbc)) != 0 &&
+ (ret == 0 || ret == DB_NOTFOUND))
+ ret = t_ret;
+
+ return (ret);
+}
+
+/*
+ * __bam_ditem --
+ * Delete one or more entries from a page.
+ *
+ * PUBLIC: int __bam_ditem __P((DBC *, PAGE *, u_int32_t));
+ */
+int
+__bam_ditem(dbc, h, indx)
+ DBC *dbc;
+ PAGE *h;
+ u_int32_t indx;
+{
+ BINTERNAL *bi;
+ BKEYDATA *bk;
+ BOVERFLOW *bo;
+ DB *dbp;
+ u_int32_t nbytes;
+ int ret;
+
+ dbp = dbc->dbp;
+
+ switch (TYPE(h)) {
+ case P_IBTREE:
+ bi = GET_BINTERNAL(h, indx);
+ switch (B_TYPE(bi->type)) {
+ case B_DUPLICATE:
+ case B_OVERFLOW:
+ nbytes = BINTERNAL_SIZE(bi->len);
+ bo = (BOVERFLOW *)bi->data;
+ goto offpage;
+ case B_KEYDATA:
+ nbytes = BINTERNAL_SIZE(bi->len);
+ break;
+ default:
+ return (__db_pgfmt(dbp, h->pgno));
+ }
+ break;
+ case P_IRECNO:
+ nbytes = RINTERNAL_SIZE;
+ break;
+ case P_LBTREE:
+ /*
+ * If it's a duplicate key, discard the index and don't touch
+ * the actual page item.
+ *
+ * XXX
+ * This works because no data item can have an index matching
+ * any other index so even if the data item is in a key "slot",
+ * it won't match any other index.
+ */
+ if ((indx % 2) == 0) {
+ /*
+ * Check for a duplicate after us on the page. NOTE:
+ * we have to delete the key item before deleting the
+ * data item, otherwise the "indx + P_INDX" calculation
+ * won't work!
+ */
+ if (indx + P_INDX < (u_int32_t)NUM_ENT(h) &&
+ h->inp[indx] == h->inp[indx + P_INDX])
+ return (__bam_adjindx(dbc,
+ h, indx, indx + O_INDX, 0));
+ /*
+ * Check for a duplicate before us on the page. It
+ * doesn't matter if we delete the key item before or
+ * after the data item for the purposes of this one.
+ */
+ if (indx > 0 && h->inp[indx] == h->inp[indx - P_INDX])
+ return (__bam_adjindx(dbc,
+ h, indx, indx - P_INDX, 0));
+ }
+ /* FALLTHROUGH */
+ case P_LRECNO:
+ bk = GET_BKEYDATA(h, indx);
+ switch (B_TYPE(bk->type)) {
+ case B_DUPLICATE:
+ case B_OVERFLOW:
+ nbytes = BOVERFLOW_SIZE;
+ bo = GET_BOVERFLOW(h, indx);
+
+offpage: /* Delete duplicate/offpage chains. */
+ if (B_TYPE(bo->type) == B_DUPLICATE) {
+ if ((ret =
+ __db_ddup(dbc, bo->pgno, __bam_free)) != 0)
+ return (ret);
+ } else
+ if ((ret =
+ __db_doff(dbc, bo->pgno, __bam_free)) != 0)
+ return (ret);
+ break;
+ case B_KEYDATA:
+ nbytes = BKEYDATA_SIZE(bk->len);
+ break;
+ default:
+ return (__db_pgfmt(dbp, h->pgno));
+ }
+ break;
+ default:
+ return (__db_pgfmt(dbp, h->pgno));
+ }
+
+ /* Delete the item. */
+ if ((ret = __db_ditem(dbc, h, indx, nbytes)) != 0)
+ return (ret);
+
+ /* Mark the page dirty. */
+ return (memp_fset(dbp->mpf, h, DB_MPOOL_DIRTY));
+}
+
+/*
+ * __bam_adjindx --
+ * Adjust an index on the page.
+ *
+ * PUBLIC: int __bam_adjindx __P((DBC *, PAGE *, u_int32_t, u_int32_t, int));
+ */
+int
+__bam_adjindx(dbc, h, indx, indx_copy, is_insert)
+ DBC *dbc;
+ PAGE *h;
+ u_int32_t indx, indx_copy;
+ int is_insert;
+{
+ DB *dbp;
+ db_indx_t copy;
+ int ret;
+
+ dbp = dbc->dbp;
+
+ /* Log the change. */
+ if (DB_LOGGING(dbc) &&
+ (ret = __bam_adj_log(dbp->dbenv->lg_info, dbc->txn, &LSN(h),
+ 0, dbp->log_fileid, PGNO(h), &LSN(h), indx, indx_copy,
+ (u_int32_t)is_insert)) != 0)
+ return (ret);
+
+ if (is_insert) {
+ copy = h->inp[indx_copy];
+ if (indx != NUM_ENT(h))
+ memmove(&h->inp[indx + O_INDX], &h->inp[indx],
+ sizeof(db_indx_t) * (NUM_ENT(h) - indx));
+ h->inp[indx] = copy;
+ ++NUM_ENT(h);
+ } else {
+ --NUM_ENT(h);
+ if (indx != NUM_ENT(h))
+ memmove(&h->inp[indx], &h->inp[indx + O_INDX],
+ sizeof(db_indx_t) * (NUM_ENT(h) - indx));
+ }
+
+ /* Mark the page dirty. */
+ ret = memp_fset(dbp->mpf, h, DB_MPOOL_DIRTY);
+
+ /* Adjust the cursors. */
+ __bam_ca_di(dbp, h->pgno, indx, is_insert ? 1 : -1);
+ return (0);
+}
+
+/*
+ * __bam_dpage --
+ * Delete a page from the tree.
+ *
+ * PUBLIC: int __bam_dpage __P((DBC *, const DBT *));
+ */
+int
+__bam_dpage(dbc, key)
+ DBC *dbc;
+ const DBT *key;
+{
+ CURSOR *cp;
+ DB *dbp;
+ DB_LOCK lock;
+ PAGE *h;
+ db_pgno_t pgno;
+ int level; /* !!!: has to hold number of tree levels. */
+ int exact, ret;
+
+ dbp = dbc->dbp;
+ cp = dbc->internal;
+ ret = 0;
+
+ /*
+ * The locking protocol is that we acquire locks by walking down the
+ * tree, to avoid the obvious deadlocks.
+ *
+ * Call __bam_search to reacquire the empty leaf page, but this time
+ * get both the leaf page and it's parent, locked. Walk back up the
+ * tree, until we have the top pair of pages that we want to delete.
+ * Once we have the top page that we want to delete locked, lock the
+ * underlying pages and check to make sure they're still empty. If
+ * they are, delete them.
+ */
+ for (level = LEAFLEVEL;; ++level) {
+ /* Acquire a page and its parent, locked. */
+ if ((ret =
+ __bam_search(dbc, key, S_WRPAIR, level, NULL, &exact)) != 0)
+ return (ret);
+
+ /*
+ * If we reach the root or the page isn't going to be empty
+ * when we delete one record, quit.
+ */
+ h = cp->csp[-1].page;
+ if (h->pgno == PGNO_ROOT || NUM_ENT(h) != 1)
+ break;
+
+ /* Release the two locked pages. */
+ (void)memp_fput(dbp->mpf, cp->csp[-1].page, 0);
+ (void)__BT_TLPUT(dbc, cp->csp[-1].lock);
+ (void)memp_fput(dbp->mpf, cp->csp[0].page, 0);
+ (void)__BT_TLPUT(dbc, cp->csp[0].lock);
+ }
+
+ /*
+ * Leave the stack pointer one after the last entry, we may be about
+ * to push more items on the stack.
+ */
+ ++cp->csp;
+
+ /*
+ * cp->csp[-2].page is the top page, which we're not going to delete,
+ * and cp->csp[-1].page is the first page we are going to delete.
+ *
+ * Walk down the chain, acquiring the rest of the pages until we've
+ * retrieved the leaf page. If we find any pages that aren't going
+ * to be emptied by the delete, someone else added something while we
+ * were walking the tree, and we discontinue the delete.
+ */
+ for (h = cp->csp[-1].page;;) {
+ if (ISLEAF(h)) {
+ if (NUM_ENT(h) != 0)
+ goto release;
+ break;
+ } else
+ if (NUM_ENT(h) != 1)
+ goto release;
+
+ /*
+ * Get the next page, write lock it and push it onto the stack.
+ * We know it's index 0, because it can only have one element.
+ */
+ pgno = TYPE(h) == P_IBTREE ?
+ GET_BINTERNAL(h, 0)->pgno : GET_RINTERNAL(h, 0)->pgno;
+
+ if ((ret = __bam_lget(dbc, 0, pgno, DB_LOCK_WRITE, &lock)) != 0)
+ goto release;
+ if ((ret = memp_fget(dbp->mpf, &pgno, 0, &h)) != 0)
+ goto release;
+ BT_STK_PUSH(cp, h, 0, lock, ret);
+ }
+
+ /* Adjust back to reference the last page on the stack. */
+ BT_STK_POP(cp);
+
+ /* Delete the pages. */
+ return (__bam_dpages(dbc));
+
+release:
+ /* Adjust back to reference the last page on the stack. */
+ BT_STK_POP(cp);
+
+ /* Discard any locked pages and return. */
+ __bam_stkrel(dbc, 0);
+
+ return (ret);
+}
+
+/*
+ * __bam_dpages --
+ * Delete a set of locked pages.
+ *
+ * PUBLIC: int __bam_dpages __P((DBC *));
+ */
+int
+__bam_dpages(dbc)
+ DBC *dbc;
+{
+ CURSOR *cp;
+ DB *dbp;
+ DBT a, b;
+ DB_LOCK c_lock, p_lock;
+ EPG *epg;
+ PAGE *child, *parent;
+ db_indx_t nitems;
+ db_pgno_t pgno;
+ db_recno_t rcnt;
+ int done, ret;
+
+ dbp = dbc->dbp;
+ cp = dbc->internal;
+ epg = cp->sp;
+
+ /*
+ * !!!
+ * There is an interesting deadlock situation here. We have to relink
+ * the leaf page chain around the leaf page being deleted. Consider
+ * a cursor walking through the leaf pages, that has the previous page
+ * read-locked and is waiting on a lock for the page we're deleting.
+ * It will deadlock here. This is a problem, because if our process is
+ * selected to resolve the deadlock, we'll leave an empty leaf page
+ * that we can never again access by walking down the tree. So, before
+ * we unlink the subtree, we relink the leaf page chain.
+ */
+ if ((ret = __db_relink(dbc, DB_REM_PAGE, cp->csp->page, NULL, 1)) != 0)
+ goto release;
+
+ /*
+ * We have the entire stack of deletable pages locked.
+ *
+ * Delete the highest page in the tree's reference to the underlying
+ * stack of pages. Then, release that page, letting the rest of the
+ * tree get back to business.
+ */
+ if ((ret = __bam_ditem(dbc, epg->page, epg->indx)) != 0) {
+release: (void)__bam_stkrel(dbc, 0);
+ return (ret);
+ }
+
+ pgno = epg->page->pgno;
+ nitems = NUM_ENT(epg->page);
+
+ (void)memp_fput(dbp->mpf, epg->page, 0);
+ (void)__BT_TLPUT(dbc, epg->lock);
+
+ /*
+ * Free the rest of the stack of pages.
+ *
+ * !!!
+ * Don't bother checking for errors. We've unlinked the subtree from
+ * the tree, and there's no possibility of recovery outside of doing
+ * TXN rollback.
+ */
+ while (++epg <= cp->csp) {
+ /*
+ * Delete page entries so they will be restored as part of
+ * recovery.
+ */
+ if (NUM_ENT(epg->page) != 0)
+ (void)__bam_ditem(dbc, epg->page, epg->indx);
+
+ (void)__bam_free(dbc, epg->page);
+ (void)__BT_TLPUT(dbc, epg->lock);
+ }
+ BT_STK_CLR(cp);
+
+ /*
+ * Try and collapse the tree a level -- this is only applicable
+ * if we've deleted the next-to-last element from the root page.
+ *
+ * There are two cases when collapsing a tree.
+ *
+ * If we've just deleted the last item from the root page, there is no
+ * further work to be done. The code above has emptied the root page
+ * and freed all pages below it.
+ */
+ if (pgno != PGNO_ROOT || nitems != 1)
+ return (0);
+
+ /*
+ * If we just deleted the next-to-last item from the root page, the
+ * tree can collapse one or more levels. While there remains only a
+ * single item on the root page, write lock the last page referenced
+ * by the root page and copy it over the root page. If we can't get a
+ * write lock, that's okay, the tree just stays deeper than we'd like.
+ */
+ for (done = 0; !done;) {
+ /* Initialize. */
+ parent = child = NULL;
+ p_lock = c_lock = LOCK_INVALID;
+
+ /* Lock the root. */
+ pgno = PGNO_ROOT;
+ if ((ret =
+ __bam_lget(dbc, 0, pgno, DB_LOCK_WRITE, &p_lock)) != 0)
+ goto stop;
+ if ((ret = memp_fget(dbp->mpf, &pgno, 0, &parent)) != 0)
+ goto stop;
+
+ if (NUM_ENT(parent) != 1 ||
+ (TYPE(parent) != P_IBTREE && TYPE(parent) != P_IRECNO))
+ goto stop;
+
+ pgno = TYPE(parent) == P_IBTREE ?
+ GET_BINTERNAL(parent, 0)->pgno :
+ GET_RINTERNAL(parent, 0)->pgno;
+
+ /* Lock the child page. */
+ if ((ret =
+ __bam_lget(dbc, 0, pgno, DB_LOCK_WRITE, &c_lock)) != 0)
+ goto stop;
+ if ((ret = memp_fget(dbp->mpf, &pgno, 0, &child)) != 0)
+ goto stop;
+
+ /* Log the change. */
+ if (DB_LOGGING(dbc)) {
+ memset(&a, 0, sizeof(a));
+ a.data = child;
+ a.size = dbp->pgsize;
+ memset(&b, 0, sizeof(b));
+ b.data = P_ENTRY(parent, 0);
+ b.size = BINTERNAL_SIZE(((BINTERNAL *)b.data)->len);
+ __bam_rsplit_log(dbp->dbenv->lg_info, dbc->txn,
+ &child->lsn, 0, dbp->log_fileid, child->pgno, &a,
+ RE_NREC(parent), &b, &parent->lsn);
+ }
+
+ /*
+ * Make the switch.
+ *
+ * One fixup -- if the tree has record numbers and we're not
+ * converting to a leaf page, we have to preserve the total
+ * record count. Note that we are about to overwrite everything
+ * on the parent, including its LSN. This is actually OK,
+ * because the above log message, which describes this update,
+ * stores its LSN on the child page. When the child is copied
+ * to the parent, the correct LSN is going to copied into
+ * place in the parent.
+ */
+ COMPQUIET(rcnt, 0);
+ if (TYPE(child) == P_IRECNO ||
+ (TYPE(child) == P_IBTREE && F_ISSET(dbp, DB_BT_RECNUM)))
+ rcnt = RE_NREC(parent);
+ memcpy(parent, child, dbp->pgsize);
+ parent->pgno = PGNO_ROOT;
+ if (TYPE(child) == P_IRECNO ||
+ (TYPE(child) == P_IBTREE && F_ISSET(dbp, DB_BT_RECNUM)))
+ RE_NREC_SET(parent, rcnt);
+
+ /* Mark the pages dirty. */
+ memp_fset(dbp->mpf, parent, DB_MPOOL_DIRTY);
+ memp_fset(dbp->mpf, child, DB_MPOOL_DIRTY);
+
+ /* Adjust the cursors. */
+ __bam_ca_rsplit(dbp, child->pgno, PGNO_ROOT);
+
+ /*
+ * Free the page copied onto the root page and discard its
+ * lock. (The call to __bam_free() discards our reference
+ * to the page.)
+ */
+ (void)__bam_free(dbc, child);
+ child = NULL;
+
+ if (0) {
+stop: done = 1;
+ }
+ if (p_lock != LOCK_INVALID)
+ (void)__BT_TLPUT(dbc, p_lock);
+ if (parent != NULL)
+ memp_fput(dbp->mpf, parent, 0);
+ if (c_lock != LOCK_INVALID)
+ (void)__BT_TLPUT(dbc, c_lock);
+ if (child != NULL)
+ memp_fput(dbp->mpf, child, 0);
+ }
+
+ return (0);
+}
diff --git a/usr/src/cmd/sendmail/db/btree/bt_open.c b/usr/src/cmd/sendmail/db/btree/bt_open.c
new file mode 100644
index 0000000000..a89cfccb97
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/btree/bt_open.c
@@ -0,0 +1,310 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+/*
+ * Copyright (c) 1990, 1993, 1994, 1995, 1996
+ * Keith Bostic. All rights reserved.
+ */
+/*
+ * Copyright (c) 1990, 1993, 1994, 1995
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Mike Olson.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)bt_open.c 10.39 (Sleepycat) 11/21/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <errno.h>
+#include <limits.h>
+#include <string.h>
+#endif
+
+#include "db_int.h"
+#include "db_page.h"
+#include "btree.h"
+
+/*
+ * __bam_open --
+ * Open a btree.
+ *
+ * PUBLIC: int __bam_open __P((DB *, DB_INFO *));
+ */
+int
+__bam_open(dbp, dbinfo)
+ DB *dbp;
+ DB_INFO *dbinfo;
+{
+ BTREE *t;
+ int ret;
+
+ /* Allocate and initialize the private btree structure. */
+ if ((ret = __os_calloc(1, sizeof(BTREE), &t)) != 0)
+ return (ret);
+ dbp->internal = t;
+
+ /*
+ * Intention is to make sure all of the user's selections are okay
+ * here and then use them without checking.
+ */
+ if (dbinfo == NULL) {
+ t->bt_minkey = DEFMINKEYPAGE;
+ t->bt_compare = __bam_defcmp;
+ t->bt_prefix = __bam_defpfx;
+ } else {
+ /* Minimum number of keys per page. */
+ if (dbinfo->bt_minkey == 0)
+ t->bt_minkey = DEFMINKEYPAGE;
+ else {
+ if (dbinfo->bt_minkey < 2)
+ goto einval;
+ t->bt_minkey = dbinfo->bt_minkey;
+ }
+
+ /* Maximum number of keys per page. */
+ if (dbinfo->bt_maxkey == 0)
+ t->bt_maxkey = 0;
+ else {
+ if (dbinfo->bt_maxkey < 1)
+ goto einval;
+ t->bt_maxkey = dbinfo->bt_maxkey;
+ }
+
+ /*
+ * If no comparison, use default comparison. If no comparison
+ * and no prefix, use default prefix. (We can't default the
+ * prefix if the user supplies a comparison routine; shortening
+ * the keys may break their comparison algorithm. We don't
+ * permit the user to specify a prefix routine if they didn't
+ * also specify a comparison routine, they can't know enough
+ * about our comparison routine to get it right.)
+ */
+ if ((t->bt_compare = dbinfo->bt_compare) == NULL) {
+ if (dbinfo->bt_prefix != NULL)
+ goto einval;
+ t->bt_compare = __bam_defcmp;
+ t->bt_prefix = __bam_defpfx;
+ } else
+ t->bt_prefix = dbinfo->bt_prefix;
+ }
+
+ /* Initialize the remaining fields/methods of the DB. */
+ dbp->am_close = __bam_close;
+ dbp->del = __bam_delete;
+ dbp->stat = __bam_stat;
+
+ /* Start up the tree. */
+ if ((ret = __bam_read_root(dbp)) != 0)
+ goto err;
+
+ /* Set the overflow page size. */
+ __bam_setovflsize(dbp);
+
+ return (0);
+
+einval: ret = EINVAL;
+
+err: __os_free(t, sizeof(BTREE));
+ return (ret);
+}
+
+/*
+ * __bam_close --
+ * Close a btree.
+ *
+ * PUBLIC: int __bam_close __P((DB *));
+ */
+int
+__bam_close(dbp)
+ DB *dbp;
+{
+ __os_free(dbp->internal, sizeof(BTREE));
+ dbp->internal = NULL;
+
+ return (0);
+}
+
+/*
+ * __bam_setovflsize --
+ *
+ * PUBLIC: void __bam_setovflsize __P((DB *));
+ */
+void
+__bam_setovflsize(dbp)
+ DB *dbp;
+{
+ BTREE *t;
+
+ t = dbp->internal;
+
+ /*
+ * !!!
+ * Correction for recno, which doesn't know anything about minimum
+ * keys per page.
+ */
+ if (t->bt_minkey == 0)
+ t->bt_minkey = DEFMINKEYPAGE;
+
+ /*
+ * The btree data structure requires that at least two key/data pairs
+ * can fit on a page, but other than that there's no fixed requirement.
+ * Translate the minimum number of items into the bytes a key/data pair
+ * can use before being placed on an overflow page. We calculate for
+ * the worst possible alignment by assuming every item requires the
+ * maximum alignment for padding.
+ *
+ * Recno uses the btree bt_ovflsize value -- it's close enough.
+ */
+ t->bt_ovflsize = (dbp->pgsize - P_OVERHEAD) / (t->bt_minkey * P_INDX)
+ - (BKEYDATA_PSIZE(0) + ALIGN(1, 4));
+}
+
+/*
+ * __bam_read_root --
+ * Check (and optionally create) a tree.
+ *
+ * PUBLIC: int __bam_read_root __P((DB *));
+ */
+int
+__bam_read_root(dbp)
+ DB *dbp;
+{
+ BTMETA *meta;
+ BTREE *t;
+ DBC *dbc;
+ DB_LOCK metalock, rootlock;
+ PAGE *root;
+ db_pgno_t pgno;
+ int ret, t_ret;
+
+ ret = 0;
+ t = dbp->internal;
+
+ /* Get a cursor. */
+ if ((ret = dbp->cursor(dbp, NULL, &dbc, 0)) != 0)
+ return (ret);
+
+ /* Get, and optionally create the metadata page. */
+ pgno = PGNO_METADATA;
+ if ((ret =
+ __bam_lget(dbc, 0, PGNO_METADATA, DB_LOCK_WRITE, &metalock)) != 0)
+ goto err;
+ if ((ret =
+ memp_fget(dbp->mpf, &pgno, DB_MPOOL_CREATE, (PAGE **)&meta)) != 0) {
+ (void)__BT_LPUT(dbc, metalock);
+ goto err;
+ }
+
+ /*
+ * If the magic number is correct, we're not creating the tree.
+ * Correct any fields that may not be right. Note, all of the
+ * local flags were set by db_open(3).
+ */
+ if (meta->magic != 0) {
+ t->bt_maxkey = meta->maxkey;
+ t->bt_minkey = meta->minkey;
+
+ (void)memp_fput(dbp->mpf, (PAGE *)meta, 0);
+ (void)__BT_LPUT(dbc, metalock);
+ goto done;
+ }
+
+ /* Initialize the tree structure metadata information. */
+ memset(meta, 0, sizeof(BTMETA));
+ ZERO_LSN(meta->lsn);
+ meta->pgno = PGNO_METADATA;
+ meta->magic = DB_BTREEMAGIC;
+ meta->version = DB_BTREEVERSION;
+ meta->pagesize = dbp->pgsize;
+ meta->maxkey = t->bt_maxkey;
+ meta->minkey = t->bt_minkey;
+ meta->free = PGNO_INVALID;
+ if (dbp->type == DB_RECNO)
+ F_SET(meta, BTM_RECNO);
+ if (F_ISSET(dbp, DB_AM_DUP))
+ F_SET(meta, BTM_DUP);
+ if (F_ISSET(dbp, DB_RE_FIXEDLEN))
+ F_SET(meta, BTM_FIXEDLEN);
+ if (F_ISSET(dbp, DB_BT_RECNUM))
+ F_SET(meta, BTM_RECNUM);
+ if (F_ISSET(dbp, DB_RE_RENUMBER))
+ F_SET(meta, BTM_RENUMBER);
+ memcpy(meta->uid, dbp->fileid, DB_FILE_ID_LEN);
+
+ /* Create and initialize a root page. */
+ pgno = PGNO_ROOT;
+ if ((ret =
+ __bam_lget(dbc, 0, PGNO_ROOT, DB_LOCK_WRITE, &rootlock)) != 0)
+ goto err;
+ if ((ret = memp_fget(dbp->mpf, &pgno, DB_MPOOL_CREATE, &root)) != 0) {
+ (void)__BT_LPUT(dbc, rootlock);
+ goto err;
+ }
+ P_INIT(root, dbp->pgsize, PGNO_ROOT, PGNO_INVALID,
+ PGNO_INVALID, 1, dbp->type == DB_RECNO ? P_LRECNO : P_LBTREE);
+ ZERO_LSN(root->lsn);
+
+ /* Release the metadata and root pages. */
+ if ((ret = memp_fput(dbp->mpf, (PAGE *)meta, DB_MPOOL_DIRTY)) != 0)
+ goto err;
+ if ((ret = memp_fput(dbp->mpf, root, DB_MPOOL_DIRTY)) != 0)
+ goto err;
+
+ /*
+ * Flush the metadata and root pages to disk -- since the user can't
+ * transaction protect open, the pages have to exist during recovery.
+ *
+ * XXX
+ * It's not useful to return not-yet-flushed here -- convert it to
+ * an error.
+ */
+ if ((ret = memp_fsync(dbp->mpf)) == DB_INCOMPLETE)
+ ret = EINVAL;
+
+ /* Release the locks. */
+ (void)__BT_LPUT(dbc, metalock);
+ (void)__BT_LPUT(dbc, rootlock);
+
+err:
+done: if ((t_ret = dbc->c_close(dbc)) != 0 && ret == 0)
+ ret = t_ret;
+ return (ret);
+}
diff --git a/usr/src/cmd/sendmail/db/btree/bt_page.c b/usr/src/cmd/sendmail/db/btree/bt_page.c
new file mode 100644
index 0000000000..6ccd68a5ab
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/btree/bt_page.c
@@ -0,0 +1,317 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+/*
+ * Copyright (c) 1990, 1993, 1994, 1995, 1996
+ * Keith Bostic. All rights reserved.
+ */
+/*
+ * Copyright (c) 1990, 1993, 1994, 1995
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Mike Olson.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)bt_page.c 10.17 (Sleepycat) 1/3/99";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <errno.h>
+#include <string.h>
+#endif
+
+#include "db_int.h"
+#include "db_page.h"
+#include "btree.h"
+
+/*
+ * __bam_new --
+ * Get a new page, preferably from the freelist.
+ *
+ * PUBLIC: int __bam_new __P((DBC *, u_int32_t, PAGE **));
+ */
+int
+__bam_new(dbc, type, pagepp)
+ DBC *dbc;
+ u_int32_t type;
+ PAGE **pagepp;
+{
+ BTMETA *meta;
+ DB *dbp;
+ DB_LOCK metalock;
+ PAGE *h;
+ db_pgno_t pgno;
+ int ret;
+
+ dbp = dbc->dbp;
+ meta = NULL;
+ h = NULL;
+ metalock = LOCK_INVALID;
+
+ pgno = PGNO_METADATA;
+ if ((ret = __bam_lget(dbc, 0, pgno, DB_LOCK_WRITE, &metalock)) != 0)
+ goto err;
+ if ((ret = memp_fget(dbp->mpf, &pgno, 0, (PAGE **)&meta)) != 0)
+ goto err;
+
+ if (meta->free == PGNO_INVALID) {
+ if ((ret = memp_fget(dbp->mpf, &pgno, DB_MPOOL_NEW, &h)) != 0)
+ goto err;
+ ZERO_LSN(h->lsn);
+ h->pgno = pgno;
+ } else {
+ pgno = meta->free;
+ if ((ret = memp_fget(dbp->mpf, &pgno, 0, &h)) != 0)
+ goto err;
+ meta->free = h->next_pgno;
+ }
+
+ /* Log the change. */
+ if (DB_LOGGING(dbc)) {
+ if ((ret = __bam_pg_alloc_log(dbp->dbenv->lg_info, dbc->txn,
+ &meta->lsn, 0, dbp->log_fileid, &meta->lsn, &h->lsn,
+ h->pgno, (u_int32_t)type, meta->free)) != 0)
+ goto err;
+ LSN(h) = LSN(meta);
+ }
+
+ (void)memp_fput(dbp->mpf, (PAGE *)meta, DB_MPOOL_DIRTY);
+ (void)__BT_TLPUT(dbc, metalock);
+
+ P_INIT(h, dbp->pgsize, h->pgno, PGNO_INVALID, PGNO_INVALID, 0, type);
+ *pagepp = h;
+ return (0);
+
+err: if (h != NULL)
+ (void)memp_fput(dbp->mpf, h, 0);
+ if (meta != NULL)
+ (void)memp_fput(dbp->mpf, meta, 0);
+ if (metalock != LOCK_INVALID)
+ (void)__BT_TLPUT(dbc, metalock);
+ return (ret);
+}
+
+/*
+ * __bam_lput --
+ * The standard lock put call.
+ *
+ * PUBLIC: int __bam_lput __P((DBC *, DB_LOCK));
+ */
+int
+__bam_lput(dbc, lock)
+ DBC *dbc;
+ DB_LOCK lock;
+{
+ return (__BT_LPUT(dbc, lock));
+}
+
+/*
+ * __bam_free --
+ * Add a page to the head of the freelist.
+ *
+ * PUBLIC: int __bam_free __P((DBC *, PAGE *));
+ */
+int
+__bam_free(dbc, h)
+ DBC *dbc;
+ PAGE *h;
+{
+ BTMETA *meta;
+ DB *dbp;
+ DBT ldbt;
+ DB_LOCK metalock;
+ db_pgno_t pgno;
+ u_int32_t dirty_flag;
+ int ret, t_ret;
+
+ dbp = dbc->dbp;
+
+ /*
+ * Retrieve the metadata page and insert the page at the head of
+ * the free list. If either the lock get or page get routines
+ * fail, then we need to put the page with which we were called
+ * back because our caller assumes we take care of it.
+ */
+ dirty_flag = 0;
+ pgno = PGNO_METADATA;
+ if ((ret = __bam_lget(dbc, 0, pgno, DB_LOCK_WRITE, &metalock)) != 0)
+ goto err;
+ if ((ret = memp_fget(dbp->mpf, &pgno, 0, (PAGE **)&meta)) != 0) {
+ (void)__BT_TLPUT(dbc, metalock);
+ goto err;
+ }
+
+ /* Log the change. */
+ if (DB_LOGGING(dbc)) {
+ memset(&ldbt, 0, sizeof(ldbt));
+ ldbt.data = h;
+ ldbt.size = P_OVERHEAD;
+ if ((ret = __bam_pg_free_log(dbp->dbenv->lg_info,
+ dbc->txn, &meta->lsn, 0, dbp->log_fileid, h->pgno,
+ &meta->lsn, &ldbt, meta->free)) != 0) {
+ (void)memp_fput(dbp->mpf, (PAGE *)meta, 0);
+ (void)__BT_TLPUT(dbc, metalock);
+ return (ret);
+ }
+ LSN(h) = LSN(meta);
+ }
+
+ /*
+ * The page should have nothing interesting on it, re-initialize it,
+ * leaving only the page number and the LSN.
+ */
+#ifdef DIAGNOSTIC
+ { db_pgno_t __pgno; DB_LSN __lsn;
+ __pgno = h->pgno;
+ __lsn = h->lsn;
+ memset(h, 0xdb, dbp->pgsize);
+ h->pgno = __pgno;
+ h->lsn = __lsn;
+ }
+#endif
+ P_INIT(h, dbp->pgsize, h->pgno, PGNO_INVALID, meta->free, 0, P_INVALID);
+
+ /* Link the page on the metadata free list. */
+ meta->free = h->pgno;
+
+ /* Discard the metadata page. */
+ ret = memp_fput(dbp->mpf, (PAGE *)meta, DB_MPOOL_DIRTY);
+ if ((t_ret = __BT_TLPUT(dbc, metalock)) != 0)
+ ret = t_ret;
+
+ /* Discard the caller's page reference. */
+ dirty_flag = DB_MPOOL_DIRTY;
+err: if ((t_ret = memp_fput(dbp->mpf, h, dirty_flag)) != 0 && ret == 0)
+ ret = t_ret;
+
+ /*
+ * XXX
+ * We have to unlock the caller's page in the caller!
+ */
+ return (ret);
+}
+
+#ifdef DEBUG
+/*
+ * __bam_lt --
+ * Print out the list of locks currently held by a cursor.
+ *
+ * PUBLIC: int __bam_lt __P((DBC *));
+ */
+int
+__bam_lt(dbc)
+ DBC *dbc;
+{
+ DB *dbp;
+ DB_LOCKREQ req;
+
+ dbp = dbc->dbp;
+ if (F_ISSET(dbp, DB_AM_LOCKING)) {
+ req.op = DB_LOCK_DUMP;
+ lock_vec(dbp->dbenv->lk_info, dbc->locker, 0, &req, 1, NULL);
+ }
+ return (0);
+}
+#endif
+
+/*
+ * __bam_lget --
+ * The standard lock get call.
+ *
+ * PUBLIC: int __bam_lget
+ * PUBLIC: __P((DBC *, int, db_pgno_t, db_lockmode_t, DB_LOCK *));
+ */
+int
+__bam_lget(dbc, do_couple, pgno, mode, lockp)
+ DBC *dbc;
+ int do_couple;
+ db_pgno_t pgno;
+ db_lockmode_t mode;
+ DB_LOCK *lockp;
+{
+ DB *dbp;
+ DB_LOCKREQ couple[2];
+ int ret;
+
+ dbp = dbc->dbp;
+
+ if (!F_ISSET(dbp, DB_AM_LOCKING)) {
+ *lockp = LOCK_INVALID;
+ return (0);
+ }
+
+ dbc->lock.pgno = pgno;
+
+ /*
+ * If the object not currently locked, acquire the lock and return,
+ * otherwise, lock couple. If we fail and it's not a system error,
+ * convert to EAGAIN.
+ */
+ if (do_couple) {
+ couple[0].op = DB_LOCK_GET;
+ couple[0].obj = &dbc->lock_dbt;
+ couple[0].mode = mode;
+ couple[1].op = DB_LOCK_PUT;
+ couple[1].lock = *lockp;
+
+ if (dbc->txn == NULL)
+ ret = lock_vec(dbp->dbenv->lk_info,
+ dbc->locker, 0, couple, 2, NULL);
+ else
+ ret = lock_tvec(dbp->dbenv->lk_info,
+ dbc->txn, 0, couple, 2, NULL);
+ if (ret != 0) {
+ /* If we fail, discard the lock we held. */
+ __BT_LPUT(dbc, *lockp);
+
+ return (ret < 0 ? EAGAIN : ret);
+ }
+ *lockp = couple[0].lock;
+ } else {
+ if (dbc->txn == NULL)
+ ret = lock_get(dbp->dbenv->lk_info,
+ dbc->locker, 0, &dbc->lock_dbt, mode, lockp);
+ else
+ ret = lock_tget(dbp->dbenv->lk_info,
+ dbc->txn, 0, &dbc->lock_dbt, mode, lockp);
+ return (ret < 0 ? EAGAIN : ret);
+ }
+ return (0);
+}
diff --git a/usr/src/cmd/sendmail/db/btree/bt_put.c b/usr/src/cmd/sendmail/db/btree/bt_put.c
new file mode 100644
index 0000000000..0d7a69889a
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/btree/bt_put.c
@@ -0,0 +1,831 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+/*
+ * Copyright (c) 1990, 1993, 1994, 1995, 1996
+ * Keith Bostic. All rights reserved.
+ */
+/*
+ * Copyright (c) 1990, 1993, 1994, 1995
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Mike Olson.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)bt_put.c 10.54 (Sleepycat) 12/6/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <errno.h>
+#include <string.h>
+#endif
+
+#include "db_int.h"
+#include "db_page.h"
+#include "btree.h"
+
+static int __bam_fixed __P((DBC *, DBT *));
+static int __bam_ndup __P((DBC *, PAGE *, u_int32_t));
+static int __bam_ovput __P((DBC *, PAGE *, u_int32_t, DBT *));
+static int __bam_partial __P((DBC *,
+ DBT *, PAGE *, u_int32_t, u_int32_t, u_int32_t));
+static u_int32_t __bam_partsize __P((DBT *, PAGE *, u_int32_t));
+
+/*
+ * __bam_iitem --
+ * Insert an item into the tree.
+ *
+ * PUBLIC: int __bam_iitem __P((DBC *,
+ * PUBLIC: PAGE **, db_indx_t *, DBT *, DBT *, u_int32_t, u_int32_t));
+ */
+int
+__bam_iitem(dbc, hp, indxp, key, data, op, flags)
+ DBC *dbc;
+ PAGE **hp;
+ db_indx_t *indxp;
+ DBT *key, *data;
+ u_int32_t op, flags;
+{
+ BTREE *t;
+ BKEYDATA *bk;
+ DB *dbp;
+ DBT tdbt;
+ PAGE *h;
+ db_indx_t indx, nbytes;
+ u_int32_t data_size, have_bytes, need_bytes, needed;
+ int bigkey, bigdata, dupadjust, replace, ret;
+
+ COMPQUIET(bk, NULL);
+
+ dbp = dbc->dbp;
+ t = dbp->internal;
+ h = *hp;
+ indx = *indxp;
+ dupadjust = replace = 0;
+
+ /*
+ * If it's a page of duplicates, call the common code to do the work.
+ *
+ * !!!
+ * Here's where the hp and indxp are important. The duplicate code
+ * may decide to rework/rearrange the pages and indices we're using,
+ * so the caller must understand that the page stack may change.
+ */
+ if (TYPE(h) == P_DUPLICATE) {
+ /* Adjust the index for the new item if it's a DB_AFTER op. */
+ if (op == DB_AFTER)
+ ++*indxp;
+
+ /* Remove the current item if it's a DB_CURRENT op. */
+ if (op == DB_CURRENT) {
+ bk = GET_BKEYDATA(*hp, *indxp);
+ switch (B_TYPE(bk->type)) {
+ case B_KEYDATA:
+ nbytes = BKEYDATA_SIZE(bk->len);
+ break;
+ case B_OVERFLOW:
+ nbytes = BOVERFLOW_SIZE;
+ break;
+ default:
+ return (__db_pgfmt(dbp, h->pgno));
+ }
+ if ((ret = __db_ditem(dbc, *hp, *indxp, nbytes)) != 0)
+ return (ret);
+ }
+
+ /* Put the new/replacement item onto the page. */
+ if ((ret = __db_dput(dbc, data, hp, indxp, __bam_new)) != 0)
+ return (ret);
+
+ goto done;
+ }
+
+ /* Handle fixed-length records: build the real record. */
+ if (F_ISSET(dbp, DB_RE_FIXEDLEN) && data->size != t->recno->re_len) {
+ tdbt = *data;
+ if ((ret = __bam_fixed(dbc, &tdbt)) != 0)
+ return (ret);
+ data = &tdbt;
+ }
+
+ /*
+ * Figure out how much space the data will take, including if it's a
+ * partial record. If either of the key or data items won't fit on
+ * a page, we'll have to store them on overflow pages.
+ */
+ bigkey = LF_ISSET(BI_NEWKEY) && key->size > t->bt_ovflsize;
+ data_size = F_ISSET(data, DB_DBT_PARTIAL) ?
+ __bam_partsize(data, h, indx) : data->size;
+ bigdata = data_size > t->bt_ovflsize;
+
+ needed = 0;
+ if (LF_ISSET(BI_NEWKEY)) {
+ /* If BI_NEWKEY is set we're adding a new key and data pair. */
+ if (bigkey)
+ needed += BOVERFLOW_PSIZE;
+ else
+ needed += BKEYDATA_PSIZE(key->size);
+ if (bigdata)
+ needed += BOVERFLOW_PSIZE;
+ else
+ needed += BKEYDATA_PSIZE(data_size);
+ } else {
+ /*
+ * We're either overwriting the data item of a key/data pair
+ * or we're adding the data item only, i.e. a new duplicate.
+ */
+ if (op == DB_CURRENT) {
+ bk = GET_BKEYDATA(h,
+ indx + (TYPE(h) == P_LBTREE ? O_INDX : 0));
+ if (B_TYPE(bk->type) == B_KEYDATA)
+ have_bytes = BKEYDATA_PSIZE(bk->len);
+ else
+ have_bytes = BOVERFLOW_PSIZE;
+ need_bytes = 0;
+ } else {
+ have_bytes = 0;
+ need_bytes = sizeof(db_indx_t);
+ }
+ if (bigdata)
+ need_bytes += BOVERFLOW_PSIZE;
+ else
+ need_bytes += BKEYDATA_PSIZE(data_size);
+
+ if (have_bytes < need_bytes)
+ needed += need_bytes - have_bytes;
+ }
+
+ /*
+ * If there's not enough room, or the user has put a ceiling on the
+ * number of keys permitted in the page, split the page.
+ *
+ * XXX
+ * The t->bt_maxkey test here may be insufficient -- do we have to
+ * check in the btree split code, so we don't undo it there!?!?
+ */
+ if (P_FREESPACE(h) < needed ||
+ (t->bt_maxkey != 0 && NUM_ENT(h) > t->bt_maxkey))
+ return (DB_NEEDSPLIT);
+
+ /* Handle partial puts: build the real record. */
+ if (F_ISSET(data, DB_DBT_PARTIAL)) {
+ tdbt = *data;
+ if ((ret = __bam_partial(dbc,
+ &tdbt, h, indx, data_size, flags)) != 0)
+ return (ret);
+ data = &tdbt;
+ }
+
+ /*
+ * The code breaks it up into six cases:
+ *
+ * 1. Append a new key/data pair.
+ * 2. Insert a new key/data pair.
+ * 3. Append a new data item (a new duplicate).
+ * 4. Insert a new data item (a new duplicate).
+ * 5. Overflow item: delete and re-add the data item.
+ * 6. Replace the data item.
+ */
+ if (LF_ISSET(BI_NEWKEY)) {
+ switch (op) {
+ case DB_AFTER: /* 1. Append a new key/data pair. */
+ indx += 2;
+ *indxp += 2;
+ break;
+ case DB_BEFORE: /* 2. Insert a new key/data pair. */
+ break;
+ default:
+ return (EINVAL);
+ }
+
+ /* Add the key. */
+ if (bigkey) {
+ if ((ret = __bam_ovput(dbc, h, indx, key)) != 0)
+ return (ret);
+ } else
+ if ((ret = __db_pitem(dbc, h, indx,
+ BKEYDATA_SIZE(key->size), NULL, key)) != 0)
+ return (ret);
+ ++indx;
+ } else {
+ switch (op) {
+ case DB_AFTER: /* 3. Append a new data item. */
+ if (TYPE(h) == P_LBTREE) {
+ /*
+ * Adjust the cursor and copy in the key for
+ * the duplicate.
+ */
+ if ((ret = __bam_adjindx(dbc,
+ h, indx + P_INDX, indx, 1)) != 0)
+ return (ret);
+
+ indx += 3;
+ dupadjust = 1;
+
+ *indxp += 2;
+ } else {
+ ++indx;
+ __bam_ca_di(dbp, h->pgno, indx, 1);
+
+ *indxp += 1;
+ }
+ break;
+ case DB_BEFORE: /* 4. Insert a new data item. */
+ if (TYPE(h) == P_LBTREE) {
+ /*
+ * Adjust the cursor and copy in the key for
+ * the duplicate.
+ */
+ if ((ret =
+ __bam_adjindx(dbc, h, indx, indx, 1)) != 0)
+ return (ret);
+
+ ++indx;
+ dupadjust = 1;
+ } else
+ __bam_ca_di(dbp, h->pgno, indx, 1);
+ break;
+ case DB_CURRENT:
+ if (TYPE(h) == P_LBTREE)
+ ++indx;
+
+ /*
+ * 5. Delete/re-add the data item.
+ *
+ * If we're dealing with offpage items, we have to
+ * delete and then re-add the item.
+ */
+ if (bigdata || B_TYPE(bk->type) != B_KEYDATA) {
+ if ((ret = __bam_ditem(dbc, h, indx)) != 0)
+ return (ret);
+ break;
+ }
+
+ /* 6. Replace the data item. */
+ replace = 1;
+ break;
+ default:
+ return (EINVAL);
+ }
+ }
+
+ /* Add the data. */
+ if (bigdata) {
+ if ((ret = __bam_ovput(dbc, h, indx, data)) != 0)
+ return (ret);
+ } else {
+ BKEYDATA __bk;
+ DBT __hdr;
+
+ if (LF_ISSET(BI_DELETED)) {
+ B_TSET(__bk.type, B_KEYDATA, 1);
+ __bk.len = data->size;
+ __hdr.data = &__bk;
+ __hdr.size = SSZA(BKEYDATA, data);
+ ret = __db_pitem(dbc, h, indx,
+ BKEYDATA_SIZE(data->size), &__hdr, data);
+ } else if (replace)
+ ret = __bam_ritem(dbc, h, indx, data);
+ else
+ ret = __db_pitem(dbc, h, indx,
+ BKEYDATA_SIZE(data->size), NULL, data);
+ if (ret != 0)
+ return (ret);
+ }
+
+ if ((ret = memp_fset(dbp->mpf, h, DB_MPOOL_DIRTY)) != 0)
+ return (ret);
+
+ /*
+ * If the page is at least 50% full, and we added a duplicate, see if
+ * that set of duplicates takes up at least 25% of the space. If it
+ * does, move it off onto its own page.
+ */
+ if (dupadjust && P_FREESPACE(h) <= dbp->pgsize / 2) {
+ --indx;
+ if ((ret = __bam_ndup(dbc, h, indx)) != 0)
+ return (ret);
+ }
+
+ /*
+ * If we've changed the record count, update the tree. Record counts
+ * need to be updated in recno databases and in btree databases where
+ * we are supporting records. In both cases, adjust the count if the
+ * operation wasn't performed on the current record or when the caller
+ * overrides and wants the adjustment made regardless.
+ */
+done: if (LF_ISSET(BI_DOINCR) ||
+ (op != DB_CURRENT &&
+ (F_ISSET(dbp, DB_BT_RECNUM) || dbp->type == DB_RECNO)))
+ if ((ret = __bam_adjust(dbc, 1)) != 0)
+ return (ret);
+
+ /* If we've modified a recno file, set the flag */
+ if (t->recno != NULL)
+ F_SET(t->recno, RECNO_MODIFIED);
+
+ return (ret);
+}
+
+/*
+ * __bam_partsize --
+ * Figure out how much space a partial data item is in total.
+ */
+static u_int32_t
+__bam_partsize(data, h, indx)
+ DBT *data;
+ PAGE *h;
+ u_int32_t indx;
+{
+ BKEYDATA *bk;
+ u_int32_t nbytes;
+
+ /*
+ * Figure out how much total space we'll need. If the record doesn't
+ * already exist, it's simply the data we're provided.
+ */
+ if (indx >= NUM_ENT(h))
+ return (data->doff + data->size);
+
+ /*
+ * Otherwise, it's the data provided plus any already existing data
+ * that we're not replacing.
+ */
+ bk = GET_BKEYDATA(h, indx + (TYPE(h) == P_LBTREE ? O_INDX : 0));
+ nbytes =
+ B_TYPE(bk->type) == B_OVERFLOW ? ((BOVERFLOW *)bk)->tlen : bk->len;
+
+ /*
+ * There are really two cases here:
+ *
+ * Case 1: We are replacing some bytes that do not exist (i.e., they
+ * are past the end of the record). In this case the number of bytes
+ * we are replacing is irrelevant and all we care about is how many
+ * bytes we are going to add from offset. So, the new record length
+ * is going to be the size of the new bytes (size) plus wherever those
+ * new bytes begin (doff).
+ *
+ * Case 2: All the bytes we are replacing exist. Therefore, the new
+ * size is the oldsize (nbytes) minus the bytes we are replacing (dlen)
+ * plus the bytes we are adding (size).
+ */
+ if (nbytes < data->doff + data->dlen) /* Case 1 */
+ return (data->doff + data->size);
+
+ return (nbytes + data->size - data->dlen); /* Case 2 */
+}
+
+/*
+ * OVPUT --
+ * Copy an overflow item onto a page.
+ */
+#undef OVPUT
+#define OVPUT(h, indx, bo) do { \
+ DBT __hdr; \
+ memset(&__hdr, 0, sizeof(__hdr)); \
+ __hdr.data = &bo; \
+ __hdr.size = BOVERFLOW_SIZE; \
+ if ((ret = __db_pitem(dbc, \
+ h, indx, BOVERFLOW_SIZE, &__hdr, NULL)) != 0) \
+ return (ret); \
+} while (0)
+
+/*
+ * __bam_ovput --
+ * Build an overflow item and put it on the page.
+ */
+static int
+__bam_ovput(dbc, h, indx, item)
+ DBC *dbc;
+ PAGE *h;
+ u_int32_t indx;
+ DBT *item;
+{
+ BOVERFLOW bo;
+ int ret;
+
+ UMRW(bo.unused1);
+ B_TSET(bo.type, B_OVERFLOW, 0);
+ UMRW(bo.unused2);
+ if ((ret = __db_poff(dbc, item, &bo.pgno, __bam_new)) != 0)
+ return (ret);
+ bo.tlen = item->size;
+
+ OVPUT(h, indx, bo);
+
+ return (0);
+}
+
+/*
+ * __bam_ritem --
+ * Replace an item on a page.
+ *
+ * PUBLIC: int __bam_ritem __P((DBC *, PAGE *, u_int32_t, DBT *));
+ */
+int
+__bam_ritem(dbc, h, indx, data)
+ DBC *dbc;
+ PAGE *h;
+ u_int32_t indx;
+ DBT *data;
+{
+ BKEYDATA *bk;
+ DB *dbp;
+ DBT orig, repl;
+ db_indx_t cnt, lo, ln, min, off, prefix, suffix;
+ int32_t nbytes;
+ int ret;
+ u_int8_t *p, *t;
+
+ dbp = dbc->dbp;
+
+ /*
+ * Replace a single item onto a page. The logic figuring out where
+ * to insert and whether it fits is handled in the caller. All we do
+ * here is manage the page shuffling.
+ */
+ bk = GET_BKEYDATA(h, indx);
+
+ /* Log the change. */
+ if (DB_LOGGING(dbc)) {
+ /*
+ * We might as well check to see if the two data items share
+ * a common prefix and suffix -- it can save us a lot of log
+ * message if they're large.
+ */
+ min = data->size < bk->len ? data->size : bk->len;
+ for (prefix = 0,
+ p = bk->data, t = data->data;
+ prefix < min && *p == *t; ++prefix, ++p, ++t)
+ ;
+
+ min -= prefix;
+ for (suffix = 0,
+ p = (u_int8_t *)bk->data + bk->len - 1,
+ t = (u_int8_t *)data->data + data->size - 1;
+ suffix < min && *p == *t; ++suffix, --p, --t)
+ ;
+
+ /* We only log the parts of the keys that have changed. */
+ orig.data = (u_int8_t *)bk->data + prefix;
+ orig.size = bk->len - (prefix + suffix);
+ repl.data = (u_int8_t *)data->data + prefix;
+ repl.size = data->size - (prefix + suffix);
+ if ((ret = __bam_repl_log(dbp->dbenv->lg_info, dbc->txn,
+ &LSN(h), 0, dbp->log_fileid, PGNO(h), &LSN(h),
+ (u_int32_t)indx, (u_int32_t)B_DISSET(bk->type),
+ &orig, &repl, (u_int32_t)prefix, (u_int32_t)suffix)) != 0)
+ return (ret);
+ }
+
+ /*
+ * Set references to the first in-use byte on the page and the
+ * first byte of the item being replaced.
+ */
+ p = (u_int8_t *)h + HOFFSET(h);
+ t = (u_int8_t *)bk;
+
+ /*
+ * If the entry is growing in size, shift the beginning of the data
+ * part of the page down. If the entry is shrinking in size, shift
+ * the beginning of the data part of the page up. Use memmove(3),
+ * the regions overlap.
+ */
+ lo = BKEYDATA_SIZE(bk->len);
+ ln = BKEYDATA_SIZE(data->size);
+ if (lo != ln) {
+ nbytes = lo - ln; /* Signed difference. */
+ if (p == t) /* First index is fast. */
+ h->inp[indx] += nbytes;
+ else { /* Else, shift the page. */
+ memmove(p + nbytes, p, t - p);
+
+ /* Adjust the indices' offsets. */
+ off = h->inp[indx];
+ for (cnt = 0; cnt < NUM_ENT(h); ++cnt)
+ if (h->inp[cnt] <= off)
+ h->inp[cnt] += nbytes;
+ }
+
+ /* Clean up the page and adjust the item's reference. */
+ HOFFSET(h) += nbytes;
+ t += nbytes;
+ }
+
+ /* Copy the new item onto the page. */
+ bk = (BKEYDATA *)t;
+ B_TSET(bk->type, B_KEYDATA, 0);
+ bk->len = data->size;
+ memcpy(bk->data, data->data, data->size);
+
+ return (0);
+}
+
+/*
+ * __bam_ndup --
+ * Check to see if the duplicate set at indx should have its own page.
+ * If it should, create it.
+ */
+static int
+__bam_ndup(dbc, h, indx)
+ DBC *dbc;
+ PAGE *h;
+ u_int32_t indx;
+{
+ BKEYDATA *bk;
+ BOVERFLOW bo;
+ DB *dbp;
+ DBT hdr;
+ PAGE *cp;
+ db_indx_t cnt, cpindx, first, sz;
+ int ret;
+
+ dbp = dbc->dbp;
+
+ while (indx > 0 && h->inp[indx] == h->inp[indx - P_INDX])
+ indx -= P_INDX;
+ for (cnt = 0, sz = 0, first = indx;; ++cnt, indx += P_INDX) {
+ if (indx >= NUM_ENT(h) || h->inp[first] != h->inp[indx])
+ break;
+ bk = GET_BKEYDATA(h, indx);
+ sz += B_TYPE(bk->type) == B_KEYDATA ?
+ BKEYDATA_PSIZE(bk->len) : BOVERFLOW_PSIZE;
+ bk = GET_BKEYDATA(h, indx + O_INDX);
+ sz += B_TYPE(bk->type) == B_KEYDATA ?
+ BKEYDATA_PSIZE(bk->len) : BOVERFLOW_PSIZE;
+ }
+
+ /*
+ * If this set of duplicates is using more than 25% of the page, move
+ * them off. The choice of 25% is a WAG, but it has to be small enough
+ * that we can always split regardless of the presence of duplicates.
+ */
+ if (sz < dbp->pgsize / 4)
+ return (0);
+
+ /* Get a new page. */
+ if ((ret = __bam_new(dbc, P_DUPLICATE, &cp)) != 0)
+ return (ret);
+
+ /*
+ * Move this set of duplicates off the page. First points to the first
+ * key of the first duplicate key/data pair, cnt is the number of pairs
+ * we're dealing with.
+ */
+ memset(&hdr, 0, sizeof(hdr));
+ for (indx = first + O_INDX, cpindx = 0;; ++cpindx) {
+ /* Copy the entry to the new page. */
+ bk = GET_BKEYDATA(h, indx);
+ hdr.data = bk;
+ hdr.size = B_TYPE(bk->type) == B_KEYDATA ?
+ BKEYDATA_SIZE(bk->len) : BOVERFLOW_SIZE;
+ if ((ret =
+ __db_pitem(dbc, cp, cpindx, hdr.size, &hdr, NULL)) != 0)
+ goto err;
+
+ /*
+ * Move cursors referencing the old entry to the new entry.
+ * Done after the page put because __db_pitem() adjusts
+ * cursors on the new page, and before the delete because
+ * __db_ditem adjusts cursors on the old page.
+ */
+ __bam_ca_dup(dbp,
+ PGNO(h), first, indx - O_INDX, PGNO(cp), cpindx);
+
+ /* Delete the data item. */
+ if ((ret = __db_ditem(dbc, h, indx, hdr.size)) != 0)
+ goto err;
+
+ /* Delete all but the first reference to the key. */
+ if (--cnt == 0)
+ break;
+ if ((ret = __bam_adjindx(dbc, h, indx, first, 0)) != 0)
+ goto err;
+ }
+
+ /* Put in a new data item that points to the duplicates page. */
+ UMRW(bo.unused1);
+ B_TSET(bo.type, B_DUPLICATE, 0);
+ UMRW(bo.unused2);
+ bo.pgno = cp->pgno;
+ bo.tlen = 0;
+
+ OVPUT(h, indx, bo);
+
+ return (memp_fput(dbp->mpf, cp, DB_MPOOL_DIRTY));
+
+err: (void)__bam_free(dbc, cp);
+ return (ret);
+}
+
+/*
+ * __bam_fixed --
+ * Build the real record for a fixed length put.
+ */
+static int
+__bam_fixed(dbc, dbt)
+ DBC *dbc;
+ DBT *dbt;
+{
+ DB *dbp;
+ RECNO *rp;
+ int ret;
+
+ dbp = dbc->dbp;
+ rp = ((BTREE *)dbp->internal)->recno;
+
+ /*
+ * If database contains fixed-length records, and the record is long,
+ * return EINVAL.
+ */
+ if (dbt->size > rp->re_len)
+ return (EINVAL);
+
+ /*
+ * The caller checked to see if it was just right, so we know it's
+ * short. Pad it out. We use the record data return memory, it's
+ * only a short-term use.
+ */
+ if (dbc->rdata.ulen < rp->re_len) {
+ if ((ret = __os_realloc(&dbc->rdata.data, rp->re_len)) != 0) {
+ dbc->rdata.ulen = 0;
+ dbc->rdata.data = NULL;
+ return (ret);
+ }
+ dbc->rdata.ulen = rp->re_len;
+ }
+ memcpy(dbc->rdata.data, dbt->data, dbt->size);
+ memset((u_int8_t *)dbc->rdata.data + dbt->size,
+ rp->re_pad, rp->re_len - dbt->size);
+
+ /*
+ * Clean up our flags and other information just in case, and
+ * change the caller's DBT to reference our created record.
+ */
+ dbc->rdata.size = rp->re_len;
+ dbc->rdata.dlen = 0;
+ dbc->rdata.doff = 0;
+ dbc->rdata.flags = 0;
+ *dbt = dbc->rdata;
+
+ return (0);
+}
+
+/*
+ * __bam_partial --
+ * Build the real record for a partial put.
+ */
+static int
+__bam_partial(dbc, dbt, h, indx, nbytes, flags)
+ DBC *dbc;
+ DBT *dbt;
+ PAGE *h;
+ u_int32_t indx, nbytes, flags;
+{
+ BKEYDATA *bk, tbk;
+ BOVERFLOW *bo;
+ DB *dbp;
+ DBT copy;
+ u_int32_t len, tlen;
+ u_int8_t *p;
+ int ret;
+
+ COMPQUIET(bo, NULL);
+
+ dbp = dbc->dbp;
+
+ /* We use the record data return memory, it's only a short-term use. */
+ if (dbc->rdata.ulen < nbytes) {
+ if ((ret = __os_realloc(&dbc->rdata.data, nbytes)) != 0) {
+ dbc->rdata.ulen = 0;
+ dbc->rdata.data = NULL;
+ return (ret);
+ }
+ dbc->rdata.ulen = nbytes;
+ }
+
+ /*
+ * We use nul bytes for any part of the record that isn't specified;
+ * get it over with.
+ */
+ memset(dbc->rdata.data, 0, nbytes);
+
+ /*
+ * In the next clauses, we need to do three things: a) set p to point
+ * to the place at which to copy the user's data, b) set tlen to the
+ * total length of the record, not including the bytes contributed by
+ * the user, and c) copy any valid data from an existing record.
+ */
+ if (LF_ISSET(BI_NEWKEY)) {
+ tlen = dbt->doff;
+ p = (u_int8_t *)dbc->rdata.data + dbt->doff;
+ goto ucopy;
+ }
+
+ /* Find the current record. */
+ if (indx < NUM_ENT(h)) {
+ bk = GET_BKEYDATA(h, indx + (TYPE(h) == P_LBTREE ? O_INDX : 0));
+ bo = (BOVERFLOW *)bk;
+ } else {
+ bk = &tbk;
+ B_TSET(bk->type, B_KEYDATA, 0);
+ bk->len = 0;
+ }
+ if (B_TYPE(bk->type) == B_OVERFLOW) {
+ /*
+ * In the case of an overflow record, we shift things around
+ * in the current record rather than allocate a separate copy.
+ */
+ memset(&copy, 0, sizeof(copy));
+ if ((ret = __db_goff(dbp, &copy, bo->tlen,
+ bo->pgno, &dbc->rdata.data, &dbc->rdata.ulen)) != 0)
+ return (ret);
+
+ /* Skip any leading data from the original record. */
+ tlen = dbt->doff;
+ p = (u_int8_t *)dbc->rdata.data + dbt->doff;
+
+ /*
+ * Copy in any trailing data from the original record.
+ *
+ * If the original record was larger than the original offset
+ * plus the bytes being deleted, there is trailing data in the
+ * original record we need to preserve. If we aren't deleting
+ * the same number of bytes as we're inserting, copy it up or
+ * down, into place.
+ *
+ * Use memmove(), the regions may overlap.
+ */
+ if (bo->tlen > dbt->doff + dbt->dlen) {
+ len = bo->tlen - (dbt->doff + dbt->dlen);
+ if (dbt->dlen != dbt->size)
+ memmove(p + dbt->size, p + dbt->dlen, len);
+ tlen += len;
+ }
+ } else {
+ /* Copy in any leading data from the original record. */
+ memcpy(dbc->rdata.data,
+ bk->data, dbt->doff > bk->len ? bk->len : dbt->doff);
+ tlen = dbt->doff;
+ p = (u_int8_t *)dbc->rdata.data + dbt->doff;
+
+ /* Copy in any trailing data from the original record. */
+ len = dbt->doff + dbt->dlen;
+ if (bk->len > len) {
+ memcpy(p + dbt->size, bk->data + len, bk->len - len);
+ tlen += bk->len - len;
+ }
+ }
+
+ucopy: /*
+ * Copy in the application provided data -- p and tlen must have been
+ * initialized above.
+ */
+ memcpy(p, dbt->data, dbt->size);
+ tlen += dbt->size;
+
+ /* Set the DBT to reference our new record. */
+ dbc->rdata.size = tlen;
+ dbc->rdata.dlen = 0;
+ dbc->rdata.doff = 0;
+ dbc->rdata.flags = 0;
+ *dbt = dbc->rdata;
+ return (0);
+}
diff --git a/usr/src/cmd/sendmail/db/btree/bt_rec.c b/usr/src/cmd/sendmail/db/btree/bt_rec.c
new file mode 100644
index 0000000000..de6b3b7d0e
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/btree/bt_rec.c
@@ -0,0 +1,903 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)bt_rec.c 10.28 (Sleepycat) 9/27/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <errno.h>
+#include <string.h>
+#endif
+
+#include "db_int.h"
+#include "db_page.h"
+#include "shqueue.h"
+#include "hash.h"
+#include "btree.h"
+#include "log.h"
+#include "common_ext.h"
+
+/*
+ * __bam_pg_alloc_recover --
+ * Recovery function for pg_alloc.
+ *
+ * PUBLIC: int __bam_pg_alloc_recover
+ * PUBLIC: __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+ */
+int
+__bam_pg_alloc_recover(logp, dbtp, lsnp, redo, info)
+ DB_LOG *logp;
+ DBT *dbtp;
+ DB_LSN *lsnp;
+ int redo;
+ void *info;
+{
+ __bam_pg_alloc_args *argp;
+ BTMETA *meta;
+ DB_MPOOLFILE *mpf;
+ PAGE *pagep;
+ DB *file_dbp;
+ DBC *dbc;
+ db_pgno_t pgno;
+ int cmp_n, cmp_p, modified, ret;
+
+ REC_PRINT(__bam_pg_alloc_print);
+ REC_INTRO(__bam_pg_alloc_read);
+
+ /*
+ * Fix up the allocated page. If we're redoing the operation, we have
+ * to get the page (creating it if it doesn't exist), and update its
+ * LSN. If we're undoing the operation, we have to reset the page's
+ * LSN and put it on the free list.
+ *
+ * Fix up the metadata page. If we're redoing the operation, we have
+ * to get the metadata page and update its LSN and its free pointer.
+ * If we're undoing the operation and the page was ever created, we put
+ * it on the freelist.
+ */
+ pgno = PGNO_METADATA;
+ if ((ret = memp_fget(mpf, &pgno, 0, &meta)) != 0) {
+ /* The metadata page must always exist. */
+ (void)__db_pgerr(file_dbp, pgno);
+ goto out;
+ }
+ if ((ret = memp_fget(mpf, &argp->pgno, DB_MPOOL_CREATE, &pagep)) != 0) {
+ /*
+ * We specify creation and check for it later, because this
+ * operation was supposed to create the page, and even in
+ * the undo case it's going to get linked onto the freelist
+ * which we're also fixing up.
+ */
+ (void)__db_pgerr(file_dbp, argp->pgno);
+ (void)memp_fput(mpf, meta, 0);
+ goto out;
+ }
+
+ /* Fix up the allocated page. */
+ modified = 0;
+ cmp_n = log_compare(lsnp, &LSN(pagep));
+ cmp_p = log_compare(&LSN(pagep), &argp->page_lsn);
+ if (cmp_p == 0 && redo) {
+ /* Need to redo update described. */
+ P_INIT(pagep, file_dbp->pgsize,
+ argp->pgno, PGNO_INVALID, PGNO_INVALID, 0, argp->ptype);
+
+ pagep->lsn = *lsnp;
+ modified = 1;
+ } else if (cmp_n == 0 && !redo) {
+ /* Need to undo update described. */
+ P_INIT(pagep, file_dbp->pgsize,
+ argp->pgno, PGNO_INVALID, meta->free, 0, P_INVALID);
+
+ pagep->lsn = argp->page_lsn;
+ modified = 1;
+ }
+ if ((ret = memp_fput(mpf, pagep, modified ? DB_MPOOL_DIRTY : 0)) != 0) {
+ (void)memp_fput(mpf, meta, 0);
+ goto out;
+ }
+
+ /* Fix up the metadata page. */
+ modified = 0;
+ cmp_n = log_compare(lsnp, &LSN(meta));
+ cmp_p = log_compare(&LSN(meta), &argp->meta_lsn);
+ if (cmp_p == 0 && redo) {
+ /* Need to redo update described. */
+ meta->lsn = *lsnp;
+ meta->free = argp->next;
+ modified = 1;
+ } else if (cmp_n == 0 && !redo) {
+ /* Need to undo update described. */
+ meta->lsn = argp->meta_lsn;
+ meta->free = argp->pgno;
+ modified = 1;
+ }
+ if ((ret = memp_fput(mpf, meta, modified ? DB_MPOOL_DIRTY : 0)) != 0)
+ goto out;
+
+done: *lsnp = argp->prev_lsn;
+ ret = 0;
+
+out: REC_CLOSE;
+}
+
+/*
+ * __bam_pg_free_recover --
+ * Recovery function for pg_free.
+ *
+ * PUBLIC: int __bam_pg_free_recover
+ * PUBLIC: __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+ */
+int
+__bam_pg_free_recover(logp, dbtp, lsnp, redo, info)
+ DB_LOG *logp;
+ DBT *dbtp;
+ DB_LSN *lsnp;
+ int redo;
+ void *info;
+{
+ __bam_pg_free_args *argp;
+ BTMETA *meta;
+ DB *file_dbp;
+ DBC *dbc;
+ DB_MPOOLFILE *mpf;
+ PAGE *pagep;
+ db_pgno_t pgno;
+ int cmp_n, cmp_p, modified, ret;
+
+ REC_PRINT(__bam_pg_free_print);
+ REC_INTRO(__bam_pg_free_read);
+
+ /*
+ * Fix up the freed page. If we're redoing the operation we get the
+ * page and explicitly discard its contents, then update its LSN. If
+ * we're undoing the operation, we get the page and restore its header.
+ */
+ if ((ret = memp_fget(mpf, &argp->pgno, 0, &pagep)) != 0) {
+ /*
+ * We don't automatically create the page. The only way the
+ * page might not exist is if the alloc never happened, and
+ * the only way the alloc might never have happened is if we
+ * are undoing, in which case there's no reason to create the
+ * page.
+ */
+ if (!redo)
+ goto done;
+ (void)__db_pgerr(file_dbp, argp->pgno);
+ goto out;
+ }
+ modified = 0;
+ cmp_n = log_compare(lsnp, &LSN(pagep));
+ cmp_p = log_compare(&LSN(pagep), &LSN(argp->header.data));
+ if (cmp_p == 0 && redo) {
+ /* Need to redo update described. */
+ P_INIT(pagep, file_dbp->pgsize,
+ pagep->pgno, PGNO_INVALID, argp->next, 0, P_INVALID);
+ pagep->lsn = *lsnp;
+
+ modified = 1;
+ } else if (cmp_n == 0 && !redo) {
+ /* Need to undo update described. */
+ memcpy(pagep, argp->header.data, argp->header.size);
+
+ modified = 1;
+ }
+ if ((ret = memp_fput(mpf, pagep, modified ? DB_MPOOL_DIRTY : 0)) != 0)
+ goto out;
+
+ /*
+ * Fix up the metadata page. If we're redoing or undoing the operation
+ * we get the page and update its LSN and free pointer.
+ */
+ pgno = PGNO_METADATA;
+ if ((ret = memp_fget(mpf, &pgno, 0, &meta)) != 0) {
+ /* The metadata page must always exist. */
+ (void)__db_pgerr(file_dbp, pgno);
+ goto out;
+ }
+
+ modified = 0;
+ cmp_n = log_compare(lsnp, &LSN(meta));
+ cmp_p = log_compare(&LSN(meta), &argp->meta_lsn);
+ if (cmp_p == 0 && redo) {
+ /* Need to redo update described. */
+ meta->free = argp->pgno;
+
+ meta->lsn = *lsnp;
+ modified = 1;
+ } else if (cmp_n == 0 && !redo) {
+ /* Need to undo update described. */
+ meta->free = argp->next;
+
+ meta->lsn = argp->meta_lsn;
+ modified = 1;
+ }
+ if ((ret = memp_fput(mpf, meta, modified ? DB_MPOOL_DIRTY : 0)) != 0)
+ goto out;
+
+done: *lsnp = argp->prev_lsn;
+ ret = 0;
+
+out: REC_CLOSE;
+}
+
+/*
+ * __bam_split_recover --
+ * Recovery function for split.
+ *
+ * PUBLIC: int __bam_split_recover
+ * PUBLIC: __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+ */
+int
+__bam_split_recover(logp, dbtp, lsnp, redo, info)
+ DB_LOG *logp;
+ DBT *dbtp;
+ DB_LSN *lsnp;
+ int redo;
+ void *info;
+{
+ __bam_split_args *argp;
+ DB *file_dbp;
+ DBC *dbc;
+ DB_MPOOLFILE *mpf;
+ PAGE *_lp, *lp, *np, *pp, *_rp, *rp, *sp;
+ db_pgno_t pgno;
+ int l_update, p_update, r_update, ret, rootsplit, t_ret;
+
+ REC_PRINT(__bam_split_print);
+
+ mpf = NULL;
+ _lp = lp = np = pp = _rp = rp = NULL;
+
+ REC_INTRO(__bam_split_read);
+
+ /*
+ * There are two kinds of splits that we have to recover from. The
+ * first is a root-page split, where the root page is split from a
+ * leaf page into an internal page and two new leaf pages are created.
+ * The second is where a page is split into two pages, and a new key
+ * is inserted into the parent page.
+ */
+ sp = argp->pg.data;
+ pgno = PGNO(sp);
+ rootsplit = pgno == PGNO_ROOT;
+ if (memp_fget(mpf, &argp->left, 0, &lp) != 0)
+ lp = NULL;
+ if (memp_fget(mpf, &argp->right, 0, &rp) != 0)
+ rp = NULL;
+
+ if (redo) {
+ l_update = r_update = p_update = 0;
+ /*
+ * Decide if we need to resplit the page.
+ *
+ * If this is a root split, then the root has to exist, it's
+ * the page we're splitting and it gets modified. If this is
+ * not a root split, then the left page has to exist, for the
+ * same reason.
+ */
+ if (rootsplit) {
+ if ((ret = memp_fget(mpf, &pgno, 0, &pp)) != 0) {
+ (void)__db_pgerr(file_dbp, pgno);
+ pp = NULL;
+ goto out;
+ }
+ p_update =
+ log_compare(&LSN(pp), &LSN(argp->pg.data)) == 0;
+ } else
+ if (lp == NULL) {
+ (void)__db_pgerr(file_dbp, argp->left);
+ goto out;
+ }
+ if (lp == NULL || log_compare(&LSN(lp), &argp->llsn) == 0)
+ l_update = 1;
+ if (rp == NULL || log_compare(&LSN(rp), &argp->rlsn) == 0)
+ r_update = 1;
+ if (!p_update && !l_update && !r_update)
+ goto done;
+
+ /* Allocate and initialize new left/right child pages. */
+ if ((ret = __os_malloc(file_dbp->pgsize, NULL, &_lp)) != 0 ||
+ (ret = __os_malloc(file_dbp->pgsize, NULL, &_rp)) != 0)
+ goto out;
+ if (rootsplit) {
+ P_INIT(_lp, file_dbp->pgsize, argp->left,
+ PGNO_INVALID,
+ ISINTERNAL(sp) ? PGNO_INVALID : argp->right,
+ LEVEL(sp), TYPE(sp));
+ P_INIT(_rp, file_dbp->pgsize, argp->right,
+ ISINTERNAL(sp) ? PGNO_INVALID : argp->left,
+ PGNO_INVALID, LEVEL(sp), TYPE(sp));
+ } else {
+ P_INIT(_lp, file_dbp->pgsize, PGNO(sp),
+ ISINTERNAL(sp) ? PGNO_INVALID : PREV_PGNO(sp),
+ ISINTERNAL(sp) ? PGNO_INVALID : argp->right,
+ LEVEL(sp), TYPE(sp));
+ P_INIT(_rp, file_dbp->pgsize, argp->right,
+ ISINTERNAL(sp) ? PGNO_INVALID : sp->pgno,
+ ISINTERNAL(sp) ? PGNO_INVALID : NEXT_PGNO(sp),
+ LEVEL(sp), TYPE(sp));
+ }
+
+ /* Split the page. */
+ if ((ret = __bam_copy(file_dbp, sp, _lp, 0, argp->indx)) != 0 ||
+ (ret = __bam_copy(file_dbp, sp, _rp, argp->indx,
+ NUM_ENT(sp))) != 0)
+ goto out;
+
+ /* If the left child is wrong, update it. */
+ if (lp == NULL && (ret =
+ memp_fget(mpf, &argp->left, DB_MPOOL_CREATE, &lp)) != 0) {
+ (void)__db_pgerr(file_dbp, argp->left);
+ lp = NULL;
+ goto out;
+ }
+ if (l_update) {
+ memcpy(lp, _lp, file_dbp->pgsize);
+ lp->lsn = *lsnp;
+ if ((ret = memp_fput(mpf, lp, DB_MPOOL_DIRTY)) != 0)
+ goto out;
+ lp = NULL;
+ }
+
+ /* If the right child is wrong, update it. */
+ if (rp == NULL && (ret = memp_fget(mpf,
+ &argp->right, DB_MPOOL_CREATE, &rp)) != 0) {
+ (void)__db_pgerr(file_dbp, argp->right);
+ rp = NULL;
+ goto out;
+ }
+ if (r_update) {
+ memcpy(rp, _rp, file_dbp->pgsize);
+ rp->lsn = *lsnp;
+ if ((ret = memp_fput(mpf, rp, DB_MPOOL_DIRTY)) != 0)
+ goto out;
+ rp = NULL;
+ }
+
+ /*
+ * If the parent page is wrong, update it. This is of interest
+ * only if it was a root split, since root splits create parent
+ * pages. All other splits modify a parent page, but those are
+ * separately logged and recovered.
+ */
+ if (rootsplit && p_update) {
+ if (file_dbp->type == DB_BTREE)
+ P_INIT(pp, file_dbp->pgsize,
+ PGNO_ROOT, PGNO_INVALID, PGNO_INVALID,
+ _lp->level + 1, P_IBTREE);
+ else
+ P_INIT(pp, file_dbp->pgsize,
+ PGNO_ROOT, PGNO_INVALID, PGNO_INVALID,
+ _lp->level + 1, P_IRECNO);
+ RE_NREC_SET(pp,
+ file_dbp->type == DB_RECNO ||
+ F_ISSET(file_dbp, DB_BT_RECNUM) ?
+ __bam_total(_lp) + __bam_total(_rp) : 0);
+ pp->lsn = *lsnp;
+ if ((ret = memp_fput(mpf, pp, DB_MPOOL_DIRTY)) != 0)
+ goto out;
+ pp = NULL;
+ }
+
+ /*
+ * Finally, redo the next-page link if necessary. This is of
+ * interest only if it wasn't a root split -- inserting a new
+ * page in the tree requires that any following page have its
+ * previous-page pointer updated to our new page. The next
+ * page must exist because we're redoing the operation.
+ */
+ if (!rootsplit && !IS_ZERO_LSN(argp->nlsn)) {
+ if ((ret = memp_fget(mpf, &argp->npgno, 0, &np)) != 0) {
+ (void)__db_pgerr(file_dbp, argp->npgno);
+ np = NULL;
+ goto out;
+ }
+ if (log_compare(&LSN(np), &argp->nlsn) == 0) {
+ PREV_PGNO(np) = argp->right;
+ np->lsn = *lsnp;
+ if ((ret =
+ memp_fput(mpf, np, DB_MPOOL_DIRTY)) != 0)
+ goto out;
+ np = NULL;
+ }
+ }
+ } else {
+ /*
+ * If the split page is wrong, replace its contents with the
+ * logged page contents. If the page doesn't exist, it means
+ * that the create of the page never happened, nor did any of
+ * the adds onto the page that caused the split, and there's
+ * really no undo-ing to be done.
+ */
+ if ((ret = memp_fget(mpf, &pgno, 0, &pp)) != 0) {
+ pp = NULL;
+ goto lrundo;
+ }
+ if (log_compare(lsnp, &LSN(pp)) == 0) {
+ memcpy(pp, argp->pg.data, argp->pg.size);
+ if ((ret = memp_fput(mpf, pp, DB_MPOOL_DIRTY)) != 0)
+ goto out;
+ pp = NULL;
+ }
+
+ /*
+ * If it's a root split and the left child ever existed, update
+ * its LSN. (If it's not a root split, we've updated the left
+ * page already -- it's the same as the split page.) If the
+ * right child ever existed, root split or not, update its LSN.
+ * The undo of the page allocation(s) will restore them to the
+ * free list.
+ */
+lrundo: if ((rootsplit && lp != NULL) || rp != NULL) {
+ if (rootsplit && lp != NULL &&
+ log_compare(lsnp, &LSN(lp)) == 0) {
+ lp->lsn = argp->llsn;
+ if ((ret =
+ memp_fput(mpf, lp, DB_MPOOL_DIRTY)) != 0)
+ goto out;
+ lp = NULL;
+ }
+ if (rp != NULL &&
+ log_compare(lsnp, &LSN(rp)) == 0) {
+ rp->lsn = argp->rlsn;
+ if ((ret =
+ memp_fput(mpf, rp, DB_MPOOL_DIRTY)) != 0)
+ goto out;
+ rp = NULL;
+ }
+ }
+
+ /*
+ * Finally, undo the next-page link if necessary. This is of
+ * interest only if it wasn't a root split -- inserting a new
+ * page in the tree requires that any following page have its
+ * previous-page pointer updated to our new page. Since it's
+ * possible that the next-page never existed, we ignore it as
+ * if there's nothing to undo.
+ */
+ if (!rootsplit && !IS_ZERO_LSN(argp->nlsn)) {
+ if ((ret = memp_fget(mpf, &argp->npgno, 0, &np)) != 0) {
+ np = NULL;
+ goto done;
+ }
+ if (log_compare(lsnp, &LSN(np)) == 0) {
+ PREV_PGNO(np) = argp->left;
+ np->lsn = argp->nlsn;
+ if (memp_fput(mpf, np, DB_MPOOL_DIRTY))
+ goto out;
+ np = NULL;
+ }
+ }
+ }
+
+done: *lsnp = argp->prev_lsn;
+ ret = 0;
+
+out: /* Free any pages that weren't dirtied. */
+ if (pp != NULL && (t_ret = memp_fput(mpf, pp, 0)) != 0 && ret == 0)
+ ret = t_ret;
+ if (lp != NULL && (t_ret = memp_fput(mpf, lp, 0)) != 0 && ret == 0)
+ ret = t_ret;
+ if (np != NULL && (t_ret = memp_fput(mpf, np, 0)) != 0 && ret == 0)
+ ret = t_ret;
+ if (rp != NULL && (t_ret = memp_fput(mpf, rp, 0)) != 0 && ret == 0)
+ ret = t_ret;
+
+ /* Free any allocated space. */
+ if (_lp != NULL)
+ __os_free(_lp, file_dbp->pgsize);
+ if (_rp != NULL)
+ __os_free(_rp, file_dbp->pgsize);
+
+ REC_CLOSE;
+}
+
+/*
+ * __bam_rsplit_recover --
+ * Recovery function for a reverse split.
+ *
+ * PUBLIC: int __bam_rsplit_recover
+ * PUBLIC: __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+ */
+int
+__bam_rsplit_recover(logp, dbtp, lsnp, redo, info)
+ DB_LOG *logp;
+ DBT *dbtp;
+ DB_LSN *lsnp;
+ int redo;
+ void *info;
+{
+ __bam_rsplit_args *argp;
+ DB *file_dbp;
+ DBC *dbc;
+ DB_MPOOLFILE *mpf;
+ PAGE *pagep;
+ db_pgno_t pgno;
+ int cmp_n, cmp_p, modified, ret;
+
+ REC_PRINT(__bam_rsplit_print);
+ REC_INTRO(__bam_rsplit_read);
+
+ /* Fix the root page. */
+ pgno = PGNO_ROOT;
+ if ((ret = memp_fget(mpf, &pgno, 0, &pagep)) != 0) {
+ /* The root page must always exist. */
+ __db_pgerr(file_dbp, pgno);
+ goto out;
+ }
+ modified = 0;
+ cmp_n = log_compare(lsnp, &LSN(pagep));
+ cmp_p = log_compare(&LSN(pagep), &argp->rootlsn);
+ if (cmp_p == 0 && redo) {
+ /* Need to redo update described. */
+ memcpy(pagep, argp->pgdbt.data, argp->pgdbt.size);
+ pagep->pgno = PGNO_ROOT;
+ pagep->lsn = *lsnp;
+ modified = 1;
+ } else if (cmp_n == 0 && !redo) {
+ /* Need to undo update described. */
+ P_INIT(pagep, file_dbp->pgsize, PGNO_ROOT,
+ argp->nrec, PGNO_INVALID, pagep->level + 1,
+ file_dbp->type == DB_BTREE ? P_IBTREE : P_IRECNO);
+ if ((ret = __db_pitem(dbc, pagep, 0,
+ argp->rootent.size, &argp->rootent, NULL)) != 0)
+ goto out;
+ pagep->lsn = argp->rootlsn;
+ modified = 1;
+ }
+ if ((ret = memp_fput(mpf, pagep, modified ? DB_MPOOL_DIRTY : 0)) != 0)
+ goto out;
+
+ /*
+ * Fix the page copied over the root page. It's possible that the
+ * page never made it to disk, so if we're undo-ing and the page
+ * doesn't exist, it's okay and there's nothing further to do.
+ */
+ if ((ret = memp_fget(mpf, &argp->pgno, 0, &pagep)) != 0) {
+ if (!redo)
+ goto done;
+ (void)__db_pgerr(file_dbp, argp->pgno);
+ goto out;
+ }
+ modified = 0;
+ cmp_n = log_compare(lsnp, &LSN(pagep));
+ cmp_p = log_compare(&LSN(pagep), &LSN(argp->pgdbt.data));
+ if (cmp_p == 0 && redo) {
+ /* Need to redo update described. */
+ pagep->lsn = *lsnp;
+ modified = 1;
+ } else if (cmp_n == 0 && !redo) {
+ /* Need to undo update described. */
+ memcpy(pagep, argp->pgdbt.data, argp->pgdbt.size);
+ modified = 1;
+ }
+ if ((ret = memp_fput(mpf, pagep, modified ? DB_MPOOL_DIRTY : 0)) != 0)
+ goto out;
+
+done: *lsnp = argp->prev_lsn;
+ ret = 0;
+
+out: REC_CLOSE;
+}
+
+/*
+ * __bam_adj_recover --
+ * Recovery function for adj.
+ *
+ * PUBLIC: int __bam_adj_recover
+ * PUBLIC: __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+ */
+int
+__bam_adj_recover(logp, dbtp, lsnp, redo, info)
+ DB_LOG *logp;
+ DBT *dbtp;
+ DB_LSN *lsnp;
+ int redo;
+ void *info;
+{
+ __bam_adj_args *argp;
+ DB *file_dbp;
+ DBC *dbc;
+ DB_MPOOLFILE *mpf;
+ PAGE *pagep;
+ int cmp_n, cmp_p, modified, ret;
+
+ REC_PRINT(__bam_adj_print);
+ REC_INTRO(__bam_adj_read);
+
+ /* Get the page; if it never existed and we're undoing, we're done. */
+ if ((ret = memp_fget(mpf, &argp->pgno, 0, &pagep)) != 0) {
+ if (!redo)
+ goto done;
+ (void)__db_pgerr(file_dbp, argp->pgno);
+ goto out;
+ }
+
+ modified = 0;
+ cmp_n = log_compare(lsnp, &LSN(pagep));
+ cmp_p = log_compare(&LSN(pagep), &argp->lsn);
+ if (cmp_p == 0 && redo) {
+ /* Need to redo update described. */
+ if ((ret = __bam_adjindx(dbc,
+ pagep, argp->indx, argp->indx_copy, argp->is_insert)) != 0)
+ goto err;
+
+ LSN(pagep) = *lsnp;
+ modified = 1;
+ } else if (cmp_n == 0 && !redo) {
+ /* Need to undo update described. */
+ if ((ret = __bam_adjindx(dbc,
+ pagep, argp->indx, argp->indx_copy, !argp->is_insert)) != 0)
+ goto err;
+
+ LSN(pagep) = argp->lsn;
+ modified = 1;
+ }
+ if ((ret = memp_fput(mpf, pagep, modified ? DB_MPOOL_DIRTY : 0)) != 0)
+ goto out;
+
+done: *lsnp = argp->prev_lsn;
+ ret = 0;
+
+ if (0) {
+err: (void)memp_fput(mpf, pagep, 0);
+ }
+out: REC_CLOSE;
+}
+
+/*
+ * __bam_cadjust_recover --
+ * Recovery function for the adjust of a count change in an internal
+ * page.
+ *
+ * PUBLIC: int __bam_cadjust_recover
+ * PUBLIC: __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+ */
+int
+__bam_cadjust_recover(logp, dbtp, lsnp, redo, info)
+ DB_LOG *logp;
+ DBT *dbtp;
+ DB_LSN *lsnp;
+ int redo;
+ void *info;
+{
+ __bam_cadjust_args *argp;
+ DB *file_dbp;
+ DBC *dbc;
+ DB_MPOOLFILE *mpf;
+ PAGE *pagep;
+ int cmp_n, cmp_p, modified, ret;
+
+ REC_PRINT(__bam_cadjust_print);
+ REC_INTRO(__bam_cadjust_read);
+
+ /* Get the page; if it never existed and we're undoing, we're done. */
+ if ((ret = memp_fget(mpf, &argp->pgno, 0, &pagep)) != 0) {
+ if (!redo)
+ goto done;
+ (void)__db_pgerr(file_dbp, argp->pgno);
+ goto out;
+ }
+
+ modified = 0;
+ cmp_n = log_compare(lsnp, &LSN(pagep));
+ cmp_p = log_compare(&LSN(pagep), &argp->lsn);
+ if (cmp_p == 0 && redo) {
+ /* Need to redo update described. */
+ if (file_dbp->type == DB_BTREE &&
+ F_ISSET(file_dbp, DB_BT_RECNUM)) {
+ GET_BINTERNAL(pagep, argp->indx)->nrecs += argp->adjust;
+ if (argp->total && PGNO(pagep) == PGNO_ROOT)
+ RE_NREC_ADJ(pagep, argp->adjust);
+ }
+ if (file_dbp->type == DB_RECNO) {
+ GET_RINTERNAL(pagep, argp->indx)->nrecs += argp->adjust;
+ if (argp->total && PGNO(pagep) == PGNO_ROOT)
+ RE_NREC_ADJ(pagep, argp->adjust);
+ }
+
+ LSN(pagep) = *lsnp;
+ modified = 1;
+ } else if (cmp_n == 0 && !redo) {
+ /* Need to undo update described. */
+ if (file_dbp->type == DB_BTREE &&
+ F_ISSET(file_dbp, DB_BT_RECNUM)) {
+ GET_BINTERNAL(pagep, argp->indx)->nrecs -= argp->adjust;
+ if (argp->total && PGNO(pagep) == PGNO_ROOT)
+ RE_NREC_ADJ(pagep, argp->adjust);
+ }
+ if (file_dbp->type == DB_RECNO) {
+ GET_RINTERNAL(pagep, argp->indx)->nrecs -= argp->adjust;
+ if (argp->total && PGNO(pagep) == PGNO_ROOT)
+ RE_NREC_ADJ(pagep, -(argp->adjust));
+ }
+ LSN(pagep) = argp->lsn;
+ modified = 1;
+ }
+ if ((ret = memp_fput(mpf, pagep, modified ? DB_MPOOL_DIRTY : 0)) != 0)
+ goto out;
+
+done: *lsnp = argp->prev_lsn;
+ ret = 0;
+
+out: REC_CLOSE;
+}
+
+/*
+ * __bam_cdel_recover --
+ * Recovery function for the intent-to-delete of a cursor record.
+ *
+ * PUBLIC: int __bam_cdel_recover
+ * PUBLIC: __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+ */
+int
+__bam_cdel_recover(logp, dbtp, lsnp, redo, info)
+ DB_LOG *logp;
+ DBT *dbtp;
+ DB_LSN *lsnp;
+ int redo;
+ void *info;
+{
+ __bam_cdel_args *argp;
+ DB *file_dbp;
+ DBC *dbc;
+ DB_MPOOLFILE *mpf;
+ PAGE *pagep;
+ int cmp_n, cmp_p, modified, ret;
+
+ REC_PRINT(__bam_cdel_print);
+ REC_INTRO(__bam_cdel_read);
+
+ /* Get the page; if it never existed and we're undoing, we're done. */
+ if ((ret = memp_fget(mpf, &argp->pgno, 0, &pagep)) != 0) {
+ if (!redo)
+ goto done;
+ (void)__db_pgerr(file_dbp, argp->pgno);
+ goto out;
+ }
+
+ modified = 0;
+ cmp_n = log_compare(lsnp, &LSN(pagep));
+ cmp_p = log_compare(&LSN(pagep), &argp->lsn);
+ if (cmp_p == 0 && redo) {
+ /* Need to redo update described. */
+ if (pagep->type == P_DUPLICATE)
+ B_DSET(GET_BKEYDATA(pagep, argp->indx)->type);
+ else
+ B_DSET(GET_BKEYDATA(pagep, argp->indx + O_INDX)->type);
+
+ LSN(pagep) = *lsnp;
+ modified = 1;
+ } else if (cmp_n == 0 && !redo) {
+ /* Need to undo update described. */
+ if (pagep->type == P_DUPLICATE)
+ B_DCLR(GET_BKEYDATA(pagep, argp->indx)->type);
+ else
+ B_DCLR(GET_BKEYDATA(pagep, argp->indx + O_INDX)->type);
+
+ LSN(pagep) = argp->lsn;
+ modified = 1;
+ }
+ if ((ret = memp_fput(mpf, pagep, modified ? DB_MPOOL_DIRTY : 0)) != 0)
+ goto out;
+
+done: *lsnp = argp->prev_lsn;
+ ret = 0;
+
+out: REC_CLOSE;
+}
+
+/*
+ * __bam_repl_recover --
+ * Recovery function for page item replacement.
+ *
+ * PUBLIC: int __bam_repl_recover
+ * PUBLIC: __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+ */
+int
+__bam_repl_recover(logp, dbtp, lsnp, redo, info)
+ DB_LOG *logp;
+ DBT *dbtp;
+ DB_LSN *lsnp;
+ int redo;
+ void *info;
+{
+ __bam_repl_args *argp;
+ BKEYDATA *bk;
+ DB *file_dbp;
+ DBC *dbc;
+ DBT dbt;
+ DB_MPOOLFILE *mpf;
+ PAGE *pagep;
+ int cmp_n, cmp_p, modified, ret;
+ u_int8_t *p;
+
+ REC_PRINT(__bam_repl_print);
+ REC_INTRO(__bam_repl_read);
+
+ /* Get the page; if it never existed and we're undoing, we're done. */
+ if ((ret = memp_fget(mpf, &argp->pgno, 0, &pagep)) != 0) {
+ if (!redo)
+ goto done;
+ (void)__db_pgerr(file_dbp, argp->pgno);
+ goto out;
+ }
+ bk = GET_BKEYDATA(pagep, argp->indx);
+
+ modified = 0;
+ cmp_n = log_compare(lsnp, &LSN(pagep));
+ cmp_p = log_compare(&LSN(pagep), &argp->lsn);
+ if (cmp_p == 0 && redo) {
+ /*
+ * Need to redo update described.
+ *
+ * Re-build the replacement item.
+ */
+ memset(&dbt, 0, sizeof(dbt));
+ dbt.size = argp->prefix + argp->suffix + argp->repl.size;
+ if ((ret = __os_malloc(dbt.size, NULL, &dbt.data)) != 0)
+ goto err;
+ p = dbt.data;
+ memcpy(p, bk->data, argp->prefix);
+ p += argp->prefix;
+ memcpy(p, argp->repl.data, argp->repl.size);
+ p += argp->repl.size;
+ memcpy(p, bk->data + (bk->len - argp->suffix), argp->suffix);
+
+ ret = __bam_ritem(dbc, pagep, argp->indx, &dbt);
+ __os_free(dbt.data, dbt.size);
+ if (ret != 0)
+ goto err;
+
+ LSN(pagep) = *lsnp;
+ modified = 1;
+ } else if (cmp_n == 0 && !redo) {
+ /*
+ * Need to undo update described.
+ *
+ * Re-build the original item.
+ */
+ memset(&dbt, 0, sizeof(dbt));
+ dbt.size = argp->prefix + argp->suffix + argp->orig.size;
+ if ((ret = __os_malloc(dbt.size, NULL, &dbt.data)) != 0)
+ goto err;
+ p = dbt.data;
+ memcpy(p, bk->data, argp->prefix);
+ p += argp->prefix;
+ memcpy(p, argp->orig.data, argp->orig.size);
+ p += argp->orig.size;
+ memcpy(p, bk->data + (bk->len - argp->suffix), argp->suffix);
+
+ ret = __bam_ritem(dbc, pagep, argp->indx, &dbt);
+ __os_free(dbt.data, dbt.size);
+ if (ret != 0)
+ goto err;
+
+ /* Reset the deleted flag, if necessary. */
+ if (argp->isdeleted)
+ B_DSET(GET_BKEYDATA(pagep, argp->indx)->type);
+
+ LSN(pagep) = argp->lsn;
+ modified = 1;
+ }
+ if ((ret = memp_fput(mpf, pagep, modified ? DB_MPOOL_DIRTY : 0)) != 0)
+ goto out;
+
+done: *lsnp = argp->prev_lsn;
+ ret = 0;
+
+ if (0) {
+err: (void)memp_fput(mpf, pagep, 0);
+ }
+out: REC_CLOSE;
+}
diff --git a/usr/src/cmd/sendmail/db/btree/bt_recno.c b/usr/src/cmd/sendmail/db/btree/bt_recno.c
new file mode 100644
index 0000000000..c69877ff7f
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/btree/bt_recno.c
@@ -0,0 +1,1356 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)bt_recno.c 10.53 (Sleepycat) 12/11/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <errno.h>
+#include <limits.h>
+#include <string.h>
+#endif
+
+#include "db_int.h"
+#include "db_page.h"
+#include "btree.h"
+#include "db_ext.h"
+#include "shqueue.h"
+#include "db_shash.h"
+#include "lock.h"
+#include "lock_ext.h"
+
+static int __ram_add __P((DBC *, db_recno_t *, DBT *, u_int32_t, u_int32_t));
+static int __ram_delete __P((DB *, DB_TXN *, DBT *, u_int32_t));
+static int __ram_fmap __P((DBC *, db_recno_t));
+static int __ram_i_delete __P((DBC *));
+static int __ram_put __P((DB *, DB_TXN *, DBT *, DBT *, u_int32_t));
+static int __ram_source __P((DB *, RECNO *, const char *));
+static int __ram_sync __P((DB *, u_int32_t));
+static int __ram_update __P((DBC *, db_recno_t, int));
+static int __ram_vmap __P((DBC *, db_recno_t));
+static int __ram_writeback __P((DBC *));
+
+/*
+ * In recno, there are two meanings to the on-page "deleted" flag. If we're
+ * re-numbering records, it means the record was implicitly created. We skip
+ * over implicitly created records if doing a cursor "next" or "prev", and
+ * return DB_KEYEMPTY if they're explicitly requested.. If not re-numbering
+ * records, it means that the record was implicitly created, or was deleted.
+ * We skip over implicitly created or deleted records if doing a cursor "next"
+ * or "prev", and return DB_KEYEMPTY if they're explicitly requested.
+ *
+ * If we're re-numbering records, then we have to detect in the cursor that
+ * a record was deleted, and adjust the cursor as necessary on the next get.
+ * If we're not re-numbering records, then we can detect that a record has
+ * been deleted by looking at the actual on-page record, so we completely
+ * ignore the cursor's delete flag. This is different from the B+tree code.
+ * It also maintains whether the cursor references a deleted record in the
+ * cursor, and it doesn't always check the on-page value.
+ */
+#define CD_SET(dbp, cp) { \
+ if (F_ISSET(dbp, DB_RE_RENUMBER)) \
+ F_SET(cp, C_DELETED); \
+}
+#define CD_CLR(dbp, cp) { \
+ if (F_ISSET(dbp, DB_RE_RENUMBER)) \
+ F_CLR(cp, C_DELETED); \
+}
+#define CD_ISSET(dbp, cp) \
+ (F_ISSET(dbp, DB_RE_RENUMBER) && F_ISSET(cp, C_DELETED))
+
+/*
+ * __ram_open --
+ * Recno open function.
+ *
+ * PUBLIC: int __ram_open __P((DB *, DB_INFO *));
+ */
+int
+__ram_open(dbp, dbinfo)
+ DB *dbp;
+ DB_INFO *dbinfo;
+{
+ BTREE *t;
+ DBC *dbc;
+ RECNO *rp;
+ int ret, t_ret;
+
+ /* Allocate and initialize the private btree structure. */
+ if ((ret = __os_calloc(1, sizeof(BTREE), &t)) != 0)
+ return (ret);
+ dbp->internal = t;
+ __bam_setovflsize(dbp);
+
+ /* Allocate and initialize the private recno structure. */
+ if ((ret = __os_calloc(1, sizeof(*rp), &rp)) != 0)
+ return (ret);
+ /* Link in the private recno structure. */
+ t->recno = rp;
+
+ /*
+ * Intention is to make sure all of the user's selections are okay
+ * here and then use them without checking.
+ */
+ if (dbinfo == NULL) {
+ rp->re_delim = '\n';
+ rp->re_pad = ' ';
+ rp->re_fd = -1;
+ F_SET(rp, RECNO_EOF);
+ } else {
+ /*
+ * If the user specified a source tree, open it and map it in.
+ *
+ * !!!
+ * We don't complain if the user specified transactions or
+ * threads. It's possible to make it work, but you'd better
+ * know what you're doing!
+ */
+ if (dbinfo->re_source == NULL) {
+ rp->re_fd = -1;
+ F_SET(rp, RECNO_EOF);
+ } else {
+ if ((ret =
+ __ram_source(dbp, rp, dbinfo->re_source)) != 0)
+ goto err;
+ }
+
+ /* Copy delimiter, length and padding values. */
+ rp->re_delim =
+ F_ISSET(dbp, DB_RE_DELIMITER) ? dbinfo->re_delim : '\n';
+ rp->re_pad = F_ISSET(dbp, DB_RE_PAD) ? dbinfo->re_pad : ' ';
+
+ if (F_ISSET(dbp, DB_RE_FIXEDLEN)) {
+ if ((rp->re_len = dbinfo->re_len) == 0) {
+ __db_err(dbp->dbenv,
+ "record length must be greater than 0");
+ ret = EINVAL;
+ goto err;
+ }
+ } else
+ rp->re_len = 0;
+ }
+
+ /* Initialize the remaining fields/methods of the DB. */
+ dbp->am_close = __ram_close;
+ dbp->del = __ram_delete;
+ dbp->put = __ram_put;
+ dbp->stat = __bam_stat;
+ dbp->sync = __ram_sync;
+
+ /* Start up the tree. */
+ if ((ret = __bam_read_root(dbp)) != 0)
+ goto err;
+
+ /* Set the overflow page size. */
+ __bam_setovflsize(dbp);
+
+ /* If we're snapshotting an underlying source file, do it now. */
+ if (dbinfo != NULL && F_ISSET(dbinfo, DB_SNAPSHOT)) {
+ /* Allocate a cursor. */
+ if ((ret = dbp->cursor(dbp, NULL, &dbc, 0)) != 0)
+ goto err;
+
+ /* Do the snapshot. */
+ if ((ret = __ram_update(dbc,
+ DB_MAX_RECORDS, 0)) != 0 && ret == DB_NOTFOUND)
+ ret = 0;
+
+ /* Discard the cursor. */
+ if ((t_ret = dbc->c_close(dbc)) != 0 && ret == 0)
+ ret = t_ret;
+
+ if (ret != 0)
+ goto err;
+ }
+
+ return (0);
+
+err: /* If we mmap'd a source file, discard it. */
+ if (rp->re_smap != NULL)
+ (void)__db_unmapfile(rp->re_smap, rp->re_msize);
+
+ /* If we opened a source file, discard it. */
+ if (rp->re_fd != -1)
+ (void)__os_close(rp->re_fd);
+ if (rp->re_source != NULL)
+ __os_freestr(rp->re_source);
+
+ __os_free(rp, sizeof(*rp));
+
+ return (ret);
+}
+
+/*
+ * __ram_delete --
+ * Recno db->del function.
+ */
+static int
+__ram_delete(dbp, txn, key, flags)
+ DB *dbp;
+ DB_TXN *txn;
+ DBT *key;
+ u_int32_t flags;
+{
+ CURSOR *cp;
+ DBC *dbc;
+ db_recno_t recno;
+ int ret, t_ret;
+
+ DB_PANIC_CHECK(dbp);
+
+ /* Check for invalid flags. */
+ if ((ret = __db_delchk(dbp,
+ key, flags, F_ISSET(dbp, DB_AM_RDONLY))) != 0)
+ return (ret);
+
+ /* Acquire a cursor. */
+ if ((ret = dbp->cursor(dbp, txn, &dbc, DB_WRITELOCK)) != 0)
+ return (ret);
+
+ DEBUG_LWRITE(dbc, txn, "ram_delete", key, NULL, flags);
+
+ /* Check the user's record number and fill in as necessary. */
+ if ((ret = __ram_getno(dbc, key, &recno, 0)) != 0)
+ goto err;
+
+ /* Do the delete. */
+ cp = dbc->internal;
+ cp->recno = recno;
+ ret = __ram_i_delete(dbc);
+
+ /* Release the cursor. */
+err: if ((t_ret = dbc->c_close(dbc)) != 0 && ret == 0)
+ ret = t_ret;
+
+ return (ret);
+}
+
+/*
+ * __ram_i_delete --
+ * Internal version of recno delete, called by __ram_delete and
+ * __ram_c_del.
+ */
+static int
+__ram_i_delete(dbc)
+ DBC *dbc;
+{
+ BKEYDATA bk;
+ BTREE *t;
+ CURSOR *cp;
+ DB *dbp;
+ DBT hdr, data;
+ PAGE *h;
+ db_indx_t indx;
+ int exact, ret, stack;
+
+ dbp = dbc->dbp;
+ cp = dbc->internal;
+ t = dbp->internal;
+ stack = 0;
+
+ /*
+ * If this is CDB and this isn't a write cursor, then it's an error.
+ * If it is a write cursor, but we don't yet hold the write lock, then
+ * we need to upgrade to the write lock.
+ */
+ if (F_ISSET(dbp, DB_AM_CDB)) {
+ /* Make sure it's a valid update cursor. */
+ if (!F_ISSET(dbc, DBC_RMW | DBC_WRITER))
+ return (EINVAL);
+
+ if (F_ISSET(dbc, DBC_RMW) &&
+ (ret = lock_get(dbp->dbenv->lk_info, dbc->locker,
+ DB_LOCK_UPGRADE, &dbc->lock_dbt, DB_LOCK_WRITE,
+ &dbc->mylock)) != 0)
+ return (EAGAIN);
+ }
+
+ /* Search the tree for the key; delete only deletes exact matches. */
+ if ((ret = __bam_rsearch(dbc, &cp->recno, S_DELETE, 1, &exact)) != 0)
+ goto err;
+ if (!exact) {
+ ret = DB_NOTFOUND;
+ goto err;
+ }
+ stack = 1;
+
+ h = cp->csp->page;
+ indx = cp->csp->indx;
+
+ /*
+ * If re-numbering records, the on-page deleted flag can only mean
+ * that this record was implicitly created. Applications aren't
+ * permitted to delete records they never created, return an error.
+ *
+ * If not re-numbering records, the on-page deleted flag means that
+ * this record was implicitly created, or, was deleted at some time.
+ * The former is an error because applications aren't permitted to
+ * delete records they never created, the latter is an error because
+ * if the record was "deleted", we could never have found it.
+ */
+ if (B_DISSET(GET_BKEYDATA(h, indx)->type)) {
+ ret = DB_KEYEMPTY;
+ goto err;
+ }
+
+ if (F_ISSET(dbp, DB_RE_RENUMBER)) {
+ /* Delete the item, adjust the counts, adjust the cursors. */
+ if ((ret = __bam_ditem(dbc, h, indx)) != 0)
+ goto err;
+ __bam_adjust(dbc, -1);
+ __ram_ca(dbp, cp->recno, CA_DELETE);
+
+ /*
+ * If the page is empty, delete it. The whole tree is locked
+ * so there are no preparations to make.
+ */
+ if (NUM_ENT(h) == 0 && h->pgno != PGNO_ROOT) {
+ stack = 0;
+ ret = __bam_dpages(dbc);
+ }
+ } else {
+ /* Use a delete/put pair to replace the record with a marker. */
+ if ((ret = __bam_ditem(dbc, h, indx)) != 0)
+ goto err;
+
+ B_TSET(bk.type, B_KEYDATA, 1);
+ bk.len = 0;
+ memset(&hdr, 0, sizeof(hdr));
+ hdr.data = &bk;
+ hdr.size = SSZA(BKEYDATA, data);
+ memset(&data, 0, sizeof(data));
+ data.data = (char *)"";
+ data.size = 0;
+ if ((ret = __db_pitem(dbc,
+ h, indx, BKEYDATA_SIZE(0), &hdr, &data)) != 0)
+ goto err;
+ }
+ F_SET(t->recno, RECNO_MODIFIED);
+
+err: if (stack)
+ __bam_stkrel(dbc, 0);
+
+ /* If we upgraded the CDB lock upon entry; downgrade it now. */
+ if (F_ISSET(dbp, DB_AM_CDB) && F_ISSET(dbc, DBC_RMW))
+ (void)__lock_downgrade(dbp->dbenv->lk_info, dbc->mylock,
+ DB_LOCK_IWRITE, 0);
+ return (ret);
+}
+
+/*
+ * __ram_put --
+ * Recno db->put function.
+ */
+static int
+__ram_put(dbp, txn, key, data, flags)
+ DB *dbp;
+ DB_TXN *txn;
+ DBT *key, *data;
+ u_int32_t flags;
+{
+ DBC *dbc;
+ db_recno_t recno;
+ int ret, t_ret;
+
+ DB_PANIC_CHECK(dbp);
+
+ /* Check for invalid flags. */
+ if ((ret = __db_putchk(dbp,
+ key, data, flags, F_ISSET(dbp, DB_AM_RDONLY), 0)) != 0)
+ return (ret);
+
+ /* Allocate a cursor. */
+ if ((ret = dbp->cursor(dbp, txn, &dbc, DB_WRITELOCK)) != 0)
+ return (ret);
+
+ DEBUG_LWRITE(dbc, txn, "ram_put", key, data, flags);
+
+ /*
+ * If we're appending to the tree, make sure we've read in all of
+ * the backing source file. Otherwise, check the user's record
+ * number and fill in as necessary.
+ */
+ ret = flags == DB_APPEND ?
+ __ram_update(dbc, DB_MAX_RECORDS, 0) :
+ __ram_getno(dbc, key, &recno, 1);
+
+ /* Add the record. */
+ if (ret == 0)
+ ret = __ram_add(dbc, &recno, data, flags, 0);
+
+ /* Discard the cursor. */
+ if ((t_ret = dbc->c_close(dbc)) != 0 && ret == 0)
+ ret = t_ret;
+
+ /* Return the record number if we're appending to the tree. */
+ if (ret == 0 && flags == DB_APPEND)
+ *(db_recno_t *)key->data = recno;
+
+ return (ret);
+}
+
+/*
+ * __ram_sync --
+ * Recno db->sync function.
+ */
+static int
+__ram_sync(dbp, flags)
+ DB *dbp;
+ u_int32_t flags;
+{
+ DBC *dbc;
+ int ret, t_ret;
+
+ /*
+ * Sync the underlying btree.
+ *
+ * !!!
+ * We don't need to do a panic check or flags check, the "real"
+ * sync function does all that for us.
+ */
+ if ((ret = __db_sync(dbp, flags)) != 0)
+ return (ret);
+
+ /* Allocate a cursor. */
+ if ((ret = dbp->cursor(dbp, NULL, &dbc, 0)) != 0)
+ return (ret);
+
+ DEBUG_LWRITE(dbc, NULL, "ram_sync", NULL, NULL, flags);
+
+ /* Copy back the backing source file. */
+ ret = __ram_writeback(dbc);
+
+ /* Discard the cursor. */
+ if ((t_ret = dbc->c_close(dbc)) != 0 && ret == 0)
+ ret = t_ret;
+
+ return (ret);
+}
+
+/*
+ * __ram_close --
+ * Recno db->close function.
+ *
+ * PUBLIC: int __ram_close __P((DB *));
+ */
+int
+__ram_close(dbp)
+ DB *dbp;
+{
+ RECNO *rp;
+
+ rp = ((BTREE *)dbp->internal)->recno;
+
+ /* Close any underlying mmap region. */
+ if (rp->re_smap != NULL)
+ (void)__db_unmapfile(rp->re_smap, rp->re_msize);
+
+ /* Close any backing source file descriptor. */
+ if (rp->re_fd != -1)
+ (void)__os_close(rp->re_fd);
+
+ /* Free any backing source file name. */
+ if (rp->re_source != NULL)
+ __os_freestr(rp->re_source);
+
+ /* Free allocated memory. */
+ __os_free(rp, sizeof(RECNO));
+ ((BTREE *)dbp->internal)->recno = NULL;
+
+ /* Close the underlying btree. */
+ return (__bam_close(dbp));
+}
+
+/*
+ * __ram_c_del --
+ * Recno cursor->c_del function.
+ *
+ * PUBLIC: int __ram_c_del __P((DBC *, u_int32_t));
+ */
+int
+__ram_c_del(dbc, flags)
+ DBC *dbc;
+ u_int32_t flags;
+{
+ CURSOR *cp;
+ DB *dbp;
+ int ret;
+
+ dbp = dbc->dbp;
+ cp = dbc->internal;
+
+ DB_PANIC_CHECK(dbp);
+
+ /* Check for invalid flags. */
+ if ((ret = __db_cdelchk(dbp, flags,
+ F_ISSET(dbp, DB_AM_RDONLY), cp->recno != RECNO_OOB)) != 0)
+ return (ret);
+
+ DEBUG_LWRITE(dbc, dbc->txn, "ram_c_del", NULL, NULL, flags);
+
+ /*
+ * If we are running CDB, this had better be either a write
+ * cursor or an immediate writer.
+ */
+ if (F_ISSET(dbp, DB_AM_CDB))
+ if (!F_ISSET(dbc, DBC_RMW | DBC_WRITER))
+ return (EINVAL);
+
+ /*
+ * The semantics of cursors during delete are as follows: if record
+ * numbers are mutable (DB_RE_RENUMBER is set), deleting a record
+ * causes the cursor to automatically point to the record immediately
+ * following. In this case it is possible to use a single cursor for
+ * repeated delete operations, without intervening operations.
+ *
+ * If record numbers are not mutable, then records are replaced with
+ * a marker containing a delete flag. If the record referenced by
+ * this cursor has already been deleted, we will detect that as part
+ * of the delete operation, and fail.
+ */
+ return (__ram_i_delete(dbc));
+}
+
+/*
+ * __ram_c_get --
+ * Recno cursor->c_get function.
+ *
+ * PUBLIC: int __ram_c_get __P((DBC *, DBT *, DBT *, u_int32_t));
+ */
+int
+__ram_c_get(dbc, key, data, flags)
+ DBC *dbc;
+ DBT *key, *data;
+ u_int32_t flags;
+{
+ CURSOR *cp, copy;
+ DB *dbp;
+ PAGE *h;
+ db_indx_t indx;
+ int exact, ret, stack, tmp_rmw;
+
+ dbp = dbc->dbp;
+ cp = dbc->internal;
+
+ DB_PANIC_CHECK(dbp);
+
+ /* Check for invalid flags. */
+ if ((ret = __db_cgetchk(dbc->dbp,
+ key, data, flags, cp->recno != RECNO_OOB)) != 0)
+ return (ret);
+
+ /* Clear OR'd in additional bits so we can check for flag equality. */
+ tmp_rmw = 0;
+ if (LF_ISSET(DB_RMW)) {
+ if (!F_ISSET(dbp, DB_AM_CDB)) {
+ tmp_rmw = 1;
+ F_SET(dbc, DBC_RMW);
+ }
+ LF_CLR(DB_RMW);
+ }
+
+ DEBUG_LREAD(dbc, dbc->txn, "ram_c_get",
+ flags == DB_SET || flags == DB_SET_RANGE ? key : NULL, NULL, flags);
+
+ /* Initialize the cursor for a new retrieval. */
+ copy = *cp;
+
+retry: /* Update the record number. */
+ stack = 0;
+ switch (flags) {
+ case DB_CURRENT:
+ /*
+ * If record numbers are mutable: if we just deleted a record,
+ * there is no action necessary, we return the record following
+ * the deleted item by virtue of renumbering the tree.
+ */
+ break;
+ case DB_NEXT:
+ /*
+ * If record numbers are mutable: if we just deleted a record,
+ * we have to avoid incrementing the record number so that we
+ * return the right record by virtue of renumbering the tree.
+ */
+ if (CD_ISSET(dbp, cp))
+ break;
+
+ if (cp->recno != RECNO_OOB) {
+ ++cp->recno;
+ break;
+ }
+ /* FALLTHROUGH */
+ case DB_FIRST:
+ flags = DB_NEXT;
+ cp->recno = 1;
+ break;
+ case DB_PREV:
+ if (cp->recno != RECNO_OOB) {
+ if (cp->recno == 1) {
+ ret = DB_NOTFOUND;
+ goto err;
+ }
+ --cp->recno;
+ break;
+ }
+ /* FALLTHROUGH */
+ case DB_LAST:
+ flags = DB_PREV;
+ if (((ret = __ram_update(dbc,
+ DB_MAX_RECORDS, 0)) != 0) && ret != DB_NOTFOUND)
+ goto err;
+ if ((ret = __bam_nrecs(dbc, &cp->recno)) != 0)
+ goto err;
+ if (cp->recno == 0) {
+ ret = DB_NOTFOUND;
+ goto err;
+ }
+ break;
+ case DB_SET:
+ case DB_SET_RANGE:
+ if ((ret = __ram_getno(dbc, key, &cp->recno, 0)) != 0)
+ goto err;
+ break;
+ }
+
+ /* Return the key if the user didn't give us one. */
+ if (flags != DB_SET && flags != DB_SET_RANGE &&
+ (ret = __db_retcopy(key, &cp->recno, sizeof(cp->recno),
+ &dbc->rkey.data, &dbc->rkey.ulen, dbp->db_malloc)) != 0)
+ goto err;
+
+ /* Search the tree for the record. */
+ if ((ret = __bam_rsearch(dbc, &cp->recno,
+ F_ISSET(dbc, DBC_RMW) ? S_FIND_WR : S_FIND, 1, &exact)) != 0)
+ goto err;
+ stack = 1;
+ if (!exact) {
+ ret = DB_NOTFOUND;
+ goto err;
+ }
+ h = cp->csp->page;
+ indx = cp->csp->indx;
+
+ /*
+ * If re-numbering records, the on-page deleted flag means this record
+ * was implicitly created. If not re-numbering records, the on-page
+ * deleted flag means this record was implicitly created, or, it was
+ * deleted at some time. Regardless, we skip such records if doing
+ * cursor next/prev operations, and fail if the application requested
+ * them explicitly.
+ */
+ if (B_DISSET(GET_BKEYDATA(h, indx)->type)) {
+ if (flags == DB_NEXT || flags == DB_PREV) {
+ (void)__bam_stkrel(dbc, 0);
+ goto retry;
+ }
+ ret = DB_KEYEMPTY;
+ goto err;
+ }
+
+ /* Return the data item. */
+ if ((ret = __db_ret(dbp,
+ h, indx, data, &dbc->rdata.data, &dbc->rdata.ulen)) != 0)
+ goto err;
+
+ /* The cursor was reset, no further delete adjustment is necessary. */
+ CD_CLR(dbp, cp);
+
+err: if (stack)
+ (void)__bam_stkrel(dbc, 0);
+
+ /* Release temporary lock upgrade. */
+ if (tmp_rmw)
+ F_CLR(dbc, DBC_RMW);
+
+ if (ret != 0)
+ *cp = copy;
+
+ return (ret);
+}
+
+/*
+ * __ram_c_put --
+ * Recno cursor->c_put function.
+ *
+ * PUBLIC: int __ram_c_put __P((DBC *, DBT *, DBT *, u_int32_t));
+ */
+int
+__ram_c_put(dbc, key, data, flags)
+ DBC *dbc;
+ DBT *key, *data;
+ u_int32_t flags;
+{
+ CURSOR *cp, copy;
+ DB *dbp;
+ int exact, ret;
+ void *arg;
+
+ dbp = dbc->dbp;
+ cp = dbc->internal;
+
+ DB_PANIC_CHECK(dbp);
+
+ if ((ret = __db_cputchk(dbc->dbp, key, data, flags,
+ F_ISSET(dbc->dbp, DB_AM_RDONLY), cp->recno != RECNO_OOB)) != 0)
+ return (ret);
+
+ DEBUG_LWRITE(dbc, dbc->txn, "ram_c_put", NULL, data, flags);
+
+ /*
+ * If we are running CDB, this had better be either a write
+ * cursor or an immediate writer. If it's a regular writer,
+ * that means we have an IWRITE lock and we need to upgrade
+ * it to a write lock.
+ */
+ if (F_ISSET(dbp, DB_AM_CDB)) {
+ if (!F_ISSET(dbc, DBC_RMW | DBC_WRITER))
+ return (EINVAL);
+
+ if (F_ISSET(dbc, DBC_RMW) &&
+ (ret = lock_get(dbp->dbenv->lk_info, dbc->locker,
+ DB_LOCK_UPGRADE, &dbc->lock_dbt, DB_LOCK_WRITE,
+ &dbc->mylock)) != 0)
+ return (EAGAIN);
+ }
+
+ /* Initialize the cursor for a new retrieval. */
+ copy = *cp;
+
+ /*
+ * To split, we need a valid key for the page. Since it's a cursor,
+ * we have to build one.
+ *
+ * The split code discards all short-term locks and stack pages.
+ */
+ if (0) {
+split: arg = &cp->recno;
+ if ((ret = __bam_split(dbc, arg)) != 0)
+ goto err;
+ }
+
+ if ((ret = __bam_rsearch(dbc, &cp->recno, S_INSERT, 1, &exact)) != 0)
+ goto err;
+ if (!exact) {
+ ret = DB_NOTFOUND;
+ goto err;
+ }
+ if ((ret = __bam_iitem(dbc, &cp->csp->page,
+ &cp->csp->indx, key, data, flags, 0)) == DB_NEEDSPLIT) {
+ if ((ret = __bam_stkrel(dbc, 0)) != 0)
+ goto err;
+ goto split;
+ }
+ if ((ret = __bam_stkrel(dbc, 0)) != 0)
+ goto err;
+
+ switch (flags) {
+ case DB_AFTER:
+ /* Adjust the cursors. */
+ __ram_ca(dbp, cp->recno, CA_IAFTER);
+
+ /* Set this cursor to reference the new record. */
+ cp->recno = copy.recno + 1;
+ break;
+ case DB_BEFORE:
+ /* Adjust the cursors. */
+ __ram_ca(dbp, cp->recno, CA_IBEFORE);
+
+ /* Set this cursor to reference the new record. */
+ cp->recno = copy.recno;
+ break;
+ }
+
+ /* The cursor was reset, no further delete adjustment is necessary. */
+ CD_CLR(dbp, cp);
+
+err: if (F_ISSET(dbp, DB_AM_CDB) && F_ISSET(dbc, DBC_RMW))
+ (void)__lock_downgrade(dbp->dbenv->lk_info, dbc->mylock,
+ DB_LOCK_IWRITE, 0);
+
+ if (ret != 0)
+ *cp = copy;
+
+ return (ret);
+}
+
+/*
+ * __ram_ca --
+ * Adjust cursors.
+ *
+ * PUBLIC: void __ram_ca __P((DB *, db_recno_t, ca_recno_arg));
+ */
+void
+__ram_ca(dbp, recno, op)
+ DB *dbp;
+ db_recno_t recno;
+ ca_recno_arg op;
+{
+ CURSOR *cp;
+ DBC *dbc;
+
+ /*
+ * Adjust the cursors. See the comment in __bam_ca_delete().
+ */
+ DB_THREAD_LOCK(dbp);
+ for (dbc = TAILQ_FIRST(&dbp->active_queue);
+ dbc != NULL; dbc = TAILQ_NEXT(dbc, links)) {
+ cp = dbc->internal;
+ switch (op) {
+ case CA_DELETE:
+ if (recno > cp->recno)
+ --cp->recno;
+ if (recno == cp->recno)
+ CD_SET(dbp, cp);
+ break;
+ case CA_IAFTER:
+ if (recno > cp->recno)
+ ++cp->recno;
+ break;
+ case CA_IBEFORE:
+ if (recno >= cp->recno)
+ ++cp->recno;
+ break;
+ }
+ }
+ DB_THREAD_UNLOCK(dbp);
+}
+
+/*
+ * __ram_getno --
+ * Check the user's record number, and make sure we've seen it.
+ *
+ * PUBLIC: int __ram_getno __P((DBC *, const DBT *, db_recno_t *, int));
+ */
+int
+__ram_getno(dbc, key, rep, can_create)
+ DBC *dbc;
+ const DBT *key;
+ db_recno_t *rep;
+ int can_create;
+{
+ DB *dbp;
+ db_recno_t recno;
+
+ dbp = dbc->dbp;
+
+ /* Check the user's record number. */
+ if ((recno = *(db_recno_t *)key->data) == 0) {
+ __db_err(dbp->dbenv, "illegal record number of 0");
+ return (EINVAL);
+ }
+ if (rep != NULL)
+ *rep = recno;
+
+ /*
+ * Btree can neither create records nor read them in. Recno can
+ * do both, see if we can find the record.
+ */
+ return (dbp->type == DB_RECNO ?
+ __ram_update(dbc, recno, can_create) : 0);
+}
+
+/*
+ * __ram_update --
+ * Ensure the tree has records up to and including the specified one.
+ */
+static int
+__ram_update(dbc, recno, can_create)
+ DBC *dbc;
+ db_recno_t recno;
+ int can_create;
+{
+ BTREE *t;
+ DB *dbp;
+ RECNO *rp;
+ db_recno_t nrecs;
+ int ret;
+
+ dbp = dbc->dbp;
+ t = dbp->internal;
+ rp = t->recno;
+
+ /*
+ * If we can't create records and we've read the entire backing input
+ * file, we're done.
+ */
+ if (!can_create && F_ISSET(rp, RECNO_EOF))
+ return (0);
+
+ /*
+ * If we haven't seen this record yet, try to get it from the original
+ * file.
+ */
+ if ((ret = __bam_nrecs(dbc, &nrecs)) != 0)
+ return (ret);
+ if (!F_ISSET(rp, RECNO_EOF) && recno > nrecs) {
+ if ((ret = rp->re_irec(dbc, recno)) != 0)
+ return (ret);
+ if ((ret = __bam_nrecs(dbc, &nrecs)) != 0)
+ return (ret);
+ }
+
+ /*
+ * If we can create records, create empty ones up to the requested
+ * record.
+ */
+ if (!can_create || recno <= nrecs + 1)
+ return (0);
+
+ dbc->rdata.dlen = 0;
+ dbc->rdata.doff = 0;
+ dbc->rdata.flags = 0;
+ if (F_ISSET(dbp, DB_RE_FIXEDLEN)) {
+ if (dbc->rdata.ulen < rp->re_len) {
+ if ((ret =
+ __os_realloc(&dbc->rdata.data, rp->re_len)) != 0) {
+ dbc->rdata.ulen = 0;
+ dbc->rdata.data = NULL;
+ return (ret);
+ }
+ dbc->rdata.ulen = rp->re_len;
+ }
+ dbc->rdata.size = rp->re_len;
+ memset(dbc->rdata.data, rp->re_pad, rp->re_len);
+ } else
+ dbc->rdata.size = 0;
+
+ while (recno > ++nrecs)
+ if ((ret = __ram_add(dbc,
+ &nrecs, &dbc->rdata, 0, BI_DELETED)) != 0)
+ return (ret);
+ return (0);
+}
+
+/*
+ * __ram_source --
+ * Load information about the backing file.
+ */
+static int
+__ram_source(dbp, rp, fname)
+ DB *dbp;
+ RECNO *rp;
+ const char *fname;
+{
+ size_t size;
+ u_int32_t bytes, mbytes, oflags;
+ int ret;
+
+ /*
+ * !!!
+ * The caller has full responsibility for cleaning up on error --
+ * (it has to anyway, in case it fails after this routine succeeds).
+ */
+ if ((ret = __db_appname(dbp->dbenv,
+ DB_APP_DATA, NULL, fname, 0, NULL, &rp->re_source)) != 0)
+ return (ret);
+
+ oflags = F_ISSET(dbp, DB_AM_RDONLY) ? DB_RDONLY : 0;
+ if ((ret =
+ __db_open(rp->re_source, oflags, oflags, 0, &rp->re_fd)) != 0) {
+ __db_err(dbp->dbenv, "%s: %s", rp->re_source, strerror(ret));
+ return (ret);
+ }
+
+ /*
+ * XXX
+ * We'd like to test to see if the file is too big to mmap. Since we
+ * don't know what size or type off_t's or size_t's are, or the largest
+ * unsigned integral type is, or what random insanity the local C
+ * compiler will perpetrate, doing the comparison in a portable way is
+ * flatly impossible. Hope that mmap fails if the file is too large.
+ */
+ if ((ret = __os_ioinfo(rp->re_source,
+ rp->re_fd, &mbytes, &bytes, NULL)) != 0) {
+ __db_err(dbp->dbenv, "%s: %s", rp->re_source, strerror(ret));
+ return (ret);
+ }
+ if (mbytes == 0 && bytes == 0) {
+ F_SET(rp, RECNO_EOF);
+ return (0);
+ }
+
+ size = mbytes * MEGABYTE + bytes;
+ if ((ret = __db_mapfile(rp->re_source,
+ rp->re_fd, (size_t)size, 1, &rp->re_smap)) != 0)
+ return (ret);
+ rp->re_cmap = rp->re_smap;
+ rp->re_emap = (u_int8_t *)rp->re_smap + (rp->re_msize = size);
+ rp->re_irec = F_ISSET(dbp, DB_RE_FIXEDLEN) ? __ram_fmap : __ram_vmap;
+ return (0);
+}
+
+/*
+ * __ram_writeback --
+ * Rewrite the backing file.
+ */
+static int
+__ram_writeback(dbc)
+ DBC *dbc;
+{
+ DB *dbp;
+ DBT key, data;
+ RECNO *rp;
+ db_recno_t keyno;
+ ssize_t nw;
+ int fd, ret, t_ret;
+ u_int8_t delim, *pad;
+
+ dbp = dbc->dbp;
+ rp = ((BTREE *)dbp->internal)->recno;
+
+ /* If the file wasn't modified, we're done. */
+ if (!F_ISSET(rp, RECNO_MODIFIED))
+ return (0);
+
+ /* If there's no backing source file, we're done. */
+ if (rp->re_source == NULL) {
+ F_CLR(rp, RECNO_MODIFIED);
+ return (0);
+ }
+
+ /*
+ * Read any remaining records into the tree.
+ *
+ * !!!
+ * This is why we can't support transactions when applications specify
+ * backing (re_source) files. At this point we have to read in the
+ * rest of the records from the file so that we can write all of the
+ * records back out again, which could modify a page for which we'd
+ * have to log changes and which we don't have locked. This could be
+ * partially fixed by taking a snapshot of the entire file during the
+ * db_open(), or, since db_open() isn't transaction protected, as part
+ * of the first DB operation. But, if a checkpoint occurs then, the
+ * part of the log holding the copy of the file could be discarded, and
+ * that would make it impossible to recover in the face of disaster.
+ * This could all probably be fixed, but it would require transaction
+ * protecting the backing source file, i.e. mpool would have to know
+ * about it, and we don't want to go there.
+ */
+ if ((ret =
+ __ram_update(dbc, DB_MAX_RECORDS, 0)) != 0 && ret != DB_NOTFOUND)
+ return (ret);
+
+ /*
+ * !!!
+ * Close any underlying mmap region. This is required for Windows NT
+ * (4.0, Service Pack 2) -- if the file is still mapped, the following
+ * open will fail.
+ */
+ if (rp->re_smap != NULL) {
+ (void)__db_unmapfile(rp->re_smap, rp->re_msize);
+ rp->re_smap = NULL;
+ }
+
+ /* Get rid of any backing file descriptor, just on GP's. */
+ if (rp->re_fd != -1) {
+ (void)__os_close(rp->re_fd);
+ rp->re_fd = -1;
+ }
+
+ /* Open the file, truncating it. */
+ if ((ret = __db_open(rp->re_source,
+ DB_SEQUENTIAL | DB_TRUNCATE,
+ DB_SEQUENTIAL | DB_TRUNCATE, 0, &fd)) != 0) {
+ __db_err(dbp->dbenv, "%s: %s", rp->re_source, strerror(ret));
+ return (ret);
+ }
+
+ /*
+ * We step through the records, writing each one out. Use the record
+ * number and the dbp->get() function, instead of a cursor, so we find
+ * and write out "deleted" or non-existent records.
+ */
+ memset(&key, 0, sizeof(key));
+ memset(&data, 0, sizeof(data));
+ key.size = sizeof(db_recno_t);
+ key.data = &keyno;
+
+ /*
+ * We'll need the delimiter if we're doing variable-length records,
+ * and the pad character if we're doing fixed-length records.
+ */
+ delim = rp->re_delim;
+ if (F_ISSET(dbp, DB_RE_FIXEDLEN)) {
+ if ((ret = __os_malloc(rp->re_len, NULL, &pad)) != 0)
+ goto err;
+ memset(pad, rp->re_pad, rp->re_len);
+ } else
+ COMPQUIET(pad, NULL);
+ for (keyno = 1;; ++keyno) {
+ switch (ret = dbp->get(dbp, NULL, &key, &data, 0)) {
+ case 0:
+ if ((ret =
+ __os_write(fd, data.data, data.size, &nw)) != 0)
+ goto err;
+ if (nw != (ssize_t)data.size) {
+ ret = EIO;
+ goto err;
+ }
+ break;
+ case DB_KEYEMPTY:
+ if (F_ISSET(dbp, DB_RE_FIXEDLEN)) {
+ if ((ret =
+ __os_write(fd, pad, rp->re_len, &nw)) != 0)
+ goto err;
+ if (nw != (ssize_t)rp->re_len) {
+ ret = EIO;
+ goto err;
+ }
+ }
+ break;
+ case DB_NOTFOUND:
+ ret = 0;
+ goto done;
+ }
+ if (!F_ISSET(dbp, DB_RE_FIXEDLEN)) {
+ if ((ret = __os_write(fd, &delim, 1, &nw)) != 0)
+ goto err;
+ if (nw != 1) {
+ ret = EIO;
+ goto err;
+ }
+ }
+ }
+
+err:
+done: /* Close the file descriptor. */
+ if ((t_ret = __os_close(fd)) != 0 || ret == 0)
+ ret = t_ret;
+
+ if (ret == 0)
+ F_CLR(rp, RECNO_MODIFIED);
+ return (ret);
+}
+
+/*
+ * __ram_fmap --
+ * Get fixed length records from a file.
+ */
+static int
+__ram_fmap(dbc, top)
+ DBC *dbc;
+ db_recno_t top;
+{
+ DB *dbp;
+ DBT data;
+ RECNO *rp;
+ db_recno_t recno;
+ u_int32_t len;
+ u_int8_t *sp, *ep, *p;
+ int ret;
+
+ if ((ret = __bam_nrecs(dbc, &recno)) != 0)
+ return (ret);
+
+ dbp = dbc->dbp;
+ rp = ((BTREE *)(dbp->internal))->recno;
+
+ if (dbc->rdata.ulen < rp->re_len) {
+ if ((ret = __os_realloc(&dbc->rdata.data, rp->re_len)) != 0) {
+ dbc->rdata.ulen = 0;
+ dbc->rdata.data = NULL;
+ return (ret);
+ }
+ dbc->rdata.ulen = rp->re_len;
+ }
+
+ memset(&data, 0, sizeof(data));
+ data.data = dbc->rdata.data;
+ data.size = rp->re_len;
+
+ sp = (u_int8_t *)rp->re_cmap;
+ ep = (u_int8_t *)rp->re_emap;
+ while (recno < top) {
+ if (sp >= ep) {
+ F_SET(rp, RECNO_EOF);
+ return (DB_NOTFOUND);
+ }
+ len = rp->re_len;
+ for (p = dbc->rdata.data;
+ sp < ep && len > 0; *p++ = *sp++, --len)
+ ;
+
+ /*
+ * Another process may have read this record from the input
+ * file and stored it into the database already, in which
+ * case we don't need to repeat that operation. We detect
+ * this by checking if the last record we've read is greater
+ * or equal to the number of records in the database.
+ *
+ * XXX
+ * We should just do a seek, since the records are fixed
+ * length.
+ */
+ if (rp->re_last >= recno) {
+ if (len != 0)
+ memset(p, rp->re_pad, len);
+
+ ++recno;
+ if ((ret = __ram_add(dbc, &recno, &data, 0, 0)) != 0)
+ return (ret);
+ }
+ ++rp->re_last;
+ }
+ rp->re_cmap = sp;
+ return (0);
+}
+
+/*
+ * __ram_vmap --
+ * Get variable length records from a file.
+ */
+static int
+__ram_vmap(dbc, top)
+ DBC *dbc;
+ db_recno_t top;
+{
+ DBT data;
+ RECNO *rp;
+ db_recno_t recno;
+ u_int8_t *sp, *ep;
+ int delim, ret;
+
+ rp = ((BTREE *)(dbc->dbp->internal))->recno;
+
+ if ((ret = __bam_nrecs(dbc, &recno)) != 0)
+ return (ret);
+
+ memset(&data, 0, sizeof(data));
+
+ delim = rp->re_delim;
+
+ sp = (u_int8_t *)rp->re_cmap;
+ ep = (u_int8_t *)rp->re_emap;
+ while (recno < top) {
+ if (sp >= ep) {
+ F_SET(rp, RECNO_EOF);
+ return (DB_NOTFOUND);
+ }
+ for (data.data = sp; sp < ep && *sp != delim; ++sp)
+ ;
+
+ /*
+ * Another process may have read this record from the input
+ * file and stored it into the database already, in which
+ * case we don't need to repeat that operation. We detect
+ * this by checking if the last record we've read is greater
+ * or equal to the number of records in the database.
+ */
+ if (rp->re_last >= recno) {
+ data.size = sp - (u_int8_t *)data.data;
+ ++recno;
+ if ((ret = __ram_add(dbc, &recno, &data, 0, 0)) != 0)
+ return (ret);
+ }
+ ++rp->re_last;
+ ++sp;
+ }
+ rp->re_cmap = sp;
+ return (0);
+}
+
+/*
+ * __ram_add --
+ * Add records into the tree.
+ */
+static int
+__ram_add(dbc, recnop, data, flags, bi_flags)
+ DBC *dbc;
+ db_recno_t *recnop;
+ DBT *data;
+ u_int32_t flags, bi_flags;
+{
+ BKEYDATA *bk;
+ CURSOR *cp;
+ DB *dbp;
+ PAGE *h;
+ db_indx_t indx;
+ int exact, isdeleted, ret, stack;
+
+ dbp = dbc->dbp;
+ cp = dbc->internal;
+
+retry: /* Find the slot for insertion. */
+ if ((ret = __bam_rsearch(dbc, recnop,
+ S_INSERT | (flags == DB_APPEND ? S_APPEND : 0), 1, &exact)) != 0)
+ return (ret);
+ h = cp->csp->page;
+ indx = cp->csp->indx;
+ stack = 1;
+
+ /*
+ * If re-numbering records, the on-page deleted flag means this record
+ * was implicitly created. If not re-numbering records, the on-page
+ * deleted flag means this record was implicitly created, or, it was
+ * deleted at some time.
+ *
+ * If DB_NOOVERWRITE is set and the item already exists in the tree,
+ * return an error unless the item was either marked for deletion or
+ * only implicitly created.
+ */
+ isdeleted = 0;
+ if (exact) {
+ bk = GET_BKEYDATA(h, indx);
+ if (B_DISSET(bk->type))
+ isdeleted = 1;
+ else
+ if (flags == DB_NOOVERWRITE) {
+ ret = DB_KEYEXIST;
+ goto err;
+ }
+ }
+
+ /*
+ * Select the arguments for __bam_iitem() and do the insert. If the
+ * key is an exact match, or we're replacing the data item with a
+ * new data item, replace the current item. If the key isn't an exact
+ * match, we're inserting a new key/data pair, before the search
+ * location.
+ */
+ switch (ret = __bam_iitem(dbc,
+ &h, &indx, NULL, data, exact ? DB_CURRENT : DB_BEFORE, bi_flags)) {
+ case 0:
+ /*
+ * Don't adjust anything.
+ *
+ * If we inserted a record, no cursors need adjusting because
+ * the only new record it's possible to insert is at the very
+ * end of the tree. The necessary adjustments to the internal
+ * page counts were made by __bam_iitem().
+ *
+ * If we overwrote a record, no cursors need adjusting because
+ * future DBcursor->get calls will simply return the underlying
+ * record (there's no adjustment made for the DB_CURRENT flag
+ * when a cursor get operation immediately follows a cursor
+ * delete operation, and the normal adjustment for the DB_NEXT
+ * flag is still correct).
+ */
+ break;
+ case DB_NEEDSPLIT:
+ /* Discard the stack of pages and split the page. */
+ (void)__bam_stkrel(dbc, 0);
+ stack = 0;
+
+ if ((ret = __bam_split(dbc, recnop)) != 0)
+ goto err;
+
+ goto retry;
+ /* NOTREACHED */
+ default:
+ goto err;
+ }
+
+
+err: if (stack)
+ __bam_stkrel(dbc, 0);
+
+ return (ret);
+}
diff --git a/usr/src/cmd/sendmail/db/btree/bt_rsearch.c b/usr/src/cmd/sendmail/db/btree/bt_rsearch.c
new file mode 100644
index 0000000000..8efe4059a8
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/btree/bt_rsearch.c
@@ -0,0 +1,391 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+/*
+ * Copyright (c) 1990, 1993, 1994, 1995, 1996
+ * Keith Bostic. All rights reserved.
+ */
+/*
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)bt_rsearch.c 10.21 (Sleepycat) 12/2/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+#endif
+
+#include "db_int.h"
+#include "db_page.h"
+#include "btree.h"
+
+/*
+ * __bam_rsearch --
+ * Search a btree for a record number.
+ *
+ * PUBLIC: int __bam_rsearch __P((DBC *, db_recno_t *, u_int32_t, int, int *));
+ */
+int
+__bam_rsearch(dbc, recnop, flags, stop, exactp)
+ DBC *dbc;
+ db_recno_t *recnop;
+ u_int32_t flags;
+ int stop, *exactp;
+{
+ BINTERNAL *bi;
+ CURSOR *cp;
+ DB *dbp;
+ DB_LOCK lock;
+ PAGE *h;
+ RINTERNAL *ri;
+ db_indx_t indx, top;
+ db_pgno_t pg;
+ db_recno_t i, recno, total;
+ int ret, stack;
+
+ dbp = dbc->dbp;
+ cp = dbc->internal;
+
+ BT_STK_CLR(cp);
+
+ /*
+ * There are several ways we search a btree tree. The flags argument
+ * specifies if we're acquiring read or write locks and if we are
+ * locking pairs of pages. In addition, if we're adding or deleting
+ * an item, we have to lock the entire tree, regardless. See btree.h
+ * for more details.
+ *
+ * If write-locking pages, we need to know whether or not to acquire a
+ * write lock on a page before getting it. This depends on how deep it
+ * is in tree, which we don't know until we acquire the root page. So,
+ * if we need to lock the root page we may have to upgrade it later,
+ * because we won't get the correct lock initially.
+ *
+ * Retrieve the root page.
+ */
+ pg = PGNO_ROOT;
+ stack = LF_ISSET(S_STACK);
+ if ((ret = __bam_lget(dbc,
+ 0, pg, stack ? DB_LOCK_WRITE : DB_LOCK_READ, &lock)) != 0)
+ return (ret);
+ if ((ret = memp_fget(dbp->mpf, &pg, 0, &h)) != 0) {
+ (void)__BT_LPUT(dbc, lock);
+ return (ret);
+ }
+
+ /*
+ * Decide if we need to save this page; if we do, write lock it.
+ * We deliberately don't lock-couple on this call. If the tree
+ * is tiny, i.e., one page, and two threads are busily updating
+ * the root page, we're almost guaranteed deadlocks galore, as
+ * each one gets a read lock and then blocks the other's attempt
+ * for a write lock.
+ */
+ if (!stack &&
+ ((LF_ISSET(S_PARENT) && (u_int8_t)(stop + 1) >= h->level) ||
+ (LF_ISSET(S_WRITE) && h->level == LEAFLEVEL))) {
+ (void)memp_fput(dbp->mpf, h, 0);
+ (void)__BT_LPUT(dbc, lock);
+ if ((ret = __bam_lget(dbc, 0, pg, DB_LOCK_WRITE, &lock)) != 0)
+ return (ret);
+ if ((ret = memp_fget(dbp->mpf, &pg, 0, &h)) != 0) {
+ (void)__BT_LPUT(dbc, lock);
+ return (ret);
+ }
+ stack = 1;
+ }
+
+ /*
+ * If appending to the tree, set the record number now -- we have the
+ * root page locked.
+ *
+ * Delete only deletes exact matches, read only returns exact matches.
+ * Note, this is different from __bam_search(), which returns non-exact
+ * matches for read.
+ *
+ * The record may not exist. We can only return the correct location
+ * for the record immediately after the last record in the tree, so do
+ * a fast check now.
+ */
+ total = RE_NREC(h);
+ if (LF_ISSET(S_APPEND)) {
+ *exactp = 0;
+ *recnop = recno = total + 1;
+ } else {
+ recno = *recnop;
+ if (recno <= total)
+ *exactp = 1;
+ else {
+ *exactp = 0;
+ if (!LF_ISSET(S_PAST_EOF) || recno > total + 1) {
+ (void)memp_fput(dbp->mpf, h, 0);
+ (void)__BT_LPUT(dbc, lock);
+ return (DB_NOTFOUND);
+ }
+ }
+ }
+
+ /*
+ * !!!
+ * Record numbers in the tree are 0-based, but the recno is
+ * 1-based. All of the calculations below have to take this
+ * into account.
+ */
+ for (total = 0;;) {
+ switch (TYPE(h)) {
+ case P_LBTREE:
+ recno -= total;
+
+ /*
+ * There may be logically deleted records on the page,
+ * walk the page correcting for them. The record may
+ * not exist if there are enough deleted records in the
+ * page.
+ */
+ if (recno <= (db_recno_t)NUM_ENT(h) / P_INDX)
+ for (i = recno - 1;; --i) {
+ if (B_DISSET(GET_BKEYDATA(h,
+ i * P_INDX + O_INDX)->type))
+ ++recno;
+ if (i == 0)
+ break;
+ }
+ if (recno > (db_recno_t)NUM_ENT(h) / P_INDX) {
+ *exactp = 0;
+ if (!LF_ISSET(S_PAST_EOF) || recno >
+ (db_recno_t)(NUM_ENT(h) / P_INDX + 1)) {
+ ret = DB_NOTFOUND;
+ goto err;
+ }
+
+ }
+
+ /* Correct from 1-based to 0-based for a page offset. */
+ --recno;
+ BT_STK_ENTER(cp, h, recno * P_INDX, lock, ret);
+ return (ret);
+ case P_IBTREE:
+ for (indx = 0, top = NUM_ENT(h);;) {
+ bi = GET_BINTERNAL(h, indx);
+ if (++indx == top || total + bi->nrecs >= recno)
+ break;
+ total += bi->nrecs;
+ }
+ pg = bi->pgno;
+ break;
+ case P_LRECNO:
+ recno -= total;
+
+ /* Correct from 1-based to 0-based for a page offset. */
+ --recno;
+ BT_STK_ENTER(cp, h, recno, lock, ret);
+ return (ret);
+ case P_IRECNO:
+ for (indx = 0, top = NUM_ENT(h);;) {
+ ri = GET_RINTERNAL(h, indx);
+ if (++indx == top || total + ri->nrecs >= recno)
+ break;
+ total += ri->nrecs;
+ }
+ pg = ri->pgno;
+ break;
+ default:
+ return (__db_pgfmt(dbp, h->pgno));
+ }
+ --indx;
+
+ if (stack) {
+ /* Return if this is the lowest page wanted. */
+ if (LF_ISSET(S_PARENT) && stop == h->level) {
+ BT_STK_ENTER(cp, h, indx, lock, ret);
+ return (ret);
+ }
+ BT_STK_PUSH(cp, h, indx, lock, ret);
+ if (ret != 0)
+ goto err;
+
+ if ((ret =
+ __bam_lget(dbc, 0, pg, DB_LOCK_WRITE, &lock)) != 0)
+ goto err;
+ } else {
+ /*
+ * Decide if we want to return a pointer to the next
+ * page in the stack. If we do, write lock it and
+ * never unlock it.
+ */
+ if ((LF_ISSET(S_PARENT) &&
+ (u_int8_t)(stop + 1) >= (u_int8_t)(h->level - 1)) ||
+ (h->level - 1) == LEAFLEVEL)
+ stack = 1;
+
+ (void)memp_fput(dbp->mpf, h, 0);
+
+ if ((ret =
+ __bam_lget(dbc, 1, pg, stack && LF_ISSET(S_WRITE) ?
+ DB_LOCK_WRITE : DB_LOCK_READ, &lock)) != 0)
+ goto err;
+ }
+
+ if ((ret = memp_fget(dbp->mpf, &pg, 0, &h)) != 0)
+ goto err;
+ }
+ /* NOTREACHED */
+
+err: BT_STK_POP(cp);
+ __bam_stkrel(dbc, 0);
+ return (ret);
+}
+
+/*
+ * __bam_adjust --
+ * Adjust the tree after adding or deleting a record.
+ *
+ * PUBLIC: int __bam_adjust __P((DBC *, int32_t));
+ */
+int
+__bam_adjust(dbc, adjust)
+ DBC *dbc;
+ int32_t adjust;
+{
+ CURSOR *cp;
+ DB *dbp;
+ EPG *epg;
+ PAGE *h;
+ int ret;
+
+ dbp = dbc->dbp;
+ cp = dbc->internal;
+
+ /* Update the record counts for the tree. */
+ for (epg = cp->sp; epg <= cp->csp; ++epg) {
+ h = epg->page;
+ if (TYPE(h) == P_IBTREE || TYPE(h) == P_IRECNO) {
+ if (DB_LOGGING(dbc) &&
+ (ret = __bam_cadjust_log(dbp->dbenv->lg_info,
+ dbc->txn, &LSN(h), 0, dbp->log_fileid,
+ PGNO(h), &LSN(h), (u_int32_t)epg->indx,
+ adjust, 1)) != 0)
+ return (ret);
+
+ if (TYPE(h) == P_IBTREE)
+ GET_BINTERNAL(h, epg->indx)->nrecs += adjust;
+ else
+ GET_RINTERNAL(h, epg->indx)->nrecs += adjust;
+
+ if (PGNO(h) == PGNO_ROOT)
+ RE_NREC_ADJ(h, adjust);
+
+ if ((ret = memp_fset(dbp->mpf, h, DB_MPOOL_DIRTY)) != 0)
+ return (ret);
+ }
+ }
+ return (0);
+}
+
+/*
+ * __bam_nrecs --
+ * Return the number of records in the tree.
+ *
+ * PUBLIC: int __bam_nrecs __P((DBC *, db_recno_t *));
+ */
+int
+__bam_nrecs(dbc, rep)
+ DBC *dbc;
+ db_recno_t *rep;
+{
+ DB *dbp;
+ DB_LOCK lock;
+ PAGE *h;
+ db_pgno_t pgno;
+ int ret;
+
+ dbp = dbc->dbp;
+
+ pgno = PGNO_ROOT;
+ if ((ret = __bam_lget(dbc, 0, pgno, DB_LOCK_READ, &lock)) != 0)
+ return (ret);
+ if ((ret = memp_fget(dbp->mpf, &pgno, 0, &h)) != 0)
+ return (ret);
+
+ *rep = RE_NREC(h);
+
+ (void)memp_fput(dbp->mpf, h, 0);
+ (void)__BT_TLPUT(dbc, lock);
+
+ return (0);
+}
+
+/*
+ * __bam_total --
+ * Return the number of records below a page.
+ *
+ * PUBLIC: db_recno_t __bam_total __P((PAGE *));
+ */
+db_recno_t
+__bam_total(h)
+ PAGE *h;
+{
+ db_recno_t nrecs;
+ db_indx_t indx, top;
+
+ nrecs = 0;
+ top = NUM_ENT(h);
+
+ switch (TYPE(h)) {
+ case P_LBTREE:
+ /* Check for logically deleted records. */
+ for (indx = 0; indx < top; indx += P_INDX)
+ if (!B_DISSET(GET_BKEYDATA(h, indx + O_INDX)->type))
+ ++nrecs;
+ break;
+ case P_IBTREE:
+ for (indx = 0; indx < top; indx += O_INDX)
+ nrecs += GET_BINTERNAL(h, indx)->nrecs;
+ break;
+ case P_LRECNO:
+ nrecs = NUM_ENT(h);
+ break;
+ case P_IRECNO:
+ for (indx = 0; indx < top; indx += O_INDX)
+ nrecs += GET_RINTERNAL(h, indx)->nrecs;
+ break;
+ }
+
+ return (nrecs);
+}
diff --git a/usr/src/cmd/sendmail/db/btree/bt_search.c b/usr/src/cmd/sendmail/db/btree/bt_search.c
new file mode 100644
index 0000000000..888fbc923f
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/btree/bt_search.c
@@ -0,0 +1,368 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+/*
+ * Copyright (c) 1990, 1993, 1994, 1995, 1996
+ * Keith Bostic. All rights reserved.
+ */
+/*
+ * Copyright (c) 1990, 1993, 1994, 1995
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Mike Olson.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)bt_search.c 10.25 (Sleepycat) 12/16/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <errno.h>
+#include <string.h>
+#endif
+
+#include "db_int.h"
+#include "db_page.h"
+#include "btree.h"
+
+/*
+ * __bam_search --
+ * Search a btree for a key.
+ *
+ * PUBLIC: int __bam_search __P((DBC *,
+ * PUBLIC: const DBT *, u_int32_t, int, db_recno_t *, int *));
+ */
+int
+__bam_search(dbc, key, flags, stop, recnop, exactp)
+ DBC *dbc;
+ const DBT *key;
+ u_int32_t flags;
+ int stop, *exactp;
+ db_recno_t *recnop;
+{
+ BTREE *t;
+ CURSOR *cp;
+ DB *dbp;
+ DB_LOCK lock;
+ PAGE *h;
+ db_indx_t base, i, indx, lim;
+ db_pgno_t pg;
+ db_recno_t recno;
+ int cmp, jump, ret, stack;
+
+ dbp = dbc->dbp;
+ cp = dbc->internal;
+ t = dbp->internal;
+ recno = 0;
+
+ BT_STK_CLR(cp);
+
+ /*
+ * There are several ways we search a btree tree. The flags argument
+ * specifies if we're acquiring read or write locks, if we position
+ * to the first or last item in a set of duplicates, if we return
+ * deleted items, and if we are locking pairs of pages. In addition,
+ * if we're modifying record numbers, we have to lock the entire tree
+ * regardless. See btree.h for more details.
+ *
+ * If write-locking pages, we need to know whether or not to acquire a
+ * write lock on a page before getting it. This depends on how deep it
+ * is in tree, which we don't know until we acquire the root page. So,
+ * if we need to lock the root page we may have to upgrade it later,
+ * because we won't get the correct lock initially.
+ *
+ * Retrieve the root page.
+ */
+ pg = PGNO_ROOT;
+ stack = F_ISSET(dbp, DB_BT_RECNUM) && LF_ISSET(S_STACK);
+ if ((ret = __bam_lget(dbc,
+ 0, pg, stack ? DB_LOCK_WRITE : DB_LOCK_READ, &lock)) != 0)
+ return (ret);
+ if ((ret = memp_fget(dbp->mpf, &pg, 0, &h)) != 0) {
+ (void)__BT_LPUT(dbc, lock);
+ return (ret);
+ }
+
+ /*
+ * Decide if we need to save this page; if we do, write lock it.
+ * We deliberately don't lock-couple on this call. If the tree
+ * is tiny, i.e., one page, and two threads are busily updating
+ * the root page, we're almost guaranteed deadlocks galore, as
+ * each one gets a read lock and then blocks the other's attempt
+ * for a write lock.
+ */
+ if (!stack &&
+ ((LF_ISSET(S_PARENT) && (u_int8_t)(stop + 1) >= h->level) ||
+ (LF_ISSET(S_WRITE) && h->level == LEAFLEVEL))) {
+ (void)memp_fput(dbp->mpf, h, 0);
+ (void)__BT_LPUT(dbc, lock);
+ if ((ret = __bam_lget(dbc, 0, pg, DB_LOCK_WRITE, &lock)) != 0)
+ return (ret);
+ if ((ret = memp_fget(dbp->mpf, &pg, 0, &h)) != 0) {
+ (void)__BT_LPUT(dbc, lock);
+ return (ret);
+ }
+ stack = 1;
+ }
+
+ for (;;) {
+ /*
+ * Do a binary search on the current page. If we're searching
+ * a leaf page, we have to manipulate the indices in groups of
+ * two. If we're searching an internal page, they're an index
+ * per page item. If we find an exact match on a leaf page,
+ * we're done.
+ */
+ jump = TYPE(h) == P_LBTREE ? P_INDX : O_INDX;
+ for (base = 0,
+ lim = NUM_ENT(h) / (db_indx_t)jump; lim != 0; lim >>= 1) {
+ indx = base + ((lim >> 1) * jump);
+ if ((cmp =
+ __bam_cmp(dbp, key, h, indx, t->bt_compare)) == 0) {
+ if (TYPE(h) == P_LBTREE)
+ goto match;
+ goto next;
+ }
+ if (cmp > 0) {
+ base = indx + jump;
+ --lim;
+ }
+ }
+
+ /*
+ * No match found. Base is the smallest index greater than
+ * key and may be zero or a last + O_INDX index.
+ *
+ * If it's a leaf page, return base as the "found" value.
+ * Delete only deletes exact matches.
+ */
+ if (TYPE(h) == P_LBTREE) {
+ *exactp = 0;
+
+ if (LF_ISSET(S_EXACT))
+ goto notfound;
+
+ /*
+ * !!!
+ * Possibly returning a deleted record -- DB_SET_RANGE,
+ * DB_KEYFIRST and DB_KEYLAST don't require an exact
+ * match, and we don't want to walk multiple pages here
+ * to find an undeleted record. This is handled in the
+ * __bam_c_search() routine.
+ */
+ BT_STK_ENTER(cp, h, base, lock, ret);
+ return (ret);
+ }
+
+ /*
+ * If it's not a leaf page, record the internal page (which is
+ * a parent page for the key). Decrement the base by 1 if it's
+ * non-zero so that if a split later occurs, the inserted page
+ * will be to the right of the saved page.
+ */
+ indx = base > 0 ? base - O_INDX : base;
+
+ /*
+ * If we're trying to calculate the record number, sum up
+ * all the record numbers on this page up to the indx point.
+ */
+ if (recnop != NULL)
+ for (i = 0; i < indx; ++i)
+ recno += GET_BINTERNAL(h, i)->nrecs;
+
+next: pg = GET_BINTERNAL(h, indx)->pgno;
+ if (stack) {
+ /* Return if this is the lowest page wanted. */
+ if (LF_ISSET(S_PARENT) && stop == h->level) {
+ BT_STK_ENTER(cp, h, indx, lock, ret);
+ return (ret);
+ }
+ BT_STK_PUSH(cp, h, indx, lock, ret);
+ if (ret != 0)
+ goto err;
+
+ if ((ret =
+ __bam_lget(dbc, 0, pg, DB_LOCK_WRITE, &lock)) != 0)
+ goto err;
+ } else {
+ /*
+ * Decide if we want to return a reference to the next
+ * page in the return stack. If so, lock it and never
+ * unlock it.
+ */
+ if ((LF_ISSET(S_PARENT) &&
+ (u_int8_t)(stop + 1) >= (u_int8_t)(h->level - 1)) ||
+ (h->level - 1) == LEAFLEVEL)
+ stack = 1;
+
+ (void)memp_fput(dbp->mpf, h, 0);
+
+ if ((ret =
+ __bam_lget(dbc, 1, pg, stack && LF_ISSET(S_WRITE) ?
+ DB_LOCK_WRITE : DB_LOCK_READ, &lock)) != 0)
+ goto err;
+ }
+ if ((ret = memp_fget(dbp->mpf, &pg, 0, &h)) != 0)
+ goto err;
+ }
+ /* NOTREACHED */
+
+match: *exactp = 1;
+
+ /*
+ * If we're trying to calculate the record number, add in the
+ * offset on this page and correct for the fact that records
+ * in the tree are 0-based.
+ */
+ if (recnop != NULL)
+ *recnop = recno + (indx / P_INDX) + 1;
+
+ /*
+ * If we got here, we know that we have a btree leaf page.
+ *
+ * If there are duplicates, go to the first/last one. This is
+ * safe because we know that we're not going to leave the page,
+ * all duplicate sets that are not on overflow pages exist on a
+ * single leaf page.
+ */
+ if (LF_ISSET(S_DUPLAST))
+ while (indx < (db_indx_t)(NUM_ENT(h) - P_INDX) &&
+ h->inp[indx] == h->inp[indx + P_INDX])
+ indx += P_INDX;
+ else
+ while (indx > 0 &&
+ h->inp[indx] == h->inp[indx - P_INDX])
+ indx -= P_INDX;
+
+ /*
+ * Now check if we are allowed to return deleted items; if not
+ * find the next (or previous) non-deleted item.
+ */
+ if (LF_ISSET(S_DELNO)) {
+ if (LF_ISSET(S_DUPLAST))
+ while (B_DISSET(GET_BKEYDATA(h, indx + O_INDX)->type) &&
+ indx > 0 &&
+ h->inp[indx] == h->inp[indx - P_INDX])
+ indx -= P_INDX;
+ else
+ while (B_DISSET(GET_BKEYDATA(h, indx + O_INDX)->type) &&
+ indx < (db_indx_t)(NUM_ENT(h) - P_INDX) &&
+ h->inp[indx] == h->inp[indx + P_INDX])
+ indx += P_INDX;
+
+ if (B_DISSET(GET_BKEYDATA(h, indx + O_INDX)->type))
+ goto notfound;
+ }
+
+ BT_STK_ENTER(cp, h, indx, lock, ret);
+ return (ret);
+
+notfound:
+ (void)memp_fput(dbp->mpf, h, 0);
+ (void)__BT_LPUT(dbc, lock);
+ ret = DB_NOTFOUND;
+
+err: if (cp->csp > cp->sp) {
+ BT_STK_POP(cp);
+ __bam_stkrel(dbc, 0);
+ }
+ return (ret);
+}
+
+/*
+ * __bam_stkrel --
+ * Release all pages currently held in the stack.
+ *
+ * PUBLIC: int __bam_stkrel __P((DBC *, int));
+ */
+int
+__bam_stkrel(dbc, nolocks)
+ DBC *dbc;
+ int nolocks;
+{
+ CURSOR *cp;
+ DB *dbp;
+ EPG *epg;
+
+ dbp = dbc->dbp;
+ cp = dbc->internal;
+
+ /* Release inner pages first. */
+ for (epg = cp->sp; epg <= cp->csp; ++epg) {
+ if (epg->page != NULL)
+ (void)memp_fput(dbp->mpf, epg->page, 0);
+ if (epg->lock != LOCK_INVALID)
+ if (nolocks)
+ (void)__BT_LPUT(dbc, epg->lock);
+ else
+ (void)__BT_TLPUT(dbc, epg->lock);
+ }
+
+ /* Clear the stack, all pages have been released. */
+ BT_STK_CLR(cp);
+
+ return (0);
+}
+
+/*
+ * __bam_stkgrow --
+ * Grow the stack.
+ *
+ * PUBLIC: int __bam_stkgrow __P((CURSOR *));
+ */
+int
+__bam_stkgrow(cp)
+ CURSOR *cp;
+{
+ EPG *p;
+ size_t entries;
+ int ret;
+
+ entries = cp->esp - cp->sp;
+
+ if ((ret = __os_calloc(entries * 2, sizeof(EPG), &p)) != 0)
+ return (ret);
+ memcpy(p, cp->sp, entries * sizeof(EPG));
+ if (cp->sp != cp->stack)
+ __os_free(cp->sp, entries * sizeof(EPG));
+ cp->sp = p;
+ cp->csp = p + entries;
+ cp->esp = p + entries * 2;
+ return (0);
+}
diff --git a/usr/src/cmd/sendmail/db/btree/bt_split.c b/usr/src/cmd/sendmail/db/btree/bt_split.c
new file mode 100644
index 0000000000..ede8f64e89
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/btree/bt_split.c
@@ -0,0 +1,978 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+/*
+ * Copyright (c) 1990, 1993, 1994, 1995, 1996
+ * Keith Bostic. All rights reserved.
+ */
+/*
+ * Copyright (c) 1990, 1993, 1994, 1995
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)bt_split.c 10.33 (Sleepycat) 10/13/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <errno.h>
+#include <limits.h>
+#include <string.h>
+#endif
+
+#include "db_int.h"
+#include "db_page.h"
+#include "btree.h"
+
+static int __bam_broot __P((DBC *, PAGE *, PAGE *, PAGE *));
+static int __bam_page __P((DBC *, EPG *, EPG *));
+static int __bam_pinsert __P((DBC *, EPG *, PAGE *, PAGE *));
+static int __bam_psplit __P((DBC *, EPG *, PAGE *, PAGE *, db_indx_t *));
+static int __bam_root __P((DBC *, EPG *));
+static int __ram_root __P((DBC *, PAGE *, PAGE *, PAGE *));
+
+/*
+ * __bam_split --
+ * Split a page.
+ *
+ * PUBLIC: int __bam_split __P((DBC *, void *));
+ */
+int
+__bam_split(dbc, arg)
+ DBC *dbc;
+ void *arg;
+{
+ BTREE *t;
+ CURSOR *cp;
+ DB *dbp;
+ enum { UP, DOWN } dir;
+ int exact, level, ret;
+
+ dbp = dbc->dbp;
+ cp = dbc->internal;
+
+ /*
+ * The locking protocol we use to avoid deadlock to acquire locks by
+ * walking down the tree, but we do it as lazily as possible, locking
+ * the root only as a last resort. We expect all stack pages to have
+ * been discarded before we're called; we discard all short-term locks.
+ *
+ * When __bam_split is first called, we know that a leaf page was too
+ * full for an insert. We don't know what leaf page it was, but we
+ * have the key/recno that caused the problem. We call XX_search to
+ * reacquire the leaf page, but this time get both the leaf page and
+ * its parent, locked. We then split the leaf page and see if the new
+ * internal key will fit into the parent page. If it will, we're done.
+ *
+ * If it won't, we discard our current locks and repeat the process,
+ * only this time acquiring the parent page and its parent, locked.
+ * This process repeats until we succeed in the split, splitting the
+ * root page as the final resort. The entire process then repeats,
+ * as necessary, until we split a leaf page.
+ *
+ * XXX
+ * A traditional method of speeding this up is to maintain a stack of
+ * the pages traversed in the original search. You can detect if the
+ * stack is correct by storing the page's LSN when it was searched and
+ * comparing that LSN with the current one when it's locked during the
+ * split. This would be an easy change for this code, but I have no
+ * numbers that indicate it's worthwhile.
+ */
+ t = dbp->internal;
+ for (dir = UP, level = LEAFLEVEL;; dir == UP ? ++level : --level) {
+ /*
+ * Acquire a page and its parent, locked.
+ */
+ if ((ret = (dbp->type == DB_BTREE ?
+ __bam_search(dbc, arg, S_WRPAIR, level, NULL, &exact) :
+ __bam_rsearch(dbc,
+ (db_recno_t *)arg, S_WRPAIR, level, &exact))) != 0)
+ return (ret);
+
+ /*
+ * Split the page if it still needs it (it's possible another
+ * thread of control has already split the page). If we are
+ * guaranteed that two items will fit on the page, the split
+ * is no longer necessary.
+ */
+ if (t->bt_ovflsize * 2 <=
+ (db_indx_t)P_FREESPACE(cp->csp[0].page)) {
+ __bam_stkrel(dbc, 1);
+ return (0);
+ }
+ ret = cp->csp[0].page->pgno == PGNO_ROOT ?
+ __bam_root(dbc, &cp->csp[0]) :
+ __bam_page(dbc, &cp->csp[-1], &cp->csp[0]);
+ BT_STK_CLR(cp);
+
+ switch (ret) {
+ case 0:
+ /* Once we've split the leaf page, we're done. */
+ if (level == LEAFLEVEL)
+ return (0);
+
+ /* Switch directions. */
+ if (dir == UP)
+ dir = DOWN;
+ break;
+ case DB_NEEDSPLIT:
+ /*
+ * It's possible to fail to split repeatedly, as other
+ * threads may be modifying the tree, or the page usage
+ * is sufficiently bad that we don't get enough space
+ * the first time.
+ */
+ if (dir == DOWN)
+ dir = UP;
+ break;
+ default:
+ return (ret);
+ }
+ }
+ /* NOTREACHED */
+}
+
+/*
+ * __bam_root --
+ * Split the root page of a btree.
+ */
+static int
+__bam_root(dbc, cp)
+ DBC *dbc;
+ EPG *cp;
+{
+ DB *dbp;
+ PAGE *lp, *rp;
+ db_indx_t split;
+ int ret;
+
+ dbp = dbc->dbp;
+
+ /* Yeah, right. */
+ if (cp->page->level >= MAXBTREELEVEL) {
+ ret = ENOSPC;
+ goto err;
+ }
+
+ /* Create new left and right pages for the split. */
+ lp = rp = NULL;
+ if ((ret = __bam_new(dbc, TYPE(cp->page), &lp)) != 0 ||
+ (ret = __bam_new(dbc, TYPE(cp->page), &rp)) != 0)
+ goto err;
+ P_INIT(lp, dbp->pgsize, lp->pgno,
+ PGNO_INVALID, ISINTERNAL(cp->page) ? PGNO_INVALID : rp->pgno,
+ cp->page->level, TYPE(cp->page));
+ P_INIT(rp, dbp->pgsize, rp->pgno,
+ ISINTERNAL(cp->page) ? PGNO_INVALID : lp->pgno, PGNO_INVALID,
+ cp->page->level, TYPE(cp->page));
+
+ /* Split the page. */
+ if ((ret = __bam_psplit(dbc, cp, lp, rp, &split)) != 0)
+ goto err;
+
+ /* Log the change. */
+ if (DB_LOGGING(dbc)) {
+ DBT __a;
+ DB_LSN __lsn;
+ memset(&__a, 0, sizeof(__a));
+ __a.data = cp->page;
+ __a.size = dbp->pgsize;
+ ZERO_LSN(__lsn);
+ if ((ret = __bam_split_log(dbp->dbenv->lg_info, dbc->txn,
+ &LSN(cp->page), 0, dbp->log_fileid, PGNO(lp), &LSN(lp),
+ PGNO(rp), &LSN(rp), (u_int32_t)NUM_ENT(lp), 0, &__lsn,
+ &__a)) != 0)
+ goto err;
+ LSN(lp) = LSN(rp) = LSN(cp->page);
+ }
+
+ /* Clean up the new root page. */
+ if ((ret = (dbp->type == DB_RECNO ?
+ __ram_root(dbc, cp->page, lp, rp) :
+ __bam_broot(dbc, cp->page, lp, rp))) != 0)
+ goto err;
+
+ /* Adjust any cursors. Do it last so we don't have to undo it. */
+ __bam_ca_split(dbp, cp->page->pgno, lp->pgno, rp->pgno, split, 1);
+
+ /* Success -- write the real pages back to the store. */
+ (void)memp_fput(dbp->mpf, cp->page, DB_MPOOL_DIRTY);
+ (void)__BT_TLPUT(dbc, cp->lock);
+ (void)memp_fput(dbp->mpf, lp, DB_MPOOL_DIRTY);
+ (void)memp_fput(dbp->mpf, rp, DB_MPOOL_DIRTY);
+
+ return (0);
+
+err: if (lp != NULL)
+ (void)__bam_free(dbc, lp);
+ if (rp != NULL)
+ (void)__bam_free(dbc, rp);
+ (void)memp_fput(dbp->mpf, cp->page, 0);
+ (void)__BT_TLPUT(dbc, cp->lock);
+ return (ret);
+}
+
+/*
+ * __bam_page --
+ * Split the non-root page of a btree.
+ */
+static int
+__bam_page(dbc, pp, cp)
+ DBC *dbc;
+ EPG *pp, *cp;
+{
+ DB *dbp;
+ DB_LOCK tplock;
+ PAGE *lp, *rp, *tp;
+ db_indx_t split;
+ int ret;
+
+ dbp = dbc->dbp;
+ lp = rp = tp = NULL;
+ ret = -1;
+
+ /* Create new right page for the split. */
+ if ((ret = __bam_new(dbc, TYPE(cp->page), &rp)) != 0)
+ goto err;
+ P_INIT(rp, dbp->pgsize, rp->pgno,
+ ISINTERNAL(cp->page) ? PGNO_INVALID : cp->page->pgno,
+ ISINTERNAL(cp->page) ? PGNO_INVALID : cp->page->next_pgno,
+ cp->page->level, TYPE(cp->page));
+
+ /* Create new left page for the split. */
+ if ((ret = __os_malloc(dbp->pgsize, NULL, &lp)) != 0)
+ goto err;
+ P_INIT(lp, dbp->pgsize, cp->page->pgno,
+ ISINTERNAL(cp->page) ? PGNO_INVALID : cp->page->prev_pgno,
+ ISINTERNAL(cp->page) ? PGNO_INVALID : rp->pgno,
+ cp->page->level, TYPE(cp->page));
+ ZERO_LSN(lp->lsn);
+
+ /*
+ * Split right.
+ *
+ * Only the indices are sorted on the page, i.e., the key/data pairs
+ * aren't, so it's simpler to copy the data from the split page onto
+ * two new pages instead of copying half the data to the right page
+ * and compacting the left page in place. Since the left page can't
+ * change, we swap the original and the allocated left page after the
+ * split.
+ */
+ if ((ret = __bam_psplit(dbc, cp, lp, rp, &split)) != 0)
+ goto err;
+
+ /*
+ * Fix up the previous pointer of any leaf page following the split
+ * page.
+ *
+ * !!!
+ * There are interesting deadlock situations here as we write-lock a
+ * page that's not in our direct ancestry. Consider a cursor walking
+ * through the leaf pages, that has the previous page read-locked and
+ * is waiting on a lock for the page we just split. It will deadlock
+ * here. If this is a problem, we can fail in the split; it's not a
+ * problem as the split will succeed after the cursor passes through
+ * the page we're splitting.
+ */
+ if (TYPE(cp->page) == P_LBTREE && rp->next_pgno != PGNO_INVALID) {
+ if ((ret = __bam_lget(dbc,
+ 0, rp->next_pgno, DB_LOCK_WRITE, &tplock)) != 0)
+ goto err;
+ if ((ret = memp_fget(dbp->mpf, &rp->next_pgno, 0, &tp)) != 0)
+ goto err;
+ }
+
+ /* Insert the new pages into the parent page. */
+ if ((ret = __bam_pinsert(dbc, pp, lp, rp)) != 0)
+ goto err;
+
+ /* Log the change. */
+ if (DB_LOGGING(dbc)) {
+ DBT __a;
+ DB_LSN __lsn;
+ memset(&__a, 0, sizeof(__a));
+ __a.data = cp->page;
+ __a.size = dbp->pgsize;
+ if (tp == NULL)
+ ZERO_LSN(__lsn);
+ if ((ret = __bam_split_log(dbp->dbenv->lg_info, dbc->txn,
+ &cp->page->lsn, 0, dbp->log_fileid, PGNO(cp->page),
+ &LSN(cp->page), PGNO(rp), &LSN(rp), (u_int32_t)NUM_ENT(lp),
+ tp == NULL ? 0 : PGNO(tp),
+ tp == NULL ? &__lsn : &LSN(tp), &__a)) != 0)
+ goto err;
+
+ LSN(lp) = LSN(rp) = LSN(cp->page);
+ if (tp != NULL)
+ LSN(tp) = LSN(cp->page);
+ }
+
+ /* Copy the allocated page into place. */
+ memcpy(cp->page, lp, LOFFSET(lp));
+ memcpy((u_int8_t *)cp->page + HOFFSET(lp),
+ (u_int8_t *)lp + HOFFSET(lp), dbp->pgsize - HOFFSET(lp));
+ __os_free(lp, dbp->pgsize);
+ lp = NULL;
+
+ /* Finish the next-page link. */
+ if (tp != NULL)
+ tp->prev_pgno = rp->pgno;
+
+ /* Adjust any cursors. Do so last so we don't have to undo it. */
+ __bam_ca_split(dbp, cp->page->pgno, cp->page->pgno, rp->pgno, split, 0);
+
+ /* Success -- write the real pages back to the store. */
+ (void)memp_fput(dbp->mpf, pp->page, DB_MPOOL_DIRTY);
+ (void)__BT_TLPUT(dbc, pp->lock);
+ (void)memp_fput(dbp->mpf, cp->page, DB_MPOOL_DIRTY);
+ (void)__BT_TLPUT(dbc, cp->lock);
+ (void)memp_fput(dbp->mpf, rp, DB_MPOOL_DIRTY);
+ if (tp != NULL) {
+ (void)memp_fput(dbp->mpf, tp, DB_MPOOL_DIRTY);
+ (void)__BT_TLPUT(dbc, tplock);
+ }
+ return (0);
+
+err: if (lp != NULL)
+ __os_free(lp, dbp->pgsize);
+ if (rp != NULL)
+ (void)__bam_free(dbc, rp);
+ if (tp != NULL) {
+ (void)memp_fput(dbp->mpf, tp, 0);
+ if (ret == DB_NEEDSPLIT)
+ (void)__BT_LPUT(dbc, tplock);
+ else
+ (void)__BT_TLPUT(dbc, tplock);
+ }
+ (void)memp_fput(dbp->mpf, pp->page, 0);
+ if (ret == DB_NEEDSPLIT)
+ (void)__BT_LPUT(dbc, pp->lock);
+ else
+ (void)__BT_TLPUT(dbc, pp->lock);
+ (void)memp_fput(dbp->mpf, cp->page, 0);
+ if (ret == DB_NEEDSPLIT)
+ (void)__BT_LPUT(dbc, cp->lock);
+ else
+ (void)__BT_TLPUT(dbc, cp->lock);
+ return (ret);
+}
+
+/*
+ * __bam_broot --
+ * Fix up the btree root page after it has been split.
+ */
+static int
+__bam_broot(dbc, rootp, lp, rp)
+ DBC *dbc;
+ PAGE *rootp, *lp, *rp;
+{
+ BINTERNAL bi, *child_bi;
+ BKEYDATA *child_bk;
+ DB *dbp;
+ DBT hdr, data;
+ int ret;
+
+ dbp = dbc->dbp;
+
+ /*
+ * If the root page was a leaf page, change it into an internal page.
+ * We copy the key we split on (but not the key's data, in the case of
+ * a leaf page) to the new root page.
+ */
+ P_INIT(rootp, dbp->pgsize,
+ PGNO_ROOT, PGNO_INVALID, PGNO_INVALID, lp->level + 1, P_IBTREE);
+
+ memset(&data, 0, sizeof(data));
+ memset(&hdr, 0, sizeof(hdr));
+
+ /*
+ * The btree comparison code guarantees that the left-most key on any
+ * level of the tree is never used, so it doesn't need to be filled in.
+ */
+ memset(&bi, 0, sizeof(bi));
+ bi.len = 0;
+ B_TSET(bi.type, B_KEYDATA, 0);
+ bi.pgno = lp->pgno;
+ if (F_ISSET(dbp, DB_BT_RECNUM)) {
+ bi.nrecs = __bam_total(lp);
+ RE_NREC_SET(rootp, bi.nrecs);
+ }
+ hdr.data = &bi;
+ hdr.size = SSZA(BINTERNAL, data);
+ if ((ret =
+ __db_pitem(dbc, rootp, 0, BINTERNAL_SIZE(0), &hdr, NULL)) != 0)
+ return (ret);
+
+ switch (TYPE(rp)) {
+ case P_IBTREE:
+ /* Copy the first key of the child page onto the root page. */
+ child_bi = GET_BINTERNAL(rp, 0);
+
+ bi.len = child_bi->len;
+ B_TSET(bi.type, child_bi->type, 0);
+ bi.pgno = rp->pgno;
+ if (F_ISSET(dbp, DB_BT_RECNUM)) {
+ bi.nrecs = __bam_total(rp);
+ RE_NREC_ADJ(rootp, bi.nrecs);
+ }
+ hdr.data = &bi;
+ hdr.size = SSZA(BINTERNAL, data);
+ data.data = child_bi->data;
+ data.size = child_bi->len;
+ if ((ret = __db_pitem(dbc, rootp, 1,
+ BINTERNAL_SIZE(child_bi->len), &hdr, &data)) != 0)
+ return (ret);
+
+ /* Increment the overflow ref count. */
+ if (B_TYPE(child_bi->type) == B_OVERFLOW)
+ if ((ret = __db_ovref(dbc,
+ ((BOVERFLOW *)(child_bi->data))->pgno, 1)) != 0)
+ return (ret);
+ break;
+ case P_LBTREE:
+ /* Copy the first key of the child page onto the root page. */
+ child_bk = GET_BKEYDATA(rp, 0);
+ switch (B_TYPE(child_bk->type)) {
+ case B_KEYDATA:
+ bi.len = child_bk->len;
+ B_TSET(bi.type, child_bk->type, 0);
+ bi.pgno = rp->pgno;
+ if (F_ISSET(dbp, DB_BT_RECNUM)) {
+ bi.nrecs = __bam_total(rp);
+ RE_NREC_ADJ(rootp, bi.nrecs);
+ }
+ hdr.data = &bi;
+ hdr.size = SSZA(BINTERNAL, data);
+ data.data = child_bk->data;
+ data.size = child_bk->len;
+ if ((ret = __db_pitem(dbc, rootp, 1,
+ BINTERNAL_SIZE(child_bk->len), &hdr, &data)) != 0)
+ return (ret);
+ break;
+ case B_DUPLICATE:
+ case B_OVERFLOW:
+ bi.len = BOVERFLOW_SIZE;
+ B_TSET(bi.type, child_bk->type, 0);
+ bi.pgno = rp->pgno;
+ if (F_ISSET(dbp, DB_BT_RECNUM)) {
+ bi.nrecs = __bam_total(rp);
+ RE_NREC_ADJ(rootp, bi.nrecs);
+ }
+ hdr.data = &bi;
+ hdr.size = SSZA(BINTERNAL, data);
+ data.data = child_bk;
+ data.size = BOVERFLOW_SIZE;
+ if ((ret = __db_pitem(dbc, rootp, 1,
+ BINTERNAL_SIZE(BOVERFLOW_SIZE), &hdr, &data)) != 0)
+ return (ret);
+
+ /* Increment the overflow ref count. */
+ if (B_TYPE(child_bk->type) == B_OVERFLOW)
+ if ((ret = __db_ovref(dbc,
+ ((BOVERFLOW *)child_bk)->pgno, 1)) != 0)
+ return (ret);
+ break;
+ default:
+ return (__db_pgfmt(dbp, rp->pgno));
+ }
+ break;
+ default:
+ return (__db_pgfmt(dbp, rp->pgno));
+ }
+ return (0);
+}
+
+/*
+ * __ram_root --
+ * Fix up the recno root page after it has been split.
+ */
+static int
+__ram_root(dbc, rootp, lp, rp)
+ DBC *dbc;
+ PAGE *rootp, *lp, *rp;
+{
+ DB *dbp;
+ DBT hdr;
+ RINTERNAL ri;
+ int ret;
+
+ dbp = dbc->dbp;
+
+ /* Initialize the page. */
+ P_INIT(rootp, dbp->pgsize,
+ PGNO_ROOT, PGNO_INVALID, PGNO_INVALID, lp->level + 1, P_IRECNO);
+
+ /* Initialize the header. */
+ memset(&hdr, 0, sizeof(hdr));
+ hdr.data = &ri;
+ hdr.size = RINTERNAL_SIZE;
+
+ /* Insert the left and right keys, set the header information. */
+ ri.pgno = lp->pgno;
+ ri.nrecs = __bam_total(lp);
+ if ((ret = __db_pitem(dbc, rootp, 0, RINTERNAL_SIZE, &hdr, NULL)) != 0)
+ return (ret);
+ RE_NREC_SET(rootp, ri.nrecs);
+ ri.pgno = rp->pgno;
+ ri.nrecs = __bam_total(rp);
+ if ((ret = __db_pitem(dbc, rootp, 1, RINTERNAL_SIZE, &hdr, NULL)) != 0)
+ return (ret);
+ RE_NREC_ADJ(rootp, ri.nrecs);
+ return (0);
+}
+
+/*
+ * __bam_pinsert --
+ * Insert a new key into a parent page, completing the split.
+ */
+static int
+__bam_pinsert(dbc, parent, lchild, rchild)
+ DBC *dbc;
+ EPG *parent;
+ PAGE *lchild, *rchild;
+{
+ BINTERNAL bi, *child_bi;
+ BKEYDATA *child_bk, *tmp_bk;
+ BTREE *t;
+ DB *dbp;
+ DBT a, b, hdr, data;
+ PAGE *ppage;
+ RINTERNAL ri;
+ db_indx_t off;
+ db_recno_t nrecs;
+ u_int32_t n, nbytes, nksize;
+ int ret;
+
+ dbp = dbc->dbp;
+ t = dbp->internal;
+ ppage = parent->page;
+
+ /* If handling record numbers, count records split to the right page. */
+ nrecs = dbp->type == DB_RECNO || F_ISSET(dbp, DB_BT_RECNUM) ?
+ __bam_total(rchild) : 0;
+
+ /*
+ * Now we insert the new page's first key into the parent page, which
+ * completes the split. The parent points to a PAGE and a page index
+ * offset, where the new key goes ONE AFTER the index, because we split
+ * to the right.
+ *
+ * XXX
+ * Some btree algorithms replace the key for the old page as well as
+ * the new page. We don't, as there's no reason to believe that the
+ * first key on the old page is any better than the key we have, and,
+ * in the case of a key being placed at index 0 causing the split, the
+ * key is unavailable.
+ */
+ off = parent->indx + O_INDX;
+
+ /*
+ * Calculate the space needed on the parent page.
+ *
+ * Prefix trees: space hack used when inserting into BINTERNAL pages.
+ * Retain only what's needed to distinguish between the new entry and
+ * the LAST entry on the page to its left. If the keys compare equal,
+ * retain the entire key. We ignore overflow keys, and the entire key
+ * must be retained for the next-to-leftmost key on the leftmost page
+ * of each level, or the search will fail. Applicable ONLY to internal
+ * pages that have leaf pages as children. Further reduction of the
+ * key between pairs of internal pages loses too much information.
+ */
+ switch (TYPE(rchild)) {
+ case P_IBTREE:
+ child_bi = GET_BINTERNAL(rchild, 0);
+ nbytes = BINTERNAL_PSIZE(child_bi->len);
+
+ if (P_FREESPACE(ppage) < nbytes)
+ return (DB_NEEDSPLIT);
+
+ /* Add a new record for the right page. */
+ memset(&bi, 0, sizeof(bi));
+ bi.len = child_bi->len;
+ B_TSET(bi.type, child_bi->type, 0);
+ bi.pgno = rchild->pgno;
+ bi.nrecs = nrecs;
+ memset(&hdr, 0, sizeof(hdr));
+ hdr.data = &bi;
+ hdr.size = SSZA(BINTERNAL, data);
+ memset(&data, 0, sizeof(data));
+ data.data = child_bi->data;
+ data.size = child_bi->len;
+ if ((ret = __db_pitem(dbc, ppage, off,
+ BINTERNAL_SIZE(child_bi->len), &hdr, &data)) != 0)
+ return (ret);
+
+ /* Increment the overflow ref count. */
+ if (B_TYPE(child_bi->type) == B_OVERFLOW)
+ if ((ret = __db_ovref(dbc,
+ ((BOVERFLOW *)(child_bi->data))->pgno, 1)) != 0)
+ return (ret);
+ break;
+ case P_LBTREE:
+ child_bk = GET_BKEYDATA(rchild, 0);
+ switch (B_TYPE(child_bk->type)) {
+ case B_KEYDATA:
+ nbytes = BINTERNAL_PSIZE(child_bk->len);
+ nksize = child_bk->len;
+ if (t->bt_prefix == NULL)
+ goto noprefix;
+ if (ppage->prev_pgno == PGNO_INVALID && off <= 1)
+ goto noprefix;
+ tmp_bk = GET_BKEYDATA(lchild, NUM_ENT(lchild) - P_INDX);
+ if (B_TYPE(tmp_bk->type) != B_KEYDATA)
+ goto noprefix;
+ memset(&a, 0, sizeof(a));
+ a.size = tmp_bk->len;
+ a.data = tmp_bk->data;
+ memset(&b, 0, sizeof(b));
+ b.size = child_bk->len;
+ b.data = child_bk->data;
+ nksize = t->bt_prefix(&a, &b);
+ if ((n = BINTERNAL_PSIZE(nksize)) < nbytes)
+ nbytes = n;
+ else
+noprefix: nksize = child_bk->len;
+
+ if (P_FREESPACE(ppage) < nbytes)
+ return (DB_NEEDSPLIT);
+
+ memset(&bi, 0, sizeof(bi));
+ bi.len = nksize;
+ B_TSET(bi.type, child_bk->type, 0);
+ bi.pgno = rchild->pgno;
+ bi.nrecs = nrecs;
+ memset(&hdr, 0, sizeof(hdr));
+ hdr.data = &bi;
+ hdr.size = SSZA(BINTERNAL, data);
+ memset(&data, 0, sizeof(data));
+ data.data = child_bk->data;
+ data.size = nksize;
+ if ((ret = __db_pitem(dbc, ppage, off,
+ BINTERNAL_SIZE(nksize), &hdr, &data)) != 0)
+ return (ret);
+ break;
+ case B_DUPLICATE:
+ case B_OVERFLOW:
+ nbytes = BINTERNAL_PSIZE(BOVERFLOW_SIZE);
+
+ if (P_FREESPACE(ppage) < nbytes)
+ return (DB_NEEDSPLIT);
+
+ memset(&bi, 0, sizeof(bi));
+ bi.len = BOVERFLOW_SIZE;
+ B_TSET(bi.type, child_bk->type, 0);
+ bi.pgno = rchild->pgno;
+ bi.nrecs = nrecs;
+ memset(&hdr, 0, sizeof(hdr));
+ hdr.data = &bi;
+ hdr.size = SSZA(BINTERNAL, data);
+ memset(&data, 0, sizeof(data));
+ data.data = child_bk;
+ data.size = BOVERFLOW_SIZE;
+ if ((ret = __db_pitem(dbc, ppage, off,
+ BINTERNAL_SIZE(BOVERFLOW_SIZE), &hdr, &data)) != 0)
+ return (ret);
+
+ /* Increment the overflow ref count. */
+ if (B_TYPE(child_bk->type) == B_OVERFLOW)
+ if ((ret = __db_ovref(dbc,
+ ((BOVERFLOW *)child_bk)->pgno, 1)) != 0)
+ return (ret);
+ break;
+ default:
+ return (__db_pgfmt(dbp, rchild->pgno));
+ }
+ break;
+ case P_IRECNO:
+ case P_LRECNO:
+ nbytes = RINTERNAL_PSIZE;
+
+ if (P_FREESPACE(ppage) < nbytes)
+ return (DB_NEEDSPLIT);
+
+ /* Add a new record for the right page. */
+ memset(&hdr, 0, sizeof(hdr));
+ hdr.data = &ri;
+ hdr.size = RINTERNAL_SIZE;
+ ri.pgno = rchild->pgno;
+ ri.nrecs = nrecs;
+ if ((ret = __db_pitem(dbc,
+ ppage, off, RINTERNAL_SIZE, &hdr, NULL)) != 0)
+ return (ret);
+ break;
+ default:
+ return (__db_pgfmt(dbp, rchild->pgno));
+ }
+
+ /* Adjust the parent page's left page record count. */
+ if (dbp->type == DB_RECNO || F_ISSET(dbp, DB_BT_RECNUM)) {
+ /* Log the change. */
+ if (DB_LOGGING(dbc) &&
+ (ret = __bam_cadjust_log(dbp->dbenv->lg_info,
+ dbc->txn, &LSN(ppage), 0, dbp->log_fileid,
+ PGNO(ppage), &LSN(ppage), (u_int32_t)parent->indx,
+ -(int32_t)nrecs, (int32_t)0)) != 0)
+ return (ret);
+
+ /* Update the left page count. */
+ if (dbp->type == DB_RECNO)
+ GET_RINTERNAL(ppage, parent->indx)->nrecs -= nrecs;
+ else
+ GET_BINTERNAL(ppage, parent->indx)->nrecs -= nrecs;
+ }
+
+ return (0);
+}
+
+/*
+ * __bam_psplit --
+ * Do the real work of splitting the page.
+ */
+static int
+__bam_psplit(dbc, cp, lp, rp, splitret)
+ DBC *dbc;
+ EPG *cp;
+ PAGE *lp, *rp;
+ db_indx_t *splitret;
+{
+ DB *dbp;
+ PAGE *pp;
+ db_indx_t half, nbytes, off, splitp, top;
+ int adjust, cnt, isbigkey, ret;
+
+ dbp = dbc->dbp;
+ pp = cp->page;
+ adjust = TYPE(pp) == P_LBTREE ? P_INDX : O_INDX;
+
+ /*
+ * If we're splitting the first (last) page on a level because we're
+ * inserting (appending) a key to it, it's likely that the data is
+ * sorted. Moving a single item to the new page is less work and can
+ * push the fill factor higher than normal. If we're wrong it's not
+ * a big deal, we'll just do the split the right way next time.
+ */
+ off = 0;
+ if (NEXT_PGNO(pp) == PGNO_INVALID &&
+ ((ISINTERNAL(pp) && cp->indx == NUM_ENT(cp->page) - 1) ||
+ (!ISINTERNAL(pp) && cp->indx == NUM_ENT(cp->page))))
+ off = NUM_ENT(cp->page) - adjust;
+ else if (PREV_PGNO(pp) == PGNO_INVALID && cp->indx == 0)
+ off = adjust;
+
+ if (off != 0)
+ goto sort;
+
+ /*
+ * Split the data to the left and right pages. Try not to split on
+ * an overflow key. (Overflow keys on internal pages will slow down
+ * searches.) Refuse to split in the middle of a set of duplicates.
+ *
+ * First, find the optimum place to split.
+ *
+ * It's possible to try and split past the last record on the page if
+ * there's a very large record at the end of the page. Make sure this
+ * doesn't happen by bounding the check at the next-to-last entry on
+ * the page.
+ *
+ * Note, we try and split half the data present on the page. This is
+ * because another process may have already split the page and left
+ * it half empty. We don't try and skip the split -- we don't know
+ * how much space we're going to need on the page, and we may need up
+ * to half the page for a big item, so there's no easy test to decide
+ * if we need to split or not. Besides, if two threads are inserting
+ * data into the same place in the database, we're probably going to
+ * need more space soon anyway.
+ */
+ top = NUM_ENT(pp) - adjust;
+ half = (dbp->pgsize - HOFFSET(pp)) / 2;
+ for (nbytes = 0, off = 0; off < top && nbytes < half; ++off)
+ switch (TYPE(pp)) {
+ case P_IBTREE:
+ if (B_TYPE(GET_BINTERNAL(pp, off)->type) == B_KEYDATA)
+ nbytes +=
+ BINTERNAL_SIZE(GET_BINTERNAL(pp, off)->len);
+ else
+ nbytes += BINTERNAL_SIZE(BOVERFLOW_SIZE);
+ break;
+ case P_LBTREE:
+ if (B_TYPE(GET_BKEYDATA(pp, off)->type) == B_KEYDATA)
+ nbytes +=
+ BKEYDATA_SIZE(GET_BKEYDATA(pp, off)->len);
+ else
+ nbytes += BOVERFLOW_SIZE;
+
+ ++off;
+ if (B_TYPE(GET_BKEYDATA(pp, off)->type) == B_KEYDATA)
+ nbytes +=
+ BKEYDATA_SIZE(GET_BKEYDATA(pp, off)->len);
+ else
+ nbytes += BOVERFLOW_SIZE;
+ break;
+ case P_IRECNO:
+ nbytes += RINTERNAL_SIZE;
+ break;
+ case P_LRECNO:
+ nbytes += BKEYDATA_SIZE(GET_BKEYDATA(pp, off)->len);
+ break;
+ default:
+ return (__db_pgfmt(dbp, pp->pgno));
+ }
+sort: splitp = off;
+
+ /*
+ * Splitp is either at or just past the optimum split point. If
+ * it's a big key, try and find something close by that's not.
+ */
+ if (TYPE(pp) == P_IBTREE)
+ isbigkey = B_TYPE(GET_BINTERNAL(pp, off)->type) != B_KEYDATA;
+ else if (TYPE(pp) == P_LBTREE)
+ isbigkey = B_TYPE(GET_BKEYDATA(pp, off)->type) != B_KEYDATA;
+ else
+ isbigkey = 0;
+ if (isbigkey)
+ for (cnt = 1; cnt <= 3; ++cnt) {
+ off = splitp + cnt * adjust;
+ if (off < (db_indx_t)NUM_ENT(pp) &&
+ ((TYPE(pp) == P_IBTREE &&
+ B_TYPE(GET_BINTERNAL(pp,off)->type) == B_KEYDATA) ||
+ B_TYPE(GET_BKEYDATA(pp, off)->type) == B_KEYDATA)) {
+ splitp = off;
+ break;
+ }
+ if (splitp <= (db_indx_t)(cnt * adjust))
+ continue;
+ off = splitp - cnt * adjust;
+ if (TYPE(pp) == P_IBTREE ?
+ B_TYPE(GET_BINTERNAL(pp, off)->type) == B_KEYDATA :
+ B_TYPE(GET_BKEYDATA(pp, off)->type) == B_KEYDATA) {
+ splitp = off;
+ break;
+ }
+ }
+
+ /*
+ * We can't split in the middle a set of duplicates. We know that
+ * no duplicate set can take up more than about 25% of the page,
+ * because that's the point where we push it off onto a duplicate
+ * page set. So, this loop can't be unbounded.
+ */
+ if (F_ISSET(dbp, DB_AM_DUP) && TYPE(pp) == P_LBTREE &&
+ pp->inp[splitp] == pp->inp[splitp - adjust])
+ for (cnt = 1;; ++cnt) {
+ off = splitp + cnt * adjust;
+ if (off < NUM_ENT(pp) &&
+ pp->inp[splitp] != pp->inp[off]) {
+ splitp = off;
+ break;
+ }
+ if (splitp <= (db_indx_t)(cnt * adjust))
+ continue;
+ off = splitp - cnt * adjust;
+ if (pp->inp[splitp] != pp->inp[off]) {
+ splitp = off + adjust;
+ break;
+ }
+ }
+
+
+ /* We're going to split at splitp. */
+ if ((ret = __bam_copy(dbp, pp, lp, 0, splitp)) != 0)
+ return (ret);
+ if ((ret = __bam_copy(dbp, pp, rp, splitp, NUM_ENT(pp))) != 0)
+ return (ret);
+
+ *splitret = splitp;
+ return (0);
+}
+
+/*
+ * __bam_copy --
+ * Copy a set of records from one page to another.
+ *
+ * PUBLIC: int __bam_copy __P((DB *, PAGE *, PAGE *, u_int32_t, u_int32_t));
+ */
+int
+__bam_copy(dbp, pp, cp, nxt, stop)
+ DB *dbp;
+ PAGE *pp, *cp;
+ u_int32_t nxt, stop;
+{
+ db_indx_t nbytes, off;
+
+ /*
+ * Copy the rest of the data to the right page. Nxt is the next
+ * offset placed on the target page.
+ */
+ for (off = 0; nxt < stop; ++nxt, ++NUM_ENT(cp), ++off) {
+ switch (TYPE(pp)) {
+ case P_IBTREE:
+ if (B_TYPE(GET_BINTERNAL(pp, nxt)->type) == B_KEYDATA)
+ nbytes =
+ BINTERNAL_SIZE(GET_BINTERNAL(pp, nxt)->len);
+ else
+ nbytes = BINTERNAL_SIZE(BOVERFLOW_SIZE);
+ break;
+ case P_LBTREE:
+ /*
+ * If we're on a key and it's a duplicate, just copy
+ * the offset.
+ */
+ if (off != 0 && (nxt % P_INDX) == 0 &&
+ pp->inp[nxt] == pp->inp[nxt - P_INDX]) {
+ cp->inp[off] = cp->inp[off - P_INDX];
+ continue;
+ }
+ /* FALLTHROUGH */
+ case P_LRECNO:
+ if (B_TYPE(GET_BKEYDATA(pp, nxt)->type) == B_KEYDATA)
+ nbytes =
+ BKEYDATA_SIZE(GET_BKEYDATA(pp, nxt)->len);
+ else
+ nbytes = BOVERFLOW_SIZE;
+ break;
+ case P_IRECNO:
+ nbytes = RINTERNAL_SIZE;
+ break;
+ default:
+ return (__db_pgfmt(dbp, pp->pgno));
+ }
+ cp->inp[off] = HOFFSET(cp) -= nbytes;
+ memcpy(P_ENTRY(cp, off), P_ENTRY(pp, nxt), nbytes);
+ }
+ return (0);
+}
diff --git a/usr/src/cmd/sendmail/db/btree/bt_stat.c b/usr/src/cmd/sendmail/db/btree/bt_stat.c
new file mode 100644
index 0000000000..855ef40bbd
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/btree/bt_stat.c
@@ -0,0 +1,198 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)bt_stat.c 10.27 (Sleepycat) 11/25/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <errno.h>
+#include <string.h>
+#endif
+
+#include "db_int.h"
+#include "db_page.h"
+#include "btree.h"
+
+/*
+ * __bam_stat --
+ * Gather/print the btree statistics
+ *
+ * PUBLIC: int __bam_stat __P((DB *, void *, void *(*)(size_t), u_int32_t));
+ */
+int
+__bam_stat(dbp, spp, db_malloc, flags)
+ DB *dbp;
+ void *spp;
+ void *(*db_malloc) __P((size_t));
+ u_int32_t flags;
+{
+ BTMETA *meta;
+ BTREE *t;
+ DBC *dbc;
+ DB_BTREE_STAT *sp;
+ DB_LOCK lock;
+ PAGE *h;
+ db_pgno_t lastpgno, pgno;
+ int ret, t_ret;
+
+ DB_PANIC_CHECK(dbp);
+
+ /* Check for invalid flags. */
+ if ((ret = __db_statchk(dbp, flags)) != 0)
+ return (ret);
+
+ if ((ret = dbp->cursor(dbp, NULL, &dbc, 0)) != 0)
+ return (ret);
+
+ DEBUG_LWRITE(dbc, NULL, "bam_stat", NULL, NULL, flags);
+
+ t = dbp->internal;
+
+ if (spp == NULL)
+ return (0);
+
+ /* Allocate and clear the structure. */
+ if ((ret = __os_malloc(sizeof(*sp), db_malloc, &sp)) != 0)
+ goto err;
+ memset(sp, 0, sizeof(*sp));
+
+ /* If the app just wants the record count, make it fast. */
+ if (flags == DB_RECORDCOUNT) {
+ pgno = PGNO_ROOT;
+ if ((ret = __bam_lget(dbc, 0, pgno, DB_LOCK_READ, &lock)) != 0)
+ goto err;
+ if ((ret = memp_fget(dbp->mpf, &pgno, 0, (PAGE **)&h)) != 0)
+ goto err;
+
+ sp->bt_nrecs = RE_NREC(h);
+
+ (void)memp_fput(dbp->mpf, h, 0);
+ (void)__BT_LPUT(dbc, lock);
+ goto done;
+ }
+
+ /* Get the meta-data page. */
+ pgno = PGNO_METADATA;
+ if ((ret = __bam_lget(dbc, 0, pgno, DB_LOCK_READ, &lock)) != 0)
+ goto err;
+ if ((ret = memp_fget(dbp->mpf, &pgno, 0, (PAGE **)&meta)) != 0)
+ goto err;
+
+ /* Translate the metadata flags. */
+ if (F_ISSET(meta, BTM_DUP))
+ sp->bt_flags |= DB_DUP;
+ if (F_ISSET(meta, BTM_FIXEDLEN))
+ sp->bt_flags |= DB_FIXEDLEN;
+ if (F_ISSET(meta, BTM_RECNUM))
+ sp->bt_flags |= DB_RECNUM;
+ if (F_ISSET(meta, BTM_RENUMBER))
+ sp->bt_flags |= DB_RENUMBER;
+
+ /* Get the remaining metadata fields. */
+ sp->bt_minkey = meta->minkey;
+ sp->bt_maxkey = meta->maxkey;
+ sp->bt_re_len = meta->re_len;
+ sp->bt_re_pad = meta->re_pad;
+ sp->bt_magic = meta->magic;
+ sp->bt_version = meta->version;
+
+ /* Get the page size from the DB. */
+ sp->bt_pagesize = dbp->pgsize;
+
+ /* Walk the free list, counting pages. */
+ for (sp->bt_free = 0, pgno = meta->free; pgno != PGNO_INVALID;) {
+ ++sp->bt_free;
+
+ if ((ret = memp_fget(dbp->mpf, &pgno, 0, &h)) != 0) {
+ (void)memp_fput(dbp->mpf, meta, 0);
+ (void)__BT_TLPUT(dbc, lock);
+ goto err;
+ }
+ pgno = h->next_pgno;
+ (void)memp_fput(dbp->mpf, h, 0);
+ }
+
+ /* Discard the meta-data page. */
+ (void)memp_fput(dbp->mpf, meta, 0);
+ (void)__BT_TLPUT(dbc, lock);
+
+ /* Determine the last page of the database. */
+ if ((ret = memp_fget(dbp->mpf, &lastpgno, DB_MPOOL_LAST, &h)) != 0)
+ goto err;
+ (void)memp_fput(dbp->mpf, h, 0);
+
+ /* Get the root page. */
+ pgno = PGNO_ROOT;
+ if ((ret = __bam_lget(dbc, 0, PGNO_ROOT, DB_LOCK_READ, &lock)) != 0)
+ goto err;
+ if ((ret = memp_fget(dbp->mpf, &pgno, 0, &h)) != 0) {
+ (void)__BT_LPUT(dbc, lock);
+ goto err;
+ }
+
+ /* Get the levels from the root page. */
+ sp->bt_levels = h->level;
+
+ /* Walk the page list, counting things. */
+ for (;;) {
+ switch (TYPE(h)) {
+ case P_INVALID:
+ break;
+ case P_IBTREE:
+ case P_IRECNO:
+ ++sp->bt_int_pg;
+ sp->bt_int_pgfree += HOFFSET(h) - LOFFSET(h);
+ break;
+ case P_LBTREE:
+ ++sp->bt_leaf_pg;
+ sp->bt_leaf_pgfree += HOFFSET(h) - LOFFSET(h);
+ sp->bt_nrecs += NUM_ENT(h) / P_INDX;
+ break;
+ case P_LRECNO:
+ ++sp->bt_leaf_pg;
+ sp->bt_leaf_pgfree += HOFFSET(h) - LOFFSET(h);
+ sp->bt_nrecs += NUM_ENT(h);
+ break;
+ case P_DUPLICATE:
+ ++sp->bt_dup_pg;
+ /* XXX MARGO: sp->bt_dup_pgfree; */
+ break;
+ case P_OVERFLOW:
+ ++sp->bt_over_pg;
+ /* XXX MARGO: sp->bt_over_pgfree; */
+ break;
+ default:
+ (void)memp_fput(dbp->mpf, h, 0);
+ (void)__BT_LPUT(dbc, lock);
+ return (__db_pgfmt(dbp, pgno));
+ }
+
+ (void)memp_fput(dbp->mpf, h, 0);
+ (void)__BT_LPUT(dbc, lock);
+
+ if (++pgno > lastpgno)
+ break;
+ if (__bam_lget(dbc, 0, pgno, DB_LOCK_READ, &lock))
+ break;
+ if (memp_fget(dbp->mpf, &pgno, 0, &h) != 0) {
+ (void)__BT_LPUT(dbc, lock);
+ break;
+ }
+ }
+
+done: *(DB_BTREE_STAT **)spp = sp;
+ ret = 0;
+
+err: if ((t_ret = dbc->c_close(dbc)) != 0 && ret == 0)
+ ret = t_ret;
+ return (ret);
+}
diff --git a/usr/src/cmd/sendmail/db/btree/btree_auto.c b/usr/src/cmd/sendmail/db/btree/btree_auto.c
new file mode 100644
index 0000000000..95ea76e2cd
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/btree/btree_auto.c
@@ -0,0 +1,1508 @@
+/* Do not edit: automatically built by dist/db_gen.sh. */
+#include "config.h"
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <ctype.h>
+#include <errno.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#endif
+
+#include "db_int.h"
+#include "db_page.h"
+#include "db_dispatch.h"
+#include "btree.h"
+#include "db_am.h"
+/*
+ * PUBLIC: int __bam_pg_alloc_log
+ * PUBLIC: __P((DB_LOG *, DB_TXN *, DB_LSN *, u_int32_t,
+ * PUBLIC: u_int32_t, DB_LSN *, DB_LSN *, db_pgno_t,
+ * PUBLIC: u_int32_t, db_pgno_t));
+ */
+int __bam_pg_alloc_log(logp, txnid, ret_lsnp, flags,
+ fileid, meta_lsn, page_lsn, pgno, ptype, next)
+ DB_LOG *logp;
+ DB_TXN *txnid;
+ DB_LSN *ret_lsnp;
+ u_int32_t flags;
+ u_int32_t fileid;
+ DB_LSN * meta_lsn;
+ DB_LSN * page_lsn;
+ db_pgno_t pgno;
+ u_int32_t ptype;
+ db_pgno_t next;
+{
+ DBT logrec;
+ DB_LSN *lsnp, null_lsn;
+ u_int32_t rectype, txn_num;
+ int ret;
+ u_int8_t *bp;
+
+ rectype = DB_bam_pg_alloc;
+ txn_num = txnid == NULL ? 0 : txnid->txnid;
+ if (txnid == NULL) {
+ ZERO_LSN(null_lsn);
+ lsnp = &null_lsn;
+ } else
+ lsnp = &txnid->last_lsn;
+ logrec.size = sizeof(rectype) + sizeof(txn_num) + sizeof(DB_LSN)
+ + sizeof(fileid)
+ + sizeof(*meta_lsn)
+ + sizeof(*page_lsn)
+ + sizeof(pgno)
+ + sizeof(ptype)
+ + sizeof(next);
+ if ((ret = __os_malloc(logrec.size, NULL, &logrec.data)) != 0)
+ return (ret);
+
+ bp = logrec.data;
+ memcpy(bp, &rectype, sizeof(rectype));
+ bp += sizeof(rectype);
+ memcpy(bp, &txn_num, sizeof(txn_num));
+ bp += sizeof(txn_num);
+ memcpy(bp, lsnp, sizeof(DB_LSN));
+ bp += sizeof(DB_LSN);
+ memcpy(bp, &fileid, sizeof(fileid));
+ bp += sizeof(fileid);
+ if (meta_lsn != NULL)
+ memcpy(bp, meta_lsn, sizeof(*meta_lsn));
+ else
+ memset(bp, 0, sizeof(*meta_lsn));
+ bp += sizeof(*meta_lsn);
+ if (page_lsn != NULL)
+ memcpy(bp, page_lsn, sizeof(*page_lsn));
+ else
+ memset(bp, 0, sizeof(*page_lsn));
+ bp += sizeof(*page_lsn);
+ memcpy(bp, &pgno, sizeof(pgno));
+ bp += sizeof(pgno);
+ memcpy(bp, &ptype, sizeof(ptype));
+ bp += sizeof(ptype);
+ memcpy(bp, &next, sizeof(next));
+ bp += sizeof(next);
+#ifdef DIAGNOSTIC
+ if ((u_int32_t)(bp - (u_int8_t *)logrec.data) != logrec.size)
+ fprintf(stderr, "Error in log record length");
+#endif
+ ret = log_put(logp, ret_lsnp, (DBT *)&logrec, flags);
+ if (txnid != NULL)
+ txnid->last_lsn = *ret_lsnp;
+ __os_free(logrec.data, 0);
+ return (ret);
+}
+
+/*
+ * PUBLIC: int __bam_pg_alloc_print
+ * PUBLIC: __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+ */
+int
+__bam_pg_alloc_print(notused1, dbtp, lsnp, notused2, notused3)
+ DB_LOG *notused1;
+ DBT *dbtp;
+ DB_LSN *lsnp;
+ int notused2;
+ void *notused3;
+{
+ __bam_pg_alloc_args *argp;
+ u_int32_t i;
+ u_int ch;
+ int ret;
+
+ i = 0;
+ ch = 0;
+ notused1 = NULL;
+ notused2 = 0;
+ notused3 = NULL;
+
+ if ((ret = __bam_pg_alloc_read(dbtp->data, &argp)) != 0)
+ return (ret);
+ printf("[%lu][%lu]bam_pg_alloc: rec: %lu txnid %lx prevlsn [%lu][%lu]\n",
+ (u_long)lsnp->file,
+ (u_long)lsnp->offset,
+ (u_long)argp->type,
+ (u_long)argp->txnid->txnid,
+ (u_long)argp->prev_lsn.file,
+ (u_long)argp->prev_lsn.offset);
+ printf("\tfileid: %lu\n", (u_long)argp->fileid);
+ printf("\tmeta_lsn: [%lu][%lu]\n",
+ (u_long)argp->meta_lsn.file, (u_long)argp->meta_lsn.offset);
+ printf("\tpage_lsn: [%lu][%lu]\n",
+ (u_long)argp->page_lsn.file, (u_long)argp->page_lsn.offset);
+ printf("\tpgno: %lu\n", (u_long)argp->pgno);
+ printf("\tptype: %lu\n", (u_long)argp->ptype);
+ printf("\tnext: %lu\n", (u_long)argp->next);
+ printf("\n");
+ __os_free(argp, 0);
+ return (0);
+}
+
+/*
+ * PUBLIC: int __bam_pg_alloc_read __P((void *, __bam_pg_alloc_args **));
+ */
+int
+__bam_pg_alloc_read(recbuf, argpp)
+ void *recbuf;
+ __bam_pg_alloc_args **argpp;
+{
+ __bam_pg_alloc_args *argp;
+ u_int8_t *bp;
+ int ret;
+
+ ret = __os_malloc(sizeof(__bam_pg_alloc_args) +
+ sizeof(DB_TXN), NULL, &argp);
+ if (ret != 0)
+ return (ret);
+ argp->txnid = (DB_TXN *)&argp[1];
+ bp = recbuf;
+ memcpy(&argp->type, bp, sizeof(argp->type));
+ bp += sizeof(argp->type);
+ memcpy(&argp->txnid->txnid, bp, sizeof(argp->txnid->txnid));
+ bp += sizeof(argp->txnid->txnid);
+ memcpy(&argp->prev_lsn, bp, sizeof(DB_LSN));
+ bp += sizeof(DB_LSN);
+ memcpy(&argp->fileid, bp, sizeof(argp->fileid));
+ bp += sizeof(argp->fileid);
+ memcpy(&argp->meta_lsn, bp, sizeof(argp->meta_lsn));
+ bp += sizeof(argp->meta_lsn);
+ memcpy(&argp->page_lsn, bp, sizeof(argp->page_lsn));
+ bp += sizeof(argp->page_lsn);
+ memcpy(&argp->pgno, bp, sizeof(argp->pgno));
+ bp += sizeof(argp->pgno);
+ memcpy(&argp->ptype, bp, sizeof(argp->ptype));
+ bp += sizeof(argp->ptype);
+ memcpy(&argp->next, bp, sizeof(argp->next));
+ bp += sizeof(argp->next);
+ *argpp = argp;
+ return (0);
+}
+
+/*
+ * PUBLIC: int __bam_pg_free_log
+ * PUBLIC: __P((DB_LOG *, DB_TXN *, DB_LSN *, u_int32_t,
+ * PUBLIC: u_int32_t, db_pgno_t, DB_LSN *, const DBT *,
+ * PUBLIC: db_pgno_t));
+ */
+int __bam_pg_free_log(logp, txnid, ret_lsnp, flags,
+ fileid, pgno, meta_lsn, header, next)
+ DB_LOG *logp;
+ DB_TXN *txnid;
+ DB_LSN *ret_lsnp;
+ u_int32_t flags;
+ u_int32_t fileid;
+ db_pgno_t pgno;
+ DB_LSN * meta_lsn;
+ const DBT *header;
+ db_pgno_t next;
+{
+ DBT logrec;
+ DB_LSN *lsnp, null_lsn;
+ u_int32_t zero;
+ u_int32_t rectype, txn_num;
+ int ret;
+ u_int8_t *bp;
+
+ rectype = DB_bam_pg_free;
+ txn_num = txnid == NULL ? 0 : txnid->txnid;
+ if (txnid == NULL) {
+ ZERO_LSN(null_lsn);
+ lsnp = &null_lsn;
+ } else
+ lsnp = &txnid->last_lsn;
+ logrec.size = sizeof(rectype) + sizeof(txn_num) + sizeof(DB_LSN)
+ + sizeof(fileid)
+ + sizeof(pgno)
+ + sizeof(*meta_lsn)
+ + sizeof(u_int32_t) + (header == NULL ? 0 : header->size)
+ + sizeof(next);
+ if ((ret = __os_malloc(logrec.size, NULL, &logrec.data)) != 0)
+ return (ret);
+
+ bp = logrec.data;
+ memcpy(bp, &rectype, sizeof(rectype));
+ bp += sizeof(rectype);
+ memcpy(bp, &txn_num, sizeof(txn_num));
+ bp += sizeof(txn_num);
+ memcpy(bp, lsnp, sizeof(DB_LSN));
+ bp += sizeof(DB_LSN);
+ memcpy(bp, &fileid, sizeof(fileid));
+ bp += sizeof(fileid);
+ memcpy(bp, &pgno, sizeof(pgno));
+ bp += sizeof(pgno);
+ if (meta_lsn != NULL)
+ memcpy(bp, meta_lsn, sizeof(*meta_lsn));
+ else
+ memset(bp, 0, sizeof(*meta_lsn));
+ bp += sizeof(*meta_lsn);
+ if (header == NULL) {
+ zero = 0;
+ memcpy(bp, &zero, sizeof(u_int32_t));
+ bp += sizeof(u_int32_t);
+ } else {
+ memcpy(bp, &header->size, sizeof(header->size));
+ bp += sizeof(header->size);
+ memcpy(bp, header->data, header->size);
+ bp += header->size;
+ }
+ memcpy(bp, &next, sizeof(next));
+ bp += sizeof(next);
+#ifdef DIAGNOSTIC
+ if ((u_int32_t)(bp - (u_int8_t *)logrec.data) != logrec.size)
+ fprintf(stderr, "Error in log record length");
+#endif
+ ret = log_put(logp, ret_lsnp, (DBT *)&logrec, flags);
+ if (txnid != NULL)
+ txnid->last_lsn = *ret_lsnp;
+ __os_free(logrec.data, 0);
+ return (ret);
+}
+
+/*
+ * PUBLIC: int __bam_pg_free_print
+ * PUBLIC: __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+ */
+int
+__bam_pg_free_print(notused1, dbtp, lsnp, notused2, notused3)
+ DB_LOG *notused1;
+ DBT *dbtp;
+ DB_LSN *lsnp;
+ int notused2;
+ void *notused3;
+{
+ __bam_pg_free_args *argp;
+ u_int32_t i;
+ u_int ch;
+ int ret;
+
+ i = 0;
+ ch = 0;
+ notused1 = NULL;
+ notused2 = 0;
+ notused3 = NULL;
+
+ if ((ret = __bam_pg_free_read(dbtp->data, &argp)) != 0)
+ return (ret);
+ printf("[%lu][%lu]bam_pg_free: rec: %lu txnid %lx prevlsn [%lu][%lu]\n",
+ (u_long)lsnp->file,
+ (u_long)lsnp->offset,
+ (u_long)argp->type,
+ (u_long)argp->txnid->txnid,
+ (u_long)argp->prev_lsn.file,
+ (u_long)argp->prev_lsn.offset);
+ printf("\tfileid: %lu\n", (u_long)argp->fileid);
+ printf("\tpgno: %lu\n", (u_long)argp->pgno);
+ printf("\tmeta_lsn: [%lu][%lu]\n",
+ (u_long)argp->meta_lsn.file, (u_long)argp->meta_lsn.offset);
+ printf("\theader: ");
+ for (i = 0; i < argp->header.size; i++) {
+ ch = ((u_int8_t *)argp->header.data)[i];
+ if (isprint(ch) || ch == 0xa)
+ putchar(ch);
+ else
+ printf("%#x ", ch);
+ }
+ printf("\n");
+ printf("\tnext: %lu\n", (u_long)argp->next);
+ printf("\n");
+ __os_free(argp, 0);
+ return (0);
+}
+
+/*
+ * PUBLIC: int __bam_pg_free_read __P((void *, __bam_pg_free_args **));
+ */
+int
+__bam_pg_free_read(recbuf, argpp)
+ void *recbuf;
+ __bam_pg_free_args **argpp;
+{
+ __bam_pg_free_args *argp;
+ u_int8_t *bp;
+ int ret;
+
+ ret = __os_malloc(sizeof(__bam_pg_free_args) +
+ sizeof(DB_TXN), NULL, &argp);
+ if (ret != 0)
+ return (ret);
+ argp->txnid = (DB_TXN *)&argp[1];
+ bp = recbuf;
+ memcpy(&argp->type, bp, sizeof(argp->type));
+ bp += sizeof(argp->type);
+ memcpy(&argp->txnid->txnid, bp, sizeof(argp->txnid->txnid));
+ bp += sizeof(argp->txnid->txnid);
+ memcpy(&argp->prev_lsn, bp, sizeof(DB_LSN));
+ bp += sizeof(DB_LSN);
+ memcpy(&argp->fileid, bp, sizeof(argp->fileid));
+ bp += sizeof(argp->fileid);
+ memcpy(&argp->pgno, bp, sizeof(argp->pgno));
+ bp += sizeof(argp->pgno);
+ memcpy(&argp->meta_lsn, bp, sizeof(argp->meta_lsn));
+ bp += sizeof(argp->meta_lsn);
+ memcpy(&argp->header.size, bp, sizeof(u_int32_t));
+ bp += sizeof(u_int32_t);
+ argp->header.data = bp;
+ bp += argp->header.size;
+ memcpy(&argp->next, bp, sizeof(argp->next));
+ bp += sizeof(argp->next);
+ *argpp = argp;
+ return (0);
+}
+
+/*
+ * PUBLIC: int __bam_split_log
+ * PUBLIC: __P((DB_LOG *, DB_TXN *, DB_LSN *, u_int32_t,
+ * PUBLIC: u_int32_t, db_pgno_t, DB_LSN *, db_pgno_t,
+ * PUBLIC: DB_LSN *, u_int32_t, db_pgno_t, DB_LSN *,
+ * PUBLIC: const DBT *));
+ */
+int __bam_split_log(logp, txnid, ret_lsnp, flags,
+ fileid, left, llsn, right, rlsn, indx,
+ npgno, nlsn, pg)
+ DB_LOG *logp;
+ DB_TXN *txnid;
+ DB_LSN *ret_lsnp;
+ u_int32_t flags;
+ u_int32_t fileid;
+ db_pgno_t left;
+ DB_LSN * llsn;
+ db_pgno_t right;
+ DB_LSN * rlsn;
+ u_int32_t indx;
+ db_pgno_t npgno;
+ DB_LSN * nlsn;
+ const DBT *pg;
+{
+ DBT logrec;
+ DB_LSN *lsnp, null_lsn;
+ u_int32_t zero;
+ u_int32_t rectype, txn_num;
+ int ret;
+ u_int8_t *bp;
+
+ rectype = DB_bam_split;
+ txn_num = txnid == NULL ? 0 : txnid->txnid;
+ if (txnid == NULL) {
+ ZERO_LSN(null_lsn);
+ lsnp = &null_lsn;
+ } else
+ lsnp = &txnid->last_lsn;
+ logrec.size = sizeof(rectype) + sizeof(txn_num) + sizeof(DB_LSN)
+ + sizeof(fileid)
+ + sizeof(left)
+ + sizeof(*llsn)
+ + sizeof(right)
+ + sizeof(*rlsn)
+ + sizeof(indx)
+ + sizeof(npgno)
+ + sizeof(*nlsn)
+ + sizeof(u_int32_t) + (pg == NULL ? 0 : pg->size);
+ if ((ret = __os_malloc(logrec.size, NULL, &logrec.data)) != 0)
+ return (ret);
+
+ bp = logrec.data;
+ memcpy(bp, &rectype, sizeof(rectype));
+ bp += sizeof(rectype);
+ memcpy(bp, &txn_num, sizeof(txn_num));
+ bp += sizeof(txn_num);
+ memcpy(bp, lsnp, sizeof(DB_LSN));
+ bp += sizeof(DB_LSN);
+ memcpy(bp, &fileid, sizeof(fileid));
+ bp += sizeof(fileid);
+ memcpy(bp, &left, sizeof(left));
+ bp += sizeof(left);
+ if (llsn != NULL)
+ memcpy(bp, llsn, sizeof(*llsn));
+ else
+ memset(bp, 0, sizeof(*llsn));
+ bp += sizeof(*llsn);
+ memcpy(bp, &right, sizeof(right));
+ bp += sizeof(right);
+ if (rlsn != NULL)
+ memcpy(bp, rlsn, sizeof(*rlsn));
+ else
+ memset(bp, 0, sizeof(*rlsn));
+ bp += sizeof(*rlsn);
+ memcpy(bp, &indx, sizeof(indx));
+ bp += sizeof(indx);
+ memcpy(bp, &npgno, sizeof(npgno));
+ bp += sizeof(npgno);
+ if (nlsn != NULL)
+ memcpy(bp, nlsn, sizeof(*nlsn));
+ else
+ memset(bp, 0, sizeof(*nlsn));
+ bp += sizeof(*nlsn);
+ if (pg == NULL) {
+ zero = 0;
+ memcpy(bp, &zero, sizeof(u_int32_t));
+ bp += sizeof(u_int32_t);
+ } else {
+ memcpy(bp, &pg->size, sizeof(pg->size));
+ bp += sizeof(pg->size);
+ memcpy(bp, pg->data, pg->size);
+ bp += pg->size;
+ }
+#ifdef DIAGNOSTIC
+ if ((u_int32_t)(bp - (u_int8_t *)logrec.data) != logrec.size)
+ fprintf(stderr, "Error in log record length");
+#endif
+ ret = log_put(logp, ret_lsnp, (DBT *)&logrec, flags);
+ if (txnid != NULL)
+ txnid->last_lsn = *ret_lsnp;
+ __os_free(logrec.data, 0);
+ return (ret);
+}
+
+/*
+ * PUBLIC: int __bam_split_print
+ * PUBLIC: __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+ */
+int
+__bam_split_print(notused1, dbtp, lsnp, notused2, notused3)
+ DB_LOG *notused1;
+ DBT *dbtp;
+ DB_LSN *lsnp;
+ int notused2;
+ void *notused3;
+{
+ __bam_split_args *argp;
+ u_int32_t i;
+ u_int ch;
+ int ret;
+
+ i = 0;
+ ch = 0;
+ notused1 = NULL;
+ notused2 = 0;
+ notused3 = NULL;
+
+ if ((ret = __bam_split_read(dbtp->data, &argp)) != 0)
+ return (ret);
+ printf("[%lu][%lu]bam_split: rec: %lu txnid %lx prevlsn [%lu][%lu]\n",
+ (u_long)lsnp->file,
+ (u_long)lsnp->offset,
+ (u_long)argp->type,
+ (u_long)argp->txnid->txnid,
+ (u_long)argp->prev_lsn.file,
+ (u_long)argp->prev_lsn.offset);
+ printf("\tfileid: %lu\n", (u_long)argp->fileid);
+ printf("\tleft: %lu\n", (u_long)argp->left);
+ printf("\tllsn: [%lu][%lu]\n",
+ (u_long)argp->llsn.file, (u_long)argp->llsn.offset);
+ printf("\tright: %lu\n", (u_long)argp->right);
+ printf("\trlsn: [%lu][%lu]\n",
+ (u_long)argp->rlsn.file, (u_long)argp->rlsn.offset);
+ printf("\tindx: %lu\n", (u_long)argp->indx);
+ printf("\tnpgno: %lu\n", (u_long)argp->npgno);
+ printf("\tnlsn: [%lu][%lu]\n",
+ (u_long)argp->nlsn.file, (u_long)argp->nlsn.offset);
+ printf("\tpg: ");
+ for (i = 0; i < argp->pg.size; i++) {
+ ch = ((u_int8_t *)argp->pg.data)[i];
+ if (isprint(ch) || ch == 0xa)
+ putchar(ch);
+ else
+ printf("%#x ", ch);
+ }
+ printf("\n");
+ printf("\n");
+ __os_free(argp, 0);
+ return (0);
+}
+
+/*
+ * PUBLIC: int __bam_split_read __P((void *, __bam_split_args **));
+ */
+int
+__bam_split_read(recbuf, argpp)
+ void *recbuf;
+ __bam_split_args **argpp;
+{
+ __bam_split_args *argp;
+ u_int8_t *bp;
+ int ret;
+
+ ret = __os_malloc(sizeof(__bam_split_args) +
+ sizeof(DB_TXN), NULL, &argp);
+ if (ret != 0)
+ return (ret);
+ argp->txnid = (DB_TXN *)&argp[1];
+ bp = recbuf;
+ memcpy(&argp->type, bp, sizeof(argp->type));
+ bp += sizeof(argp->type);
+ memcpy(&argp->txnid->txnid, bp, sizeof(argp->txnid->txnid));
+ bp += sizeof(argp->txnid->txnid);
+ memcpy(&argp->prev_lsn, bp, sizeof(DB_LSN));
+ bp += sizeof(DB_LSN);
+ memcpy(&argp->fileid, bp, sizeof(argp->fileid));
+ bp += sizeof(argp->fileid);
+ memcpy(&argp->left, bp, sizeof(argp->left));
+ bp += sizeof(argp->left);
+ memcpy(&argp->llsn, bp, sizeof(argp->llsn));
+ bp += sizeof(argp->llsn);
+ memcpy(&argp->right, bp, sizeof(argp->right));
+ bp += sizeof(argp->right);
+ memcpy(&argp->rlsn, bp, sizeof(argp->rlsn));
+ bp += sizeof(argp->rlsn);
+ memcpy(&argp->indx, bp, sizeof(argp->indx));
+ bp += sizeof(argp->indx);
+ memcpy(&argp->npgno, bp, sizeof(argp->npgno));
+ bp += sizeof(argp->npgno);
+ memcpy(&argp->nlsn, bp, sizeof(argp->nlsn));
+ bp += sizeof(argp->nlsn);
+ memcpy(&argp->pg.size, bp, sizeof(u_int32_t));
+ bp += sizeof(u_int32_t);
+ argp->pg.data = bp;
+ bp += argp->pg.size;
+ *argpp = argp;
+ return (0);
+}
+
+/*
+ * PUBLIC: int __bam_rsplit_log
+ * PUBLIC: __P((DB_LOG *, DB_TXN *, DB_LSN *, u_int32_t,
+ * PUBLIC: u_int32_t, db_pgno_t, const DBT *, db_pgno_t,
+ * PUBLIC: const DBT *, DB_LSN *));
+ */
+int __bam_rsplit_log(logp, txnid, ret_lsnp, flags,
+ fileid, pgno, pgdbt, nrec, rootent, rootlsn)
+ DB_LOG *logp;
+ DB_TXN *txnid;
+ DB_LSN *ret_lsnp;
+ u_int32_t flags;
+ u_int32_t fileid;
+ db_pgno_t pgno;
+ const DBT *pgdbt;
+ db_pgno_t nrec;
+ const DBT *rootent;
+ DB_LSN * rootlsn;
+{
+ DBT logrec;
+ DB_LSN *lsnp, null_lsn;
+ u_int32_t zero;
+ u_int32_t rectype, txn_num;
+ int ret;
+ u_int8_t *bp;
+
+ rectype = DB_bam_rsplit;
+ txn_num = txnid == NULL ? 0 : txnid->txnid;
+ if (txnid == NULL) {
+ ZERO_LSN(null_lsn);
+ lsnp = &null_lsn;
+ } else
+ lsnp = &txnid->last_lsn;
+ logrec.size = sizeof(rectype) + sizeof(txn_num) + sizeof(DB_LSN)
+ + sizeof(fileid)
+ + sizeof(pgno)
+ + sizeof(u_int32_t) + (pgdbt == NULL ? 0 : pgdbt->size)
+ + sizeof(nrec)
+ + sizeof(u_int32_t) + (rootent == NULL ? 0 : rootent->size)
+ + sizeof(*rootlsn);
+ if ((ret = __os_malloc(logrec.size, NULL, &logrec.data)) != 0)
+ return (ret);
+
+ bp = logrec.data;
+ memcpy(bp, &rectype, sizeof(rectype));
+ bp += sizeof(rectype);
+ memcpy(bp, &txn_num, sizeof(txn_num));
+ bp += sizeof(txn_num);
+ memcpy(bp, lsnp, sizeof(DB_LSN));
+ bp += sizeof(DB_LSN);
+ memcpy(bp, &fileid, sizeof(fileid));
+ bp += sizeof(fileid);
+ memcpy(bp, &pgno, sizeof(pgno));
+ bp += sizeof(pgno);
+ if (pgdbt == NULL) {
+ zero = 0;
+ memcpy(bp, &zero, sizeof(u_int32_t));
+ bp += sizeof(u_int32_t);
+ } else {
+ memcpy(bp, &pgdbt->size, sizeof(pgdbt->size));
+ bp += sizeof(pgdbt->size);
+ memcpy(bp, pgdbt->data, pgdbt->size);
+ bp += pgdbt->size;
+ }
+ memcpy(bp, &nrec, sizeof(nrec));
+ bp += sizeof(nrec);
+ if (rootent == NULL) {
+ zero = 0;
+ memcpy(bp, &zero, sizeof(u_int32_t));
+ bp += sizeof(u_int32_t);
+ } else {
+ memcpy(bp, &rootent->size, sizeof(rootent->size));
+ bp += sizeof(rootent->size);
+ memcpy(bp, rootent->data, rootent->size);
+ bp += rootent->size;
+ }
+ if (rootlsn != NULL)
+ memcpy(bp, rootlsn, sizeof(*rootlsn));
+ else
+ memset(bp, 0, sizeof(*rootlsn));
+ bp += sizeof(*rootlsn);
+#ifdef DIAGNOSTIC
+ if ((u_int32_t)(bp - (u_int8_t *)logrec.data) != logrec.size)
+ fprintf(stderr, "Error in log record length");
+#endif
+ ret = log_put(logp, ret_lsnp, (DBT *)&logrec, flags);
+ if (txnid != NULL)
+ txnid->last_lsn = *ret_lsnp;
+ __os_free(logrec.data, 0);
+ return (ret);
+}
+
+/*
+ * PUBLIC: int __bam_rsplit_print
+ * PUBLIC: __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+ */
+int
+__bam_rsplit_print(notused1, dbtp, lsnp, notused2, notused3)
+ DB_LOG *notused1;
+ DBT *dbtp;
+ DB_LSN *lsnp;
+ int notused2;
+ void *notused3;
+{
+ __bam_rsplit_args *argp;
+ u_int32_t i;
+ u_int ch;
+ int ret;
+
+ i = 0;
+ ch = 0;
+ notused1 = NULL;
+ notused2 = 0;
+ notused3 = NULL;
+
+ if ((ret = __bam_rsplit_read(dbtp->data, &argp)) != 0)
+ return (ret);
+ printf("[%lu][%lu]bam_rsplit: rec: %lu txnid %lx prevlsn [%lu][%lu]\n",
+ (u_long)lsnp->file,
+ (u_long)lsnp->offset,
+ (u_long)argp->type,
+ (u_long)argp->txnid->txnid,
+ (u_long)argp->prev_lsn.file,
+ (u_long)argp->prev_lsn.offset);
+ printf("\tfileid: %lu\n", (u_long)argp->fileid);
+ printf("\tpgno: %lu\n", (u_long)argp->pgno);
+ printf("\tpgdbt: ");
+ for (i = 0; i < argp->pgdbt.size; i++) {
+ ch = ((u_int8_t *)argp->pgdbt.data)[i];
+ if (isprint(ch) || ch == 0xa)
+ putchar(ch);
+ else
+ printf("%#x ", ch);
+ }
+ printf("\n");
+ printf("\tnrec: %lu\n", (u_long)argp->nrec);
+ printf("\trootent: ");
+ for (i = 0; i < argp->rootent.size; i++) {
+ ch = ((u_int8_t *)argp->rootent.data)[i];
+ if (isprint(ch) || ch == 0xa)
+ putchar(ch);
+ else
+ printf("%#x ", ch);
+ }
+ printf("\n");
+ printf("\trootlsn: [%lu][%lu]\n",
+ (u_long)argp->rootlsn.file, (u_long)argp->rootlsn.offset);
+ printf("\n");
+ __os_free(argp, 0);
+ return (0);
+}
+
+/*
+ * PUBLIC: int __bam_rsplit_read __P((void *, __bam_rsplit_args **));
+ */
+int
+__bam_rsplit_read(recbuf, argpp)
+ void *recbuf;
+ __bam_rsplit_args **argpp;
+{
+ __bam_rsplit_args *argp;
+ u_int8_t *bp;
+ int ret;
+
+ ret = __os_malloc(sizeof(__bam_rsplit_args) +
+ sizeof(DB_TXN), NULL, &argp);
+ if (ret != 0)
+ return (ret);
+ argp->txnid = (DB_TXN *)&argp[1];
+ bp = recbuf;
+ memcpy(&argp->type, bp, sizeof(argp->type));
+ bp += sizeof(argp->type);
+ memcpy(&argp->txnid->txnid, bp, sizeof(argp->txnid->txnid));
+ bp += sizeof(argp->txnid->txnid);
+ memcpy(&argp->prev_lsn, bp, sizeof(DB_LSN));
+ bp += sizeof(DB_LSN);
+ memcpy(&argp->fileid, bp, sizeof(argp->fileid));
+ bp += sizeof(argp->fileid);
+ memcpy(&argp->pgno, bp, sizeof(argp->pgno));
+ bp += sizeof(argp->pgno);
+ memcpy(&argp->pgdbt.size, bp, sizeof(u_int32_t));
+ bp += sizeof(u_int32_t);
+ argp->pgdbt.data = bp;
+ bp += argp->pgdbt.size;
+ memcpy(&argp->nrec, bp, sizeof(argp->nrec));
+ bp += sizeof(argp->nrec);
+ memcpy(&argp->rootent.size, bp, sizeof(u_int32_t));
+ bp += sizeof(u_int32_t);
+ argp->rootent.data = bp;
+ bp += argp->rootent.size;
+ memcpy(&argp->rootlsn, bp, sizeof(argp->rootlsn));
+ bp += sizeof(argp->rootlsn);
+ *argpp = argp;
+ return (0);
+}
+
+/*
+ * PUBLIC: int __bam_adj_log
+ * PUBLIC: __P((DB_LOG *, DB_TXN *, DB_LSN *, u_int32_t,
+ * PUBLIC: u_int32_t, db_pgno_t, DB_LSN *, u_int32_t,
+ * PUBLIC: u_int32_t, u_int32_t));
+ */
+int __bam_adj_log(logp, txnid, ret_lsnp, flags,
+ fileid, pgno, lsn, indx, indx_copy, is_insert)
+ DB_LOG *logp;
+ DB_TXN *txnid;
+ DB_LSN *ret_lsnp;
+ u_int32_t flags;
+ u_int32_t fileid;
+ db_pgno_t pgno;
+ DB_LSN * lsn;
+ u_int32_t indx;
+ u_int32_t indx_copy;
+ u_int32_t is_insert;
+{
+ DBT logrec;
+ DB_LSN *lsnp, null_lsn;
+ u_int32_t rectype, txn_num;
+ int ret;
+ u_int8_t *bp;
+
+ rectype = DB_bam_adj;
+ txn_num = txnid == NULL ? 0 : txnid->txnid;
+ if (txnid == NULL) {
+ ZERO_LSN(null_lsn);
+ lsnp = &null_lsn;
+ } else
+ lsnp = &txnid->last_lsn;
+ logrec.size = sizeof(rectype) + sizeof(txn_num) + sizeof(DB_LSN)
+ + sizeof(fileid)
+ + sizeof(pgno)
+ + sizeof(*lsn)
+ + sizeof(indx)
+ + sizeof(indx_copy)
+ + sizeof(is_insert);
+ if ((ret = __os_malloc(logrec.size, NULL, &logrec.data)) != 0)
+ return (ret);
+
+ bp = logrec.data;
+ memcpy(bp, &rectype, sizeof(rectype));
+ bp += sizeof(rectype);
+ memcpy(bp, &txn_num, sizeof(txn_num));
+ bp += sizeof(txn_num);
+ memcpy(bp, lsnp, sizeof(DB_LSN));
+ bp += sizeof(DB_LSN);
+ memcpy(bp, &fileid, sizeof(fileid));
+ bp += sizeof(fileid);
+ memcpy(bp, &pgno, sizeof(pgno));
+ bp += sizeof(pgno);
+ if (lsn != NULL)
+ memcpy(bp, lsn, sizeof(*lsn));
+ else
+ memset(bp, 0, sizeof(*lsn));
+ bp += sizeof(*lsn);
+ memcpy(bp, &indx, sizeof(indx));
+ bp += sizeof(indx);
+ memcpy(bp, &indx_copy, sizeof(indx_copy));
+ bp += sizeof(indx_copy);
+ memcpy(bp, &is_insert, sizeof(is_insert));
+ bp += sizeof(is_insert);
+#ifdef DIAGNOSTIC
+ if ((u_int32_t)(bp - (u_int8_t *)logrec.data) != logrec.size)
+ fprintf(stderr, "Error in log record length");
+#endif
+ ret = log_put(logp, ret_lsnp, (DBT *)&logrec, flags);
+ if (txnid != NULL)
+ txnid->last_lsn = *ret_lsnp;
+ __os_free(logrec.data, 0);
+ return (ret);
+}
+
+/*
+ * PUBLIC: int __bam_adj_print
+ * PUBLIC: __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+ */
+int
+__bam_adj_print(notused1, dbtp, lsnp, notused2, notused3)
+ DB_LOG *notused1;
+ DBT *dbtp;
+ DB_LSN *lsnp;
+ int notused2;
+ void *notused3;
+{
+ __bam_adj_args *argp;
+ u_int32_t i;
+ u_int ch;
+ int ret;
+
+ i = 0;
+ ch = 0;
+ notused1 = NULL;
+ notused2 = 0;
+ notused3 = NULL;
+
+ if ((ret = __bam_adj_read(dbtp->data, &argp)) != 0)
+ return (ret);
+ printf("[%lu][%lu]bam_adj: rec: %lu txnid %lx prevlsn [%lu][%lu]\n",
+ (u_long)lsnp->file,
+ (u_long)lsnp->offset,
+ (u_long)argp->type,
+ (u_long)argp->txnid->txnid,
+ (u_long)argp->prev_lsn.file,
+ (u_long)argp->prev_lsn.offset);
+ printf("\tfileid: %lu\n", (u_long)argp->fileid);
+ printf("\tpgno: %lu\n", (u_long)argp->pgno);
+ printf("\tlsn: [%lu][%lu]\n",
+ (u_long)argp->lsn.file, (u_long)argp->lsn.offset);
+ printf("\tindx: %lu\n", (u_long)argp->indx);
+ printf("\tindx_copy: %lu\n", (u_long)argp->indx_copy);
+ printf("\tis_insert: %lu\n", (u_long)argp->is_insert);
+ printf("\n");
+ __os_free(argp, 0);
+ return (0);
+}
+
+/*
+ * PUBLIC: int __bam_adj_read __P((void *, __bam_adj_args **));
+ */
+int
+__bam_adj_read(recbuf, argpp)
+ void *recbuf;
+ __bam_adj_args **argpp;
+{
+ __bam_adj_args *argp;
+ u_int8_t *bp;
+ int ret;
+
+ ret = __os_malloc(sizeof(__bam_adj_args) +
+ sizeof(DB_TXN), NULL, &argp);
+ if (ret != 0)
+ return (ret);
+ argp->txnid = (DB_TXN *)&argp[1];
+ bp = recbuf;
+ memcpy(&argp->type, bp, sizeof(argp->type));
+ bp += sizeof(argp->type);
+ memcpy(&argp->txnid->txnid, bp, sizeof(argp->txnid->txnid));
+ bp += sizeof(argp->txnid->txnid);
+ memcpy(&argp->prev_lsn, bp, sizeof(DB_LSN));
+ bp += sizeof(DB_LSN);
+ memcpy(&argp->fileid, bp, sizeof(argp->fileid));
+ bp += sizeof(argp->fileid);
+ memcpy(&argp->pgno, bp, sizeof(argp->pgno));
+ bp += sizeof(argp->pgno);
+ memcpy(&argp->lsn, bp, sizeof(argp->lsn));
+ bp += sizeof(argp->lsn);
+ memcpy(&argp->indx, bp, sizeof(argp->indx));
+ bp += sizeof(argp->indx);
+ memcpy(&argp->indx_copy, bp, sizeof(argp->indx_copy));
+ bp += sizeof(argp->indx_copy);
+ memcpy(&argp->is_insert, bp, sizeof(argp->is_insert));
+ bp += sizeof(argp->is_insert);
+ *argpp = argp;
+ return (0);
+}
+
+/*
+ * PUBLIC: int __bam_cadjust_log
+ * PUBLIC: __P((DB_LOG *, DB_TXN *, DB_LSN *, u_int32_t,
+ * PUBLIC: u_int32_t, db_pgno_t, DB_LSN *, u_int32_t,
+ * PUBLIC: int32_t, int32_t));
+ */
+int __bam_cadjust_log(logp, txnid, ret_lsnp, flags,
+ fileid, pgno, lsn, indx, adjust, total)
+ DB_LOG *logp;
+ DB_TXN *txnid;
+ DB_LSN *ret_lsnp;
+ u_int32_t flags;
+ u_int32_t fileid;
+ db_pgno_t pgno;
+ DB_LSN * lsn;
+ u_int32_t indx;
+ int32_t adjust;
+ int32_t total;
+{
+ DBT logrec;
+ DB_LSN *lsnp, null_lsn;
+ u_int32_t rectype, txn_num;
+ int ret;
+ u_int8_t *bp;
+
+ rectype = DB_bam_cadjust;
+ txn_num = txnid == NULL ? 0 : txnid->txnid;
+ if (txnid == NULL) {
+ ZERO_LSN(null_lsn);
+ lsnp = &null_lsn;
+ } else
+ lsnp = &txnid->last_lsn;
+ logrec.size = sizeof(rectype) + sizeof(txn_num) + sizeof(DB_LSN)
+ + sizeof(fileid)
+ + sizeof(pgno)
+ + sizeof(*lsn)
+ + sizeof(indx)
+ + sizeof(adjust)
+ + sizeof(total);
+ if ((ret = __os_malloc(logrec.size, NULL, &logrec.data)) != 0)
+ return (ret);
+
+ bp = logrec.data;
+ memcpy(bp, &rectype, sizeof(rectype));
+ bp += sizeof(rectype);
+ memcpy(bp, &txn_num, sizeof(txn_num));
+ bp += sizeof(txn_num);
+ memcpy(bp, lsnp, sizeof(DB_LSN));
+ bp += sizeof(DB_LSN);
+ memcpy(bp, &fileid, sizeof(fileid));
+ bp += sizeof(fileid);
+ memcpy(bp, &pgno, sizeof(pgno));
+ bp += sizeof(pgno);
+ if (lsn != NULL)
+ memcpy(bp, lsn, sizeof(*lsn));
+ else
+ memset(bp, 0, sizeof(*lsn));
+ bp += sizeof(*lsn);
+ memcpy(bp, &indx, sizeof(indx));
+ bp += sizeof(indx);
+ memcpy(bp, &adjust, sizeof(adjust));
+ bp += sizeof(adjust);
+ memcpy(bp, &total, sizeof(total));
+ bp += sizeof(total);
+#ifdef DIAGNOSTIC
+ if ((u_int32_t)(bp - (u_int8_t *)logrec.data) != logrec.size)
+ fprintf(stderr, "Error in log record length");
+#endif
+ ret = log_put(logp, ret_lsnp, (DBT *)&logrec, flags);
+ if (txnid != NULL)
+ txnid->last_lsn = *ret_lsnp;
+ __os_free(logrec.data, 0);
+ return (ret);
+}
+
+/*
+ * PUBLIC: int __bam_cadjust_print
+ * PUBLIC: __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+ */
+int
+__bam_cadjust_print(notused1, dbtp, lsnp, notused2, notused3)
+ DB_LOG *notused1;
+ DBT *dbtp;
+ DB_LSN *lsnp;
+ int notused2;
+ void *notused3;
+{
+ __bam_cadjust_args *argp;
+ u_int32_t i;
+ u_int ch;
+ int ret;
+
+ i = 0;
+ ch = 0;
+ notused1 = NULL;
+ notused2 = 0;
+ notused3 = NULL;
+
+ if ((ret = __bam_cadjust_read(dbtp->data, &argp)) != 0)
+ return (ret);
+ printf("[%lu][%lu]bam_cadjust: rec: %lu txnid %lx prevlsn [%lu][%lu]\n",
+ (u_long)lsnp->file,
+ (u_long)lsnp->offset,
+ (u_long)argp->type,
+ (u_long)argp->txnid->txnid,
+ (u_long)argp->prev_lsn.file,
+ (u_long)argp->prev_lsn.offset);
+ printf("\tfileid: %lu\n", (u_long)argp->fileid);
+ printf("\tpgno: %lu\n", (u_long)argp->pgno);
+ printf("\tlsn: [%lu][%lu]\n",
+ (u_long)argp->lsn.file, (u_long)argp->lsn.offset);
+ printf("\tindx: %lu\n", (u_long)argp->indx);
+ printf("\tadjust: %ld\n", (long)argp->adjust);
+ printf("\ttotal: %ld\n", (long)argp->total);
+ printf("\n");
+ __os_free(argp, 0);
+ return (0);
+}
+
+/*
+ * PUBLIC: int __bam_cadjust_read __P((void *, __bam_cadjust_args **));
+ */
+int
+__bam_cadjust_read(recbuf, argpp)
+ void *recbuf;
+ __bam_cadjust_args **argpp;
+{
+ __bam_cadjust_args *argp;
+ u_int8_t *bp;
+ int ret;
+
+ ret = __os_malloc(sizeof(__bam_cadjust_args) +
+ sizeof(DB_TXN), NULL, &argp);
+ if (ret != 0)
+ return (ret);
+ argp->txnid = (DB_TXN *)&argp[1];
+ bp = recbuf;
+ memcpy(&argp->type, bp, sizeof(argp->type));
+ bp += sizeof(argp->type);
+ memcpy(&argp->txnid->txnid, bp, sizeof(argp->txnid->txnid));
+ bp += sizeof(argp->txnid->txnid);
+ memcpy(&argp->prev_lsn, bp, sizeof(DB_LSN));
+ bp += sizeof(DB_LSN);
+ memcpy(&argp->fileid, bp, sizeof(argp->fileid));
+ bp += sizeof(argp->fileid);
+ memcpy(&argp->pgno, bp, sizeof(argp->pgno));
+ bp += sizeof(argp->pgno);
+ memcpy(&argp->lsn, bp, sizeof(argp->lsn));
+ bp += sizeof(argp->lsn);
+ memcpy(&argp->indx, bp, sizeof(argp->indx));
+ bp += sizeof(argp->indx);
+ memcpy(&argp->adjust, bp, sizeof(argp->adjust));
+ bp += sizeof(argp->adjust);
+ memcpy(&argp->total, bp, sizeof(argp->total));
+ bp += sizeof(argp->total);
+ *argpp = argp;
+ return (0);
+}
+
+/*
+ * PUBLIC: int __bam_cdel_log
+ * PUBLIC: __P((DB_LOG *, DB_TXN *, DB_LSN *, u_int32_t,
+ * PUBLIC: u_int32_t, db_pgno_t, DB_LSN *, u_int32_t));
+ */
+int __bam_cdel_log(logp, txnid, ret_lsnp, flags,
+ fileid, pgno, lsn, indx)
+ DB_LOG *logp;
+ DB_TXN *txnid;
+ DB_LSN *ret_lsnp;
+ u_int32_t flags;
+ u_int32_t fileid;
+ db_pgno_t pgno;
+ DB_LSN * lsn;
+ u_int32_t indx;
+{
+ DBT logrec;
+ DB_LSN *lsnp, null_lsn;
+ u_int32_t rectype, txn_num;
+ int ret;
+ u_int8_t *bp;
+
+ rectype = DB_bam_cdel;
+ txn_num = txnid == NULL ? 0 : txnid->txnid;
+ if (txnid == NULL) {
+ ZERO_LSN(null_lsn);
+ lsnp = &null_lsn;
+ } else
+ lsnp = &txnid->last_lsn;
+ logrec.size = sizeof(rectype) + sizeof(txn_num) + sizeof(DB_LSN)
+ + sizeof(fileid)
+ + sizeof(pgno)
+ + sizeof(*lsn)
+ + sizeof(indx);
+ if ((ret = __os_malloc(logrec.size, NULL, &logrec.data)) != 0)
+ return (ret);
+
+ bp = logrec.data;
+ memcpy(bp, &rectype, sizeof(rectype));
+ bp += sizeof(rectype);
+ memcpy(bp, &txn_num, sizeof(txn_num));
+ bp += sizeof(txn_num);
+ memcpy(bp, lsnp, sizeof(DB_LSN));
+ bp += sizeof(DB_LSN);
+ memcpy(bp, &fileid, sizeof(fileid));
+ bp += sizeof(fileid);
+ memcpy(bp, &pgno, sizeof(pgno));
+ bp += sizeof(pgno);
+ if (lsn != NULL)
+ memcpy(bp, lsn, sizeof(*lsn));
+ else
+ memset(bp, 0, sizeof(*lsn));
+ bp += sizeof(*lsn);
+ memcpy(bp, &indx, sizeof(indx));
+ bp += sizeof(indx);
+#ifdef DIAGNOSTIC
+ if ((u_int32_t)(bp - (u_int8_t *)logrec.data) != logrec.size)
+ fprintf(stderr, "Error in log record length");
+#endif
+ ret = log_put(logp, ret_lsnp, (DBT *)&logrec, flags);
+ if (txnid != NULL)
+ txnid->last_lsn = *ret_lsnp;
+ __os_free(logrec.data, 0);
+ return (ret);
+}
+
+/*
+ * PUBLIC: int __bam_cdel_print
+ * PUBLIC: __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+ */
+int
+__bam_cdel_print(notused1, dbtp, lsnp, notused2, notused3)
+ DB_LOG *notused1;
+ DBT *dbtp;
+ DB_LSN *lsnp;
+ int notused2;
+ void *notused3;
+{
+ __bam_cdel_args *argp;
+ u_int32_t i;
+ u_int ch;
+ int ret;
+
+ i = 0;
+ ch = 0;
+ notused1 = NULL;
+ notused2 = 0;
+ notused3 = NULL;
+
+ if ((ret = __bam_cdel_read(dbtp->data, &argp)) != 0)
+ return (ret);
+ printf("[%lu][%lu]bam_cdel: rec: %lu txnid %lx prevlsn [%lu][%lu]\n",
+ (u_long)lsnp->file,
+ (u_long)lsnp->offset,
+ (u_long)argp->type,
+ (u_long)argp->txnid->txnid,
+ (u_long)argp->prev_lsn.file,
+ (u_long)argp->prev_lsn.offset);
+ printf("\tfileid: %lu\n", (u_long)argp->fileid);
+ printf("\tpgno: %lu\n", (u_long)argp->pgno);
+ printf("\tlsn: [%lu][%lu]\n",
+ (u_long)argp->lsn.file, (u_long)argp->lsn.offset);
+ printf("\tindx: %lu\n", (u_long)argp->indx);
+ printf("\n");
+ __os_free(argp, 0);
+ return (0);
+}
+
+/*
+ * PUBLIC: int __bam_cdel_read __P((void *, __bam_cdel_args **));
+ */
+int
+__bam_cdel_read(recbuf, argpp)
+ void *recbuf;
+ __bam_cdel_args **argpp;
+{
+ __bam_cdel_args *argp;
+ u_int8_t *bp;
+ int ret;
+
+ ret = __os_malloc(sizeof(__bam_cdel_args) +
+ sizeof(DB_TXN), NULL, &argp);
+ if (ret != 0)
+ return (ret);
+ argp->txnid = (DB_TXN *)&argp[1];
+ bp = recbuf;
+ memcpy(&argp->type, bp, sizeof(argp->type));
+ bp += sizeof(argp->type);
+ memcpy(&argp->txnid->txnid, bp, sizeof(argp->txnid->txnid));
+ bp += sizeof(argp->txnid->txnid);
+ memcpy(&argp->prev_lsn, bp, sizeof(DB_LSN));
+ bp += sizeof(DB_LSN);
+ memcpy(&argp->fileid, bp, sizeof(argp->fileid));
+ bp += sizeof(argp->fileid);
+ memcpy(&argp->pgno, bp, sizeof(argp->pgno));
+ bp += sizeof(argp->pgno);
+ memcpy(&argp->lsn, bp, sizeof(argp->lsn));
+ bp += sizeof(argp->lsn);
+ memcpy(&argp->indx, bp, sizeof(argp->indx));
+ bp += sizeof(argp->indx);
+ *argpp = argp;
+ return (0);
+}
+
+/*
+ * PUBLIC: int __bam_repl_log
+ * PUBLIC: __P((DB_LOG *, DB_TXN *, DB_LSN *, u_int32_t,
+ * PUBLIC: u_int32_t, db_pgno_t, DB_LSN *, u_int32_t,
+ * PUBLIC: u_int32_t, const DBT *, const DBT *, u_int32_t,
+ * PUBLIC: u_int32_t));
+ */
+int __bam_repl_log(logp, txnid, ret_lsnp, flags,
+ fileid, pgno, lsn, indx, isdeleted, orig,
+ repl, prefix, suffix)
+ DB_LOG *logp;
+ DB_TXN *txnid;
+ DB_LSN *ret_lsnp;
+ u_int32_t flags;
+ u_int32_t fileid;
+ db_pgno_t pgno;
+ DB_LSN * lsn;
+ u_int32_t indx;
+ u_int32_t isdeleted;
+ const DBT *orig;
+ const DBT *repl;
+ u_int32_t prefix;
+ u_int32_t suffix;
+{
+ DBT logrec;
+ DB_LSN *lsnp, null_lsn;
+ u_int32_t zero;
+ u_int32_t rectype, txn_num;
+ int ret;
+ u_int8_t *bp;
+
+ rectype = DB_bam_repl;
+ txn_num = txnid == NULL ? 0 : txnid->txnid;
+ if (txnid == NULL) {
+ ZERO_LSN(null_lsn);
+ lsnp = &null_lsn;
+ } else
+ lsnp = &txnid->last_lsn;
+ logrec.size = sizeof(rectype) + sizeof(txn_num) + sizeof(DB_LSN)
+ + sizeof(fileid)
+ + sizeof(pgno)
+ + sizeof(*lsn)
+ + sizeof(indx)
+ + sizeof(isdeleted)
+ + sizeof(u_int32_t) + (orig == NULL ? 0 : orig->size)
+ + sizeof(u_int32_t) + (repl == NULL ? 0 : repl->size)
+ + sizeof(prefix)
+ + sizeof(suffix);
+ if ((ret = __os_malloc(logrec.size, NULL, &logrec.data)) != 0)
+ return (ret);
+
+ bp = logrec.data;
+ memcpy(bp, &rectype, sizeof(rectype));
+ bp += sizeof(rectype);
+ memcpy(bp, &txn_num, sizeof(txn_num));
+ bp += sizeof(txn_num);
+ memcpy(bp, lsnp, sizeof(DB_LSN));
+ bp += sizeof(DB_LSN);
+ memcpy(bp, &fileid, sizeof(fileid));
+ bp += sizeof(fileid);
+ memcpy(bp, &pgno, sizeof(pgno));
+ bp += sizeof(pgno);
+ if (lsn != NULL)
+ memcpy(bp, lsn, sizeof(*lsn));
+ else
+ memset(bp, 0, sizeof(*lsn));
+ bp += sizeof(*lsn);
+ memcpy(bp, &indx, sizeof(indx));
+ bp += sizeof(indx);
+ memcpy(bp, &isdeleted, sizeof(isdeleted));
+ bp += sizeof(isdeleted);
+ if (orig == NULL) {
+ zero = 0;
+ memcpy(bp, &zero, sizeof(u_int32_t));
+ bp += sizeof(u_int32_t);
+ } else {
+ memcpy(bp, &orig->size, sizeof(orig->size));
+ bp += sizeof(orig->size);
+ memcpy(bp, orig->data, orig->size);
+ bp += orig->size;
+ }
+ if (repl == NULL) {
+ zero = 0;
+ memcpy(bp, &zero, sizeof(u_int32_t));
+ bp += sizeof(u_int32_t);
+ } else {
+ memcpy(bp, &repl->size, sizeof(repl->size));
+ bp += sizeof(repl->size);
+ memcpy(bp, repl->data, repl->size);
+ bp += repl->size;
+ }
+ memcpy(bp, &prefix, sizeof(prefix));
+ bp += sizeof(prefix);
+ memcpy(bp, &suffix, sizeof(suffix));
+ bp += sizeof(suffix);
+#ifdef DIAGNOSTIC
+ if ((u_int32_t)(bp - (u_int8_t *)logrec.data) != logrec.size)
+ fprintf(stderr, "Error in log record length");
+#endif
+ ret = log_put(logp, ret_lsnp, (DBT *)&logrec, flags);
+ if (txnid != NULL)
+ txnid->last_lsn = *ret_lsnp;
+ __os_free(logrec.data, 0);
+ return (ret);
+}
+
+/*
+ * PUBLIC: int __bam_repl_print
+ * PUBLIC: __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+ */
+int
+__bam_repl_print(notused1, dbtp, lsnp, notused2, notused3)
+ DB_LOG *notused1;
+ DBT *dbtp;
+ DB_LSN *lsnp;
+ int notused2;
+ void *notused3;
+{
+ __bam_repl_args *argp;
+ u_int32_t i;
+ u_int ch;
+ int ret;
+
+ i = 0;
+ ch = 0;
+ notused1 = NULL;
+ notused2 = 0;
+ notused3 = NULL;
+
+ if ((ret = __bam_repl_read(dbtp->data, &argp)) != 0)
+ return (ret);
+ printf("[%lu][%lu]bam_repl: rec: %lu txnid %lx prevlsn [%lu][%lu]\n",
+ (u_long)lsnp->file,
+ (u_long)lsnp->offset,
+ (u_long)argp->type,
+ (u_long)argp->txnid->txnid,
+ (u_long)argp->prev_lsn.file,
+ (u_long)argp->prev_lsn.offset);
+ printf("\tfileid: %lu\n", (u_long)argp->fileid);
+ printf("\tpgno: %lu\n", (u_long)argp->pgno);
+ printf("\tlsn: [%lu][%lu]\n",
+ (u_long)argp->lsn.file, (u_long)argp->lsn.offset);
+ printf("\tindx: %lu\n", (u_long)argp->indx);
+ printf("\tisdeleted: %lu\n", (u_long)argp->isdeleted);
+ printf("\torig: ");
+ for (i = 0; i < argp->orig.size; i++) {
+ ch = ((u_int8_t *)argp->orig.data)[i];
+ if (isprint(ch) || ch == 0xa)
+ putchar(ch);
+ else
+ printf("%#x ", ch);
+ }
+ printf("\n");
+ printf("\trepl: ");
+ for (i = 0; i < argp->repl.size; i++) {
+ ch = ((u_int8_t *)argp->repl.data)[i];
+ if (isprint(ch) || ch == 0xa)
+ putchar(ch);
+ else
+ printf("%#x ", ch);
+ }
+ printf("\n");
+ printf("\tprefix: %lu\n", (u_long)argp->prefix);
+ printf("\tsuffix: %lu\n", (u_long)argp->suffix);
+ printf("\n");
+ __os_free(argp, 0);
+ return (0);
+}
+
+/*
+ * PUBLIC: int __bam_repl_read __P((void *, __bam_repl_args **));
+ */
+int
+__bam_repl_read(recbuf, argpp)
+ void *recbuf;
+ __bam_repl_args **argpp;
+{
+ __bam_repl_args *argp;
+ u_int8_t *bp;
+ int ret;
+
+ ret = __os_malloc(sizeof(__bam_repl_args) +
+ sizeof(DB_TXN), NULL, &argp);
+ if (ret != 0)
+ return (ret);
+ argp->txnid = (DB_TXN *)&argp[1];
+ bp = recbuf;
+ memcpy(&argp->type, bp, sizeof(argp->type));
+ bp += sizeof(argp->type);
+ memcpy(&argp->txnid->txnid, bp, sizeof(argp->txnid->txnid));
+ bp += sizeof(argp->txnid->txnid);
+ memcpy(&argp->prev_lsn, bp, sizeof(DB_LSN));
+ bp += sizeof(DB_LSN);
+ memcpy(&argp->fileid, bp, sizeof(argp->fileid));
+ bp += sizeof(argp->fileid);
+ memcpy(&argp->pgno, bp, sizeof(argp->pgno));
+ bp += sizeof(argp->pgno);
+ memcpy(&argp->lsn, bp, sizeof(argp->lsn));
+ bp += sizeof(argp->lsn);
+ memcpy(&argp->indx, bp, sizeof(argp->indx));
+ bp += sizeof(argp->indx);
+ memcpy(&argp->isdeleted, bp, sizeof(argp->isdeleted));
+ bp += sizeof(argp->isdeleted);
+ memcpy(&argp->orig.size, bp, sizeof(u_int32_t));
+ bp += sizeof(u_int32_t);
+ argp->orig.data = bp;
+ bp += argp->orig.size;
+ memcpy(&argp->repl.size, bp, sizeof(u_int32_t));
+ bp += sizeof(u_int32_t);
+ argp->repl.data = bp;
+ bp += argp->repl.size;
+ memcpy(&argp->prefix, bp, sizeof(argp->prefix));
+ bp += sizeof(argp->prefix);
+ memcpy(&argp->suffix, bp, sizeof(argp->suffix));
+ bp += sizeof(argp->suffix);
+ *argpp = argp;
+ return (0);
+}
+
+/*
+ * PUBLIC: int __bam_init_print __P((DB_ENV *));
+ */
+int
+__bam_init_print(dbenv)
+ DB_ENV *dbenv;
+{
+ int ret;
+
+ if ((ret = __db_add_recovery(dbenv,
+ __bam_pg_alloc_print, DB_bam_pg_alloc)) != 0)
+ return (ret);
+ if ((ret = __db_add_recovery(dbenv,
+ __bam_pg_free_print, DB_bam_pg_free)) != 0)
+ return (ret);
+ if ((ret = __db_add_recovery(dbenv,
+ __bam_split_print, DB_bam_split)) != 0)
+ return (ret);
+ if ((ret = __db_add_recovery(dbenv,
+ __bam_rsplit_print, DB_bam_rsplit)) != 0)
+ return (ret);
+ if ((ret = __db_add_recovery(dbenv,
+ __bam_adj_print, DB_bam_adj)) != 0)
+ return (ret);
+ if ((ret = __db_add_recovery(dbenv,
+ __bam_cadjust_print, DB_bam_cadjust)) != 0)
+ return (ret);
+ if ((ret = __db_add_recovery(dbenv,
+ __bam_cdel_print, DB_bam_cdel)) != 0)
+ return (ret);
+ if ((ret = __db_add_recovery(dbenv,
+ __bam_repl_print, DB_bam_repl)) != 0)
+ return (ret);
+ return (0);
+}
+
+/*
+ * PUBLIC: int __bam_init_recover __P((DB_ENV *));
+ */
+int
+__bam_init_recover(dbenv)
+ DB_ENV *dbenv;
+{
+ int ret;
+
+ if ((ret = __db_add_recovery(dbenv,
+ __bam_pg_alloc_recover, DB_bam_pg_alloc)) != 0)
+ return (ret);
+ if ((ret = __db_add_recovery(dbenv,
+ __bam_pg_free_recover, DB_bam_pg_free)) != 0)
+ return (ret);
+ if ((ret = __db_add_recovery(dbenv,
+ __bam_split_recover, DB_bam_split)) != 0)
+ return (ret);
+ if ((ret = __db_add_recovery(dbenv,
+ __bam_rsplit_recover, DB_bam_rsplit)) != 0)
+ return (ret);
+ if ((ret = __db_add_recovery(dbenv,
+ __bam_adj_recover, DB_bam_adj)) != 0)
+ return (ret);
+ if ((ret = __db_add_recovery(dbenv,
+ __bam_cadjust_recover, DB_bam_cadjust)) != 0)
+ return (ret);
+ if ((ret = __db_add_recovery(dbenv,
+ __bam_cdel_recover, DB_bam_cdel)) != 0)
+ return (ret);
+ if ((ret = __db_add_recovery(dbenv,
+ __bam_repl_recover, DB_bam_repl)) != 0)
+ return (ret);
+ return (0);
+}
+
diff --git a/usr/src/cmd/sendmail/db/clib/strsep.c b/usr/src/cmd/sendmail/db/clib/strsep.c
new file mode 100644
index 0000000000..093a1c9328
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/clib/strsep.c
@@ -0,0 +1,100 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997
+ * Sleepycat Software. All rights reserved.
+ */
+/*
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+/*
+ * Copyright (c) 1998 by Sun Microsystems, Inc.
+ * All rights reserved.
+ */
+
+#include "config.h"
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#ifndef lint
+static const char sccsid[] = "@(#)strsep.c 10.1 (Sleepycat) 4/12/97";
+static const char sccsi2[] = "%W% (Sun) %G%";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <string.h>
+#include <stdio.h>
+#endif
+
+/*
+ * Get next token from string *stringp, where tokens are possibly-empty
+ * strings separated by characters from delim.
+ *
+ * Writes NULs into the string at *stringp to end tokens.
+ * delim need not remain constant from call to call.
+ * On return, *stringp points past the last NUL written (if there might
+ * be further tokens), or is NULL (if there are definitely no more tokens).
+ *
+ * If *stringp is NULL, strsep returns NULL.
+ *
+ * PUBLIC: #ifndef HAVE_STRSEP
+ * PUBLIC: char *strsep __P((char **, const char *));
+ * PUBLIC: #endif
+ */
+char *
+strsep(stringp, delim)
+ register char **stringp;
+ register const char *delim;
+{
+ register char *s;
+ register const char *spanp;
+ register int c, sc;
+ char *tok;
+
+ if ((s = *stringp) == NULL)
+ return (NULL);
+ for (tok = s;;) {
+ c = *s++;
+ spanp = delim;
+ do {
+ if ((sc = *spanp++) == c) {
+ if (c == 0)
+ s = NULL;
+ else
+ s[-1] = 0;
+ *stringp = s;
+ return (tok);
+ }
+ } while (sc != 0);
+ }
+ /* NOTREACHED */
+}
diff --git a/usr/src/cmd/sendmail/db/config.h b/usr/src/cmd/sendmail/db/config.h
new file mode 100644
index 0000000000..c652a604a4
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/config.h
@@ -0,0 +1,179 @@
+/* config.h. Generated automatically by configure. */
+/* config.hin. Generated automatically from configure.in by autoheader. */
+
+/* Define to empty if the keyword does not work. */
+/* #undef const */
+
+/* Define if your struct stat has st_blksize. */
+#define HAVE_ST_BLKSIZE 1
+
+/* Define to `int' if <sys/types.h> doesn't define. */
+/* #undef mode_t */
+
+/* Define to `long' if <sys/types.h> doesn't define. */
+/* #undef off_t */
+
+/* Define to `int' if <sys/types.h> doesn't define. */
+/* #undef pid_t */
+
+/* Define to `unsigned' if <sys/types.h> doesn't define. */
+/* #undef size_t */
+
+/* Define if the `S_IS*' macros in <sys/stat.h> do not work properly. */
+/* #undef STAT_MACROS_BROKEN */
+
+/* Define if you have the ANSI C header files. */
+#define STDC_HEADERS 1
+
+/* Define if your processor stores words with the most significant
+ byte first (like Motorola and SPARC, unlike Intel and VAX). */
+#define WORDS_BIGENDIAN 1
+
+/* Define to `int' if <sys/types.h> doesn't define. */
+/* #undef ssize_t */
+
+/* Define if you want a debugging version. */
+/* #undef DEBUG */
+
+/* Define if you want a version with run-time diagnostic checking. */
+/* #undef DIAGNOSTIC */
+
+/* Define if you have sigfillset (and sigprocmask). */
+#define HAVE_SIGFILLSET 1
+
+/* Define if building big-file environment (e.g., Solaris, HP/UX). */
+#define HAVE_FILE_OFFSET_BITS 1
+
+/* Define if you have spinlocks. */
+#define HAVE_SPINLOCKS 1
+
+/* Define if you want to use mc68020/gcc assembly spinlocks. */
+/* #undef HAVE_ASSEM_MC68020_GCC */
+
+/* Define if you want to use parisc/gcc assembly spinlocks. */
+/* #undef HAVE_ASSEM_PARISC_GCC */
+
+/* Define if you want to use sco/cc assembly spinlocks. */
+/* #undef HAVE_ASSEM_SCO_CC */
+
+/* Define if you want to use sparc/gcc assembly spinlocks. */
+/* #undef HAVE_ASSEM_SPARC_GCC */
+
+/* Define if you want to use uts4/cc assembly spinlocks. */
+/* #undef HAVE_ASSEM_UTS4_CC */
+
+/* Define if you want to use x86/gcc assembly spinlocks. */
+/* #undef HAVE_ASSEM_X86_GCC */
+
+/* Define if you have the AIX _check_lock spinlocks. */
+/* #undef HAVE_FUNC_AIX */
+
+/* Define if you have the OSF1 or HPPA msemaphore spinlocks. */
+/* #undef HAVE_FUNC_MSEM */
+
+/* Define if you have the SGI abilock_t spinlocks. */
+/* #undef HAVE_FUNC_SGI */
+
+/* Define if you have the ReliantUNIX spinlock_t spinlocks. */
+/* #undef HAVE_FUNC_RELIANT */
+
+/* Define if you have the Solaris mutex_t spinlocks. */
+#define HAVE_FUNC_SOLARIS 1
+
+/* Define if your sprintf returns a pointer, not a length. */
+/* #undef SPRINTF_RET_CHARPNT */
+
+/* Define if you have the getcwd function. */
+#define HAVE_GETCWD 1
+
+/* Define if you have the getopt function. */
+#define HAVE_GETOPT 1
+
+/* Define if you have the getuid function. */
+#define HAVE_GETUID 1
+
+/* Define if you have the memcmp function. */
+#define HAVE_MEMCMP 1
+
+/* Define if you have the memcpy function. */
+#define HAVE_MEMCPY 1
+
+/* Define if you have the memmove function. */
+#define HAVE_MEMMOVE 1
+
+/* Define if you have the mmap function. */
+#define HAVE_MMAP 1
+
+/* Define if you have the munmap function. */
+#define HAVE_MUNMAP 1
+
+/* Define if you have the pread function. */
+#define HAVE_PREAD 1
+
+/* Define if you have the pstat_getdynamic function. */
+/* #undef HAVE_PSTAT_GETDYNAMIC */
+
+/* Define if you have the qsort function. */
+#define HAVE_QSORT 1
+
+/* Define if you have the raise function. */
+#define HAVE_RAISE 1
+
+/* Define if you have the select function. */
+#define HAVE_SELECT 1
+
+/* Define if you have the shmget function. */
+#define HAVE_SHMGET 1
+
+/* Define if you have the snprintf function. */
+#define HAVE_SNPRINTF 1
+
+/* Define if you have the strerror function. */
+#define HAVE_STRERROR 1
+
+/* Define if you have the strsep function. */
+/* #undef HAVE_STRSEP */
+
+/* Define if you have the sysconf function. */
+#define HAVE_SYSCONF 1
+
+/* Define if you have the vsnprintf function. */
+#define HAVE_VSNPRINTF 1
+
+/* Define if you have the <dirent.h> header file. */
+#define HAVE_DIRENT_H 1
+
+/* Define if you have the <ndir.h> header file. */
+/* #undef HAVE_NDIR_H */
+
+/* Define if you have the <sys/dir.h> header file. */
+/* #undef HAVE_SYS_DIR_H */
+
+/* Define if you have the <sys/ndir.h> header file. */
+/* #undef HAVE_SYS_NDIR_H */
+
+/* Define if you have the <sys/select.h> header file. */
+#define HAVE_SYS_SELECT_H 1
+
+/* Define if you have the <sys/time.h> header file. */
+#define HAVE_SYS_TIME_H 1
+
+/*
+ * Don't step on the namespace. Also, other libraries have real snprintf(3)
+ * implementations, don't want to override them just because they're loaded
+ * after us.
+ */
+#ifndef HAVE_SNPRINTF
+#define snprintf __db_snprintf
+#endif
+#ifndef HAVE_VSNPRINTF
+#define vsnprintf __db_vsnprintf
+#endif
+
+/*
+ * Big-file configuration.
+ */
+#ifdef HAVE_FILE_OFFSET_BITS
+#define _LARGE_FILES /* AIX specific. */
+#define _FILE_OFFSET_BITS 64
+#endif
diff --git a/usr/src/cmd/sendmail/db/db.h b/usr/src/cmd/sendmail/db/db.h
new file mode 100644
index 0000000000..64684b9d12
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/db.h
@@ -0,0 +1,1050 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ *
+ * @(#)db.h 10.174 (Sleepycat) 1/3/99
+ */
+
+#ifndef _DB_H_
+#define _DB_H_
+
+#ifndef __NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <stdio.h>
+#endif
+
+/*
+ * XXX
+ * MacOS: ensure that Metrowerks C makes enumeration types int sized.
+ */
+#ifdef __MWERKS__
+#pragma enumsalwaysint on
+#endif
+
+/*
+ * XXX
+ * Handle function prototypes and the keyword "const". This steps on name
+ * space that DB doesn't control, but all of the other solutions are worse.
+ *
+ * XXX
+ * While Microsoft's compiler is ANSI C compliant, it doesn't have _STDC_
+ * defined by default, you specify a command line flag or #pragma to turn
+ * it on. Don't do that, however, because some of Microsoft's own header
+ * files won't compile.
+ */
+#undef __P
+#if defined(__STDC__) || defined(__cplusplus) || defined(_MSC_VER)
+#define __P(protos) protos /* ANSI C prototypes */
+#else
+#define const
+#define __P(protos) () /* K&R C preprocessor */
+#endif
+
+/*
+ * !!!
+ * DB needs basic information about specifically sized types. If they're
+ * not provided by the system, typedef them here.
+ *
+ * We protect them against multiple inclusion using __BIT_TYPES_DEFINED__,
+ * as does BIND and Kerberos, since we don't know for sure what #include
+ * files the user is using.
+ *
+ * !!!
+ * We also provide the standard u_int, u_long etc., if they're not provided
+ * by the system.
+ */
+#ifndef __BIT_TYPES_DEFINED__
+#define __BIT_TYPES_DEFINED__
+typedef unsigned char u_int8_t;
+
+typedef unsigned short u_int16_t;
+
+typedef unsigned int u_int32_t;
+#endif
+
+
+
+
+
+
+#define DB_VERSION_MAJOR 2
+#define DB_VERSION_MINOR 7
+#define DB_VERSION_PATCH 7
+#define DB_VERSION_STRING "Sleepycat Software: Berkeley DB 2.7.7: (08/20/99)"
+
+typedef u_int32_t db_pgno_t; /* Page number type. */
+typedef u_int16_t db_indx_t; /* Page offset type. */
+#define DB_MAX_PAGES 0xffffffff /* >= # of pages in a file */
+
+typedef u_int32_t db_recno_t; /* Record number type. */
+#define DB_MAX_RECORDS 0xffffffff /* >= # of records in a tree */
+
+typedef size_t DB_LOCK; /* Object returned by lock manager. */
+
+/* Forward structure declarations, so applications get type checking. */
+struct __db; typedef struct __db DB;
+#ifdef DB_DBM_HSEARCH
+ typedef struct __db DBM;
+#endif
+struct __db_bt_stat; typedef struct __db_bt_stat DB_BTREE_STAT;
+struct __db_dbt; typedef struct __db_dbt DBT;
+struct __db_env; typedef struct __db_env DB_ENV;
+struct __db_ilock; typedef struct __db_ilock DB_LOCK_ILOCK;
+struct __db_info; typedef struct __db_info DB_INFO;
+struct __db_lock_stat; typedef struct __db_lock_stat DB_LOCK_STAT;
+struct __db_lockregion; typedef struct __db_lockregion DB_LOCKREGION;
+struct __db_lockreq; typedef struct __db_lockreq DB_LOCKREQ;
+struct __db_locktab; typedef struct __db_locktab DB_LOCKTAB;
+struct __db_log; typedef struct __db_log DB_LOG;
+struct __db_log_stat; typedef struct __db_log_stat DB_LOG_STAT;
+struct __db_lsn; typedef struct __db_lsn DB_LSN;
+struct __db_mpool; typedef struct __db_mpool DB_MPOOL;
+struct __db_mpool_finfo;typedef struct __db_mpool_finfo DB_MPOOL_FINFO;
+struct __db_mpool_fstat;typedef struct __db_mpool_fstat DB_MPOOL_FSTAT;
+struct __db_mpool_stat; typedef struct __db_mpool_stat DB_MPOOL_STAT;
+struct __db_mpoolfile; typedef struct __db_mpoolfile DB_MPOOLFILE;
+struct __db_txn; typedef struct __db_txn DB_TXN;
+struct __db_txn_active; typedef struct __db_txn_active DB_TXN_ACTIVE;
+struct __db_txn_stat; typedef struct __db_txn_stat DB_TXN_STAT;
+struct __db_txnmgr; typedef struct __db_txnmgr DB_TXNMGR;
+struct __db_txnregion; typedef struct __db_txnregion DB_TXNREGION;
+struct __dbc; typedef struct __dbc DBC;
+
+/* Key/data structure -- a Data-Base Thang. */
+struct __db_dbt {
+ void *data; /* key/data */
+ u_int32_t size; /* key/data length */
+ u_int32_t ulen; /* RO: length of user buffer. */
+ u_int32_t dlen; /* RO: get/put record length. */
+ u_int32_t doff; /* RO: get/put record offset. */
+
+#define DB_DBT_INTERNAL 0x01 /* Ignore user's malloc (internal). */
+#define DB_DBT_MALLOC 0x02 /* Return in allocated memory. */
+#define DB_DBT_PARTIAL 0x04 /* Partial put/get. */
+#define DB_DBT_USERMEM 0x08 /* Return in user's memory. */
+ u_int32_t flags;
+};
+
+/*
+ * DB run-time interface configuration.
+ *
+ * There are a set of functions that the application can replace with its
+ * own versions, and some other knobs which can be turned at run-time.
+ */
+#define DB_FUNC_CLOSE 1 /* POSIX 1003.1 close. */
+#define DB_FUNC_DIRFREE 2 /* DB: free directory list. */
+#define DB_FUNC_DIRLIST 3 /* DB: create directory list. */
+#define DB_FUNC_EXISTS 4 /* DB: return if file exists. */
+#define DB_FUNC_FREE 5 /* ANSI C free. */
+#define DB_FUNC_FSYNC 6 /* POSIX 1003.1 fsync. */
+#define DB_FUNC_IOINFO 7 /* DB: return file I/O information. */
+#define DB_FUNC_MALLOC 8 /* ANSI C malloc. */
+#define DB_FUNC_MAP 9 /* DB: map file into shared memory. */
+#define DB_FUNC_OPEN 10 /* POSIX 1003.1 open. */
+#define DB_FUNC_READ 11 /* POSIX 1003.1 read. */
+#define DB_FUNC_REALLOC 12 /* ANSI C realloc. */
+#define DB_FUNC_RUNLINK 13 /* DB: remove a shared region. */
+#define DB_FUNC_SEEK 14 /* POSIX 1003.1 lseek. */
+#define DB_FUNC_SLEEP 15 /* DB: sleep secs/usecs. */
+#define DB_FUNC_UNLINK 16 /* POSIX 1003.1 unlink. */
+#define DB_FUNC_UNMAP 17 /* DB: unmap shared memory file. */
+#define DB_FUNC_WRITE 18 /* POSIX 1003.1 write. */
+#define DB_FUNC_YIELD 19 /* DB: yield thread to scheduler. */
+#define DB_MUTEXLOCKS 20 /* DB: turn off all mutex locks. */
+#define DB_PAGEYIELD 21 /* DB: yield the CPU on pool get. */
+#define DB_REGION_ANON 22 /* DB: anonymous, unnamed regions. */
+#define DB_REGION_INIT 23 /* DB: page-fault regions in create. */
+#define DB_REGION_NAME 24 /* DB: anonymous, named regions. */
+#define DB_TSL_SPINS 25 /* DB: initialize spin count. */
+
+/*
+ * Database configuration and initialization.
+ */
+ /*
+ * Flags understood by both db_open(3) and db_appinit(3).
+ */
+#define DB_CREATE 0x000001 /* O_CREAT: create file as necessary. */
+#define DB_NOMMAP 0x000002 /* Don't mmap underlying file. */
+#define DB_THREAD 0x000004 /* Free-thread DB package handles. */
+
+/*
+ * Flags understood by db_appinit(3).
+ */
+/* 0x000007 COMMON MASK. */
+#define DB_INIT_CDB 0x000008 /* Concurrent Access Methods. */
+#define DB_INIT_LOCK 0x000010 /* Initialize locking. */
+#define DB_INIT_LOG 0x000020 /* Initialize logging. */
+#define DB_INIT_MPOOL 0x000040 /* Initialize mpool. */
+#define DB_INIT_TXN 0x000080 /* Initialize transactions. */
+#define DB_MPOOL_PRIVATE 0x000100 /* Mpool: private memory pool. */
+#define DB_RECOVER 0x000200 /* Run normal recovery. */
+#define DB_RECOVER_FATAL 0x000400 /* Run catastrophic recovery. */
+#define DB_TXN_NOSYNC 0x000800 /* Do not sync log on commit. */
+#define DB_USE_ENVIRON 0x001000 /* Use the environment. */
+#define DB_USE_ENVIRON_ROOT 0x002000 /* Use the environment if root. */
+
+/*
+ * Flags understood by db_open(3).
+ *
+ * DB_EXCL and DB_TEMPORARY are internal only, and are not documented.
+ * DB_SEQUENTIAL is currently internal, but may be exported some day.
+ */
+/* 0x000007 COMMON MASK. */
+/* 0x001fff ALREADY USED. */
+#define DB_EXCL 0x002000 /* O_EXCL: exclusive open (internal). */
+#define DB_RDONLY 0x004000 /* O_RDONLY: read-only. */
+#define DB_SEQUENTIAL 0x008000 /* Sequential access (internal). */
+#define DB_TEMPORARY 0x010000 /* Remove on last close (internal). */
+#define DB_TRUNCATE 0x020000 /* O_TRUNCATE: replace existing DB. */
+#define DB_FCNTL_LOCKING 0x040000 /* Undocumented: fcntl(2) locking. */
+
+/*
+ * Deadlock detector modes; used in the DBENV structure to configure the
+ * locking subsystem.
+ */
+#define DB_LOCK_NORUN 0
+#define DB_LOCK_DEFAULT 1 /* Default policy. */
+#define DB_LOCK_OLDEST 2 /* Abort oldest transaction. */
+#define DB_LOCK_RANDOM 3 /* Abort random transaction. */
+#define DB_LOCK_YOUNGEST 4 /* Abort youngest transaction. */
+
+struct __db_env {
+ int db_lorder; /* Byte order. */
+
+ /* Error message callback. */
+ void (*db_errcall) __P((const char *, char *));
+ FILE *db_errfile; /* Error message file stream. */
+ const char *db_errpfx; /* Error message prefix. */
+ int db_verbose; /* Generate debugging messages. */
+ int db_panic; /* Panic flag, callback function. */
+ void (*db_paniccall) __P((DB_ENV *, int));
+
+ /* User paths. */
+ char *db_home; /* Database home. */
+ char *db_log_dir; /* Database log file directory. */
+ char *db_tmp_dir; /* Database tmp file directory. */
+
+ char **db_data_dir; /* Database data file directories. */
+ int data_cnt; /* Database data file slots. */
+ int data_next; /* Next Database data file slot. */
+
+ /* Locking. */
+ DB_LOCKTAB *lk_info; /* Return from lock_open(). */
+ const u_int8_t *lk_conflicts; /* Two dimensional conflict matrix. */
+ u_int32_t lk_modes; /* Number of lock modes in table. */
+ u_int32_t lk_max; /* Maximum number of locks. */
+ u_int32_t lk_detect; /* Deadlock detect on all conflicts. */
+
+ /* Logging. */
+ DB_LOG *lg_info; /* Return from log_open(). */
+ u_int32_t lg_max; /* Maximum file size. */
+
+ /* Memory pool. */
+ DB_MPOOL *mp_info; /* Return from memp_open(). */
+ size_t mp_mmapsize; /* Maximum file size for mmap. */
+ size_t mp_size; /* Bytes in the mpool cache. */
+
+ /* Transactions. */
+ DB_TXNMGR *tx_info; /* Return from txn_open(). */
+ u_int32_t tx_max; /* Maximum number of transactions. */
+ int (*tx_recover) /* Dispatch function for recovery. */
+ __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+
+ /*
+ * XA support.
+ *
+ * !!!
+ * Explicit representations of structures in queue.h.
+ *
+ * TAILQ_ENTRY(__db_env);
+ */
+ struct {
+ struct __db_env *tqe_next;
+ struct __db_env **tqe_prev;
+ } links;
+ int xa_rmid; /* XA Resource Manager ID. */
+ DB_TXN *xa_txn; /* XA Current transaction. */
+
+#define DB_ENV_APPINIT 0x01 /* Paths initialized by db_appinit(). */
+#define DB_ENV_CDB 0x02 /* Concurrent DB product. */
+#define DB_ENV_STANDALONE 0x04 /* Test: freestanding environment. */
+#define DB_ENV_THREAD 0x08 /* DB_ENV is multi-threaded. */
+ u_int32_t flags; /* Flags. */
+};
+
+/*******************************************************
+ * Access methods.
+ *******************************************************/
+/*
+ * !!!
+ * Changes here must be reflected in java/src/com/sleepycat/db/Db.java.
+ */
+typedef enum {
+ DB_BTREE=1, /* B+tree. */
+ DB_HASH, /* Extended Linear Hashing. */
+ DB_RECNO, /* Fixed and variable-length records. */
+ DB_UNKNOWN /* Figure it out on open. */
+} DBTYPE;
+
+#define DB_BTREEVERSION 6 /* Current btree version. */
+#define DB_BTREEOLDVER 6 /* Oldest btree version supported. */
+#define DB_BTREEMAGIC 0x053162
+
+#define DB_HASHVERSION 5 /* Current hash version. */
+#define DB_HASHOLDVER 4 /* Oldest hash version supported. */
+#define DB_HASHMAGIC 0x061561
+
+#define DB_LOGVERSION 2 /* Current log version. */
+#define DB_LOGOLDVER 2 /* Oldest log version supported. */
+#define DB_LOGMAGIC 0x040988
+
+struct __db_info {
+ int db_lorder; /* Byte order. */
+ size_t db_cachesize; /* Underlying cache size. */
+ size_t db_pagesize; /* Underlying page size. */
+
+ /* Local heap allocation. */
+ void *(*db_malloc) __P((size_t));
+ int (*dup_compare) /* Duplicate compare function. */
+ __P((const DBT *, const DBT *));
+
+ /* Btree access method. */
+ u_int32_t bt_maxkey; /* Maximum keys per page. */
+ u_int32_t bt_minkey; /* Minimum keys per page. */
+ int (*bt_compare) /* Comparison function. */
+ __P((const DBT *, const DBT *));
+ size_t (*bt_prefix) /* Prefix function. */
+ __P((const DBT *, const DBT *));
+
+ /* Hash access method. */
+ u_int32_t h_ffactor; /* Fill factor. */
+ u_int32_t h_nelem; /* Number of elements. */
+ u_int32_t (*h_hash) /* Hash function. */
+ __P((const void *, u_int32_t));
+
+ /* Recno access method. */
+ int re_pad; /* Fixed-length padding byte. */
+ int re_delim; /* Variable-length delimiting byte. */
+ u_int32_t re_len; /* Length for fixed-length records. */
+ char *re_source; /* Source file name. */
+
+#define DB_DELIMITER 0x0001 /* Recno: re_delim set. */
+#define DB_DUP 0x0002 /* Btree, Hash: duplicate keys. */
+#define DB_DUPSORT 0x0004 /* Btree, Hash: duplicate keys. */
+#define DB_FIXEDLEN 0x0008 /* Recno: fixed-length records. */
+#define DB_PAD 0x0010 /* Recno: re_pad set. */
+#define DB_RECNUM 0x0020 /* Btree: record numbers. */
+#define DB_RENUMBER 0x0040 /* Recno: renumber on insert/delete. */
+#define DB_SNAPSHOT 0x0080 /* Recno: snapshot the input. */
+ u_int32_t flags;
+};
+
+/*
+ * DB access method and cursor operation values. Each value is an operation
+ * code to which additional bit flags are added.
+ */
+#define DB_AFTER 1 /* c_put() */
+#define DB_APPEND 2 /* put() */
+#define DB_BEFORE 3 /* c_put() */
+#define DB_CHECKPOINT 4 /* log_put(), log_get() */
+#define DB_CURLSN 5 /* log_put() */
+#define DB_CURRENT 6 /* c_get(), c_put(), log_get() */
+#define DB_FIRST 7 /* c_get(), log_get() */
+#define DB_FLUSH 8 /* log_put() */
+#define DB_GET_BOTH 9 /* get(), c_get() */
+#define DB_GET_RECNO 10 /* c_get() */
+#define DB_JOIN_ITEM 11 /* c_get(); do not do primary lookup */
+#define DB_KEYFIRST 12 /* c_put() */
+#define DB_KEYLAST 13 /* c_put() */
+#define DB_LAST 14 /* c_get(), log_get() */
+#define DB_NEXT 15 /* c_get(), log_get() */
+#define DB_NEXT_DUP 16 /* c_get() */
+#define DB_NOOVERWRITE 17 /* put() */
+#define DB_NOSYNC 18 /* close() */
+#define DB_PREV 19 /* c_get(), log_get() */
+#define DB_RECORDCOUNT 20 /* stat() */
+#define DB_SET 21 /* c_get(), log_get() */
+#define DB_SET_RANGE 22 /* c_get() */
+#define DB_SET_RECNO 23 /* get(), c_get() */
+#define DB_WRITELOCK 24 /* cursor() (internal) */
+
+#define DB_OPFLAGS_MASK 0x1f /* Mask for operations flags. */
+#define DB_RMW 0x80000000 /* Acquire write flag immediately. */
+
+/*
+ * DB (user visible) error return codes.
+ *
+ * !!!
+ * Changes to any of the user visible error return codes must be reflected
+ * in java/src/com/sleepycat/db/Db.java.
+ */
+#define DB_INCOMPLETE ( -1) /* Sync didn't finish. */
+#define DB_KEYEMPTY ( -2) /* The key/data pair was deleted or
+ was never created by the user. */
+#define DB_KEYEXIST ( -3) /* The key/data pair already exists. */
+#define DB_LOCK_DEADLOCK ( -4) /* Locker killed to resolve deadlock. */
+#define DB_LOCK_NOTGRANTED ( -5) /* Lock unavailable, no-wait set. */
+#define DB_LOCK_NOTHELD ( -6) /* Lock not held by locker. */
+#define DB_NOTFOUND ( -7) /* Key/data pair not found (EOF). */
+#define DB_RUNRECOVERY ( -8) /* Panic return. */
+
+/* DB (private) error return codes. */
+#define DB_DELETED ( -9) /* Recovery file marked deleted. */
+#define DB_NEEDSPLIT (-10) /* Page needs to be split. */
+#define DB_SWAPBYTES (-11) /* Database needs byte swapping. */
+#define DB_TXN_CKP (-12) /* Encountered ckp record in log. */
+
+#define DB_FILE_ID_LEN 20 /* DB file ID length. */
+
+/* DB access method description structure. */
+struct __db {
+ void *mutexp; /* Synchronization for free threading */
+
+ /* Documented, returned information. */
+ DBTYPE type; /* DB access method. */
+ int byteswapped; /* Database byte order is swapped. */
+ int saved_open_fd; /* For fcntl lock preservation. */
+
+ DB_ENV *dbenv; /* DB_ENV structure. */
+ DB_ENV *mp_dbenv; /* DB_ENV for local mpool creation. */
+
+ void *internal; /* Access method private. */
+
+ DB_MPOOL *mp; /* The access method's mpool. */
+ DB_MPOOLFILE *mpf; /* The access method's mpool file. */
+
+ /*
+ * !!!
+ * Explicit representations of structures in queue.h.
+ *
+ * TAILQ_HEAD(free_queue, __dbc);
+ * TAILQ_HEAD(active_queue, __dbc);
+ */
+ struct {
+ struct __dbc *tqh_first;
+ struct __dbc **tqh_last;
+ } free_queue;
+ struct {
+ struct __dbc *tqh_first;
+ struct __dbc **tqh_last;
+ } active_queue;
+
+ u_int8_t fileid[DB_FILE_ID_LEN]; /* Uniquely identify this file for
+ locking. */
+ u_int32_t log_fileid; /* Logging file id. */
+ size_t pgsize; /* Logical page size of file. */
+
+ /* Local heap allocation. */
+ void *(*db_malloc) __P((size_t));
+ int (*dup_compare) /* Duplicate compare function. */
+ __P((const DBT *, const DBT *));
+ u_int32_t (*h_hash) /* Hash function. */
+ __P((const void *, u_int32_t));
+
+ /* Functions. */
+ int (*am_close) __P((DB *));
+ int (*close) __P((DB *, u_int32_t));
+ int (*cursor) __P((DB *, DB_TXN *, DBC **, u_int32_t));
+ int (*del) __P((DB *, DB_TXN *, DBT *, u_int32_t));
+ int (*fd) __P((DB *, int *));
+ int (*get) __P((DB *, DB_TXN *, DBT *, DBT *, u_int32_t));
+ int (*join) __P((DB *, DBC **, u_int32_t, DBC **));
+ int (*put) __P((DB *, DB_TXN *, DBT *, DBT *, u_int32_t));
+ int (*stat) __P((DB *, void *, void *(*)(size_t), u_int32_t));
+ int (*sync) __P((DB *, u_int32_t));
+
+#define DB_AM_CDB 0x000001 /* Concurrent Access Methods. */
+#define DB_AM_DUP 0x000002 /* DB_DUP (internal). */
+#define DB_AM_INMEM 0x000004 /* In-memory; no sync on close. */
+#define DB_AM_LOCKING 0x000008 /* Perform locking. */
+#define DB_AM_LOGGING 0x000010 /* Perform logging. */
+#define DB_AM_MLOCAL 0x000020 /* Database memory pool is local. */
+#define DB_AM_PGDEF 0x000040 /* Page size was defaulted. */
+#define DB_AM_RDONLY 0x000080 /* Database is readonly. */
+#define DB_AM_SWAP 0x000100 /* Pages need to be byte-swapped. */
+#define DB_AM_THREAD 0x000200 /* DB is multi-threaded. */
+#define DB_BT_RECNUM 0x000400 /* DB_RECNUM (internal). */
+#define DB_DBM_ERROR 0x000800 /* Error in DBM/NDBM database. */
+#define DB_RE_DELIMITER 0x001000 /* DB_DELIMITER (internal). */
+#define DB_RE_FIXEDLEN 0x002000 /* DB_FIXEDLEN (internal). */
+#define DB_RE_PAD 0x004000 /* DB_PAD (internal). */
+#define DB_RE_RENUMBER 0x008000 /* DB_RENUMBER (internal). */
+#define DB_RE_SNAPSHOT 0x010000 /* DB_SNAPSHOT (internal). */
+ u_int32_t flags;
+};
+
+struct __db_ilock { /* Internal DB access method lock. */
+ db_pgno_t pgno; /* Page being locked. */
+ u_int8_t fileid[DB_FILE_ID_LEN];/* File id. */
+};
+
+/* Cursor description structure. */
+struct __dbc {
+ DB *dbp; /* Related DB access method. */
+ DB_TXN *txn; /* Associated transaction. */
+
+ /*
+ * !!!
+ * Explicit representations of structures in queue.h.
+ *
+ * TAILQ_ENTRY(__dbc);
+ */
+ struct {
+ struct __dbc *tqe_next;
+ struct __dbc **tqe_prev;
+ } links;
+
+ u_int32_t lid; /* Default process' locker id. */
+ u_int32_t locker; /* Locker for this operation. */
+ DBT lock_dbt; /* DBT referencing lock. */
+ DB_LOCK_ILOCK lock; /* Object to be locked. */
+ DB_LOCK mylock; /* Lock held on this cursor. */
+
+ DBT rkey; /* Returned key. */
+ DBT rdata; /* Returned data. */
+
+ int (*c_am_close) __P((DBC *));
+ int (*c_am_destroy) __P((DBC *));
+ int (*c_close) __P((DBC *));
+ int (*c_del) __P((DBC *, u_int32_t));
+ int (*c_get) __P((DBC *, DBT *, DBT *, u_int32_t));
+ int (*c_put) __P((DBC *, DBT *, DBT *, u_int32_t));
+
+ void *internal; /* Access method private. */
+
+#define DBC_CONTINUE 0x001 /* Continue dup search: next item. */
+#define DBC_KEYSET 0x002 /* Continue dup search: current item. */
+#define DBC_RECOVER 0x004 /* In recovery (do not log or lock). */
+#define DBC_RMW 0x008 /* Acquire write flag in read op. */
+#define DBC_WRITER 0x010 /* Cursor immediately writing (CDB). */
+ u_int32_t flags;
+};
+
+/* Btree/recno statistics structure. */
+struct __db_bt_stat {
+ u_int32_t bt_flags; /* Open flags. */
+ u_int32_t bt_maxkey; /* Maxkey value. */
+ u_int32_t bt_minkey; /* Minkey value. */
+ u_int32_t bt_re_len; /* Fixed-length record length. */
+ u_int32_t bt_re_pad; /* Fixed-length record pad. */
+ u_int32_t bt_pagesize; /* Page size. */
+ u_int32_t bt_levels; /* Tree levels. */
+ u_int32_t bt_nrecs; /* Number of records. */
+ u_int32_t bt_int_pg; /* Internal pages. */
+ u_int32_t bt_leaf_pg; /* Leaf pages. */
+ u_int32_t bt_dup_pg; /* Duplicate pages. */
+ u_int32_t bt_over_pg; /* Overflow pages. */
+ u_int32_t bt_free; /* Pages on the free list. */
+ u_int32_t bt_int_pgfree; /* Bytes free in internal pages. */
+ u_int32_t bt_leaf_pgfree; /* Bytes free in leaf pages. */
+ u_int32_t bt_dup_pgfree; /* Bytes free in duplicate pages. */
+ u_int32_t bt_over_pgfree; /* Bytes free in overflow pages. */
+ u_int32_t bt_magic; /* Magic number. */
+ u_int32_t bt_version; /* Version number. */
+};
+
+/* Hash statistics structure. */
+struct __db_h_stat {
+ u_int32_t hash_accesses; /* Number of accesses to this table. */
+ u_int32_t hash_collisions; /* Number of collisions on search. */
+ u_int32_t hash_expansions; /* Number of times we added a bucket. */
+ u_int32_t hash_overflows; /* Number of overflow pages. */
+ u_int32_t hash_bigpages; /* Number of big key/data pages. */
+ u_int32_t hash_dup; /* Number of dup pages. */
+ u_int32_t hash_free; /* Pages on the free list. */
+ u_int32_t hash_bfree; /* Bytes free on bucket pages. */
+ u_int32_t hash_dup_free; /* Bytes free on duplicate pages. */
+ u_int32_t hash_big_bfree; /* Bytes free on big item pages. */
+ u_int32_t hash_buckets; /* Number of hash buckets. */
+ u_int32_t hash_put; /* Number of puts. */
+ u_int32_t hash_deleted; /* Number of deletes. */
+ u_int32_t hash_get; /* Number of gets. */
+ u_int32_t hash_magic; /* Magic number. */
+ u_int32_t hash_version; /* Version number. */
+ u_int32_t hash_pagesize; /* Page size. */
+ u_int32_t hash_nrecs; /* Number of records. */
+};
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+int db_appinit __P((const char *, char * const *, DB_ENV *, u_int32_t));
+int db_appexit __P((DB_ENV *));
+int db_jump_set __P((void *, int));
+int db_open __P((const char *,
+ DBTYPE, u_int32_t, int, DB_ENV *, DB_INFO *, DB **));
+int db_value_set __P((int, int));
+char *db_version __P((int *, int *, int *));
+int db_xa_open __P((const char *,
+ DBTYPE, u_int32_t, int, DB_INFO *, DB **));
+#if defined(__cplusplus)
+}
+#endif
+
+/*******************************************************
+ * Locking
+ *******************************************************/
+#define DB_LOCKVERSION 1
+#define DB_LOCKMAGIC 0x090193
+
+/* Flag values for lock_vec(), lock_get(). */
+#define DB_LOCK_NOWAIT 0x01 /* Don't wait on unavailable lock. */
+#define DB_LOCK_UPGRADE 0x02 /* Upgrade an existing lock instead
+ of granting a new one (internal). */
+
+/* Flag values for lock_detect(). */
+#define DB_LOCK_CONFLICT 0x01 /* Run on any conflict. */
+
+/*
+ * Request types.
+ *
+ * !!!
+ * Changes here must be reflected in java/src/com/sleepycat/db/Db.java.
+ */
+typedef enum {
+ DB_LOCK_DUMP=0, /* Display held locks. */
+ DB_LOCK_GET, /* Get the lock. */
+ DB_LOCK_INHERIT, /* Pass locks to parent. */
+ DB_LOCK_PUT, /* Release the lock. */
+ DB_LOCK_PUT_ALL, /* Release locker's locks. */
+ DB_LOCK_PUT_OBJ /* Release locker's locks on obj. */
+} db_lockop_t;
+
+/*
+ * Simple R/W lock modes and for multi-granularity intention locking.
+ *
+ * !!!
+ * These values are NOT random, as they are used as an index into the lock
+ * conflicts arrays, i.e., DB_LOCK_IWRITE must be == 3, and DB_LOCK_IREAD
+ * must be == 4.
+ *
+ * !!!
+ * Changes here must be reflected in java/src/com/sleepycat/db/Db.java.
+ */
+typedef enum {
+ DB_LOCK_NG=0, /* Not granted. */
+ DB_LOCK_READ, /* Shared/read. */
+ DB_LOCK_WRITE, /* Exclusive/write. */
+ DB_LOCK_IWRITE, /* Intent exclusive/write. */
+ DB_LOCK_IREAD, /* Intent to share/read. */
+ DB_LOCK_IWR /* Intent to read and write. */
+} db_lockmode_t;
+
+/*
+ * Status of a lock.
+ */
+typedef enum {
+ DB_LSTAT_ABORTED, /* Lock belongs to an aborted txn. */
+ DB_LSTAT_ERR, /* Lock is bad. */
+ DB_LSTAT_FREE, /* Lock is unallocated. */
+ DB_LSTAT_HELD, /* Lock is currently held. */
+ DB_LSTAT_NOGRANT, /* Lock was not granted. */
+ DB_LSTAT_PENDING, /* Lock was waiting and has been
+ * promoted; waiting for the owner
+ * to run and upgrade it to held. */
+ DB_LSTAT_WAITING /* Lock is on the wait queue. */
+} db_status_t;
+
+/* Lock request structure. */
+struct __db_lockreq {
+ db_lockop_t op; /* Operation. */
+ db_lockmode_t mode; /* Requested mode. */
+ u_int32_t locker; /* Locker identity. */
+ DBT *obj; /* Object being locked. */
+ DB_LOCK lock; /* Lock returned. */
+};
+
+/*
+ * Commonly used conflict matrices.
+ *
+ * Standard Read/Write (or exclusive/shared) locks.
+ */
+#define DB_LOCK_RW_N 3
+extern const u_int8_t db_rw_conflicts[];
+
+/* Multi-granularity locking. */
+#define DB_LOCK_RIW_N 6
+extern const u_int8_t db_riw_conflicts[];
+
+struct __db_lock_stat {
+ u_int32_t st_magic; /* Lock file magic number. */
+ u_int32_t st_version; /* Lock file version number. */
+ u_int32_t st_maxlocks; /* Maximum number of locks in table. */
+ u_int32_t st_nmodes; /* Number of lock modes. */
+ u_int32_t st_numobjs; /* Number of objects. */
+ u_int32_t st_nlockers; /* Number of lockers. */
+ u_int32_t st_nconflicts; /* Number of lock conflicts. */
+ u_int32_t st_nrequests; /* Number of lock gets. */
+ u_int32_t st_nreleases; /* Number of lock puts. */
+ u_int32_t st_ndeadlocks; /* Number of lock deadlocks. */
+ u_int32_t st_region_wait; /* Region lock granted after wait. */
+ u_int32_t st_region_nowait; /* Region lock granted without wait. */
+ u_int32_t st_refcnt; /* Region reference count. */
+ u_int32_t st_regsize; /* Region size. */
+};
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+int lock_close __P((DB_LOCKTAB *));
+int lock_detect __P((DB_LOCKTAB *, u_int32_t, u_int32_t));
+int lock_get __P((DB_LOCKTAB *,
+ u_int32_t, u_int32_t, const DBT *, db_lockmode_t, DB_LOCK *));
+int lock_id __P((DB_LOCKTAB *, u_int32_t *));
+int lock_open __P((const char *,
+ u_int32_t, int, DB_ENV *, DB_LOCKTAB **));
+int lock_put __P((DB_LOCKTAB *, DB_LOCK));
+int lock_tget __P((DB_LOCKTAB *,
+ DB_TXN *, u_int32_t, const DBT *, db_lockmode_t, DB_LOCK *));
+int lock_stat __P((DB_LOCKTAB *, DB_LOCK_STAT **, void *(*)(size_t)));
+int lock_unlink __P((const char *, int, DB_ENV *));
+int lock_vec __P((DB_LOCKTAB *,
+ u_int32_t, u_int32_t, DB_LOCKREQ *, int, DB_LOCKREQ **));
+int lock_tvec __P((DB_LOCKTAB *,
+ DB_TXN *, u_int32_t, DB_LOCKREQ *, int, DB_LOCKREQ **));
+#if defined(__cplusplus)
+}
+#endif
+
+/*******************************************************
+ * Logging.
+ *******************************************************/
+/* Flag values for log_archive(). */
+#define DB_ARCH_ABS 0x001 /* Absolute pathnames. */
+#define DB_ARCH_DATA 0x002 /* Data files. */
+#define DB_ARCH_LOG 0x004 /* Log files. */
+
+/*
+ * A DB_LSN has two parts, a fileid which identifies a specific file, and an
+ * offset within that file. The fileid is an unsigned 4-byte quantity that
+ * uniquely identifies a file within the log directory -- currently a simple
+ * counter inside the log. The offset is also an unsigned 4-byte value. The
+ * log manager guarantees the offset is never more than 4 bytes by switching
+ * to a new log file before the maximum length imposed by an unsigned 4-byte
+ * offset is reached.
+ */
+struct __db_lsn {
+ u_int32_t file; /* File ID. */
+ u_int32_t offset; /* File offset. */
+};
+
+/* Log statistics structure. */
+struct __db_log_stat {
+ u_int32_t st_magic; /* Log file magic number. */
+ u_int32_t st_version; /* Log file version number. */
+ int st_mode; /* Log file mode. */
+ u_int32_t st_lg_max; /* Maximum log file size. */
+ u_int32_t st_w_bytes; /* Bytes to log. */
+ u_int32_t st_w_mbytes; /* Megabytes to log. */
+ u_int32_t st_wc_bytes; /* Bytes to log since checkpoint. */
+ u_int32_t st_wc_mbytes; /* Megabytes to log since checkpoint. */
+ u_int32_t st_wcount; /* Total syncs to the log. */
+ u_int32_t st_scount; /* Total writes to the log. */
+ u_int32_t st_region_wait; /* Region lock granted after wait. */
+ u_int32_t st_region_nowait; /* Region lock granted without wait. */
+ u_int32_t st_cur_file; /* Current log file number. */
+ u_int32_t st_cur_offset; /* Current log file offset. */
+ u_int32_t st_refcnt; /* Region reference count. */
+ u_int32_t st_regsize; /* Region size. */
+};
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+int log_archive __P((DB_LOG *, char **[], u_int32_t, void *(*)(size_t)));
+int log_close __P((DB_LOG *));
+int log_compare __P((const DB_LSN *, const DB_LSN *));
+int log_file __P((DB_LOG *, const DB_LSN *, char *, size_t));
+int log_flush __P((DB_LOG *, const DB_LSN *));
+int log_get __P((DB_LOG *, DB_LSN *, DBT *, u_int32_t));
+int log_open __P((const char *, u_int32_t, int, DB_ENV *, DB_LOG **));
+int log_put __P((DB_LOG *, DB_LSN *, const DBT *, u_int32_t));
+int log_register __P((DB_LOG *, DB *, const char *, DBTYPE, u_int32_t *));
+int log_stat __P((DB_LOG *, DB_LOG_STAT **, void *(*)(size_t)));
+int log_unlink __P((const char *, int, DB_ENV *));
+int log_unregister __P((DB_LOG *, u_int32_t));
+#if defined(__cplusplus)
+}
+#endif
+
+/*******************************************************
+ * Mpool
+ *******************************************************/
+/* Flag values for memp_fget(). */
+#define DB_MPOOL_CREATE 0x001 /* Create a page. */
+#define DB_MPOOL_LAST 0x002 /* Return the last page. */
+#define DB_MPOOL_NEW 0x004 /* Create a new page. */
+
+/* Flag values for memp_fput(), memp_fset(). */
+#define DB_MPOOL_CLEAN 0x001 /* Clear modified bit. */
+#define DB_MPOOL_DIRTY 0x002 /* Page is modified. */
+#define DB_MPOOL_DISCARD 0x004 /* Don't cache the page. */
+
+/* Mpool statistics structure. */
+struct __db_mpool_stat {
+ size_t st_cachesize; /* Cache size. */
+ u_int32_t st_cache_hit; /* Pages found in the cache. */
+ u_int32_t st_cache_miss; /* Pages not found in the cache. */
+ u_int32_t st_map; /* Pages from mapped files. */
+ u_int32_t st_page_create; /* Pages created in the cache. */
+ u_int32_t st_page_in; /* Pages read in. */
+ u_int32_t st_page_out; /* Pages written out. */
+ u_int32_t st_ro_evict; /* Clean pages forced from the cache. */
+ u_int32_t st_rw_evict; /* Dirty pages forced from the cache. */
+ u_int32_t st_hash_buckets; /* Number of hash buckets. */
+ u_int32_t st_hash_searches; /* Total hash chain searches. */
+ u_int32_t st_hash_longest; /* Longest hash chain searched. */
+ u_int32_t st_hash_examined; /* Total hash entries searched. */
+ u_int32_t st_page_clean; /* Clean pages. */
+ u_int32_t st_page_dirty; /* Dirty pages. */
+ u_int32_t st_page_trickle; /* Pages written by memp_trickle. */
+ u_int32_t st_region_wait; /* Region lock granted after wait. */
+ u_int32_t st_region_nowait; /* Region lock granted without wait. */
+ u_int32_t st_refcnt; /* Region reference count. */
+ u_int32_t st_regsize; /* Region size. */
+};
+
+/* Mpool file open information structure. */
+struct __db_mpool_finfo {
+ int ftype; /* File type. */
+ DBT *pgcookie; /* Byte-string passed to pgin/pgout. */
+ u_int8_t *fileid; /* Unique file ID. */
+ int32_t lsn_offset; /* LSN offset in page. */
+ u_int32_t clear_len; /* Cleared length on created pages. */
+};
+
+/* Mpool file statistics structure. */
+struct __db_mpool_fstat {
+ char *file_name; /* File name. */
+ size_t st_pagesize; /* Page size. */
+ u_int32_t st_cache_hit; /* Pages found in the cache. */
+ u_int32_t st_cache_miss; /* Pages not found in the cache. */
+ u_int32_t st_map; /* Pages from mapped files. */
+ u_int32_t st_page_create; /* Pages created in the cache. */
+ u_int32_t st_page_in; /* Pages read in. */
+ u_int32_t st_page_out; /* Pages written out. */
+};
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+int memp_close __P((DB_MPOOL *));
+int memp_fclose __P((DB_MPOOLFILE *));
+int memp_fget __P((DB_MPOOLFILE *, db_pgno_t *, u_int32_t, void *));
+int memp_fopen __P((DB_MPOOL *, const char *,
+ u_int32_t, int, size_t, DB_MPOOL_FINFO *, DB_MPOOLFILE **));
+int memp_fput __P((DB_MPOOLFILE *, void *, u_int32_t));
+int memp_fset __P((DB_MPOOLFILE *, void *, u_int32_t));
+int memp_fsync __P((DB_MPOOLFILE *));
+int memp_open __P((const char *, u_int32_t, int, DB_ENV *, DB_MPOOL **));
+int memp_register __P((DB_MPOOL *, int,
+ int (*)(db_pgno_t, void *, DBT *),
+ int (*)(db_pgno_t, void *, DBT *)));
+int memp_stat __P((DB_MPOOL *,
+ DB_MPOOL_STAT **, DB_MPOOL_FSTAT ***, void *(*)(size_t)));
+int memp_sync __P((DB_MPOOL *, DB_LSN *));
+int memp_trickle __P((DB_MPOOL *, int, int *));
+int memp_unlink __P((const char *, int, DB_ENV *));
+#if defined(__cplusplus)
+}
+#endif
+
+/*******************************************************
+ * Transactions.
+ *******************************************************/
+#define DB_TXNVERSION 1
+#define DB_TXNMAGIC 0x041593
+
+/* Operations values to the tx_recover() function. */
+#define DB_TXN_BACKWARD_ROLL 1 /* Read the log backwards. */
+#define DB_TXN_FORWARD_ROLL 2 /* Read the log forwards. */
+#define DB_TXN_OPENFILES 3 /* Read for open files. */
+#define DB_TXN_REDO 4 /* Redo the operation. */
+#define DB_TXN_UNDO 5 /* Undo the operation. */
+
+/* Internal transaction status values. */
+
+/* Transaction statistics structure. */
+struct __db_txn_active {
+ u_int32_t txnid; /* Transaction ID */
+ DB_LSN lsn; /* Lsn of the begin record */
+};
+
+struct __db_txn_stat {
+ DB_LSN st_last_ckp; /* lsn of the last checkpoint */
+ DB_LSN st_pending_ckp; /* last checkpoint did not finish */
+ time_t st_time_ckp; /* time of last checkpoint */
+ u_int32_t st_last_txnid; /* last transaction id given out */
+ u_int32_t st_maxtxns; /* maximum number of active txns */
+ u_int32_t st_naborts; /* number of aborted transactions */
+ u_int32_t st_nbegins; /* number of begun transactions */
+ u_int32_t st_ncommits; /* number of committed transactions */
+ u_int32_t st_nactive; /* number of active transactions */
+ DB_TXN_ACTIVE
+ *st_txnarray; /* array of active transactions */
+ u_int32_t st_region_wait; /* Region lock granted after wait. */
+ u_int32_t st_region_nowait; /* Region lock granted without wait. */
+ u_int32_t st_refcnt; /* Region reference count. */
+ u_int32_t st_regsize; /* Region size. */
+};
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+int txn_abort __P((DB_TXN *));
+int txn_begin __P((DB_TXNMGR *, DB_TXN *, DB_TXN **));
+int txn_checkpoint __P((const DB_TXNMGR *, u_int32_t, u_int32_t));
+int txn_commit __P((DB_TXN *));
+int txn_close __P((DB_TXNMGR *));
+u_int32_t txn_id __P((DB_TXN *));
+int txn_open __P((const char *, u_int32_t, int, DB_ENV *, DB_TXNMGR **));
+int txn_prepare __P((DB_TXN *));
+int txn_stat __P((DB_TXNMGR *, DB_TXN_STAT **, void *(*)(size_t)));
+int txn_unlink __P((const char *, int, DB_ENV *));
+#if defined(__cplusplus)
+}
+#endif
+
+#ifndef DB_DBM_HSEARCH
+#define DB_DBM_HSEARCH 0 /* No historic interfaces by default. */
+#endif
+#if DB_DBM_HSEARCH != 0
+/*******************************************************
+ * Dbm/Ndbm historic interfaces.
+ *******************************************************/
+#define DBM_INSERT 0 /* Flags to dbm_store(). */
+#define DBM_REPLACE 1
+
+/*
+ * The db(3) support for ndbm(3) always appends this suffix to the
+ * file name to avoid overwriting the user's original database.
+ */
+#define DBM_SUFFIX ".db"
+
+#if defined(_XPG4_2)
+typedef struct {
+ char *dptr;
+ size_t dsize;
+} datum;
+#else
+typedef struct {
+ char *dptr;
+ int dsize;
+} datum;
+#endif
+
+/*
+ * Translate DBM calls into DB calls so that DB doesn't step on the
+ * application's name space.
+ *
+ * The global variables dbrdonly, dirf and pagf were not retained when
+ * 4BSD replaced the dbm interface with ndbm, and are not support here.
+ */
+#define dbminit(a) __db_dbm_init(a)
+#define dbmclose __db_dbm_close
+#if !defined(__cplusplus)
+#define delete(a) __db_dbm_delete(a)
+#endif
+#define fetch(a) __db_dbm_fetch(a)
+#define firstkey __db_dbm_firstkey
+#define nextkey(a) __db_dbm_nextkey(a)
+#define store(a, b) __db_dbm_store(a, b)
+
+/* Prototype the DB calls. */
+#if defined(__cplusplus)
+extern "C" {
+#endif
+int __db_dbm_close __P((void));
+int __db_dbm_dbrdonly __P((void));
+int __db_dbm_delete __P((datum));
+int __db_dbm_dirf __P((void));
+datum __db_dbm_fetch __P((datum));
+datum __db_dbm_firstkey __P((void));
+int __db_dbm_init __P((char *));
+datum __db_dbm_nextkey __P((datum));
+int __db_dbm_pagf __P((void));
+int __db_dbm_store __P((datum, datum));
+#if defined(__cplusplus)
+}
+#endif
+
+/*
+ * Translate NDBM calls into DB calls so that DB doesn't step on the
+ * application's name space.
+ */
+#define dbm_clearerr(a) __db_ndbm_clearerr(a)
+#define dbm_close(a) __db_ndbm_close(a)
+#define dbm_delete(a, b) __db_ndbm_delete(a, b)
+#define dbm_dirfno(a) __db_ndbm_dirfno(a)
+#define dbm_error(a) __db_ndbm_error(a)
+#define dbm_fetch(a, b) __db_ndbm_fetch(a, b)
+#define dbm_firstkey(a) __db_ndbm_firstkey(a)
+#define dbm_nextkey(a) __db_ndbm_nextkey(a)
+#define dbm_open(a, b, c) __db_ndbm_open(a, b, c)
+#define dbm_pagfno(a) __db_ndbm_pagfno(a)
+#define dbm_rdonly(a) __db_ndbm_rdonly(a)
+#define dbm_store(a, b, c, d) __db_ndbm_store(a, b, c, d)
+
+/* Prototype the DB calls. */
+#if defined(__cplusplus)
+extern "C" {
+#endif
+int __db_ndbm_clearerr __P((DBM *));
+void __db_ndbm_close __P((DBM *));
+int __db_ndbm_delete __P((DBM *, datum));
+int __db_ndbm_dirfno __P((DBM *));
+int __db_ndbm_error __P((DBM *));
+datum __db_ndbm_fetch __P((DBM *, datum));
+datum __db_ndbm_firstkey __P((DBM *));
+datum __db_ndbm_nextkey __P((DBM *));
+DBM *__db_ndbm_open __P((const char *, int, int));
+int __db_ndbm_pagfno __P((DBM *));
+int __db_ndbm_rdonly __P((DBM *));
+int __db_ndbm_store __P((DBM *, datum, datum, int));
+#if defined(__cplusplus)
+}
+#endif
+
+/*******************************************************
+ * Hsearch historic interface.
+ *******************************************************/
+typedef enum {
+ FIND, ENTER
+} ACTION;
+
+typedef struct entry {
+ char *key;
+ char *data;
+} ENTRY;
+
+/*
+ * Translate HSEARCH calls into DB calls so that DB doesn't step on the
+ * application's name space.
+ */
+#define hcreate(a) __db_hcreate(a)
+#define hdestroy __db_hdestroy
+#define hsearch(a, b) __db_hsearch(a, b)
+
+/* Prototype the DB calls. */
+#if defined(__cplusplus)
+extern "C" {
+#endif
+int __db_hcreate __P((size_t));
+void __db_hdestroy __P((void));
+ENTRY *__db_hsearch __P((ENTRY, ACTION));
+#if defined(__cplusplus)
+}
+#endif
+#endif /* DB_DBM_HSEARCH */
+
+/*
+ * XXX
+ * MacOS: Reset Metrowerks C enum sizes.
+ */
+#ifdef __MWERKS__
+#pragma enumsalwaysint reset
+#endif
+#endif /* !_DB_H_ */
diff --git a/usr/src/cmd/sendmail/db/db/db.c b/usr/src/cmd/sendmail/db/db/db.c
new file mode 100644
index 0000000000..9e4c04856a
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/db/db.c
@@ -0,0 +1,785 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+/*
+ * Copyright (c) 1990, 1993, 1994, 1995, 1996
+ * Keith Bostic. All rights reserved.
+ */
+/*
+ * Copyright (c) 1990, 1993, 1994, 1995
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)db.c 10.75 (Sleepycat) 12/3/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <errno.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#endif
+
+#include "db_int.h"
+#include "shqueue.h"
+#include "db_page.h"
+#include "db_shash.h"
+#include "db_swap.h"
+#include "btree.h"
+#include "hash.h"
+#include "mp.h"
+#include "db_am.h"
+#include "common_ext.h"
+
+/*
+ * If the metadata page has the flag set, set the local flag. If the page
+ * does NOT have the flag set, return EINVAL if the user's dbinfo argument
+ * caused us to already set the local flag.
+ */
+#define DBINFO_FCHK(dbp, fn, meta_flags, m_name, dbp_name) { \
+ if ((meta_flags) & (m_name)) \
+ F_SET(dbp, dbp_name); \
+ else \
+ if (F_ISSET(dbp, dbp_name)) { \
+ __db_err(dbenv, \
+ "%s: %s specified in dbinfo argument but not set in file", \
+ fname, fn); \
+ goto einval; \
+ } \
+}
+
+/*
+ * db_open --
+ * Main library interface to the DB access methods.
+ */
+int
+db_open(fname, type, flags, mode, dbenv, dbinfo, dbpp)
+ const char *fname;
+ DBTYPE type;
+ u_int32_t flags;
+ int mode;
+ DB_ENV *dbenv;
+ DB_INFO *dbinfo;
+ DB **dbpp;
+{
+ BTMETA *btm;
+ DB *dbp;
+ DBT pgcookie;
+ DB_ENV *envp, t_dbenv;
+ DB_MPOOL_FINFO finfo;
+ DB_PGINFO pginfo;
+ HASHHDR *hashm;
+ size_t cachesize;
+ ssize_t nr;
+ u_int32_t iopsize;
+ int fd, ftype, need_fileid, restore, ret, retry_cnt, swapped;
+ char *real_name, mbuf[512];
+
+ /* Validate arguments. */
+#ifdef HAVE_SPINLOCKS
+#define OKFLAGS (DB_CREATE | DB_FCNTL_LOCKING | DB_NOMMAP | DB_RDONLY | DB_THREAD | DB_TRUNCATE)
+#else
+#define OKFLAGS (DB_CREATE | DB_FCNTL_LOCKING | DB_NOMMAP | DB_RDONLY | DB_TRUNCATE)
+#endif
+ if ((ret = __db_fchk(dbenv, "db_open", flags, OKFLAGS)) != 0)
+ return (ret);
+
+ if (dbenv != NULL) {
+ /*
+ * You can't specify threads during the db_open() if the
+ * environment wasn't configured with them.
+ */
+ if (LF_ISSET(DB_THREAD) && !F_ISSET(dbenv, DB_ENV_THREAD)) {
+ __db_err(dbenv,
+ "environment not created using DB_THREAD");
+ return (EINVAL);
+ }
+
+ /*
+ * Specifying a cachesize to db_open(3), after creating an
+ * environment with DB_INIT_MPOOL, is a common mistake.
+ */
+ if (dbenv->mp_info != NULL &&
+ dbinfo != NULL && dbinfo->db_cachesize != 0) {
+ __db_err(dbenv,
+ "cachesize will be ignored if environment exists");
+ return (EINVAL);
+ }
+ }
+
+ /* Allocate the DB structure, reference the DB_ENV structure. */
+ if ((ret = __os_calloc(1, sizeof(DB), &dbp)) != 0)
+ return (ret);
+ dbp->dbenv = dbenv;
+
+ /* Initialize for error return. */
+ dbp->saved_open_fd = fd = -1;
+ need_fileid = 1;
+ real_name = NULL;
+
+ /* Random initialization. */
+ TAILQ_INIT(&dbp->free_queue);
+ TAILQ_INIT(&dbp->active_queue);
+ if ((ret = __db_init_wrapper(dbp)) != 0)
+ goto err;
+
+ /* Convert the db_open(3) flags. */
+ if (LF_ISSET(DB_RDONLY))
+ F_SET(dbp, DB_AM_RDONLY);
+ if (LF_ISSET(DB_THREAD))
+ F_SET(dbp, DB_AM_THREAD);
+
+ /* Convert the dbinfo structure flags. */
+ if (dbinfo != NULL) {
+ /*
+ * !!!
+ * We can't check for illegal flags until we know what type
+ * of open we're doing.
+ */
+ if (F_ISSET(dbinfo, DB_DELIMITER))
+ F_SET(dbp, DB_RE_DELIMITER);
+ if (F_ISSET(dbinfo, DB_DUP))
+ F_SET(dbp, DB_AM_DUP);
+ if (F_ISSET(dbinfo, DB_FIXEDLEN))
+ F_SET(dbp, DB_RE_FIXEDLEN);
+ if (F_ISSET(dbinfo, DB_PAD))
+ F_SET(dbp, DB_RE_PAD);
+ if (F_ISSET(dbinfo, DB_RECNUM))
+ F_SET(dbp, DB_BT_RECNUM);
+ if (F_ISSET(dbinfo, DB_RENUMBER))
+ F_SET(dbp, DB_RE_RENUMBER);
+ if (F_ISSET(dbinfo, DB_SNAPSHOT))
+ F_SET(dbp, DB_RE_SNAPSHOT);
+ }
+
+ /*
+ * Set based on the dbenv fields, although no logging or transactions
+ * are possible for temporary files.
+ */
+ if (dbenv != NULL) {
+ if (dbenv->lk_info != NULL)
+ if (F_ISSET(dbenv, DB_ENV_CDB))
+ F_SET(dbp, DB_AM_CDB);
+ else
+ F_SET(dbp, DB_AM_LOCKING);
+ if (fname != NULL && dbenv->lg_info != NULL)
+ F_SET(dbp, DB_AM_LOGGING);
+ }
+
+ /* Set the common fields. */
+ if (dbinfo == NULL) {
+ dbp->pgsize = 0;
+ dbp->db_malloc = NULL;
+ dbp->dup_compare = NULL;
+ } else {
+ /*
+ * We don't want anything that's not a power-of-2, as we rely
+ * on that for alignment of various types on the pages.
+ */
+ if ((dbp->pgsize = dbinfo->db_pagesize) != 0 &&
+ (u_int32_t)1 << __db_log2(dbp->pgsize) != dbp->pgsize) {
+ __db_err(dbenv, "page sizes must be a power-of-2");
+ goto einval;
+ }
+ dbp->pgsize = dbinfo->db_pagesize;
+ dbp->db_malloc = dbinfo->db_malloc;
+ if (F_ISSET(dbinfo, DB_DUPSORT)) {
+ if (F_ISSET(dbinfo, DB_DUP))
+ dbp->dup_compare = dbinfo->dup_compare == NULL ?
+ __bam_defcmp : dbinfo->dup_compare;
+ else {
+ __db_err(dbenv, "DB_DUPSORT requires DB_DUP");
+ goto einval;
+ }
+ F_CLR(dbinfo, DB_DUPSORT);
+ }
+ }
+
+ /* Fill in the default file mode. */
+ if (mode == 0)
+ mode = __db_omode("rwrw--");
+
+ /* Check if the user wants us to swap byte order. */
+ if (dbinfo != NULL)
+ switch (ret = __db_byteorder(dbenv, dbinfo->db_lorder)) {
+ case 0:
+ break;
+ case DB_SWAPBYTES:
+ F_SET(dbp, DB_AM_SWAP);
+ break;
+ default:
+ goto err;
+ }
+ dbp->byteswapped = F_ISSET(dbp, DB_AM_SWAP) ? 1 : 0;
+
+ /*
+ * If we have a file name, try and read the first page, figure out
+ * what type of file it is, and initialize everything we can based
+ * on that file's meta-data page.
+ *
+ * XXX
+ * We don't actually expect zero-length strings as arguments. We
+ * do the check, permitting them, because scripting languages, e.g.,
+ * the Tcl test suite, doesn't know anything about passing NULL's.
+ */
+ if (fname != NULL && fname[0] != '\0') {
+ /* Get the real file name. */
+ if ((ret = __db_appname(dbenv,
+ DB_APP_DATA, NULL, fname, 0, NULL, &real_name)) != 0)
+ goto err;
+
+ /*
+ * Open the backing file. We need to make sure that multiple
+ * processes attempting to create the file at the same time
+ * are properly ordered so that only one of them creates the
+ * "unique" file id, so we open it O_EXCL and O_CREAT so two
+ * simultaneous attempts to create the region will return
+ * failure in one of the attempts. If we're one of the ones
+ * that fail, we simply retry without the O_CREAT flag, which
+ * will require that the meta-data page exist.
+ */
+ retry_cnt = 0;
+open_retry: if (LF_ISSET(DB_CREATE)) {
+ if ((ret = __db_open(real_name, flags | DB_EXCL,
+ OKFLAGS | DB_EXCL, mode, &fd)) != 0)
+ if (ret == EEXIST) {
+ LF_CLR(DB_CREATE);
+ goto open_retry;
+ } else {
+ __db_err(dbenv,
+ "%s: %s", fname, strerror(ret));
+ goto err;
+ }
+ } else
+ if ((ret = __db_open(real_name,
+ flags, OKFLAGS, mode, &fd)) != 0) {
+ __db_err(dbenv, "%s: %s", fname, strerror(ret));
+ goto err;
+ }
+
+ /*
+ * Use the optimum I/O size as the pagesize if a pagesize not
+ * specified. Some filesystems have 64K as their optimum I/O
+ * size, but as that results in impossibly large default cache
+ * sizes, we limit the default pagesize to 16K.
+ */
+ if (dbp->pgsize == 0) {
+ if ((ret = __os_ioinfo(real_name,
+ fd, NULL, NULL, &iopsize)) != 0) {
+ __db_err(dbenv,
+ "%s: %s", real_name, strerror(ret));
+ goto err;
+ }
+ if (iopsize < 512)
+ iopsize = 512;
+ if (iopsize > 16 * 1024)
+ iopsize = 16 * 1024;
+
+ /*
+ * Sheer paranoia, but we don't want anything that's
+ * not a power-of-2, as we rely on that for alignment
+ * of various types on the pages.
+ */
+ DB_ROUNDOFF(iopsize, 512);
+
+ dbp->pgsize = iopsize;
+ F_SET(dbp, DB_AM_PGDEF);
+ }
+
+ /*
+ * Try and read the first disk sector -- this code assumes
+ * that the meta-data for all access methods fits in 512
+ * bytes, and that no database will be smaller than that.
+ */
+ if ((ret = __os_read(fd, mbuf, sizeof(mbuf), &nr)) != 0)
+ goto err;
+
+ if (LF_ISSET(DB_FCNTL_LOCKING))
+ dbp->saved_open_fd = fd;
+ else
+ (void)__os_close(fd);
+ fd = -1;
+
+ if (nr != sizeof(mbuf)) {
+ if (nr != 0) {
+ __db_err(dbenv,
+ "%s: unexpected file format", fname);
+ goto einval;
+ }
+ /*
+ * The only way we can reach here with the DB_CREATE
+ * flag set is if we created the file. If that's not
+ * the case, then a) someone else created the file
+ * but has not yet written out the meta-data page, or
+ * b) we truncated the file (DB_TRUNCATE) leaving it
+ * zero-length. In the case of a), we want to sleep
+ * and give the file creator some time to write the
+ * metadata page. In the case of b), charge forward.
+ * Note, there is a race in the case of two processes
+ * opening the file with the DB_TRUNCATE flag set at
+ * roughly the same time, and they could theoretically
+ * hurt each other, although it's pretty unlikely.
+ */
+ if (retry_cnt++ < 3 &&
+ !LF_ISSET(DB_CREATE | DB_TRUNCATE)) {
+ __os_sleep(1, 0);
+ goto open_retry;
+ }
+ if (type == DB_UNKNOWN) {
+ __db_err(dbenv,
+ "%s: DBTYPE of unknown with empty file",
+ fname);
+ goto einval;
+ }
+ goto empty;
+ }
+
+ /*
+ * A found file overrides some user information. We'll check
+ * for possible error conditions based on conflicts between
+ * the file and the user's arguments below.
+ */
+ swapped = 0;
+ F_CLR(dbp, DB_AM_SWAP);
+
+retry: switch (((BTMETA *)mbuf)->magic) {
+ case DB_BTREEMAGIC:
+ if (type != DB_BTREE &&
+ type != DB_RECNO && type != DB_UNKNOWN)
+ goto einval;
+
+ btm = (BTMETA *)mbuf;
+ if (swapped && (ret = __bam_mswap((PAGE *)btm)) != 0)
+ goto err;
+
+ if (btm->version < DB_BTREEOLDVER ||
+ btm->version > DB_BTREEVERSION) {
+ __db_err(dbenv,
+ "%s: unsupported btree version number %lu",
+ fname, (u_long)btm->version);
+ goto einval;
+ }
+ dbp->pgsize = btm->pagesize;
+ F_CLR(dbp, DB_AM_PGDEF);
+
+ if ((ret = __db_fchk(dbenv,
+ "db_open", btm->flags, BTM_MASK)) != 0)
+ goto err;
+ DBINFO_FCHK(dbp, "DB_DUP",
+ btm->flags, BTM_DUP, DB_AM_DUP);
+ if (F_ISSET(btm, BTM_RECNO)) {
+ DBINFO_FCHK(dbp, "DB_FIXEDLEN",
+ btm->flags, BTM_FIXEDLEN, DB_RE_FIXEDLEN);
+ DBINFO_FCHK(dbp, "DB_RENUMBER",
+ btm->flags, BTM_RENUMBER, DB_RE_RENUMBER);
+ type = DB_RECNO;
+ } else {
+ DBINFO_FCHK(dbp, "DB_RECNUM",
+ btm->flags, BTM_RECNUM, DB_BT_RECNUM);
+ type = DB_BTREE;
+ }
+
+ /* Copy the file's unique id. */
+ need_fileid = 0;
+ memcpy(dbp->fileid, btm->uid, DB_FILE_ID_LEN);
+ break;
+ case DB_HASHMAGIC:
+ if (type != DB_HASH && type != DB_UNKNOWN)
+ goto einval;
+
+ hashm = (HASHHDR *)mbuf;
+ if (swapped && (ret = __ham_mswap((PAGE *)hashm)) != 0)
+ goto err;
+
+ if (hashm->version < DB_HASHOLDVER ||
+ hashm->version > DB_HASHVERSION) {
+ __db_err(dbenv,
+ "%s: unsupported hash version number %lu",
+ fname, hashm->version);
+ goto einval;
+ }
+ dbp->pgsize = hashm->pagesize;
+ F_CLR(dbp, DB_AM_PGDEF);
+
+ if ((ret = __db_fchk(dbenv,
+ "db_open", hashm->flags, DB_HASH_DUP)) != 0)
+ goto err;
+ DBINFO_FCHK(dbp, "DB_DUP",
+ hashm->flags, DB_HASH_DUP, DB_AM_DUP);
+ type = DB_HASH;
+
+ /* Copy the file's unique id. */
+ need_fileid = 0;
+ memcpy(dbp->fileid, hashm->uid, DB_FILE_ID_LEN);
+ break;
+ default:
+ if (swapped) {
+ __db_err(dbenv, "unrecognized file type");
+ goto einval;
+ }
+ M_32_SWAP(((BTMETA *)mbuf)->magic);
+ F_SET(dbp, DB_AM_SWAP);
+
+ swapped = 1;
+ goto retry;
+ }
+ } else {
+ fname = real_name = NULL;
+
+ if (type == DB_UNKNOWN) {
+ __db_err(dbenv,
+ "DBTYPE of unknown without existing file");
+ goto einval;
+ }
+ F_SET(dbp, DB_AM_INMEM);
+ }
+
+empty: /*
+ * By the time we get here we've either set the type or we're taking
+ * it from the user.
+ */
+ dbp->type = type;
+
+ /*
+ * Set the page size to the best value for I/O to this file. Don't
+ * overflow the page offset type. The page size must be db_indx_t
+ * aligned and >= MIN_PAGE_SIZE.
+ *
+ * XXX
+ * Should we be checking for a page size that's not a multiple of 512?
+ */
+ if (dbp->pgsize == 0) {
+ F_SET(dbp, DB_AM_PGDEF);
+ dbp->pgsize = 8 * 1024;
+ }
+ if (dbp->pgsize < DB_MIN_PGSIZE ||
+ dbp->pgsize > DB_MAX_PGSIZE ||
+ dbp->pgsize & (sizeof(db_indx_t) - 1)) {
+ __db_err(dbenv, "illegal page size");
+ goto einval;
+ }
+
+ /*
+ * If no mpool supplied by the application, attach to a local,
+ * created buffer pool.
+ *
+ * XXX
+ * If the user has a DB_ENV structure, we have to use a temporary
+ * one so that we don't step on their values. If the user doesn't,
+ * we have to create one, and keep it around until the call to the
+ * memp_close() function. This is all so the mpool functions get
+ * the error stuff right.
+ */
+ if (dbenv == NULL || dbenv->mp_info == NULL) {
+ F_SET(dbp, DB_AM_MLOCAL);
+
+ if (dbenv == NULL) {
+ if ((ret = __os_calloc(1,
+ sizeof(DB_ENV), &dbp->mp_dbenv)) != 0)
+ goto err;
+
+ envp = dbp->mp_dbenv;
+ restore = 0;
+ } else {
+ t_dbenv = *dbenv;
+
+ envp = dbenv;
+ restore = 1;
+ }
+
+ /*
+ * Set and/or correct the cache size; must be a multiple of
+ * the page size.
+ */
+ if (dbinfo == NULL || dbinfo->db_cachesize == 0)
+ cachesize = dbp->pgsize * DB_MINCACHE;
+ else {
+ cachesize = dbinfo->db_cachesize;
+ if (cachesize & (dbp->pgsize - 1))
+ cachesize +=
+ (~cachesize & (dbp->pgsize - 1)) + 1;
+ if (cachesize < dbp->pgsize * DB_MINCACHE)
+ cachesize = dbp->pgsize * DB_MINCACHE;
+ if (cachesize < 20 * 1024)
+ cachesize = 20 * 1024;
+ }
+ envp->mp_size = cachesize;
+
+ if ((ret = memp_open(NULL, DB_CREATE | DB_MPOOL_PRIVATE |
+ (F_ISSET(dbp, DB_AM_THREAD) ? DB_THREAD : 0),
+ __db_omode("rw----"), envp, &dbp->mp)) != 0)
+ goto err;
+ if (restore)
+ *dbenv = t_dbenv;
+ } else
+ dbp->mp = dbenv->mp_info;
+
+ /* Register DB's pgin/pgout functions. */
+ if ((ret = memp_register(dbp->mp,
+ DB_FTYPE_BTREE, __bam_pgin, __bam_pgout)) != 0)
+ goto err;
+ if ((ret = memp_register(dbp->mp,
+ DB_FTYPE_HASH, __ham_pgin, __ham_pgout)) != 0)
+ goto err;
+
+ /*
+ * If we don't already have one, get a unique file ID. If the file
+ * is a temporary file, then we have to create a unique file ID --
+ * no backing file will be created until the mpool cache is filled
+ * forcing it to go to disk. The created ID must never match any
+ * potential real file ID -- we know it won't because real file IDs
+ * contain a time stamp after the dev/ino pair, and we're simply
+ * storing a 4-byte locker ID.
+ *
+ * XXX
+ * Store the file id in the locker structure -- we can get it from
+ * there as necessary, and it saves having two copies.
+ */
+ if (need_fileid)
+ if (fname == NULL) {
+ memset(dbp->fileid, 0, DB_FILE_ID_LEN);
+ if (F_ISSET(dbp, DB_AM_LOCKING) &&
+ (ret = lock_id(dbenv->lk_info,
+ (u_int32_t *)dbp->fileid)) != 0)
+ goto err;
+ } else
+ if ((ret = __os_fileid(dbenv,
+ real_name, 1, dbp->fileid)) != 0)
+ goto err;
+
+ /* No further use for the real name. */
+ if (real_name != NULL)
+ __os_freestr(real_name);
+ real_name = NULL;
+
+ /*
+ * Open a backing file in the memory pool.
+ *
+ * If we need to process the file's pages on I/O, set the file type.
+ * If it's a hash file, always call pgin and pgout routines. This
+ * means that hash files can never be mapped into process memory. If
+ * it's a btree file and requires swapping, we need to page the file
+ * in and out. This has to be right -- we can't mmap files that are
+ * being paged in and out.
+ */
+ if (type == DB_HASH)
+ ftype = DB_FTYPE_HASH;
+ else
+ ftype = F_ISSET(dbp, DB_AM_SWAP) ? DB_FTYPE_BTREE : 0;
+ pginfo.db_pagesize = dbp->pgsize;
+ pginfo.needswap = F_ISSET(dbp, DB_AM_SWAP);
+ pgcookie.data = &pginfo;
+ pgcookie.size = sizeof(DB_PGINFO);
+
+ /*
+ * Set up additional memp_fopen information.
+ */
+ memset(&finfo, 0, sizeof(finfo));
+ finfo.ftype = ftype;
+ finfo.pgcookie = &pgcookie;
+ finfo.fileid = dbp->fileid;
+ finfo.lsn_offset = 0;
+ finfo.clear_len = DB_PAGE_CLEAR_LEN;
+ if ((ret = memp_fopen(dbp->mp, fname,
+ F_ISSET(dbp, DB_AM_RDONLY) ? DB_RDONLY : 0,
+ 0, dbp->pgsize, &finfo, &dbp->mpf)) != 0)
+ goto err;
+
+ /*
+ * XXX
+ * We need a per-thread mutex that lives in shared memory -- HP-UX
+ * can't allocate mutexes in malloc'd memory. Allocate it from the
+ * shared memory region, since it's the only one that is guaranteed
+ * to exist.
+ */
+ if (F_ISSET(dbp, DB_AM_THREAD)) {
+ if ((ret = __memp_reg_alloc(dbp->mp,
+ sizeof(db_mutex_t), NULL, &dbp->mutexp)) != 0)
+ goto err;
+ /*
+ * Since we only get here if DB_THREAD was specified, we know
+ * we have spinlocks and no file offset argument is needed.
+ */
+ (void)__db_mutex_init(dbp->mutexp, 0);
+ }
+
+ /* Get a log file id. */
+ if (F_ISSET(dbp, DB_AM_LOGGING) &&
+ (ret = log_register(dbenv->lg_info,
+ dbp, fname, type, &dbp->log_fileid)) != 0)
+ goto err;
+
+ /* Call the real open function. */
+ switch (type) {
+ case DB_BTREE:
+ if (dbinfo != NULL && (ret = __db_fchk(dbenv,
+ "db_open", dbinfo->flags, DB_RECNUM | DB_DUP)) != 0)
+ goto err;
+ if (dbinfo != NULL && (ret = __db_fcchk(dbenv,
+ "db_open", dbinfo->flags, DB_DUP, DB_RECNUM)) != 0)
+ goto err;
+ if ((ret = __bam_open(dbp, dbinfo)) != 0)
+ goto err;
+ break;
+ case DB_HASH:
+ if (dbinfo != NULL && (ret = __db_fchk(dbenv,
+ "db_open", dbinfo->flags, DB_DUP)) != 0)
+ goto err;
+ if ((ret = __ham_open(dbp, dbinfo)) != 0)
+ goto err;
+ break;
+ case DB_RECNO:
+#define DB_INFO_FLAGS \
+ (DB_DELIMITER | DB_FIXEDLEN | DB_PAD | DB_RENUMBER | DB_SNAPSHOT)
+ if (dbinfo != NULL && (ret = __db_fchk(dbenv,
+ "db_open", dbinfo->flags, DB_INFO_FLAGS)) != 0)
+ goto err;
+ if ((ret = __ram_open(dbp, dbinfo)) != 0)
+ goto err;
+ break;
+ default:
+ abort();
+ }
+
+ *dbpp = dbp;
+ return (0);
+
+einval: ret = EINVAL;
+err: /* Close the file descriptor. */
+ if (fd != -1)
+ (void)__os_close(fd);
+
+ /* Discard the log file id. */
+ if (dbp->log_fileid != 0)
+ (void)log_unregister(dbenv->lg_info, dbp->log_fileid);
+
+ /* Close the memory pool file. */
+ if (dbp->mpf != NULL)
+ (void)memp_fclose(dbp->mpf);
+
+ /* If the memory pool was local, close it. */
+ if (F_ISSET(dbp, DB_AM_MLOCAL) && dbp->mp != NULL)
+ (void)memp_close(dbp->mp);
+
+ /* If we allocated a DB_ENV, discard it. */
+ if (dbp->mp_dbenv != NULL)
+ __os_free(dbp->mp_dbenv, sizeof(DB_ENV));
+
+ if (real_name != NULL)
+ __os_freestr(real_name);
+ if (dbp != NULL)
+ __os_free(dbp, sizeof(DB));
+
+ return (ret);
+}
+
+/*
+ * __db_close --
+ * Close a DB tree.
+ *
+ * PUBLIC: int __db_close __P((DB *, u_int32_t));
+ */
+int
+__db_close(dbp, flags)
+ DB *dbp;
+ u_int32_t flags;
+{
+ DBC *dbc;
+ int ret, t_ret;
+
+ DB_PANIC_CHECK(dbp);
+
+ /* Validate arguments. */
+ if ((ret = __db_closechk(dbp, flags)) != 0)
+ return (ret);
+
+ /* Sync the underlying file. */
+ if (flags != DB_NOSYNC &&
+ (t_ret = dbp->sync(dbp, 0)) != 0 && ret == 0)
+ ret = t_ret;
+
+ /*
+ * Go through the active cursors and call the cursor recycle routine,
+ * which resolves pending operations and moves the cursors onto the
+ * free list. Then, walk the free list and call the cursor destroy
+ * routine.
+ */
+ while ((dbc = TAILQ_FIRST(&dbp->active_queue)) != NULL)
+ if ((t_ret = dbc->c_close(dbc)) != 0 && ret == 0)
+ ret = t_ret;
+ while ((dbc = TAILQ_FIRST(&dbp->free_queue)) != NULL)
+ if ((t_ret = __db_c_destroy(dbc)) != 0 && ret == 0)
+ ret = t_ret;
+
+ /* Call the access specific close function. */
+ if ((t_ret = dbp->am_close(dbp)) != 0 && ret == 0)
+ ret = t_ret;
+
+ /* Sync the memory pool. */
+ if (flags != DB_NOSYNC && (t_ret = memp_fsync(dbp->mpf)) != 0 &&
+ t_ret != DB_INCOMPLETE && ret == 0)
+ ret = t_ret;
+
+ /* Close the memory pool file. */
+ if ((t_ret = memp_fclose(dbp->mpf)) != 0 && ret == 0)
+ ret = t_ret;
+
+ /* If the memory pool was local, close it. */
+ if (F_ISSET(dbp, DB_AM_MLOCAL) &&
+ (t_ret = memp_close(dbp->mp)) != 0 && ret == 0)
+ ret = t_ret;
+
+ if (dbp->saved_open_fd != -1) {
+ (void)__os_close(dbp->saved_open_fd);
+ dbp->saved_open_fd = -1;
+ }
+
+ /* Discard the log file id. */
+ if (F_ISSET(dbp, DB_AM_LOGGING))
+ (void)log_unregister(dbp->dbenv->lg_info, dbp->log_fileid);
+
+ /* If we allocated a DB_ENV, discard it. */
+ if (dbp->mp_dbenv != NULL)
+ __os_free(dbp->mp_dbenv, sizeof(DB_ENV));
+
+ /* Free the DB. */
+ __os_free(dbp, sizeof(*dbp));
+
+ return (ret);
+}
diff --git a/usr/src/cmd/sendmail/db/db/db_am.c b/usr/src/cmd/sendmail/db/db/db_am.c
new file mode 100644
index 0000000000..71b17b2b99
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/db/db_am.c
@@ -0,0 +1,432 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1998
+ * Sleepycat Software. All rights reserved.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)db_am.c 10.15 (Sleepycat) 12/30/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#endif
+
+#include "db_int.h"
+#include "shqueue.h"
+#include "db_page.h"
+#include "db_shash.h"
+#include "mp.h"
+#include "btree.h"
+#include "hash.h"
+#include "db_am.h"
+#include "db_ext.h"
+
+static int __db_c_close __P((DBC *));
+static int __db_cursor __P((DB *, DB_TXN *, DBC **, u_int32_t));
+static int __db_fd __P((DB *, int *));
+static int __db_get __P((DB *, DB_TXN *, DBT *, DBT *, u_int32_t));
+static int __db_put __P((DB *, DB_TXN *, DBT *, DBT *, u_int32_t));
+
+/*
+ * __db_init_wrapper --
+ * Wrapper layer to implement generic DB functions.
+ *
+ * PUBLIC: int __db_init_wrapper __P((DB *));
+ */
+int
+__db_init_wrapper(dbp)
+ DB *dbp;
+{
+ dbp->close = __db_close;
+ dbp->cursor = __db_cursor;
+ dbp->del = NULL; /* !!! Must be set by access method. */
+ dbp->fd = __db_fd;
+ dbp->get = __db_get;
+ dbp->join = __db_join;
+ dbp->put = __db_put;
+ dbp->stat = NULL; /* !!! Must be set by access method. */
+ dbp->sync = __db_sync;
+
+ return (0);
+}
+
+/*
+ * __db_cursor --
+ * Allocate and return a cursor.
+ */
+static int
+__db_cursor(dbp, txn, dbcp, flags)
+ DB *dbp;
+ DB_TXN *txn;
+ DBC **dbcp;
+ u_int32_t flags;
+{
+ DBC *dbc, *adbc;
+ int ret;
+ db_lockmode_t mode;
+ u_int32_t op;
+
+ DB_PANIC_CHECK(dbp);
+
+ /* Take one from the free list if it's available. */
+ DB_THREAD_LOCK(dbp);
+ if ((dbc = TAILQ_FIRST(&dbp->free_queue)) != NULL)
+ TAILQ_REMOVE(&dbp->free_queue, dbc, links);
+ else {
+ DB_THREAD_UNLOCK(dbp);
+
+ if ((ret = __os_calloc(1, sizeof(DBC), &dbc)) != 0)
+ return (ret);
+
+ dbc->dbp = dbp;
+ dbc->c_close = __db_c_close;
+
+ /* Set up locking information. */
+ if (F_ISSET(dbp, DB_AM_LOCKING | DB_AM_CDB)) {
+ /*
+ * If we are not threaded, then there is no need to
+ * create new locker ids. We know that no one else
+ * is running concurrently using this DB, so we can
+ * take a peek at any cursors on the active queue.
+ */
+ if (!F_ISSET(dbp, DB_AM_THREAD) &&
+ (adbc = TAILQ_FIRST(&dbp->active_queue)) != NULL)
+ dbc->lid = adbc->lid;
+ else
+ if ((ret = lock_id(dbp->dbenv->lk_info,
+ &dbc->lid)) != 0)
+ goto err;
+
+ memcpy(dbc->lock.fileid, dbp->fileid, DB_FILE_ID_LEN);
+ if (F_ISSET(dbp, DB_AM_CDB)) {
+ dbc->lock_dbt.size = DB_FILE_ID_LEN;
+ dbc->lock_dbt.data = dbc->lock.fileid;
+ } else {
+ dbc->lock_dbt.size = sizeof(dbc->lock);
+ dbc->lock_dbt.data = &dbc->lock;
+ }
+ }
+
+ switch (dbp->type) {
+ case DB_BTREE:
+ case DB_RECNO:
+ if ((ret = __bam_c_init(dbc)) != 0)
+ goto err;
+ break;
+ case DB_HASH:
+ if ((ret = __ham_c_init(dbc)) != 0)
+ goto err;
+ break;
+ default:
+ ret = EINVAL;
+ goto err;
+ }
+
+ DB_THREAD_LOCK(dbp);
+ }
+
+ if ((dbc->txn = txn) == NULL)
+ dbc->locker = dbc->lid;
+ else
+ dbc->locker = txn->txnid;
+
+ TAILQ_INSERT_TAIL(&dbp->active_queue, dbc, links);
+ DB_THREAD_UNLOCK(dbp);
+
+ /*
+ * If this is the concurrent DB product, then we do all locking
+ * in the interface, which is right here.
+ */
+ if (F_ISSET(dbp, DB_AM_CDB)) {
+ op = LF_ISSET(DB_OPFLAGS_MASK);
+ mode = (op == DB_WRITELOCK) ? DB_LOCK_WRITE :
+ (LF_ISSET(DB_RMW) ? DB_LOCK_IWRITE : DB_LOCK_READ);
+ if ((ret = lock_get(dbp->dbenv->lk_info, dbc->locker, 0,
+ &dbc->lock_dbt, mode, &dbc->mylock)) != 0) {
+ (void)__db_c_close(dbc);
+ return (EAGAIN);
+ }
+ if (LF_ISSET(DB_RMW))
+ F_SET(dbc, DBC_RMW);
+ if (op == DB_WRITELOCK)
+ F_SET(dbc, DBC_WRITER);
+ }
+
+ *dbcp = dbc;
+ return (0);
+
+err: __os_free(dbc, sizeof(*dbc));
+ return (ret);
+}
+
+/*
+ * __db_c_close --
+ * Close the cursor (recycle for later use).
+ */
+static int
+__db_c_close(dbc)
+ DBC *dbc;
+{
+ DB *dbp;
+ int ret, t_ret;
+
+ dbp = dbc->dbp;
+
+ DB_PANIC_CHECK(dbp);
+
+ ret = 0;
+
+ /*
+ * We cannot release the lock until after we've called the
+ * access method specific routine, since btrees may have pending
+ * deletes.
+ */
+
+ /* Remove the cursor from the active queue. */
+ DB_THREAD_LOCK(dbp);
+ TAILQ_REMOVE(&dbp->active_queue, dbc, links);
+ DB_THREAD_UNLOCK(dbp);
+
+ /* Call the access specific cursor close routine. */
+ if ((t_ret = dbc->c_am_close(dbc)) != 0 && ret == 0)
+ t_ret = ret;
+
+ /* Release the lock. */
+ if (F_ISSET(dbc->dbp, DB_AM_CDB) && dbc->mylock != LOCK_INVALID) {
+ ret = lock_put(dbc->dbp->dbenv->lk_info, dbc->mylock);
+ dbc->mylock = LOCK_INVALID;
+ }
+
+ /* Clean up the cursor. */
+ dbc->flags = 0;
+
+#ifdef DEBUG
+ /*
+ * Check for leftover locks, unless we're running with transactions.
+ *
+ * If we're running tests, display any locks currently held. It's
+ * possible that some applications may hold locks for long periods,
+ * e.g., conference room locks, but the DB tests should never close
+ * holding locks.
+ */
+ if (F_ISSET(dbp, DB_AM_LOCKING) && dbc->lid == dbc->locker) {
+ DB_LOCKREQ request;
+
+ request.op = DB_LOCK_DUMP;
+ if ((t_ret = lock_vec(dbp->dbenv->lk_info,
+ dbc->locker, 0, &request, 1, NULL)) != 0 && ret == 0)
+ ret = EAGAIN;
+ }
+#endif
+ /* Move the cursor to the free queue. */
+ DB_THREAD_LOCK(dbp);
+ TAILQ_INSERT_TAIL(&dbp->free_queue, dbc, links);
+ DB_THREAD_UNLOCK(dbp);
+
+ return (ret);
+}
+
+#ifdef DEBUG
+/*
+ * __db_cprint --
+ * Display the current cursor list.
+ *
+ * PUBLIC: int __db_cprint __P((DB *));
+ */
+int
+__db_cprint(dbp)
+ DB *dbp;
+{
+ static const FN fn[] = {
+ { DBC_RECOVER, "recover" },
+ { DBC_RMW, "read-modify-write" },
+ { 0 },
+ };
+ DBC *dbc;
+
+ DB_THREAD_LOCK(dbp);
+ for (dbc = TAILQ_FIRST(&dbp->active_queue);
+ dbc != NULL; dbc = TAILQ_NEXT(dbc, links)) {
+ fprintf(stderr,
+ "%#0x: dbp: %#0x txn: %#0x lid: %lu locker: %lu",
+ (u_int)dbc, (u_int)dbc->dbp, (u_int)dbc->txn,
+ (u_long)dbc->lid, (u_long)dbc->locker);
+ __db_prflags(dbc->flags, fn, stderr);
+ fprintf(stderr, "\n");
+ }
+ DB_THREAD_UNLOCK(dbp);
+
+ return (0);
+}
+#endif /* DEBUG */
+
+/*
+ * __db_c_destroy --
+ * Destroy the cursor.
+ *
+ * PUBLIC: int __db_c_destroy __P((DBC *));
+ */
+int
+__db_c_destroy(dbc)
+ DBC *dbc;
+{
+ DB *dbp;
+ int ret;
+
+ dbp = dbc->dbp;
+
+ /* Remove the cursor from the free queue. */
+ DB_THREAD_LOCK(dbp);
+ TAILQ_REMOVE(&dbp->free_queue, dbc, links);
+ DB_THREAD_UNLOCK(dbp);
+
+ /* Call the access specific cursor destroy routine. */
+ ret = dbc->c_am_destroy == NULL ? 0 : dbc->c_am_destroy(dbc);
+
+ /* Free up allocated memory. */
+ if (dbc->rkey.data != NULL)
+ __os_free(dbc->rkey.data, dbc->rkey.ulen);
+ if (dbc->rdata.data != NULL)
+ __os_free(dbc->rdata.data, dbc->rdata.ulen);
+ __os_free(dbc, sizeof(*dbc));
+
+ return (0);
+}
+
+/*
+ * db_fd --
+ * Return a file descriptor for flock'ing.
+ */
+static int
+__db_fd(dbp, fdp)
+ DB *dbp;
+ int *fdp;
+{
+ DB_PANIC_CHECK(dbp);
+
+ /*
+ * XXX
+ * Truly spectacular layering violation.
+ */
+ return (__mp_xxx_fd(dbp->mpf, fdp));
+}
+
+/*
+ * __db_get --
+ * Return a key/data pair.
+ */
+static int
+__db_get(dbp, txn, key, data, flags)
+ DB *dbp;
+ DB_TXN *txn;
+ DBT *key, *data;
+ u_int32_t flags;
+{
+ DBC *dbc;
+ int ret, t_ret;
+
+ DB_PANIC_CHECK(dbp);
+
+ if ((ret = __db_getchk(dbp, key, data, flags)) != 0)
+ return (ret);
+
+ if ((ret = dbp->cursor(dbp, txn, &dbc, 0)) != 0)
+ return (ret);
+
+ DEBUG_LREAD(dbc, txn, "__db_get", key, NULL, flags);
+
+ ret = dbc->c_get(dbc, key, data,
+ flags == 0 || flags == DB_RMW ? flags | DB_SET : flags);
+
+ if ((t_ret = __db_c_close(dbc)) != 0 && ret == 0)
+ ret = t_ret;
+
+ return (ret);
+}
+
+/*
+ * __db_put --
+ * Store a key/data pair.
+ */
+static int
+__db_put(dbp, txn, key, data, flags)
+ DB *dbp;
+ DB_TXN *txn;
+ DBT *key, *data;
+ u_int32_t flags;
+{
+ DBC *dbc;
+ DBT tdata;
+ int ret, t_ret;
+
+ DB_PANIC_CHECK(dbp);
+
+ if ((ret = __db_putchk(dbp, key, data,
+ flags, F_ISSET(dbp, DB_AM_RDONLY), F_ISSET(dbp, DB_AM_DUP))) != 0)
+ return (ret);
+
+ if ((ret = dbp->cursor(dbp, txn, &dbc, DB_WRITELOCK)) != 0)
+ return (ret);
+
+ DEBUG_LWRITE(dbc, txn, "__db_put", key, data, flags);
+
+ if (flags == DB_NOOVERWRITE) {
+ /*
+ * Set DB_DBT_USERMEM, this might be a threaded application and
+ * the flags checking will catch us. We don't want the actual
+ * data, so request a partial of length 0.
+ */
+ memset(&tdata, 0, sizeof(tdata));
+ F_SET(&tdata, DB_DBT_USERMEM | DB_DBT_PARTIAL);
+ if ((ret = dbc->c_get(dbc, key, &tdata, DB_SET | DB_RMW)) == 0)
+ ret = DB_KEYEXIST;
+ else if (ret == DB_NOTFOUND)
+ ret = 0;
+ }
+ if (ret == 0)
+ ret = dbc->c_put(dbc, key, data, DB_KEYLAST);
+
+ if ((t_ret = __db_c_close(dbc)) != 0 && ret == 0)
+ ret = t_ret;
+
+ return (ret);
+}
+
+/*
+ * __db_sync --
+ * Flush the database cache.
+ *
+ * PUBLIC: int __db_sync __P((DB *, u_int32_t));
+ */
+int
+__db_sync(dbp, flags)
+ DB *dbp;
+ u_int32_t flags;
+{
+ int ret;
+
+ DB_PANIC_CHECK(dbp);
+
+ if ((ret = __db_syncchk(dbp, flags)) != 0)
+ return (ret);
+
+ /* If it wasn't possible to modify the file, we're done. */
+ if (F_ISSET(dbp, DB_AM_INMEM | DB_AM_RDONLY))
+ return (0);
+
+ /* Flush any dirty pages from the cache to the backing file. */
+ if ((ret = memp_fsync(dbp->mpf)) == DB_INCOMPLETE)
+ ret = 0;
+
+ return (ret);
+}
diff --git a/usr/src/cmd/sendmail/db/db/db_appinit.c b/usr/src/cmd/sendmail/db/db/db_appinit.c
new file mode 100644
index 0000000000..e02b1a872d
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/db/db_appinit.c
@@ -0,0 +1,734 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)db_appinit.c 10.66 (Sleepycat) 12/7/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#endif
+
+#include "db_int.h"
+#include "shqueue.h"
+#include "db_page.h"
+#include "btree.h"
+#include "hash.h"
+#include "log.h"
+#include "txn.h"
+#include "clib_ext.h"
+#include "common_ext.h"
+
+static int __db_home __P((DB_ENV *, const char *, u_int32_t));
+static int __db_parse __P((DB_ENV *, char *));
+static int __db_tmp_open __P((DB_ENV *, u_int32_t, char *, int *));
+
+/*
+ * This conflict array is used for concurrent db access (cdb). It
+ * uses the same locks as the db_rw_conflict array, but adds an IW
+ * mode to be used for write cursors.
+ */
+static u_int8_t const db_cdb_conflicts[] = {
+ /* N R W IW */
+ /* N */ 0, 0, 0, 0,
+ /* R */ 0, 0, 1, 0,
+ /* W */ 0, 1, 1, 1,
+ /* IW */ 0, 0, 1, 1
+};
+
+/*
+ * db_version --
+ * Return version information.
+ */
+char *
+db_version(majverp, minverp, patchp)
+ int *majverp, *minverp, *patchp;
+{
+ if (majverp != NULL)
+ *majverp = DB_VERSION_MAJOR;
+ if (minverp != NULL)
+ *minverp = DB_VERSION_MINOR;
+ if (patchp != NULL)
+ *patchp = DB_VERSION_PATCH;
+ return ((char *)DB_VERSION_STRING);
+}
+
+/*
+ * db_appinit --
+ * Initialize the application environment.
+ */
+int
+db_appinit(db_home, db_config, dbenv, flags)
+ const char *db_home;
+ char * const *db_config;
+ DB_ENV *dbenv;
+ u_int32_t flags;
+{
+ FILE *fp;
+ int mode, ret;
+ char * const *p;
+ char *lp, buf[MAXPATHLEN * 2];
+
+ fp = NULL;
+
+ /* Validate arguments. */
+ if (dbenv == NULL)
+ return (EINVAL);
+
+#ifdef HAVE_SPINLOCKS
+#define OKFLAGS \
+ (DB_CREATE | DB_INIT_CDB | DB_INIT_LOCK | DB_INIT_LOG | \
+ DB_INIT_MPOOL | DB_INIT_TXN | DB_MPOOL_PRIVATE | DB_NOMMAP | \
+ DB_RECOVER | DB_RECOVER_FATAL | DB_THREAD | DB_TXN_NOSYNC | \
+ DB_USE_ENVIRON | DB_USE_ENVIRON_ROOT)
+#else
+#define OKFLAGS \
+ (DB_CREATE | DB_INIT_CDB | DB_INIT_LOCK | DB_INIT_LOG | \
+ DB_INIT_MPOOL | DB_INIT_TXN | DB_MPOOL_PRIVATE | DB_NOMMAP | \
+ DB_RECOVER | DB_RECOVER_FATAL | DB_TXN_NOSYNC | \
+ DB_USE_ENVIRON | DB_USE_ENVIRON_ROOT)
+#endif
+ if ((ret = __db_fchk(dbenv, "db_appinit", flags, OKFLAGS)) != 0)
+ return (ret);
+
+ /* Transactions imply logging. */
+ if (LF_ISSET(DB_INIT_TXN))
+ LF_SET(DB_INIT_LOG);
+
+ /* Convert the db_appinit(3) flags. */
+ if (LF_ISSET(DB_THREAD))
+ F_SET(dbenv, DB_ENV_THREAD);
+
+ /* Set the database home. */
+ if ((ret = __db_home(dbenv, db_home, flags)) != 0)
+ goto err;
+
+ /* Parse the config array. */
+ for (p = db_config; p != NULL && *p != NULL; ++p)
+ if ((ret = __db_parse(dbenv, *p)) != 0)
+ goto err;
+
+ /*
+ * Parse the config file.
+ *
+ * XXX
+ * Don't use sprintf(3)/snprintf(3) -- the former is dangerous, and
+ * the latter isn't standard, and we're manipulating strings handed
+ * us by the application.
+ */
+ if (dbenv->db_home != NULL) {
+#define CONFIG_NAME "/DB_CONFIG"
+ if (strlen(dbenv->db_home) +
+ strlen(CONFIG_NAME) + 1 > sizeof(buf)) {
+ ret = ENAMETOOLONG;
+ goto err;
+ }
+ (void)strcpy(buf, dbenv->db_home);
+ (void)strcat(buf, CONFIG_NAME);
+ if ((fp = fopen(buf, "r")) != NULL) {
+ while (fgets(buf, sizeof(buf), fp) != NULL) {
+ if ((lp = strchr(buf, '\n')) == NULL) {
+ __db_err(dbenv,
+ "%s: line too long", CONFIG_NAME);
+ ret = EINVAL;
+ goto err;
+ }
+ *lp = '\0';
+ if (buf[0] == '\0' ||
+ buf[0] == '#' || isspace(buf[0]))
+ continue;
+
+ if ((ret = __db_parse(dbenv, buf)) != 0)
+ goto err;
+ }
+ (void)fclose(fp);
+ fp = NULL;
+ }
+ }
+
+ /* Set up the tmp directory path. */
+ if (dbenv->db_tmp_dir == NULL && (ret = __os_tmpdir(dbenv, flags)) != 0)
+ goto err;
+
+ /*
+ * Flag that the structure has been initialized by the application.
+ * Note, this must be set before calling into the subsystems as it
+ * is used when we're doing file naming.
+ */
+ F_SET(dbenv, DB_ENV_APPINIT);
+
+ /*
+ * If we are doing recovery, remove all the old shared memory
+ * regions.
+ */
+ if (LF_ISSET(DB_RECOVER | DB_RECOVER_FATAL)) {
+ if ((ret = log_unlink(NULL, 1, dbenv)) != 0)
+ goto err;
+ if ((ret = memp_unlink(NULL, 1, dbenv)) != 0)
+ goto err;
+ if ((ret = lock_unlink(NULL, 1, dbenv)) != 0)
+ goto err;
+ if ((ret = txn_unlink(NULL, 1, dbenv)) != 0)
+ goto err;
+ }
+
+ /*
+ * Create the new shared regions.
+ *
+ * Default permissions are read-write for both owner and group.
+ */
+ mode = __db_omode("rwrw--");
+ if (LF_ISSET(DB_INIT_CDB)) {
+ if (LF_ISSET(DB_INIT_LOCK | DB_INIT_LOG | DB_INIT_TXN)) {
+ ret = EINVAL;
+ goto err;
+ }
+ F_SET(dbenv, DB_ENV_CDB);
+ dbenv->lk_conflicts = db_cdb_conflicts;
+ dbenv->lk_modes = DB_LOCK_RW_N + 1;
+ if ((ret = lock_open(NULL, LF_ISSET(DB_CREATE | DB_THREAD),
+ mode, dbenv, &dbenv->lk_info)) != 0)
+ goto err;
+ }
+ if (LF_ISSET(DB_INIT_LOCK) && (ret = lock_open(NULL,
+ LF_ISSET(DB_CREATE | DB_THREAD),
+ mode, dbenv, &dbenv->lk_info)) != 0)
+ goto err;
+ if (LF_ISSET(DB_INIT_LOG) && (ret = log_open(NULL,
+ LF_ISSET(DB_CREATE | DB_THREAD),
+ mode, dbenv, &dbenv->lg_info)) != 0)
+ goto err;
+ if (LF_ISSET(DB_INIT_MPOOL) && (ret = memp_open(NULL,
+ LF_ISSET(DB_CREATE | DB_MPOOL_PRIVATE | DB_NOMMAP | DB_THREAD),
+ mode, dbenv, &dbenv->mp_info)) != 0)
+ goto err;
+ if (LF_ISSET(DB_INIT_TXN) && (ret = txn_open(NULL,
+ LF_ISSET(DB_CREATE | DB_THREAD | DB_TXN_NOSYNC),
+ mode, dbenv, &dbenv->tx_info)) != 0)
+ goto err;
+
+ /*
+ * If the application is running with transactions, initialize the
+ * function tables. Once that's done, do recovery for any previous
+ * run.
+ */
+ if (LF_ISSET(DB_INIT_TXN)) {
+ if ((ret = __bam_init_recover(dbenv)) != 0)
+ goto err;
+ if ((ret = __db_init_recover(dbenv)) != 0)
+ goto err;
+ if ((ret = __ham_init_recover(dbenv)) != 0)
+ goto err;
+ if ((ret = __log_init_recover(dbenv)) != 0)
+ goto err;
+ if ((ret = __txn_init_recover(dbenv)) != 0)
+ goto err;
+
+ if (LF_ISSET(DB_RECOVER | DB_RECOVER_FATAL) &&
+ (ret = __db_apprec(dbenv,
+ LF_ISSET(DB_RECOVER | DB_RECOVER_FATAL))) != 0)
+ goto err;
+ }
+
+ return (ret);
+
+err: if (fp != NULL)
+ (void)fclose(fp);
+
+ (void)db_appexit(dbenv);
+ return (ret);
+}
+
+/*
+ * db_appexit --
+ * Close down the default application environment.
+ */
+int
+db_appexit(dbenv)
+ DB_ENV *dbenv;
+{
+ int ret, t_ret;
+ char **p;
+
+ ret = 0;
+
+ /* Close subsystems. */
+ if (dbenv->tx_info && (t_ret = txn_close(dbenv->tx_info)) != 0)
+ if (ret == 0)
+ ret = t_ret;
+ if (dbenv->lg_info && (t_ret = log_close(dbenv->lg_info)) != 0)
+ if (ret == 0)
+ ret = t_ret;
+ if (dbenv->mp_info && (t_ret = memp_close(dbenv->mp_info)) != 0)
+ if (ret == 0)
+ ret = t_ret;
+ if (dbenv->lk_info && (t_ret = lock_close(dbenv->lk_info)) != 0)
+ if (ret == 0)
+ ret = t_ret;
+
+ /* Clear initialized flag (after subsystems, it affects naming). */
+ F_CLR(dbenv, DB_ENV_APPINIT);
+
+ /* Free allocated memory. */
+ if (dbenv->db_home != NULL)
+ __os_freestr(dbenv->db_home);
+ if ((p = dbenv->db_data_dir) != NULL) {
+ for (; *p != NULL; ++p)
+ __os_freestr(*p);
+ __os_free(dbenv->db_data_dir,
+ dbenv->data_cnt * sizeof(char **));
+ }
+ if (dbenv->db_log_dir != NULL)
+ __os_freestr(dbenv->db_log_dir);
+ if (dbenv->db_tmp_dir != NULL)
+ __os_freestr(dbenv->db_tmp_dir);
+
+ return (ret);
+}
+
+#define DB_ADDSTR(str) { \
+ if ((str) != NULL) { \
+ /* If leading slash, start over. */ \
+ if (__os_abspath(str)) { \
+ p = start; \
+ slash = 0; \
+ } \
+ /* Append to the current string. */ \
+ len = strlen(str); \
+ if (slash) \
+ *p++ = PATH_SEPARATOR[0]; \
+ memcpy(p, str, len); \
+ p += len; \
+ slash = strchr(PATH_SEPARATOR, p[-1]) == NULL; \
+ } \
+}
+
+/*
+ * __db_appname --
+ * Given an optional DB environment, directory and file name and type
+ * of call, build a path based on the db_appinit(3) rules, and return
+ * it in allocated space.
+ *
+ * PUBLIC: int __db_appname __P((DB_ENV *,
+ * PUBLIC: APPNAME, const char *, const char *, u_int32_t, int *, char **));
+ */
+int
+__db_appname(dbenv, appname, dir, file, tmp_oflags, fdp, namep)
+ DB_ENV *dbenv;
+ APPNAME appname;
+ const char *dir, *file;
+ u_int32_t tmp_oflags;
+ int *fdp;
+ char **namep;
+{
+ DB_ENV etmp;
+ size_t len;
+ int data_entry, ret, slash, tmp_create, tmp_free;
+ const char *a, *b, *c;
+ char *p, *start;
+
+ a = b = c = NULL;
+ data_entry = -1;
+ tmp_create = tmp_free = 0;
+
+ /*
+ * We don't return a name when creating temporary files, just an fd.
+ * Default to error now.
+ */
+ if (fdp != NULL)
+ *fdp = -1;
+ if (namep != NULL)
+ *namep = NULL;
+
+ /*
+ * Absolute path names are never modified. If the file is an absolute
+ * path, we're done. If the directory is, simply append the file and
+ * return.
+ */
+ if (file != NULL && __os_abspath(file))
+ return (__os_strdup(file, namep));
+ if (dir != NULL && __os_abspath(dir)) {
+ a = dir;
+ goto done;
+ }
+
+ /*
+ * DB_ENV DIR APPNAME RESULT
+ * -------------------------------------------
+ * null null none <tmp>/file
+ * null set none DIR/file
+ * set null none DB_HOME/file
+ * set set none DB_HOME/DIR/file
+ *
+ * DB_ENV FILE APPNAME RESULT
+ * -------------------------------------------
+ * null null DB_APP_DATA <tmp>/<create>
+ * null set DB_APP_DATA ./file
+ * set null DB_APP_DATA <tmp>/<create>
+ * set set DB_APP_DATA DB_HOME/DB_DATA_DIR/file
+ *
+ * DB_ENV DIR APPNAME RESULT
+ * -------------------------------------------
+ * null null DB_APP_LOG <tmp>/file
+ * null set DB_APP_LOG DIR/file
+ * set null DB_APP_LOG DB_HOME/DB_LOG_DIR/file
+ * set set DB_APP_LOG DB_HOME/DB_LOG_DIR/DIR/file
+ *
+ * DB_ENV APPNAME RESULT
+ * -------------------------------------------
+ * null DB_APP_TMP* <tmp>/<create>
+ * set DB_APP_TMP* DB_HOME/DB_TMP_DIR/<create>
+ */
+retry: switch (appname) {
+ case DB_APP_NONE:
+ if (dbenv == NULL || !F_ISSET(dbenv, DB_ENV_APPINIT)) {
+ if (dir == NULL)
+ goto tmp;
+ a = dir;
+ } else {
+ a = dbenv->db_home;
+ b = dir;
+ }
+ break;
+ case DB_APP_DATA:
+ if (dir != NULL) {
+ __db_err(dbenv,
+ "DB_APP_DATA: illegal directory specification");
+ return (EINVAL);
+ }
+
+ if (file == NULL) {
+ tmp_create = 1;
+ goto tmp;
+ }
+ if (dbenv == NULL || !F_ISSET(dbenv, DB_ENV_APPINIT))
+ a = PATH_DOT;
+ else {
+ a = dbenv->db_home;
+ if (dbenv->db_data_dir != NULL &&
+ (b = dbenv->db_data_dir[++data_entry]) == NULL) {
+ data_entry = -1;
+ b = dbenv->db_data_dir[0];
+ }
+ }
+ break;
+ case DB_APP_LOG:
+ if (dbenv == NULL || !F_ISSET(dbenv, DB_ENV_APPINIT)) {
+ if (dir == NULL)
+ goto tmp;
+ a = dir;
+ } else {
+ a = dbenv->db_home;
+ b = dbenv->db_log_dir;
+ c = dir;
+ }
+ break;
+ case DB_APP_TMP:
+ if (dir != NULL || file != NULL) {
+ __db_err(dbenv,
+ "DB_APP_TMP: illegal directory or file specification");
+ return (EINVAL);
+ }
+
+ tmp_create = 1;
+ if (dbenv == NULL || !F_ISSET(dbenv, DB_ENV_APPINIT))
+ goto tmp;
+ else {
+ a = dbenv->db_home;
+ b = dbenv->db_tmp_dir;
+ }
+ break;
+ }
+
+ /* Reference a file from the appropriate temporary directory. */
+ if (0) {
+tmp: if (dbenv == NULL || !F_ISSET(dbenv, DB_ENV_APPINIT)) {
+ memset(&etmp, 0, sizeof(etmp));
+ if ((ret = __os_tmpdir(&etmp, DB_USE_ENVIRON)) != 0)
+ return (ret);
+ tmp_free = 1;
+ a = etmp.db_tmp_dir;
+ } else
+ a = dbenv->db_tmp_dir;
+ }
+
+done: len =
+ (a == NULL ? 0 : strlen(a) + 1) +
+ (b == NULL ? 0 : strlen(b) + 1) +
+ (c == NULL ? 0 : strlen(c) + 1) +
+ (file == NULL ? 0 : strlen(file) + 1);
+
+ /*
+ * Allocate space to hold the current path information, as well as any
+ * temporary space that we're going to need to create a temporary file
+ * name.
+ */
+#define DB_TRAIL "XXXXXX"
+ if ((ret =
+ __os_malloc(len + sizeof(DB_TRAIL) + 10, NULL, &start)) != 0) {
+ if (tmp_free)
+ __os_freestr(etmp.db_tmp_dir);
+ return (ret);
+ }
+
+ slash = 0;
+ p = start;
+ DB_ADDSTR(a);
+ DB_ADDSTR(b);
+ DB_ADDSTR(file);
+ *p = '\0';
+
+ /* Discard any space allocated to find the temp directory. */
+ if (tmp_free) {
+ __os_freestr(etmp.db_tmp_dir);
+ tmp_free = 0;
+ }
+
+ /*
+ * If we're opening a data file, see if it exists. If it does,
+ * return it, otherwise, try and find another one to open.
+ */
+ if (data_entry != -1 && __os_exists(start, NULL) != 0) {
+ __os_freestr(start);
+ a = b = c = NULL;
+ goto retry;
+ }
+
+ /* Create the file if so requested. */
+ if (tmp_create &&
+ (ret = __db_tmp_open(dbenv, tmp_oflags, start, fdp)) != 0) {
+ __os_freestr(start);
+ return (ret);
+ }
+
+ if (namep == NULL)
+ __os_freestr(start);
+ else
+ *namep = start;
+ return (0);
+}
+
+/*
+ * __db_home --
+ * Find the database home.
+ */
+static int
+__db_home(dbenv, db_home, flags)
+ DB_ENV *dbenv;
+ const char *db_home;
+ u_int32_t flags;
+{
+ const char *p;
+
+ p = db_home;
+
+ /* Use the environment if it's permitted and initialized. */
+#ifdef HAVE_GETUID
+ if (LF_ISSET(DB_USE_ENVIRON) ||
+ (LF_ISSET(DB_USE_ENVIRON_ROOT) && getuid() == 0)) {
+#else
+ if (LF_ISSET(DB_USE_ENVIRON)) {
+#endif
+ if ((p = getenv("DB_HOME")) == NULL)
+ p = db_home;
+ else if (p[0] == '\0') {
+ __db_err(dbenv,
+ "illegal DB_HOME environment variable");
+ return (EINVAL);
+ }
+ }
+
+ if (p == NULL)
+ return (0);
+
+ return (__os_strdup(p, &dbenv->db_home));
+}
+
+/*
+ * __db_parse --
+ * Parse a single NAME VALUE pair.
+ */
+static int
+__db_parse(dbenv, s)
+ DB_ENV *dbenv;
+ char *s;
+{
+ int ret;
+ char *local_s, *name, *value, **p, *tp;
+
+ /*
+ * We need to strdup the argument in case the caller passed us
+ * static data.
+ */
+ if ((ret = __os_strdup(s, &local_s)) != 0)
+ return (ret);
+
+ /*
+ * Name/value pairs are parsed as two white-space separated strings.
+ * Leading and trailing white-space is trimmed from the value, but
+ * it may contain embedded white-space. Note: we use the isspace(3)
+ * macro because it's more portable, but that means that you can use
+ * characters like form-feed to separate the strings.
+ */
+ name = local_s;
+ for (tp = name; *tp != '\0' && !isspace(*tp); ++tp)
+ ;
+ if (*tp == '\0' || tp == name)
+ goto illegal;
+ *tp = '\0';
+ for (++tp; isspace(*tp); ++tp)
+ ;
+ if (*tp == '\0')
+ goto illegal;
+ value = tp;
+ for (++tp; *tp != '\0'; ++tp)
+ ;
+ for (--tp; isspace(*tp); --tp)
+ ;
+ if (tp == value) {
+illegal: ret = EINVAL;
+ __db_err(dbenv, "illegal name-value pair: %s", s);
+ goto err;
+ }
+ *++tp = '\0';
+
+#define DATA_INIT_CNT 20 /* Start with 20 data slots. */
+ if (!strcmp(name, "DB_DATA_DIR")) {
+ if (dbenv->db_data_dir == NULL) {
+ if ((ret = __os_calloc(DATA_INIT_CNT,
+ sizeof(char **), &dbenv->db_data_dir)) != 0)
+ goto err;
+ dbenv->data_cnt = DATA_INIT_CNT;
+ } else if (dbenv->data_next == dbenv->data_cnt - 1) {
+ dbenv->data_cnt *= 2;
+ if ((ret = __os_realloc(&dbenv->db_data_dir,
+ dbenv->data_cnt * sizeof(char **))) != 0)
+ goto err;
+ }
+ p = &dbenv->db_data_dir[dbenv->data_next++];
+ } else if (!strcmp(name, "DB_LOG_DIR")) {
+ if (dbenv->db_log_dir != NULL)
+ __os_freestr(dbenv->db_log_dir);
+ p = &dbenv->db_log_dir;
+ } else if (!strcmp(name, "DB_TMP_DIR")) {
+ if (dbenv->db_tmp_dir != NULL)
+ __os_freestr(dbenv->db_tmp_dir);
+ p = &dbenv->db_tmp_dir;
+ } else
+ goto err;
+
+ ret = __os_strdup(value, p);
+
+err: __os_freestr(local_s);
+ return (ret);
+}
+
+/*
+ * __db_tmp_open --
+ * Create a temporary file.
+ */
+static int
+__db_tmp_open(dbenv, flags, path, fdp)
+ DB_ENV *dbenv;
+ u_int32_t flags;
+ char *path;
+ int *fdp;
+{
+ u_long pid;
+ int mode, isdir, ret;
+ const char *p;
+ char *trv;
+
+ /*
+ * Check the target directory; if you have six X's and it doesn't
+ * exist, this runs for a *very* long time.
+ */
+ if ((ret = __os_exists(path, &isdir)) != 0) {
+ __db_err(dbenv, "%s: %s", path, strerror(ret));
+ return (ret);
+ }
+ if (!isdir) {
+ __db_err(dbenv, "%s: %s", path, strerror(EINVAL));
+ return (EINVAL);
+ }
+
+ /* Build the path. */
+ for (trv = path; *trv != '\0'; ++trv)
+ ;
+ *trv = PATH_SEPARATOR[0];
+ for (p = DB_TRAIL; (*++trv = *p) != '\0'; ++p)
+ ;
+
+ /*
+ * Replace the X's with the process ID. Pid should be a pid_t,
+ * but we use unsigned long for portability.
+ */
+ for (pid = getpid(); *--trv == 'X'; pid /= 10)
+ switch (pid % 10) {
+ case 0: *trv = '0'; break;
+ case 1: *trv = '1'; break;
+ case 2: *trv = '2'; break;
+ case 3: *trv = '3'; break;
+ case 4: *trv = '4'; break;
+ case 5: *trv = '5'; break;
+ case 6: *trv = '6'; break;
+ case 7: *trv = '7'; break;
+ case 8: *trv = '8'; break;
+ case 9: *trv = '9'; break;
+ }
+ ++trv;
+
+ /* Set up open flags and mode. */
+ LF_SET(DB_CREATE | DB_EXCL);
+ mode = __db_omode("rw----");
+
+ /* Loop, trying to open a file. */
+ for (;;) {
+ if ((ret = __db_open(path, flags, flags, mode, fdp)) == 0)
+ return (0);
+
+ /*
+ * XXX:
+ * If we don't get an EEXIST error, then there's something
+ * seriously wrong. Unfortunately, if the implementation
+ * doesn't return EEXIST for O_CREAT and O_EXCL regardless
+ * of other possible errors, we've lost.
+ */
+ if (ret != EEXIST) {
+ __db_err(dbenv,
+ "tmp_open: %s: %s", path, strerror(ret));
+ return (ret);
+ }
+
+ /*
+ * Tricky little algorithm for backward compatibility.
+ * Assumes the ASCII ordering of lower-case characters.
+ */
+ for (;;) {
+ if (*trv == '\0')
+ return (EINVAL);
+ if (*trv == 'z')
+ *trv++ = 'a';
+ else {
+ if (isdigit(*trv))
+ *trv = 'a';
+ else
+ ++*trv;
+ break;
+ }
+ }
+ }
+ /* NOTREACHED */
+}
diff --git a/usr/src/cmd/sendmail/db/db/db_apprec.c b/usr/src/cmd/sendmail/db/db/db_apprec.c
new file mode 100644
index 0000000000..7da4404d15
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/db/db_apprec.c
@@ -0,0 +1,245 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char copyright[] =
+"@(#) Copyright (c) 1996, 1997, 1998\n\
+ Sleepycat Software Inc. All rights reserved.\n";
+static const char sccsid[] = "@(#)db_apprec.c 10.33 (Sleepycat) 10/5/98";
+#endif
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <errno.h>
+#include <string.h>
+#include <time.h>
+#endif
+
+#include "db_int.h"
+#include "shqueue.h"
+#include "db_page.h"
+#include "db_dispatch.h"
+#include "db_am.h"
+#include "log.h"
+#include "txn.h"
+#include "common_ext.h"
+
+/*
+ * __db_apprec --
+ * Perform recovery.
+ *
+ * PUBLIC: int __db_apprec __P((DB_ENV *, u_int32_t));
+ */
+int
+__db_apprec(dbenv, flags)
+ DB_ENV *dbenv;
+ u_int32_t flags;
+{
+ DBT data;
+ DB_LOG *lp;
+ DB_LSN ckp_lsn, first_lsn, lsn, open_lsn;
+ __txn_ckp_args *ckp_args;
+ time_t now;
+ u_int32_t is_thread;
+ int ret;
+ void *txninfo;
+
+ lp = dbenv->lg_info;
+
+ /* Initialize the transaction list. */
+ if ((ret = __db_txnlist_init(&txninfo)) != 0)
+ return (ret);
+
+ /*
+ * Save the state of the thread flag -- we don't need it on at the
+ * moment because we're single-threaded until recovery is complete.
+ */
+ is_thread = F_ISSET(lp, DB_AM_THREAD);
+ F_CLR(lp, DB_AM_THREAD);
+
+ /*
+ * Recovery is done in three passes:
+ * Pass #0:
+ * We need to find the position from which we will open files
+ * We need to open files beginning with the last to next
+ * checkpoint because we might have crashed after writing the
+ * last checkpoint record, but before having written out all
+ * the open file information.
+ * Pass #1:
+ * Read forward through the log from the second to last checkpoint
+ * opening and closing files so that at the end of the log we have
+ * the "current" set of files open.
+ * Pass #2:
+ * Read backward through the log undoing any uncompleted TXNs.
+ * If doing catastrophic recovery, we read to the beginning of
+ * the log, otherwise, to the most recent checkpoint that occurs
+ * before the most recent checkpoint LSN, which is returned by
+ * __log_findckp(). During this pass, checkpoint file information
+ * is ignored, and file openings and closings are undone.
+ * Pass #3:
+ * Read forward through the log from the LSN found in pass #2,
+ * redoing any committed TXNs. During this pass, checkpoint
+ * file information is ignored, and file openings and closings
+ * are redone.
+ */
+
+ /*
+ * Find the second to last checkpoint in the log. This is the point
+ * from which we want to begin pass #1 (the TXN_OPENFILES pass).
+ */
+ memset(&data, 0, sizeof(data));
+ ckp_args = NULL;
+
+ if ((ret = log_get(lp, &ckp_lsn, &data, DB_CHECKPOINT)) != 0) {
+ /*
+ * If we don't find a checkpoint, start from the beginning.
+ * If that fails, we're done. Note, we do not require that
+ * there be log records if we're performing recovery.
+ */
+first: if ((ret = log_get(lp, &ckp_lsn, &data, DB_FIRST)) != 0) {
+ if (ret == DB_NOTFOUND)
+ ret = 0;
+ else
+ __db_err(dbenv, "First log record not found");
+ goto out;
+ }
+ open_lsn = ckp_lsn;
+ } else if ((ret = __txn_ckp_read(data.data, &ckp_args)) != 0) {
+ __db_err(dbenv, "Invalid checkpoint record at [%ld][%ld]\n",
+ (u_long)ckp_lsn.file, (u_long)ckp_lsn.offset);
+ goto out;
+ } else if (IS_ZERO_LSN(ckp_args->last_ckp) ||
+ (ret = log_get(lp, &ckp_args->last_ckp, &data, DB_SET)) != 0)
+ goto first;
+ else
+ open_lsn = ckp_args->last_ckp;
+
+ /*
+ * Now, ckp_lsn is either the lsn of the last checkpoint or the lsn
+ * of the first record in the log. Open_lsn is the second to last
+ * checkpoint or the beinning of the log; begin the TXN_OPENFILES
+ * pass from that lsn, and proceed to the end of the log.
+ */
+ lsn = open_lsn;
+ for (;;) {
+ if (dbenv->tx_recover != NULL)
+ ret = dbenv->tx_recover(lp,
+ &data, &lsn, TXN_OPENFILES, txninfo);
+ else
+ ret = __db_dispatch(lp,
+ &data, &lsn, TXN_OPENFILES, txninfo);
+ if (ret != 0 && ret != DB_TXN_CKP)
+ goto msgerr;
+ if ((ret = log_get(lp, &lsn, &data, DB_NEXT)) != 0) {
+ if (ret == DB_NOTFOUND)
+ break;
+ goto out;
+ }
+ }
+
+ /*
+ * Pass #2.
+ *
+ * Before we can begin pass #2, backward roll phase, we determine how
+ * far back in the log to recover. If we are doing catastrophic
+ * recovery, then we go as far back as we have files. If we are
+ * doing normal recovery, we go as back to the most recent checkpoint
+ * that occurs before the most recent checkpoint LSN.
+ */
+ if (LF_ISSET(DB_RECOVER_FATAL)) {
+ ZERO_LSN(first_lsn);
+ } else
+ if ((ret = __log_findckp(lp, &first_lsn)) == DB_NOTFOUND) {
+ /*
+ * We don't require that log files exist if recovery
+ * was specified.
+ */
+ ret = 0;
+ goto out;
+ }
+
+ if (dbenv->db_verbose)
+ __db_err(lp->dbenv, "Recovery starting from [%lu][%lu]",
+ (u_long)first_lsn.file, (u_long)first_lsn.offset);
+
+ for (ret = log_get(lp, &lsn, &data, DB_LAST);
+ ret == 0 && log_compare(&lsn, &first_lsn) > 0;
+ ret = log_get(lp, &lsn, &data, DB_PREV)) {
+ if (dbenv->tx_recover != NULL)
+ ret = dbenv->tx_recover(lp,
+ &data, &lsn, TXN_BACKWARD_ROLL, txninfo);
+ else
+ ret = __db_dispatch(lp,
+ &data, &lsn, TXN_BACKWARD_ROLL, txninfo);
+ if (ret != 0)
+ if (ret != DB_TXN_CKP)
+ goto msgerr;
+ else
+ ret = 0;
+ }
+ if (ret != 0 && ret != DB_NOTFOUND)
+ goto out;
+
+ /*
+ * Pass #3.
+ */
+ for (ret = log_get(lp, &lsn, &data, DB_NEXT);
+ ret == 0; ret = log_get(lp, &lsn, &data, DB_NEXT)) {
+ if (dbenv->tx_recover != NULL)
+ ret = dbenv->tx_recover(lp,
+ &data, &lsn, TXN_FORWARD_ROLL, txninfo);
+ else
+ ret = __db_dispatch(lp,
+ &data, &lsn, TXN_FORWARD_ROLL, txninfo);
+ if (ret != 0)
+ if (ret != DB_TXN_CKP)
+ goto msgerr;
+ else
+ ret = 0;
+ }
+ if (ret != DB_NOTFOUND)
+ goto out;
+
+ /* Now close all the db files that are open. */
+ __log_close_files(lp);
+
+ /*
+ * Now set the last checkpoint lsn and the current time,
+ * take a checkpoint, and reset the txnid.
+ */
+ (void)time(&now);
+ dbenv->tx_info->region->last_ckp = ckp_lsn;
+ dbenv->tx_info->region->time_ckp = (u_int32_t)now;
+ if ((ret = txn_checkpoint(dbenv->tx_info, 0, 0)) != 0)
+ goto out;
+ dbenv->tx_info->region->last_txnid = TXN_MINIMUM;
+
+ if (dbenv->db_verbose) {
+ __db_err(lp->dbenv, "Recovery complete at %.24s", ctime(&now));
+ __db_err(lp->dbenv, "%s %lx %s [%lu][%lu]",
+ "Maximum transaction id",
+ ((DB_TXNHEAD *)txninfo)->maxid,
+ "Recovery checkpoint",
+ (u_long)dbenv->tx_info->region->last_ckp.file,
+ (u_long)dbenv->tx_info->region->last_ckp.offset);
+ }
+
+ if (0) {
+msgerr: __db_err(dbenv, "Recovery function for LSN %lu %lu failed",
+ (u_long)lsn.file, (u_long)lsn.offset);
+ }
+
+out: F_SET(lp, is_thread);
+ __db_txnlist_end(txninfo);
+ if (ckp_args != NULL)
+ __os_free(ckp_args, sizeof(*ckp_args));
+
+ return (ret);
+}
diff --git a/usr/src/cmd/sendmail/db/db/db_auto.c b/usr/src/cmd/sendmail/db/db/db_auto.c
new file mode 100644
index 0000000000..e3dba23c8b
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/db/db_auto.c
@@ -0,0 +1,1357 @@
+/* Do not edit: automatically built by dist/db_gen.sh. */
+#include "config.h"
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <ctype.h>
+#include <errno.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#endif
+
+#include "db_int.h"
+#include "db_page.h"
+#include "db_dispatch.h"
+#include "db_am.h"
+/*
+ * PUBLIC: int __db_addrem_log
+ * PUBLIC: __P((DB_LOG *, DB_TXN *, DB_LSN *, u_int32_t,
+ * PUBLIC: u_int32_t, u_int32_t, db_pgno_t, u_int32_t,
+ * PUBLIC: size_t, const DBT *, const DBT *, DB_LSN *));
+ */
+int __db_addrem_log(logp, txnid, ret_lsnp, flags,
+ opcode, fileid, pgno, indx, nbytes, hdr,
+ dbt, pagelsn)
+ DB_LOG *logp;
+ DB_TXN *txnid;
+ DB_LSN *ret_lsnp;
+ u_int32_t flags;
+ u_int32_t opcode;
+ u_int32_t fileid;
+ db_pgno_t pgno;
+ u_int32_t indx;
+ size_t nbytes;
+ const DBT *hdr;
+ const DBT *dbt;
+ DB_LSN * pagelsn;
+{
+ DBT logrec;
+ DB_LSN *lsnp, null_lsn;
+ u_int32_t zero;
+ u_int32_t rectype, txn_num;
+ int ret;
+ u_int8_t *bp;
+
+ rectype = DB_db_addrem;
+ txn_num = txnid == NULL ? 0 : txnid->txnid;
+ if (txnid == NULL) {
+ ZERO_LSN(null_lsn);
+ lsnp = &null_lsn;
+ } else
+ lsnp = &txnid->last_lsn;
+ logrec.size = sizeof(rectype) + sizeof(txn_num) + sizeof(DB_LSN)
+ + sizeof(opcode)
+ + sizeof(fileid)
+ + sizeof(pgno)
+ + sizeof(indx)
+ + sizeof(nbytes)
+ + sizeof(u_int32_t) + (hdr == NULL ? 0 : hdr->size)
+ + sizeof(u_int32_t) + (dbt == NULL ? 0 : dbt->size)
+ + sizeof(*pagelsn);
+ if ((ret = __os_malloc(logrec.size, NULL, &logrec.data)) != 0)
+ return (ret);
+
+ bp = logrec.data;
+ memcpy(bp, &rectype, sizeof(rectype));
+ bp += sizeof(rectype);
+ memcpy(bp, &txn_num, sizeof(txn_num));
+ bp += sizeof(txn_num);
+ memcpy(bp, lsnp, sizeof(DB_LSN));
+ bp += sizeof(DB_LSN);
+ memcpy(bp, &opcode, sizeof(opcode));
+ bp += sizeof(opcode);
+ memcpy(bp, &fileid, sizeof(fileid));
+ bp += sizeof(fileid);
+ memcpy(bp, &pgno, sizeof(pgno));
+ bp += sizeof(pgno);
+ memcpy(bp, &indx, sizeof(indx));
+ bp += sizeof(indx);
+ memcpy(bp, &nbytes, sizeof(nbytes));
+ bp += sizeof(nbytes);
+ if (hdr == NULL) {
+ zero = 0;
+ memcpy(bp, &zero, sizeof(u_int32_t));
+ bp += sizeof(u_int32_t);
+ } else {
+ memcpy(bp, &hdr->size, sizeof(hdr->size));
+ bp += sizeof(hdr->size);
+ memcpy(bp, hdr->data, hdr->size);
+ bp += hdr->size;
+ }
+ if (dbt == NULL) {
+ zero = 0;
+ memcpy(bp, &zero, sizeof(u_int32_t));
+ bp += sizeof(u_int32_t);
+ } else {
+ memcpy(bp, &dbt->size, sizeof(dbt->size));
+ bp += sizeof(dbt->size);
+ memcpy(bp, dbt->data, dbt->size);
+ bp += dbt->size;
+ }
+ if (pagelsn != NULL)
+ memcpy(bp, pagelsn, sizeof(*pagelsn));
+ else
+ memset(bp, 0, sizeof(*pagelsn));
+ bp += sizeof(*pagelsn);
+#ifdef DIAGNOSTIC
+ if ((u_int32_t)(bp - (u_int8_t *)logrec.data) != logrec.size)
+ fprintf(stderr, "Error in log record length");
+#endif
+ ret = log_put(logp, ret_lsnp, (DBT *)&logrec, flags);
+ if (txnid != NULL)
+ txnid->last_lsn = *ret_lsnp;
+ __os_free(logrec.data, 0);
+ return (ret);
+}
+
+/*
+ * PUBLIC: int __db_addrem_print
+ * PUBLIC: __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+ */
+int
+__db_addrem_print(notused1, dbtp, lsnp, notused2, notused3)
+ DB_LOG *notused1;
+ DBT *dbtp;
+ DB_LSN *lsnp;
+ int notused2;
+ void *notused3;
+{
+ __db_addrem_args *argp;
+ u_int32_t i;
+ u_int ch;
+ int ret;
+
+ i = 0;
+ ch = 0;
+ notused1 = NULL;
+ notused2 = 0;
+ notused3 = NULL;
+
+ if ((ret = __db_addrem_read(dbtp->data, &argp)) != 0)
+ return (ret);
+ printf("[%lu][%lu]db_addrem: rec: %lu txnid %lx prevlsn [%lu][%lu]\n",
+ (u_long)lsnp->file,
+ (u_long)lsnp->offset,
+ (u_long)argp->type,
+ (u_long)argp->txnid->txnid,
+ (u_long)argp->prev_lsn.file,
+ (u_long)argp->prev_lsn.offset);
+ printf("\topcode: %lu\n", (u_long)argp->opcode);
+ printf("\tfileid: %lu\n", (u_long)argp->fileid);
+ printf("\tpgno: %lu\n", (u_long)argp->pgno);
+ printf("\tindx: %lu\n", (u_long)argp->indx);
+ printf("\tnbytes: %lu\n", (u_long)argp->nbytes);
+ printf("\thdr: ");
+ for (i = 0; i < argp->hdr.size; i++) {
+ ch = ((u_int8_t *)argp->hdr.data)[i];
+ if (isprint(ch) || ch == 0xa)
+ putchar(ch);
+ else
+ printf("%#x ", ch);
+ }
+ printf("\n");
+ printf("\tdbt: ");
+ for (i = 0; i < argp->dbt.size; i++) {
+ ch = ((u_int8_t *)argp->dbt.data)[i];
+ if (isprint(ch) || ch == 0xa)
+ putchar(ch);
+ else
+ printf("%#x ", ch);
+ }
+ printf("\n");
+ printf("\tpagelsn: [%lu][%lu]\n",
+ (u_long)argp->pagelsn.file, (u_long)argp->pagelsn.offset);
+ printf("\n");
+ __os_free(argp, 0);
+ return (0);
+}
+
+/*
+ * PUBLIC: int __db_addrem_read __P((void *, __db_addrem_args **));
+ */
+int
+__db_addrem_read(recbuf, argpp)
+ void *recbuf;
+ __db_addrem_args **argpp;
+{
+ __db_addrem_args *argp;
+ u_int8_t *bp;
+ int ret;
+
+ ret = __os_malloc(sizeof(__db_addrem_args) +
+ sizeof(DB_TXN), NULL, &argp);
+ if (ret != 0)
+ return (ret);
+ argp->txnid = (DB_TXN *)&argp[1];
+ bp = recbuf;
+ memcpy(&argp->type, bp, sizeof(argp->type));
+ bp += sizeof(argp->type);
+ memcpy(&argp->txnid->txnid, bp, sizeof(argp->txnid->txnid));
+ bp += sizeof(argp->txnid->txnid);
+ memcpy(&argp->prev_lsn, bp, sizeof(DB_LSN));
+ bp += sizeof(DB_LSN);
+ memcpy(&argp->opcode, bp, sizeof(argp->opcode));
+ bp += sizeof(argp->opcode);
+ memcpy(&argp->fileid, bp, sizeof(argp->fileid));
+ bp += sizeof(argp->fileid);
+ memcpy(&argp->pgno, bp, sizeof(argp->pgno));
+ bp += sizeof(argp->pgno);
+ memcpy(&argp->indx, bp, sizeof(argp->indx));
+ bp += sizeof(argp->indx);
+ memcpy(&argp->nbytes, bp, sizeof(argp->nbytes));
+ bp += sizeof(argp->nbytes);
+ memcpy(&argp->hdr.size, bp, sizeof(u_int32_t));
+ bp += sizeof(u_int32_t);
+ argp->hdr.data = bp;
+ bp += argp->hdr.size;
+ memcpy(&argp->dbt.size, bp, sizeof(u_int32_t));
+ bp += sizeof(u_int32_t);
+ argp->dbt.data = bp;
+ bp += argp->dbt.size;
+ memcpy(&argp->pagelsn, bp, sizeof(argp->pagelsn));
+ bp += sizeof(argp->pagelsn);
+ *argpp = argp;
+ return (0);
+}
+
+/*
+ * PUBLIC: int __db_split_log
+ * PUBLIC: __P((DB_LOG *, DB_TXN *, DB_LSN *, u_int32_t,
+ * PUBLIC: u_int32_t, u_int32_t, db_pgno_t, const DBT *,
+ * PUBLIC: DB_LSN *));
+ */
+int __db_split_log(logp, txnid, ret_lsnp, flags,
+ opcode, fileid, pgno, pageimage, pagelsn)
+ DB_LOG *logp;
+ DB_TXN *txnid;
+ DB_LSN *ret_lsnp;
+ u_int32_t flags;
+ u_int32_t opcode;
+ u_int32_t fileid;
+ db_pgno_t pgno;
+ const DBT *pageimage;
+ DB_LSN * pagelsn;
+{
+ DBT logrec;
+ DB_LSN *lsnp, null_lsn;
+ u_int32_t zero;
+ u_int32_t rectype, txn_num;
+ int ret;
+ u_int8_t *bp;
+
+ rectype = DB_db_split;
+ txn_num = txnid == NULL ? 0 : txnid->txnid;
+ if (txnid == NULL) {
+ ZERO_LSN(null_lsn);
+ lsnp = &null_lsn;
+ } else
+ lsnp = &txnid->last_lsn;
+ logrec.size = sizeof(rectype) + sizeof(txn_num) + sizeof(DB_LSN)
+ + sizeof(opcode)
+ + sizeof(fileid)
+ + sizeof(pgno)
+ + sizeof(u_int32_t) + (pageimage == NULL ? 0 : pageimage->size)
+ + sizeof(*pagelsn);
+ if ((ret = __os_malloc(logrec.size, NULL, &logrec.data)) != 0)
+ return (ret);
+
+ bp = logrec.data;
+ memcpy(bp, &rectype, sizeof(rectype));
+ bp += sizeof(rectype);
+ memcpy(bp, &txn_num, sizeof(txn_num));
+ bp += sizeof(txn_num);
+ memcpy(bp, lsnp, sizeof(DB_LSN));
+ bp += sizeof(DB_LSN);
+ memcpy(bp, &opcode, sizeof(opcode));
+ bp += sizeof(opcode);
+ memcpy(bp, &fileid, sizeof(fileid));
+ bp += sizeof(fileid);
+ memcpy(bp, &pgno, sizeof(pgno));
+ bp += sizeof(pgno);
+ if (pageimage == NULL) {
+ zero = 0;
+ memcpy(bp, &zero, sizeof(u_int32_t));
+ bp += sizeof(u_int32_t);
+ } else {
+ memcpy(bp, &pageimage->size, sizeof(pageimage->size));
+ bp += sizeof(pageimage->size);
+ memcpy(bp, pageimage->data, pageimage->size);
+ bp += pageimage->size;
+ }
+ if (pagelsn != NULL)
+ memcpy(bp, pagelsn, sizeof(*pagelsn));
+ else
+ memset(bp, 0, sizeof(*pagelsn));
+ bp += sizeof(*pagelsn);
+#ifdef DIAGNOSTIC
+ if ((u_int32_t)(bp - (u_int8_t *)logrec.data) != logrec.size)
+ fprintf(stderr, "Error in log record length");
+#endif
+ ret = log_put(logp, ret_lsnp, (DBT *)&logrec, flags);
+ if (txnid != NULL)
+ txnid->last_lsn = *ret_lsnp;
+ __os_free(logrec.data, 0);
+ return (ret);
+}
+
+/*
+ * PUBLIC: int __db_split_print
+ * PUBLIC: __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+ */
+int
+__db_split_print(notused1, dbtp, lsnp, notused2, notused3)
+ DB_LOG *notused1;
+ DBT *dbtp;
+ DB_LSN *lsnp;
+ int notused2;
+ void *notused3;
+{
+ __db_split_args *argp;
+ u_int32_t i;
+ u_int ch;
+ int ret;
+
+ i = 0;
+ ch = 0;
+ notused1 = NULL;
+ notused2 = 0;
+ notused3 = NULL;
+
+ if ((ret = __db_split_read(dbtp->data, &argp)) != 0)
+ return (ret);
+ printf("[%lu][%lu]db_split: rec: %lu txnid %lx prevlsn [%lu][%lu]\n",
+ (u_long)lsnp->file,
+ (u_long)lsnp->offset,
+ (u_long)argp->type,
+ (u_long)argp->txnid->txnid,
+ (u_long)argp->prev_lsn.file,
+ (u_long)argp->prev_lsn.offset);
+ printf("\topcode: %lu\n", (u_long)argp->opcode);
+ printf("\tfileid: %lu\n", (u_long)argp->fileid);
+ printf("\tpgno: %lu\n", (u_long)argp->pgno);
+ printf("\tpageimage: ");
+ for (i = 0; i < argp->pageimage.size; i++) {
+ ch = ((u_int8_t *)argp->pageimage.data)[i];
+ if (isprint(ch) || ch == 0xa)
+ putchar(ch);
+ else
+ printf("%#x ", ch);
+ }
+ printf("\n");
+ printf("\tpagelsn: [%lu][%lu]\n",
+ (u_long)argp->pagelsn.file, (u_long)argp->pagelsn.offset);
+ printf("\n");
+ __os_free(argp, 0);
+ return (0);
+}
+
+/*
+ * PUBLIC: int __db_split_read __P((void *, __db_split_args **));
+ */
+int
+__db_split_read(recbuf, argpp)
+ void *recbuf;
+ __db_split_args **argpp;
+{
+ __db_split_args *argp;
+ u_int8_t *bp;
+ int ret;
+
+ ret = __os_malloc(sizeof(__db_split_args) +
+ sizeof(DB_TXN), NULL, &argp);
+ if (ret != 0)
+ return (ret);
+ argp->txnid = (DB_TXN *)&argp[1];
+ bp = recbuf;
+ memcpy(&argp->type, bp, sizeof(argp->type));
+ bp += sizeof(argp->type);
+ memcpy(&argp->txnid->txnid, bp, sizeof(argp->txnid->txnid));
+ bp += sizeof(argp->txnid->txnid);
+ memcpy(&argp->prev_lsn, bp, sizeof(DB_LSN));
+ bp += sizeof(DB_LSN);
+ memcpy(&argp->opcode, bp, sizeof(argp->opcode));
+ bp += sizeof(argp->opcode);
+ memcpy(&argp->fileid, bp, sizeof(argp->fileid));
+ bp += sizeof(argp->fileid);
+ memcpy(&argp->pgno, bp, sizeof(argp->pgno));
+ bp += sizeof(argp->pgno);
+ memcpy(&argp->pageimage.size, bp, sizeof(u_int32_t));
+ bp += sizeof(u_int32_t);
+ argp->pageimage.data = bp;
+ bp += argp->pageimage.size;
+ memcpy(&argp->pagelsn, bp, sizeof(argp->pagelsn));
+ bp += sizeof(argp->pagelsn);
+ *argpp = argp;
+ return (0);
+}
+
+/*
+ * PUBLIC: int __db_big_log
+ * PUBLIC: __P((DB_LOG *, DB_TXN *, DB_LSN *, u_int32_t,
+ * PUBLIC: u_int32_t, u_int32_t, db_pgno_t, db_pgno_t,
+ * PUBLIC: db_pgno_t, const DBT *, DB_LSN *, DB_LSN *,
+ * PUBLIC: DB_LSN *));
+ */
+int __db_big_log(logp, txnid, ret_lsnp, flags,
+ opcode, fileid, pgno, prev_pgno, next_pgno, dbt,
+ pagelsn, prevlsn, nextlsn)
+ DB_LOG *logp;
+ DB_TXN *txnid;
+ DB_LSN *ret_lsnp;
+ u_int32_t flags;
+ u_int32_t opcode;
+ u_int32_t fileid;
+ db_pgno_t pgno;
+ db_pgno_t prev_pgno;
+ db_pgno_t next_pgno;
+ const DBT *dbt;
+ DB_LSN * pagelsn;
+ DB_LSN * prevlsn;
+ DB_LSN * nextlsn;
+{
+ DBT logrec;
+ DB_LSN *lsnp, null_lsn;
+ u_int32_t zero;
+ u_int32_t rectype, txn_num;
+ int ret;
+ u_int8_t *bp;
+
+ rectype = DB_db_big;
+ txn_num = txnid == NULL ? 0 : txnid->txnid;
+ if (txnid == NULL) {
+ ZERO_LSN(null_lsn);
+ lsnp = &null_lsn;
+ } else
+ lsnp = &txnid->last_lsn;
+ logrec.size = sizeof(rectype) + sizeof(txn_num) + sizeof(DB_LSN)
+ + sizeof(opcode)
+ + sizeof(fileid)
+ + sizeof(pgno)
+ + sizeof(prev_pgno)
+ + sizeof(next_pgno)
+ + sizeof(u_int32_t) + (dbt == NULL ? 0 : dbt->size)
+ + sizeof(*pagelsn)
+ + sizeof(*prevlsn)
+ + sizeof(*nextlsn);
+ if ((ret = __os_malloc(logrec.size, NULL, &logrec.data)) != 0)
+ return (ret);
+
+ bp = logrec.data;
+ memcpy(bp, &rectype, sizeof(rectype));
+ bp += sizeof(rectype);
+ memcpy(bp, &txn_num, sizeof(txn_num));
+ bp += sizeof(txn_num);
+ memcpy(bp, lsnp, sizeof(DB_LSN));
+ bp += sizeof(DB_LSN);
+ memcpy(bp, &opcode, sizeof(opcode));
+ bp += sizeof(opcode);
+ memcpy(bp, &fileid, sizeof(fileid));
+ bp += sizeof(fileid);
+ memcpy(bp, &pgno, sizeof(pgno));
+ bp += sizeof(pgno);
+ memcpy(bp, &prev_pgno, sizeof(prev_pgno));
+ bp += sizeof(prev_pgno);
+ memcpy(bp, &next_pgno, sizeof(next_pgno));
+ bp += sizeof(next_pgno);
+ if (dbt == NULL) {
+ zero = 0;
+ memcpy(bp, &zero, sizeof(u_int32_t));
+ bp += sizeof(u_int32_t);
+ } else {
+ memcpy(bp, &dbt->size, sizeof(dbt->size));
+ bp += sizeof(dbt->size);
+ memcpy(bp, dbt->data, dbt->size);
+ bp += dbt->size;
+ }
+ if (pagelsn != NULL)
+ memcpy(bp, pagelsn, sizeof(*pagelsn));
+ else
+ memset(bp, 0, sizeof(*pagelsn));
+ bp += sizeof(*pagelsn);
+ if (prevlsn != NULL)
+ memcpy(bp, prevlsn, sizeof(*prevlsn));
+ else
+ memset(bp, 0, sizeof(*prevlsn));
+ bp += sizeof(*prevlsn);
+ if (nextlsn != NULL)
+ memcpy(bp, nextlsn, sizeof(*nextlsn));
+ else
+ memset(bp, 0, sizeof(*nextlsn));
+ bp += sizeof(*nextlsn);
+#ifdef DIAGNOSTIC
+ if ((u_int32_t)(bp - (u_int8_t *)logrec.data) != logrec.size)
+ fprintf(stderr, "Error in log record length");
+#endif
+ ret = log_put(logp, ret_lsnp, (DBT *)&logrec, flags);
+ if (txnid != NULL)
+ txnid->last_lsn = *ret_lsnp;
+ __os_free(logrec.data, 0);
+ return (ret);
+}
+
+/*
+ * PUBLIC: int __db_big_print
+ * PUBLIC: __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+ */
+int
+__db_big_print(notused1, dbtp, lsnp, notused2, notused3)
+ DB_LOG *notused1;
+ DBT *dbtp;
+ DB_LSN *lsnp;
+ int notused2;
+ void *notused3;
+{
+ __db_big_args *argp;
+ u_int32_t i;
+ u_int ch;
+ int ret;
+
+ i = 0;
+ ch = 0;
+ notused1 = NULL;
+ notused2 = 0;
+ notused3 = NULL;
+
+ if ((ret = __db_big_read(dbtp->data, &argp)) != 0)
+ return (ret);
+ printf("[%lu][%lu]db_big: rec: %lu txnid %lx prevlsn [%lu][%lu]\n",
+ (u_long)lsnp->file,
+ (u_long)lsnp->offset,
+ (u_long)argp->type,
+ (u_long)argp->txnid->txnid,
+ (u_long)argp->prev_lsn.file,
+ (u_long)argp->prev_lsn.offset);
+ printf("\topcode: %lu\n", (u_long)argp->opcode);
+ printf("\tfileid: %lu\n", (u_long)argp->fileid);
+ printf("\tpgno: %lu\n", (u_long)argp->pgno);
+ printf("\tprev_pgno: %lu\n", (u_long)argp->prev_pgno);
+ printf("\tnext_pgno: %lu\n", (u_long)argp->next_pgno);
+ printf("\tdbt: ");
+ for (i = 0; i < argp->dbt.size; i++) {
+ ch = ((u_int8_t *)argp->dbt.data)[i];
+ if (isprint(ch) || ch == 0xa)
+ putchar(ch);
+ else
+ printf("%#x ", ch);
+ }
+ printf("\n");
+ printf("\tpagelsn: [%lu][%lu]\n",
+ (u_long)argp->pagelsn.file, (u_long)argp->pagelsn.offset);
+ printf("\tprevlsn: [%lu][%lu]\n",
+ (u_long)argp->prevlsn.file, (u_long)argp->prevlsn.offset);
+ printf("\tnextlsn: [%lu][%lu]\n",
+ (u_long)argp->nextlsn.file, (u_long)argp->nextlsn.offset);
+ printf("\n");
+ __os_free(argp, 0);
+ return (0);
+}
+
+/*
+ * PUBLIC: int __db_big_read __P((void *, __db_big_args **));
+ */
+int
+__db_big_read(recbuf, argpp)
+ void *recbuf;
+ __db_big_args **argpp;
+{
+ __db_big_args *argp;
+ u_int8_t *bp;
+ int ret;
+
+ ret = __os_malloc(sizeof(__db_big_args) +
+ sizeof(DB_TXN), NULL, &argp);
+ if (ret != 0)
+ return (ret);
+ argp->txnid = (DB_TXN *)&argp[1];
+ bp = recbuf;
+ memcpy(&argp->type, bp, sizeof(argp->type));
+ bp += sizeof(argp->type);
+ memcpy(&argp->txnid->txnid, bp, sizeof(argp->txnid->txnid));
+ bp += sizeof(argp->txnid->txnid);
+ memcpy(&argp->prev_lsn, bp, sizeof(DB_LSN));
+ bp += sizeof(DB_LSN);
+ memcpy(&argp->opcode, bp, sizeof(argp->opcode));
+ bp += sizeof(argp->opcode);
+ memcpy(&argp->fileid, bp, sizeof(argp->fileid));
+ bp += sizeof(argp->fileid);
+ memcpy(&argp->pgno, bp, sizeof(argp->pgno));
+ bp += sizeof(argp->pgno);
+ memcpy(&argp->prev_pgno, bp, sizeof(argp->prev_pgno));
+ bp += sizeof(argp->prev_pgno);
+ memcpy(&argp->next_pgno, bp, sizeof(argp->next_pgno));
+ bp += sizeof(argp->next_pgno);
+ memcpy(&argp->dbt.size, bp, sizeof(u_int32_t));
+ bp += sizeof(u_int32_t);
+ argp->dbt.data = bp;
+ bp += argp->dbt.size;
+ memcpy(&argp->pagelsn, bp, sizeof(argp->pagelsn));
+ bp += sizeof(argp->pagelsn);
+ memcpy(&argp->prevlsn, bp, sizeof(argp->prevlsn));
+ bp += sizeof(argp->prevlsn);
+ memcpy(&argp->nextlsn, bp, sizeof(argp->nextlsn));
+ bp += sizeof(argp->nextlsn);
+ *argpp = argp;
+ return (0);
+}
+
+/*
+ * PUBLIC: int __db_ovref_log
+ * PUBLIC: __P((DB_LOG *, DB_TXN *, DB_LSN *, u_int32_t,
+ * PUBLIC: u_int32_t, db_pgno_t, int32_t, DB_LSN *));
+ */
+int __db_ovref_log(logp, txnid, ret_lsnp, flags,
+ fileid, pgno, adjust, lsn)
+ DB_LOG *logp;
+ DB_TXN *txnid;
+ DB_LSN *ret_lsnp;
+ u_int32_t flags;
+ u_int32_t fileid;
+ db_pgno_t pgno;
+ int32_t adjust;
+ DB_LSN * lsn;
+{
+ DBT logrec;
+ DB_LSN *lsnp, null_lsn;
+ u_int32_t rectype, txn_num;
+ int ret;
+ u_int8_t *bp;
+
+ rectype = DB_db_ovref;
+ txn_num = txnid == NULL ? 0 : txnid->txnid;
+ if (txnid == NULL) {
+ ZERO_LSN(null_lsn);
+ lsnp = &null_lsn;
+ } else
+ lsnp = &txnid->last_lsn;
+ logrec.size = sizeof(rectype) + sizeof(txn_num) + sizeof(DB_LSN)
+ + sizeof(fileid)
+ + sizeof(pgno)
+ + sizeof(adjust)
+ + sizeof(*lsn);
+ if ((ret = __os_malloc(logrec.size, NULL, &logrec.data)) != 0)
+ return (ret);
+
+ bp = logrec.data;
+ memcpy(bp, &rectype, sizeof(rectype));
+ bp += sizeof(rectype);
+ memcpy(bp, &txn_num, sizeof(txn_num));
+ bp += sizeof(txn_num);
+ memcpy(bp, lsnp, sizeof(DB_LSN));
+ bp += sizeof(DB_LSN);
+ memcpy(bp, &fileid, sizeof(fileid));
+ bp += sizeof(fileid);
+ memcpy(bp, &pgno, sizeof(pgno));
+ bp += sizeof(pgno);
+ memcpy(bp, &adjust, sizeof(adjust));
+ bp += sizeof(adjust);
+ if (lsn != NULL)
+ memcpy(bp, lsn, sizeof(*lsn));
+ else
+ memset(bp, 0, sizeof(*lsn));
+ bp += sizeof(*lsn);
+#ifdef DIAGNOSTIC
+ if ((u_int32_t)(bp - (u_int8_t *)logrec.data) != logrec.size)
+ fprintf(stderr, "Error in log record length");
+#endif
+ ret = log_put(logp, ret_lsnp, (DBT *)&logrec, flags);
+ if (txnid != NULL)
+ txnid->last_lsn = *ret_lsnp;
+ __os_free(logrec.data, 0);
+ return (ret);
+}
+
+/*
+ * PUBLIC: int __db_ovref_print
+ * PUBLIC: __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+ */
+int
+__db_ovref_print(notused1, dbtp, lsnp, notused2, notused3)
+ DB_LOG *notused1;
+ DBT *dbtp;
+ DB_LSN *lsnp;
+ int notused2;
+ void *notused3;
+{
+ __db_ovref_args *argp;
+ u_int32_t i;
+ u_int ch;
+ int ret;
+
+ i = 0;
+ ch = 0;
+ notused1 = NULL;
+ notused2 = 0;
+ notused3 = NULL;
+
+ if ((ret = __db_ovref_read(dbtp->data, &argp)) != 0)
+ return (ret);
+ printf("[%lu][%lu]db_ovref: rec: %lu txnid %lx prevlsn [%lu][%lu]\n",
+ (u_long)lsnp->file,
+ (u_long)lsnp->offset,
+ (u_long)argp->type,
+ (u_long)argp->txnid->txnid,
+ (u_long)argp->prev_lsn.file,
+ (u_long)argp->prev_lsn.offset);
+ printf("\tfileid: %lu\n", (u_long)argp->fileid);
+ printf("\tpgno: %lu\n", (u_long)argp->pgno);
+ printf("\tadjust: %ld\n", (long)argp->adjust);
+ printf("\tlsn: [%lu][%lu]\n",
+ (u_long)argp->lsn.file, (u_long)argp->lsn.offset);
+ printf("\n");
+ __os_free(argp, 0);
+ return (0);
+}
+
+/*
+ * PUBLIC: int __db_ovref_read __P((void *, __db_ovref_args **));
+ */
+int
+__db_ovref_read(recbuf, argpp)
+ void *recbuf;
+ __db_ovref_args **argpp;
+{
+ __db_ovref_args *argp;
+ u_int8_t *bp;
+ int ret;
+
+ ret = __os_malloc(sizeof(__db_ovref_args) +
+ sizeof(DB_TXN), NULL, &argp);
+ if (ret != 0)
+ return (ret);
+ argp->txnid = (DB_TXN *)&argp[1];
+ bp = recbuf;
+ memcpy(&argp->type, bp, sizeof(argp->type));
+ bp += sizeof(argp->type);
+ memcpy(&argp->txnid->txnid, bp, sizeof(argp->txnid->txnid));
+ bp += sizeof(argp->txnid->txnid);
+ memcpy(&argp->prev_lsn, bp, sizeof(DB_LSN));
+ bp += sizeof(DB_LSN);
+ memcpy(&argp->fileid, bp, sizeof(argp->fileid));
+ bp += sizeof(argp->fileid);
+ memcpy(&argp->pgno, bp, sizeof(argp->pgno));
+ bp += sizeof(argp->pgno);
+ memcpy(&argp->adjust, bp, sizeof(argp->adjust));
+ bp += sizeof(argp->adjust);
+ memcpy(&argp->lsn, bp, sizeof(argp->lsn));
+ bp += sizeof(argp->lsn);
+ *argpp = argp;
+ return (0);
+}
+
+/*
+ * PUBLIC: int __db_relink_log
+ * PUBLIC: __P((DB_LOG *, DB_TXN *, DB_LSN *, u_int32_t,
+ * PUBLIC: u_int32_t, u_int32_t, db_pgno_t, DB_LSN *,
+ * PUBLIC: db_pgno_t, DB_LSN *, db_pgno_t, DB_LSN *));
+ */
+int __db_relink_log(logp, txnid, ret_lsnp, flags,
+ opcode, fileid, pgno, lsn, prev, lsn_prev,
+ next, lsn_next)
+ DB_LOG *logp;
+ DB_TXN *txnid;
+ DB_LSN *ret_lsnp;
+ u_int32_t flags;
+ u_int32_t opcode;
+ u_int32_t fileid;
+ db_pgno_t pgno;
+ DB_LSN * lsn;
+ db_pgno_t prev;
+ DB_LSN * lsn_prev;
+ db_pgno_t next;
+ DB_LSN * lsn_next;
+{
+ DBT logrec;
+ DB_LSN *lsnp, null_lsn;
+ u_int32_t rectype, txn_num;
+ int ret;
+ u_int8_t *bp;
+
+ rectype = DB_db_relink;
+ txn_num = txnid == NULL ? 0 : txnid->txnid;
+ if (txnid == NULL) {
+ ZERO_LSN(null_lsn);
+ lsnp = &null_lsn;
+ } else
+ lsnp = &txnid->last_lsn;
+ logrec.size = sizeof(rectype) + sizeof(txn_num) + sizeof(DB_LSN)
+ + sizeof(opcode)
+ + sizeof(fileid)
+ + sizeof(pgno)
+ + sizeof(*lsn)
+ + sizeof(prev)
+ + sizeof(*lsn_prev)
+ + sizeof(next)
+ + sizeof(*lsn_next);
+ if ((ret = __os_malloc(logrec.size, NULL, &logrec.data)) != 0)
+ return (ret);
+
+ bp = logrec.data;
+ memcpy(bp, &rectype, sizeof(rectype));
+ bp += sizeof(rectype);
+ memcpy(bp, &txn_num, sizeof(txn_num));
+ bp += sizeof(txn_num);
+ memcpy(bp, lsnp, sizeof(DB_LSN));
+ bp += sizeof(DB_LSN);
+ memcpy(bp, &opcode, sizeof(opcode));
+ bp += sizeof(opcode);
+ memcpy(bp, &fileid, sizeof(fileid));
+ bp += sizeof(fileid);
+ memcpy(bp, &pgno, sizeof(pgno));
+ bp += sizeof(pgno);
+ if (lsn != NULL)
+ memcpy(bp, lsn, sizeof(*lsn));
+ else
+ memset(bp, 0, sizeof(*lsn));
+ bp += sizeof(*lsn);
+ memcpy(bp, &prev, sizeof(prev));
+ bp += sizeof(prev);
+ if (lsn_prev != NULL)
+ memcpy(bp, lsn_prev, sizeof(*lsn_prev));
+ else
+ memset(bp, 0, sizeof(*lsn_prev));
+ bp += sizeof(*lsn_prev);
+ memcpy(bp, &next, sizeof(next));
+ bp += sizeof(next);
+ if (lsn_next != NULL)
+ memcpy(bp, lsn_next, sizeof(*lsn_next));
+ else
+ memset(bp, 0, sizeof(*lsn_next));
+ bp += sizeof(*lsn_next);
+#ifdef DIAGNOSTIC
+ if ((u_int32_t)(bp - (u_int8_t *)logrec.data) != logrec.size)
+ fprintf(stderr, "Error in log record length");
+#endif
+ ret = log_put(logp, ret_lsnp, (DBT *)&logrec, flags);
+ if (txnid != NULL)
+ txnid->last_lsn = *ret_lsnp;
+ __os_free(logrec.data, 0);
+ return (ret);
+}
+
+/*
+ * PUBLIC: int __db_relink_print
+ * PUBLIC: __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+ */
+int
+__db_relink_print(notused1, dbtp, lsnp, notused2, notused3)
+ DB_LOG *notused1;
+ DBT *dbtp;
+ DB_LSN *lsnp;
+ int notused2;
+ void *notused3;
+{
+ __db_relink_args *argp;
+ u_int32_t i;
+ u_int ch;
+ int ret;
+
+ i = 0;
+ ch = 0;
+ notused1 = NULL;
+ notused2 = 0;
+ notused3 = NULL;
+
+ if ((ret = __db_relink_read(dbtp->data, &argp)) != 0)
+ return (ret);
+ printf("[%lu][%lu]db_relink: rec: %lu txnid %lx prevlsn [%lu][%lu]\n",
+ (u_long)lsnp->file,
+ (u_long)lsnp->offset,
+ (u_long)argp->type,
+ (u_long)argp->txnid->txnid,
+ (u_long)argp->prev_lsn.file,
+ (u_long)argp->prev_lsn.offset);
+ printf("\topcode: %lu\n", (u_long)argp->opcode);
+ printf("\tfileid: %lu\n", (u_long)argp->fileid);
+ printf("\tpgno: %lu\n", (u_long)argp->pgno);
+ printf("\tlsn: [%lu][%lu]\n",
+ (u_long)argp->lsn.file, (u_long)argp->lsn.offset);
+ printf("\tprev: %lu\n", (u_long)argp->prev);
+ printf("\tlsn_prev: [%lu][%lu]\n",
+ (u_long)argp->lsn_prev.file, (u_long)argp->lsn_prev.offset);
+ printf("\tnext: %lu\n", (u_long)argp->next);
+ printf("\tlsn_next: [%lu][%lu]\n",
+ (u_long)argp->lsn_next.file, (u_long)argp->lsn_next.offset);
+ printf("\n");
+ __os_free(argp, 0);
+ return (0);
+}
+
+/*
+ * PUBLIC: int __db_relink_read __P((void *, __db_relink_args **));
+ */
+int
+__db_relink_read(recbuf, argpp)
+ void *recbuf;
+ __db_relink_args **argpp;
+{
+ __db_relink_args *argp;
+ u_int8_t *bp;
+ int ret;
+
+ ret = __os_malloc(sizeof(__db_relink_args) +
+ sizeof(DB_TXN), NULL, &argp);
+ if (ret != 0)
+ return (ret);
+ argp->txnid = (DB_TXN *)&argp[1];
+ bp = recbuf;
+ memcpy(&argp->type, bp, sizeof(argp->type));
+ bp += sizeof(argp->type);
+ memcpy(&argp->txnid->txnid, bp, sizeof(argp->txnid->txnid));
+ bp += sizeof(argp->txnid->txnid);
+ memcpy(&argp->prev_lsn, bp, sizeof(DB_LSN));
+ bp += sizeof(DB_LSN);
+ memcpy(&argp->opcode, bp, sizeof(argp->opcode));
+ bp += sizeof(argp->opcode);
+ memcpy(&argp->fileid, bp, sizeof(argp->fileid));
+ bp += sizeof(argp->fileid);
+ memcpy(&argp->pgno, bp, sizeof(argp->pgno));
+ bp += sizeof(argp->pgno);
+ memcpy(&argp->lsn, bp, sizeof(argp->lsn));
+ bp += sizeof(argp->lsn);
+ memcpy(&argp->prev, bp, sizeof(argp->prev));
+ bp += sizeof(argp->prev);
+ memcpy(&argp->lsn_prev, bp, sizeof(argp->lsn_prev));
+ bp += sizeof(argp->lsn_prev);
+ memcpy(&argp->next, bp, sizeof(argp->next));
+ bp += sizeof(argp->next);
+ memcpy(&argp->lsn_next, bp, sizeof(argp->lsn_next));
+ bp += sizeof(argp->lsn_next);
+ *argpp = argp;
+ return (0);
+}
+
+/*
+ * PUBLIC: int __db_addpage_log
+ * PUBLIC: __P((DB_LOG *, DB_TXN *, DB_LSN *, u_int32_t,
+ * PUBLIC: u_int32_t, db_pgno_t, DB_LSN *, db_pgno_t,
+ * PUBLIC: DB_LSN *));
+ */
+int __db_addpage_log(logp, txnid, ret_lsnp, flags,
+ fileid, pgno, lsn, nextpgno, nextlsn)
+ DB_LOG *logp;
+ DB_TXN *txnid;
+ DB_LSN *ret_lsnp;
+ u_int32_t flags;
+ u_int32_t fileid;
+ db_pgno_t pgno;
+ DB_LSN * lsn;
+ db_pgno_t nextpgno;
+ DB_LSN * nextlsn;
+{
+ DBT logrec;
+ DB_LSN *lsnp, null_lsn;
+ u_int32_t rectype, txn_num;
+ int ret;
+ u_int8_t *bp;
+
+ rectype = DB_db_addpage;
+ txn_num = txnid == NULL ? 0 : txnid->txnid;
+ if (txnid == NULL) {
+ ZERO_LSN(null_lsn);
+ lsnp = &null_lsn;
+ } else
+ lsnp = &txnid->last_lsn;
+ logrec.size = sizeof(rectype) + sizeof(txn_num) + sizeof(DB_LSN)
+ + sizeof(fileid)
+ + sizeof(pgno)
+ + sizeof(*lsn)
+ + sizeof(nextpgno)
+ + sizeof(*nextlsn);
+ if ((ret = __os_malloc(logrec.size, NULL, &logrec.data)) != 0)
+ return (ret);
+
+ bp = logrec.data;
+ memcpy(bp, &rectype, sizeof(rectype));
+ bp += sizeof(rectype);
+ memcpy(bp, &txn_num, sizeof(txn_num));
+ bp += sizeof(txn_num);
+ memcpy(bp, lsnp, sizeof(DB_LSN));
+ bp += sizeof(DB_LSN);
+ memcpy(bp, &fileid, sizeof(fileid));
+ bp += sizeof(fileid);
+ memcpy(bp, &pgno, sizeof(pgno));
+ bp += sizeof(pgno);
+ if (lsn != NULL)
+ memcpy(bp, lsn, sizeof(*lsn));
+ else
+ memset(bp, 0, sizeof(*lsn));
+ bp += sizeof(*lsn);
+ memcpy(bp, &nextpgno, sizeof(nextpgno));
+ bp += sizeof(nextpgno);
+ if (nextlsn != NULL)
+ memcpy(bp, nextlsn, sizeof(*nextlsn));
+ else
+ memset(bp, 0, sizeof(*nextlsn));
+ bp += sizeof(*nextlsn);
+#ifdef DIAGNOSTIC
+ if ((u_int32_t)(bp - (u_int8_t *)logrec.data) != logrec.size)
+ fprintf(stderr, "Error in log record length");
+#endif
+ ret = log_put(logp, ret_lsnp, (DBT *)&logrec, flags);
+ if (txnid != NULL)
+ txnid->last_lsn = *ret_lsnp;
+ __os_free(logrec.data, 0);
+ return (ret);
+}
+
+/*
+ * PUBLIC: int __db_addpage_print
+ * PUBLIC: __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+ */
+int
+__db_addpage_print(notused1, dbtp, lsnp, notused2, notused3)
+ DB_LOG *notused1;
+ DBT *dbtp;
+ DB_LSN *lsnp;
+ int notused2;
+ void *notused3;
+{
+ __db_addpage_args *argp;
+ u_int32_t i;
+ u_int ch;
+ int ret;
+
+ i = 0;
+ ch = 0;
+ notused1 = NULL;
+ notused2 = 0;
+ notused3 = NULL;
+
+ if ((ret = __db_addpage_read(dbtp->data, &argp)) != 0)
+ return (ret);
+ printf("[%lu][%lu]db_addpage: rec: %lu txnid %lx prevlsn [%lu][%lu]\n",
+ (u_long)lsnp->file,
+ (u_long)lsnp->offset,
+ (u_long)argp->type,
+ (u_long)argp->txnid->txnid,
+ (u_long)argp->prev_lsn.file,
+ (u_long)argp->prev_lsn.offset);
+ printf("\tfileid: %lu\n", (u_long)argp->fileid);
+ printf("\tpgno: %lu\n", (u_long)argp->pgno);
+ printf("\tlsn: [%lu][%lu]\n",
+ (u_long)argp->lsn.file, (u_long)argp->lsn.offset);
+ printf("\tnextpgno: %lu\n", (u_long)argp->nextpgno);
+ printf("\tnextlsn: [%lu][%lu]\n",
+ (u_long)argp->nextlsn.file, (u_long)argp->nextlsn.offset);
+ printf("\n");
+ __os_free(argp, 0);
+ return (0);
+}
+
+/*
+ * PUBLIC: int __db_addpage_read __P((void *, __db_addpage_args **));
+ */
+int
+__db_addpage_read(recbuf, argpp)
+ void *recbuf;
+ __db_addpage_args **argpp;
+{
+ __db_addpage_args *argp;
+ u_int8_t *bp;
+ int ret;
+
+ ret = __os_malloc(sizeof(__db_addpage_args) +
+ sizeof(DB_TXN), NULL, &argp);
+ if (ret != 0)
+ return (ret);
+ argp->txnid = (DB_TXN *)&argp[1];
+ bp = recbuf;
+ memcpy(&argp->type, bp, sizeof(argp->type));
+ bp += sizeof(argp->type);
+ memcpy(&argp->txnid->txnid, bp, sizeof(argp->txnid->txnid));
+ bp += sizeof(argp->txnid->txnid);
+ memcpy(&argp->prev_lsn, bp, sizeof(DB_LSN));
+ bp += sizeof(DB_LSN);
+ memcpy(&argp->fileid, bp, sizeof(argp->fileid));
+ bp += sizeof(argp->fileid);
+ memcpy(&argp->pgno, bp, sizeof(argp->pgno));
+ bp += sizeof(argp->pgno);
+ memcpy(&argp->lsn, bp, sizeof(argp->lsn));
+ bp += sizeof(argp->lsn);
+ memcpy(&argp->nextpgno, bp, sizeof(argp->nextpgno));
+ bp += sizeof(argp->nextpgno);
+ memcpy(&argp->nextlsn, bp, sizeof(argp->nextlsn));
+ bp += sizeof(argp->nextlsn);
+ *argpp = argp;
+ return (0);
+}
+
+/*
+ * PUBLIC: int __db_debug_log
+ * PUBLIC: __P((DB_LOG *, DB_TXN *, DB_LSN *, u_int32_t,
+ * PUBLIC: const DBT *, u_int32_t, const DBT *, const DBT *,
+ * PUBLIC: u_int32_t));
+ */
+int __db_debug_log(logp, txnid, ret_lsnp, flags,
+ op, fileid, key, data, arg_flags)
+ DB_LOG *logp;
+ DB_TXN *txnid;
+ DB_LSN *ret_lsnp;
+ u_int32_t flags;
+ const DBT *op;
+ u_int32_t fileid;
+ const DBT *key;
+ const DBT *data;
+ u_int32_t arg_flags;
+{
+ DBT logrec;
+ DB_LSN *lsnp, null_lsn;
+ u_int32_t zero;
+ u_int32_t rectype, txn_num;
+ int ret;
+ u_int8_t *bp;
+
+ rectype = DB_db_debug;
+ txn_num = txnid == NULL ? 0 : txnid->txnid;
+ if (txnid == NULL) {
+ ZERO_LSN(null_lsn);
+ lsnp = &null_lsn;
+ } else
+ lsnp = &txnid->last_lsn;
+ logrec.size = sizeof(rectype) + sizeof(txn_num) + sizeof(DB_LSN)
+ + sizeof(u_int32_t) + (op == NULL ? 0 : op->size)
+ + sizeof(fileid)
+ + sizeof(u_int32_t) + (key == NULL ? 0 : key->size)
+ + sizeof(u_int32_t) + (data == NULL ? 0 : data->size)
+ + sizeof(arg_flags);
+ if ((ret = __os_malloc(logrec.size, NULL, &logrec.data)) != 0)
+ return (ret);
+
+ bp = logrec.data;
+ memcpy(bp, &rectype, sizeof(rectype));
+ bp += sizeof(rectype);
+ memcpy(bp, &txn_num, sizeof(txn_num));
+ bp += sizeof(txn_num);
+ memcpy(bp, lsnp, sizeof(DB_LSN));
+ bp += sizeof(DB_LSN);
+ if (op == NULL) {
+ zero = 0;
+ memcpy(bp, &zero, sizeof(u_int32_t));
+ bp += sizeof(u_int32_t);
+ } else {
+ memcpy(bp, &op->size, sizeof(op->size));
+ bp += sizeof(op->size);
+ memcpy(bp, op->data, op->size);
+ bp += op->size;
+ }
+ memcpy(bp, &fileid, sizeof(fileid));
+ bp += sizeof(fileid);
+ if (key == NULL) {
+ zero = 0;
+ memcpy(bp, &zero, sizeof(u_int32_t));
+ bp += sizeof(u_int32_t);
+ } else {
+ memcpy(bp, &key->size, sizeof(key->size));
+ bp += sizeof(key->size);
+ memcpy(bp, key->data, key->size);
+ bp += key->size;
+ }
+ if (data == NULL) {
+ zero = 0;
+ memcpy(bp, &zero, sizeof(u_int32_t));
+ bp += sizeof(u_int32_t);
+ } else {
+ memcpy(bp, &data->size, sizeof(data->size));
+ bp += sizeof(data->size);
+ memcpy(bp, data->data, data->size);
+ bp += data->size;
+ }
+ memcpy(bp, &arg_flags, sizeof(arg_flags));
+ bp += sizeof(arg_flags);
+#ifdef DIAGNOSTIC
+ if ((u_int32_t)(bp - (u_int8_t *)logrec.data) != logrec.size)
+ fprintf(stderr, "Error in log record length");
+#endif
+ ret = log_put(logp, ret_lsnp, (DBT *)&logrec, flags);
+ if (txnid != NULL)
+ txnid->last_lsn = *ret_lsnp;
+ __os_free(logrec.data, 0);
+ return (ret);
+}
+
+/*
+ * PUBLIC: int __db_debug_print
+ * PUBLIC: __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+ */
+int
+__db_debug_print(notused1, dbtp, lsnp, notused2, notused3)
+ DB_LOG *notused1;
+ DBT *dbtp;
+ DB_LSN *lsnp;
+ int notused2;
+ void *notused3;
+{
+ __db_debug_args *argp;
+ u_int32_t i;
+ u_int ch;
+ int ret;
+
+ i = 0;
+ ch = 0;
+ notused1 = NULL;
+ notused2 = 0;
+ notused3 = NULL;
+
+ if ((ret = __db_debug_read(dbtp->data, &argp)) != 0)
+ return (ret);
+ printf("[%lu][%lu]db_debug: rec: %lu txnid %lx prevlsn [%lu][%lu]\n",
+ (u_long)lsnp->file,
+ (u_long)lsnp->offset,
+ (u_long)argp->type,
+ (u_long)argp->txnid->txnid,
+ (u_long)argp->prev_lsn.file,
+ (u_long)argp->prev_lsn.offset);
+ printf("\top: ");
+ for (i = 0; i < argp->op.size; i++) {
+ ch = ((u_int8_t *)argp->op.data)[i];
+ if (isprint(ch) || ch == 0xa)
+ putchar(ch);
+ else
+ printf("%#x ", ch);
+ }
+ printf("\n");
+ printf("\tfileid: %lu\n", (u_long)argp->fileid);
+ printf("\tkey: ");
+ for (i = 0; i < argp->key.size; i++) {
+ ch = ((u_int8_t *)argp->key.data)[i];
+ if (isprint(ch) || ch == 0xa)
+ putchar(ch);
+ else
+ printf("%#x ", ch);
+ }
+ printf("\n");
+ printf("\tdata: ");
+ for (i = 0; i < argp->data.size; i++) {
+ ch = ((u_int8_t *)argp->data.data)[i];
+ if (isprint(ch) || ch == 0xa)
+ putchar(ch);
+ else
+ printf("%#x ", ch);
+ }
+ printf("\n");
+ printf("\targ_flags: %lu\n", (u_long)argp->arg_flags);
+ printf("\n");
+ __os_free(argp, 0);
+ return (0);
+}
+
+/*
+ * PUBLIC: int __db_debug_read __P((void *, __db_debug_args **));
+ */
+int
+__db_debug_read(recbuf, argpp)
+ void *recbuf;
+ __db_debug_args **argpp;
+{
+ __db_debug_args *argp;
+ u_int8_t *bp;
+ int ret;
+
+ ret = __os_malloc(sizeof(__db_debug_args) +
+ sizeof(DB_TXN), NULL, &argp);
+ if (ret != 0)
+ return (ret);
+ argp->txnid = (DB_TXN *)&argp[1];
+ bp = recbuf;
+ memcpy(&argp->type, bp, sizeof(argp->type));
+ bp += sizeof(argp->type);
+ memcpy(&argp->txnid->txnid, bp, sizeof(argp->txnid->txnid));
+ bp += sizeof(argp->txnid->txnid);
+ memcpy(&argp->prev_lsn, bp, sizeof(DB_LSN));
+ bp += sizeof(DB_LSN);
+ memcpy(&argp->op.size, bp, sizeof(u_int32_t));
+ bp += sizeof(u_int32_t);
+ argp->op.data = bp;
+ bp += argp->op.size;
+ memcpy(&argp->fileid, bp, sizeof(argp->fileid));
+ bp += sizeof(argp->fileid);
+ memcpy(&argp->key.size, bp, sizeof(u_int32_t));
+ bp += sizeof(u_int32_t);
+ argp->key.data = bp;
+ bp += argp->key.size;
+ memcpy(&argp->data.size, bp, sizeof(u_int32_t));
+ bp += sizeof(u_int32_t);
+ argp->data.data = bp;
+ bp += argp->data.size;
+ memcpy(&argp->arg_flags, bp, sizeof(argp->arg_flags));
+ bp += sizeof(argp->arg_flags);
+ *argpp = argp;
+ return (0);
+}
+
+/*
+ * PUBLIC: int __db_init_print __P((DB_ENV *));
+ */
+int
+__db_init_print(dbenv)
+ DB_ENV *dbenv;
+{
+ int ret;
+
+ if ((ret = __db_add_recovery(dbenv,
+ __db_addrem_print, DB_db_addrem)) != 0)
+ return (ret);
+ if ((ret = __db_add_recovery(dbenv,
+ __db_split_print, DB_db_split)) != 0)
+ return (ret);
+ if ((ret = __db_add_recovery(dbenv,
+ __db_big_print, DB_db_big)) != 0)
+ return (ret);
+ if ((ret = __db_add_recovery(dbenv,
+ __db_ovref_print, DB_db_ovref)) != 0)
+ return (ret);
+ if ((ret = __db_add_recovery(dbenv,
+ __db_relink_print, DB_db_relink)) != 0)
+ return (ret);
+ if ((ret = __db_add_recovery(dbenv,
+ __db_addpage_print, DB_db_addpage)) != 0)
+ return (ret);
+ if ((ret = __db_add_recovery(dbenv,
+ __db_debug_print, DB_db_debug)) != 0)
+ return (ret);
+ return (0);
+}
+
+/*
+ * PUBLIC: int __db_init_recover __P((DB_ENV *));
+ */
+int
+__db_init_recover(dbenv)
+ DB_ENV *dbenv;
+{
+ int ret;
+
+ if ((ret = __db_add_recovery(dbenv,
+ __db_addrem_recover, DB_db_addrem)) != 0)
+ return (ret);
+ if ((ret = __db_add_recovery(dbenv,
+ __db_split_recover, DB_db_split)) != 0)
+ return (ret);
+ if ((ret = __db_add_recovery(dbenv,
+ __db_big_recover, DB_db_big)) != 0)
+ return (ret);
+ if ((ret = __db_add_recovery(dbenv,
+ __db_ovref_recover, DB_db_ovref)) != 0)
+ return (ret);
+ if ((ret = __db_add_recovery(dbenv,
+ __db_relink_recover, DB_db_relink)) != 0)
+ return (ret);
+ if ((ret = __db_add_recovery(dbenv,
+ __db_addpage_recover, DB_db_addpage)) != 0)
+ return (ret);
+ if ((ret = __db_add_recovery(dbenv,
+ __db_debug_recover, DB_db_debug)) != 0)
+ return (ret);
+ return (0);
+}
+
diff --git a/usr/src/cmd/sendmail/db/db/db_byteorder.c b/usr/src/cmd/sendmail/db/db/db_byteorder.c
new file mode 100644
index 0000000000..bd348483c2
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/db/db_byteorder.c
@@ -0,0 +1,70 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997
+ * Sleepycat Software. All rights reserved.
+ */
+/*
+ * Copyright (c) 1998 by Sun Microsystems, Inc.
+ * All rights reserved.
+ */
+
+#include "config.h"
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#ifndef lint
+static const char sccsid[] = "@(#)db_byteorder.c 10.4 (Sleepycat) 9/4/97";
+static const char sccsi2[] = "%W% (Sun) %G%";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#ifdef HAVE_ENDIAN_H
+#include <endian.h>
+#if BYTE_ORDER == BIG_ENDIAN
+#define WORDS_BIGENDIAN 1
+#endif
+#endif
+
+#include <errno.h>
+#endif
+
+#include "db_int.h"
+#include "common_ext.h"
+
+/*
+ * __db_byteorder --
+ * Return if we need to do byte swapping, checking for illegal
+ * values.
+ *
+ * PUBLIC: int __db_byteorder __P((DB_ENV *, int));
+ */
+int
+__db_byteorder(dbenv, lorder)
+ DB_ENV *dbenv;
+ int lorder;
+{
+ switch (lorder) {
+ case 0:
+ break;
+ case 1234:
+#if defined(WORDS_BIGENDIAN)
+ return (DB_SWAPBYTES);
+#else
+ break;
+#endif
+ case 4321:
+#if defined(WORDS_BIGENDIAN)
+ break;
+#else
+ return (DB_SWAPBYTES);
+#endif
+ default:
+ __db_err(dbenv,
+ "illegal byte order, only big and little-endian supported");
+ return (EINVAL);
+ }
+ return (0);
+}
diff --git a/usr/src/cmd/sendmail/db/db/db_conv.c b/usr/src/cmd/sendmail/db/db/db_conv.c
new file mode 100644
index 0000000000..8b5cf5f4a7
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/db/db_conv.c
@@ -0,0 +1,255 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+/*
+ * Copyright (c) 1990, 1993, 1994, 1995, 1996
+ * Keith Bostic. All rights reserved.
+ */
+/*
+ * Copyright (c) 1990, 1993, 1994, 1995
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)db_conv.c 10.13 (Sleepycat) 4/26/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <errno.h>
+#include <string.h>
+#endif
+
+#include "db_int.h"
+#include "db_page.h"
+#include "db_swap.h"
+#include "db_am.h"
+
+static int __db_convert __P((db_pgno_t, void *, size_t, int));
+
+/*
+ * __db_pgin --
+ *
+ * PUBLIC: int __db_pgin __P((db_pgno_t, size_t, void *));
+ */
+int
+__db_pgin(pg, pagesize, pp)
+ db_pgno_t pg;
+ size_t pagesize;
+ void *pp;
+{
+ return (__db_convert(pg, pp, pagesize, 1));
+}
+
+/*
+ * __db_pgout --
+ *
+ * PUBLIC: int __db_pgout __P((db_pgno_t, size_t, void *));
+ */
+int
+__db_pgout(pg, pagesize, pp)
+ db_pgno_t pg;
+ size_t pagesize;
+ void *pp;
+{
+ return (__db_convert(pg, pp, pagesize, 0));
+}
+
+/*
+ * __db_convert --
+ * Actually convert a page.
+ */
+static int
+__db_convert(pg, pp, pagesize, pgin)
+ db_pgno_t pg;
+ void *pp;
+ size_t pagesize;
+ int pgin;
+{
+ BINTERNAL *bi;
+ BKEYDATA *bk;
+ BOVERFLOW *bo;
+ PAGE *h;
+ RINTERNAL *ri;
+ db_indx_t i, len, tmp;
+ u_int8_t *p, *end;
+
+ COMPQUIET(pg, 0);
+
+ h = pp;
+ if (pgin) {
+ M_32_SWAP(h->lsn.file);
+ M_32_SWAP(h->lsn.offset);
+ M_32_SWAP(h->pgno);
+ M_32_SWAP(h->prev_pgno);
+ M_32_SWAP(h->next_pgno);
+ M_16_SWAP(h->entries);
+ M_16_SWAP(h->hf_offset);
+ }
+
+ switch (h->type) {
+ case P_HASH:
+ for (i = 0; i < NUM_ENT(h); i++) {
+ if (pgin)
+ M_16_SWAP(h->inp[i]);
+
+ switch (HPAGE_TYPE(h, i)) {
+ case H_KEYDATA:
+ break;
+ case H_DUPLICATE:
+ len = LEN_HKEYDATA(h, pagesize, i);
+ p = HKEYDATA_DATA(P_ENTRY(h, i));
+ for (end = p + len; p < end;) {
+ if (pgin) {
+ P_16_SWAP(p);
+ memcpy(&tmp,
+ p, sizeof(db_indx_t));
+ p += sizeof(db_indx_t);
+ } else {
+ memcpy(&tmp,
+ p, sizeof(db_indx_t));
+ SWAP16(p);
+ }
+ p += tmp;
+ SWAP16(p);
+ }
+ break;
+ case H_OFFDUP:
+ p = HOFFPAGE_PGNO(P_ENTRY(h, i));
+ SWAP32(p); /* pgno */
+ break;
+ case H_OFFPAGE:
+ p = HOFFPAGE_PGNO(P_ENTRY(h, i));
+ SWAP32(p); /* pgno */
+ SWAP32(p); /* tlen */
+ break;
+ }
+
+ }
+
+ /*
+ * The offsets in the inp array are used to determine
+ * the size of entries on a page; therefore they
+ * cannot be converted until we've done all the
+ * entries.
+ */
+ if (!pgin)
+ for (i = 0; i < NUM_ENT(h); i++)
+ M_16_SWAP(h->inp[i]);
+ break;
+ case P_LBTREE:
+ case P_LRECNO:
+ case P_DUPLICATE:
+ for (i = 0; i < NUM_ENT(h); i++) {
+ if (pgin)
+ M_16_SWAP(h->inp[i]);
+
+ bk = GET_BKEYDATA(h, i);
+ switch (B_TYPE(bk->type)) {
+ case B_KEYDATA:
+ M_16_SWAP(bk->len);
+ break;
+ case B_DUPLICATE:
+ case B_OVERFLOW:
+ bo = (BOVERFLOW *)bk;
+ M_32_SWAP(bo->pgno);
+ M_32_SWAP(bo->tlen);
+ break;
+ }
+
+ if (!pgin)
+ M_16_SWAP(h->inp[i]);
+ }
+ break;
+ case P_IBTREE:
+ for (i = 0; i < NUM_ENT(h); i++) {
+ if (pgin)
+ M_16_SWAP(h->inp[i]);
+
+ bi = GET_BINTERNAL(h, i);
+ M_16_SWAP(bi->len);
+ M_32_SWAP(bi->pgno);
+ M_32_SWAP(bi->nrecs);
+
+ switch (B_TYPE(bi->type)) {
+ case B_KEYDATA:
+ break;
+ case B_DUPLICATE:
+ case B_OVERFLOW:
+ bo = (BOVERFLOW *)bi->data;
+ M_32_SWAP(bo->pgno);
+ M_32_SWAP(bo->tlen);
+ break;
+ }
+
+ if (!pgin)
+ M_16_SWAP(h->inp[i]);
+ }
+ break;
+ case P_IRECNO:
+ for (i = 0; i < NUM_ENT(h); i++) {
+ if (pgin)
+ M_16_SWAP(h->inp[i]);
+
+ ri = GET_RINTERNAL(h, i);
+ M_32_SWAP(ri->pgno);
+ M_32_SWAP(ri->nrecs);
+
+ if (!pgin)
+ M_16_SWAP(h->inp[i]);
+ }
+ break;
+ case P_OVERFLOW:
+ case P_INVALID:
+ /* Nothing to do. */
+ break;
+ default:
+ return (EINVAL);
+ }
+
+ if (!pgin) {
+ /* Swap the header information. */
+ M_32_SWAP(h->lsn.file);
+ M_32_SWAP(h->lsn.offset);
+ M_32_SWAP(h->pgno);
+ M_32_SWAP(h->prev_pgno);
+ M_32_SWAP(h->next_pgno);
+ M_16_SWAP(h->entries);
+ M_16_SWAP(h->hf_offset);
+ }
+ return (0);
+}
diff --git a/usr/src/cmd/sendmail/db/db/db_dispatch.c b/usr/src/cmd/sendmail/db/db/db_dispatch.c
new file mode 100644
index 0000000000..3b59363d0f
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/db/db_dispatch.c
@@ -0,0 +1,317 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+/*
+ * Copyright (c) 1995, 1996
+ * The President and Fellows of Harvard University. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Margo Seltzer.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)db_dispatch.c 10.20 (Sleepycat) 10/10/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <errno.h>
+#include <shqueue.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#endif
+
+#include "db_int.h"
+#include "db_page.h"
+#include "db_dispatch.h"
+#include "db_am.h"
+#include "common_ext.h"
+#include "log_auto.h"
+#include "txn.h"
+#include "txn_auto.h"
+
+/*
+ * Data structures to manage the DB dispatch table. The dispatch table
+ * is a dynamically allocated array of pointers to dispatch functions.
+ * The dispatch_size is the number of entries possible in the current
+ * dispatch table and the dispatch_valid is the number of valid entries
+ * in the dispatch table.
+ */
+static int (**dispatch_table) __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+static u_int32_t dispatch_size = 0;
+
+/*
+ * __db_dispatch --
+ *
+ * This is the transaction dispatch function used by the db access methods.
+ * It is designed to handle the record format used by all the access
+ * methods (the one automatically generated by the db_{h,log,read}.sh
+ * scripts in the tools directory). An application using a different
+ * recovery paradigm will supply a different dispatch function to txn_open.
+ *
+ * PUBLIC: int __db_dispatch __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+ */
+int
+__db_dispatch(logp, db, lsnp, redo, info)
+ DB_LOG *logp; /* The log file. */
+ DBT *db; /* The log record upon which to dispatch. */
+ DB_LSN *lsnp; /* The lsn of the record being dispatched. */
+ int redo; /* Redo this op (or undo it). */
+ void *info;
+{
+ u_int32_t rectype, txnid;
+
+ memcpy(&rectype, db->data, sizeof(rectype));
+ memcpy(&txnid, (u_int8_t *)db->data + sizeof(rectype), sizeof(txnid));
+
+ switch (redo) {
+ case TXN_REDO:
+ case TXN_UNDO:
+ return ((dispatch_table[rectype])(logp, db, lsnp, redo, info));
+ case TXN_OPENFILES:
+ if (rectype < DB_txn_BEGIN )
+ return ((dispatch_table[rectype])(logp,
+ db, lsnp, redo, info));
+ break;
+ case TXN_BACKWARD_ROLL:
+ /*
+ * Running full recovery in the backward pass. If we've
+ * seen this txnid before and added to it our commit list,
+ * then we do nothing during this pass. If we've never
+ * seen it, then we call the appropriate recovery routine
+ * in "abort mode".
+ */
+ if (rectype == DB_log_register || rectype == DB_txn_ckp ||
+ (__db_txnlist_find(info, txnid) == DB_NOTFOUND &&
+ txnid != 0))
+ return ((dispatch_table[rectype])(logp,
+ db, lsnp, TXN_UNDO, info));
+ break;
+ case TXN_FORWARD_ROLL:
+ /*
+ * In the forward pass, if we haven't seen the transaction,
+ * do nothing, else recovery it.
+ */
+ if (rectype == DB_log_register || rectype == DB_txn_ckp ||
+ __db_txnlist_find(info, txnid) != DB_NOTFOUND)
+ return ((dispatch_table[rectype])(logp,
+ db, lsnp, TXN_REDO, info));
+ break;
+ default:
+ abort();
+ }
+ return (0);
+}
+
+/*
+ * __db_add_recovery --
+ *
+ * PUBLIC: int __db_add_recovery __P((DB_ENV *,
+ * PUBLIC: int (*)(DB_LOG *, DBT *, DB_LSN *, int, void *), u_int32_t));
+ */
+int
+__db_add_recovery(dbenv, func, ndx)
+ DB_ENV *dbenv;
+ int (*func) __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+ u_int32_t ndx;
+{
+ u_int32_t i;
+ int ret;
+
+ COMPQUIET(dbenv, NULL); /* !!!: not currently used. */
+
+ /* Check if we have to grow the table. */
+ if (ndx >= dispatch_size) {
+ if ((ret = __os_realloc(&dispatch_table,
+ (DB_user_BEGIN + dispatch_size) *
+ sizeof(dispatch_table[0]))) != 0)
+ return (ret);
+ for (i = dispatch_size,
+ dispatch_size += DB_user_BEGIN; i < dispatch_size; ++i)
+ dispatch_table[i] = NULL;
+ }
+
+ dispatch_table[ndx] = func;
+ return (0);
+}
+
+/*
+ * __db_txnlist_init --
+ * Initialize transaction linked list.
+ *
+ * PUBLIC: int __db_txnlist_init __P((void *));
+ */
+int
+__db_txnlist_init(retp)
+ void *retp;
+{
+ DB_TXNHEAD *headp;
+ int ret;
+
+ if ((ret = __os_malloc(sizeof(DB_TXNHEAD), NULL, &headp)) != 0)
+ return (ret);
+
+ LIST_INIT(&headp->head);
+ headp->maxid = 0;
+ headp->generation = 1;
+
+ *(void **)retp = headp;
+ return (0);
+}
+
+/*
+ * __db_txnlist_add --
+ * Add an element to our transaction linked list.
+ *
+ * PUBLIC: int __db_txnlist_add __P((void *, u_int32_t));
+ */
+int
+__db_txnlist_add(listp, txnid)
+ void *listp;
+ u_int32_t txnid;
+{
+ DB_TXNHEAD *hp;
+ DB_TXNLIST *elp;
+ int ret;
+
+ if ((ret = __os_malloc(sizeof(DB_TXNLIST), NULL, &elp)) != 0)
+ return (ret);
+
+ elp->txnid = txnid;
+ hp = (DB_TXNHEAD *)listp;
+ LIST_INSERT_HEAD(&hp->head, elp, links);
+ if (txnid > hp->maxid)
+ hp->maxid = txnid;
+ elp->generation = hp->generation;
+
+ return (0);
+}
+
+/*
+ * __db_txnlist_find --
+ * Checks to see if a txnid with the current generation is in the
+ * txnid list.
+ *
+ * PUBLIC: int __db_txnlist_find __P((void *, u_int32_t));
+ */
+int
+__db_txnlist_find(listp, txnid)
+ void *listp;
+ u_int32_t txnid;
+{
+ DB_TXNHEAD *hp;
+ DB_TXNLIST *p;
+
+ if ((hp = (DB_TXNHEAD *)listp) == NULL)
+ return (DB_NOTFOUND);
+
+ for (p = hp->head.lh_first; p != NULL; p = p->links.le_next)
+ if (p->txnid == txnid && hp->generation == p->generation)
+ return (0);
+
+ return (DB_NOTFOUND);
+}
+
+/*
+ * __db_txnlist_end --
+ * Discard transaction linked list.
+ *
+ * PUBLIC: void __db_txnlist_end __P((void *));
+ */
+void
+__db_txnlist_end(listp)
+ void *listp;
+{
+ DB_TXNHEAD *hp;
+ DB_TXNLIST *p;
+
+ hp = (DB_TXNHEAD *)listp;
+ while ((p = LIST_FIRST(&hp->head)) != LIST_END(&hp->head)) {
+ LIST_REMOVE(p, links);
+ __os_free(p, 0);
+ }
+ __os_free(listp, sizeof(DB_TXNHEAD));
+}
+
+/*
+ * __db_txnlist_gen --
+ * Change the current generation number.
+ *
+ * PUBLIC: void __db_txnlist_gen __P((void *, int));
+ */
+void
+__db_txnlist_gen(listp, incr)
+ void *listp;
+ int incr;
+{
+ DB_TXNHEAD *hp;
+
+ /*
+ * During recovery generation numbers keep track of how many "restart"
+ * checkpoints we've seen. Restart checkpoints occur whenever we take
+ * a checkpoint and there are no outstanding transactions. When that
+ * happens, we can reset transaction IDs back to 1. It always happens
+ * at recovery and it prevents us from exhausting the transaction IDs
+ * name space.
+ */
+ hp = (DB_TXNHEAD *)listp;
+ hp->generation += incr;
+}
+
+#ifdef DEBUG
+/*
+ * __db_txnlist_print --
+ * Print out the transaction list.
+ *
+ * PUBLIC: void __db_txnlist_print __P((void *));
+ */
+void
+__db_txnlist_print(listp)
+ void *listp;
+{
+ DB_TXNHEAD *hp;
+ DB_TXNLIST *p;
+
+ hp = (DB_TXNHEAD *)listp;
+ printf("Maxid: %lu Generation: %lu\n", (u_long)hp->maxid,
+ (u_long)hp->generation);
+ for (p = hp->head.lh_first; p != NULL; p = p->links.le_next)
+ printf("TXNID: %lu(%lu)\n", (u_long)p->txnid,
+ (u_long)p->generation);
+}
+#endif
diff --git a/usr/src/cmd/sendmail/db/db/db_dup.c b/usr/src/cmd/sendmail/db/db/db_dup.c
new file mode 100644
index 0000000000..2673bbcd61
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/db/db_dup.c
@@ -0,0 +1,947 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)db_dup.c 10.35 (Sleepycat) 12/2/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <errno.h>
+#include <string.h>
+#endif
+
+#include "db_int.h"
+#include "db_page.h"
+#include "btree.h"
+#include "db_am.h"
+
+static int __db_addpage __P((DBC *,
+ PAGE **, db_indx_t *, int (*)(DBC *, u_int32_t, PAGE **)));
+static int __db_dsplit __P((DBC *,
+ PAGE **, db_indx_t *, u_int32_t, int (*)(DBC *, u_int32_t, PAGE **)));
+
+/*
+ * __db_dput --
+ * Put a duplicate item onto a duplicate page at the given index.
+ *
+ * PUBLIC: int __db_dput __P((DBC *, DBT *,
+ * PUBLIC: PAGE **, db_indx_t *, int (*)(DBC *, u_int32_t, PAGE **)));
+ */
+int
+__db_dput(dbc, dbt, pp, indxp, newfunc)
+ DBC *dbc;
+ DBT *dbt;
+ PAGE **pp;
+ db_indx_t *indxp;
+ int (*newfunc) __P((DBC *, u_int32_t, PAGE **));
+{
+ BOVERFLOW bo;
+ DBT *data_dbtp, hdr_dbt, *hdr_dbtp;
+ PAGE *pagep;
+ db_indx_t size, isize;
+ db_pgno_t pgno;
+ int ret;
+
+ /*
+ * We need some access method independent threshold for when we put
+ * a duplicate item onto an overflow page.
+ */
+ if (dbt->size > 0.25 * dbc->dbp->pgsize) {
+ if ((ret = __db_poff(dbc, dbt, &pgno, newfunc)) != 0)
+ return (ret);
+ UMRW(bo.unused1);
+ B_TSET(bo.type, B_OVERFLOW, 0);
+ UMRW(bo.unused2);
+ bo.tlen = dbt->size;
+ bo.pgno = pgno;
+ hdr_dbt.data = &bo;
+ hdr_dbt.size = isize = BOVERFLOW_SIZE;
+ hdr_dbtp = &hdr_dbt;
+ size = BOVERFLOW_PSIZE;
+ data_dbtp = NULL;
+ } else {
+ size = BKEYDATA_PSIZE(dbt->size);
+ isize = BKEYDATA_SIZE(dbt->size);
+ hdr_dbtp = NULL;
+ data_dbtp = dbt;
+ }
+
+ pagep = *pp;
+ if (size > P_FREESPACE(pagep)) {
+ if (*indxp == NUM_ENT(*pp) && NEXT_PGNO(*pp) == PGNO_INVALID)
+ ret = __db_addpage(dbc, pp, indxp, newfunc);
+ else
+ ret = __db_dsplit(dbc, pp, indxp, isize, newfunc);
+ if (ret != 0)
+ /*
+ * XXX
+ * Pages not returned to free list.
+ */
+ return (ret);
+ pagep = *pp;
+ }
+
+ /*
+ * Now, pagep references the page on which to insert and indx is the
+ * the location to insert.
+ */
+ if ((ret = __db_pitem(dbc,
+ pagep, (u_int32_t)*indxp, isize, hdr_dbtp, data_dbtp)) != 0)
+ return (ret);
+
+ (void)memp_fset(dbc->dbp->mpf, pagep, DB_MPOOL_DIRTY);
+ return (0);
+}
+
+/*
+ * __db_drem --
+ * Remove a duplicate at the given index on the given page.
+ *
+ * PUBLIC: int __db_drem __P((DBC *,
+ * PUBLIC: PAGE **, u_int32_t, int (*)(DBC *, PAGE *)));
+ */
+int
+__db_drem(dbc, pp, indx, freefunc)
+ DBC *dbc;
+ PAGE **pp;
+ u_int32_t indx;
+ int (*freefunc) __P((DBC *, PAGE *));
+{
+ PAGE *pagep;
+ int ret;
+
+ pagep = *pp;
+
+ /* Check if we are freeing a big item. */
+ if (B_TYPE(GET_BKEYDATA(pagep, indx)->type) == B_OVERFLOW) {
+ if ((ret = __db_doff(dbc,
+ GET_BOVERFLOW(pagep, indx)->pgno, freefunc)) != 0)
+ return (ret);
+ ret = __db_ditem(dbc, pagep, indx, BOVERFLOW_SIZE);
+ } else
+ ret = __db_ditem(dbc, pagep, indx,
+ BKEYDATA_SIZE(GET_BKEYDATA(pagep, indx)->len));
+ if (ret != 0)
+ return (ret);
+
+ if (NUM_ENT(pagep) == 0) {
+ /*
+ * If the page is emptied, then the page is freed and the pp
+ * parameter is set to reference the next, locked page in the
+ * duplicate chain, if one exists. If there was no such page,
+ * then it is set to NULL.
+ *
+ * !!!
+ * __db_relink will set the dirty bit for us.
+ */
+ if ((ret = __db_relink(dbc, DB_REM_PAGE, pagep, pp, 0)) != 0)
+ return (ret);
+ if ((ret = freefunc(dbc, pagep)) != 0)
+ return (ret);
+ } else
+ (void)memp_fset(dbc->dbp->mpf, pagep, DB_MPOOL_DIRTY);
+
+ return (0);
+}
+
+/*
+ * __db_dend --
+ * Find the last page in a set of offpage duplicates.
+ *
+ * PUBLIC: int __db_dend __P((DBC *, db_pgno_t, PAGE **));
+ */
+int
+__db_dend(dbc, pgno, pp)
+ DBC *dbc;
+ db_pgno_t pgno;
+ PAGE **pp;
+{
+ DB *dbp;
+ PAGE *h;
+ int ret;
+
+ dbp = dbc->dbp;
+
+ /*
+ * This implements DB_KEYLAST. The last page is returned in pp; pgno
+ * should be the page number of the first page of the duplicate chain.
+ *
+ * *pp may be non-NULL -- if given a valid page use it.
+ */
+ if (*pp != NULL)
+ goto started;
+ for (;;) {
+ if ((ret = memp_fget(dbp->mpf, &pgno, 0, pp)) != 0) {
+ (void)__db_pgerr(dbp, pgno);
+ return (ret);
+ }
+started: h = *pp;
+
+ if ((pgno = NEXT_PGNO(h)) == PGNO_INVALID)
+ break;
+
+ if ((ret = memp_fput(dbp->mpf, h, 0)) != 0)
+ return (ret);
+ }
+ return (0);
+}
+
+/*
+ * __db_dsplit --
+ * Split a page of duplicates, calculating the split point based
+ * on an element of size "size" being added at "*indxp".
+ * On entry hp contains a pointer to the page-pointer of the original
+ * page. On exit, it returns a pointer to the page containing "*indxp"
+ * and "indxp" has been modified to reflect the index on the new page
+ * where the element should be added. The function returns with
+ * the page on which the insert should happen, not yet put.
+ */
+static int
+__db_dsplit(dbc, hp, indxp, size, newfunc)
+ DBC *dbc;
+ PAGE **hp;
+ db_indx_t *indxp;
+ u_int32_t size;
+ int (*newfunc) __P((DBC *, u_int32_t, PAGE **));
+{
+ PAGE *h, *np, *tp;
+ BKEYDATA *bk;
+ DBT page_dbt;
+ DB *dbp;
+ size_t pgsize;
+ db_indx_t halfbytes, i, indx, lastsum, nindex, oindex, s, sum;
+ int did_indx, ret, t_ret;
+
+ h = *hp;
+ indx = *indxp;
+ ret = 0;
+ dbp = dbc->dbp;
+ pgsize = dbp->pgsize;
+
+ /* Create a temporary page to do compaction onto. */
+ if ((ret = __os_malloc(pgsize, NULL, &tp)) != 0)
+ return (ret);
+
+ /* Create new page for the split. */
+ if ((ret = newfunc(dbc, P_DUPLICATE, &np)) != 0) {
+ __os_free(tp, pgsize);
+ return (ret);
+ }
+
+ P_INIT(np, pgsize, PGNO(np), PGNO(h), NEXT_PGNO(h), 0,
+ P_DUPLICATE);
+ P_INIT(tp, pgsize, PGNO(h), PREV_PGNO(h), PGNO(np), 0,
+ P_DUPLICATE);
+
+ /* Figure out the split point */
+ halfbytes = (pgsize - HOFFSET(h)) / 2;
+ did_indx = 0;
+ for (sum = 0, lastsum = 0, i = 0; i < NUM_ENT(h); i++) {
+ if (i == indx) {
+ sum += size;
+ did_indx = 1;
+ if (lastsum < halfbytes && sum >= halfbytes) {
+ /* We've crossed the halfway point. */
+ if ((db_indx_t)(halfbytes - lastsum) <
+ (db_indx_t)(sum - halfbytes)) {
+ *hp = np;
+ *indxp = 0;
+ } else
+ *indxp = i;
+ break;
+ }
+ *indxp = i;
+ lastsum = sum;
+ }
+ if (B_TYPE(GET_BKEYDATA(h, i)->type) == B_KEYDATA)
+ sum += BKEYDATA_SIZE(GET_BKEYDATA(h, i)->len);
+ else
+ sum += BOVERFLOW_SIZE;
+
+ if (lastsum < halfbytes && sum >= halfbytes) {
+ /* We've crossed the halfway point. */
+ if ((db_indx_t)(sum - halfbytes) <
+ (db_indx_t)(halfbytes - lastsum))
+ i++;
+ break;
+ }
+ }
+ /*
+ * Check if we have set the return values of the index pointer and
+ * page pointer.
+ */
+ if (!did_indx) {
+ *hp = np;
+ *indxp = indx - i;
+ }
+
+ if (DB_LOGGING(dbc)) {
+ page_dbt.size = dbp->pgsize;
+ page_dbt.data = h;
+ if ((ret = __db_split_log(dbp->dbenv->lg_info,
+ dbc->txn, &LSN(h), 0, DB_SPLITOLD, dbp->log_fileid,
+ PGNO(h), &page_dbt, &LSN(h))) != 0) {
+ __os_free(tp, pgsize);
+ return (ret);
+ }
+ LSN(tp) = LSN(h);
+ }
+
+ /*
+ * If it's a btree, adjust the cursors.
+ *
+ * i is the index of the first element to move onto the new page.
+ */
+ if (dbp->type == DB_BTREE)
+ __bam_ca_split(dbp, PGNO(h), PGNO(h), PGNO(np), i, 0);
+
+ for (nindex = 0, oindex = i; oindex < NUM_ENT(h); oindex++) {
+ bk = GET_BKEYDATA(h, oindex);
+ if (B_TYPE(bk->type) == B_KEYDATA)
+ s = BKEYDATA_SIZE(bk->len);
+ else
+ s = BOVERFLOW_SIZE;
+
+ np->inp[nindex++] = HOFFSET(np) -= s;
+ memcpy((u_int8_t *)np + HOFFSET(np), bk, s);
+ NUM_ENT(np)++;
+ }
+
+ /*
+ * Now do data compaction by copying the remaining stuff onto the
+ * temporary page and then copying it back to the real page.
+ */
+ for (nindex = 0, oindex = 0; oindex < i; oindex++) {
+ bk = GET_BKEYDATA(h, oindex);
+ if (B_TYPE(bk->type) == B_KEYDATA)
+ s = BKEYDATA_SIZE(bk->len);
+ else
+ s = BOVERFLOW_SIZE;
+
+ tp->inp[nindex++] = HOFFSET(tp) -= s;
+ memcpy((u_int8_t *)tp + HOFFSET(tp), bk, s);
+ NUM_ENT(tp)++;
+ }
+
+ /*
+ * This page (the temporary) should be only half full, so we do two
+ * memcpy's, one for the top of the page and one for the bottom of
+ * the page. This way we avoid copying the middle which should be
+ * about half a page.
+ */
+ memcpy(h, tp, LOFFSET(tp));
+ memcpy((u_int8_t *)h + HOFFSET(tp),
+ (u_int8_t *)tp + HOFFSET(tp), pgsize - HOFFSET(tp));
+ __os_free(tp, pgsize);
+
+ if (DB_LOGGING(dbc)) {
+ /*
+ * XXX
+ * If either of these fails, are we leaving pages pinned?
+ * Yes, but it seems like this happens in error case.
+ */
+ page_dbt.size = pgsize;
+ page_dbt.data = h;
+ if ((ret = __db_split_log(dbp->dbenv->lg_info,
+ dbc->txn, &LSN(h), 0, DB_SPLITNEW, dbp->log_fileid,
+ PGNO(h), &page_dbt, &LSN(h))) != 0)
+ return (ret);
+
+ page_dbt.size = pgsize;
+ page_dbt.data = np;
+ if ((ret = __db_split_log(dbp->dbenv->lg_info,
+ dbc->txn, &LSN(np), 0, DB_SPLITNEW, dbp->log_fileid,
+ PGNO(np), &page_dbt, &LSN(np))) != 0)
+ return (ret);
+ }
+
+ /*
+ * Finally, if there was a next page after the page being
+ * split, fix its prev pointer.
+ */
+ if (np->next_pgno != PGNO_INVALID)
+ ret = __db_relink(dbc, DB_ADD_PAGE, np, NULL, 1);
+
+ /*
+ * Figure out if the location we're interested in is on the new
+ * page, and if so, reset the callers' pointer. Push the other
+ * page back to the store.
+ */
+ if (*hp == h)
+ t_ret = memp_fput(dbp->mpf, np, DB_MPOOL_DIRTY);
+ else
+ t_ret = memp_fput(dbp->mpf, h, DB_MPOOL_DIRTY);
+
+ return (ret != 0 ? ret : t_ret);
+}
+
+/*
+ * __db_ditem --
+ * Remove an item from a page.
+ *
+ * PUBLIC: int __db_ditem __P((DBC *, PAGE *, u_int32_t, u_int32_t));
+ */
+int
+__db_ditem(dbc, pagep, indx, nbytes)
+ DBC *dbc;
+ PAGE *pagep;
+ u_int32_t indx, nbytes;
+{
+ DB *dbp;
+ DBT ldbt;
+ db_indx_t cnt, offset;
+ int ret;
+ u_int8_t *from;
+
+ dbp = dbc->dbp;
+ if (DB_LOGGING(dbc)) {
+ ldbt.data = P_ENTRY(pagep, indx);
+ ldbt.size = nbytes;
+ if ((ret = __db_addrem_log(dbp->dbenv->lg_info, dbc->txn,
+ &LSN(pagep), 0, DB_REM_DUP, dbp->log_fileid, PGNO(pagep),
+ (u_int32_t)indx, nbytes, &ldbt, NULL, &LSN(pagep))) != 0)
+ return (ret);
+ }
+
+ /*
+ * If there's only a single item on the page, we don't have to
+ * work hard.
+ */
+ if (NUM_ENT(pagep) == 1) {
+ NUM_ENT(pagep) = 0;
+ HOFFSET(pagep) = dbp->pgsize;
+ return (0);
+ }
+
+ /*
+ * Pack the remaining key/data items at the end of the page. Use
+ * memmove(3), the regions may overlap.
+ */
+ from = (u_int8_t *)pagep + HOFFSET(pagep);
+ memmove(from + nbytes, from, pagep->inp[indx] - HOFFSET(pagep));
+ HOFFSET(pagep) += nbytes;
+
+ /* Adjust the indices' offsets. */
+ offset = pagep->inp[indx];
+ for (cnt = 0; cnt < NUM_ENT(pagep); ++cnt)
+ if (pagep->inp[cnt] < offset)
+ pagep->inp[cnt] += nbytes;
+
+ /* Shift the indices down. */
+ --NUM_ENT(pagep);
+ if (indx != NUM_ENT(pagep))
+ memmove(&pagep->inp[indx], &pagep->inp[indx + 1],
+ sizeof(db_indx_t) * (NUM_ENT(pagep) - indx));
+
+ /* If it's a btree, adjust the cursors. */
+ if (dbp->type == DB_BTREE)
+ __bam_ca_di(dbp, PGNO(pagep), indx, -1);
+
+ return (0);
+}
+
+/*
+ * __db_pitem --
+ * Put an item on a page.
+ *
+ * PUBLIC: int __db_pitem
+ * PUBLIC: __P((DBC *, PAGE *, u_int32_t, u_int32_t, DBT *, DBT *));
+ */
+int
+__db_pitem(dbc, pagep, indx, nbytes, hdr, data)
+ DBC *dbc;
+ PAGE *pagep;
+ u_int32_t indx;
+ u_int32_t nbytes;
+ DBT *hdr, *data;
+{
+ DB *dbp;
+ BKEYDATA bk;
+ DBT thdr;
+ int ret;
+ u_int8_t *p;
+
+ /*
+ * Put a single item onto a page. The logic figuring out where to
+ * insert and whether it fits is handled in the caller. All we do
+ * here is manage the page shuffling. We cheat a little bit in that
+ * we don't want to copy the dbt on a normal put twice. If hdr is
+ * NULL, we create a BKEYDATA structure on the page, otherwise, just
+ * copy the caller's information onto the page.
+ *
+ * This routine is also used to put entries onto the page where the
+ * entry is pre-built, e.g., during recovery. In this case, the hdr
+ * will point to the entry, and the data argument will be NULL.
+ *
+ * !!!
+ * There's a tremendous potential for off-by-one errors here, since
+ * the passed in header sizes must be adjusted for the structure's
+ * placeholder for the trailing variable-length data field.
+ */
+ dbp = dbc->dbp;
+ if (DB_LOGGING(dbc))
+ if ((ret = __db_addrem_log(dbp->dbenv->lg_info, dbc->txn,
+ &LSN(pagep), 0, DB_ADD_DUP, dbp->log_fileid, PGNO(pagep),
+ (u_int32_t)indx, nbytes, hdr, data, &LSN(pagep))) != 0)
+ return (ret);
+
+ if (hdr == NULL) {
+ B_TSET(bk.type, B_KEYDATA, 0);
+ bk.len = data == NULL ? 0 : data->size;
+
+ thdr.data = &bk;
+ thdr.size = SSZA(BKEYDATA, data);
+ hdr = &thdr;
+ }
+
+ /* Adjust the index table, then put the item on the page. */
+ if (indx != NUM_ENT(pagep))
+ memmove(&pagep->inp[indx + 1], &pagep->inp[indx],
+ sizeof(db_indx_t) * (NUM_ENT(pagep) - indx));
+ HOFFSET(pagep) -= nbytes;
+ pagep->inp[indx] = HOFFSET(pagep);
+ ++NUM_ENT(pagep);
+
+ p = P_ENTRY(pagep, indx);
+ memcpy(p, hdr->data, hdr->size);
+ if (data != NULL)
+ memcpy(p + hdr->size, data->data, data->size);
+
+ /* If it's a btree, adjust the cursors. */
+ if (dbp->type == DB_BTREE)
+ __bam_ca_di(dbp, PGNO(pagep), indx, 1);
+
+ return (0);
+}
+
+/*
+ * __db_relink --
+ * Relink around a deleted page.
+ *
+ * PUBLIC: int __db_relink __P((DBC *, u_int32_t, PAGE *, PAGE **, int));
+ */
+int
+__db_relink(dbc, add_rem, pagep, new_next, needlock)
+ DBC *dbc;
+ u_int32_t add_rem;
+ PAGE *pagep, **new_next;
+ int needlock;
+{
+ DB *dbp;
+ PAGE *np, *pp;
+ DB_LOCK npl, ppl;
+ DB_LSN *nlsnp, *plsnp;
+ int ret;
+
+ ret = 0;
+ np = pp = NULL;
+ npl = ppl = LOCK_INVALID;
+ nlsnp = plsnp = NULL;
+ dbp = dbc->dbp;
+
+ /*
+ * Retrieve and lock the one/two pages. For a remove, we may need
+ * two pages (the before and after). For an add, we only need one
+ * because, the split took care of the prev.
+ */
+ if (pagep->next_pgno != PGNO_INVALID) {
+ if (needlock && (ret = __bam_lget(dbc,
+ 0, pagep->next_pgno, DB_LOCK_WRITE, &npl)) != 0)
+ goto err;
+ if ((ret = memp_fget(dbp->mpf,
+ &pagep->next_pgno, 0, &np)) != 0) {
+ (void)__db_pgerr(dbp, pagep->next_pgno);
+ goto err;
+ }
+ nlsnp = &np->lsn;
+ }
+ if (add_rem == DB_REM_PAGE && pagep->prev_pgno != PGNO_INVALID) {
+ if (needlock && (ret = __bam_lget(dbc,
+ 0, pagep->prev_pgno, DB_LOCK_WRITE, &ppl)) != 0)
+ goto err;
+ if ((ret = memp_fget(dbp->mpf,
+ &pagep->prev_pgno, 0, &pp)) != 0) {
+ (void)__db_pgerr(dbp, pagep->next_pgno);
+ goto err;
+ }
+ plsnp = &pp->lsn;
+ }
+
+ /* Log the change. */
+ if (DB_LOGGING(dbc)) {
+ if ((ret = __db_relink_log(dbp->dbenv->lg_info, dbc->txn,
+ &pagep->lsn, 0, add_rem, dbp->log_fileid,
+ pagep->pgno, &pagep->lsn,
+ pagep->prev_pgno, plsnp, pagep->next_pgno, nlsnp)) != 0)
+ goto err;
+ if (np != NULL)
+ np->lsn = pagep->lsn;
+ if (pp != NULL)
+ pp->lsn = pagep->lsn;
+ }
+
+ /*
+ * Modify and release the two pages.
+ *
+ * !!!
+ * The parameter new_next gets set to the page following the page we
+ * are removing. If there is no following page, then new_next gets
+ * set to NULL.
+ */
+ if (np != NULL) {
+ if (add_rem == DB_ADD_PAGE)
+ np->prev_pgno = pagep->pgno;
+ else
+ np->prev_pgno = pagep->prev_pgno;
+ if (new_next == NULL)
+ ret = memp_fput(dbp->mpf, np, DB_MPOOL_DIRTY);
+ else {
+ *new_next = np;
+ ret = memp_fset(dbp->mpf, np, DB_MPOOL_DIRTY);
+ }
+ if (ret != 0)
+ goto err;
+ if (needlock)
+ (void)__bam_lput(dbc, npl);
+ } else if (new_next != NULL)
+ *new_next = NULL;
+
+ if (pp != NULL) {
+ pp->next_pgno = pagep->next_pgno;
+ if ((ret = memp_fput(dbp->mpf, pp, DB_MPOOL_DIRTY)) != 0)
+ goto err;
+ if (needlock)
+ (void)__bam_lput(dbc, ppl);
+ }
+ return (0);
+
+err: if (np != NULL)
+ (void)memp_fput(dbp->mpf, np, 0);
+ if (needlock && npl != LOCK_INVALID)
+ (void)__bam_lput(dbc, npl);
+ if (pp != NULL)
+ (void)memp_fput(dbp->mpf, pp, 0);
+ if (needlock && ppl != LOCK_INVALID)
+ (void)__bam_lput(dbc, ppl);
+ return (ret);
+}
+
+/*
+ * __db_ddup --
+ * Delete an offpage chain of duplicates.
+ *
+ * PUBLIC: int __db_ddup __P((DBC *, db_pgno_t, int (*)(DBC *, PAGE *)));
+ */
+int
+__db_ddup(dbc, pgno, freefunc)
+ DBC *dbc;
+ db_pgno_t pgno;
+ int (*freefunc) __P((DBC *, PAGE *));
+{
+ DB *dbp;
+ PAGE *pagep;
+ DBT tmp_dbt;
+ int ret;
+
+ dbp = dbc->dbp;
+ do {
+ if ((ret = memp_fget(dbp->mpf, &pgno, 0, &pagep)) != 0) {
+ (void)__db_pgerr(dbp, pgno);
+ return (ret);
+ }
+
+ if (DB_LOGGING(dbc)) {
+ tmp_dbt.data = pagep;
+ tmp_dbt.size = dbp->pgsize;
+ if ((ret = __db_split_log(dbp->dbenv->lg_info,
+ dbc->txn, &LSN(pagep), 0, DB_SPLITOLD,
+ dbp->log_fileid, PGNO(pagep), &tmp_dbt,
+ &LSN(pagep))) != 0)
+ return (ret);
+ }
+ pgno = pagep->next_pgno;
+ if ((ret = freefunc(dbc, pagep)) != 0)
+ return (ret);
+ } while (pgno != PGNO_INVALID);
+
+ return (0);
+}
+
+/*
+ * __db_addpage --
+ * Create a new page and link it onto the next_pgno field of the
+ * current page.
+ */
+static int
+__db_addpage(dbc, hp, indxp, newfunc)
+ DBC *dbc;
+ PAGE **hp;
+ db_indx_t *indxp;
+ int (*newfunc) __P((DBC *, u_int32_t, PAGE **));
+{
+ DB *dbp;
+ PAGE *newpage;
+ int ret;
+
+ dbp = dbc->dbp;
+ if ((ret = newfunc(dbc, P_DUPLICATE, &newpage)) != 0)
+ return (ret);
+
+ if (DB_LOGGING(dbc)) {
+ if ((ret = __db_addpage_log(dbp->dbenv->lg_info,
+ dbc->txn, &LSN(*hp), 0, dbp->log_fileid,
+ PGNO(*hp), &LSN(*hp), PGNO(newpage), &LSN(newpage))) != 0) {
+ return (ret);
+ }
+ LSN(newpage) = LSN(*hp);
+ }
+
+ PREV_PGNO(newpage) = PGNO(*hp);
+ NEXT_PGNO(*hp) = PGNO(newpage);
+
+ if ((ret = memp_fput(dbp->mpf, *hp, DB_MPOOL_DIRTY)) != 0)
+ return (ret);
+ *hp = newpage;
+ *indxp = 0;
+ return (0);
+}
+
+/*
+ * __db_dsearch --
+ * Search a set of duplicates for the proper position for a new duplicate.
+ *
+ * + pgno is the page number of the page on which to begin searching.
+ * Since we can continue duplicate searches, it might not be the first
+ * page.
+ *
+ * + If we are continuing a search, then *pp may be non-NULL in which
+ * case we do not have to retrieve the page.
+ *
+ * + If we are continuing a search, then *indxp contains the first
+ * on pgno of where we should begin the search.
+ *
+ * NOTE: if there is no comparison function, then continuing is
+ * meaningless, and *pp should always be NULL and *indxp will be
+ * ignored.
+ *
+ * 3 return values::
+ *
+ * + pp is the returned page pointer of where this element should go.
+ * + indxp is the returned index on that page
+ * + cmpp is the returned final comparison result.
+ *
+ * PUBLIC: int __db_dsearch __P((DBC *,
+ * PUBLIC: int, DBT *, db_pgno_t, db_indx_t *, PAGE **, int *));
+ */
+int
+__db_dsearch(dbc, is_insert, dbt, pgno, indxp, pp, cmpp)
+ DBC *dbc;
+ int is_insert, *cmpp;
+ DBT *dbt;
+ db_pgno_t pgno;
+ db_indx_t *indxp;
+ PAGE **pp;
+{
+ DB *dbp;
+ PAGE *h;
+ db_indx_t base, indx, lim, save_indx;
+ db_pgno_t save_pgno;
+ int ret;
+
+ dbp = dbc->dbp;
+
+ if (dbp->dup_compare == NULL) {
+ /*
+ * We may have been given a valid page, but we may not be
+ * able to use it. The problem is that the application is
+ * doing a join and we're trying to continue the search,
+ * but since the items aren't sorted, we can't. Discard
+ * the page if it's not the one we're going to start with
+ * anyway.
+ */
+ if (*pp != NULL && (*pp)->pgno != pgno) {
+ if ((ret = memp_fput(dbp->mpf, *pp, 0)) != 0)
+ return (ret);
+ *pp = NULL;
+ }
+
+ /*
+ * If no duplicate function is specified, just go to the end
+ * of the duplicate set.
+ */
+ if (is_insert) {
+ if ((ret = __db_dend(dbc, pgno, pp)) != 0)
+ return (ret);
+ *indxp = NUM_ENT(*pp);
+ return (0);
+ }
+
+ /*
+ * We are looking for a specific duplicate, so do a linear
+ * search.
+ */
+ if (*pp != NULL)
+ goto nocmp_started;
+ for (;;) {
+ if ((ret = memp_fget(dbp->mpf, &pgno, 0, pp)) != 0)
+ goto pg_err;
+nocmp_started: h = *pp;
+
+ for (*indxp = 0; *indxp < NUM_ENT(h); ++*indxp) {
+ if ((*cmpp = __bam_cmp(dbp,
+ dbt, h, *indxp, __bam_defcmp)) != 0)
+ continue;
+ /*
+ * The duplicate may have already been deleted,
+ * if it's a btree page, in which case we skip
+ * it.
+ */
+ if (dbp->type == DB_BTREE &&
+ B_DISSET(GET_BKEYDATA(h, *indxp)->type))
+ continue;
+
+ return (0);
+ }
+
+ if ((pgno = h->next_pgno) == PGNO_INVALID)
+ break;
+
+ if ((ret = memp_fput(dbp->mpf, h, 0)) != 0)
+ return (ret);
+ }
+ *cmpp = 1; /* We didn't succeed... */
+ return (0);
+ }
+
+ /*
+ * We have a comparison routine, i.e., the duplicates are sorted.
+ * Walk through the chain of duplicates, checking the last entry
+ * on each page to decide if it's the page we want to search.
+ *
+ * *pp may be non-NULL -- if we were given a valid page (e.g., are
+ * in mid-search), then use the provided page.
+ */
+ if (*pp != NULL)
+ goto cmp_started;
+ for (;;) {
+ if ((ret = memp_fget(dbp->mpf, &pgno, 0, pp)) != 0)
+ goto pg_err;
+cmp_started: h = *pp;
+
+ if ((pgno = h->next_pgno) == PGNO_INVALID || __bam_cmp(dbp,
+ dbt, h, h->entries - 1, dbp->dup_compare) <= 0)
+ break;
+ /*
+ * Even when continuing a search, make sure we don't skip
+ * entries on a new page
+ */
+ *indxp = 0;
+
+ if ((ret = memp_fput(dbp->mpf, h, 0)) != 0)
+ return (ret);
+ }
+
+ /* Next, do a binary search on the page. */
+ base = F_ISSET(dbc, DBC_CONTINUE) ? *indxp : 0;
+ for (lim = NUM_ENT(h) - base; lim != 0; lim >>= 1) {
+ indx = base + (lim >> 1);
+ if ((*cmpp = __bam_cmp(dbp,
+ dbt, h, indx, dbp->dup_compare)) == 0) {
+ *indxp = indx;
+
+ if (dbp->type != DB_BTREE ||
+ !B_DISSET(GET_BKEYDATA(h, *indxp)->type))
+ return (0);
+ goto check_delete;
+ }
+ if (*cmpp > 0) {
+ base = indx + 1;
+ lim--;
+ }
+ }
+
+ /*
+ * Base references the smallest index larger than the supplied DBT's
+ * data item, potentially both 0 and NUM_ENT.
+ */
+ *indxp = base;
+ return (0);
+
+check_delete:
+ /*
+ * The duplicate may have already been deleted, if it's a btree page,
+ * in which case we wander around, hoping to find an entry that hasn't
+ * been deleted. First, wander in a forwardly direction.
+ */
+ save_pgno = (*pp)->pgno;
+ save_indx = *indxp;
+ for (++*indxp;;) {
+ for (; *indxp < NUM_ENT(h); ++*indxp) {
+ if ((*cmpp = __bam_cmp(dbp,
+ dbt, h, *indxp, dbp->dup_compare)) != 0)
+ goto check_delete_rev;
+
+ if (!B_DISSET(GET_BKEYDATA(h, *indxp)->type))
+ return (0);
+ }
+ if ((pgno = h->next_pgno) == PGNO_INVALID)
+ break;
+
+ if ((ret = memp_fput(dbp->mpf, h, 0)) != 0)
+ return (ret);
+
+ if ((ret = memp_fget(dbp->mpf, &pgno, 0, pp)) != 0)
+ goto pg_err;
+ h = *pp;
+
+ *indxp = 0;
+ }
+
+check_delete_rev:
+ /* Go back to where we started, and wander in a backwardly direction. */
+ if (h->pgno != save_pgno) {
+ if ((ret = memp_fput(dbp->mpf, h, 0)) != 0)
+ return (ret);
+ if ((ret = memp_fget(dbp->mpf, &save_pgno, 0, pp)) != 0)
+ goto pg_err;
+ h = *pp;
+ }
+
+ for (;;) {
+ while (*indxp > 0) {
+ --*indxp;
+ if ((*cmpp = __bam_cmp(dbp,
+ dbt, h, *indxp, dbp->dup_compare)) != 0)
+ goto check_delete_fail;
+
+ if (!B_DISSET(GET_BKEYDATA(h, *indxp)->type))
+ return (0);
+ }
+ if ((pgno = h->prev_pgno) == PGNO_INVALID)
+ break;
+
+ if ((ret = memp_fput(dbp->mpf, h, 0)) != 0)
+ return (ret);
+
+ if ((ret = memp_fget(dbp->mpf, &pgno, 0, pp)) != 0)
+ goto pg_err;
+ h = *pp;
+
+ *indxp = NUM_ENT(h);
+ }
+
+check_delete_fail:
+ *cmpp = 1; /* We didn't succeed... */
+ return (0);
+
+pg_err: __db_pgerr(dbp, pgno);
+ return (ret);
+}
diff --git a/usr/src/cmd/sendmail/db/db/db_err.c b/usr/src/cmd/sendmail/db/db/db_err.c
new file mode 100644
index 0000000000..e935ddfcc5
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/db/db_err.c
@@ -0,0 +1,211 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)db_err.c 10.42 (Sleepycat) 11/24/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+#ifdef __STDC__
+#include <stdarg.h>
+#else
+#include <varargs.h>
+#endif
+#endif
+
+#include "db_int.h"
+#include "shqueue.h"
+#include "db_shash.h"
+#include "lock.h"
+#include "lock_ext.h"
+#include "log.h"
+#include "log_ext.h"
+#include "mp.h"
+#include "mp_ext.h"
+#include "txn.h"
+#include "txn_ext.h"
+#include "common_ext.h"
+#include "clib_ext.h"
+
+/*
+ * __db_fchk --
+ * General flags checking routine.
+ *
+ * PUBLIC: int __db_fchk __P((DB_ENV *, const char *, u_int32_t, u_int32_t));
+ */
+int
+__db_fchk(dbenv, name, flags, ok_flags)
+ DB_ENV *dbenv;
+ const char *name;
+ u_int32_t flags, ok_flags;
+{
+ return (flags & ~ok_flags ? __db_ferr(dbenv, name, 0) : 0);
+}
+
+/*
+ * __db_fcchk --
+ * General combination flags checking routine.
+ *
+ * PUBLIC: int __db_fcchk
+ * PUBLIC: __P((DB_ENV *, const char *, u_int32_t, u_int32_t, u_int32_t));
+ */
+int
+__db_fcchk(dbenv, name, flags, flag1, flag2)
+ DB_ENV *dbenv;
+ const char *name;
+ u_int32_t flags, flag1, flag2;
+{
+ return ((flags & flag1) &&
+ (flags & flag2) ? __db_ferr(dbenv, name, 1) : 0);
+}
+
+/*
+ * __db_ferr --
+ * Common flag errors.
+ *
+ * PUBLIC: int __db_ferr __P((const DB_ENV *, const char *, int));
+ */
+int
+__db_ferr(dbenv, name, iscombo)
+ const DB_ENV *dbenv;
+ const char *name;
+ int iscombo;
+{
+ __db_err(dbenv, "illegal flag %sspecified to %s",
+ iscombo ? "combination " : "", name);
+ return (EINVAL);
+}
+
+/*
+ * __db_err --
+ * Standard DB error routine.
+ *
+ * PUBLIC: #ifdef __STDC__
+ * PUBLIC: void __db_err __P((const DB_ENV *dbenv, const char *fmt, ...));
+ * PUBLIC: #else
+ * PUBLIC: void __db_err();
+ * PUBLIC: #endif
+ */
+void
+#ifdef __STDC__
+__db_err(const DB_ENV *dbenv, const char *fmt, ...)
+#else
+__db_err(dbenv, fmt, va_alist)
+ const DB_ENV *dbenv;
+ const char *fmt;
+ va_dcl
+#endif
+{
+ va_list ap;
+ char errbuf[2048]; /* XXX: END OF THE STACK DON'T TRUST SPRINTF. */
+
+ if (dbenv == NULL)
+ return;
+
+ if (dbenv->db_errcall != NULL) {
+#ifdef __STDC__
+ va_start(ap, fmt);
+#else
+ va_start(ap);
+#endif
+ (void)vsnprintf(errbuf, sizeof(errbuf), fmt, ap);
+ dbenv->db_errcall(dbenv->db_errpfx, errbuf);
+ va_end(ap);
+ }
+ if (dbenv->db_errfile != NULL) {
+ if (dbenv->db_errpfx != NULL)
+ (void)fprintf(dbenv->db_errfile, "%s: ",
+ dbenv->db_errpfx);
+#ifdef __STDC__
+ va_start(ap, fmt);
+#else
+ va_start(ap);
+#endif
+ (void)vfprintf(dbenv->db_errfile, fmt, ap);
+ (void)fprintf(dbenv->db_errfile, "\n");
+ (void)fflush(dbenv->db_errfile);
+ va_end(ap);
+ }
+}
+
+/*
+ * __db_pgerr --
+ * Error when unable to retrieve a specified page.
+ *
+ * PUBLIC: int __db_pgerr __P((DB *, db_pgno_t));
+ */
+int
+__db_pgerr(dbp, pgno)
+ DB *dbp;
+ db_pgno_t pgno;
+{
+ /*
+ * Three things are certain:
+ * Death, taxes, and lost data.
+ * Guess which has occurred.
+ */
+ __db_err(dbp->dbenv,
+ "unable to create/retrieve page %lu", (u_long)pgno);
+ return (__db_panic(dbp->dbenv, EIO));
+}
+
+/*
+ * __db_pgfmt --
+ * Error when a page has the wrong format.
+ *
+ * PUBLIC: int __db_pgfmt __P((DB *, db_pgno_t));
+ */
+int
+__db_pgfmt(dbp, pgno)
+ DB *dbp;
+ db_pgno_t pgno;
+{
+ __db_err(dbp->dbenv,
+ "page %lu: illegal page type or format", (u_long)pgno);
+ return (__db_panic(dbp->dbenv, EINVAL));
+}
+
+/*
+ * __db_panic --
+ * Lock out the tree due to unrecoverable error.
+ *
+ * PUBLIC: int __db_panic __P((DB_ENV *, int));
+ */
+int
+__db_panic(dbenv, errval)
+ DB_ENV *dbenv;
+ int errval;
+{
+ if (dbenv != NULL) {
+ dbenv->db_panic = errval;
+
+ (void)__log_panic(dbenv);
+ (void)__memp_panic(dbenv);
+ (void)__lock_panic(dbenv);
+ (void)__txn_panic(dbenv);
+
+ __db_err(dbenv, "PANIC: %s", strerror(errval));
+
+ if (dbenv->db_paniccall != NULL)
+ dbenv->db_paniccall(dbenv, errval);
+ }
+
+ /*
+ * Chaos reigns within.
+ * Reflect, repent, and reboot.
+ * Order shall return.
+ */
+ return (DB_RUNRECOVERY);
+}
diff --git a/usr/src/cmd/sendmail/db/db/db_iface.c b/usr/src/cmd/sendmail/db/db/db_iface.c
new file mode 100644
index 0000000000..7303b557c2
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/db/db_iface.c
@@ -0,0 +1,490 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)db_iface.c 10.40 (Sleepycat) 12/19/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <errno.h>
+#endif
+
+#include "db_int.h"
+#include "db_page.h"
+#include "db_auto.h"
+#include "db_ext.h"
+#include "common_ext.h"
+
+static int __db_keyempty __P((const DB_ENV *));
+static int __db_rdonly __P((const DB_ENV *, const char *));
+static int __dbt_ferr __P((const DB *, const char *, const DBT *, int));
+
+/*
+ * __db_cdelchk --
+ * Common cursor delete argument checking routine.
+ *
+ * PUBLIC: int __db_cdelchk __P((const DB *, u_int32_t, int, int));
+ */
+int
+__db_cdelchk(dbp, flags, isrdonly, isvalid)
+ const DB *dbp;
+ u_int32_t flags;
+ int isrdonly, isvalid;
+{
+ /* Check for changes to a read-only tree. */
+ if (isrdonly)
+ return (__db_rdonly(dbp->dbenv, "c_del"));
+
+ /* Check for invalid function flags. */
+ switch (flags) {
+ case 0:
+ break;
+ default:
+ return (__db_ferr(dbp->dbenv, "DBcursor->c_del", 0));
+ }
+
+ /*
+ * The cursor must be initialized, return -1 for an invalid cursor,
+ * otherwise 0.
+ */
+ return (isvalid ? 0 : EINVAL);
+}
+
+/*
+ * __db_cgetchk --
+ * Common cursor get argument checking routine.
+ *
+ * PUBLIC: int __db_cgetchk __P((const DB *, DBT *, DBT *, u_int32_t, int));
+ */
+int
+__db_cgetchk(dbp, key, data, flags, isvalid)
+ const DB *dbp;
+ DBT *key, *data;
+ u_int32_t flags;
+ int isvalid;
+{
+ int key_einval, key_flags, ret;
+
+ key_einval = key_flags = 0;
+
+ /* Check for invalid function flags. */
+ LF_CLR(DB_RMW);
+ switch (flags) {
+ case DB_NEXT_DUP:
+ if (dbp->type == DB_RECNO)
+ goto err;
+ /* FALLTHROUGH */
+ case DB_CURRENT:
+ case DB_FIRST:
+ case DB_LAST:
+ case DB_NEXT:
+ case DB_PREV:
+ key_flags = 1;
+ break;
+ case DB_GET_BOTH:
+ case DB_SET_RANGE:
+ key_einval = key_flags = 1;
+ break;
+ case DB_SET:
+ key_einval = 1;
+ break;
+ case DB_GET_RECNO:
+ if (!F_ISSET(dbp, DB_BT_RECNUM))
+ goto err;
+ break;
+ case DB_SET_RECNO:
+ if (!F_ISSET(dbp, DB_BT_RECNUM))
+ goto err;
+ key_einval = key_flags = 1;
+ break;
+ default:
+err: return (__db_ferr(dbp->dbenv, "DBcursor->c_get", 0));
+ }
+
+ /* Check for invalid key/data flags. */
+ if ((ret = __dbt_ferr(dbp, "key", key, 0)) != 0)
+ return (ret);
+ if ((ret = __dbt_ferr(dbp, "data", data, 0)) != 0)
+ return (ret);
+
+ /* Check for missing keys. */
+ if (key_einval && (key->data == NULL || key->size == 0))
+ return (__db_keyempty(dbp->dbenv));
+
+ /*
+ * The cursor must be initialized for DB_CURRENT, return -1 for an
+ * invalid cursor, otherwise 0.
+ */
+ return (isvalid || flags != DB_CURRENT ? 0 : EINVAL);
+}
+
+/*
+ * __db_cputchk --
+ * Common cursor put argument checking routine.
+ *
+ * PUBLIC: int __db_cputchk __P((const DB *,
+ * PUBLIC: const DBT *, DBT *, u_int32_t, int, int));
+ */
+int
+__db_cputchk(dbp, key, data, flags, isrdonly, isvalid)
+ const DB *dbp;
+ const DBT *key;
+ DBT *data;
+ u_int32_t flags;
+ int isrdonly, isvalid;
+{
+ int key_einval, key_flags, ret;
+
+ key_einval = key_flags = 0;
+
+ /* Check for changes to a read-only tree. */
+ if (isrdonly)
+ return (__db_rdonly(dbp->dbenv, "c_put"));
+
+ /* Check for invalid function flags. */
+ switch (flags) {
+ case DB_AFTER:
+ case DB_BEFORE:
+ if (dbp->dup_compare != NULL)
+ goto err;
+ if (dbp->type == DB_RECNO && !F_ISSET(dbp, DB_RE_RENUMBER))
+ goto err;
+ if (dbp->type != DB_RECNO && !F_ISSET(dbp, DB_AM_DUP))
+ goto err;
+ break;
+ case DB_CURRENT:
+ /*
+ * If there is a comparison function, doing a DB_CURRENT
+ * must not change the part of the data item that is used
+ * for the comparison.
+ */
+ break;
+ case DB_KEYFIRST:
+ case DB_KEYLAST:
+ if (dbp->type == DB_RECNO)
+ goto err;
+ key_einval = key_flags = 1;
+ break;
+ default:
+err: return (__db_ferr(dbp->dbenv, "DBcursor->c_put", 0));
+ }
+
+ /* Check for invalid key/data flags. */
+ if (key_flags && (ret = __dbt_ferr(dbp, "key", key, 0)) != 0)
+ return (ret);
+ if ((ret = __dbt_ferr(dbp, "data", data, 0)) != 0)
+ return (ret);
+
+ /* Check for missing keys. */
+ if (key_einval && (key->data == NULL || key->size == 0))
+ return (__db_keyempty(dbp->dbenv));
+
+ /*
+ * The cursor must be initialized for anything other than DB_KEYFIRST
+ * and DB_KEYLAST, return -1 for an invalid cursor, otherwise 0.
+ */
+ return (isvalid ||
+ flags == DB_KEYFIRST || flags == DB_KEYLAST ? 0 : EINVAL);
+}
+
+/*
+ * __db_closechk --
+ * DB->close flag check.
+ *
+ * PUBLIC: int __db_closechk __P((const DB *, u_int32_t));
+ */
+int
+__db_closechk(dbp, flags)
+ const DB *dbp;
+ u_int32_t flags;
+{
+ /* Check for invalid function flags. */
+ if (flags != 0 && flags != DB_NOSYNC)
+ return (__db_ferr(dbp->dbenv, "DB->close", 0));
+
+ return (0);
+}
+
+/*
+ * __db_delchk --
+ * Common delete argument checking routine.
+ *
+ * PUBLIC: int __db_delchk __P((const DB *, DBT *, u_int32_t, int));
+ */
+int
+__db_delchk(dbp, key, flags, isrdonly)
+ const DB *dbp;
+ DBT *key;
+ u_int32_t flags;
+ int isrdonly;
+{
+ /* Check for changes to a read-only tree. */
+ if (isrdonly)
+ return (__db_rdonly(dbp->dbenv, "delete"));
+
+ /* Check for invalid function flags. */
+ switch (flags) {
+ case 0:
+ break;
+ default:
+ return (__db_ferr(dbp->dbenv, "DB->del", 0));
+ }
+
+ /* Check for missing keys. */
+ if (key->data == NULL || key->size == 0)
+ return (__db_keyempty(dbp->dbenv));
+
+ return (0);
+}
+
+/*
+ * __db_getchk --
+ * Common get argument checking routine.
+ *
+ * PUBLIC: int __db_getchk __P((const DB *, const DBT *, DBT *, u_int32_t));
+ */
+int
+__db_getchk(dbp, key, data, flags)
+ const DB *dbp;
+ const DBT *key;
+ DBT *data;
+ u_int32_t flags;
+{
+ int ret;
+
+ /* Check for invalid function flags. */
+ LF_CLR(DB_RMW);
+ switch (flags) {
+ case 0:
+ case DB_GET_BOTH:
+ break;
+ case DB_SET_RECNO:
+ if (!F_ISSET(dbp, DB_BT_RECNUM))
+ goto err;
+ break;
+ default:
+err: return (__db_ferr(dbp->dbenv, "DB->get", 0));
+ }
+
+ /* Check for invalid key/data flags. */
+ if ((ret = __dbt_ferr(dbp, "key", key, flags == DB_SET_RECNO)) != 0)
+ return (ret);
+ if ((ret = __dbt_ferr(dbp, "data", data, 1)) != 0)
+ return (ret);
+
+ /* Check for missing keys. */
+ if (key->data == NULL || key->size == 0)
+ return (__db_keyempty(dbp->dbenv));
+
+ return (0);
+}
+
+/*
+ * __db_joinchk --
+ * Common join argument checking routine.
+ *
+ * PUBLIC: int __db_joinchk __P((const DB *, u_int32_t));
+ */
+int
+__db_joinchk(dbp, flags)
+ const DB *dbp;
+ u_int32_t flags;
+{
+ if (flags != 0)
+ return (__db_ferr(dbp->dbenv, "DB->join", 0));
+
+ return (0);
+}
+
+/*
+ * __db_putchk --
+ * Common put argument checking routine.
+ *
+ * PUBLIC: int __db_putchk
+ * PUBLIC: __P((const DB *, DBT *, const DBT *, u_int32_t, int, int));
+ */
+int
+__db_putchk(dbp, key, data, flags, isrdonly, isdup)
+ const DB *dbp;
+ DBT *key;
+ const DBT *data;
+ u_int32_t flags;
+ int isrdonly, isdup;
+{
+ int ret;
+
+ /* Check for changes to a read-only tree. */
+ if (isrdonly)
+ return (__db_rdonly(dbp->dbenv, "put"));
+
+ /* Check for invalid function flags. */
+ switch (flags) {
+ case 0:
+ case DB_NOOVERWRITE:
+ break;
+ case DB_APPEND:
+ if (dbp->type != DB_RECNO)
+ goto err;
+ break;
+ default:
+err: return (__db_ferr(dbp->dbenv, "DB->put", 0));
+ }
+
+ /* Check for invalid key/data flags. */
+ if ((ret = __dbt_ferr(dbp, "key", key, 0)) != 0)
+ return (ret);
+ if ((ret = __dbt_ferr(dbp, "data", data, 0)) != 0)
+ return (ret);
+
+ /* Check for missing keys. */
+ if (key->data == NULL || key->size == 0)
+ return (__db_keyempty(dbp->dbenv));
+
+ /* Check for partial puts in the presence of duplicates. */
+ if (isdup && F_ISSET(data, DB_DBT_PARTIAL)) {
+ __db_err(dbp->dbenv,
+"a partial put in the presence of duplicates requires a cursor operation");
+ return (EINVAL);
+ }
+
+ return (0);
+}
+
+/*
+ * __db_statchk --
+ * Common stat argument checking routine.
+ *
+ * PUBLIC: int __db_statchk __P((const DB *, u_int32_t));
+ */
+int
+__db_statchk(dbp, flags)
+ const DB *dbp;
+ u_int32_t flags;
+{
+ /* Check for invalid function flags. */
+ switch (flags) {
+ case 0:
+ break;
+ case DB_RECORDCOUNT:
+ if (dbp->type == DB_RECNO)
+ break;
+ if (dbp->type == DB_BTREE && F_ISSET(dbp, DB_BT_RECNUM))
+ break;
+ goto err;
+ default:
+err: return (__db_ferr(dbp->dbenv, "DB->stat", 0));
+ }
+
+ return (0);
+}
+
+/*
+ * __db_syncchk --
+ * Common sync argument checking routine.
+ *
+ * PUBLIC: int __db_syncchk __P((const DB *, u_int32_t));
+ */
+int
+__db_syncchk(dbp, flags)
+ const DB *dbp;
+ u_int32_t flags;
+{
+ /* Check for invalid function flags. */
+ switch (flags) {
+ case 0:
+ break;
+ default:
+ return (__db_ferr(dbp->dbenv, "DB->sync", 0));
+ }
+
+ return (0);
+}
+
+/*
+ * __dbt_ferr --
+ * Check a DBT for flag errors.
+ */
+static int
+__dbt_ferr(dbp, name, dbt, check_thread)
+ const DB *dbp;
+ const char *name;
+ const DBT *dbt;
+ int check_thread;
+{
+ int ret;
+
+ /*
+ * Check for invalid DBT flags. We allow any of the flags to be
+ * specified to any DB or DBcursor call so that applications can
+ * set DB_DBT_MALLOC when retrieving a data item from a secondary
+ * database and then specify that same DBT as a key to a primary
+ * database, without having to clear flags.
+ */
+ if ((ret = __db_fchk(dbp->dbenv, name, dbt->flags,
+ DB_DBT_MALLOC | DB_DBT_USERMEM | DB_DBT_PARTIAL)) != 0)
+ return (ret);
+ if ((ret = __db_fcchk(dbp->dbenv, name,
+ dbt->flags, DB_DBT_MALLOC, DB_DBT_USERMEM)) != 0)
+ return (ret);
+
+ if (check_thread && F_ISSET(dbp, DB_AM_THREAD) &&
+ !F_ISSET(dbt, DB_DBT_MALLOC | DB_DBT_USERMEM)) {
+ __db_err(dbp->dbenv,
+ "missing flag thread flag for %s DBT", name);
+ return (EINVAL);
+ }
+ return (0);
+}
+
+/*
+ * __db_eopnotsup --
+ * Common operation not supported message.
+ *
+ * PUBLIC: int __db_eopnotsup __P((const DB_ENV *));
+ */
+int
+__db_eopnotsup(dbenv)
+ const DB_ENV *dbenv;
+{
+ __db_err(dbenv, "operation not supported");
+#ifdef EOPNOTSUPP
+ return (EOPNOTSUPP);
+#else
+ return (EINVAL);
+#endif
+}
+
+/*
+ * __db_keyempty --
+ * Common missing or empty key value message.
+ */
+static int
+__db_keyempty(dbenv)
+ const DB_ENV *dbenv;
+{
+ __db_err(dbenv, "missing or empty key value specified");
+ return (EINVAL);
+}
+
+/*
+ * __db_rdonly --
+ * Common readonly message.
+ */
+static int
+__db_rdonly(dbenv, name)
+ const DB_ENV *dbenv;
+ const char *name;
+{
+ __db_err(dbenv, "%s: attempt to modify a read-only tree", name);
+ return (EACCES);
+}
diff --git a/usr/src/cmd/sendmail/db/db/db_join.c b/usr/src/cmd/sendmail/db/db/db_join.c
new file mode 100644
index 0000000000..d44a1e0e43
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/db/db_join.c
@@ -0,0 +1,273 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1998
+ * Sleepycat Software. All rights reserved.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)db_join.c 10.10 (Sleepycat) 10/9/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <errno.h>
+#include <string.h>
+#endif
+
+#include "db_int.h"
+#include "db_page.h"
+#include "db_join.h"
+#include "db_am.h"
+#include "common_ext.h"
+
+static int __db_join_close __P((DBC *));
+static int __db_join_del __P((DBC *, u_int32_t));
+static int __db_join_get __P((DBC *, DBT *, DBT *, u_int32_t));
+static int __db_join_put __P((DBC *, DBT *, DBT *, u_int32_t));
+
+/*
+ * This is the duplicate-assisted join functionality. Right now we're
+ * going to write it such that we return one item at a time, although
+ * I think we may need to optimize it to return them all at once.
+ * It should be easier to get it working this way, and I believe that
+ * changing it should be fairly straightforward.
+ *
+ * XXX
+ * Right now we do not maintain the number of duplicates so we do
+ * not optimize the join. If the caller does, then best performance
+ * will be achieved by putting the cursor with the smallest cardinality
+ * first.
+ *
+ * The first cursor moves sequentially through the duplicate set while
+ * the others search explicitly for the duplicate in question.
+ *
+ */
+
+/*
+ * __db_join --
+ * This is the interface to the duplicate-assisted join functionality.
+ * In the same way that cursors mark a position in a database, a cursor
+ * can mark a position in a join. While most cursors are created by the
+ * cursor method of a DB, join cursors are created through an explicit
+ * call to DB->join.
+ *
+ * The curslist is an array of existing, intialized cursors and primary
+ * is the DB of the primary file. The data item that joins all the
+ * cursors in the curslist is used as the key into the primary and that
+ * key and data are returned. When no more items are left in the join
+ * set, the c_next operation off the join cursor will return DB_NOTFOUND.
+ *
+ * PUBLIC: int __db_join __P((DB *, DBC **, u_int32_t, DBC **));
+ */
+int
+__db_join(primary, curslist, flags, dbcp)
+ DB *primary;
+ DBC **curslist, **dbcp;
+ u_int32_t flags;
+{
+ DBC *dbc;
+ JOIN_CURSOR *jc;
+ int i, ret;
+
+ DB_PANIC_CHECK(primary);
+
+ if ((ret = __db_joinchk(primary, flags)) != 0)
+ return (ret);
+
+ if (curslist == NULL || curslist[0] == NULL)
+ return (EINVAL);
+
+ dbc = NULL;
+ jc = NULL;
+
+ if ((ret = __os_calloc(1, sizeof(DBC), &dbc)) != 0)
+ goto err;
+
+ if ((ret = __os_calloc(1, sizeof(JOIN_CURSOR), &jc)) != 0)
+ goto err;
+
+ if ((ret = __os_malloc(256, NULL, &jc->j_key.data)) != 0)
+ goto err;
+ jc->j_key.ulen = 256;
+ F_SET(&jc->j_key, DB_DBT_USERMEM);
+
+ for (jc->j_curslist = curslist;
+ *jc->j_curslist != NULL; jc->j_curslist++)
+ ;
+ if ((ret = __os_calloc((jc->j_curslist - curslist + 1),
+ sizeof(DBC *), &jc->j_curslist)) != 0)
+ goto err;
+ for (i = 0; curslist[i] != NULL; i++) {
+ if (i != 0)
+ F_SET(curslist[i], DBC_KEYSET);
+ jc->j_curslist[i] = curslist[i];
+ }
+
+ dbc->c_close = __db_join_close;
+ dbc->c_del = __db_join_del;
+ dbc->c_get = __db_join_get;
+ dbc->c_put = __db_join_put;
+ dbc->internal = jc;
+ dbc->dbp = primary;
+ jc->j_init = 1;
+ jc->j_primary = primary;
+
+ *dbcp = dbc;
+
+ return (0);
+
+err: if (jc != NULL) {
+ if (jc->j_curslist != NULL)
+ __os_free(jc->j_curslist,
+ (jc->j_curslist - curslist + 1) * sizeof(DBC *));
+ __os_free(jc, sizeof(JOIN_CURSOR));
+ }
+ if (dbc != NULL)
+ __os_free(dbc, sizeof(DBC));
+ return (ret);
+}
+
+static int
+__db_join_put(dbc, key, data, flags)
+ DBC *dbc;
+ DBT *key;
+ DBT *data;
+ u_int32_t flags;
+{
+ DB_PANIC_CHECK(dbc->dbp);
+
+ COMPQUIET(key, NULL);
+ COMPQUIET(data, NULL);
+ COMPQUIET(flags, 0);
+ return (EINVAL);
+}
+
+static int
+__db_join_del(dbc, flags)
+ DBC *dbc;
+ u_int32_t flags;
+{
+ DB_PANIC_CHECK(dbc->dbp);
+
+ COMPQUIET(flags, 0);
+ return (EINVAL);
+}
+
+static int
+__db_join_get(dbc, key, data, flags)
+ DBC *dbc;
+ DBT *key, *data;
+ u_int32_t flags;
+{
+ DB *dbp;
+ DBC **cpp;
+ JOIN_CURSOR *jc;
+ int ret;
+ u_int32_t operation;
+
+ dbp = dbc->dbp;
+
+ DB_PANIC_CHECK(dbp);
+
+ operation = LF_ISSET(DB_OPFLAGS_MASK);
+ if (operation != 0 && operation != DB_JOIN_ITEM)
+ return (__db_ferr(dbp->dbenv, "DBcursor->c_get", 0));
+
+ LF_CLR(DB_OPFLAGS_MASK);
+ if ((ret =
+ __db_fchk(dbp->dbenv, "DBcursor->c_get", flags, DB_RMW)) != 0)
+ return (ret);
+
+ jc = (JOIN_CURSOR *)dbc->internal;
+retry:
+ ret = jc->j_curslist[0]->c_get(jc->j_curslist[0],
+ &jc->j_key, key, jc->j_init ? DB_CURRENT : DB_NEXT_DUP);
+
+ if (ret == ENOMEM) {
+ jc->j_key.ulen <<= 1;
+ if ((ret = __os_realloc(&jc->j_key.data, jc->j_key.ulen)) != 0)
+ return (ret);
+ goto retry;
+ }
+ if (ret != 0)
+ return (ret);
+
+ jc->j_init = 0;
+ do {
+ /*
+ * We have the first element; now look for it in the
+ * other cursors.
+ */
+ for (cpp = jc->j_curslist + 1; *cpp != NULL; cpp++) {
+retry2: if ((ret = ((*cpp)->c_get)(*cpp,
+ &jc->j_key, key, DB_GET_BOTH)) == DB_NOTFOUND)
+ break;
+ if (ret == ENOMEM) {
+ jc->j_key.ulen <<= 1;
+ if ((ret = __os_realloc(&jc->j_key.data,
+ jc->j_key.ulen)) != 0)
+ return (ret);
+ goto retry2;
+ }
+ if (F_ISSET(*cpp, DBC_KEYSET)) {
+ F_CLR(*cpp, DBC_KEYSET);
+ F_SET(*cpp, DBC_CONTINUE);
+ }
+ }
+
+ /*
+ * If we got out of here with ret != 0, then we failed to
+ * find the duplicate in one of the files, so we go on to
+ * the next item in the outermost relation. If ret was
+ * equal to 0, then we've got something to return.
+ */
+ if (ret == 0)
+ break;
+ } while ((ret = jc->j_curslist[0]->c_get(jc->j_curslist[0],
+ &jc->j_key, key, DB_NEXT_DUP)) == 0);
+
+ /*
+ * If ret != 0 here, we've exhausted the first file. Otherwise,
+ * key and data are set and we need to do the lookup on the
+ * primary.
+ */
+ if (ret != 0)
+ return (ret);
+
+ if (operation == DB_JOIN_ITEM)
+ return (0);
+ else
+ return ((jc->j_primary->get)(jc->j_primary,
+ jc->j_curslist[0]->txn, key, data, 0));
+}
+
+static int
+__db_join_close(dbc)
+ DBC *dbc;
+{
+ JOIN_CURSOR *jc;
+ int i;
+
+ DB_PANIC_CHECK(dbc->dbp);
+
+ jc = (JOIN_CURSOR *)dbc->internal;
+
+ /*
+ * Clear the optimization flag in the cursors.
+ */
+ for (i = 0; jc->j_curslist[i] != NULL; i++)
+ F_CLR(jc->j_curslist[i], DBC_CONTINUE | DBC_KEYSET);
+
+ __os_free(jc->j_curslist, 0);
+ __os_free(jc->j_key.data, jc->j_key.ulen);
+ __os_free(jc, sizeof(JOIN_CURSOR));
+ __os_free(dbc, sizeof(DBC));
+
+ return (0);
+}
diff --git a/usr/src/cmd/sendmail/db/db/db_log2.c b/usr/src/cmd/sendmail/db/db/db_log2.c
new file mode 100644
index 0000000000..d6b14f540b
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/db/db_log2.c
@@ -0,0 +1,69 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+/*
+ * Copyright (c) 1995, 1996
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Margo Seltzer.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)db_log2.c 10.5 (Sleepycat) 4/26/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+#endif
+
+#include "db_int.h"
+#include "common_ext.h"
+
+/*
+ * PUBLIC: u_int32_t __db_log2 __P((u_int32_t));
+ */
+u_int32_t
+__db_log2(num)
+ u_int32_t num;
+{
+ u_int32_t i, limit;
+
+ limit = 1;
+ for (i = 0; limit < num; limit = limit << 1, i++)
+ ;
+ return (i);
+}
diff --git a/usr/src/cmd/sendmail/db/db/db_overflow.c b/usr/src/cmd/sendmail/db/db/db_overflow.c
new file mode 100644
index 0000000000..0efcc9de7f
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/db/db_overflow.c
@@ -0,0 +1,407 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+/*
+ * Copyright (c) 1990, 1993, 1994, 1995, 1996
+ * Keith Bostic. All rights reserved.
+ */
+/*
+ * Copyright (c) 1990, 1993, 1994, 1995
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Mike Olson.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)db_overflow.c 10.21 (Sleepycat) 9/27/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <errno.h>
+#include <string.h>
+#endif
+
+#include "db_int.h"
+#include "db_page.h"
+#include "db_am.h"
+#include "common_ext.h"
+
+/*
+ * Big key/data code.
+ *
+ * Big key and data entries are stored on linked lists of pages. The initial
+ * reference is a structure with the total length of the item and the page
+ * number where it begins. Each entry in the linked list contains a pointer
+ * to the next page of data, and so on.
+ */
+
+/*
+ * __db_goff --
+ * Get an offpage item.
+ *
+ * PUBLIC: int __db_goff __P((DB *, DBT *,
+ * PUBLIC: u_int32_t, db_pgno_t, void **, u_int32_t *));
+ */
+int
+__db_goff(dbp, dbt, tlen, pgno, bpp, bpsz)
+ DB *dbp;
+ DBT *dbt;
+ u_int32_t tlen;
+ db_pgno_t pgno;
+ void **bpp;
+ u_int32_t *bpsz;
+{
+ PAGE *h;
+ db_indx_t bytes;
+ u_int32_t curoff, needed, start;
+ u_int8_t *p, *src;
+ int ret;
+
+ /*
+ * Check if the buffer is big enough; if it is not and we are
+ * allowed to malloc space, then we'll malloc it. If we are
+ * not (DB_DBT_USERMEM), then we'll set the dbt and return
+ * appropriately.
+ */
+ if (F_ISSET(dbt, DB_DBT_PARTIAL)) {
+ start = dbt->doff;
+ needed = dbt->dlen;
+ } else {
+ start = 0;
+ needed = tlen;
+ }
+
+ /* Allocate any necessary memory. */
+ if (F_ISSET(dbt, DB_DBT_USERMEM)) {
+ if (needed > dbt->ulen) {
+ dbt->size = needed;
+ return (ENOMEM);
+ }
+ } else if (F_ISSET(dbt, DB_DBT_MALLOC)) {
+ if ((ret =
+ __os_malloc(needed, dbp->db_malloc, &dbt->data)) != 0)
+ return (ret);
+ } else if (*bpsz == 0 || *bpsz < needed) {
+ if ((ret = __os_realloc(bpp, needed)) != 0)
+ return (ret);
+ *bpsz = needed;
+ dbt->data = *bpp;
+ } else
+ dbt->data = *bpp;
+
+ /*
+ * Step through the linked list of pages, copying the data on each
+ * one into the buffer. Never copy more than the total data length.
+ */
+ dbt->size = needed;
+ for (curoff = 0, p = dbt->data; pgno != P_INVALID && needed > 0;) {
+ if ((ret = memp_fget(dbp->mpf, &pgno, 0, &h)) != 0) {
+ (void)__db_pgerr(dbp, pgno);
+ return (ret);
+ }
+ /* Check if we need any bytes from this page. */
+ if (curoff + OV_LEN(h) >= start) {
+ src = (u_int8_t *)h + P_OVERHEAD;
+ bytes = OV_LEN(h);
+ if (start > curoff) {
+ src += start - curoff;
+ bytes -= start - curoff;
+ }
+ if (bytes > needed)
+ bytes = needed;
+ memcpy(p, src, bytes);
+ p += bytes;
+ needed -= bytes;
+ }
+ curoff += OV_LEN(h);
+ pgno = h->next_pgno;
+ memp_fput(dbp->mpf, h, 0);
+ }
+ return (0);
+}
+
+/*
+ * __db_poff --
+ * Put an offpage item.
+ *
+ * PUBLIC: int __db_poff __P((DBC *, const DBT *, db_pgno_t *,
+ * PUBLIC: int (*)(DBC *, u_int32_t, PAGE **)));
+ */
+int
+__db_poff(dbc, dbt, pgnop, newfunc)
+ DBC *dbc;
+ const DBT *dbt;
+ db_pgno_t *pgnop;
+ int (*newfunc) __P((DBC *, u_int32_t, PAGE **));
+{
+ DB *dbp;
+ PAGE *pagep, *lastp;
+ DB_LSN new_lsn, null_lsn;
+ DBT tmp_dbt;
+ db_indx_t pagespace;
+ u_int32_t sz;
+ u_int8_t *p;
+ int ret;
+
+ /*
+ * Allocate pages and copy the key/data item into them. Calculate the
+ * number of bytes we get for pages we fill completely with a single
+ * item.
+ */
+ dbp = dbc->dbp;
+ pagespace = P_MAXSPACE(dbp->pgsize);
+
+ lastp = NULL;
+ for (p = dbt->data,
+ sz = dbt->size; sz > 0; p += pagespace, sz -= pagespace) {
+ /*
+ * Reduce pagespace so we terminate the loop correctly and
+ * don't copy too much data.
+ */
+ if (sz < pagespace)
+ pagespace = sz;
+
+ /*
+ * Allocate and initialize a new page and copy all or part of
+ * the item onto the page. If sz is less than pagespace, we
+ * have a partial record.
+ */
+ if ((ret = newfunc(dbc, P_OVERFLOW, &pagep)) != 0)
+ return (ret);
+ if (DB_LOGGING(dbc)) {
+ tmp_dbt.data = p;
+ tmp_dbt.size = pagespace;
+ ZERO_LSN(null_lsn);
+ if ((ret = __db_big_log(dbp->dbenv->lg_info, dbc->txn,
+ &new_lsn, 0, DB_ADD_BIG, dbp->log_fileid,
+ PGNO(pagep), lastp ? PGNO(lastp) : PGNO_INVALID,
+ PGNO_INVALID, &tmp_dbt, &LSN(pagep),
+ lastp == NULL ? &null_lsn : &LSN(lastp),
+ &null_lsn)) != 0)
+ return (ret);
+
+ /* Move lsn onto page. */
+ if (lastp)
+ LSN(lastp) = new_lsn;
+ LSN(pagep) = new_lsn;
+ }
+
+ P_INIT(pagep, dbp->pgsize,
+ PGNO(pagep), PGNO_INVALID, PGNO_INVALID, 0, P_OVERFLOW);
+ OV_LEN(pagep) = pagespace;
+ OV_REF(pagep) = 1;
+ memcpy((u_int8_t *)pagep + P_OVERHEAD, p, pagespace);
+
+ /*
+ * If this is the first entry, update the user's info.
+ * Otherwise, update the entry on the last page filled
+ * in and release that page.
+ */
+ if (lastp == NULL)
+ *pgnop = PGNO(pagep);
+ else {
+ lastp->next_pgno = PGNO(pagep);
+ pagep->prev_pgno = PGNO(lastp);
+ (void)memp_fput(dbp->mpf, lastp, DB_MPOOL_DIRTY);
+ }
+ lastp = pagep;
+ }
+ (void)memp_fput(dbp->mpf, lastp, DB_MPOOL_DIRTY);
+ return (0);
+}
+
+/*
+ * __db_ovref --
+ * Increment/decrement the reference count on an overflow page.
+ *
+ * PUBLIC: int __db_ovref __P((DBC *, db_pgno_t, int32_t));
+ */
+int
+__db_ovref(dbc, pgno, adjust)
+ DBC *dbc;
+ db_pgno_t pgno;
+ int32_t adjust;
+{
+ DB *dbp;
+ PAGE *h;
+ int ret;
+
+ dbp = dbc->dbp;
+ if ((ret = memp_fget(dbp->mpf, &pgno, 0, &h)) != 0) {
+ (void)__db_pgerr(dbp, pgno);
+ return (ret);
+ }
+
+ if (DB_LOGGING(dbc))
+ if ((ret = __db_ovref_log(dbp->dbenv->lg_info, dbc->txn,
+ &LSN(h), 0, dbp->log_fileid, h->pgno, adjust,
+ &LSN(h))) != 0)
+ return (ret);
+ OV_REF(h) += adjust;
+
+ (void)memp_fput(dbp->mpf, h, DB_MPOOL_DIRTY);
+ return (0);
+}
+
+/*
+ * __db_doff --
+ * Delete an offpage chain of overflow pages.
+ *
+ * PUBLIC: int __db_doff __P((DBC *, db_pgno_t, int (*)(DBC *, PAGE *)));
+ */
+int
+__db_doff(dbc, pgno, freefunc)
+ DBC *dbc;
+ db_pgno_t pgno;
+ int (*freefunc) __P((DBC *, PAGE *));
+{
+ DB *dbp;
+ PAGE *pagep;
+ DB_LSN null_lsn;
+ DBT tmp_dbt;
+ int ret;
+
+ dbp = dbc->dbp;
+ do {
+ if ((ret = memp_fget(dbp->mpf, &pgno, 0, &pagep)) != 0) {
+ (void)__db_pgerr(dbp, pgno);
+ return (ret);
+ }
+
+ /*
+ * If it's an overflow page and it's referenced by more than
+ * one key/data item, decrement the reference count and return.
+ */
+ if (TYPE(pagep) == P_OVERFLOW && OV_REF(pagep) > 1) {
+ (void)memp_fput(dbp->mpf, pagep, 0);
+ return (__db_ovref(dbc, pgno, -1));
+ }
+
+ if (DB_LOGGING(dbc)) {
+ tmp_dbt.data = (u_int8_t *)pagep + P_OVERHEAD;
+ tmp_dbt.size = OV_LEN(pagep);
+ ZERO_LSN(null_lsn);
+ if ((ret = __db_big_log(dbp->dbenv->lg_info, dbc->txn,
+ &LSN(pagep), 0, DB_REM_BIG, dbp->log_fileid,
+ PGNO(pagep), PREV_PGNO(pagep), NEXT_PGNO(pagep),
+ &tmp_dbt, &LSN(pagep), &null_lsn, &null_lsn)) != 0)
+ return (ret);
+ }
+ pgno = pagep->next_pgno;
+ if ((ret = freefunc(dbc, pagep)) != 0)
+ return (ret);
+ } while (pgno != PGNO_INVALID);
+
+ return (0);
+}
+
+/*
+ * __db_moff --
+ * Match on overflow pages.
+ *
+ * Given a starting page number and a key, return <0, 0, >0 to indicate if the
+ * key on the page is less than, equal to or greater than the key specified.
+ * We optimize this by doing chunk at a time comparison unless the user has
+ * specified a comparison function. In this case, we need to materialize
+ * the entire object and call their comparison routine.
+ *
+ * PUBLIC: int __db_moff __P((DB *, const DBT *, db_pgno_t, u_int32_t,
+ * PUBLIC: int (*)(const DBT *, const DBT *), int *));
+ */
+int
+__db_moff(dbp, dbt, pgno, tlen, cmpfunc, cmpp)
+ DB *dbp;
+ const DBT *dbt;
+ db_pgno_t pgno;
+ u_int32_t tlen;
+ int (*cmpfunc) __P((const DBT *, const DBT *)), *cmpp;
+{
+ PAGE *pagep;
+ DBT local_dbt;
+ void *buf;
+ u_int32_t bufsize, cmp_bytes, key_left;
+ u_int8_t *p1, *p2;
+ int ret;
+
+ /*
+ * If there is a user-specified comparison function, build a
+ * contiguous copy of the key, and call it.
+ */
+ if (cmpfunc != NULL) {
+ memset(&local_dbt, 0, sizeof(local_dbt));
+ buf = NULL;
+ bufsize = 0;
+
+ if ((ret = __db_goff(dbp,
+ &local_dbt, tlen, pgno, &buf, &bufsize)) != 0)
+ return (ret);
+ *cmpp = cmpfunc(&local_dbt, dbt);
+ __os_free(buf, bufsize);
+ return (0);
+ }
+
+ /* While there are both keys to compare. */
+ for (*cmpp = 0, p1 = dbt->data,
+ key_left = dbt->size; key_left > 0 && pgno != PGNO_INVALID;) {
+ if ((ret = memp_fget(dbp->mpf, &pgno, 0, &pagep)) != 0)
+ return (ret);
+
+ cmp_bytes = OV_LEN(pagep) < key_left ? OV_LEN(pagep) : key_left;
+ key_left -= cmp_bytes;
+ for (p2 =
+ (u_int8_t *)pagep + P_OVERHEAD; cmp_bytes-- > 0; ++p1, ++p2)
+ if (*p1 != *p2) {
+ *cmpp = (long)*p1 - (long)*p2;
+ break;
+ }
+ pgno = NEXT_PGNO(pagep);
+ if ((ret = memp_fput(dbp->mpf, pagep, 0)) != 0)
+ return (ret);
+ if (*cmpp != 0)
+ return (0);
+ }
+ if (key_left > 0) /* DBT is longer than page key. */
+ *cmpp = -1;
+ else if (pgno != PGNO_INVALID) /* DBT is shorter than page key. */
+ *cmpp = 1;
+ else
+ *cmpp = 0;
+
+ return (0);
+}
diff --git a/usr/src/cmd/sendmail/db/db/db_pr.c b/usr/src/cmd/sendmail/db/db/db_pr.c
new file mode 100644
index 0000000000..7f4364c6e1
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/db/db_pr.c
@@ -0,0 +1,831 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)db_pr.c 10.40 (Sleepycat) 11/22/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#endif
+
+#include "db_int.h"
+#include "db_page.h"
+#include "btree.h"
+#include "hash.h"
+#include "db_am.h"
+
+static void __db_proff __P((void *));
+static void __db_psize __P((DB_MPOOLFILE *));
+
+/*
+ * __db_loadme --
+ * Force loading of this file.
+ *
+ * PUBLIC: void __db_loadme __P((void));
+ */
+void
+__db_loadme()
+{
+ getpid();
+}
+
+static FILE *set_fp;
+
+/*
+ * 64K is the maximum page size, so by default we check for offsets
+ * larger than that, and, where possible, we refine the test.
+ */
+#define PSIZE_BOUNDARY (64 * 1024 + 1)
+static size_t set_psize = PSIZE_BOUNDARY;
+
+/*
+ * __db_prinit --
+ * Initialize tree printing routines.
+ *
+ * PUBLIC: FILE *__db_prinit __P((FILE *));
+ */
+FILE *
+__db_prinit(fp)
+ FILE *fp;
+{
+ if (set_fp == NULL)
+ set_fp = fp == NULL ? stdout : fp;
+ return (set_fp);
+}
+
+/*
+ * __db_dump --
+ * Dump the tree to a file.
+ *
+ * PUBLIC: int __db_dump __P((DB *, char *, int));
+ */
+int
+__db_dump(dbp, name, all)
+ DB *dbp;
+ char *name;
+ int all;
+{
+ FILE *fp, *save_fp;
+
+ COMPQUIET(save_fp, NULL);
+
+ if (set_psize == PSIZE_BOUNDARY)
+ __db_psize(dbp->mpf);
+
+ if (name != NULL) {
+ if ((fp = fopen(name, "w")) == NULL)
+ return (errno);
+ save_fp = set_fp;
+ set_fp = fp;
+ } else
+ fp = __db_prinit(NULL);
+
+ (void)__db_prdb(dbp);
+ if (dbp->type == DB_HASH)
+ (void)__db_prhash(dbp);
+ else
+ (void)__db_prbtree(dbp);
+ fprintf(fp, "%s\n", DB_LINE);
+ __db_prtree(dbp->mpf, all);
+
+ if (name != NULL) {
+ (void)fclose(fp);
+ set_fp = save_fp;
+ }
+ return (0);
+}
+
+/*
+ * __db_prdb --
+ * Print out the DB structure information.
+ *
+ * PUBLIC: int __db_prdb __P((DB *));
+ */
+int
+__db_prdb(dbp)
+ DB *dbp;
+{
+ static const FN fn[] = {
+ { DB_AM_DUP, "duplicates" },
+ { DB_AM_INMEM, "in-memory" },
+ { DB_AM_LOCKING, "locking" },
+ { DB_AM_LOGGING, "logging" },
+ { DB_AM_MLOCAL, "local mpool" },
+ { DB_AM_PGDEF, "default page size" },
+ { DB_AM_RDONLY, "read-only" },
+ { DB_AM_SWAP, "needswap" },
+ { DB_AM_THREAD, "thread" },
+ { DB_BT_RECNUM, "btree:recnum" },
+ { DB_DBM_ERROR, "dbm/ndbm error" },
+ { DB_RE_DELIMITER, "recno:delimiter" },
+ { DB_RE_FIXEDLEN, "recno:fixed-length" },
+ { DB_RE_PAD, "recno:pad" },
+ { DB_RE_RENUMBER, "recno:renumber" },
+ { DB_RE_SNAPSHOT, "recno:snapshot" },
+ { 0 },
+ };
+ FILE *fp;
+ const char *t;
+
+ fp = __db_prinit(NULL);
+
+ switch (dbp->type) {
+ case DB_BTREE:
+ t = "btree";
+ break;
+ case DB_HASH:
+ t = "hash";
+ break;
+ case DB_RECNO:
+ t = "recno";
+ break;
+ default:
+ t = "UNKNOWN";
+ break;
+ }
+
+ fprintf(fp, "%s ", t);
+ __db_prflags(dbp->flags, fn, fp);
+ fprintf(fp, "\n");
+
+ return (0);
+}
+
+/*
+ * __db_prbtree --
+ * Print out the btree internal information.
+ *
+ * PUBLIC: int __db_prbtree __P((DB *));
+ */
+int
+__db_prbtree(dbp)
+ DB *dbp;
+{
+ static const FN mfn[] = {
+ { BTM_DUP, "duplicates" },
+ { BTM_RECNO, "recno" },
+ { BTM_RECNUM, "btree:recnum" },
+ { BTM_FIXEDLEN, "recno:fixed-length" },
+ { BTM_RENUMBER, "recno:renumber" },
+ { 0 },
+ };
+ DBC *dbc;
+ BTMETA *mp;
+ BTREE *t;
+ FILE *fp;
+ PAGE *h;
+ RECNO *rp;
+ db_pgno_t i;
+ int cnt, ret;
+ const char *sep;
+
+ t = dbp->internal;
+ fp = __db_prinit(NULL);
+ if ((ret = dbp->cursor(dbp, NULL, &dbc, 0)) != 0)
+ return (ret);
+
+ (void)fprintf(fp, "%s\nOn-page metadata:\n", DB_LINE);
+
+ i = PGNO_METADATA;
+ if ((ret = memp_fget(dbp->mpf, &i, 0, (PAGE **)&mp)) != 0) {
+ (void)dbc->c_close(dbc);
+ return (ret);
+ }
+
+ fprintf(fp, "lsn.file: %lu lsn.offset: %lu\n",
+ (u_long)LSN(mp).file, (u_long)LSN(mp).offset);
+ (void)fprintf(fp, "magic %#lx\n", (u_long)mp->magic);
+ (void)fprintf(fp, "version %#lx\n", (u_long)mp->version);
+ (void)fprintf(fp, "pagesize %lu\n", (u_long)mp->pagesize);
+ (void)fprintf(fp, "maxkey: %lu minkey: %lu\n",
+ (u_long)mp->maxkey, (u_long)mp->minkey);
+
+ (void)fprintf(fp, "free list: %lu", (u_long)mp->free);
+ for (i = mp->free, cnt = 0, sep = ", "; i != PGNO_INVALID;) {
+ if ((ret = memp_fget(dbp->mpf, &i, 0, &h)) != 0)
+ return (ret);
+ i = h->next_pgno;
+ (void)memp_fput(dbp->mpf, h, 0);
+ (void)fprintf(fp, "%s%lu", sep, (u_long)i);
+ if (++cnt % 10 == 0) {
+ (void)fprintf(fp, "\n");
+ cnt = 0;
+ sep = "";
+ } else
+ sep = ", ";
+ }
+ (void)fprintf(fp, "\n");
+
+ (void)fprintf(fp, "flags %#lx", (u_long)mp->flags);
+ __db_prflags(mp->flags, mfn, fp);
+ (void)fprintf(fp, "\n");
+ (void)memp_fput(dbp->mpf, mp, 0);
+
+ (void)fprintf(fp, "%s\nDB_INFO:\n", DB_LINE);
+ (void)fprintf(fp, "bt_maxkey: %lu bt_minkey: %lu\n",
+ (u_long)t->bt_maxkey, (u_long)t->bt_minkey);
+ (void)fprintf(fp, "bt_compare: %#lx bt_prefix: %#lx\n",
+ (u_long)t->bt_compare, (u_long)t->bt_prefix);
+ if ((rp = t->recno) != NULL) {
+ (void)fprintf(fp,
+ "re_delim: %#lx re_pad: %#lx re_len: %lu re_source: %s\n",
+ (u_long)rp->re_delim, (u_long)rp->re_pad,
+ (u_long)rp->re_len,
+ rp->re_source == NULL ? "" : rp->re_source);
+ (void)fprintf(fp,
+ "cmap: %#lx smap: %#lx emap: %#lx msize: %lu\n",
+ (u_long)rp->re_cmap, (u_long)rp->re_smap,
+ (u_long)rp->re_emap, (u_long)rp->re_msize);
+ }
+ (void)fprintf(fp, "ovflsize: %lu\n", (u_long)t->bt_ovflsize);
+ (void)fflush(fp);
+ return (dbc->c_close(dbc));
+}
+
+/*
+ * __db_prhash --
+ * Print out the hash internal information.
+ *
+ * PUBLIC: int __db_prhash __P((DB *));
+ */
+int
+__db_prhash(dbp)
+ DB *dbp;
+{
+ FILE *fp;
+ DBC *dbc;
+ HASH_CURSOR *hcp;
+ int i, put_page, ret;
+ db_pgno_t pgno;
+
+ fp = __db_prinit(NULL);
+ if ((ret = dbp->cursor(dbp, NULL, &dbc, 0)) != 0)
+ return (ret);
+ hcp = (HASH_CURSOR *)dbc->internal;
+
+ /*
+ * In this case, hcp->hdr will never be null, if we decide
+ * to pass dbc's to this routine instead, then it could be.
+ */
+ if (hcp->hdr == NULL) {
+ pgno = PGNO_METADATA;
+ if ((ret = memp_fget(dbp->mpf, &pgno, 0, &hcp->hdr)) != 0)
+ return (ret);
+ put_page = 1;
+ } else
+ put_page = 0;
+
+ fprintf(fp, "\tmagic %#lx\n", (u_long)hcp->hdr->magic);
+ fprintf(fp, "\tversion %lu\n", (u_long)hcp->hdr->version);
+ fprintf(fp, "\tpagesize %lu\n", (u_long)hcp->hdr->pagesize);
+ fprintf(fp, "\tovfl_point %lu\n", (u_long)hcp->hdr->ovfl_point);
+ fprintf(fp, "\tlast_freed %lu\n", (u_long)hcp->hdr->last_freed);
+ fprintf(fp, "\tmax_bucket %lu\n", (u_long)hcp->hdr->max_bucket);
+ fprintf(fp, "\thigh_mask %#lx\n", (u_long)hcp->hdr->high_mask);
+ fprintf(fp, "\tlow_mask %#lx\n", (u_long)hcp->hdr->low_mask);
+ fprintf(fp, "\tffactor %lu\n", (u_long)hcp->hdr->ffactor);
+ fprintf(fp, "\tnelem %lu\n", (u_long)hcp->hdr->nelem);
+ fprintf(fp, "\th_charkey %#lx\n", (u_long)hcp->hdr->h_charkey);
+
+ for (i = 0; i < NCACHED; i++)
+ fprintf(fp, "%lu ", (u_long)hcp->hdr->spares[i]);
+ fprintf(fp, "\n");
+
+ (void)fflush(fp);
+ if (put_page) {
+ (void)memp_fput(dbp->mpf, (PAGE *)hcp->hdr, 0);
+ hcp->hdr = NULL;
+ }
+ return (dbc->c_close(dbc));
+}
+
+/*
+ * __db_prtree --
+ * Print out the entire tree.
+ *
+ * PUBLIC: int __db_prtree __P((DB_MPOOLFILE *, int));
+ */
+int
+__db_prtree(mpf, all)
+ DB_MPOOLFILE *mpf;
+ int all;
+{
+ PAGE *h;
+ db_pgno_t i;
+
+ if (set_psize == PSIZE_BOUNDARY)
+ __db_psize(mpf);
+
+ for (i = PGNO_ROOT;; ++i) {
+ if (memp_fget(mpf, &i, 0, &h) != 0)
+ break;
+ (void)__db_prpage(h, all);
+ (void)memp_fput(mpf, h, 0);
+ }
+ (void)fflush(__db_prinit(NULL));
+ return (0);
+}
+
+/*
+ * __db_prnpage
+ * -- Print out a specific page.
+ *
+ * PUBLIC: int __db_prnpage __P((DB_MPOOLFILE *, db_pgno_t));
+ */
+int
+__db_prnpage(mpf, pgno)
+ DB_MPOOLFILE *mpf;
+ db_pgno_t pgno;
+{
+ PAGE *h;
+ int ret;
+
+ if (set_psize == PSIZE_BOUNDARY)
+ __db_psize(mpf);
+
+ if ((ret = memp_fget(mpf, &pgno, 0, &h)) != 0)
+ return (ret);
+
+ ret = __db_prpage(h, 1);
+ (void)fflush(__db_prinit(NULL));
+
+ (void)memp_fput(mpf, h, 0);
+ return (ret);
+}
+
+/*
+ * __db_prpage
+ * -- Print out a page.
+ *
+ * PUBLIC: int __db_prpage __P((PAGE *, int));
+ */
+int
+__db_prpage(h, all)
+ PAGE *h;
+ int all;
+{
+ BINTERNAL *bi;
+ BKEYDATA *bk;
+ HOFFPAGE a_hkd;
+ FILE *fp;
+ RINTERNAL *ri;
+ db_indx_t dlen, len, i;
+ db_pgno_t pgno;
+ int deleted, ret;
+ const char *s;
+ u_int8_t *ep, *hk, *p;
+ void *sp;
+
+ fp = __db_prinit(NULL);
+
+ switch (TYPE(h)) {
+ case P_DUPLICATE:
+ s = "duplicate";
+ break;
+ case P_HASH:
+ s = "hash";
+ break;
+ case P_IBTREE:
+ s = "btree internal";
+ break;
+ case P_INVALID:
+ s = "invalid";
+ break;
+ case P_IRECNO:
+ s = "recno internal";
+ break;
+ case P_LBTREE:
+ s = "btree leaf";
+ break;
+ case P_LRECNO:
+ s = "recno leaf";
+ break;
+ case P_OVERFLOW:
+ s = "overflow";
+ break;
+ default:
+ fprintf(fp, "ILLEGAL PAGE TYPE: page: %lu type: %lu\n",
+ (u_long)h->pgno, (u_long)TYPE(h));
+ return (1);
+ }
+ fprintf(fp, "page %4lu: (%s)\n", (u_long)h->pgno, s);
+ fprintf(fp, " lsn.file: %lu lsn.offset: %lu",
+ (u_long)LSN(h).file, (u_long)LSN(h).offset);
+ if (TYPE(h) == P_IBTREE || TYPE(h) == P_IRECNO ||
+ (TYPE(h) == P_LRECNO && h->pgno == PGNO_ROOT))
+ fprintf(fp, " total records: %4lu", (u_long)RE_NREC(h));
+ fprintf(fp, "\n");
+ if (TYPE(h) != P_IBTREE && TYPE(h) != P_IRECNO)
+ fprintf(fp, " prev: %4lu next: %4lu",
+ (u_long)PREV_PGNO(h), (u_long)NEXT_PGNO(h));
+ if (TYPE(h) == P_IBTREE || TYPE(h) == P_LBTREE)
+ fprintf(fp, " level: %2lu", (u_long)h->level);
+ if (TYPE(h) == P_OVERFLOW) {
+ fprintf(fp, " ref cnt: %4lu ", (u_long)OV_REF(h));
+ __db_pr((u_int8_t *)h + P_OVERHEAD, OV_LEN(h));
+ return (0);
+ }
+ fprintf(fp, " entries: %4lu", (u_long)NUM_ENT(h));
+ fprintf(fp, " offset: %4lu\n", (u_long)HOFFSET(h));
+
+ if (!all || TYPE(h) == P_INVALID)
+ return (0);
+
+ ret = 0;
+ for (i = 0; i < NUM_ENT(h); i++) {
+ if (P_ENTRY(h, i) - (u_int8_t *)h < P_OVERHEAD ||
+ (size_t)(P_ENTRY(h, i) - (u_int8_t *)h) >= set_psize) {
+ fprintf(fp,
+ "ILLEGAL PAGE OFFSET: indx: %lu of %lu\n",
+ (u_long)i, (u_long)h->inp[i]);
+ ret = EINVAL;
+ continue;
+ }
+ deleted = 0;
+ switch (TYPE(h)) {
+ case P_HASH:
+ case P_IBTREE:
+ case P_IRECNO:
+ sp = P_ENTRY(h, i);
+ break;
+ case P_LBTREE:
+ sp = P_ENTRY(h, i);
+ deleted = i % 2 == 0 &&
+ B_DISSET(GET_BKEYDATA(h, i + O_INDX)->type);
+ break;
+ case P_LRECNO:
+ case P_DUPLICATE:
+ sp = P_ENTRY(h, i);
+ deleted = B_DISSET(GET_BKEYDATA(h, i)->type);
+ break;
+ default:
+ fprintf(fp,
+ "ILLEGAL PAGE ITEM: %lu\n", (u_long)TYPE(h));
+ ret = EINVAL;
+ continue;
+ }
+ fprintf(fp, " %s[%03lu] %4lu ",
+ deleted ? "D" : " ", (u_long)i, (u_long)h->inp[i]);
+ switch (TYPE(h)) {
+ case P_HASH:
+ hk = sp;
+ switch (HPAGE_PTYPE(hk)) {
+ case H_OFFDUP:
+ memcpy(&pgno,
+ HOFFDUP_PGNO(hk), sizeof(db_pgno_t));
+ fprintf(fp,
+ "%4lu [offpage dups]\n", (u_long)pgno);
+ break;
+ case H_DUPLICATE:
+ /*
+ * If this is the first item on a page, then
+ * we cannot figure out how long it is, so
+ * we only print the first one in the duplicate
+ * set.
+ */
+ if (i != 0)
+ len = LEN_HKEYDATA(h, 0, i);
+ else
+ len = 1;
+
+ fprintf(fp, "Duplicates:\n");
+ for (p = HKEYDATA_DATA(hk),
+ ep = p + len; p < ep;) {
+ memcpy(&dlen, p, sizeof(db_indx_t));
+ p += sizeof(db_indx_t);
+ fprintf(fp, "\t\t");
+ __db_pr(p, dlen);
+ p += sizeof(db_indx_t) + dlen;
+ }
+ break;
+ case H_KEYDATA:
+ if (i != 0)
+ __db_pr(HKEYDATA_DATA(hk),
+ LEN_HKEYDATA(h, 0, i));
+ else
+ fprintf(fp, "%s\n", HKEYDATA_DATA(hk));
+ break;
+ case H_OFFPAGE:
+ memcpy(&a_hkd, hk, HOFFPAGE_SIZE);
+ fprintf(fp,
+ "overflow: total len: %4lu page: %4lu\n",
+ (u_long)a_hkd.tlen, (u_long)a_hkd.pgno);
+ break;
+ }
+ break;
+ case P_IBTREE:
+ bi = sp;
+ fprintf(fp, "count: %4lu pgno: %4lu ",
+ (u_long)bi->nrecs, (u_long)bi->pgno);
+ switch (B_TYPE(bi->type)) {
+ case B_KEYDATA:
+ __db_pr(bi->data, bi->len);
+ break;
+ case B_DUPLICATE:
+ case B_OVERFLOW:
+ __db_proff(bi->data);
+ break;
+ default:
+ fprintf(fp, "ILLEGAL BINTERNAL TYPE: %lu\n",
+ (u_long)B_TYPE(bi->type));
+ ret = EINVAL;
+ break;
+ }
+ break;
+ case P_IRECNO:
+ ri = sp;
+ fprintf(fp, "entries %4lu pgno %4lu\n",
+ (u_long)ri->nrecs, (u_long)ri->pgno);
+ break;
+ case P_LBTREE:
+ case P_LRECNO:
+ case P_DUPLICATE:
+ bk = sp;
+ switch (B_TYPE(bk->type)) {
+ case B_KEYDATA:
+ __db_pr(bk->data, bk->len);
+ break;
+ case B_DUPLICATE:
+ case B_OVERFLOW:
+ __db_proff(bk);
+ break;
+ default:
+ fprintf(fp,
+ "ILLEGAL DUPLICATE/LBTREE/LRECNO TYPE: %lu\n",
+ (u_long)B_TYPE(bk->type));
+ ret = EINVAL;
+ break;
+ }
+ break;
+ }
+ }
+ (void)fflush(fp);
+ return (ret);
+}
+
+/*
+ * __db_isbad
+ * -- Decide if a page is corrupted.
+ *
+ * PUBLIC: int __db_isbad __P((PAGE *, int));
+ */
+int
+__db_isbad(h, die)
+ PAGE *h;
+ int die;
+{
+ BINTERNAL *bi;
+ BKEYDATA *bk;
+ FILE *fp;
+ db_indx_t i;
+ u_int type;
+
+ fp = __db_prinit(NULL);
+
+ switch (TYPE(h)) {
+ case P_DUPLICATE:
+ case P_HASH:
+ case P_IBTREE:
+ case P_INVALID:
+ case P_IRECNO:
+ case P_LBTREE:
+ case P_LRECNO:
+ case P_OVERFLOW:
+ break;
+ default:
+ fprintf(fp, "ILLEGAL PAGE TYPE: page: %lu type: %lu\n",
+ (u_long)h->pgno, (u_long)TYPE(h));
+ goto bad;
+ }
+
+ for (i = 0; i < NUM_ENT(h); i++) {
+ if (P_ENTRY(h, i) - (u_int8_t *)h < P_OVERHEAD ||
+ (size_t)(P_ENTRY(h, i) - (u_int8_t *)h) >= set_psize) {
+ fprintf(fp,
+ "ILLEGAL PAGE OFFSET: indx: %lu of %lu\n",
+ (u_long)i, (u_long)h->inp[i]);
+ goto bad;
+ }
+ switch (TYPE(h)) {
+ case P_HASH:
+ type = HPAGE_TYPE(h, i);
+ if (type != H_OFFDUP &&
+ type != H_DUPLICATE &&
+ type != H_KEYDATA &&
+ type != H_OFFPAGE) {
+ fprintf(fp, "ILLEGAL HASH TYPE: %lu\n",
+ (u_long)type);
+ goto bad;
+ }
+ break;
+ case P_IBTREE:
+ bi = GET_BINTERNAL(h, i);
+ if (B_TYPE(bi->type) != B_KEYDATA &&
+ B_TYPE(bi->type) != B_DUPLICATE &&
+ B_TYPE(bi->type) != B_OVERFLOW) {
+ fprintf(fp, "ILLEGAL BINTERNAL TYPE: %lu\n",
+ (u_long)B_TYPE(bi->type));
+ goto bad;
+ }
+ break;
+ case P_IRECNO:
+ case P_LBTREE:
+ case P_LRECNO:
+ break;
+ case P_DUPLICATE:
+ bk = GET_BKEYDATA(h, i);
+ if (B_TYPE(bk->type) != B_KEYDATA &&
+ B_TYPE(bk->type) != B_DUPLICATE &&
+ B_TYPE(bk->type) != B_OVERFLOW) {
+ fprintf(fp,
+ "ILLEGAL DUPLICATE/LBTREE/LRECNO TYPE: %lu\n",
+ (u_long)B_TYPE(bk->type));
+ goto bad;
+ }
+ break;
+ default:
+ fprintf(fp,
+ "ILLEGAL PAGE ITEM: %lu\n", (u_long)TYPE(h));
+ goto bad;
+ }
+ }
+ return (0);
+
+bad: if (die) {
+ abort();
+ /* NOTREACHED */
+ }
+ return (1);
+}
+
+/*
+ * __db_pr --
+ * Print out a data element.
+ *
+ * PUBLIC: void __db_pr __P((u_int8_t *, u_int32_t));
+ */
+void
+__db_pr(p, len)
+ u_int8_t *p;
+ u_int32_t len;
+{
+ FILE *fp;
+ u_int lastch;
+ int i;
+
+ fp = __db_prinit(NULL);
+
+ fprintf(fp, "len: %3lu", (u_long)len);
+ lastch = '.';
+ if (len != 0) {
+ fprintf(fp, " data: ");
+ for (i = len <= 20 ? len : 20; i > 0; --i, ++p) {
+ lastch = *p;
+ if (isprint(*p) || *p == '\n')
+ fprintf(fp, "%c", *p);
+ else
+ fprintf(fp, "0x%.2x", (u_int)*p);
+ }
+ if (len > 20) {
+ fprintf(fp, "...");
+ lastch = '.';
+ }
+ }
+ if (lastch != '\n')
+ fprintf(fp, "\n");
+}
+
+/*
+ * __db_prdbt --
+ * Print out a DBT data element.
+ *
+ * PUBLIC: int __db_prdbt __P((DBT *, int, FILE *));
+ */
+int
+__db_prdbt(dbtp, checkprint, fp)
+ DBT *dbtp;
+ int checkprint;
+ FILE *fp;
+{
+ static const char hex[] = "0123456789abcdef";
+ u_int8_t *p;
+ u_int32_t len;
+
+ /*
+ * !!!
+ * This routine is the routine that dumps out items in the format
+ * used by db_dump(1) and db_load(1). This means that the format
+ * cannot change.
+ */
+ if (checkprint) {
+ for (len = dbtp->size, p = dbtp->data; len--; ++p)
+ if (isprint(*p)) {
+ if (*p == '\\' && fprintf(fp, "\\") != 1)
+ return (EIO);
+ if (fprintf(fp, "%c", *p) != 1)
+ return (EIO);
+ } else
+ if (fprintf(fp, "\\%c%c",
+ hex[(u_int8_t)(*p & 0xf0) >> 4],
+ hex[*p & 0x0f]) != 3)
+ return (EIO);
+ } else
+ for (len = dbtp->size, p = dbtp->data; len--; ++p)
+ if (fprintf(fp, "%c%c",
+ hex[(u_int8_t)(*p & 0xf0) >> 4],
+ hex[*p & 0x0f]) != 2)
+ return (EIO);
+
+ return (fprintf(fp, "\n") == 1 ? 0 : EIO);
+}
+
+/*
+ * __db_proff --
+ * Print out an off-page element.
+ */
+static void
+__db_proff(vp)
+ void *vp;
+{
+ FILE *fp;
+ BOVERFLOW *bo;
+
+ fp = __db_prinit(NULL);
+
+ bo = vp;
+ switch (B_TYPE(bo->type)) {
+ case B_OVERFLOW:
+ fprintf(fp, "overflow: total len: %4lu page: %4lu\n",
+ (u_long)bo->tlen, (u_long)bo->pgno);
+ break;
+ case B_DUPLICATE:
+ fprintf(fp, "duplicate: page: %4lu\n", (u_long)bo->pgno);
+ break;
+ }
+}
+
+/*
+ * __db_prflags --
+ * Print out flags values.
+ *
+ * PUBLIC: void __db_prflags __P((u_int32_t, const FN *, FILE *));
+ */
+void
+__db_prflags(flags, fn, fp)
+ u_int32_t flags;
+ FN const *fn;
+ FILE *fp;
+{
+ const FN *fnp;
+ int found;
+ const char *sep;
+
+ sep = " (";
+ for (found = 0, fnp = fn; fnp->mask != 0; ++fnp)
+ if (LF_ISSET(fnp->mask)) {
+ fprintf(fp, "%s%s", sep, fnp->name);
+ sep = ", ";
+ found = 1;
+ }
+ if (found)
+ fprintf(fp, ")");
+}
+
+/*
+ * __db_psize --
+ * Get the page size.
+ */
+static void
+__db_psize(mpf)
+ DB_MPOOLFILE *mpf;
+{
+ BTMETA *mp;
+ db_pgno_t pgno;
+
+ set_psize = PSIZE_BOUNDARY - 1;
+
+ pgno = PGNO_METADATA;
+ if (memp_fget(mpf, &pgno, 0, &mp) != 0)
+ return;
+
+ switch (mp->magic) {
+ case DB_BTREEMAGIC:
+ case DB_HASHMAGIC:
+ set_psize = mp->pagesize;
+ break;
+ }
+ (void)memp_fput(mpf, mp, 0);
+}
diff --git a/usr/src/cmd/sendmail/db/db/db_rec.c b/usr/src/cmd/sendmail/db/db/db_rec.c
new file mode 100644
index 0000000000..5a34b2e236
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/db/db_rec.c
@@ -0,0 +1,611 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)db_rec.c 10.19 (Sleepycat) 9/27/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <string.h>
+#endif
+
+#include "db_int.h"
+#include "shqueue.h"
+#include "db_page.h"
+#include "log.h"
+#include "hash.h"
+#include "btree.h"
+
+/*
+ * PUBLIC: int __db_addrem_recover
+ * PUBLIC: __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+ *
+ * This log message is generated whenever we add or remove a duplicate
+ * to/from a duplicate page. On recover, we just do the opposite.
+ */
+int
+__db_addrem_recover(logp, dbtp, lsnp, redo, info)
+ DB_LOG *logp;
+ DBT *dbtp;
+ DB_LSN *lsnp;
+ int redo;
+ void *info;
+{
+ __db_addrem_args *argp;
+ DB *file_dbp;
+ DBC *dbc;
+ DB_MPOOLFILE *mpf;
+ PAGE *pagep;
+ u_int32_t change;
+ int cmp_n, cmp_p, ret;
+
+ REC_PRINT(__db_addrem_print);
+ REC_INTRO(__db_addrem_read);
+
+ if ((ret = memp_fget(mpf, &argp->pgno, 0, &pagep)) != 0) {
+ if (!redo) {
+ /*
+ * We are undoing and the page doesn't exist. That
+ * is equivalent to having a pagelsn of 0, so we
+ * would not have to undo anything. In this case,
+ * don't bother creating a page.
+ */
+ goto done;
+ } else
+ if ((ret = memp_fget(mpf,
+ &argp->pgno, DB_MPOOL_CREATE, &pagep)) != 0)
+ goto out;
+ }
+
+ cmp_n = log_compare(lsnp, &LSN(pagep));
+ cmp_p = log_compare(&LSN(pagep), &argp->pagelsn);
+ change = 0;
+ if ((cmp_p == 0 && redo && argp->opcode == DB_ADD_DUP) ||
+ (cmp_n == 0 && !redo && argp->opcode == DB_REM_DUP)) {
+
+ /* Need to redo an add, or undo a delete. */
+ if ((ret = __db_pitem(dbc, pagep, argp->indx, argp->nbytes,
+ argp->hdr.size == 0 ? NULL : &argp->hdr,
+ argp->dbt.size == 0 ? NULL : &argp->dbt)) != 0)
+ goto out;
+
+ change = DB_MPOOL_DIRTY;
+
+ } else if ((cmp_n == 0 && !redo && argp->opcode == DB_ADD_DUP) ||
+ (cmp_p == 0 && redo && argp->opcode == DB_REM_DUP)) {
+ /* Need to undo an add, or redo a delete. */
+ if ((ret = __db_ditem(dbc,
+ pagep, argp->indx, argp->nbytes)) != 0)
+ goto out;
+ change = DB_MPOOL_DIRTY;
+ }
+
+ if (change)
+ if (redo)
+ LSN(pagep) = *lsnp;
+ else
+ LSN(pagep) = argp->pagelsn;
+
+ if ((ret = memp_fput(mpf, pagep, change)) != 0)
+ goto out;
+
+done: *lsnp = argp->prev_lsn;
+ ret = 0;
+
+out: REC_CLOSE;
+}
+
+/*
+ * PUBLIC: int __db_split_recover __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+ */
+int
+__db_split_recover(logp, dbtp, lsnp, redo, info)
+ DB_LOG *logp;
+ DBT *dbtp;
+ DB_LSN *lsnp;
+ int redo;
+ void *info;
+{
+ __db_split_args *argp;
+ DB *file_dbp;
+ DBC *dbc;
+ DB_MPOOLFILE *mpf;
+ PAGE *pagep;
+ int change, cmp_n, cmp_p, ret;
+
+ REC_PRINT(__db_split_print);
+ REC_INTRO(__db_split_read);
+
+ if ((ret = memp_fget(mpf, &argp->pgno, 0, &pagep)) != 0)
+ if (!redo) {
+ /*
+ * We are undoing and the page doesn't exist. That
+ * is equivalent to having a pagelsn of 0, so we
+ * would not have to undo anything. In this case,
+ * don't bother creating a page.
+ */
+ goto done;
+ } else
+ if ((ret = memp_fget(mpf,
+ &argp->pgno, DB_MPOOL_CREATE, &pagep)) != 0)
+ goto out;
+
+ /*
+ * There are two types of log messages here, one for the old page
+ * and one for the new pages created. The original image in the
+ * SPLITOLD record is used for undo. The image in the SPLITNEW
+ * is used for redo. We should never have a case where there is
+ * a redo operation and the SPLITOLD record is on disk, but not
+ * the SPLITNEW record. Therefore, we only redo NEW messages
+ * and only undo OLD messages.
+ */
+
+ change = 0;
+ cmp_n = log_compare(lsnp, &LSN(pagep));
+ cmp_p = log_compare(&LSN(pagep), &argp->pagelsn);
+ if (cmp_p == 0 && redo) {
+ if (argp->opcode == DB_SPLITNEW) {
+ /* Need to redo the split described. */
+ memcpy(pagep,
+ argp->pageimage.data, argp->pageimage.size);
+ }
+ LSN(pagep) = *lsnp;
+ change = DB_MPOOL_DIRTY;
+ } else if (cmp_n == 0 && !redo) {
+ if (argp->opcode == DB_SPLITOLD) {
+ /* Put back the old image. */
+ memcpy(pagep,
+ argp->pageimage.data, argp->pageimage.size);
+ }
+ LSN(pagep) = argp->pagelsn;
+ change = DB_MPOOL_DIRTY;
+ }
+ if ((ret = memp_fput(mpf, pagep, change)) != 0)
+ goto out;
+
+done: *lsnp = argp->prev_lsn;
+ ret = 0;
+
+out: REC_CLOSE;
+}
+
+/*
+ * PUBLIC: int __db_big_recover __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+ */
+int
+__db_big_recover(logp, dbtp, lsnp, redo, info)
+ DB_LOG *logp;
+ DBT *dbtp;
+ DB_LSN *lsnp;
+ int redo;
+ void *info;
+{
+ __db_big_args *argp;
+ DB *file_dbp;
+ DBC *dbc;
+ DB_MPOOLFILE *mpf;
+ PAGE *pagep;
+ u_int32_t change;
+ int cmp_n, cmp_p, ret;
+
+ REC_PRINT(__db_big_print);
+ REC_INTRO(__db_big_read);
+
+ if ((ret = memp_fget(mpf, &argp->pgno, 0, &pagep)) != 0) {
+ if (!redo) {
+ /*
+ * We are undoing and the page doesn't exist. That
+ * is equivalent to having a pagelsn of 0, so we
+ * would not have to undo anything. In this case,
+ * don't bother creating a page.
+ */
+ ret = 0;
+ goto ppage;
+ } else
+ if ((ret = memp_fget(mpf,
+ &argp->pgno, DB_MPOOL_CREATE, &pagep)) != 0)
+ goto out;
+ }
+
+ /*
+ * There are three pages we need to check. The one on which we are
+ * adding data, the previous one whose next_pointer may have
+ * been updated, and the next one whose prev_pointer may have
+ * been updated.
+ */
+ cmp_n = log_compare(lsnp, &LSN(pagep));
+ cmp_p = log_compare(&LSN(pagep), &argp->pagelsn);
+ change = 0;
+ if ((cmp_p == 0 && redo && argp->opcode == DB_ADD_BIG) ||
+ (cmp_n == 0 && !redo && argp->opcode == DB_REM_BIG)) {
+ /* We are either redo-ing an add, or undoing a delete. */
+ P_INIT(pagep, file_dbp->pgsize, argp->pgno, argp->prev_pgno,
+ argp->next_pgno, 0, P_OVERFLOW);
+ OV_LEN(pagep) = argp->dbt.size;
+ OV_REF(pagep) = 1;
+ memcpy((u_int8_t *)pagep + P_OVERHEAD, argp->dbt.data,
+ argp->dbt.size);
+ PREV_PGNO(pagep) = argp->prev_pgno;
+ change = DB_MPOOL_DIRTY;
+ } else if ((cmp_n == 0 && !redo && argp->opcode == DB_ADD_BIG) ||
+ (cmp_p == 0 && redo && argp->opcode == DB_REM_BIG)) {
+ /*
+ * We are either undo-ing an add or redo-ing a delete.
+ * The page is about to be reclaimed in either case, so
+ * there really isn't anything to do here.
+ */
+ change = DB_MPOOL_DIRTY;
+ }
+ if (change)
+ LSN(pagep) = redo ? *lsnp : argp->pagelsn;
+
+ if ((ret = memp_fput(mpf, pagep, change)) != 0)
+ goto out;
+
+ /* Now check the previous page. */
+ppage: if (argp->prev_pgno != PGNO_INVALID) {
+ change = 0;
+ if ((ret = memp_fget(mpf, &argp->prev_pgno, 0, &pagep)) != 0)
+ if (!redo) {
+ /*
+ * We are undoing and the page doesn't exist.
+ * That is equivalent to having a pagelsn of 0,
+ * so we would not have to undo anything. In
+ * this case, don't bother creating a page.
+ */
+ *lsnp = argp->prev_lsn;
+ ret = 0;
+ goto npage;
+ } else
+ if ((ret = memp_fget(mpf, &argp->prev_pgno,
+ DB_MPOOL_CREATE, &pagep)) != 0)
+ goto out;
+
+ cmp_n = log_compare(lsnp, &LSN(pagep));
+ cmp_p = log_compare(&LSN(pagep), &argp->prevlsn);
+
+ if ((cmp_p == 0 && redo && argp->opcode == DB_ADD_BIG) ||
+ (cmp_n == 0 && !redo && argp->opcode == DB_REM_BIG)) {
+ /* Redo add, undo delete. */
+ NEXT_PGNO(pagep) = argp->pgno;
+ change = DB_MPOOL_DIRTY;
+ } else if ((cmp_n == 0 &&
+ !redo && argp->opcode == DB_ADD_BIG) ||
+ (cmp_p == 0 && redo && argp->opcode == DB_REM_BIG)) {
+ /* Redo delete, undo add. */
+ NEXT_PGNO(pagep) = argp->next_pgno;
+ change = DB_MPOOL_DIRTY;
+ }
+ if (change)
+ LSN(pagep) = redo ? *lsnp : argp->prevlsn;
+ if ((ret = memp_fput(mpf, pagep, change)) != 0)
+ goto out;
+ }
+
+ /* Now check the next page. Can only be set on a delete. */
+npage: if (argp->next_pgno != PGNO_INVALID) {
+ change = 0;
+ if ((ret = memp_fget(mpf, &argp->next_pgno, 0, &pagep)) != 0)
+ if (!redo) {
+ /*
+ * We are undoing and the page doesn't exist.
+ * That is equivalent to having a pagelsn of 0,
+ * so we would not have to undo anything. In
+ * this case, don't bother creating a page.
+ */
+ goto done;
+ } else
+ if ((ret = memp_fget(mpf, &argp->next_pgno,
+ DB_MPOOL_CREATE, &pagep)) != 0)
+ goto out;
+
+ cmp_n = log_compare(lsnp, &LSN(pagep));
+ cmp_p = log_compare(&LSN(pagep), &argp->nextlsn);
+ if (cmp_p == 0 && redo) {
+ PREV_PGNO(pagep) = PGNO_INVALID;
+ change = DB_MPOOL_DIRTY;
+ } else if (cmp_n == 0 && !redo) {
+ PREV_PGNO(pagep) = argp->pgno;
+ change = DB_MPOOL_DIRTY;
+ }
+ if (change)
+ LSN(pagep) = redo ? *lsnp : argp->nextlsn;
+ if ((ret = memp_fput(mpf, pagep, change)) != 0)
+ goto out;
+ }
+
+done: *lsnp = argp->prev_lsn;
+ ret = 0;
+
+out: REC_CLOSE;
+}
+
+/*
+ * __db_ovref_recover --
+ * Recovery function for __db_ovref().
+ *
+ * PUBLIC: int __db_ovref_recover __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+ */
+int
+__db_ovref_recover(logp, dbtp, lsnp, redo, info)
+ DB_LOG *logp;
+ DBT *dbtp;
+ DB_LSN *lsnp;
+ int redo;
+ void *info;
+{
+ __db_ovref_args *argp;
+ DB *file_dbp;
+ DBC *dbc;
+ DB_MPOOLFILE *mpf;
+ PAGE *pagep;
+ int modified, ret;
+
+ REC_PRINT(__db_ovref_print);
+ REC_INTRO(__db_ovref_read);
+
+ if ((ret = memp_fget(mpf, &argp->pgno, 0, &pagep)) != 0) {
+ (void)__db_pgerr(file_dbp, argp->pgno);
+ goto out;
+ }
+
+ modified = 0;
+ if (log_compare(&LSN(pagep), &argp->lsn) == 0 && redo) {
+ /* Need to redo update described. */
+ OV_REF(pagep) += argp->adjust;
+
+ pagep->lsn = *lsnp;
+ modified = 1;
+ } else if (log_compare(lsnp, &LSN(pagep)) == 0 && !redo) {
+ /* Need to undo update described. */
+ OV_REF(pagep) -= argp->adjust;
+
+ pagep->lsn = argp->lsn;
+ modified = 1;
+ }
+ if ((ret = memp_fput(mpf, pagep, modified ? DB_MPOOL_DIRTY : 0)) != 0)
+ goto out;
+
+done: *lsnp = argp->prev_lsn;
+ ret = 0;
+
+out: REC_CLOSE;
+}
+
+/*
+ * __db_relink_recover --
+ * Recovery function for relink.
+ *
+ * PUBLIC: int __db_relink_recover
+ * PUBLIC: __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+ */
+int
+__db_relink_recover(logp, dbtp, lsnp, redo, info)
+ DB_LOG *logp;
+ DBT *dbtp;
+ DB_LSN *lsnp;
+ int redo;
+ void *info;
+{
+ __db_relink_args *argp;
+ DB *file_dbp;
+ DBC *dbc;
+ DB_MPOOLFILE *mpf;
+ PAGE *pagep;
+ int cmp_n, cmp_p, modified, ret;
+
+ REC_PRINT(__db_relink_print);
+ REC_INTRO(__db_relink_read);
+
+ /*
+ * There are up to three pages we need to check -- the page, and the
+ * previous and next pages, if they existed. For a page add operation,
+ * the current page is the result of a split and is being recovered
+ * elsewhere, so all we need do is recover the next page.
+ */
+ if ((ret = memp_fget(mpf, &argp->pgno, 0, &pagep)) != 0) {
+ if (redo) {
+ (void)__db_pgerr(file_dbp, argp->pgno);
+ goto out;
+ }
+ goto next;
+ }
+ if (argp->opcode == DB_ADD_PAGE)
+ goto next;
+
+ modified = 0;
+ if (log_compare(&LSN(pagep), &argp->lsn) == 0 && redo) {
+ /* Redo the relink. */
+ pagep->lsn = *lsnp;
+ modified = 1;
+ } else if (log_compare(lsnp, &LSN(pagep)) == 0 && !redo) {
+ /* Undo the relink. */
+ pagep->next_pgno = argp->next;
+ pagep->prev_pgno = argp->prev;
+
+ pagep->lsn = argp->lsn;
+ modified = 1;
+ }
+ if ((ret = memp_fput(mpf, pagep, modified ? DB_MPOOL_DIRTY : 0)) != 0)
+ goto out;
+
+next: if ((ret = memp_fget(mpf, &argp->next, 0, &pagep)) != 0) {
+ if (redo) {
+ (void)__db_pgerr(file_dbp, argp->next);
+ goto out;
+ }
+ goto prev;
+ }
+ modified = 0;
+ cmp_n = log_compare(lsnp, &LSN(pagep));
+ cmp_p = log_compare(&LSN(pagep), &argp->lsn_next);
+ if ((argp->opcode == DB_REM_PAGE && cmp_p == 0 && redo) ||
+ (argp->opcode == DB_ADD_PAGE && cmp_n == 0 && !redo)) {
+ /* Redo the remove or undo the add. */
+ pagep->prev_pgno = argp->prev;
+
+ pagep->lsn = *lsnp;
+ modified = 1;
+ } else if ((argp->opcode == DB_REM_PAGE && cmp_n == 0 && !redo) ||
+ (argp->opcode == DB_ADD_PAGE && cmp_p == 0 && redo)) {
+ /* Undo the remove or redo the add. */
+ pagep->prev_pgno = argp->pgno;
+
+ pagep->lsn = argp->lsn_next;
+ modified = 1;
+ }
+ if ((ret = memp_fput(mpf, pagep, modified ? DB_MPOOL_DIRTY : 0)) != 0)
+ goto out;
+ if (argp->opcode == DB_ADD_PAGE)
+ goto done;
+
+prev: if ((ret = memp_fget(mpf, &argp->prev, 0, &pagep)) != 0) {
+ if (redo) {
+ (void)__db_pgerr(file_dbp, argp->prev);
+ goto out;
+ }
+ goto done;
+ }
+ modified = 0;
+ if (log_compare(&LSN(pagep), &argp->lsn_prev) == 0 && redo) {
+ /* Redo the relink. */
+ pagep->next_pgno = argp->next;
+
+ pagep->lsn = *lsnp;
+ modified = 1;
+ } else if (log_compare(lsnp, &LSN(pagep)) == 0 && !redo) {
+ /* Undo the relink. */
+ pagep->next_pgno = argp->pgno;
+
+ pagep->lsn = argp->lsn_prev;
+ modified = 1;
+ }
+ if ((ret = memp_fput(mpf, pagep, modified ? DB_MPOOL_DIRTY : 0)) != 0)
+ goto out;
+
+done: *lsnp = argp->prev_lsn;
+ ret = 0;
+
+out: REC_CLOSE;
+}
+
+/*
+ * PUBLIC: int __db_addpage_recover
+ * PUBLIC: __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+ */
+int
+__db_addpage_recover(logp, dbtp, lsnp, redo, info)
+ DB_LOG *logp;
+ DBT *dbtp;
+ DB_LSN *lsnp;
+ int redo;
+ void *info;
+{
+ __db_addpage_args *argp;
+ DB *file_dbp;
+ DBC *dbc;
+ DB_MPOOLFILE *mpf;
+ PAGE *pagep;
+ u_int32_t change;
+ int cmp_n, cmp_p, ret;
+
+ REC_PRINT(__db_addpage_print);
+ REC_INTRO(__db_addpage_read);
+
+ /*
+ * We need to check two pages: the old one and the new one onto
+ * which we're going to add duplicates. Do the old one first.
+ */
+ if ((ret = memp_fget(mpf, &argp->pgno, 0, &pagep)) != 0)
+ goto out;
+
+ change = 0;
+ cmp_n = log_compare(lsnp, &LSN(pagep));
+ cmp_p = log_compare(&LSN(pagep), &argp->lsn);
+ if (cmp_p == 0 && redo) {
+ NEXT_PGNO(pagep) = argp->nextpgno;
+
+ LSN(pagep) = *lsnp;
+ change = DB_MPOOL_DIRTY;
+ } else if (cmp_n == 0 && !redo) {
+ NEXT_PGNO(pagep) = PGNO_INVALID;
+
+ LSN(pagep) = argp->lsn;
+ change = DB_MPOOL_DIRTY;
+ }
+ if ((ret = memp_fput(mpf, pagep, change)) != 0)
+ goto out;
+
+ if ((ret = memp_fget(mpf, &argp->nextpgno, 0, &pagep)) != 0)
+ if (!redo) {
+ /*
+ * We are undoing and the page doesn't exist. That
+ * is equivalent to having a pagelsn of 0, so we
+ * would not have to undo anything. In this case,
+ * don't bother creating a page.
+ */
+ goto done;
+ } else
+ if ((ret = memp_fget(mpf,
+ &argp->nextpgno, DB_MPOOL_CREATE, &pagep)) != 0)
+ goto out;
+
+ change = 0;
+ cmp_n = log_compare(lsnp, &LSN(pagep));
+ cmp_p = log_compare(&LSN(pagep), &argp->nextlsn);
+ if (cmp_p == 0 && redo) {
+ PREV_PGNO(pagep) = argp->pgno;
+
+ LSN(pagep) = *lsnp;
+ change = DB_MPOOL_DIRTY;
+ } else if (cmp_n == 0 && !redo) {
+ PREV_PGNO(pagep) = PGNO_INVALID;
+
+ LSN(pagep) = argp->nextlsn;
+ change = DB_MPOOL_DIRTY;
+ }
+ if ((ret = memp_fput(mpf, pagep, change)) != 0)
+ goto out;
+
+done: *lsnp = argp->prev_lsn;
+ ret = 0;
+
+out: REC_CLOSE;
+}
+
+/*
+ * __db_debug_recover --
+ * Recovery function for debug.
+ *
+ * PUBLIC: int __db_debug_recover __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+ */
+int
+__db_debug_recover(logp, dbtp, lsnp, redo, info)
+ DB_LOG *logp;
+ DBT *dbtp;
+ DB_LSN *lsnp;
+ int redo;
+ void *info;
+{
+ __db_debug_args *argp;
+ int ret;
+
+ COMPQUIET(redo, 0);
+ COMPQUIET(logp, NULL);
+
+ REC_PRINT(__db_debug_print);
+ REC_NOOP_INTRO(__db_debug_read);
+
+ *lsnp = argp->prev_lsn;
+ ret = 0;
+
+ REC_NOOP_CLOSE;
+}
diff --git a/usr/src/cmd/sendmail/db/db/db_region.c b/usr/src/cmd/sendmail/db/db/db_region.c
new file mode 100644
index 0000000000..7b57cda134
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/db/db_region.c
@@ -0,0 +1,870 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)db_region.c 10.53 (Sleepycat) 11/10/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#endif
+
+#include "db_int.h"
+#include "common_ext.h"
+
+static int __db_growregion __P((REGINFO *, size_t));
+
+/*
+ * __db_rattach --
+ * Optionally create and attach to a shared memory region.
+ *
+ * PUBLIC: int __db_rattach __P((REGINFO *));
+ */
+int
+__db_rattach(infop)
+ REGINFO *infop;
+{
+ RLAYOUT *rlp, rl;
+ size_t grow_region, size;
+ ssize_t nr, nw;
+ u_int32_t flags, mbytes, bytes;
+ u_int8_t *p;
+ int malloc_possible, ret, retry_cnt;
+
+ grow_region = 0;
+ malloc_possible = 1;
+ ret = retry_cnt = 0;
+
+ /* Round off the requested size to the next page boundary. */
+ DB_ROUNDOFF(infop->size, DB_VMPAGESIZE);
+
+ /* Some architectures have hard limits on the maximum region size. */
+#ifdef DB_REGIONSIZE_MAX
+ if (infop->size > DB_REGIONSIZE_MAX) {
+ __db_err(infop->dbenv, "__db_rattach: cache size too large");
+ return (EINVAL);
+ }
+#endif
+
+ /* Intialize the return information in the REGINFO structure. */
+loop: infop->addr = NULL;
+ infop->fd = -1;
+ infop->segid = INVALID_SEGID;
+ if (infop->name != NULL) {
+ __os_freestr(infop->name);
+ infop->name = NULL;
+ }
+ F_CLR(infop, REGION_CANGROW | REGION_CREATED);
+
+#ifndef HAVE_SPINLOCKS
+ /*
+ * XXX
+ * Lacking spinlocks, we must have a file descriptor for fcntl(2)
+ * locking, which implies using mmap(2) to map in a regular file.
+ * (Theoretically, we could probably get a file descriptor to lock
+ * other types of shared regions, but I don't see any reason to
+ * bother.)
+ *
+ * Since we may be using shared memory regions, e.g., shmget(2),
+ * and not mmap of regular files, the backing file may be only a
+ * few tens of bytes in length. So, this depends on the ability
+ * to fcntl lock file offsets much larger than the physical file.
+ */
+ malloc_possible = 0;
+#endif
+
+#ifdef __hppa
+ /*
+ * XXX
+ * HP-UX won't permit mutexes to live in anything but shared memory.
+ * Instantiate a shared region file on that architecture, regardless.
+ */
+ malloc_possible = 0;
+#endif
+ /*
+ * If a region is truly private, malloc the memory. That's faster
+ * than either anonymous memory or a shared file.
+ */
+ if (malloc_possible && F_ISSET(infop, REGION_PRIVATE)) {
+ if ((ret = __os_malloc(infop->size, NULL, &infop->addr)) != 0)
+ return (ret);
+
+ /*
+ * It's sometimes significantly faster to page-fault in all of
+ * the region's pages before we run the application, as we see
+ * nasty side-effects when we page-fault while holding various
+ * locks, i.e., the lock takes a long time to acquire because
+ * of the underlying page fault, and the other threads convoy
+ * behind the lock holder.
+ */
+ if (DB_GLOBAL(db_region_init))
+ for (p = infop->addr;
+ p < (u_int8_t *)infop->addr + infop->size;
+ p += DB_VMPAGESIZE)
+ p[0] = '\0';
+
+ F_SET(infop, REGION_CREATED | REGION_MALLOC);
+ goto region_init;
+ }
+
+ /*
+ * Get the name of the region (creating the file if a temporary file
+ * is being used). The dbenv contains the current DB environment,
+ * including naming information. The path argument may be a file or
+ * a directory. If path is a directory, it must exist and file is the
+ * file name to be created inside the directory. If path is a file,
+ * then file must be NULL.
+ */
+ if ((ret = __db_appname(infop->dbenv, infop->appname, infop->path,
+ infop->file, infop->dbflags, &infop->fd, &infop->name)) != 0)
+ return (ret);
+ if (infop->fd != -1)
+ F_SET(infop, REGION_CREATED);
+
+ /*
+ * Try to create the file, if we have authority. We have to make sure
+ * that multiple threads/processes attempting to simultaneously create
+ * the region are properly ordered, so we open it using DB_CREATE and
+ * DB_EXCL, so two attempts to create the region will return failure in
+ * one.
+ */
+ if (infop->fd == -1 && infop->dbflags & DB_CREATE) {
+ flags = infop->dbflags;
+ LF_SET(DB_EXCL);
+ if ((ret = __db_open(infop->name,
+ flags, flags, infop->mode, &infop->fd)) == 0)
+ F_SET(infop, REGION_CREATED);
+ else
+ if (ret != EEXIST)
+ goto errmsg;
+ }
+
+ /* If we couldn't create the file, try and open it. */
+ if (infop->fd == -1) {
+ flags = infop->dbflags;
+ LF_CLR(DB_CREATE | DB_EXCL);
+ if ((ret = __db_open(infop->name,
+ flags, flags, infop->mode, &infop->fd)) != 0)
+ goto errmsg;
+ }
+
+ /*
+ * There are three cases we support:
+ * 1. Named anonymous memory (shmget(2)).
+ * 2. Unnamed anonymous memory (mmap(2): MAP_ANON/MAP_ANONYMOUS).
+ * 3. Memory backed by a regular file (mmap(2)).
+ *
+ * We instantiate a backing file in all cases, which contains at least
+ * the RLAYOUT structure, and in case #3, contains the actual region.
+ * This is necessary for a couple of reasons:
+ *
+ * First, the mpool region uses temporary files to name regions, and
+ * since you may have multiple regions in the same directory, we need
+ * a filesystem name to ensure that they don't collide.
+ *
+ * Second, applications are allowed to forcibly remove regions, even
+ * if they don't know anything about them other than the name. If a
+ * region is backed by anonymous memory, there has to be some way for
+ * the application to find out that information, and, in some cases,
+ * determine ID information for the anonymous memory.
+ */
+ if (F_ISSET(infop, REGION_CREATED)) {
+ /*
+ * If we're using anonymous memory to back this region, set
+ * the flag.
+ */
+ if (DB_GLOBAL(db_region_anon))
+ F_SET(infop, REGION_ANONYMOUS);
+
+ /*
+ * If we're using a regular file to back a region we created,
+ * grow it to the specified size.
+ */
+ if (!DB_GLOBAL(db_region_anon) &&
+ (ret = __db_growregion(infop, infop->size)) != 0)
+ goto err;
+ } else {
+ /*
+ * If we're joining a region, figure out what it looks like.
+ *
+ * XXX
+ * We have to figure out if the file is a regular file backing
+ * a region that we want to map into our address space, or a
+ * file with the information we need to find a shared anonymous
+ * region that we want to map into our address space.
+ *
+ * All this noise is because some systems don't have a coherent
+ * VM and buffer cache, and worse, if you mix operations on the
+ * VM and buffer cache, half the time you hang the system.
+ *
+ * There are two possibilities. If the file is the size of an
+ * RLAYOUT structure, then we know that the real region is in
+ * shared memory, because otherwise it would be bigger. (As
+ * the RLAYOUT structure size is smaller than a disk sector,
+ * the only way it can be this size is if deliberately written
+ * that way.) In which case, retrieve the information we need
+ * from the RLAYOUT structure and use it to acquire the shared
+ * memory.
+ *
+ * If the structure is larger than an RLAYOUT structure, then
+ * the file is backing the shared memory region, and we use
+ * the current size of the file without reading any information
+ * from the file itself so that we don't confuse the VM.
+ *
+ * And yes, this makes me want to take somebody and kill them,
+ * but I can't think of any other solution.
+ */
+ if ((ret = __os_ioinfo(infop->name,
+ infop->fd, &mbytes, &bytes, NULL)) != 0)
+ goto errmsg;
+ size = mbytes * MEGABYTE + bytes;
+
+ if (size <= sizeof(RLAYOUT)) {
+ /*
+ * If the size is too small, the read fails or the
+ * valid flag is incorrect, assume it's because the
+ * RLAYOUT information hasn't been written out yet,
+ * and retry.
+ */
+ if (size < sizeof(RLAYOUT))
+ goto retry;
+ if ((ret =
+ __os_read(infop->fd, &rl, sizeof(rl), &nr)) != 0)
+ goto retry;
+ if (rl.valid != DB_REGIONMAGIC)
+ goto retry;
+
+ /* Copy the size, memory id and characteristics. */
+ size = rl.size;
+ infop->segid = rl.segid;
+ if (F_ISSET(&rl, REGION_ANONYMOUS))
+ F_SET(infop, REGION_ANONYMOUS);
+ }
+
+ /*
+ * If the region is larger than we think, that's okay, use the
+ * current size. If it's smaller than we think, and we were
+ * just using the default size, that's okay, use the current
+ * size. If it's smaller than we think and we really care,
+ * save the size and we'll catch that further down -- we can't
+ * correct it here because we have to have a lock to grow the
+ * region.
+ */
+ if (infop->size > size && !F_ISSET(infop, REGION_SIZEDEF))
+ grow_region = infop->size;
+ infop->size = size;
+ }
+
+ /*
+ * Map the region into our address space. If we're creating it, the
+ * underlying routines will make it the right size.
+ *
+ * There are at least two cases where we can "reasonably" fail when
+ * we attempt to map in the region. On Windows/95, closing the last
+ * reference to a region causes it to be zeroed out. On UNIX, when
+ * using the shmget(2) interfaces, the region will no longer exist
+ * if the system was rebooted. In these cases, the underlying map call
+ * returns EAGAIN, and we *remove* our file and try again. There are
+ * obvious races in doing this, but it should eventually settle down
+ * to a winner and then things should proceed normally.
+ */
+ if ((ret = __db_mapregion(infop->name, infop)) != 0)
+ if (ret == EAGAIN) {
+ /*
+ * Pretend we created the region even if we didn't so
+ * that our error processing unlinks it.
+ */
+ F_SET(infop, REGION_CREATED);
+ ret = 0;
+ goto retry;
+ } else
+ goto err;
+
+region_init:
+ /*
+ * Initialize the common region information.
+ *
+ * !!!
+ * We have to order the region creates so that two processes don't try
+ * to simultaneously create the region. This is handled by using the
+ * DB_CREATE and DB_EXCL flags when we create the "backing" region file.
+ *
+ * We also have to order region joins so that processes joining regions
+ * never see inconsistent data. We'd like to play permissions games
+ * with the backing file, but we can't because WNT filesystems won't
+ * open a file mode 0.
+ */
+ rlp = (RLAYOUT *)infop->addr;
+ if (F_ISSET(infop, REGION_CREATED)) {
+ /*
+ * The process creating the region acquires a lock before it
+ * sets the valid flag. Any processes joining the region will
+ * check the valid flag before acquiring the lock.
+ *
+ * Check the return of __db_mutex_init() and __db_mutex_lock(),
+ * even though we don't usually check elsewhere. This is the
+ * first lock we initialize and acquire, and we have to know if
+ * it fails. (It CAN fail, e.g., SunOS, when using fcntl(2)
+ * for locking, with an in-memory filesystem specified as the
+ * database home.)
+ */
+ if ((ret = __db_mutex_init(&rlp->lock,
+ MUTEX_LOCK_OFFSET(rlp, &rlp->lock))) != 0 ||
+ (ret = __db_mutex_lock(&rlp->lock, infop->fd)) != 0)
+ goto err;
+
+ /* Initialize the remaining region information. */
+ rlp->refcnt = 1;
+ rlp->size = infop->size;
+ db_version(&rlp->majver, &rlp->minver, &rlp->patch);
+ rlp->panic = 0;
+ rlp->segid = infop->segid;
+ rlp->flags = 0;
+ if (F_ISSET(infop, REGION_ANONYMOUS))
+ F_SET(rlp, REGION_ANONYMOUS);
+
+ /*
+ * Fill in the valid field last -- use a magic number, memory
+ * may not be zero-filled, and we want to minimize the chance
+ * for collision.
+ */
+ rlp->valid = DB_REGIONMAGIC;
+
+ /*
+ * If the region is anonymous, write the RLAYOUT information
+ * into the backing file so that future region join and unlink
+ * calls can find it.
+ *
+ * XXX
+ * We MUST do the seek before we do the write. On Win95, while
+ * closing the last reference to an anonymous shared region
+ * doesn't discard the region, it does zero it out. So, the
+ * REGION_CREATED may be set, but the file may have already
+ * been written and the file descriptor may be at the end of
+ * the file.
+ */
+ if (F_ISSET(infop, REGION_ANONYMOUS)) {
+ if ((ret = __os_seek(infop->fd, 0, 0, 0, 0, 0)) != 0)
+ goto err;
+ if ((ret =
+ __os_write(infop->fd, rlp, sizeof(*rlp), &nw)) != 0)
+ goto err;
+ }
+ } else {
+ /* Check to see if the region has had catastrophic failure. */
+ if (rlp->panic) {
+ ret = DB_RUNRECOVERY;
+ goto err;
+ }
+
+ /*
+ * Check the valid flag to ensure the region is initialized.
+ * If the valid flag has not been set, the mutex may not have
+ * been initialized, and an attempt to get it could lead to
+ * random behavior.
+ */
+ if (rlp->valid != DB_REGIONMAGIC)
+ goto retry;
+
+ /* Get the region lock. */
+ (void)__db_mutex_lock(&rlp->lock, infop->fd);
+
+ /*
+ * We now own the region. There are a couple of things that
+ * may have gone wrong, however.
+ *
+ * Problem #1: while we were waiting for the lock, the region
+ * was deleted. Detected by re-checking the valid flag, since
+ * it's cleared by the delete region routines.
+ */
+ if (rlp->valid != DB_REGIONMAGIC) {
+ (void)__db_mutex_unlock(&rlp->lock, infop->fd);
+ goto retry;
+ }
+
+ /*
+ * Problem #3: when we checked the size of the file, it was
+ * still growing as part of creation. Detected by the fact
+ * that infop->size isn't the same size as the region.
+ */
+ if (infop->size != rlp->size) {
+ (void)__db_mutex_unlock(&rlp->lock, infop->fd);
+ goto retry;
+ }
+
+ /* Increment the reference count. */
+ ++rlp->refcnt;
+ }
+
+ /* Return the region in a locked condition. */
+
+ if (0) {
+errmsg: __db_err(infop->dbenv, "%s: %s", infop->name, strerror(ret));
+
+err:
+retry: /* Discard the region. */
+ if (infop->addr != NULL) {
+ (void)__db_unmapregion(infop);
+ infop->addr = NULL;
+ }
+
+ /* Discard the backing file. */
+ if (infop->fd != -1) {
+ (void)__os_close(infop->fd);
+ infop->fd = -1;
+
+ if (F_ISSET(infop, REGION_CREATED))
+ (void)__os_unlink(infop->name);
+ }
+
+ /* Discard the name. */
+ if (infop->name != NULL) {
+ __os_freestr(infop->name);
+ infop->name = NULL;
+ }
+
+ /*
+ * If we had a temporary error, wait a few seconds and
+ * try again.
+ */
+ if (ret == 0) {
+ if (++retry_cnt <= 3) {
+ __os_sleep(retry_cnt * 2, 0);
+ goto loop;
+ }
+ ret = EAGAIN;
+ }
+ }
+
+ /*
+ * XXX
+ * HP-UX won't permit mutexes to live in anything but shared memory.
+ * Instantiate a shared region file on that architecture, regardless.
+ *
+ * XXX
+ * There's a problem in cleaning this up on application exit, or on
+ * application failure. If an application opens a database without
+ * an environment, we create a temporary backing mpool region for it.
+ * That region is marked REGION_PRIVATE, but as HP-UX won't permit
+ * mutexes to live in anything but shared memory, we instantiate a
+ * real file plus a memory region of some form. If the application
+ * crashes, the necessary information to delete the backing file and
+ * any system region (e.g., the shmget(2) segment ID) is no longer
+ * available. We can't completely fix the problem, but we try.
+ *
+ * The underlying UNIX __db_mapregion() code preferentially uses the
+ * mmap(2) interface with the MAP_ANON/MAP_ANONYMOUS flags for regions
+ * that are marked REGION_PRIVATE. This means that we normally aren't
+ * holding any system resources when we get here, in which case we can
+ * delete the backing file. This results in a short race, from the
+ * __db_open() call above to here.
+ *
+ * If, for some reason, we are holding system resources when we get
+ * here, we don't have any choice -- we can't delete the backing file
+ * because we may need it to detach from the resources. Set the
+ * REGION_LASTDETACH flag, so that we do all necessary cleanup when
+ * the application closes the region.
+ */
+ if (F_ISSET(infop, REGION_PRIVATE) && !F_ISSET(infop, REGION_MALLOC))
+ if (F_ISSET(infop, REGION_HOLDINGSYS))
+ F_SET(infop, REGION_LASTDETACH);
+ else {
+ F_SET(infop, REGION_REMOVED);
+ F_CLR(infop, REGION_CANGROW);
+
+ (void)__os_close(infop->fd);
+ (void)__os_unlink(infop->name);
+ }
+
+ return (ret);
+}
+
+/*
+ * __db_rdetach --
+ * De-attach from a shared memory region.
+ *
+ * PUBLIC: int __db_rdetach __P((REGINFO *));
+ */
+int
+__db_rdetach(infop)
+ REGINFO *infop;
+{
+ RLAYOUT *rlp;
+ int detach, ret, t_ret;
+
+ ret = 0;
+
+ /*
+ * If the region was removed when it was created, no further action
+ * is required.
+ */
+ if (F_ISSET(infop, REGION_REMOVED))
+ goto done;
+ /*
+ * If the region was created in memory returned by malloc, the only
+ * action required is freeing the memory.
+ */
+ if (F_ISSET(infop, REGION_MALLOC)) {
+ __os_free(infop->addr, 0);
+ goto done;
+ }
+
+ /* Otherwise, attach to the region and optionally delete it. */
+ rlp = infop->addr;
+
+ /* Get the lock. */
+ (void)__db_mutex_lock(&rlp->lock, infop->fd);
+
+ /* Decrement the reference count. */
+ if (rlp->refcnt == 0)
+ __db_err(infop->dbenv,
+ "region rdetach: reference count went to zero!");
+ else
+ --rlp->refcnt;
+
+ /*
+ * If we're going to remove the region, clear the valid flag so
+ * that any region join that's blocked waiting for us will know
+ * what happened.
+ */
+ detach = 0;
+ if (F_ISSET(infop, REGION_LASTDETACH))
+ if (rlp->refcnt == 0) {
+ detach = 1;
+ rlp->valid = 0;
+ } else
+ ret = EBUSY;
+
+ /* Release the lock. */
+ (void)__db_mutex_unlock(&rlp->lock, infop->fd);
+
+ /* Close the backing file descriptor. */
+ (void)__os_close(infop->fd);
+ infop->fd = -1;
+
+ /* Discard our mapping of the region. */
+ if ((t_ret = __db_unmapregion(infop)) != 0 && ret == 0)
+ ret = t_ret;
+
+ /* Discard the region itself. */
+ if (detach) {
+ if ((t_ret =
+ __db_unlinkregion(infop->name, infop) != 0) && ret == 0)
+ ret = t_ret;
+ if ((t_ret = __os_unlink(infop->name) != 0) && ret == 0)
+ ret = t_ret;
+ }
+
+done: /* Discard the name. */
+ if (infop->name != NULL) {
+ __os_freestr(infop->name);
+ infop->name = NULL;
+ }
+
+ return (ret);
+}
+
+/*
+ * __db_runlink --
+ * Remove a region.
+ *
+ * PUBLIC: int __db_runlink __P((REGINFO *, int));
+ */
+int
+__db_runlink(infop, force)
+ REGINFO *infop;
+ int force;
+{
+ RLAYOUT rl, *rlp;
+ size_t size;
+ ssize_t nr;
+ u_int32_t mbytes, bytes;
+ int fd, ret, t_ret;
+ char *name;
+
+ /*
+ * XXX
+ * We assume that we've created a new REGINFO structure for this
+ * call, not used one that was already initialized. Regardless,
+ * if anyone is planning to use it after we're done, they're going
+ * to be sorely disappointed.
+ *
+ * If force isn't set, we attach to the region, set a flag to delete
+ * the region on last close, and let the region delete code do the
+ * work.
+ */
+ if (!force) {
+ if ((ret = __db_rattach(infop)) != 0)
+ return (ret);
+
+ rlp = (RLAYOUT *)infop->addr;
+ (void)__db_mutex_unlock(&rlp->lock, infop->fd);
+
+ F_SET(infop, REGION_LASTDETACH);
+
+ return (__db_rdetach(infop));
+ }
+
+ /*
+ * Otherwise, we don't want to attach to the region. We may have been
+ * called to clean up if a process died leaving a region locked and/or
+ * corrupted, which could cause the attach to hang.
+ */
+ if ((ret = __db_appname(infop->dbenv, infop->appname,
+ infop->path, infop->file, infop->dbflags, NULL, &name)) != 0)
+ return (ret);
+
+ /*
+ * An underlying file is created for all regions other than private
+ * (REGION_PRIVATE) ones, regardless of whether or not it's used to
+ * back the region. If that file doesn't exist, we're done.
+ */
+ if (__os_exists(name, NULL) != 0) {
+ __os_freestr(name);
+ return (0);
+ }
+
+ /*
+ * See the comments in __db_rattach -- figure out if this is a regular
+ * file backing a region or if it's a regular file with information
+ * about a region.
+ */
+ if ((ret = __db_open(name, DB_RDONLY, DB_RDONLY, 0, &fd)) != 0)
+ goto errmsg;
+ if ((ret = __os_ioinfo(name, fd, &mbytes, &bytes, NULL)) != 0)
+ goto errmsg;
+ size = mbytes * MEGABYTE + bytes;
+
+ if (size <= sizeof(RLAYOUT)) {
+ if ((ret = __os_read(fd, &rl, sizeof(rl), &nr)) != 0)
+ goto errmsg;
+ if (rl.valid != DB_REGIONMAGIC) {
+ __db_err(infop->dbenv,
+ "%s: illegal region magic number", name);
+ ret = EINVAL;
+ goto err;
+ }
+
+ /* Set the size, memory id and characteristics. */
+ infop->size = rl.size;
+ infop->segid = rl.segid;
+ if (F_ISSET(&rl, REGION_ANONYMOUS))
+ F_SET(infop, REGION_ANONYMOUS);
+ } else {
+ infop->size = size;
+ infop->segid = INVALID_SEGID;
+ }
+
+ /* Remove the underlying region. */
+ ret = __db_unlinkregion(name, infop);
+
+ /*
+ * Unlink the backing file. Close the open file descriptor first,
+ * because some architectures (e.g., Win32) won't unlink a file if
+ * open file descriptors remain.
+ */
+ (void)__os_close(fd);
+ if ((t_ret = __os_unlink(name)) != 0 && ret == 0)
+ ret = t_ret;
+
+ if (0) {
+errmsg: __db_err(infop->dbenv, "%s: %s", name, strerror(ret));
+err: (void)__os_close(fd);
+ }
+
+ __os_freestr(name);
+ return (ret);
+}
+
+/*
+ * __db_rgrow --
+ * Extend a region.
+ *
+ * PUBLIC: int __db_rgrow __P((REGINFO *, size_t));
+ */
+int
+__db_rgrow(infop, new_size)
+ REGINFO *infop;
+ size_t new_size;
+{
+ RLAYOUT *rlp;
+ size_t increment;
+ int ret;
+
+ /*
+ * !!!
+ * This routine MUST be called with the region already locked.
+ */
+
+ /* The underlying routines have flagged if this region can grow. */
+ if (!F_ISSET(infop, REGION_CANGROW))
+ return (EINVAL);
+
+ /*
+ * Round off the requested size to the next page boundary, and
+ * determine the additional space required.
+ */
+ rlp = (RLAYOUT *)infop->addr;
+ DB_ROUNDOFF(new_size, DB_VMPAGESIZE);
+ increment = new_size - rlp->size;
+
+ if ((ret = __db_growregion(infop, increment)) != 0)
+ return (ret);
+
+ /* Update the on-disk region size. */
+ rlp->size = new_size;
+
+ /* Detach from and reattach to the region. */
+ return (__db_rreattach(infop, new_size));
+}
+
+/*
+ * __db_growregion --
+ * Grow a shared memory region.
+ */
+static int
+__db_growregion(infop, increment)
+ REGINFO *infop;
+ size_t increment;
+{
+ db_pgno_t pages;
+ size_t i;
+ ssize_t nr, nw;
+ u_int32_t relative;
+ int ret;
+ char buf[DB_VMPAGESIZE];
+
+ /* Seek to the end of the region. */
+ if ((ret = __os_seek(infop->fd, 0, 0, 0, 0, SEEK_END)) != 0)
+ goto err;
+
+ /* Write nuls to the new bytes. */
+ memset(buf, 0, sizeof(buf));
+
+ /*
+ * Some systems require that all of the bytes of the region be
+ * written before it can be mapped and accessed randomly, and
+ * other systems don't zero out the pages.
+ */
+ if (__db_mapinit())
+ /* Extend the region by writing each new page. */
+ for (i = 0; i < increment; i += DB_VMPAGESIZE) {
+ if ((ret =
+ __os_write(infop->fd, buf, sizeof(buf), &nw)) != 0)
+ goto err;
+ if (nw != sizeof(buf))
+ goto eio;
+ }
+ else {
+ /*
+ * Extend the region by writing the last page. If the region
+ * is >4Gb, increment may be larger than the maximum possible
+ * seek "relative" argument, as it's an unsigned 32-bit value.
+ * Break the offset into pages of 1MB each so that we don't
+ * overflow (2^20 + 2^32 is bigger than any memory I expect
+ * to see for awhile).
+ */
+ pages = (increment - DB_VMPAGESIZE) / MEGABYTE;
+ relative = (increment - DB_VMPAGESIZE) % MEGABYTE;
+ if ((ret = __os_seek(infop->fd,
+ MEGABYTE, pages, relative, 0, SEEK_CUR)) != 0)
+ goto err;
+ if ((ret = __os_write(infop->fd, buf, sizeof(buf), &nw)) != 0)
+ goto err;
+ if (nw != sizeof(buf))
+ goto eio;
+
+ /*
+ * It's sometimes significantly faster to page-fault in all of
+ * the region's pages before we run the application, as we see
+ * nasty side-effects when we page-fault while holding various
+ * locks, i.e., the lock takes a long time to acquire because
+ * of the underlying page fault, and the other threads convoy
+ * behind the lock holder.
+ *
+ * We also use REGION_INIT to guarantee that there is enough
+ * disk space for the region, so we also write a byte to each
+ * page. Reading the byte is insufficient as some systems
+ * (e.g., Solaris) do not instantiate disk pages to satisfy
+ * a read, and so we don't know if there is enough disk space
+ * or not.
+ */
+ if (DB_GLOBAL(db_region_init)) {
+ pages = increment / MEGABYTE;
+ relative = increment % MEGABYTE;
+ if ((ret = __os_seek(infop->fd,
+ MEGABYTE, pages, relative, 1, SEEK_END)) != 0)
+ goto err;
+
+ /* Write a byte to each page. */
+ for (i = 0; i < increment; i += DB_VMPAGESIZE) {
+ if ((ret =
+ __os_write(infop->fd, buf, 1, &nr)) != 0)
+ goto err;
+ if (nr != 1)
+ goto eio;
+ if ((ret = __os_seek(infop->fd,
+ 0, 0, DB_VMPAGESIZE - 1, 0, SEEK_CUR)) != 0)
+ goto err;
+ }
+ }
+ }
+ return (0);
+
+eio: ret = EIO;
+err: __db_err(infop->dbenv, "region grow: %s", strerror(ret));
+ return (ret);
+}
+
+/*
+ * __db_rreattach --
+ * Detach from and reattach to a region.
+ *
+ * PUBLIC: int __db_rreattach __P((REGINFO *, size_t));
+ */
+int
+__db_rreattach(infop, new_size)
+ REGINFO *infop;
+ size_t new_size;
+{
+ int ret;
+
+#ifdef DIAGNOSTIC
+ if (infop->name == NULL) {
+ __db_err(infop->dbenv, "__db_rreattach: name was NULL");
+ return (EINVAL);
+ }
+#endif
+ /*
+ * If we're growing an already mapped region, we have to unmap it
+ * and get it back. We have it locked, so nobody else can get in,
+ * which makes it fairly straight-forward to do, as everybody else
+ * is going to block while we do the unmap/remap. NB: if we fail
+ * to get it back, the pooch is genuinely screwed, because we can
+ * never release the lock we're holding.
+ *
+ * Detach from the region. We have to do this first so architectures
+ * that don't permit a file to be mapped into different places in the
+ * address space simultaneously, e.g., HP's PaRisc, will work.
+ */
+ if ((ret = __db_unmapregion(infop)) != 0)
+ return (ret);
+
+ /* Update the caller's REGINFO size to the new map size. */
+ infop->size = new_size;
+
+ /* Attach to the region. */
+ ret = __db_mapregion(infop->name, infop);
+
+ return (ret);
+}
diff --git a/usr/src/cmd/sendmail/db/db/db_ret.c b/usr/src/cmd/sendmail/db/db/db_ret.c
new file mode 100644
index 0000000000..9f0d0ecf8d
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/db/db_ret.c
@@ -0,0 +1,153 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)db_ret.c 10.16 (Sleepycat) 10/4/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <errno.h>
+#include <string.h>
+#endif
+
+#include "db_int.h"
+#include "db_page.h"
+#include "btree.h"
+#include "db_am.h"
+
+/*
+ * __db_ret --
+ * Build return DBT.
+ *
+ * PUBLIC: int __db_ret __P((DB *,
+ * PUBLIC: PAGE *, u_int32_t, DBT *, void **, u_int32_t *));
+ */
+int
+__db_ret(dbp, h, indx, dbt, memp, memsize)
+ DB *dbp;
+ PAGE *h;
+ u_int32_t indx;
+ DBT *dbt;
+ void **memp;
+ u_int32_t *memsize;
+{
+ BKEYDATA *bk;
+ HOFFPAGE ho;
+ BOVERFLOW *bo;
+ u_int32_t len;
+ u_int8_t *hk;
+ void *data;
+
+ switch (TYPE(h)) {
+ case P_HASH:
+ hk = P_ENTRY(h, indx);
+ if (HPAGE_PTYPE(hk) == H_OFFPAGE) {
+ memcpy(&ho, hk, sizeof(HOFFPAGE));
+ return (__db_goff(dbp, dbt,
+ ho.tlen, ho.pgno, memp, memsize));
+ }
+ len = LEN_HKEYDATA(h, dbp->pgsize, indx);
+ data = HKEYDATA_DATA(hk);
+ break;
+ case P_DUPLICATE:
+ case P_LBTREE:
+ case P_LRECNO:
+ bk = GET_BKEYDATA(h, indx);
+ if (B_TYPE(bk->type) == B_OVERFLOW) {
+ bo = (BOVERFLOW *)bk;
+ return (__db_goff(dbp, dbt,
+ bo->tlen, bo->pgno, memp, memsize));
+ }
+ len = bk->len;
+ data = bk->data;
+ break;
+ default:
+ return (__db_pgfmt(dbp, h->pgno));
+ }
+
+ return (__db_retcopy(dbt, data, len, memp, memsize,
+ F_ISSET(dbt, DB_DBT_INTERNAL) ? NULL : dbp->db_malloc));
+}
+
+/*
+ * __db_retcopy --
+ * Copy the returned data into the user's DBT, handling special flags.
+ *
+ * PUBLIC: int __db_retcopy __P((DBT *,
+ * PUBLIC: void *, u_int32_t, void **, u_int32_t *, void *(*)(size_t)));
+ */
+int
+__db_retcopy(dbt, data, len, memp, memsize, db_malloc)
+ DBT *dbt;
+ void *data;
+ u_int32_t len;
+ void **memp;
+ u_int32_t *memsize;
+ void *(*db_malloc) __P((size_t));
+{
+ int ret;
+
+ /* If returning a partial record, reset the length. */
+ if (F_ISSET(dbt, DB_DBT_PARTIAL)) {
+ data = (u_int8_t *)data + dbt->doff;
+ if (len > dbt->doff) {
+ len -= dbt->doff;
+ if (len > dbt->dlen)
+ len = dbt->dlen;
+ } else
+ len = 0;
+ }
+
+ /*
+ * Return the length of the returned record in the DBT size field.
+ * This satisfies the requirement that if we're using user memory
+ * and insufficient memory was provided, return the amount necessary
+ * in the size field.
+ */
+ dbt->size = len;
+
+ /*
+ * Allocate memory to be owned by the application: DB_DBT_MALLOC.
+ *
+ * !!!
+ * We always allocate memory, even if we're copying out 0 bytes. This
+ * guarantees consistency, i.e., the application can always free memory
+ * without concern as to how many bytes of the record were requested.
+ *
+ * Use the memory specified by the application: DB_DBT_USERMEM.
+ *
+ * !!!
+ * If the length we're going to copy is 0, the application-supplied
+ * memory pointer is allowed to be NULL.
+ */
+ if (F_ISSET(dbt, DB_DBT_MALLOC)) {
+ if ((ret = __os_malloc(len, db_malloc, &dbt->data)) != 0)
+ return (ret);
+ } else if (F_ISSET(dbt, DB_DBT_USERMEM)) {
+ if (len != 0 && (dbt->data == NULL || dbt->ulen < len))
+ return (ENOMEM);
+ } else if (memp == NULL || memsize == NULL) {
+ return (EINVAL);
+ } else {
+ if (len != 0 && (*memsize == 0 || *memsize < len)) {
+ if ((ret = __os_realloc(memp, len)) != 0) {
+ *memsize = 0;
+ return (ret);
+ }
+ *memsize = len;
+ }
+ dbt->data = *memp;
+ }
+
+ if (len != 0)
+ memcpy(dbt->data, data, len);
+ return (0);
+}
diff --git a/usr/src/cmd/sendmail/db/db/db_salloc.c b/usr/src/cmd/sendmail/db/db/db_salloc.c
new file mode 100644
index 0000000000..d888ae8c7e
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/db/db_salloc.c
@@ -0,0 +1,301 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)db_salloc.c 10.14 (Sleepycat) 11/16/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <errno.h>
+#include <string.h>
+#endif
+
+#include "db_int.h"
+#include "shqueue.h"
+#include "common_ext.h"
+
+/*
+ * Implement shared memory region allocation, using simple first-fit algorithm.
+ * The model is that we take a "chunk" of shared memory store and begin carving
+ * it up into areas, similarly to how malloc works. We do coalescing on free.
+ *
+ * The "len" field in the __data struct contains the length of the free region
+ * (less the size_t bytes that holds the length). We use the address provided
+ * by the caller to find this length, which allows us to free a chunk without
+ * requiring that the caller pass in the length of the chunk they're freeing.
+ */
+SH_LIST_HEAD(__head);
+struct __data {
+ size_t len;
+ SH_LIST_ENTRY links;
+};
+
+/*
+ * __db_shalloc_init --
+ * Initialize the area as one large chunk.
+ *
+ * PUBLIC: void __db_shalloc_init __P((void *, size_t));
+ */
+void
+__db_shalloc_init(area, size)
+ void *area;
+ size_t size;
+{
+ struct __data *elp;
+ struct __head *hp;
+
+ hp = area;
+ SH_LIST_INIT(hp);
+
+ elp = (struct __data *)(hp + 1);
+ elp->len = size - sizeof(struct __head) - sizeof(elp->len);
+ SH_LIST_INSERT_HEAD(hp, elp, links, __data);
+}
+
+/*
+ * __db_shalloc --
+ * Allocate some space from the shared region.
+ *
+ * PUBLIC: int __db_shalloc __P((void *, size_t, size_t, void *));
+ */
+int
+__db_shalloc(p, len, align, retp)
+ void *p, *retp;
+ size_t len, align;
+{
+ struct __data *elp;
+ size_t *sp;
+ void *rp;
+
+ /*
+ * We never allocate less than the size of a struct __data, align
+ * to less than a size_t boundary, or align to something that's not
+ * a multiple of a size_t.
+ */
+ if (len < sizeof(struct __data))
+ len = sizeof(struct __data);
+ align = align <= sizeof(size_t) ?
+ sizeof(size_t) : ALIGN(align, sizeof(size_t));
+
+ /* Walk the list, looking for a slot. */
+ for (elp = SH_LIST_FIRST((struct __head *)p, __data);
+ elp != NULL;
+ elp = SH_LIST_NEXT(elp, links, __data)) {
+ /*
+ * Calculate the value of the returned pointer if we were to
+ * use this chunk.
+ * + Find the end of the chunk.
+ * + Subtract the memory the user wants.
+ * + Find the closest previous correctly-aligned address.
+ */
+ rp = (u_int8_t *)elp + sizeof(size_t) + elp->len;
+ rp = (u_int8_t *)rp - len;
+ rp = (u_int8_t *)((ALIGNTYPE)rp & ~(align - 1));
+
+ /*
+ * Rp may now point before elp->links, in which case the chunk
+ * was too small, and we have to try again.
+ */
+ if ((u_int8_t *)rp < (u_int8_t *)&elp->links)
+ continue;
+
+ *(void **)retp = rp;
+
+#define SHALLOC_FRAGMENT 32
+ /*
+ * If there are at least SHALLOC_FRAGMENT additional bytes of
+ * memory, divide the chunk into two chunks.
+ */
+ if ((u_int8_t *)rp >=
+ (u_int8_t *)&elp->links + SHALLOC_FRAGMENT) {
+ sp = rp;
+ *--sp = elp->len -
+ ((u_int8_t *)rp - (u_int8_t *)&elp->links);
+ elp->len -= *sp + sizeof(size_t);
+ return (0);
+ }
+
+ /*
+ * Otherwise, we return the entire chunk, wasting some amount
+ * of space to keep the list compact. However, because the
+ * address we're returning to the user may not be the address
+ * of the start of the region for alignment reasons, set the
+ * size_t length fields back to the "real" length field to a
+ * flag value, so that we can find the real length during free.
+ */
+#define ILLEGAL_SIZE 1
+ SH_LIST_REMOVE(elp, links, __data);
+ for (sp = rp; (u_int8_t *)--sp >= (u_int8_t *)&elp->links;)
+ *sp = ILLEGAL_SIZE;
+ return (0);
+ }
+
+ /* Nothing found large enough; need to grow the region. */
+ return (ENOMEM);
+}
+
+/*
+ * __db_shalloc_free --
+ * Free a shared memory allocation.
+ *
+ * PUBLIC: void __db_shalloc_free __P((void *, void *));
+ */
+void
+__db_shalloc_free(regionp, ptr)
+ void *regionp, *ptr;
+{
+ struct __data *elp, *lastp, *newp;
+ struct __head *hp;
+ size_t free_size, *sp;
+ int merged;
+
+ /*
+ * Step back over flagged length fields to find the beginning of
+ * the object and its real size.
+ */
+ for (sp = (size_t *)ptr; sp[-1] == ILLEGAL_SIZE; --sp)
+ ;
+ ptr = sp;
+
+ newp = (struct __data *)((u_int8_t *)ptr - sizeof(size_t));
+ free_size = newp->len;
+
+ /* Trash the returned memory. */
+#ifdef DIAGNOSTIC
+ memset(ptr, 0xdb, free_size);
+#endif
+
+ /*
+ * Walk the list, looking for where this entry goes.
+ *
+ * We keep the free list sorted by address so that coalescing is
+ * trivial.
+ *
+ * XXX
+ * Probably worth profiling this to see how expensive it is.
+ */
+ hp = (struct __head *)regionp;
+ for (elp = SH_LIST_FIRST(hp, __data), lastp = NULL;
+ elp != NULL && (void *)elp < (void *)ptr;
+ lastp = elp, elp = SH_LIST_NEXT(elp, links, __data))
+ ;
+
+ /*
+ * Elp is either NULL (we reached the end of the list), or the slot
+ * after the one that's being returned. Lastp is either NULL (we're
+ * returning the first element of the list) or the element before the
+ * one being returned.
+ *
+ * Check for coalescing with the next element.
+ */
+ merged = 0;
+ if ((u_int8_t *)ptr + free_size == (u_int8_t *)elp) {
+ newp->len += elp->len + sizeof(size_t);
+ SH_LIST_REMOVE(elp, links, __data);
+ if (lastp != NULL)
+ SH_LIST_INSERT_AFTER(lastp, newp, links, __data);
+ else
+ SH_LIST_INSERT_HEAD(hp, newp, links, __data);
+ merged = 1;
+ }
+
+ /* Check for coalescing with the previous element. */
+ if (lastp != NULL && (u_int8_t *)lastp +
+ lastp->len + sizeof(size_t) == (u_int8_t *)newp) {
+ lastp->len += newp->len + sizeof(size_t);
+
+ /*
+ * If we have already put the new element into the list take
+ * it back off again because it's just been merged with the
+ * previous element.
+ */
+ if (merged)
+ SH_LIST_REMOVE(newp, links, __data);
+ merged = 1;
+ }
+
+ if (!merged)
+ if (lastp == NULL)
+ SH_LIST_INSERT_HEAD(hp, newp, links, __data);
+ else
+ SH_LIST_INSERT_AFTER(lastp, newp, links, __data);
+}
+
+/*
+ * __db_shalloc_count --
+ * Return the amount of memory on the free list.
+ *
+ * PUBLIC: size_t __db_shalloc_count __P((void *));
+ */
+size_t
+__db_shalloc_count(addr)
+ void *addr;
+{
+ struct __data *elp;
+ size_t count;
+
+ count = 0;
+ for (elp = SH_LIST_FIRST((struct __head *)addr, __data);
+ elp != NULL;
+ elp = SH_LIST_NEXT(elp, links, __data))
+ count += elp->len;
+
+ return (count);
+}
+
+/*
+ * __db_shsizeof --
+ * Return the size of a shalloc'd piece of memory.
+ *
+ * PUBLIC: size_t __db_shsizeof __P((void *));
+ */
+size_t
+__db_shsizeof(ptr)
+ void *ptr;
+{
+ struct __data *elp;
+ size_t *sp;
+
+ /*
+ * Step back over flagged length fields to find the beginning of
+ * the object and its real size.
+ */
+ for (sp = (size_t *)ptr; sp[-1] == ILLEGAL_SIZE; --sp)
+ ;
+
+ elp = (struct __data *)((u_int8_t *)sp - sizeof(size_t));
+ return (elp->len);
+}
+
+/*
+ * __db_shalloc_dump --
+ *
+ * PUBLIC: void __db_shalloc_dump __P((void *, FILE *));
+ */
+void
+__db_shalloc_dump(addr, fp)
+ void *addr;
+ FILE *fp;
+{
+ struct __data *elp;
+
+ /* Make it easy to call from the debugger. */
+ if (fp == NULL)
+ fp = stderr;
+
+ fprintf(fp, "%s\nMemory free list\n", DB_LINE);
+
+ for (elp = SH_LIST_FIRST((struct __head *)addr, __data);
+ elp != NULL;
+ elp = SH_LIST_NEXT(elp, links, __data))
+ fprintf(fp, "%#lx: %lu\t", (u_long)elp, (u_long)elp->len);
+ fprintf(fp, "\n");
+}
diff --git a/usr/src/cmd/sendmail/db/db/db_shash.c b/usr/src/cmd/sendmail/db/db/db_shash.c
new file mode 100644
index 0000000000..3f48a55907
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/db/db_shash.c
@@ -0,0 +1,126 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)db_shash.c 10.9 (Sleepycat) 4/10/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+#endif
+
+#include "db_int.h"
+#include "shqueue.h"
+#include "common_ext.h"
+
+/*
+ * Table of good hash values. Up to ~250,000 buckets, we use powers of 2.
+ * After that, we slow the rate of increase by half. For each choice, we
+ * then use a nearby prime number as the hash value.
+ *
+ * If a terabyte is the maximum cache we'll see, and we assume there are
+ * 10 1K buckets on each hash chain, then 107374182 is the maximum number
+ * of buckets we'll ever need.
+ */
+static const struct {
+ u_int32_t power;
+ u_int32_t prime;
+} list[] = {
+ { 64, 67}, /* 2^6 */
+ { 128, 131}, /* 2^7 */
+ { 256, 257}, /* 2^8 */
+ { 512, 521}, /* 2^9 */
+ { 1024, 1031}, /* 2^10 */
+ { 2048, 2053}, /* 2^11 */
+ { 4096, 4099}, /* 2^12 */
+ { 8192, 8191}, /* 2^13 */
+ { 16384, 16381}, /* 2^14 */
+ { 32768, 32771}, /* 2^15 */
+ { 65536, 65537}, /* 2^16 */
+ { 131072, 131071}, /* 2^17 */
+ { 262144, 262147}, /* 2^18 */
+ { 393216, 393209}, /* 2^18 + 2^18/2 */
+ { 524288, 524287}, /* 2^19 */
+ { 786432, 786431}, /* 2^19 + 2^19/2 */
+ { 1048576, 1048573}, /* 2^20 */
+ { 1572864, 1572869}, /* 2^20 + 2^20/2 */
+ { 2097152, 2097169}, /* 2^21 */
+ { 3145728, 3145721}, /* 2^21 + 2^21/2 */
+ { 4194304, 4194301}, /* 2^22 */
+ { 6291456, 6291449}, /* 2^22 + 2^22/2 */
+ { 8388608, 8388617}, /* 2^23 */
+ { 12582912, 12582917}, /* 2^23 + 2^23/2 */
+ { 16777216, 16777213}, /* 2^24 */
+ { 25165824, 25165813}, /* 2^24 + 2^24/2 */
+ { 33554432, 33554393}, /* 2^25 */
+ { 50331648, 50331653}, /* 2^25 + 2^25/2 */
+ { 67108864, 67108859}, /* 2^26 */
+ { 100663296, 100663291}, /* 2^26 + 2^26/2 */
+ { 134217728, 134217757}, /* 2^27 */
+ { 201326592, 201326611}, /* 2^27 + 2^27/2 */
+ { 268435456, 268435459}, /* 2^28 */
+ { 402653184, 402653189}, /* 2^28 + 2^28/2 */
+ { 536870912, 536870909}, /* 2^29 */
+ { 805306368, 805306357}, /* 2^29 + 2^29/2 */
+ {1073741824, 1073741827}, /* 2^30 */
+ {0, 0}
+};
+
+/*
+ * __db_tablesize --
+ * Choose a size for the hash table.
+ *
+ * PUBLIC: int __db_tablesize __P((u_int32_t));
+ */
+int
+__db_tablesize(n_buckets)
+ u_int32_t n_buckets;
+{
+ int i;
+
+ /*
+ * We try to be clever about how big we make the hash tables. Use a
+ * prime number close to the "suggested" number of elements that will
+ * be in the hash table. Use 64 as the minimum hash table size.
+ *
+ * Ref: Sedgewick, Algorithms in C, "Hash Functions"
+ */
+ if (n_buckets < 64)
+ n_buckets = 64;
+
+ for (i = 0;; ++i) {
+ if (list[i].power == 0) {
+ --i;
+ break;
+ }
+ if (list[i].power >= n_buckets)
+ break;
+ }
+ return (list[i].prime);
+}
+
+/*
+ * __db_hashinit --
+ * Initialize a hash table that resides in shared memory.
+ *
+ * PUBLIC: void __db_hashinit __P((void *, u_int32_t));
+ */
+void
+__db_hashinit(begin, nelements)
+ void *begin;
+ u_int32_t nelements;
+{
+ u_int32_t i;
+ SH_TAILQ_HEAD(hash_head) *headp;
+
+ headp = (struct hash_head *)begin;
+
+ for (i = 0; i < nelements; i++, headp++)
+ SH_TAILQ_INIT(headp);
+}
diff --git a/usr/src/cmd/sendmail/db/db_int.h b/usr/src/cmd/sendmail/db/db_int.h
new file mode 100644
index 0000000000..3b92704bb9
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/db_int.h
@@ -0,0 +1,409 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ *
+ * @(#)db_int.h 10.77 (Sleepycat) 1/3/99
+ */
+
+#ifndef _DB_INTERNAL_H_
+#define _DB_INTERNAL_H_
+
+#include "db.h" /* Standard DB include file. */
+#include "queue.h"
+#include "shqueue.h"
+
+/*******************************************************
+ * General purpose constants and macros.
+ *******************************************************/
+#define UINT16_T_MAX 0xffff /* Maximum 16 bit unsigned. */
+#define UINT32_T_MAX 0xffffffff /* Maximum 32 bit unsigned. */
+
+#define DB_MIN_PGSIZE 0x000200 /* Minimum page size. */
+#define DB_MAX_PGSIZE 0x010000 /* Maximum page size. */
+
+#define DB_MINCACHE 10 /* Minimum cached pages */
+
+#define MEGABYTE 1048576
+
+/*
+ * If we are unable to determine the underlying filesystem block size, use
+ * 8K on the grounds that most OS's use less than 8K as their VM page size.
+ */
+#define DB_DEF_IOSIZE (8 * 1024)
+
+/*
+ * Aligning items to particular sizes or in pages or memory. ALIGNP is a
+ * separate macro, as we've had to cast the pointer to different integral
+ * types on different architectures.
+ *
+ * We cast pointers into unsigned longs when manipulating them because C89
+ * guarantees that u_long is the largest available integral type and further,
+ * to never generate overflows. However, neither C89 or C9X requires that
+ * any integer type be large enough to hold a pointer, although C9X created
+ * the intptr_t type, which is guaranteed to hold a pointer but may or may
+ * not exist. At some point in the future, we should test for intptr_t and
+ * use it where available.
+ */
+#undef ALIGNTYPE
+#define ALIGNTYPE u_long
+#undef ALIGNP
+#define ALIGNP(value, bound) ALIGN((ALIGNTYPE)value, bound)
+#undef ALIGN
+#define ALIGN(value, bound) (((value) + (bound) - 1) & ~((bound) - 1))
+
+/*
+ * There are several on-page structures that are declared to have a number of
+ * fields followed by a variable length array of items. The structure size
+ * without including the variable length array or the address of the first of
+ * those elements can be found using SSZ.
+ *
+ * This macro can also be used to find the offset of a structure element in a
+ * structure. This is used in various places to copy structure elements from
+ * unaligned memory references, e.g., pointers into a packed page.
+ *
+ * There are two versions because compilers object if you take the address of
+ * an array.
+ */
+#undef SSZ
+#define SSZ(name, field) ((int)&(((name *)0)->field))
+
+#undef SSZA
+#define SSZA(name, field) ((int)&(((name *)0)->field[0]))
+
+/* Macros to return per-process address, offsets based on shared regions. */
+#define R_ADDR(base, offset) ((void *)((u_int8_t *)((base)->addr) + offset))
+#define R_OFFSET(base, p) ((u_int8_t *)(p) - (u_int8_t *)(base)->addr)
+
+#define DB_DEFAULT 0x000000 /* No flag was specified. */
+
+/* Structure used to print flag values. */
+typedef struct __fn {
+ u_int32_t mask; /* Flag value. */
+ const char *name; /* Flag name. */
+} FN;
+
+/* Set, clear and test flags. */
+#define F_SET(p, f) (p)->flags |= (f)
+#define F_CLR(p, f) (p)->flags &= ~(f)
+#define F_ISSET(p, f) ((p)->flags & (f))
+#define LF_SET(f) (flags |= (f))
+#define LF_CLR(f) (flags &= ~(f))
+#define LF_ISSET(f) (flags & (f))
+
+/*
+ * Panic check:
+ * All interfaces check the panic flag, if it's set, the tree is dead.
+ */
+#define DB_PANIC_CHECK(dbp) { \
+ if ((dbp)->dbenv != NULL && (dbp)->dbenv->db_panic != 0) \
+ return (DB_RUNRECOVERY); \
+}
+
+/* Display separator string. */
+#undef DB_LINE
+#define DB_LINE "=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-="
+
+/* Unused, or not-used-yet variable. "Shut that bloody compiler up!" */
+#define COMPQUIET(n, v) (n) = (v)
+
+/*
+ * Purify and similar run-time tools complain about unitialized reads/writes
+ * for structure fields whose only purpose is padding.
+ */
+#define UMRW(v) (v) = 0
+
+/*
+ * Win16 needs specific syntax on callback functions. Nobody else cares.
+ */
+#ifndef DB_CALLBACK
+#define DB_CALLBACK /* Nothing. */
+#endif
+
+/*******************************************************
+ * Files.
+ *******************************************************/
+ /*
+ * We use 1024 as the maximum path length. It's too hard to figure out what
+ * the real path length is, as it was traditionally stored in <sys/param.h>,
+ * and that file isn't always available.
+ */
+#undef MAXPATHLEN
+#define MAXPATHLEN 1024
+
+#define PATH_DOT "." /* Current working directory. */
+#define PATH_SEPARATOR "/" /* Path separator character. */
+
+/*******************************************************
+ * Mutex support.
+ *******************************************************/
+#include <sys/machlock.h>
+typedef lock_t tsl_t;
+
+
+/*
+ * !!!
+ * Various systems require different alignments for mutexes (the worst we've
+ * seen so far is 16-bytes on some HP architectures). The mutex (tsl_t) must
+ * be first in the db_mutex_t structure, which must itself be first in the
+ * region. This ensures the alignment is as returned by mmap(2), which should
+ * be sufficient. All other mutex users must ensure proper alignment locally.
+ */
+#define MUTEX_ALIGNMENT sizeof(int)
+
+/*
+ * The offset of a mutex in memory.
+ *
+ * !!!
+ * Not an off_t, so backing file offsets MUST be less than 4Gb. See the
+ * off field of the db_mutex_t as well.
+ */
+#define MUTEX_LOCK_OFFSET(a, b) ((u_int32_t)((u_int8_t *)b - (u_int8_t *)a))
+
+typedef struct _db_mutex_t {
+#ifdef HAVE_SPINLOCKS
+ tsl_t tsl_resource; /* Resource test and set. */
+#ifdef DIAGNOSTIC
+ u_int32_t pid; /* Lock holder: 0 or process pid. */
+#endif
+#else
+ u_int32_t off; /* Backing file offset. */
+ u_int32_t pid; /* Lock holder: 0 or process pid. */
+#endif
+ u_int32_t spins; /* Spins before block. */
+ u_int32_t mutex_set_wait; /* Granted after wait. */
+ u_int32_t mutex_set_nowait; /* Granted without waiting. */
+} db_mutex_t;
+
+#include "mutex_ext.h"
+
+/*******************************************************
+ * Access methods.
+ *******************************************************/
+/* Lock/unlock a DB thread. */
+#define DB_THREAD_LOCK(dbp) \
+ if (F_ISSET(dbp, DB_AM_THREAD)) \
+ (void)__db_mutex_lock((db_mutex_t *)(dbp)->mutexp, -1);
+#define DB_THREAD_UNLOCK(dbp) \
+ if (F_ISSET(dbp, DB_AM_THREAD)) \
+ (void)__db_mutex_unlock((db_mutex_t *)(dbp)->mutexp, -1);
+
+/*******************************************************
+ * Environment.
+ *******************************************************/
+/* Type passed to __db_appname(). */
+typedef enum {
+ DB_APP_NONE=0, /* No type (region). */
+ DB_APP_DATA, /* Data file. */
+ DB_APP_LOG, /* Log file. */
+ DB_APP_TMP /* Temporary file. */
+} APPNAME;
+
+/*******************************************************
+ * Shared memory regions.
+ *******************************************************/
+/*
+ * The shared memory regions share an initial structure so that the general
+ * region code can handle races between the region being deleted and other
+ * processes waiting on the region mutex.
+ *
+ * !!!
+ * Note, the mutex must be the first entry in the region; see comment above.
+ */
+typedef struct _rlayout {
+ db_mutex_t lock; /* Region mutex. */
+#define DB_REGIONMAGIC 0x120897
+ u_int32_t valid; /* Valid magic number. */
+ u_int32_t refcnt; /* Region reference count. */
+ size_t size; /* Region length. */
+ int majver; /* Major version number. */
+ int minver; /* Minor version number. */
+ int patch; /* Patch version number. */
+ int panic; /* Region is dead. */
+#define INVALID_SEGID -1
+ int segid; /* shmget(2) ID, or Win16 segment ID. */
+
+#define REGION_ANONYMOUS 0x01 /* Region is/should be in anon mem. */
+ u_int32_t flags;
+} RLAYOUT;
+
+/*
+ * DB creates all regions on 4K boundaries out of sheer paranoia, so that
+ * we don't make the underlying VM unhappy.
+ */
+#define DB_VMPAGESIZE (4 * 1024)
+#define DB_ROUNDOFF(n, round) { \
+ (n) += (round) - 1; \
+ (n) -= (n) % (round); \
+}
+
+/*
+ * The interface to region attach is nasty, there is a lot of complex stuff
+ * going on, which has to be retained between create/attach and detach. The
+ * REGINFO structure keeps track of it.
+ */
+struct __db_reginfo; typedef struct __db_reginfo REGINFO;
+struct __db_reginfo {
+ /* Arguments. */
+ DB_ENV *dbenv; /* Region naming info. */
+ APPNAME appname; /* Region naming info. */
+ char *path; /* Region naming info. */
+ const char *file; /* Region naming info. */
+ int mode; /* Region mode, if a file. */
+ size_t size; /* Region size. */
+ u_int32_t dbflags; /* Region file open flags, if a file. */
+
+ /* Results. */
+ char *name; /* Region name. */
+ void *addr; /* Region address. */
+ int fd; /* Fcntl(2) locking file descriptor.
+ NB: this is only valid if a regular
+ file is backing the shared region,
+ and mmap(2) is being used to map it
+ into our address space. */
+ int segid; /* shmget(2) ID, or Win16 segment ID. */
+ void *wnt_handle; /* Win/NT HANDLE. */
+
+ /* Shared flags. */
+/* 0x0001 COMMON MASK with RLAYOUT structure. */
+#define REGION_CANGROW 0x0002 /* Can grow. */
+#define REGION_CREATED 0x0004 /* Created. */
+#define REGION_HOLDINGSYS 0x0008 /* Holding system resources. */
+#define REGION_LASTDETACH 0x0010 /* Delete on last detach. */
+#define REGION_MALLOC 0x0020 /* Created in malloc'd memory. */
+#define REGION_PRIVATE 0x0040 /* Private to thread/process. */
+#define REGION_REMOVED 0x0080 /* Already deleted. */
+#define REGION_SIZEDEF 0x0100 /* Use default region size if exists. */
+ u_int32_t flags;
+};
+
+/*******************************************************
+ * Mpool.
+ *******************************************************/
+/*
+ * File types for DB access methods. Negative numbers are reserved to DB.
+ */
+#define DB_FTYPE_BTREE -1 /* Btree. */
+#define DB_FTYPE_HASH -2 /* Hash. */
+
+/* Structure used as the DB pgin/pgout pgcookie. */
+typedef struct __dbpginfo {
+ size_t db_pagesize; /* Underlying page size. */
+ int needswap; /* If swapping required. */
+} DB_PGINFO;
+
+/*******************************************************
+ * Log.
+ *******************************************************/
+/* Initialize an LSN to 'zero'. */
+#define ZERO_LSN(LSN) { \
+ (LSN).file = 0; \
+ (LSN).offset = 0; \
+}
+
+/* Return 1 if LSN is a 'zero' lsn, otherwise return 0. */
+#define IS_ZERO_LSN(LSN) ((LSN).file == 0)
+
+/* Test if we need to log a change. */
+#define DB_LOGGING(dbc) \
+ (F_ISSET((dbc)->dbp, DB_AM_LOGGING) && !F_ISSET(dbc, DBC_RECOVER))
+
+#ifdef DIAGNOSTIC
+/*
+ * Debugging macro to log operations.
+ * If DEBUG_WOP is defined, log operations that modify the database.
+ * If DEBUG_ROP is defined, log operations that read the database.
+ *
+ * D dbp
+ * T txn
+ * O operation (string)
+ * K key
+ * A data
+ * F flags
+ */
+#define LOG_OP(C, T, O, K, A, F) { \
+ DB_LSN _lsn; \
+ DBT _op; \
+ if (DB_LOGGING((C))) { \
+ memset(&_op, 0, sizeof(_op)); \
+ _op.data = O; \
+ _op.size = strlen(O) + 1; \
+ (void)__db_debug_log((C)->dbp->dbenv->lg_info, \
+ T, &_lsn, 0, &_op, (C)->dbp->log_fileid, K, A, F); \
+ } \
+}
+#ifdef DEBUG_ROP
+#define DEBUG_LREAD(C, T, O, K, A, F) LOG_OP(C, T, O, K, A, F)
+#else
+#define DEBUG_LREAD(C, T, O, K, A, F)
+#endif
+#ifdef DEBUG_WOP
+#define DEBUG_LWRITE(C, T, O, K, A, F) LOG_OP(C, T, O, K, A, F)
+#else
+#define DEBUG_LWRITE(C, T, O, K, A, F)
+#endif
+#else
+#define DEBUG_LREAD(C, T, O, K, A, F)
+#define DEBUG_LWRITE(C, T, O, K, A, F)
+#endif /* DIAGNOSTIC */
+
+/*******************************************************
+ * Transactions and recovery.
+ *******************************************************/
+/*
+ * Out of band value for a lock. The locks are returned to callers as offsets
+ * into the lock regions. Since the RLAYOUT structure begins all regions, an
+ * offset of 0 is guaranteed not to be a valid lock.
+ */
+#define LOCK_INVALID 0
+
+/* The structure allocated for every transaction. */
+struct __db_txn {
+ DB_TXNMGR *mgrp; /* Pointer to transaction manager. */
+ DB_TXN *parent; /* Pointer to transaction's parent. */
+ DB_LSN last_lsn; /* Lsn of last log write. */
+ u_int32_t txnid; /* Unique transaction id. */
+ size_t off; /* Detail structure within region. */
+ TAILQ_ENTRY(__db_txn) links; /* Links transactions off manager. */
+ TAILQ_HEAD(__kids, __db_txn) kids; /* Child transactions. */
+ TAILQ_ENTRY(__db_txn) klinks; /* Links child transactions. */
+
+#define TXN_MALLOC 0x01 /* Structure allocated by TXN system. */
+ u_int32_t flags;
+};
+
+/*******************************************************
+ * Global variables.
+ *******************************************************/
+/*
+ * !!!
+ * Initialized in os/os_config.c, don't change this unless you change it
+ * as well.
+ */
+
+struct __rmname {
+ char *dbhome;
+ int rmid;
+ TAILQ_ENTRY(__rmname) links;
+};
+
+typedef struct __db_globals {
+ int db_mutexlocks; /* DB_MUTEXLOCKS */
+ int db_pageyield; /* DB_PAGEYIELD */
+ int db_region_anon; /* DB_REGION_ANON, DB_REGION_NAME */
+ int db_region_init; /* DB_REGION_INIT */
+ int db_tsl_spins; /* DB_TSL_SPINS */
+ /* XA: list of opened environments. */
+ TAILQ_HEAD(__db_envq, __db_env) db_envq;
+ /* XA: list of id to dbhome mappings. */
+ TAILQ_HEAD(__db_nameq, __rmname) db_nameq;
+} DB_GLOBALS;
+
+extern DB_GLOBALS __db_global_values;
+#define DB_GLOBAL(v) __db_global_values.v
+
+#include "os.h"
+#include "os_ext.h"
+
+#endif /* !_DB_INTERNAL_H_ */
diff --git a/usr/src/cmd/sendmail/db/dbm/dbm.c b/usr/src/cmd/sendmail/db/dbm/dbm.c
new file mode 100644
index 0000000000..aa5ab5fdb6
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/dbm/dbm.c
@@ -0,0 +1,485 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+/*
+ * Copyright (c) 1990, 1993
+ * Margo Seltzer. All rights reserved.
+ */
+/*
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Margo Seltzer.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)dbm.c 10.23 (Sleepycat) 11/22/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#endif
+
+#define DB_DBM_HSEARCH 1
+#include "db_int.h"
+
+#include "db_page.h"
+#include "hash.h"
+
+/*
+ *
+ * This package provides dbm and ndbm compatible interfaces to DB.
+ *
+ * The DBM routines, which call the NDBM routines.
+ */
+static DBM *__cur_db;
+
+static void __db_no_open __P((void));
+
+int
+__db_dbm_init(file)
+ char *file;
+{
+ if (__cur_db != NULL)
+ (void)dbm_close(__cur_db);
+ if ((__cur_db =
+ dbm_open(file, O_CREAT | O_RDWR, __db_omode("rw----"))) != NULL)
+ return (0);
+ if ((__cur_db = dbm_open(file, O_RDONLY, 0)) != NULL)
+ return (0);
+ return (-1);
+}
+
+int
+__db_dbm_close()
+{
+ if (__cur_db != NULL) {
+ dbm_close(__cur_db);
+ __cur_db = NULL;
+ }
+ return (0);
+}
+
+datum
+__db_dbm_fetch(key)
+ datum key;
+{
+ datum item;
+
+ if (__cur_db == NULL) {
+ __db_no_open();
+ item.dptr = 0;
+ return (item);
+ }
+ return (dbm_fetch(__cur_db, key));
+}
+
+datum
+__db_dbm_firstkey()
+{
+ datum item;
+
+ if (__cur_db == NULL) {
+ __db_no_open();
+ item.dptr = 0;
+ return (item);
+ }
+ return (dbm_firstkey(__cur_db));
+}
+
+datum
+__db_dbm_nextkey(key)
+ datum key;
+{
+ datum item;
+
+ COMPQUIET(key.dsize, 0);
+
+ if (__cur_db == NULL) {
+ __db_no_open();
+ item.dptr = 0;
+ return (item);
+ }
+ return (dbm_nextkey(__cur_db));
+}
+
+int
+__db_dbm_delete(key)
+ datum key;
+{
+ if (__cur_db == NULL) {
+ __db_no_open();
+ return (-1);
+ }
+ return (dbm_delete(__cur_db, key));
+}
+
+int
+__db_dbm_store(key, dat)
+ datum key, dat;
+{
+ if (__cur_db == NULL) {
+ __db_no_open();
+ return (-1);
+ }
+ return (dbm_store(__cur_db, key, dat, DBM_REPLACE));
+}
+
+static void
+__db_no_open()
+{
+ (void)fprintf(stderr, "dbm: no open database.\n");
+}
+
+/*
+ * This package provides dbm and ndbm compatible interfaces to DB.
+ *
+ * The NDBM routines, which call the DB routines.
+ */
+/*
+ * Returns:
+ * *DBM on success
+ * NULL on failure
+ */
+DBM *
+__db_ndbm_open(file, oflags, mode)
+ const char *file;
+ int oflags, mode;
+{
+ DB *dbp;
+ DBC *dbc;
+ DB_INFO dbinfo;
+ int sv_errno;
+ char path[MAXPATHLEN];
+
+ memset(&dbinfo, 0, sizeof(dbinfo));
+ dbinfo.db_pagesize = 4096;
+ dbinfo.h_ffactor = 40;
+ dbinfo.h_nelem = 1;
+
+ /*
+ * XXX
+ * Don't use sprintf(3)/snprintf(3) -- the former is dangerous, and
+ * the latter isn't standard, and we're manipulating strings handed
+ * us by the application.
+ */
+ if (strlen(file) + strlen(DBM_SUFFIX) + 1 > sizeof(path)) {
+ errno = ENAMETOOLONG;
+ return (NULL);
+ }
+ (void)strcpy(path, file);
+ (void)strcat(path, DBM_SUFFIX);
+ if ((errno = db_open(path,
+ DB_HASH, __db_oflags(oflags), mode, NULL, &dbinfo, &dbp)) != 0)
+ return (NULL);
+
+ if ((errno = dbp->cursor(dbp, NULL, &dbc, 0)) != 0) {
+ sv_errno = errno;
+ (void)dbp->close(dbp, 0);
+ errno = sv_errno;
+ return (NULL);
+ }
+
+ return ((DBM *)dbc);
+}
+
+/*
+ * Returns:
+ * Nothing.
+ */
+void
+__db_ndbm_close(dbm)
+ DBM *dbm;
+{
+ DBC *dbc;
+
+ dbc = (DBC *)dbm;
+
+ (void)dbc->dbp->close(dbc->dbp, 0);
+}
+
+/*
+ * Returns:
+ * DATUM on success
+ * NULL on failure
+ */
+datum
+__db_ndbm_fetch(dbm, key)
+ DBM *dbm;
+ datum key;
+{
+ DBC *dbc;
+ DBT _key, _data;
+ datum data;
+ int ret;
+
+ dbc = (DBC *)dbm;
+
+ memset(&_key, 0, sizeof(DBT));
+ memset(&_data, 0, sizeof(DBT));
+ _key.size = key.dsize;
+ _key.data = key.dptr;
+
+ /*
+ * Note that we can't simply use the dbc we have to do a c_get/SET,
+ * because that cursor is the one used for sequential iteration and
+ * it has to remain stable in the face of intervening gets and puts.
+ */
+ if ((ret = dbc->dbp->get(dbc->dbp, NULL, &_key, &_data, 0)) == 0) {
+ data.dptr = _data.data;
+ data.dsize = _data.size;
+ } else {
+ data.dptr = NULL;
+ data.dsize = 0;
+ if (ret == DB_NOTFOUND)
+ errno = ENOENT;
+ else {
+ errno = ret;
+ F_SET(dbc->dbp, DB_DBM_ERROR);
+ }
+ }
+ return (data);
+}
+
+/*
+ * Returns:
+ * DATUM on success
+ * NULL on failure
+ */
+datum
+__db_ndbm_firstkey(dbm)
+ DBM *dbm;
+{
+ DBC *dbc;
+ DBT _key, _data;
+ datum key;
+ int ret;
+
+ dbc = (DBC *)dbm;
+
+ memset(&_key, 0, sizeof(DBT));
+ memset(&_data, 0, sizeof(DBT));
+
+ if ((ret = dbc->c_get(dbc, &_key, &_data, DB_FIRST)) == 0) {
+ key.dptr = _key.data;
+ key.dsize = _key.size;
+ } else {
+ key.dptr = NULL;
+ key.dsize = 0;
+ if (ret == DB_NOTFOUND)
+ errno = ENOENT;
+ else {
+ errno = ret;
+ F_SET(dbc->dbp, DB_DBM_ERROR);
+ }
+ }
+ return (key);
+}
+
+/*
+ * Returns:
+ * DATUM on success
+ * NULL on failure
+ */
+datum
+__db_ndbm_nextkey(dbm)
+ DBM *dbm;
+{
+ DBC *dbc;
+ DBT _key, _data;
+ datum key;
+ int ret;
+
+ dbc = (DBC *)dbm;
+
+ memset(&_key, 0, sizeof(DBT));
+ memset(&_data, 0, sizeof(DBT));
+
+ if ((ret = dbc->c_get(dbc, &_key, &_data, DB_NEXT)) == 0) {
+ key.dptr = _key.data;
+ key.dsize = _key.size;
+ } else {
+ key.dptr = NULL;
+ key.dsize = 0;
+ if (ret == DB_NOTFOUND)
+ errno = ENOENT;
+ else {
+ errno = ret;
+ F_SET(dbc->dbp, DB_DBM_ERROR);
+ }
+ }
+ return (key);
+}
+
+/*
+ * Returns:
+ * 0 on success
+ * <0 failure
+ */
+int
+__db_ndbm_delete(dbm, key)
+ DBM *dbm;
+ datum key;
+{
+ DBC *dbc;
+ DBT _key;
+ int ret;
+
+ dbc = (DBC *)dbm;
+
+ memset(&_key, 0, sizeof(DBT));
+ _key.data = key.dptr;
+ _key.size = key.dsize;
+
+ if ((ret = dbc->dbp->del(dbc->dbp, NULL, &_key, 0)) == 0)
+ return (0);
+
+ if (ret == DB_NOTFOUND)
+ errno = ENOENT;
+ else {
+ errno = ret;
+ F_SET(dbc->dbp, DB_DBM_ERROR);
+ }
+ return (-1);
+}
+
+/*
+ * Returns:
+ * 0 on success
+ * <0 failure
+ * 1 if DBM_INSERT and entry exists
+ */
+int
+__db_ndbm_store(dbm, key, data, flags)
+ DBM *dbm;
+ datum key, data;
+ int flags;
+{
+ DBC *dbc;
+ DBT _key, _data;
+ int ret;
+
+ dbc = (DBC *)dbm;
+
+ memset(&_key, 0, sizeof(DBT));
+ _key.data = key.dptr;
+ _key.size = key.dsize;
+
+ memset(&_data, 0, sizeof(DBT));
+ _data.data = data.dptr;
+ _data.size = data.dsize;
+
+ if ((ret = dbc->dbp->put(dbc->dbp, NULL,
+ &_key, &_data, flags == DBM_INSERT ? DB_NOOVERWRITE : 0)) == 0)
+ return (0);
+
+ if (ret == DB_KEYEXIST)
+ return (1);
+
+ errno = ret;
+ F_SET(dbc->dbp, DB_DBM_ERROR);
+ return (-1);
+}
+
+int
+__db_ndbm_error(dbm)
+ DBM *dbm;
+{
+ DBC *dbc;
+
+ dbc = (DBC *)dbm;
+
+ return (F_ISSET(dbc->dbp, DB_DBM_ERROR));
+}
+
+int
+__db_ndbm_clearerr(dbm)
+ DBM *dbm;
+{
+ DBC *dbc;
+
+ dbc = (DBC *)dbm;
+
+ F_CLR(dbc->dbp, DB_DBM_ERROR);
+ return (0);
+}
+
+/*
+ * Returns:
+ * 1 if read-only
+ * 0 if not read-only
+ */
+int
+__db_ndbm_rdonly(dbm)
+ DBM *dbm;
+{
+ DBC *dbc;
+
+ dbc = (DBC *)dbm;
+
+ return (F_ISSET(dbc->dbp, DB_AM_RDONLY) ? 1 : 0);
+}
+
+/*
+ * XXX
+ * We only have a single file descriptor that we can return, not two. Return
+ * the same one for both files. Hopefully, the user is using it for locking
+ * and picked one to use at random.
+ */
+int
+__db_ndbm_dirfno(dbm)
+ DBM *dbm;
+{
+ return (dbm_pagfno(dbm));
+}
+
+int
+__db_ndbm_pagfno(dbm)
+ DBM *dbm;
+{
+ DBC *dbc;
+ int fd;
+
+ dbc = (DBC *)dbm;
+
+ (void)dbc->dbp->fd(dbc->dbp, &fd);
+ return (fd);
+}
diff --git a/usr/src/cmd/sendmail/db/hash/hash.c b/usr/src/cmd/sendmail/db/hash/hash.c
new file mode 100644
index 0000000000..a54fbe26ec
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/hash/hash.c
@@ -0,0 +1,1336 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+/*
+ * Copyright (c) 1990, 1993, 1994
+ * Margo Seltzer. All rights reserved.
+ */
+/*
+ * Copyright (c) 1990, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Margo Seltzer.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)hash.c 10.63 (Sleepycat) 12/11/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#endif
+
+#include "db_int.h"
+#include "shqueue.h"
+#include "db_page.h"
+#include "db_am.h"
+#include "db_ext.h"
+#include "hash.h"
+#include "btree.h"
+#include "log.h"
+#include "db_shash.h"
+#include "lock.h"
+#include "lock_ext.h"
+
+static int __ham_c_close __P((DBC *));
+static int __ham_c_del __P((DBC *, u_int32_t));
+static int __ham_c_destroy __P((DBC *));
+static int __ham_c_get __P((DBC *, DBT *, DBT *, u_int32_t));
+static int __ham_c_put __P((DBC *, DBT *, DBT *, u_int32_t));
+static int __ham_delete __P((DB *, DB_TXN *, DBT *, u_int32_t));
+static int __ham_dup_return __P((DBC *, DBT *, u_int32_t));
+static int __ham_expand_table __P((DBC *));
+static void __ham_init_htab __P((DBC *, u_int32_t, u_int32_t));
+static int __ham_lookup __P((DBC *, const DBT *, u_int32_t, db_lockmode_t));
+static int __ham_overwrite __P((DBC *, DBT *));
+
+/************************** INTERFACE ROUTINES ***************************/
+/* OPEN/CLOSE */
+
+/*
+ * __ham_open --
+ *
+ * PUBLIC: int __ham_open __P((DB *, DB_INFO *));
+ */
+int
+__ham_open(dbp, dbinfo)
+ DB *dbp;
+ DB_INFO *dbinfo;
+{
+ DB_ENV *dbenv;
+ DBC *dbc;
+ HASH_CURSOR *hcp;
+ int file_existed, ret;
+
+ dbc = NULL;
+ dbenv = dbp->dbenv;
+
+ /* Set the hash function if specified by the user. */
+ if (dbinfo != NULL && dbinfo->h_hash != NULL)
+ dbp->h_hash = dbinfo->h_hash;
+
+ /*
+ * Initialize the remaining fields of the dbp. The only function
+ * that differs from the default set is __ham_stat().
+ */
+ dbp->internal = NULL;
+ dbp->am_close = __ham_close;
+ dbp->del = __ham_delete;
+ dbp->stat = __ham_stat;
+
+ /* Get a cursor we can use for the rest of this function. */
+ if ((ret = dbp->cursor(dbp, NULL, &dbc, 0)) != 0)
+ goto out;
+
+ hcp = (HASH_CURSOR *)dbc->internal;
+ GET_META(dbp, hcp, ret);
+ if (ret != 0)
+ goto out;
+
+ /*
+ * If this is a new file, initialize it, and put it back dirty.
+ */
+
+ /* Initialize the hdr structure */
+ if (hcp->hdr->magic == DB_HASHMAGIC) {
+ file_existed = 1;
+ /* File exists, verify the data in the header. */
+ if (dbp->h_hash == NULL)
+ dbp->h_hash =
+ hcp->hdr->version < 5 ? __ham_func4 : __ham_func5;
+ if (dbp->h_hash(CHARKEY, sizeof(CHARKEY)) !=
+ hcp->hdr->h_charkey) {
+ __db_err(dbp->dbenv, "hash: incompatible hash function");
+ ret = EINVAL;
+ goto out;
+ }
+ if (F_ISSET(hcp->hdr, DB_HASH_DUP))
+ F_SET(dbp, DB_AM_DUP);
+ } else {
+ /*
+ * File does not exist, we must initialize the header. If
+ * locking is enabled that means getting a write lock first.
+ */
+ file_existed = 0;
+ if (F_ISSET(dbp, DB_AM_LOCKING) &&
+ ((ret = lock_put(dbenv->lk_info, hcp->hlock)) != 0 ||
+ (ret = lock_get(dbenv->lk_info, dbc->locker, 0,
+ &dbc->lock_dbt, DB_LOCK_WRITE, &hcp->hlock)) != 0)) {
+ if (ret < 0)
+ ret = EAGAIN;
+ goto out;
+ }
+
+ __ham_init_htab(dbc, dbinfo != NULL ? dbinfo->h_nelem : 0,
+ dbinfo != NULL ? dbinfo->h_ffactor : 0);
+ if (F_ISSET(dbp, DB_AM_DUP))
+ F_SET(hcp->hdr, DB_HASH_DUP);
+ if ((ret = __ham_dirty_page(dbp, (PAGE *)hcp->hdr)) != 0)
+ goto out;
+ }
+
+ /* Release the meta data page */
+ RELEASE_META(dbp, hcp);
+ if ((ret = dbc->c_close(dbc)) != 0)
+ goto out;
+
+ /* Sync the file so that we know that the meta data goes to disk. */
+ if (!file_existed && (ret = dbp->sync(dbp, 0)) != 0)
+ goto out;
+ return (0);
+
+out: (void)__ham_close(dbp);
+ return (ret);
+}
+
+/*
+ * PUBLIC: int __ham_close __P((DB *));
+ */
+int
+__ham_close(dbp)
+ DB *dbp;
+{
+ COMPQUIET(dbp, NULL);
+ return (0);
+}
+
+/************************** LOCAL CREATION ROUTINES **********************/
+/*
+ * Returns 0 on No Error
+ */
+static void
+__ham_init_htab(dbc, nelem, ffactor)
+ DBC *dbc;
+ u_int32_t nelem, ffactor;
+{
+ DB *dbp;
+ HASH_CURSOR *hcp;
+ int32_t l2, nbuckets;
+
+ hcp = (HASH_CURSOR *)dbc->internal;
+ dbp = dbc->dbp;
+ memset(hcp->hdr, 0, sizeof(HASHHDR));
+ hcp->hdr->ffactor = ffactor;
+ hcp->hdr->pagesize = dbp->pgsize;
+ ZERO_LSN(hcp->hdr->lsn);
+ hcp->hdr->magic = DB_HASHMAGIC;
+ hcp->hdr->version = DB_HASHVERSION;
+
+ if (dbp->h_hash == NULL)
+ dbp->h_hash = hcp->hdr->version < 5 ? __ham_func4 : __ham_func5;
+ hcp->hdr->h_charkey = dbp->h_hash(CHARKEY, sizeof(CHARKEY));
+ if (nelem != 0 && hcp->hdr->ffactor != 0) {
+ nelem = (nelem - 1) / hcp->hdr->ffactor + 1;
+ l2 = __db_log2(nelem > 2 ? nelem : 2);
+ } else
+ l2 = 2;
+
+ nbuckets = 1 << l2;
+
+ hcp->hdr->ovfl_point = l2;
+ hcp->hdr->last_freed = PGNO_INVALID;
+
+ hcp->hdr->max_bucket = hcp->hdr->high_mask = nbuckets - 1;
+ hcp->hdr->low_mask = (nbuckets >> 1) - 1;
+ memcpy(hcp->hdr->uid, dbp->fileid, DB_FILE_ID_LEN);
+}
+
+static int
+__ham_delete(dbp, txn, key, flags)
+ DB *dbp;
+ DB_TXN *txn;
+ DBT *key;
+ u_int32_t flags;
+{
+ DBC *dbc;
+ HASH_CURSOR *hcp;
+ int ret, tret;
+
+ DB_PANIC_CHECK(dbp);
+
+ if ((ret =
+ __db_delchk(dbp, key, flags, F_ISSET(dbp, DB_AM_RDONLY))) != 0)
+ return (ret);
+
+ if ((ret = dbp->cursor(dbp, txn, &dbc, DB_WRITELOCK)) != 0)
+ return (ret);
+
+ DEBUG_LWRITE(dbc, txn, "ham_delete", key, NULL, flags);
+
+ hcp = (HASH_CURSOR *)dbc->internal;
+ GET_META(dbp, hcp, ret);
+ if (ret != 0)
+ goto out;
+
+ hcp->stats.hash_deleted++;
+ if ((ret = __ham_lookup(dbc, key, 0, DB_LOCK_WRITE)) == 0)
+ if (F_ISSET(hcp, H_OK))
+ ret = __ham_del_pair(dbc, 1);
+ else
+ ret = DB_NOTFOUND;
+
+ RELEASE_META(dbp, hcp);
+out: if ((tret = dbc->c_close(dbc)) != 0 && ret == 0)
+ ret = tret;
+ return (ret);
+}
+
+/* ****************** CURSORS ********************************** */
+/*
+ * __ham_c_init --
+ * Initialize the hash-specific portion of a cursor.
+ *
+ * PUBLIC: int __ham_c_init __P((DBC *));
+ */
+int
+__ham_c_init(dbc)
+ DBC *dbc;
+ {
+ HASH_CURSOR *new_curs;
+ int ret;
+
+ if ((ret = __os_calloc(1, sizeof(struct cursor_t), &new_curs)) != 0)
+ return (ret);
+ if ((ret =
+ __os_malloc(dbc->dbp->pgsize, NULL, &new_curs->split_buf)) != 0) {
+ __os_free(new_curs, sizeof(*new_curs));
+ return (ret);
+ }
+
+ new_curs->dbc = dbc;
+
+ dbc->internal = new_curs;
+ dbc->c_am_close = __ham_c_close;
+ dbc->c_am_destroy = __ham_c_destroy;
+ dbc->c_del = __ham_c_del;
+ dbc->c_get = __ham_c_get;
+ dbc->c_put = __ham_c_put;
+
+ __ham_item_init(new_curs);
+
+ return (0);
+}
+
+/*
+ * __ham_c_close --
+ * Close down the cursor from a single use.
+ */
+static int
+__ham_c_close(dbc)
+ DBC *dbc;
+{
+ int ret;
+
+ if ((ret = __ham_item_done(dbc, 0)) != 0)
+ return (ret);
+
+ __ham_item_init((HASH_CURSOR *)dbc->internal);
+ return (0);
+}
+
+/*
+ * __ham_c_destroy --
+ * Cleanup the access method private part of a cursor.
+ */
+static int
+__ham_c_destroy(dbc)
+ DBC *dbc;
+{
+ HASH_CURSOR *hcp;
+
+ hcp = (HASH_CURSOR *)dbc->internal;
+ if (hcp->split_buf != NULL)
+ __os_free(hcp->split_buf, dbc->dbp->pgsize);
+ __os_free(hcp, sizeof(HASH_CURSOR));
+
+ return (0);
+}
+
+static int
+__ham_c_del(dbc, flags)
+ DBC *dbc;
+ u_int32_t flags;
+{
+ DB *dbp;
+ DBT repldbt;
+ HASH_CURSOR *hcp;
+ HASH_CURSOR save_curs;
+ db_pgno_t ppgno, chg_pgno;
+ int ret, t_ret;
+
+ DEBUG_LWRITE(dbc, dbc->txn, "ham_c_del", NULL, NULL, flags);
+ dbp = dbc->dbp;
+ DB_PANIC_CHECK(dbp);
+ hcp = (HASH_CURSOR *)dbc->internal;
+
+ if ((ret = __db_cdelchk(dbc->dbp, flags,
+ F_ISSET(dbc->dbp, DB_AM_RDONLY), IS_VALID(hcp))) != 0)
+ return (ret);
+
+ if (F_ISSET(hcp, H_DELETED))
+ return (DB_NOTFOUND);
+
+ /*
+ * If we are in the concurrent DB product and this cursor
+ * is not a write cursor, then this request is invalid.
+ * If it is a simple write cursor, then we need to upgrade its
+ * lock.
+ */
+ if (F_ISSET(dbp, DB_AM_CDB)) {
+ /* Make sure it's a valid update cursor. */
+ if (!F_ISSET(dbc, DBC_RMW | DBC_WRITER))
+ return (EINVAL);
+
+ if (F_ISSET(dbc, DBC_RMW) &&
+ (ret = lock_get(dbp->dbenv->lk_info, dbc->locker,
+ DB_LOCK_UPGRADE, &dbc->lock_dbt, DB_LOCK_WRITE,
+ &dbc->mylock)) != 0)
+ return (EAGAIN);
+ }
+
+ GET_META(dbp, hcp, ret);
+ if (ret != 0)
+ return (ret);
+
+ SAVE_CURSOR(hcp, &save_curs);
+ hcp->stats.hash_deleted++;
+
+ if ((ret = __ham_get_cpage(dbc, DB_LOCK_WRITE)) != 0)
+ goto out;
+ if (F_ISSET(hcp, H_ISDUP) && hcp->dpgno != PGNO_INVALID) {
+ /*
+ * We are about to remove a duplicate from offpage.
+ *
+ * There are 4 cases.
+ * 1. We will remove an item on a page, but there are more
+ * items on that page.
+ * 2. We will remove the last item on a page, but there is a
+ * following page of duplicates.
+ * 3. We will remove the last item on a page, this page was the
+ * last page in a duplicate set, but there were dups before
+ * it.
+ * 4. We will remove the last item on a page, removing the last
+ * duplicate.
+ * In case 1 hcp->dpagep is unchanged.
+ * In case 2 hcp->dpagep comes back pointing to the next dup
+ * page.
+ * In case 3 hcp->dpagep comes back NULL.
+ * In case 4 hcp->dpagep comes back NULL.
+ *
+ * Case 4 results in deleting the pair off the master page.
+ * The normal code for doing this knows how to delete the
+ * duplicates, so we will handle this case in the normal code.
+ */
+ ppgno = PREV_PGNO(hcp->dpagep);
+ if (ppgno == PGNO_INVALID &&
+ NEXT_PGNO(hcp->dpagep) == PGNO_INVALID &&
+ NUM_ENT(hcp->dpagep) == 1)
+ goto normal;
+
+ /* Remove item from duplicate page. */
+ chg_pgno = hcp->dpgno;
+ if ((ret = __db_drem(dbc,
+ &hcp->dpagep, hcp->dndx, __ham_del_page)) != 0)
+ goto out;
+
+ if (hcp->dpagep == NULL) {
+ if (ppgno != PGNO_INVALID) { /* Case 3 */
+ hcp->dpgno = ppgno;
+ if ((ret = __ham_get_cpage(dbc,
+ DB_LOCK_READ)) != 0)
+ goto out;
+ hcp->dndx = NUM_ENT(hcp->dpagep);
+ F_SET(hcp, H_DELETED);
+ } else { /* Case 4 */
+ ret = __ham_del_pair(dbc, 1);
+ hcp->dpgno = PGNO_INVALID;
+ /*
+ * Delpair updated the cursor queue, so we
+ * don't have to do that here.
+ */
+ chg_pgno = PGNO_INVALID;
+ }
+ } else if (PGNO(hcp->dpagep) != hcp->dpgno) {
+ hcp->dndx = 0; /* Case 2 */
+ hcp->dpgno = PGNO(hcp->dpagep);
+ if (ppgno == PGNO_INVALID)
+ memcpy(HOFFDUP_PGNO(P_ENTRY(hcp->pagep,
+ H_DATAINDEX(hcp->bndx))),
+ &hcp->dpgno, sizeof(db_pgno_t));
+ /*
+ * We need to put the master page here, because
+ * although we have a duplicate page, the master
+ * page is dirty, and ham_item_done assumes that
+ * if you have a duplicate page, it's the only one
+ * that can be dirty.
+ */
+ ret = __ham_put_page(dbp, hcp->pagep, 1);
+ hcp->pagep = NULL;
+ F_SET(hcp, H_DELETED);
+ } else /* Case 1 */
+ F_SET(hcp, H_DELETED);
+ if (chg_pgno != PGNO_INVALID)
+ __ham_c_update(hcp, chg_pgno, 0, 0, 1);
+ } else if (F_ISSET(hcp, H_ISDUP)) { /* on page */
+ if (hcp->dup_off == 0 && DUP_SIZE(hcp->dup_len) ==
+ LEN_HDATA(hcp->pagep, hcp->hdr->pagesize, hcp->bndx))
+ ret = __ham_del_pair(dbc, 1);
+ else {
+ repldbt.flags = 0;
+ F_SET(&repldbt, DB_DBT_PARTIAL);
+ repldbt.doff = hcp->dup_off;
+ repldbt.dlen = DUP_SIZE(hcp->dup_len);
+ repldbt.size = 0;
+ repldbt.data =
+ HKEYDATA_DATA(H_PAIRDATA(hcp->pagep, hcp->bndx));
+ ret = __ham_replpair(dbc, &repldbt, 0);
+ hcp->dup_tlen -= DUP_SIZE(hcp->dup_len);
+ F_SET(hcp, H_DELETED);
+ __ham_c_update(hcp, hcp->pgno,
+ DUP_SIZE(hcp->dup_len), 0, 1);
+ }
+
+ } else
+ /* Not a duplicate */
+normal: ret = __ham_del_pair(dbc, 1);
+
+out: if ((t_ret = __ham_item_done(dbc, ret == 0)) != 0 && ret == 0)
+ ret = t_ret;
+ RELEASE_META(dbp, hcp);
+ RESTORE_CURSOR(dbp, hcp, &save_curs, ret);
+ if (F_ISSET(dbp, DB_AM_CDB) && F_ISSET(dbc, DBC_RMW))
+ (void)__lock_downgrade(dbp->dbenv->lk_info, dbc->mylock,
+ DB_LOCK_IWRITE, 0);
+ return (ret);
+}
+
+static int
+__ham_c_get(dbc, key, data, flags)
+ DBC *dbc;
+ DBT *key;
+ DBT *data;
+ u_int32_t flags;
+{
+ DB *dbp;
+ HASH_CURSOR *hcp, save_curs;
+ db_lockmode_t lock_type;
+ int get_key, ret, t_ret;
+
+ DEBUG_LREAD(dbc, dbc->txn, "ham_c_get",
+ flags == DB_SET || flags == DB_SET_RANGE ? key : NULL,
+ NULL, flags);
+
+ hcp = (HASH_CURSOR *)dbc->internal;
+ dbp = dbc->dbp;
+ DB_PANIC_CHECK(dbp);
+ SAVE_CURSOR(hcp, &save_curs);
+ if ((ret =
+ __db_cgetchk(dbp, key, data, flags, IS_VALID(hcp))) != 0)
+ return (ret);
+
+ /* Clear OR'd in additional bits so we can check for flag equality. */
+ if (LF_ISSET(DB_RMW)) {
+ lock_type = DB_LOCK_WRITE;
+ LF_CLR(DB_RMW);
+ } else
+ lock_type = DB_LOCK_READ;
+
+ GET_META(dbp, hcp, ret);
+ if (ret != 0)
+ return (ret);
+ hcp->stats.hash_get++;
+ hcp->seek_size = 0;
+
+ ret = 0;
+ get_key = 1;
+ switch (flags) {
+ case DB_PREV:
+ if (hcp->bucket != BUCKET_INVALID) {
+ ret = __ham_item_prev(dbc, lock_type);
+ break;
+ }
+ /* FALLTHROUGH */
+ case DB_LAST:
+ ret = __ham_item_last(dbc, lock_type);
+ break;
+ case DB_FIRST:
+ ret = __ham_item_first(dbc, lock_type);
+ break;
+ case DB_NEXT_DUP:
+ if (hcp->bucket == BUCKET_INVALID)
+ ret = EINVAL;
+ else {
+ F_SET(hcp, H_DUPONLY);
+ ret = __ham_item_next(dbc, lock_type);
+ }
+ break;
+ case DB_NEXT:
+ if (hcp->bucket == BUCKET_INVALID)
+ hcp->bucket = 0;
+ ret = __ham_item_next(dbc, lock_type);
+ break;
+ case DB_SET:
+ case DB_SET_RANGE:
+ case DB_GET_BOTH:
+ if (F_ISSET(dbc, DBC_CONTINUE)) {
+ F_SET(hcp, H_DUPONLY);
+ ret = __ham_item_next(dbc, lock_type);
+ } else if (F_ISSET(dbc, DBC_KEYSET))
+ ret = __ham_item(dbc, lock_type);
+ else
+ ret = __ham_lookup(dbc, key, 0, lock_type);
+ get_key = 0;
+ break;
+ case DB_CURRENT:
+ if (F_ISSET(hcp, H_DELETED)) {
+ ret = DB_KEYEMPTY;
+ goto out;
+ }
+
+ ret = __ham_item(dbc, lock_type);
+ break;
+ }
+
+ /*
+ * Must always enter this loop to do error handling and
+ * check for big key/data pair.
+ */
+ while (1) {
+ if (ret != 0 && ret != DB_NOTFOUND)
+ goto out1;
+ else if (F_ISSET(hcp, H_OK)) {
+ /* Get the key. */
+ if (get_key && (ret = __db_ret(dbp, hcp->pagep,
+ H_KEYINDEX(hcp->bndx), key, &dbc->rkey.data,
+ &dbc->rkey.size)) != 0)
+ goto out1;
+
+ ret = __ham_dup_return(dbc, data, flags);
+ break;
+ } else if (!F_ISSET(hcp, H_NOMORE)) {
+ abort();
+ break;
+ }
+
+ /*
+ * Ran out of entries in a bucket; change buckets.
+ */
+ switch (flags) {
+ case DB_LAST:
+ case DB_PREV:
+ ret = __ham_item_done(dbc, 0);
+ if (hcp->bucket == 0) {
+ ret = DB_NOTFOUND;
+ goto out1;
+ }
+ hcp->bucket--;
+ hcp->bndx = NDX_INVALID;
+ if (ret == 0)
+ ret = __ham_item_prev(dbc, lock_type);
+ break;
+ case DB_FIRST:
+ case DB_NEXT:
+ ret = __ham_item_done(dbc, 0);
+ hcp->bndx = NDX_INVALID;
+ hcp->bucket++;
+ hcp->pgno = PGNO_INVALID;
+ hcp->pagep = NULL;
+ if (hcp->bucket > hcp->hdr->max_bucket) {
+ ret = DB_NOTFOUND;
+ goto out1;
+ }
+ if (ret == 0)
+ ret = __ham_item_next(dbc, lock_type);
+ break;
+ case DB_GET_BOTH:
+ case DB_NEXT_DUP:
+ case DB_SET:
+ case DB_SET_RANGE:
+ /* Key not found. */
+ ret = DB_NOTFOUND;
+ goto out1;
+ }
+ }
+out1: if ((t_ret = __ham_item_done(dbc, 0)) != 0 && ret == 0)
+ ret = t_ret;
+out: RELEASE_META(dbp, hcp);
+ RESTORE_CURSOR(dbp, hcp, &save_curs, ret);
+ return (ret);
+}
+
+static int
+__ham_c_put(dbc, key, data, flags)
+ DBC *dbc;
+ DBT *key;
+ DBT *data;
+ u_int32_t flags;
+{
+ DB *dbp;
+ DBT tmp_val, *myval;
+ HASH_CURSOR *hcp, save_curs;
+ u_int32_t nbytes;
+ int ret, t_ret;
+
+ dbp = dbc->dbp;
+ DB_PANIC_CHECK(dbp);
+ DEBUG_LWRITE(dbc, dbc->txn, "ham_c_put",
+ flags == DB_KEYFIRST || flags == DB_KEYLAST ? key : NULL,
+ data, flags);
+ hcp = (HASH_CURSOR *)dbc->internal;
+
+ if ((ret = __db_cputchk(dbp, key, data, flags,
+ F_ISSET(dbp, DB_AM_RDONLY), IS_VALID(hcp))) != 0)
+ return (ret);
+
+ if (F_ISSET(hcp, H_DELETED) &&
+ flags != DB_KEYFIRST && flags != DB_KEYLAST)
+ return (DB_NOTFOUND);
+
+ /*
+ * If we are in the concurrent DB product and this cursor
+ * is not a write cursor, then this request is invalid.
+ * If it is a simple write cursor, then we need to upgrade its
+ * lock.
+ */
+ if (F_ISSET(dbp, DB_AM_CDB)) {
+ /* Make sure it's a valid update cursor. */
+ if (!F_ISSET(dbc, DBC_RMW | DBC_WRITER))
+ return (EINVAL);
+
+ if (F_ISSET(dbc, DBC_RMW) &&
+ (ret = lock_get(dbp->dbenv->lk_info, dbc->locker,
+ DB_LOCK_UPGRADE, &dbc->lock_dbt, DB_LOCK_WRITE,
+ &dbc->mylock)) != 0)
+ return (EAGAIN);
+ }
+
+ GET_META(dbp, hcp, ret);
+ if (ret != 0)
+ return (ret);
+
+ SAVE_CURSOR(hcp, &save_curs);
+ hcp->stats.hash_put++;
+
+ switch (flags) {
+ case DB_KEYLAST:
+ case DB_KEYFIRST:
+ nbytes = (ISBIG(hcp, key->size) ? HOFFPAGE_PSIZE :
+ HKEYDATA_PSIZE(key->size)) +
+ (ISBIG(hcp, data->size) ? HOFFPAGE_PSIZE :
+ HKEYDATA_PSIZE(data->size));
+ if ((ret = __ham_lookup(dbc,
+ key, nbytes, DB_LOCK_WRITE)) == DB_NOTFOUND) {
+ ret = 0;
+ if (hcp->seek_found_page != PGNO_INVALID &&
+ hcp->seek_found_page != hcp->pgno) {
+ if ((ret = __ham_item_done(dbc, 0)) != 0)
+ goto out;
+ hcp->pgno = hcp->seek_found_page;
+ hcp->bndx = NDX_INVALID;
+ }
+
+ if (F_ISSET(data, DB_DBT_PARTIAL) && data->doff != 0) {
+ /*
+ * A partial put, but the key does not exist
+ * and we are not beginning the write at 0.
+ * We must create a data item padded up to doff
+ * and then write the new bytes represented by
+ * val.
+ */
+ if ((ret = __ham_init_dbt(&tmp_val,
+ data->size + data->doff,
+ &dbc->rdata.data, &dbc->rdata.size)) == 0) {
+ memset(tmp_val.data, 0, data->doff);
+ memcpy((u_int8_t *)tmp_val.data +
+ data->doff, data->data, data->size);
+ myval = &tmp_val;
+ }
+ } else
+ myval = (DBT *)data;
+
+ if (ret == 0)
+ ret = __ham_add_el(dbc, key, myval, H_KEYDATA);
+ goto done;
+ }
+ break;
+ case DB_BEFORE:
+ case DB_AFTER:
+ case DB_CURRENT:
+ ret = __ham_item(dbc, DB_LOCK_WRITE);
+ break;
+ }
+
+ if (ret == 0) {
+ if ((flags == DB_CURRENT && !F_ISSET(hcp, H_ISDUP)) ||
+ ((flags == DB_KEYFIRST || flags == DB_KEYLAST) &&
+ !F_ISSET(dbp, DB_AM_DUP)))
+ ret = __ham_overwrite(dbc, data);
+ else
+ ret = __ham_add_dup(dbc, data, flags);
+ }
+
+done: if (ret == 0 && F_ISSET(hcp, H_EXPAND)) {
+ ret = __ham_expand_table(dbc);
+ F_CLR(hcp, H_EXPAND);
+ }
+
+ if ((t_ret = __ham_item_done(dbc, ret == 0)) != 0 && ret == 0)
+ ret = t_ret;
+
+out: RELEASE_META(dbp, hcp);
+ RESTORE_CURSOR(dbp, hcp, &save_curs, ret);
+ if (F_ISSET(dbp, DB_AM_CDB) && F_ISSET(dbc, DBC_RMW))
+ (void)__lock_downgrade(dbp->dbenv->lk_info, dbc->mylock,
+ DB_LOCK_IWRITE, 0);
+ return (ret);
+}
+
+/********************************* UTILITIES ************************/
+
+/*
+ * __ham_expand_table --
+ */
+static int
+__ham_expand_table(dbc)
+ DBC *dbc;
+{
+ DB *dbp;
+ HASH_CURSOR *hcp;
+ DB_LSN new_lsn;
+ u_int32_t old_bucket, new_bucket, spare_ndx;
+ int ret;
+
+ dbp = dbc->dbp;
+ hcp = (HASH_CURSOR *)dbc->internal;
+ ret = 0;
+ DIRTY_META(dbp, hcp, ret);
+ if (ret)
+ return (ret);
+
+ /*
+ * If the split point is about to increase, make sure that we
+ * have enough extra pages. The calculation here is weird.
+ * We'd like to do this after we've upped max_bucket, but it's
+ * too late then because we've logged the meta-data split. What
+ * we'll do between then and now is increment max bucket and then
+ * see what the log of one greater than that is; here we have to
+ * look at the log of max + 2. VERY NASTY STUFF.
+ */
+ if (__db_log2(hcp->hdr->max_bucket + 2) > hcp->hdr->ovfl_point) {
+ /*
+ * We are about to shift the split point. Make sure that
+ * if the next doubling is going to be big (more than 8
+ * pages), we have some extra pages around.
+ */
+ if (hcp->hdr->max_bucket + 1 >= 8 &&
+ hcp->hdr->spares[hcp->hdr->ovfl_point] <
+ hcp->hdr->spares[hcp->hdr->ovfl_point - 1] +
+ hcp->hdr->ovfl_point + 1)
+ __ham_init_ovflpages(dbc);
+ }
+
+ /* Now we can log the meta-data split. */
+ if (DB_LOGGING(dbc)) {
+ if ((ret = __ham_splitmeta_log(dbp->dbenv->lg_info,
+ dbc->txn, &new_lsn, 0, dbp->log_fileid,
+ hcp->hdr->max_bucket, hcp->hdr->ovfl_point,
+ hcp->hdr->spares[hcp->hdr->ovfl_point],
+ &hcp->hdr->lsn)) != 0)
+ return (ret);
+
+ hcp->hdr->lsn = new_lsn;
+ }
+
+ hcp->stats.hash_expansions++;
+ new_bucket = ++hcp->hdr->max_bucket;
+ old_bucket = (hcp->hdr->max_bucket & hcp->hdr->low_mask);
+
+ /*
+ * If the split point is increasing, copy the current contents
+ * of the spare split bucket to the next bucket.
+ */
+ spare_ndx = __db_log2(hcp->hdr->max_bucket + 1);
+ if (spare_ndx > hcp->hdr->ovfl_point) {
+ hcp->hdr->spares[spare_ndx] =
+ hcp->hdr->spares[hcp->hdr->ovfl_point];
+ hcp->hdr->ovfl_point = spare_ndx;
+ }
+
+ if (new_bucket > hcp->hdr->high_mask) {
+ /* Starting a new doubling */
+ hcp->hdr->low_mask = hcp->hdr->high_mask;
+ hcp->hdr->high_mask = new_bucket | hcp->hdr->low_mask;
+ }
+
+ if (BUCKET_TO_PAGE(hcp, new_bucket) > MAX_PAGES(hcp)) {
+ __db_err(dbp->dbenv,
+ "hash: Cannot allocate new bucket. Pages exhausted.");
+ return (ENOSPC);
+ }
+
+ /* Relocate records to the new bucket */
+ return (__ham_split_page(dbc, old_bucket, new_bucket));
+}
+
+/*
+ * PUBLIC: u_int32_t __ham_call_hash __P((HASH_CURSOR *, u_int8_t *, int32_t));
+ */
+u_int32_t
+__ham_call_hash(hcp, k, len)
+ HASH_CURSOR *hcp;
+ u_int8_t *k;
+ int32_t len;
+{
+ u_int32_t n, bucket;
+
+ n = (u_int32_t)(hcp->dbc->dbp->h_hash(k, len));
+
+ bucket = n & hcp->hdr->high_mask;
+ if (bucket > hcp->hdr->max_bucket)
+ bucket = bucket & hcp->hdr->low_mask;
+ return (bucket);
+}
+
+/*
+ * Check for duplicates, and call __db_ret appropriately. Release
+ * everything held by the cursor.
+ */
+static int
+__ham_dup_return(dbc, val, flags)
+ DBC *dbc;
+ DBT *val;
+ u_int32_t flags;
+{
+ DB *dbp;
+ HASH_CURSOR *hcp;
+ PAGE *pp;
+ DBT *myval, tmp_val;
+ db_indx_t ndx;
+ db_pgno_t pgno;
+ u_int32_t off, tlen;
+ u_int8_t *hk, type;
+ int cmp, ret;
+ db_indx_t len;
+
+ /* Check for duplicate and return the first one. */
+ dbp = dbc->dbp;
+ hcp = (HASH_CURSOR *)dbc->internal;
+ ndx = H_DATAINDEX(hcp->bndx);
+ type = HPAGE_TYPE(hcp->pagep, ndx);
+ pp = hcp->pagep;
+ myval = val;
+
+ /*
+ * There are 4 cases:
+ * 1. We are not in duplicate, simply call db_ret.
+ * 2. We are looking at keys and stumbled onto a duplicate.
+ * 3. We are in the middle of a duplicate set. (ISDUP set)
+ * 4. This is a duplicate and we need to return a specific item.
+ */
+
+ /*
+ * Here we check for the case where we just stumbled onto a
+ * duplicate. In this case, we do initialization and then
+ * let the normal duplicate code handle it.
+ */
+ if (!F_ISSET(hcp, H_ISDUP))
+ if (type == H_DUPLICATE) {
+ F_SET(hcp, H_ISDUP);
+ hcp->dup_tlen = LEN_HDATA(hcp->pagep,
+ hcp->hdr->pagesize, hcp->bndx);
+ hk = H_PAIRDATA(hcp->pagep, hcp->bndx);
+ if (flags == DB_LAST || flags == DB_PREV) {
+ hcp->dndx = 0;
+ hcp->dup_off = 0;
+ do {
+ memcpy(&len,
+ HKEYDATA_DATA(hk) + hcp->dup_off,
+ sizeof(db_indx_t));
+ hcp->dup_off += DUP_SIZE(len);
+ hcp->dndx++;
+ } while (hcp->dup_off < hcp->dup_tlen);
+ hcp->dup_off -= DUP_SIZE(len);
+ hcp->dndx--;
+ } else {
+ memcpy(&len,
+ HKEYDATA_DATA(hk), sizeof(db_indx_t));
+ hcp->dup_off = 0;
+ hcp->dndx = 0;
+ }
+ hcp->dup_len = len;
+ } else if (type == H_OFFDUP) {
+ F_SET(hcp, H_ISDUP);
+ memcpy(&pgno, HOFFDUP_PGNO(P_ENTRY(hcp->pagep, ndx)),
+ sizeof(db_pgno_t));
+ if (flags == DB_LAST || flags == DB_PREV) {
+ if ((ret = __db_dend(dbc,
+ pgno, &hcp->dpagep)) != 0)
+ return (ret);
+ hcp->dpgno = PGNO(hcp->dpagep);
+ hcp->dndx = NUM_ENT(hcp->dpagep) - 1;
+ } else if ((ret = __ham_next_cpage(dbc,
+ pgno, 0, H_ISDUP)) != 0)
+ return (ret);
+ }
+
+
+ /*
+ * If we are retrieving a specific key/data pair, then we
+ * may need to adjust the cursor before returning data.
+ */
+ if (flags == DB_GET_BOTH) {
+ if (F_ISSET(hcp, H_ISDUP)) {
+ if (hcp->dpgno != PGNO_INVALID) {
+ if ((ret = __db_dsearch(dbc, 0, val,
+ hcp->dpgno, &hcp->dndx, &hcp->dpagep, &cmp))
+ != 0)
+ return (ret);
+ if (cmp == 0)
+ hcp->dpgno = PGNO(hcp->dpagep);
+ } else {
+ __ham_dsearch(dbc, val, &off, &cmp);
+ hcp->dup_off = off;
+ }
+ } else {
+ hk = H_PAIRDATA(hcp->pagep, hcp->bndx);
+ if (((HKEYDATA *)hk)->type == H_OFFPAGE) {
+ memcpy(&tlen,
+ HOFFPAGE_TLEN(hk), sizeof(u_int32_t));
+ memcpy(&pgno,
+ HOFFPAGE_PGNO(hk), sizeof(db_pgno_t));
+ if ((ret = __db_moff(dbp, val,
+ pgno, tlen, dbp->dup_compare, &cmp)) != 0)
+ return (ret);
+ } else {
+ /*
+ * We do not zero tmp_val since the comparison
+ * routines may only look at data and size.
+ */
+ tmp_val.data = HKEYDATA_DATA(hk);
+ tmp_val.size = LEN_HDATA(hcp->pagep,
+ dbp->pgsize, hcp->bndx);
+ cmp = dbp->dup_compare == NULL ?
+ __bam_defcmp(&tmp_val, val) :
+ dbp->dup_compare(&tmp_val, val);
+ }
+ }
+
+ if (cmp != 0)
+ return (DB_NOTFOUND);
+ }
+
+ /*
+ * Now, everything is initialized, grab a duplicate if
+ * necessary.
+ */
+ if (F_ISSET(hcp, H_ISDUP))
+ if (hcp->dpgno != PGNO_INVALID) {
+ pp = hcp->dpagep;
+ ndx = hcp->dndx;
+ } else {
+ /*
+ * Copy the DBT in case we are retrieving into user
+ * memory and we need the parameters for it. If the
+ * user requested a partial, then we need to adjust
+ * the user's parameters to get the partial of the
+ * duplicate which is itself a partial.
+ */
+ memcpy(&tmp_val, val, sizeof(*val));
+ if (F_ISSET(&tmp_val, DB_DBT_PARTIAL)) {
+ /*
+ * Take the user's length unless it would go
+ * beyond the end of the duplicate.
+ */
+ if (tmp_val.doff + hcp->dup_off > hcp->dup_len)
+ tmp_val.dlen = 0;
+ else if (tmp_val.dlen + tmp_val.doff >
+ hcp->dup_len)
+ tmp_val.dlen =
+ hcp->dup_len - tmp_val.doff;
+
+ /*
+ * Calculate the new offset.
+ */
+ tmp_val.doff += hcp->dup_off;
+ } else {
+ F_SET(&tmp_val, DB_DBT_PARTIAL);
+ tmp_val.dlen = hcp->dup_len;
+ tmp_val.doff = hcp->dup_off + sizeof(db_indx_t);
+ }
+ myval = &tmp_val;
+ }
+
+
+ /*
+ * Finally, if we had a duplicate, pp, ndx, and myval should be
+ * set appropriately.
+ */
+ if ((ret = __db_ret(dbp, pp, ndx, myval, &dbc->rdata.data,
+ &dbc->rdata.size)) != 0)
+ return (ret);
+
+ /*
+ * In case we sent a temporary off to db_ret, set the real
+ * return values.
+ */
+ val->data = myval->data;
+ val->size = myval->size;
+
+ return (0);
+}
+
+static int
+__ham_overwrite(dbc, nval)
+ DBC *dbc;
+ DBT *nval;
+{
+ HASH_CURSOR *hcp;
+ DBT *myval, tmp_val;
+ u_int8_t *hk;
+
+ hcp = (HASH_CURSOR *)dbc->internal;
+ if (F_ISSET(dbc->dbp, DB_AM_DUP))
+ return (__ham_add_dup(dbc, nval, DB_KEYLAST));
+ else if (!F_ISSET(nval, DB_DBT_PARTIAL)) {
+ /* Put/overwrite */
+ memcpy(&tmp_val, nval, sizeof(*nval));
+ F_SET(&tmp_val, DB_DBT_PARTIAL);
+ tmp_val.doff = 0;
+ hk = H_PAIRDATA(hcp->pagep, hcp->bndx);
+ if (HPAGE_PTYPE(hk) == H_OFFPAGE)
+ memcpy(&tmp_val.dlen,
+ HOFFPAGE_TLEN(hk), sizeof(u_int32_t));
+ else
+ tmp_val.dlen = LEN_HDATA(hcp->pagep,
+ hcp->hdr->pagesize,hcp->bndx);
+ myval = &tmp_val;
+ } else /* Regular partial put */
+ myval = nval;
+
+ return (__ham_replpair(dbc, myval, 0));
+}
+
+/*
+ * Given a key and a cursor, sets the cursor to the page/ndx on which
+ * the key resides. If the key is found, the cursor H_OK flag is set
+ * and the pagep, bndx, pgno (dpagep, dndx, dpgno) fields are set.
+ * If the key is not found, the H_OK flag is not set. If the sought
+ * field is non-0, the pagep, bndx, pgno (dpagep, dndx, dpgno) fields
+ * are set indicating where an add might take place. If it is 0,
+ * non of the cursor pointer field are valid.
+ */
+static int
+__ham_lookup(dbc, key, sought, mode)
+ DBC *dbc;
+ const DBT *key;
+ u_int32_t sought;
+ db_lockmode_t mode;
+{
+ DB *dbp;
+ HASH_CURSOR *hcp;
+ db_pgno_t pgno;
+ u_int32_t tlen;
+ int match, ret, t_ret;
+ u_int8_t *hk;
+
+ dbp = dbc->dbp;
+ hcp = (HASH_CURSOR *)dbc->internal;
+ /*
+ * Set up cursor so that we're looking for space to add an item
+ * as we cycle through the pages looking for the key.
+ */
+ if ((ret = __ham_item_reset(dbc)) != 0)
+ return (ret);
+ hcp->seek_size = sought;
+
+ hcp->bucket = __ham_call_hash(hcp, (u_int8_t *)key->data, key->size);
+ while (1) {
+ if ((ret = __ham_item_next(dbc, mode)) != 0)
+ return (ret);
+
+ if (F_ISSET(hcp, H_NOMORE))
+ break;
+
+ hk = H_PAIRKEY(hcp->pagep, hcp->bndx);
+ switch (HPAGE_PTYPE(hk)) {
+ case H_OFFPAGE:
+ memcpy(&tlen, HOFFPAGE_TLEN(hk), sizeof(u_int32_t));
+ if (tlen == key->size) {
+ memcpy(&pgno,
+ HOFFPAGE_PGNO(hk), sizeof(db_pgno_t));
+ if ((ret = __db_moff(dbp,
+ key, pgno, tlen, NULL, &match)) != 0)
+ return (ret);
+ if (match == 0) {
+ F_SET(hcp, H_OK);
+ return (0);
+ }
+ }
+ break;
+ case H_KEYDATA:
+ if (key->size == LEN_HKEY(hcp->pagep,
+ hcp->hdr->pagesize, hcp->bndx) &&
+ memcmp(key->data,
+ HKEYDATA_DATA(hk), key->size) == 0) {
+ F_SET(hcp, H_OK);
+ return (0);
+ }
+ break;
+ case H_DUPLICATE:
+ case H_OFFDUP:
+ /*
+ * These are errors because keys are never
+ * duplicated, only data items are.
+ */
+ return (__db_pgfmt(dbp, PGNO(hcp->pagep)));
+ }
+ hcp->stats.hash_collisions++;
+ }
+
+ /*
+ * Item was not found, adjust cursor properly.
+ */
+
+ if (sought != 0)
+ return (ret);
+
+ if ((t_ret = __ham_item_done(dbc, 0)) != 0 && ret == 0)
+ ret = t_ret;
+ return (ret);
+}
+
+/*
+ * Initialize a dbt using some possibly already allocated storage
+ * for items.
+ * PUBLIC: int __ham_init_dbt __P((DBT *, u_int32_t, void **, u_int32_t *));
+ */
+int
+__ham_init_dbt(dbt, size, bufp, sizep)
+ DBT *dbt;
+ u_int32_t size;
+ void **bufp;
+ u_int32_t *sizep;
+{
+ int ret;
+
+ memset(dbt, 0, sizeof(*dbt));
+ if (*sizep < size) {
+ if ((ret = __os_realloc(bufp, size)) != 0) {
+ *sizep = 0;
+ return (ret);
+ }
+ *sizep = size;
+ }
+ dbt->data = *bufp;
+ dbt->size = size;
+ return (0);
+}
+
+/*
+ * Adjust the cursor after an insert or delete. The cursor passed is
+ * the one that was operated upon; we just need to check any of the
+ * others.
+ *
+ * len indicates the length of the item added/deleted
+ * add indicates if the item indicated by the cursor has just been
+ * added (add == 1) or deleted (add == 0).
+ * dup indicates if the addition occurred into a duplicate set.
+ *
+ * PUBLIC: void __ham_c_update
+ * PUBLIC: __P((HASH_CURSOR *, db_pgno_t, u_int32_t, int, int));
+ */
+void
+__ham_c_update(hcp, chg_pgno, len, add, is_dup)
+ HASH_CURSOR *hcp;
+ db_pgno_t chg_pgno;
+ u_int32_t len;
+ int add, is_dup;
+{
+ DB *dbp;
+ DBC *cp;
+ HASH_CURSOR *lcp;
+ int page_deleted;
+
+ /*
+ * Regular adds are always at the end of a given page, so we never
+ * have to adjust anyone's cursor after a regular add.
+ */
+ if (!is_dup && add)
+ return;
+
+ /*
+ * Determine if a page was deleted. If this is a regular update
+ * (i.e., not is_dup) then the deleted page's number will be that in
+ * chg_pgno, and the pgno in the cursor will be different. If this
+ * was an onpage-duplicate, then the same conditions apply. If this
+ * was an off-page duplicate, then we need to verify if hcp->dpgno
+ * is the same (no delete) or different (delete) than chg_pgno.
+ */
+ if (!is_dup || hcp->dpgno == PGNO_INVALID)
+ page_deleted =
+ chg_pgno != PGNO_INVALID && chg_pgno != hcp->pgno;
+ else
+ page_deleted =
+ chg_pgno != PGNO_INVALID && chg_pgno != hcp->dpgno;
+
+ dbp = hcp->dbc->dbp;
+ DB_THREAD_LOCK(dbp);
+
+ for (cp = TAILQ_FIRST(&dbp->active_queue); cp != NULL;
+ cp = TAILQ_NEXT(cp, links)) {
+ if (cp->internal == hcp)
+ continue;
+
+ lcp = (HASH_CURSOR *)cp->internal;
+
+ if (!is_dup && lcp->pgno != chg_pgno)
+ continue;
+
+ if (is_dup) {
+ if (F_ISSET(hcp, H_DELETED) && lcp->pgno != chg_pgno)
+ continue;
+ if (!F_ISSET(hcp, H_DELETED) && lcp->dpgno != chg_pgno)
+ continue;
+ }
+
+ if (page_deleted) {
+ if (is_dup) {
+ lcp->dpgno = hcp->dpgno;
+ lcp->dndx = hcp->dndx;
+ } else {
+ lcp->pgno = hcp->pgno;
+ lcp->bndx = hcp->bndx;
+ lcp->bucket = hcp->bucket;
+ }
+ F_CLR(lcp, H_ISDUP);
+ continue;
+ }
+
+ if (!is_dup && lcp->bndx > hcp->bndx)
+ lcp->bndx--;
+ else if (!is_dup && lcp->bndx == hcp->bndx)
+ F_SET(lcp, H_DELETED);
+ else if (is_dup && lcp->bndx == hcp->bndx) {
+ /* Assign dpgno in case there was page conversion. */
+ lcp->dpgno = hcp->dpgno;
+ if (add && lcp->dndx >= hcp->dndx )
+ lcp->dndx++;
+ else if (!add && lcp->dndx > hcp->dndx)
+ lcp->dndx--;
+ else if (!add && lcp->dndx == hcp->dndx)
+ F_SET(lcp, H_DELETED);
+
+ /* Now adjust on-page information. */
+ if (lcp->dpgno == PGNO_INVALID)
+ if (add) {
+ lcp->dup_tlen += len;
+ if (lcp->dndx > hcp->dndx)
+ lcp->dup_off += len;
+ } else {
+ lcp->dup_tlen -= len;
+ if (lcp->dndx > hcp->dndx)
+ lcp->dup_off -= len;
+ }
+ }
+ }
+ DB_THREAD_UNLOCK(dbp);
+}
+
diff --git a/usr/src/cmd/sendmail/db/hash/hash_auto.c b/usr/src/cmd/sendmail/db/hash/hash_auto.c
new file mode 100644
index 0000000000..94a1dff6ed
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/hash/hash_auto.c
@@ -0,0 +1,1554 @@
+/* Do not edit: automatically built by dist/db_gen.sh. */
+#include "config.h"
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <ctype.h>
+#include <errno.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#endif
+
+#include "db_int.h"
+#include "db_page.h"
+#include "db_dispatch.h"
+#include "hash.h"
+#include "db_am.h"
+/*
+ * PUBLIC: int __ham_insdel_log
+ * PUBLIC: __P((DB_LOG *, DB_TXN *, DB_LSN *, u_int32_t,
+ * PUBLIC: u_int32_t, u_int32_t, db_pgno_t, u_int32_t,
+ * PUBLIC: DB_LSN *, const DBT *, const DBT *));
+ */
+int __ham_insdel_log(logp, txnid, ret_lsnp, flags,
+ opcode, fileid, pgno, ndx, pagelsn, key,
+ data)
+ DB_LOG *logp;
+ DB_TXN *txnid;
+ DB_LSN *ret_lsnp;
+ u_int32_t flags;
+ u_int32_t opcode;
+ u_int32_t fileid;
+ db_pgno_t pgno;
+ u_int32_t ndx;
+ DB_LSN * pagelsn;
+ const DBT *key;
+ const DBT *data;
+{
+ DBT logrec;
+ DB_LSN *lsnp, null_lsn;
+ u_int32_t zero;
+ u_int32_t rectype, txn_num;
+ int ret;
+ u_int8_t *bp;
+
+ rectype = DB_ham_insdel;
+ txn_num = txnid == NULL ? 0 : txnid->txnid;
+ if (txnid == NULL) {
+ ZERO_LSN(null_lsn);
+ lsnp = &null_lsn;
+ } else
+ lsnp = &txnid->last_lsn;
+ logrec.size = sizeof(rectype) + sizeof(txn_num) + sizeof(DB_LSN)
+ + sizeof(opcode)
+ + sizeof(fileid)
+ + sizeof(pgno)
+ + sizeof(ndx)
+ + sizeof(*pagelsn)
+ + sizeof(u_int32_t) + (key == NULL ? 0 : key->size)
+ + sizeof(u_int32_t) + (data == NULL ? 0 : data->size);
+ if ((ret = __os_malloc(logrec.size, NULL, &logrec.data)) != 0)
+ return (ret);
+
+ bp = logrec.data;
+ memcpy(bp, &rectype, sizeof(rectype));
+ bp += sizeof(rectype);
+ memcpy(bp, &txn_num, sizeof(txn_num));
+ bp += sizeof(txn_num);
+ memcpy(bp, lsnp, sizeof(DB_LSN));
+ bp += sizeof(DB_LSN);
+ memcpy(bp, &opcode, sizeof(opcode));
+ bp += sizeof(opcode);
+ memcpy(bp, &fileid, sizeof(fileid));
+ bp += sizeof(fileid);
+ memcpy(bp, &pgno, sizeof(pgno));
+ bp += sizeof(pgno);
+ memcpy(bp, &ndx, sizeof(ndx));
+ bp += sizeof(ndx);
+ if (pagelsn != NULL)
+ memcpy(bp, pagelsn, sizeof(*pagelsn));
+ else
+ memset(bp, 0, sizeof(*pagelsn));
+ bp += sizeof(*pagelsn);
+ if (key == NULL) {
+ zero = 0;
+ memcpy(bp, &zero, sizeof(u_int32_t));
+ bp += sizeof(u_int32_t);
+ } else {
+ memcpy(bp, &key->size, sizeof(key->size));
+ bp += sizeof(key->size);
+ memcpy(bp, key->data, key->size);
+ bp += key->size;
+ }
+ if (data == NULL) {
+ zero = 0;
+ memcpy(bp, &zero, sizeof(u_int32_t));
+ bp += sizeof(u_int32_t);
+ } else {
+ memcpy(bp, &data->size, sizeof(data->size));
+ bp += sizeof(data->size);
+ memcpy(bp, data->data, data->size);
+ bp += data->size;
+ }
+#ifdef DIAGNOSTIC
+ if ((u_int32_t)(bp - (u_int8_t *)logrec.data) != logrec.size)
+ fprintf(stderr, "Error in log record length");
+#endif
+ ret = log_put(logp, ret_lsnp, (DBT *)&logrec, flags);
+ if (txnid != NULL)
+ txnid->last_lsn = *ret_lsnp;
+ __os_free(logrec.data, 0);
+ return (ret);
+}
+
+/*
+ * PUBLIC: int __ham_insdel_print
+ * PUBLIC: __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+ */
+int
+__ham_insdel_print(notused1, dbtp, lsnp, notused2, notused3)
+ DB_LOG *notused1;
+ DBT *dbtp;
+ DB_LSN *lsnp;
+ int notused2;
+ void *notused3;
+{
+ __ham_insdel_args *argp;
+ u_int32_t i;
+ u_int ch;
+ int ret;
+
+ i = 0;
+ ch = 0;
+ notused1 = NULL;
+ notused2 = 0;
+ notused3 = NULL;
+
+ if ((ret = __ham_insdel_read(dbtp->data, &argp)) != 0)
+ return (ret);
+ printf("[%lu][%lu]ham_insdel: rec: %lu txnid %lx prevlsn [%lu][%lu]\n",
+ (u_long)lsnp->file,
+ (u_long)lsnp->offset,
+ (u_long)argp->type,
+ (u_long)argp->txnid->txnid,
+ (u_long)argp->prev_lsn.file,
+ (u_long)argp->prev_lsn.offset);
+ printf("\topcode: %lu\n", (u_long)argp->opcode);
+ printf("\tfileid: %lu\n", (u_long)argp->fileid);
+ printf("\tpgno: %lu\n", (u_long)argp->pgno);
+ printf("\tndx: %lu\n", (u_long)argp->ndx);
+ printf("\tpagelsn: [%lu][%lu]\n",
+ (u_long)argp->pagelsn.file, (u_long)argp->pagelsn.offset);
+ printf("\tkey: ");
+ for (i = 0; i < argp->key.size; i++) {
+ ch = ((u_int8_t *)argp->key.data)[i];
+ if (isprint(ch) || ch == 0xa)
+ putchar(ch);
+ else
+ printf("%#x ", ch);
+ }
+ printf("\n");
+ printf("\tdata: ");
+ for (i = 0; i < argp->data.size; i++) {
+ ch = ((u_int8_t *)argp->data.data)[i];
+ if (isprint(ch) || ch == 0xa)
+ putchar(ch);
+ else
+ printf("%#x ", ch);
+ }
+ printf("\n");
+ printf("\n");
+ __os_free(argp, 0);
+ return (0);
+}
+
+/*
+ * PUBLIC: int __ham_insdel_read __P((void *, __ham_insdel_args **));
+ */
+int
+__ham_insdel_read(recbuf, argpp)
+ void *recbuf;
+ __ham_insdel_args **argpp;
+{
+ __ham_insdel_args *argp;
+ u_int8_t *bp;
+ int ret;
+
+ ret = __os_malloc(sizeof(__ham_insdel_args) +
+ sizeof(DB_TXN), NULL, &argp);
+ if (ret != 0)
+ return (ret);
+ argp->txnid = (DB_TXN *)&argp[1];
+ bp = recbuf;
+ memcpy(&argp->type, bp, sizeof(argp->type));
+ bp += sizeof(argp->type);
+ memcpy(&argp->txnid->txnid, bp, sizeof(argp->txnid->txnid));
+ bp += sizeof(argp->txnid->txnid);
+ memcpy(&argp->prev_lsn, bp, sizeof(DB_LSN));
+ bp += sizeof(DB_LSN);
+ memcpy(&argp->opcode, bp, sizeof(argp->opcode));
+ bp += sizeof(argp->opcode);
+ memcpy(&argp->fileid, bp, sizeof(argp->fileid));
+ bp += sizeof(argp->fileid);
+ memcpy(&argp->pgno, bp, sizeof(argp->pgno));
+ bp += sizeof(argp->pgno);
+ memcpy(&argp->ndx, bp, sizeof(argp->ndx));
+ bp += sizeof(argp->ndx);
+ memcpy(&argp->pagelsn, bp, sizeof(argp->pagelsn));
+ bp += sizeof(argp->pagelsn);
+ memcpy(&argp->key.size, bp, sizeof(u_int32_t));
+ bp += sizeof(u_int32_t);
+ argp->key.data = bp;
+ bp += argp->key.size;
+ memcpy(&argp->data.size, bp, sizeof(u_int32_t));
+ bp += sizeof(u_int32_t);
+ argp->data.data = bp;
+ bp += argp->data.size;
+ *argpp = argp;
+ return (0);
+}
+
+/*
+ * PUBLIC: int __ham_newpage_log
+ * PUBLIC: __P((DB_LOG *, DB_TXN *, DB_LSN *, u_int32_t,
+ * PUBLIC: u_int32_t, u_int32_t, db_pgno_t, DB_LSN *,
+ * PUBLIC: db_pgno_t, DB_LSN *, db_pgno_t, DB_LSN *));
+ */
+int __ham_newpage_log(logp, txnid, ret_lsnp, flags,
+ opcode, fileid, prev_pgno, prevlsn, new_pgno, pagelsn,
+ next_pgno, nextlsn)
+ DB_LOG *logp;
+ DB_TXN *txnid;
+ DB_LSN *ret_lsnp;
+ u_int32_t flags;
+ u_int32_t opcode;
+ u_int32_t fileid;
+ db_pgno_t prev_pgno;
+ DB_LSN * prevlsn;
+ db_pgno_t new_pgno;
+ DB_LSN * pagelsn;
+ db_pgno_t next_pgno;
+ DB_LSN * nextlsn;
+{
+ DBT logrec;
+ DB_LSN *lsnp, null_lsn;
+ u_int32_t rectype, txn_num;
+ int ret;
+ u_int8_t *bp;
+
+ rectype = DB_ham_newpage;
+ txn_num = txnid == NULL ? 0 : txnid->txnid;
+ if (txnid == NULL) {
+ ZERO_LSN(null_lsn);
+ lsnp = &null_lsn;
+ } else
+ lsnp = &txnid->last_lsn;
+ logrec.size = sizeof(rectype) + sizeof(txn_num) + sizeof(DB_LSN)
+ + sizeof(opcode)
+ + sizeof(fileid)
+ + sizeof(prev_pgno)
+ + sizeof(*prevlsn)
+ + sizeof(new_pgno)
+ + sizeof(*pagelsn)
+ + sizeof(next_pgno)
+ + sizeof(*nextlsn);
+ if ((ret = __os_malloc(logrec.size, NULL, &logrec.data)) != 0)
+ return (ret);
+
+ bp = logrec.data;
+ memcpy(bp, &rectype, sizeof(rectype));
+ bp += sizeof(rectype);
+ memcpy(bp, &txn_num, sizeof(txn_num));
+ bp += sizeof(txn_num);
+ memcpy(bp, lsnp, sizeof(DB_LSN));
+ bp += sizeof(DB_LSN);
+ memcpy(bp, &opcode, sizeof(opcode));
+ bp += sizeof(opcode);
+ memcpy(bp, &fileid, sizeof(fileid));
+ bp += sizeof(fileid);
+ memcpy(bp, &prev_pgno, sizeof(prev_pgno));
+ bp += sizeof(prev_pgno);
+ if (prevlsn != NULL)
+ memcpy(bp, prevlsn, sizeof(*prevlsn));
+ else
+ memset(bp, 0, sizeof(*prevlsn));
+ bp += sizeof(*prevlsn);
+ memcpy(bp, &new_pgno, sizeof(new_pgno));
+ bp += sizeof(new_pgno);
+ if (pagelsn != NULL)
+ memcpy(bp, pagelsn, sizeof(*pagelsn));
+ else
+ memset(bp, 0, sizeof(*pagelsn));
+ bp += sizeof(*pagelsn);
+ memcpy(bp, &next_pgno, sizeof(next_pgno));
+ bp += sizeof(next_pgno);
+ if (nextlsn != NULL)
+ memcpy(bp, nextlsn, sizeof(*nextlsn));
+ else
+ memset(bp, 0, sizeof(*nextlsn));
+ bp += sizeof(*nextlsn);
+#ifdef DIAGNOSTIC
+ if ((u_int32_t)(bp - (u_int8_t *)logrec.data) != logrec.size)
+ fprintf(stderr, "Error in log record length");
+#endif
+ ret = log_put(logp, ret_lsnp, (DBT *)&logrec, flags);
+ if (txnid != NULL)
+ txnid->last_lsn = *ret_lsnp;
+ __os_free(logrec.data, 0);
+ return (ret);
+}
+
+/*
+ * PUBLIC: int __ham_newpage_print
+ * PUBLIC: __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+ */
+int
+__ham_newpage_print(notused1, dbtp, lsnp, notused2, notused3)
+ DB_LOG *notused1;
+ DBT *dbtp;
+ DB_LSN *lsnp;
+ int notused2;
+ void *notused3;
+{
+ __ham_newpage_args *argp;
+ u_int32_t i;
+ u_int ch;
+ int ret;
+
+ i = 0;
+ ch = 0;
+ notused1 = NULL;
+ notused2 = 0;
+ notused3 = NULL;
+
+ if ((ret = __ham_newpage_read(dbtp->data, &argp)) != 0)
+ return (ret);
+ printf("[%lu][%lu]ham_newpage: rec: %lu txnid %lx prevlsn [%lu][%lu]\n",
+ (u_long)lsnp->file,
+ (u_long)lsnp->offset,
+ (u_long)argp->type,
+ (u_long)argp->txnid->txnid,
+ (u_long)argp->prev_lsn.file,
+ (u_long)argp->prev_lsn.offset);
+ printf("\topcode: %lu\n", (u_long)argp->opcode);
+ printf("\tfileid: %lu\n", (u_long)argp->fileid);
+ printf("\tprev_pgno: %lu\n", (u_long)argp->prev_pgno);
+ printf("\tprevlsn: [%lu][%lu]\n",
+ (u_long)argp->prevlsn.file, (u_long)argp->prevlsn.offset);
+ printf("\tnew_pgno: %lu\n", (u_long)argp->new_pgno);
+ printf("\tpagelsn: [%lu][%lu]\n",
+ (u_long)argp->pagelsn.file, (u_long)argp->pagelsn.offset);
+ printf("\tnext_pgno: %lu\n", (u_long)argp->next_pgno);
+ printf("\tnextlsn: [%lu][%lu]\n",
+ (u_long)argp->nextlsn.file, (u_long)argp->nextlsn.offset);
+ printf("\n");
+ __os_free(argp, 0);
+ return (0);
+}
+
+/*
+ * PUBLIC: int __ham_newpage_read __P((void *, __ham_newpage_args **));
+ */
+int
+__ham_newpage_read(recbuf, argpp)
+ void *recbuf;
+ __ham_newpage_args **argpp;
+{
+ __ham_newpage_args *argp;
+ u_int8_t *bp;
+ int ret;
+
+ ret = __os_malloc(sizeof(__ham_newpage_args) +
+ sizeof(DB_TXN), NULL, &argp);
+ if (ret != 0)
+ return (ret);
+ argp->txnid = (DB_TXN *)&argp[1];
+ bp = recbuf;
+ memcpy(&argp->type, bp, sizeof(argp->type));
+ bp += sizeof(argp->type);
+ memcpy(&argp->txnid->txnid, bp, sizeof(argp->txnid->txnid));
+ bp += sizeof(argp->txnid->txnid);
+ memcpy(&argp->prev_lsn, bp, sizeof(DB_LSN));
+ bp += sizeof(DB_LSN);
+ memcpy(&argp->opcode, bp, sizeof(argp->opcode));
+ bp += sizeof(argp->opcode);
+ memcpy(&argp->fileid, bp, sizeof(argp->fileid));
+ bp += sizeof(argp->fileid);
+ memcpy(&argp->prev_pgno, bp, sizeof(argp->prev_pgno));
+ bp += sizeof(argp->prev_pgno);
+ memcpy(&argp->prevlsn, bp, sizeof(argp->prevlsn));
+ bp += sizeof(argp->prevlsn);
+ memcpy(&argp->new_pgno, bp, sizeof(argp->new_pgno));
+ bp += sizeof(argp->new_pgno);
+ memcpy(&argp->pagelsn, bp, sizeof(argp->pagelsn));
+ bp += sizeof(argp->pagelsn);
+ memcpy(&argp->next_pgno, bp, sizeof(argp->next_pgno));
+ bp += sizeof(argp->next_pgno);
+ memcpy(&argp->nextlsn, bp, sizeof(argp->nextlsn));
+ bp += sizeof(argp->nextlsn);
+ *argpp = argp;
+ return (0);
+}
+
+/*
+ * PUBLIC: int __ham_splitmeta_log
+ * PUBLIC: __P((DB_LOG *, DB_TXN *, DB_LSN *, u_int32_t,
+ * PUBLIC: u_int32_t, u_int32_t, u_int32_t, u_int32_t,
+ * PUBLIC: DB_LSN *));
+ */
+int __ham_splitmeta_log(logp, txnid, ret_lsnp, flags,
+ fileid, bucket, ovflpoint, spares, metalsn)
+ DB_LOG *logp;
+ DB_TXN *txnid;
+ DB_LSN *ret_lsnp;
+ u_int32_t flags;
+ u_int32_t fileid;
+ u_int32_t bucket;
+ u_int32_t ovflpoint;
+ u_int32_t spares;
+ DB_LSN * metalsn;
+{
+ DBT logrec;
+ DB_LSN *lsnp, null_lsn;
+ u_int32_t rectype, txn_num;
+ int ret;
+ u_int8_t *bp;
+
+ rectype = DB_ham_splitmeta;
+ txn_num = txnid == NULL ? 0 : txnid->txnid;
+ if (txnid == NULL) {
+ ZERO_LSN(null_lsn);
+ lsnp = &null_lsn;
+ } else
+ lsnp = &txnid->last_lsn;
+ logrec.size = sizeof(rectype) + sizeof(txn_num) + sizeof(DB_LSN)
+ + sizeof(fileid)
+ + sizeof(bucket)
+ + sizeof(ovflpoint)
+ + sizeof(spares)
+ + sizeof(*metalsn);
+ if ((ret = __os_malloc(logrec.size, NULL, &logrec.data)) != 0)
+ return (ret);
+
+ bp = logrec.data;
+ memcpy(bp, &rectype, sizeof(rectype));
+ bp += sizeof(rectype);
+ memcpy(bp, &txn_num, sizeof(txn_num));
+ bp += sizeof(txn_num);
+ memcpy(bp, lsnp, sizeof(DB_LSN));
+ bp += sizeof(DB_LSN);
+ memcpy(bp, &fileid, sizeof(fileid));
+ bp += sizeof(fileid);
+ memcpy(bp, &bucket, sizeof(bucket));
+ bp += sizeof(bucket);
+ memcpy(bp, &ovflpoint, sizeof(ovflpoint));
+ bp += sizeof(ovflpoint);
+ memcpy(bp, &spares, sizeof(spares));
+ bp += sizeof(spares);
+ if (metalsn != NULL)
+ memcpy(bp, metalsn, sizeof(*metalsn));
+ else
+ memset(bp, 0, sizeof(*metalsn));
+ bp += sizeof(*metalsn);
+#ifdef DIAGNOSTIC
+ if ((u_int32_t)(bp - (u_int8_t *)logrec.data) != logrec.size)
+ fprintf(stderr, "Error in log record length");
+#endif
+ ret = log_put(logp, ret_lsnp, (DBT *)&logrec, flags);
+ if (txnid != NULL)
+ txnid->last_lsn = *ret_lsnp;
+ __os_free(logrec.data, 0);
+ return (ret);
+}
+
+/*
+ * PUBLIC: int __ham_splitmeta_print
+ * PUBLIC: __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+ */
+int
+__ham_splitmeta_print(notused1, dbtp, lsnp, notused2, notused3)
+ DB_LOG *notused1;
+ DBT *dbtp;
+ DB_LSN *lsnp;
+ int notused2;
+ void *notused3;
+{
+ __ham_splitmeta_args *argp;
+ u_int32_t i;
+ u_int ch;
+ int ret;
+
+ i = 0;
+ ch = 0;
+ notused1 = NULL;
+ notused2 = 0;
+ notused3 = NULL;
+
+ if ((ret = __ham_splitmeta_read(dbtp->data, &argp)) != 0)
+ return (ret);
+ printf("[%lu][%lu]ham_splitmeta: rec: %lu txnid %lx prevlsn [%lu][%lu]\n",
+ (u_long)lsnp->file,
+ (u_long)lsnp->offset,
+ (u_long)argp->type,
+ (u_long)argp->txnid->txnid,
+ (u_long)argp->prev_lsn.file,
+ (u_long)argp->prev_lsn.offset);
+ printf("\tfileid: %lu\n", (u_long)argp->fileid);
+ printf("\tbucket: %lu\n", (u_long)argp->bucket);
+ printf("\tovflpoint: %lu\n", (u_long)argp->ovflpoint);
+ printf("\tspares: %lu\n", (u_long)argp->spares);
+ printf("\tmetalsn: [%lu][%lu]\n",
+ (u_long)argp->metalsn.file, (u_long)argp->metalsn.offset);
+ printf("\n");
+ __os_free(argp, 0);
+ return (0);
+}
+
+/*
+ * PUBLIC: int __ham_splitmeta_read __P((void *, __ham_splitmeta_args **));
+ */
+int
+__ham_splitmeta_read(recbuf, argpp)
+ void *recbuf;
+ __ham_splitmeta_args **argpp;
+{
+ __ham_splitmeta_args *argp;
+ u_int8_t *bp;
+ int ret;
+
+ ret = __os_malloc(sizeof(__ham_splitmeta_args) +
+ sizeof(DB_TXN), NULL, &argp);
+ if (ret != 0)
+ return (ret);
+ argp->txnid = (DB_TXN *)&argp[1];
+ bp = recbuf;
+ memcpy(&argp->type, bp, sizeof(argp->type));
+ bp += sizeof(argp->type);
+ memcpy(&argp->txnid->txnid, bp, sizeof(argp->txnid->txnid));
+ bp += sizeof(argp->txnid->txnid);
+ memcpy(&argp->prev_lsn, bp, sizeof(DB_LSN));
+ bp += sizeof(DB_LSN);
+ memcpy(&argp->fileid, bp, sizeof(argp->fileid));
+ bp += sizeof(argp->fileid);
+ memcpy(&argp->bucket, bp, sizeof(argp->bucket));
+ bp += sizeof(argp->bucket);
+ memcpy(&argp->ovflpoint, bp, sizeof(argp->ovflpoint));
+ bp += sizeof(argp->ovflpoint);
+ memcpy(&argp->spares, bp, sizeof(argp->spares));
+ bp += sizeof(argp->spares);
+ memcpy(&argp->metalsn, bp, sizeof(argp->metalsn));
+ bp += sizeof(argp->metalsn);
+ *argpp = argp;
+ return (0);
+}
+
+/*
+ * PUBLIC: int __ham_splitdata_log
+ * PUBLIC: __P((DB_LOG *, DB_TXN *, DB_LSN *, u_int32_t,
+ * PUBLIC: u_int32_t, u_int32_t, db_pgno_t, const DBT *,
+ * PUBLIC: DB_LSN *));
+ */
+int __ham_splitdata_log(logp, txnid, ret_lsnp, flags,
+ fileid, opcode, pgno, pageimage, pagelsn)
+ DB_LOG *logp;
+ DB_TXN *txnid;
+ DB_LSN *ret_lsnp;
+ u_int32_t flags;
+ u_int32_t fileid;
+ u_int32_t opcode;
+ db_pgno_t pgno;
+ const DBT *pageimage;
+ DB_LSN * pagelsn;
+{
+ DBT logrec;
+ DB_LSN *lsnp, null_lsn;
+ u_int32_t zero;
+ u_int32_t rectype, txn_num;
+ int ret;
+ u_int8_t *bp;
+
+ rectype = DB_ham_splitdata;
+ txn_num = txnid == NULL ? 0 : txnid->txnid;
+ if (txnid == NULL) {
+ ZERO_LSN(null_lsn);
+ lsnp = &null_lsn;
+ } else
+ lsnp = &txnid->last_lsn;
+ logrec.size = sizeof(rectype) + sizeof(txn_num) + sizeof(DB_LSN)
+ + sizeof(fileid)
+ + sizeof(opcode)
+ + sizeof(pgno)
+ + sizeof(u_int32_t) + (pageimage == NULL ? 0 : pageimage->size)
+ + sizeof(*pagelsn);
+ if ((ret = __os_malloc(logrec.size, NULL, &logrec.data)) != 0)
+ return (ret);
+
+ bp = logrec.data;
+ memcpy(bp, &rectype, sizeof(rectype));
+ bp += sizeof(rectype);
+ memcpy(bp, &txn_num, sizeof(txn_num));
+ bp += sizeof(txn_num);
+ memcpy(bp, lsnp, sizeof(DB_LSN));
+ bp += sizeof(DB_LSN);
+ memcpy(bp, &fileid, sizeof(fileid));
+ bp += sizeof(fileid);
+ memcpy(bp, &opcode, sizeof(opcode));
+ bp += sizeof(opcode);
+ memcpy(bp, &pgno, sizeof(pgno));
+ bp += sizeof(pgno);
+ if (pageimage == NULL) {
+ zero = 0;
+ memcpy(bp, &zero, sizeof(u_int32_t));
+ bp += sizeof(u_int32_t);
+ } else {
+ memcpy(bp, &pageimage->size, sizeof(pageimage->size));
+ bp += sizeof(pageimage->size);
+ memcpy(bp, pageimage->data, pageimage->size);
+ bp += pageimage->size;
+ }
+ if (pagelsn != NULL)
+ memcpy(bp, pagelsn, sizeof(*pagelsn));
+ else
+ memset(bp, 0, sizeof(*pagelsn));
+ bp += sizeof(*pagelsn);
+#ifdef DIAGNOSTIC
+ if ((u_int32_t)(bp - (u_int8_t *)logrec.data) != logrec.size)
+ fprintf(stderr, "Error in log record length");
+#endif
+ ret = log_put(logp, ret_lsnp, (DBT *)&logrec, flags);
+ if (txnid != NULL)
+ txnid->last_lsn = *ret_lsnp;
+ __os_free(logrec.data, 0);
+ return (ret);
+}
+
+/*
+ * PUBLIC: int __ham_splitdata_print
+ * PUBLIC: __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+ */
+int
+__ham_splitdata_print(notused1, dbtp, lsnp, notused2, notused3)
+ DB_LOG *notused1;
+ DBT *dbtp;
+ DB_LSN *lsnp;
+ int notused2;
+ void *notused3;
+{
+ __ham_splitdata_args *argp;
+ u_int32_t i;
+ u_int ch;
+ int ret;
+
+ i = 0;
+ ch = 0;
+ notused1 = NULL;
+ notused2 = 0;
+ notused3 = NULL;
+
+ if ((ret = __ham_splitdata_read(dbtp->data, &argp)) != 0)
+ return (ret);
+ printf("[%lu][%lu]ham_splitdata: rec: %lu txnid %lx prevlsn [%lu][%lu]\n",
+ (u_long)lsnp->file,
+ (u_long)lsnp->offset,
+ (u_long)argp->type,
+ (u_long)argp->txnid->txnid,
+ (u_long)argp->prev_lsn.file,
+ (u_long)argp->prev_lsn.offset);
+ printf("\tfileid: %lu\n", (u_long)argp->fileid);
+ printf("\topcode: %lu\n", (u_long)argp->opcode);
+ printf("\tpgno: %lu\n", (u_long)argp->pgno);
+ printf("\tpageimage: ");
+ for (i = 0; i < argp->pageimage.size; i++) {
+ ch = ((u_int8_t *)argp->pageimage.data)[i];
+ if (isprint(ch) || ch == 0xa)
+ putchar(ch);
+ else
+ printf("%#x ", ch);
+ }
+ printf("\n");
+ printf("\tpagelsn: [%lu][%lu]\n",
+ (u_long)argp->pagelsn.file, (u_long)argp->pagelsn.offset);
+ printf("\n");
+ __os_free(argp, 0);
+ return (0);
+}
+
+/*
+ * PUBLIC: int __ham_splitdata_read __P((void *, __ham_splitdata_args **));
+ */
+int
+__ham_splitdata_read(recbuf, argpp)
+ void *recbuf;
+ __ham_splitdata_args **argpp;
+{
+ __ham_splitdata_args *argp;
+ u_int8_t *bp;
+ int ret;
+
+ ret = __os_malloc(sizeof(__ham_splitdata_args) +
+ sizeof(DB_TXN), NULL, &argp);
+ if (ret != 0)
+ return (ret);
+ argp->txnid = (DB_TXN *)&argp[1];
+ bp = recbuf;
+ memcpy(&argp->type, bp, sizeof(argp->type));
+ bp += sizeof(argp->type);
+ memcpy(&argp->txnid->txnid, bp, sizeof(argp->txnid->txnid));
+ bp += sizeof(argp->txnid->txnid);
+ memcpy(&argp->prev_lsn, bp, sizeof(DB_LSN));
+ bp += sizeof(DB_LSN);
+ memcpy(&argp->fileid, bp, sizeof(argp->fileid));
+ bp += sizeof(argp->fileid);
+ memcpy(&argp->opcode, bp, sizeof(argp->opcode));
+ bp += sizeof(argp->opcode);
+ memcpy(&argp->pgno, bp, sizeof(argp->pgno));
+ bp += sizeof(argp->pgno);
+ memcpy(&argp->pageimage.size, bp, sizeof(u_int32_t));
+ bp += sizeof(u_int32_t);
+ argp->pageimage.data = bp;
+ bp += argp->pageimage.size;
+ memcpy(&argp->pagelsn, bp, sizeof(argp->pagelsn));
+ bp += sizeof(argp->pagelsn);
+ *argpp = argp;
+ return (0);
+}
+
+/*
+ * PUBLIC: int __ham_replace_log
+ * PUBLIC: __P((DB_LOG *, DB_TXN *, DB_LSN *, u_int32_t,
+ * PUBLIC: u_int32_t, db_pgno_t, u_int32_t, DB_LSN *,
+ * PUBLIC: int32_t, const DBT *, const DBT *, u_int32_t));
+ */
+int __ham_replace_log(logp, txnid, ret_lsnp, flags,
+ fileid, pgno, ndx, pagelsn, off, olditem,
+ newitem, makedup)
+ DB_LOG *logp;
+ DB_TXN *txnid;
+ DB_LSN *ret_lsnp;
+ u_int32_t flags;
+ u_int32_t fileid;
+ db_pgno_t pgno;
+ u_int32_t ndx;
+ DB_LSN * pagelsn;
+ int32_t off;
+ const DBT *olditem;
+ const DBT *newitem;
+ u_int32_t makedup;
+{
+ DBT logrec;
+ DB_LSN *lsnp, null_lsn;
+ u_int32_t zero;
+ u_int32_t rectype, txn_num;
+ int ret;
+ u_int8_t *bp;
+
+ rectype = DB_ham_replace;
+ txn_num = txnid == NULL ? 0 : txnid->txnid;
+ if (txnid == NULL) {
+ ZERO_LSN(null_lsn);
+ lsnp = &null_lsn;
+ } else
+ lsnp = &txnid->last_lsn;
+ logrec.size = sizeof(rectype) + sizeof(txn_num) + sizeof(DB_LSN)
+ + sizeof(fileid)
+ + sizeof(pgno)
+ + sizeof(ndx)
+ + sizeof(*pagelsn)
+ + sizeof(off)
+ + sizeof(u_int32_t) + (olditem == NULL ? 0 : olditem->size)
+ + sizeof(u_int32_t) + (newitem == NULL ? 0 : newitem->size)
+ + sizeof(makedup);
+ if ((ret = __os_malloc(logrec.size, NULL, &logrec.data)) != 0)
+ return (ret);
+
+ bp = logrec.data;
+ memcpy(bp, &rectype, sizeof(rectype));
+ bp += sizeof(rectype);
+ memcpy(bp, &txn_num, sizeof(txn_num));
+ bp += sizeof(txn_num);
+ memcpy(bp, lsnp, sizeof(DB_LSN));
+ bp += sizeof(DB_LSN);
+ memcpy(bp, &fileid, sizeof(fileid));
+ bp += sizeof(fileid);
+ memcpy(bp, &pgno, sizeof(pgno));
+ bp += sizeof(pgno);
+ memcpy(bp, &ndx, sizeof(ndx));
+ bp += sizeof(ndx);
+ if (pagelsn != NULL)
+ memcpy(bp, pagelsn, sizeof(*pagelsn));
+ else
+ memset(bp, 0, sizeof(*pagelsn));
+ bp += sizeof(*pagelsn);
+ memcpy(bp, &off, sizeof(off));
+ bp += sizeof(off);
+ if (olditem == NULL) {
+ zero = 0;
+ memcpy(bp, &zero, sizeof(u_int32_t));
+ bp += sizeof(u_int32_t);
+ } else {
+ memcpy(bp, &olditem->size, sizeof(olditem->size));
+ bp += sizeof(olditem->size);
+ memcpy(bp, olditem->data, olditem->size);
+ bp += olditem->size;
+ }
+ if (newitem == NULL) {
+ zero = 0;
+ memcpy(bp, &zero, sizeof(u_int32_t));
+ bp += sizeof(u_int32_t);
+ } else {
+ memcpy(bp, &newitem->size, sizeof(newitem->size));
+ bp += sizeof(newitem->size);
+ memcpy(bp, newitem->data, newitem->size);
+ bp += newitem->size;
+ }
+ memcpy(bp, &makedup, sizeof(makedup));
+ bp += sizeof(makedup);
+#ifdef DIAGNOSTIC
+ if ((u_int32_t)(bp - (u_int8_t *)logrec.data) != logrec.size)
+ fprintf(stderr, "Error in log record length");
+#endif
+ ret = log_put(logp, ret_lsnp, (DBT *)&logrec, flags);
+ if (txnid != NULL)
+ txnid->last_lsn = *ret_lsnp;
+ __os_free(logrec.data, 0);
+ return (ret);
+}
+
+/*
+ * PUBLIC: int __ham_replace_print
+ * PUBLIC: __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+ */
+int
+__ham_replace_print(notused1, dbtp, lsnp, notused2, notused3)
+ DB_LOG *notused1;
+ DBT *dbtp;
+ DB_LSN *lsnp;
+ int notused2;
+ void *notused3;
+{
+ __ham_replace_args *argp;
+ u_int32_t i;
+ u_int ch;
+ int ret;
+
+ i = 0;
+ ch = 0;
+ notused1 = NULL;
+ notused2 = 0;
+ notused3 = NULL;
+
+ if ((ret = __ham_replace_read(dbtp->data, &argp)) != 0)
+ return (ret);
+ printf("[%lu][%lu]ham_replace: rec: %lu txnid %lx prevlsn [%lu][%lu]\n",
+ (u_long)lsnp->file,
+ (u_long)lsnp->offset,
+ (u_long)argp->type,
+ (u_long)argp->txnid->txnid,
+ (u_long)argp->prev_lsn.file,
+ (u_long)argp->prev_lsn.offset);
+ printf("\tfileid: %lu\n", (u_long)argp->fileid);
+ printf("\tpgno: %lu\n", (u_long)argp->pgno);
+ printf("\tndx: %lu\n", (u_long)argp->ndx);
+ printf("\tpagelsn: [%lu][%lu]\n",
+ (u_long)argp->pagelsn.file, (u_long)argp->pagelsn.offset);
+ printf("\toff: %ld\n", (long)argp->off);
+ printf("\tolditem: ");
+ for (i = 0; i < argp->olditem.size; i++) {
+ ch = ((u_int8_t *)argp->olditem.data)[i];
+ if (isprint(ch) || ch == 0xa)
+ putchar(ch);
+ else
+ printf("%#x ", ch);
+ }
+ printf("\n");
+ printf("\tnewitem: ");
+ for (i = 0; i < argp->newitem.size; i++) {
+ ch = ((u_int8_t *)argp->newitem.data)[i];
+ if (isprint(ch) || ch == 0xa)
+ putchar(ch);
+ else
+ printf("%#x ", ch);
+ }
+ printf("\n");
+ printf("\tmakedup: %lu\n", (u_long)argp->makedup);
+ printf("\n");
+ __os_free(argp, 0);
+ return (0);
+}
+
+/*
+ * PUBLIC: int __ham_replace_read __P((void *, __ham_replace_args **));
+ */
+int
+__ham_replace_read(recbuf, argpp)
+ void *recbuf;
+ __ham_replace_args **argpp;
+{
+ __ham_replace_args *argp;
+ u_int8_t *bp;
+ int ret;
+
+ ret = __os_malloc(sizeof(__ham_replace_args) +
+ sizeof(DB_TXN), NULL, &argp);
+ if (ret != 0)
+ return (ret);
+ argp->txnid = (DB_TXN *)&argp[1];
+ bp = recbuf;
+ memcpy(&argp->type, bp, sizeof(argp->type));
+ bp += sizeof(argp->type);
+ memcpy(&argp->txnid->txnid, bp, sizeof(argp->txnid->txnid));
+ bp += sizeof(argp->txnid->txnid);
+ memcpy(&argp->prev_lsn, bp, sizeof(DB_LSN));
+ bp += sizeof(DB_LSN);
+ memcpy(&argp->fileid, bp, sizeof(argp->fileid));
+ bp += sizeof(argp->fileid);
+ memcpy(&argp->pgno, bp, sizeof(argp->pgno));
+ bp += sizeof(argp->pgno);
+ memcpy(&argp->ndx, bp, sizeof(argp->ndx));
+ bp += sizeof(argp->ndx);
+ memcpy(&argp->pagelsn, bp, sizeof(argp->pagelsn));
+ bp += sizeof(argp->pagelsn);
+ memcpy(&argp->off, bp, sizeof(argp->off));
+ bp += sizeof(argp->off);
+ memcpy(&argp->olditem.size, bp, sizeof(u_int32_t));
+ bp += sizeof(u_int32_t);
+ argp->olditem.data = bp;
+ bp += argp->olditem.size;
+ memcpy(&argp->newitem.size, bp, sizeof(u_int32_t));
+ bp += sizeof(u_int32_t);
+ argp->newitem.data = bp;
+ bp += argp->newitem.size;
+ memcpy(&argp->makedup, bp, sizeof(argp->makedup));
+ bp += sizeof(argp->makedup);
+ *argpp = argp;
+ return (0);
+}
+
+/*
+ * PUBLIC: int __ham_newpgno_log
+ * PUBLIC: __P((DB_LOG *, DB_TXN *, DB_LSN *, u_int32_t,
+ * PUBLIC: u_int32_t, u_int32_t, db_pgno_t, db_pgno_t,
+ * PUBLIC: u_int32_t, db_pgno_t, u_int32_t, DB_LSN *,
+ * PUBLIC: DB_LSN *));
+ */
+int __ham_newpgno_log(logp, txnid, ret_lsnp, flags,
+ opcode, fileid, pgno, free_pgno, old_type, old_pgno,
+ new_type, pagelsn, metalsn)
+ DB_LOG *logp;
+ DB_TXN *txnid;
+ DB_LSN *ret_lsnp;
+ u_int32_t flags;
+ u_int32_t opcode;
+ u_int32_t fileid;
+ db_pgno_t pgno;
+ db_pgno_t free_pgno;
+ u_int32_t old_type;
+ db_pgno_t old_pgno;
+ u_int32_t new_type;
+ DB_LSN * pagelsn;
+ DB_LSN * metalsn;
+{
+ DBT logrec;
+ DB_LSN *lsnp, null_lsn;
+ u_int32_t rectype, txn_num;
+ int ret;
+ u_int8_t *bp;
+
+ rectype = DB_ham_newpgno;
+ txn_num = txnid == NULL ? 0 : txnid->txnid;
+ if (txnid == NULL) {
+ ZERO_LSN(null_lsn);
+ lsnp = &null_lsn;
+ } else
+ lsnp = &txnid->last_lsn;
+ logrec.size = sizeof(rectype) + sizeof(txn_num) + sizeof(DB_LSN)
+ + sizeof(opcode)
+ + sizeof(fileid)
+ + sizeof(pgno)
+ + sizeof(free_pgno)
+ + sizeof(old_type)
+ + sizeof(old_pgno)
+ + sizeof(new_type)
+ + sizeof(*pagelsn)
+ + sizeof(*metalsn);
+ if ((ret = __os_malloc(logrec.size, NULL, &logrec.data)) != 0)
+ return (ret);
+
+ bp = logrec.data;
+ memcpy(bp, &rectype, sizeof(rectype));
+ bp += sizeof(rectype);
+ memcpy(bp, &txn_num, sizeof(txn_num));
+ bp += sizeof(txn_num);
+ memcpy(bp, lsnp, sizeof(DB_LSN));
+ bp += sizeof(DB_LSN);
+ memcpy(bp, &opcode, sizeof(opcode));
+ bp += sizeof(opcode);
+ memcpy(bp, &fileid, sizeof(fileid));
+ bp += sizeof(fileid);
+ memcpy(bp, &pgno, sizeof(pgno));
+ bp += sizeof(pgno);
+ memcpy(bp, &free_pgno, sizeof(free_pgno));
+ bp += sizeof(free_pgno);
+ memcpy(bp, &old_type, sizeof(old_type));
+ bp += sizeof(old_type);
+ memcpy(bp, &old_pgno, sizeof(old_pgno));
+ bp += sizeof(old_pgno);
+ memcpy(bp, &new_type, sizeof(new_type));
+ bp += sizeof(new_type);
+ if (pagelsn != NULL)
+ memcpy(bp, pagelsn, sizeof(*pagelsn));
+ else
+ memset(bp, 0, sizeof(*pagelsn));
+ bp += sizeof(*pagelsn);
+ if (metalsn != NULL)
+ memcpy(bp, metalsn, sizeof(*metalsn));
+ else
+ memset(bp, 0, sizeof(*metalsn));
+ bp += sizeof(*metalsn);
+#ifdef DIAGNOSTIC
+ if ((u_int32_t)(bp - (u_int8_t *)logrec.data) != logrec.size)
+ fprintf(stderr, "Error in log record length");
+#endif
+ ret = log_put(logp, ret_lsnp, (DBT *)&logrec, flags);
+ if (txnid != NULL)
+ txnid->last_lsn = *ret_lsnp;
+ __os_free(logrec.data, 0);
+ return (ret);
+}
+
+/*
+ * PUBLIC: int __ham_newpgno_print
+ * PUBLIC: __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+ */
+int
+__ham_newpgno_print(notused1, dbtp, lsnp, notused2, notused3)
+ DB_LOG *notused1;
+ DBT *dbtp;
+ DB_LSN *lsnp;
+ int notused2;
+ void *notused3;
+{
+ __ham_newpgno_args *argp;
+ u_int32_t i;
+ u_int ch;
+ int ret;
+
+ i = 0;
+ ch = 0;
+ notused1 = NULL;
+ notused2 = 0;
+ notused3 = NULL;
+
+ if ((ret = __ham_newpgno_read(dbtp->data, &argp)) != 0)
+ return (ret);
+ printf("[%lu][%lu]ham_newpgno: rec: %lu txnid %lx prevlsn [%lu][%lu]\n",
+ (u_long)lsnp->file,
+ (u_long)lsnp->offset,
+ (u_long)argp->type,
+ (u_long)argp->txnid->txnid,
+ (u_long)argp->prev_lsn.file,
+ (u_long)argp->prev_lsn.offset);
+ printf("\topcode: %lu\n", (u_long)argp->opcode);
+ printf("\tfileid: %lu\n", (u_long)argp->fileid);
+ printf("\tpgno: %lu\n", (u_long)argp->pgno);
+ printf("\tfree_pgno: %lu\n", (u_long)argp->free_pgno);
+ printf("\told_type: %lu\n", (u_long)argp->old_type);
+ printf("\told_pgno: %lu\n", (u_long)argp->old_pgno);
+ printf("\tnew_type: %lu\n", (u_long)argp->new_type);
+ printf("\tpagelsn: [%lu][%lu]\n",
+ (u_long)argp->pagelsn.file, (u_long)argp->pagelsn.offset);
+ printf("\tmetalsn: [%lu][%lu]\n",
+ (u_long)argp->metalsn.file, (u_long)argp->metalsn.offset);
+ printf("\n");
+ __os_free(argp, 0);
+ return (0);
+}
+
+/*
+ * PUBLIC: int __ham_newpgno_read __P((void *, __ham_newpgno_args **));
+ */
+int
+__ham_newpgno_read(recbuf, argpp)
+ void *recbuf;
+ __ham_newpgno_args **argpp;
+{
+ __ham_newpgno_args *argp;
+ u_int8_t *bp;
+ int ret;
+
+ ret = __os_malloc(sizeof(__ham_newpgno_args) +
+ sizeof(DB_TXN), NULL, &argp);
+ if (ret != 0)
+ return (ret);
+ argp->txnid = (DB_TXN *)&argp[1];
+ bp = recbuf;
+ memcpy(&argp->type, bp, sizeof(argp->type));
+ bp += sizeof(argp->type);
+ memcpy(&argp->txnid->txnid, bp, sizeof(argp->txnid->txnid));
+ bp += sizeof(argp->txnid->txnid);
+ memcpy(&argp->prev_lsn, bp, sizeof(DB_LSN));
+ bp += sizeof(DB_LSN);
+ memcpy(&argp->opcode, bp, sizeof(argp->opcode));
+ bp += sizeof(argp->opcode);
+ memcpy(&argp->fileid, bp, sizeof(argp->fileid));
+ bp += sizeof(argp->fileid);
+ memcpy(&argp->pgno, bp, sizeof(argp->pgno));
+ bp += sizeof(argp->pgno);
+ memcpy(&argp->free_pgno, bp, sizeof(argp->free_pgno));
+ bp += sizeof(argp->free_pgno);
+ memcpy(&argp->old_type, bp, sizeof(argp->old_type));
+ bp += sizeof(argp->old_type);
+ memcpy(&argp->old_pgno, bp, sizeof(argp->old_pgno));
+ bp += sizeof(argp->old_pgno);
+ memcpy(&argp->new_type, bp, sizeof(argp->new_type));
+ bp += sizeof(argp->new_type);
+ memcpy(&argp->pagelsn, bp, sizeof(argp->pagelsn));
+ bp += sizeof(argp->pagelsn);
+ memcpy(&argp->metalsn, bp, sizeof(argp->metalsn));
+ bp += sizeof(argp->metalsn);
+ *argpp = argp;
+ return (0);
+}
+
+/*
+ * PUBLIC: int __ham_ovfl_log
+ * PUBLIC: __P((DB_LOG *, DB_TXN *, DB_LSN *, u_int32_t,
+ * PUBLIC: u_int32_t, db_pgno_t, u_int32_t, db_pgno_t,
+ * PUBLIC: u_int32_t, DB_LSN *));
+ */
+int __ham_ovfl_log(logp, txnid, ret_lsnp, flags,
+ fileid, start_pgno, npages, free_pgno, ovflpoint, metalsn)
+ DB_LOG *logp;
+ DB_TXN *txnid;
+ DB_LSN *ret_lsnp;
+ u_int32_t flags;
+ u_int32_t fileid;
+ db_pgno_t start_pgno;
+ u_int32_t npages;
+ db_pgno_t free_pgno;
+ u_int32_t ovflpoint;
+ DB_LSN * metalsn;
+{
+ DBT logrec;
+ DB_LSN *lsnp, null_lsn;
+ u_int32_t rectype, txn_num;
+ int ret;
+ u_int8_t *bp;
+
+ rectype = DB_ham_ovfl;
+ txn_num = txnid == NULL ? 0 : txnid->txnid;
+ if (txnid == NULL) {
+ ZERO_LSN(null_lsn);
+ lsnp = &null_lsn;
+ } else
+ lsnp = &txnid->last_lsn;
+ logrec.size = sizeof(rectype) + sizeof(txn_num) + sizeof(DB_LSN)
+ + sizeof(fileid)
+ + sizeof(start_pgno)
+ + sizeof(npages)
+ + sizeof(free_pgno)
+ + sizeof(ovflpoint)
+ + sizeof(*metalsn);
+ if ((ret = __os_malloc(logrec.size, NULL, &logrec.data)) != 0)
+ return (ret);
+
+ bp = logrec.data;
+ memcpy(bp, &rectype, sizeof(rectype));
+ bp += sizeof(rectype);
+ memcpy(bp, &txn_num, sizeof(txn_num));
+ bp += sizeof(txn_num);
+ memcpy(bp, lsnp, sizeof(DB_LSN));
+ bp += sizeof(DB_LSN);
+ memcpy(bp, &fileid, sizeof(fileid));
+ bp += sizeof(fileid);
+ memcpy(bp, &start_pgno, sizeof(start_pgno));
+ bp += sizeof(start_pgno);
+ memcpy(bp, &npages, sizeof(npages));
+ bp += sizeof(npages);
+ memcpy(bp, &free_pgno, sizeof(free_pgno));
+ bp += sizeof(free_pgno);
+ memcpy(bp, &ovflpoint, sizeof(ovflpoint));
+ bp += sizeof(ovflpoint);
+ if (metalsn != NULL)
+ memcpy(bp, metalsn, sizeof(*metalsn));
+ else
+ memset(bp, 0, sizeof(*metalsn));
+ bp += sizeof(*metalsn);
+#ifdef DIAGNOSTIC
+ if ((u_int32_t)(bp - (u_int8_t *)logrec.data) != logrec.size)
+ fprintf(stderr, "Error in log record length");
+#endif
+ ret = log_put(logp, ret_lsnp, (DBT *)&logrec, flags);
+ if (txnid != NULL)
+ txnid->last_lsn = *ret_lsnp;
+ __os_free(logrec.data, 0);
+ return (ret);
+}
+
+/*
+ * PUBLIC: int __ham_ovfl_print
+ * PUBLIC: __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+ */
+int
+__ham_ovfl_print(notused1, dbtp, lsnp, notused2, notused3)
+ DB_LOG *notused1;
+ DBT *dbtp;
+ DB_LSN *lsnp;
+ int notused2;
+ void *notused3;
+{
+ __ham_ovfl_args *argp;
+ u_int32_t i;
+ u_int ch;
+ int ret;
+
+ i = 0;
+ ch = 0;
+ notused1 = NULL;
+ notused2 = 0;
+ notused3 = NULL;
+
+ if ((ret = __ham_ovfl_read(dbtp->data, &argp)) != 0)
+ return (ret);
+ printf("[%lu][%lu]ham_ovfl: rec: %lu txnid %lx prevlsn [%lu][%lu]\n",
+ (u_long)lsnp->file,
+ (u_long)lsnp->offset,
+ (u_long)argp->type,
+ (u_long)argp->txnid->txnid,
+ (u_long)argp->prev_lsn.file,
+ (u_long)argp->prev_lsn.offset);
+ printf("\tfileid: %lu\n", (u_long)argp->fileid);
+ printf("\tstart_pgno: %lu\n", (u_long)argp->start_pgno);
+ printf("\tnpages: %lu\n", (u_long)argp->npages);
+ printf("\tfree_pgno: %lu\n", (u_long)argp->free_pgno);
+ printf("\tovflpoint: %lu\n", (u_long)argp->ovflpoint);
+ printf("\tmetalsn: [%lu][%lu]\n",
+ (u_long)argp->metalsn.file, (u_long)argp->metalsn.offset);
+ printf("\n");
+ __os_free(argp, 0);
+ return (0);
+}
+
+/*
+ * PUBLIC: int __ham_ovfl_read __P((void *, __ham_ovfl_args **));
+ */
+int
+__ham_ovfl_read(recbuf, argpp)
+ void *recbuf;
+ __ham_ovfl_args **argpp;
+{
+ __ham_ovfl_args *argp;
+ u_int8_t *bp;
+ int ret;
+
+ ret = __os_malloc(sizeof(__ham_ovfl_args) +
+ sizeof(DB_TXN), NULL, &argp);
+ if (ret != 0)
+ return (ret);
+ argp->txnid = (DB_TXN *)&argp[1];
+ bp = recbuf;
+ memcpy(&argp->type, bp, sizeof(argp->type));
+ bp += sizeof(argp->type);
+ memcpy(&argp->txnid->txnid, bp, sizeof(argp->txnid->txnid));
+ bp += sizeof(argp->txnid->txnid);
+ memcpy(&argp->prev_lsn, bp, sizeof(DB_LSN));
+ bp += sizeof(DB_LSN);
+ memcpy(&argp->fileid, bp, sizeof(argp->fileid));
+ bp += sizeof(argp->fileid);
+ memcpy(&argp->start_pgno, bp, sizeof(argp->start_pgno));
+ bp += sizeof(argp->start_pgno);
+ memcpy(&argp->npages, bp, sizeof(argp->npages));
+ bp += sizeof(argp->npages);
+ memcpy(&argp->free_pgno, bp, sizeof(argp->free_pgno));
+ bp += sizeof(argp->free_pgno);
+ memcpy(&argp->ovflpoint, bp, sizeof(argp->ovflpoint));
+ bp += sizeof(argp->ovflpoint);
+ memcpy(&argp->metalsn, bp, sizeof(argp->metalsn));
+ bp += sizeof(argp->metalsn);
+ *argpp = argp;
+ return (0);
+}
+
+/*
+ * PUBLIC: int __ham_copypage_log
+ * PUBLIC: __P((DB_LOG *, DB_TXN *, DB_LSN *, u_int32_t,
+ * PUBLIC: u_int32_t, db_pgno_t, DB_LSN *, db_pgno_t,
+ * PUBLIC: DB_LSN *, db_pgno_t, DB_LSN *, const DBT *));
+ */
+int __ham_copypage_log(logp, txnid, ret_lsnp, flags,
+ fileid, pgno, pagelsn, next_pgno, nextlsn, nnext_pgno,
+ nnextlsn, page)
+ DB_LOG *logp;
+ DB_TXN *txnid;
+ DB_LSN *ret_lsnp;
+ u_int32_t flags;
+ u_int32_t fileid;
+ db_pgno_t pgno;
+ DB_LSN * pagelsn;
+ db_pgno_t next_pgno;
+ DB_LSN * nextlsn;
+ db_pgno_t nnext_pgno;
+ DB_LSN * nnextlsn;
+ const DBT *page;
+{
+ DBT logrec;
+ DB_LSN *lsnp, null_lsn;
+ u_int32_t zero;
+ u_int32_t rectype, txn_num;
+ int ret;
+ u_int8_t *bp;
+
+ rectype = DB_ham_copypage;
+ txn_num = txnid == NULL ? 0 : txnid->txnid;
+ if (txnid == NULL) {
+ ZERO_LSN(null_lsn);
+ lsnp = &null_lsn;
+ } else
+ lsnp = &txnid->last_lsn;
+ logrec.size = sizeof(rectype) + sizeof(txn_num) + sizeof(DB_LSN)
+ + sizeof(fileid)
+ + sizeof(pgno)
+ + sizeof(*pagelsn)
+ + sizeof(next_pgno)
+ + sizeof(*nextlsn)
+ + sizeof(nnext_pgno)
+ + sizeof(*nnextlsn)
+ + sizeof(u_int32_t) + (page == NULL ? 0 : page->size);
+ if ((ret = __os_malloc(logrec.size, NULL, &logrec.data)) != 0)
+ return (ret);
+
+ bp = logrec.data;
+ memcpy(bp, &rectype, sizeof(rectype));
+ bp += sizeof(rectype);
+ memcpy(bp, &txn_num, sizeof(txn_num));
+ bp += sizeof(txn_num);
+ memcpy(bp, lsnp, sizeof(DB_LSN));
+ bp += sizeof(DB_LSN);
+ memcpy(bp, &fileid, sizeof(fileid));
+ bp += sizeof(fileid);
+ memcpy(bp, &pgno, sizeof(pgno));
+ bp += sizeof(pgno);
+ if (pagelsn != NULL)
+ memcpy(bp, pagelsn, sizeof(*pagelsn));
+ else
+ memset(bp, 0, sizeof(*pagelsn));
+ bp += sizeof(*pagelsn);
+ memcpy(bp, &next_pgno, sizeof(next_pgno));
+ bp += sizeof(next_pgno);
+ if (nextlsn != NULL)
+ memcpy(bp, nextlsn, sizeof(*nextlsn));
+ else
+ memset(bp, 0, sizeof(*nextlsn));
+ bp += sizeof(*nextlsn);
+ memcpy(bp, &nnext_pgno, sizeof(nnext_pgno));
+ bp += sizeof(nnext_pgno);
+ if (nnextlsn != NULL)
+ memcpy(bp, nnextlsn, sizeof(*nnextlsn));
+ else
+ memset(bp, 0, sizeof(*nnextlsn));
+ bp += sizeof(*nnextlsn);
+ if (page == NULL) {
+ zero = 0;
+ memcpy(bp, &zero, sizeof(u_int32_t));
+ bp += sizeof(u_int32_t);
+ } else {
+ memcpy(bp, &page->size, sizeof(page->size));
+ bp += sizeof(page->size);
+ memcpy(bp, page->data, page->size);
+ bp += page->size;
+ }
+#ifdef DIAGNOSTIC
+ if ((u_int32_t)(bp - (u_int8_t *)logrec.data) != logrec.size)
+ fprintf(stderr, "Error in log record length");
+#endif
+ ret = log_put(logp, ret_lsnp, (DBT *)&logrec, flags);
+ if (txnid != NULL)
+ txnid->last_lsn = *ret_lsnp;
+ __os_free(logrec.data, 0);
+ return (ret);
+}
+
+/*
+ * PUBLIC: int __ham_copypage_print
+ * PUBLIC: __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+ */
+int
+__ham_copypage_print(notused1, dbtp, lsnp, notused2, notused3)
+ DB_LOG *notused1;
+ DBT *dbtp;
+ DB_LSN *lsnp;
+ int notused2;
+ void *notused3;
+{
+ __ham_copypage_args *argp;
+ u_int32_t i;
+ u_int ch;
+ int ret;
+
+ i = 0;
+ ch = 0;
+ notused1 = NULL;
+ notused2 = 0;
+ notused3 = NULL;
+
+ if ((ret = __ham_copypage_read(dbtp->data, &argp)) != 0)
+ return (ret);
+ printf("[%lu][%lu]ham_copypage: rec: %lu txnid %lx prevlsn [%lu][%lu]\n",
+ (u_long)lsnp->file,
+ (u_long)lsnp->offset,
+ (u_long)argp->type,
+ (u_long)argp->txnid->txnid,
+ (u_long)argp->prev_lsn.file,
+ (u_long)argp->prev_lsn.offset);
+ printf("\tfileid: %lu\n", (u_long)argp->fileid);
+ printf("\tpgno: %lu\n", (u_long)argp->pgno);
+ printf("\tpagelsn: [%lu][%lu]\n",
+ (u_long)argp->pagelsn.file, (u_long)argp->pagelsn.offset);
+ printf("\tnext_pgno: %lu\n", (u_long)argp->next_pgno);
+ printf("\tnextlsn: [%lu][%lu]\n",
+ (u_long)argp->nextlsn.file, (u_long)argp->nextlsn.offset);
+ printf("\tnnext_pgno: %lu\n", (u_long)argp->nnext_pgno);
+ printf("\tnnextlsn: [%lu][%lu]\n",
+ (u_long)argp->nnextlsn.file, (u_long)argp->nnextlsn.offset);
+ printf("\tpage: ");
+ for (i = 0; i < argp->page.size; i++) {
+ ch = ((u_int8_t *)argp->page.data)[i];
+ if (isprint(ch) || ch == 0xa)
+ putchar(ch);
+ else
+ printf("%#x ", ch);
+ }
+ printf("\n");
+ printf("\n");
+ __os_free(argp, 0);
+ return (0);
+}
+
+/*
+ * PUBLIC: int __ham_copypage_read __P((void *, __ham_copypage_args **));
+ */
+int
+__ham_copypage_read(recbuf, argpp)
+ void *recbuf;
+ __ham_copypage_args **argpp;
+{
+ __ham_copypage_args *argp;
+ u_int8_t *bp;
+ int ret;
+
+ ret = __os_malloc(sizeof(__ham_copypage_args) +
+ sizeof(DB_TXN), NULL, &argp);
+ if (ret != 0)
+ return (ret);
+ argp->txnid = (DB_TXN *)&argp[1];
+ bp = recbuf;
+ memcpy(&argp->type, bp, sizeof(argp->type));
+ bp += sizeof(argp->type);
+ memcpy(&argp->txnid->txnid, bp, sizeof(argp->txnid->txnid));
+ bp += sizeof(argp->txnid->txnid);
+ memcpy(&argp->prev_lsn, bp, sizeof(DB_LSN));
+ bp += sizeof(DB_LSN);
+ memcpy(&argp->fileid, bp, sizeof(argp->fileid));
+ bp += sizeof(argp->fileid);
+ memcpy(&argp->pgno, bp, sizeof(argp->pgno));
+ bp += sizeof(argp->pgno);
+ memcpy(&argp->pagelsn, bp, sizeof(argp->pagelsn));
+ bp += sizeof(argp->pagelsn);
+ memcpy(&argp->next_pgno, bp, sizeof(argp->next_pgno));
+ bp += sizeof(argp->next_pgno);
+ memcpy(&argp->nextlsn, bp, sizeof(argp->nextlsn));
+ bp += sizeof(argp->nextlsn);
+ memcpy(&argp->nnext_pgno, bp, sizeof(argp->nnext_pgno));
+ bp += sizeof(argp->nnext_pgno);
+ memcpy(&argp->nnextlsn, bp, sizeof(argp->nnextlsn));
+ bp += sizeof(argp->nnextlsn);
+ memcpy(&argp->page.size, bp, sizeof(u_int32_t));
+ bp += sizeof(u_int32_t);
+ argp->page.data = bp;
+ bp += argp->page.size;
+ *argpp = argp;
+ return (0);
+}
+
+/*
+ * PUBLIC: int __ham_init_print __P((DB_ENV *));
+ */
+int
+__ham_init_print(dbenv)
+ DB_ENV *dbenv;
+{
+ int ret;
+
+ if ((ret = __db_add_recovery(dbenv,
+ __ham_insdel_print, DB_ham_insdel)) != 0)
+ return (ret);
+ if ((ret = __db_add_recovery(dbenv,
+ __ham_newpage_print, DB_ham_newpage)) != 0)
+ return (ret);
+ if ((ret = __db_add_recovery(dbenv,
+ __ham_splitmeta_print, DB_ham_splitmeta)) != 0)
+ return (ret);
+ if ((ret = __db_add_recovery(dbenv,
+ __ham_splitdata_print, DB_ham_splitdata)) != 0)
+ return (ret);
+ if ((ret = __db_add_recovery(dbenv,
+ __ham_replace_print, DB_ham_replace)) != 0)
+ return (ret);
+ if ((ret = __db_add_recovery(dbenv,
+ __ham_newpgno_print, DB_ham_newpgno)) != 0)
+ return (ret);
+ if ((ret = __db_add_recovery(dbenv,
+ __ham_ovfl_print, DB_ham_ovfl)) != 0)
+ return (ret);
+ if ((ret = __db_add_recovery(dbenv,
+ __ham_copypage_print, DB_ham_copypage)) != 0)
+ return (ret);
+ return (0);
+}
+
+/*
+ * PUBLIC: int __ham_init_recover __P((DB_ENV *));
+ */
+int
+__ham_init_recover(dbenv)
+ DB_ENV *dbenv;
+{
+ int ret;
+
+ if ((ret = __db_add_recovery(dbenv,
+ __ham_insdel_recover, DB_ham_insdel)) != 0)
+ return (ret);
+ if ((ret = __db_add_recovery(dbenv,
+ __ham_newpage_recover, DB_ham_newpage)) != 0)
+ return (ret);
+ if ((ret = __db_add_recovery(dbenv,
+ __ham_splitmeta_recover, DB_ham_splitmeta)) != 0)
+ return (ret);
+ if ((ret = __db_add_recovery(dbenv,
+ __ham_splitdata_recover, DB_ham_splitdata)) != 0)
+ return (ret);
+ if ((ret = __db_add_recovery(dbenv,
+ __ham_replace_recover, DB_ham_replace)) != 0)
+ return (ret);
+ if ((ret = __db_add_recovery(dbenv,
+ __ham_newpgno_recover, DB_ham_newpgno)) != 0)
+ return (ret);
+ if ((ret = __db_add_recovery(dbenv,
+ __ham_ovfl_recover, DB_ham_ovfl)) != 0)
+ return (ret);
+ if ((ret = __db_add_recovery(dbenv,
+ __ham_copypage_recover, DB_ham_copypage)) != 0)
+ return (ret);
+ return (0);
+}
+
diff --git a/usr/src/cmd/sendmail/db/hash/hash_conv.c b/usr/src/cmd/sendmail/db/hash/hash_conv.c
new file mode 100644
index 0000000000..a31d308021
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/hash/hash_conv.c
@@ -0,0 +1,117 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997
+ * Sleepycat Software. All rights reserved.
+ */
+/*
+ * Copyright (c) 1998 by Sun Microsystems, Inc.
+ * All rights reserved.
+ */
+
+#include "config.h"
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#ifndef lint
+static const char sccsid[] = "@(#)hash_conv.c 10.4 (Sleepycat) 9/15/97";
+static const char sccsi2[] = "%W% (Sun) %G%";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+#endif
+
+#include "db_int.h"
+#include "db_page.h"
+#include "db_swap.h"
+#include "hash.h"
+
+/*
+ * __ham_pgin --
+ * Convert host-specific page layout from the host-independent format
+ * stored on disk.
+ *
+ * PUBLIC: int __ham_pgin __P((db_pgno_t, void *, DBT *));
+ */
+int
+__ham_pgin(pg, pp, cookie)
+ db_pgno_t pg;
+ void *pp;
+ DBT *cookie;
+{
+ DB_PGINFO *pginfo;
+ u_int32_t tpgno;
+
+ pginfo = (DB_PGINFO *)cookie->data;
+ tpgno = PGNO((PAGE *)pp);
+ if (pginfo->needswap)
+ M_32_SWAP(tpgno);
+
+ if (pg != PGNO_METADATA && pg != tpgno) {
+ P_INIT(pp, pginfo->db_pagesize,
+ pg, PGNO_INVALID, PGNO_INVALID, 0, P_HASH);
+ return (0);
+ }
+
+ if (!pginfo->needswap)
+ return (0);
+ return (pg == PGNO_METADATA ?
+ __ham_mswap(pp) : __db_pgin(pg, pginfo->db_pagesize, pp));
+}
+
+/*
+ * __ham_pgout --
+ * Convert host-specific page layout to the host-independent format
+ * stored on disk.
+ *
+ * PUBLIC: int __ham_pgout __P((db_pgno_t, void *, DBT *));
+ */
+int
+__ham_pgout(pg, pp, cookie)
+ db_pgno_t pg;
+ void *pp;
+ DBT *cookie;
+{
+ DB_PGINFO *pginfo;
+
+ pginfo = (DB_PGINFO *)cookie->data;
+ if (!pginfo->needswap)
+ return (0);
+ return (pg == PGNO_METADATA ?
+ __ham_mswap(pp) : __db_pgout(pg, pginfo->db_pagesize, pp));
+}
+
+/*
+ * __ham_mswap --
+ * Swap the bytes on the hash metadata page.
+ *
+ * PUBLIC: int __ham_mswap __P((void *));
+ */
+int
+__ham_mswap(pg)
+ void *pg;
+{
+ u_int8_t *p;
+ int i;
+
+ p = (u_int8_t *)pg;
+ SWAP32(p); /* lsn part 1 */
+ SWAP32(p); /* lsn part 2 */
+ SWAP32(p); /* pgno */
+ SWAP32(p); /* magic */
+ SWAP32(p); /* version */
+ SWAP32(p); /* pagesize */
+ SWAP32(p); /* ovfl_point */
+ SWAP32(p); /* last_freed */
+ SWAP32(p); /* max_bucket */
+ SWAP32(p); /* high_mask */
+ SWAP32(p); /* low_mask */
+ SWAP32(p); /* ffactor */
+ SWAP32(p); /* nelem */
+ SWAP32(p); /* h_charkey */
+ SWAP32(p); /* flags */
+ for (i = 0; i < NCACHED; ++i)
+ SWAP32(p); /* spares */
+ return (0);
+}
diff --git a/usr/src/cmd/sendmail/db/hash/hash_dup.c b/usr/src/cmd/sendmail/db/hash/hash_dup.c
new file mode 100644
index 0000000000..bb3466428d
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/hash/hash_dup.c
@@ -0,0 +1,656 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+/*
+ * Copyright (c) 1990, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Margo Seltzer.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)hash_dup.c 10.27 (Sleepycat) 12/6/98";
+#endif /* not lint */
+
+/*
+ * PACKAGE: hashing
+ *
+ * DESCRIPTION:
+ * Manipulation of duplicates for the hash package.
+ *
+ * ROUTINES:
+ *
+ * External
+ * __add_dup
+ * Internal
+ */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <errno.h>
+#include <string.h>
+#endif
+
+#include "db_int.h"
+#include "db_page.h"
+#include "hash.h"
+#include "btree.h"
+
+static int __ham_check_move __P((DBC *, int32_t));
+static int __ham_dup_convert __P((DBC *));
+static int __ham_make_dup __P((const DBT *, DBT *d, void **, u_int32_t *));
+
+/*
+ * Called from hash_access to add a duplicate key. nval is the new
+ * value that we want to add. The flags correspond to the flag values
+ * to cursor_put indicating where to add the new element.
+ * There are 4 cases.
+ * Case 1: The existing duplicate set already resides on a separate page.
+ * We can use common code for this.
+ * Case 2: The element is small enough to just be added to the existing set.
+ * Case 3: The element is large enough to be a big item, so we're going to
+ * have to push the set onto a new page.
+ * Case 4: The element is large enough to push the duplicate set onto a
+ * separate page.
+ *
+ * PUBLIC: int __ham_add_dup __P((DBC *, DBT *, u_int32_t));
+ */
+int
+__ham_add_dup(dbc, nval, flags)
+ DBC *dbc;
+ DBT *nval;
+ u_int32_t flags;
+{
+ DB *dbp;
+ HASH_CURSOR *hcp;
+ DBT dbt, pval, tmp_val;
+ u_int32_t del_len, new_size;
+ int cmp, ret;
+ u_int8_t *hk;
+
+ dbp = dbc->dbp;
+ hcp = (HASH_CURSOR *)dbc->internal;
+ if (flags == DB_CURRENT && hcp->dpgno == PGNO_INVALID)
+ del_len = hcp->dup_len;
+ else
+ del_len = 0;
+
+ if ((ret = __ham_check_move(dbc,
+ (int32_t)DUP_SIZE(nval->size) - (int32_t)del_len)) != 0)
+ return (ret);
+
+ /*
+ * Check if resulting duplicate set is going to need to go
+ * onto a separate duplicate page. If so, convert the
+ * duplicate set and add the new one. After conversion,
+ * hcp->dndx is the first free ndx or the index of the
+ * current pointer into the duplicate set.
+ */
+ hk = H_PAIRDATA(hcp->pagep, hcp->bndx);
+ new_size = DUP_SIZE(nval->size) - del_len + LEN_HKEYDATA(hcp->pagep,
+ hcp->hdr->pagesize, H_DATAINDEX(hcp->bndx));
+
+ /*
+ * We convert to off-page duplicates if the item is a big item,
+ * the addition of the new item will make the set large, or
+ * if there isn't enough room on this page to add the next item.
+ */
+ if (HPAGE_PTYPE(hk) != H_OFFDUP &&
+ (HPAGE_PTYPE(hk) == H_OFFPAGE || ISBIG(hcp, new_size) ||
+ DUP_SIZE(nval->size) - del_len > P_FREESPACE(hcp->pagep))) {
+
+ if ((ret = __ham_dup_convert(dbc)) != 0)
+ return (ret);
+ else
+ hk = H_PAIRDATA(hcp->pagep, hcp->bndx);
+ }
+
+ /* There are two separate cases here: on page and off page. */
+ if (HPAGE_PTYPE(hk) != H_OFFDUP) {
+ if (HPAGE_PTYPE(hk) != H_DUPLICATE) {
+ HPAGE_PTYPE(hk) = H_DUPLICATE;
+ pval.flags = 0;
+ pval.data = HKEYDATA_DATA(hk);
+ pval.size = LEN_HDATA(hcp->pagep, dbp->pgsize,
+ hcp->bndx);
+ if ((ret =
+ __ham_make_dup(&pval, &tmp_val, &dbc->rdata.data,
+ &dbc->rdata.size)) != 0 || (ret =
+ __ham_replpair(dbc, &tmp_val, 1)) != 0)
+ return (ret);
+ }
+
+ /* Now make the new entry a duplicate. */
+ if ((ret = __ham_make_dup(nval,
+ &tmp_val, &dbc->rdata.data, &dbc->rdata.size)) != 0)
+ return (ret);
+
+ tmp_val.dlen = 0;
+ switch (flags) { /* On page. */
+ case DB_KEYFIRST:
+ case DB_KEYLAST:
+ if (dbp->dup_compare != NULL)
+ __ham_dsearch(dbc, nval, &tmp_val.doff, &cmp);
+ else if (flags == DB_KEYFIRST)
+ tmp_val.doff = 0;
+ else
+ tmp_val.doff = LEN_HDATA(hcp->pagep,
+ hcp->hdr->pagesize, hcp->bndx);
+ break;
+ case DB_CURRENT:
+ /*
+ * If we have a sort function, we need to verify that
+ * the new item sorts identically to the old item.
+ */
+ if (dbp->dup_compare != NULL) {
+ dbt.data = HKEYDATA_DATA(H_PAIRDATA(hcp->pagep,
+ hcp->bndx)) + hcp->dup_off;
+ dbt.size = DUP_SIZE(hcp->dup_len);
+ if (dbp->dup_compare(nval, &dbt) != 0)
+ return (EINVAL);
+ }
+ tmp_val.doff = hcp->dup_off;
+ tmp_val.dlen = DUP_SIZE(hcp->dup_len);
+ break;
+ case DB_BEFORE:
+ tmp_val.doff = hcp->dup_off;
+ break;
+ case DB_AFTER:
+ tmp_val.doff = hcp->dup_off + DUP_SIZE(hcp->dup_len);
+ break;
+ }
+ /* Add the duplicate. */
+ ret = __ham_replpair(dbc, &tmp_val, 0);
+ if (ret == 0)
+ ret = __ham_dirty_page(dbp, hcp->pagep);
+ __ham_c_update(hcp, hcp->pgno, tmp_val.size, 1, 1);
+ return (ret);
+ }
+
+ /* If we get here, then we're on duplicate pages. */
+ if (hcp->dpgno == PGNO_INVALID) {
+ memcpy(&hcp->dpgno, HOFFDUP_PGNO(hk), sizeof(db_pgno_t));
+ hcp->dndx = 0;
+ }
+
+ switch (flags) {
+ case DB_KEYFIRST:
+ if (dbp->dup_compare != NULL)
+ goto sorted_dups;
+ /*
+ * The only way that we are already on a dup page is
+ * if we just converted the on-page representation.
+ * In that case, we've only got one page of duplicates.
+ */
+ if (hcp->dpagep == NULL && (ret =
+ __db_dend(dbc, hcp->dpgno, &hcp->dpagep)) != 0)
+ return (ret);
+ hcp->dndx = 0;
+ break;
+ case DB_KEYLAST:
+ if (dbp->dup_compare != NULL) {
+sorted_dups: if ((ret = __db_dsearch(dbc, 1, nval,
+ hcp->dpgno, &hcp->dndx, &hcp->dpagep, &cmp)) != 0)
+ return (ret);
+ if (cmp == 0)
+ hcp->dpgno = PGNO(hcp->dpagep);
+ } else {
+ if (hcp->dpagep == NULL && (ret =
+ __db_dend(dbc, hcp->dpgno, &hcp->dpagep)) != 0)
+ return (ret);
+ hcp->dpgno = PGNO(hcp->dpagep);
+ hcp->dndx = NUM_ENT(hcp->dpagep);
+ }
+ break;
+ case DB_CURRENT:
+ if (dbp->dup_compare != NULL && __bam_cmp(dbp,
+ nval, hcp->dpagep, hcp->dndx, dbp->dup_compare) != 0)
+ return (EINVAL);
+ switch (GET_BKEYDATA(hcp->dpagep, hcp->dndx)->type) {
+ case B_KEYDATA:
+ del_len = BKEYDATA_SIZE(GET_BKEYDATA(hcp->dpagep,
+ hcp->dndx)->len);
+ break;
+ case B_OVERFLOW:
+ del_len = BOVERFLOW_SIZE;
+ break;
+ }
+ if ((ret =
+ __db_ditem(dbc, hcp->dpagep, hcp->dndx, del_len)) != 0)
+ return (ret);
+ break;
+ case DB_BEFORE: /* The default behavior is correct. */
+ break;
+ case DB_AFTER:
+ hcp->dndx++;
+ break;
+ }
+
+ ret = __db_dput(dbc,
+ nval, &hcp->dpagep, &hcp->dndx, __ham_overflow_page);
+ hcp->pgno = PGNO(hcp->pagep);
+ __ham_c_update(hcp, hcp->pgno, nval->size, 1, 1);
+ return (ret);
+}
+
+/*
+ * Convert an on-page set of duplicates to an offpage set of duplicates.
+ */
+static int
+__ham_dup_convert(dbc)
+ DBC *dbc;
+{
+ DB *dbp;
+ HASH_CURSOR *hcp;
+ BOVERFLOW bo;
+ DBT dbt;
+ HOFFPAGE ho;
+ db_indx_t dndx, i, len, off;
+ int ret;
+ u_int8_t *p, *pend;
+
+ /*
+ * Create a new page for the duplicates.
+ */
+ dbp = dbc->dbp;
+ hcp = (HASH_CURSOR *)dbc->internal;
+ if ((ret =
+ __ham_overflow_page(dbc, P_DUPLICATE, &hcp->dpagep)) != 0)
+ return (ret);
+ hcp->dpagep->type = P_DUPLICATE;
+ hcp->dpgno = PGNO(hcp->dpagep);
+
+ /*
+ * Now put the duplicates onto the new page.
+ */
+ dndx = 0;
+ dbt.flags = 0;
+ switch (HPAGE_PTYPE(H_PAIRDATA(hcp->pagep, hcp->bndx))) {
+ case H_KEYDATA:
+ /* Simple case, one key on page; move it to dup page. */
+ dbt.size =
+ LEN_HDATA(hcp->pagep, hcp->hdr->pagesize, hcp->bndx);
+ dbt.data = HKEYDATA_DATA(H_PAIRDATA(hcp->pagep, hcp->bndx));
+ ret = __db_pitem(dbc, hcp->dpagep,
+ (u_int32_t)dndx, BKEYDATA_SIZE(dbt.size), NULL, &dbt);
+ if (ret == 0)
+ __ham_dirty_page(dbp, hcp->dpagep);
+ break;
+ case H_OFFPAGE:
+ /* Simple case, one key on page; move it to dup page. */
+ memcpy(&ho,
+ P_ENTRY(hcp->pagep, H_DATAINDEX(hcp->bndx)), HOFFPAGE_SIZE);
+ UMRW(bo.unused1);
+ B_TSET(bo.type, ho.type, 0);
+ UMRW(bo.unused2);
+ bo.pgno = ho.pgno;
+ bo.tlen = ho.tlen;
+ dbt.size = BOVERFLOW_SIZE;
+ dbt.data = &bo;
+
+ ret = __db_pitem(dbc, hcp->dpagep,
+ (u_int32_t)dndx, dbt.size, &dbt, NULL);
+ if (ret == 0)
+ __ham_dirty_page(dbp, hcp->dpagep);
+ break;
+ case H_DUPLICATE:
+ p = HKEYDATA_DATA(H_PAIRDATA(hcp->pagep, hcp->bndx));
+ pend = p +
+ LEN_HDATA(hcp->pagep, hcp->hdr->pagesize, hcp->bndx);
+
+ /*
+ * We need to maintain the duplicate cursor position.
+ * Keep track of where we are in the duplicate set via
+ * the offset, and when it matches the one in the cursor,
+ * set the off-page duplicate cursor index to the current
+ * index.
+ */
+ for (off = 0, i = 0; p < pend; i++) {
+ if (off == hcp->dup_off)
+ dndx = i;
+ memcpy(&len, p, sizeof(db_indx_t));
+ dbt.size = len;
+ p += sizeof(db_indx_t);
+ dbt.data = p;
+ p += len + sizeof(db_indx_t);
+ off += len + 2 * sizeof(db_indx_t);
+ ret = __db_dput(dbc, &dbt,
+ &hcp->dpagep, &i, __ham_overflow_page);
+ if (ret != 0)
+ break;
+ }
+ break;
+ default:
+ ret = __db_pgfmt(dbp, (u_long)hcp->pgno);
+ break;
+ }
+ if (ret == 0) {
+ /*
+ * Now attach this to the source page in place of
+ * the old duplicate item.
+ */
+ __ham_move_offpage(dbc, hcp->pagep,
+ (u_int32_t)H_DATAINDEX(hcp->bndx), hcp->dpgno);
+
+ /* Can probably just do a "put" here. */
+ ret = __ham_dirty_page(dbp, hcp->pagep);
+ hcp->dndx = dndx;
+ } else {
+ (void)__ham_del_page(dbc, hcp->dpagep);
+ hcp->dpagep = NULL;
+ }
+ return (ret);
+}
+
+static int
+__ham_make_dup(notdup, duplicate, bufp, sizep)
+ const DBT *notdup;
+ DBT *duplicate;
+ void **bufp;
+ u_int32_t *sizep;
+{
+ db_indx_t tsize, item_size;
+ int ret;
+ u_int8_t *p;
+
+ item_size = (db_indx_t)notdup->size;
+ tsize = DUP_SIZE(item_size);
+ if ((ret = __ham_init_dbt(duplicate, tsize, bufp, sizep)) != 0)
+ return (ret);
+
+ duplicate->dlen = 0;
+ duplicate->flags = notdup->flags;
+ F_SET(duplicate, DB_DBT_PARTIAL);
+
+ p = duplicate->data;
+ memcpy(p, &item_size, sizeof(db_indx_t));
+ p += sizeof(db_indx_t);
+ memcpy(p, notdup->data, notdup->size);
+ p += notdup->size;
+ memcpy(p, &item_size, sizeof(db_indx_t));
+
+ duplicate->doff = 0;
+ duplicate->dlen = notdup->size;
+
+ return (0);
+}
+
+static int
+__ham_check_move(dbc, add_len)
+ DBC *dbc;
+ int32_t add_len;
+{
+ DB *dbp;
+ HASH_CURSOR *hcp;
+ DBT k, d;
+ DB_LSN new_lsn;
+ PAGE *next_pagep;
+ db_pgno_t next_pgno;
+ u_int32_t new_datalen, old_len, rectype;
+ u_int8_t *hk;
+ int ret;
+
+ dbp = dbc->dbp;
+ hcp = (HASH_CURSOR *)dbc->internal;
+ /*
+ * Check if we can do whatever we need to on this page. If not,
+ * then we'll have to move the current element to a new page.
+ */
+ hk = H_PAIRDATA(hcp->pagep, hcp->bndx);
+
+ /*
+ * If the item is already off page duplicates or an offpage item,
+ * then we know we can do whatever we need to do in-place
+ */
+ if (HPAGE_PTYPE(hk) == H_OFFDUP || HPAGE_PTYPE(hk) == H_OFFPAGE)
+ return (0);
+
+ old_len =
+ LEN_HITEM(hcp->pagep, hcp->hdr->pagesize, H_DATAINDEX(hcp->bndx));
+ new_datalen = old_len - HKEYDATA_SIZE(0) + add_len;
+
+ /*
+ * We need to add a new page under two conditions:
+ * 1. The addition makes the total data length cross the BIG
+ * threshold and the OFFDUP structure won't fit on this page.
+ * 2. The addition does not make the total data cross the
+ * threshold, but the new data won't fit on the page.
+ * If neither of these is true, then we can return.
+ */
+ if (ISBIG(hcp, new_datalen) && (old_len > HOFFDUP_SIZE ||
+ HOFFDUP_SIZE - old_len <= P_FREESPACE(hcp->pagep)))
+ return (0);
+
+ if (!ISBIG(hcp, new_datalen) &&
+ add_len <= (int32_t)P_FREESPACE(hcp->pagep))
+ return (0);
+
+ /*
+ * If we get here, then we need to move the item to a new page.
+ * Check if there are more pages in the chain.
+ */
+
+ new_datalen = ISBIG(hcp, new_datalen) ?
+ HOFFDUP_SIZE : HKEYDATA_SIZE(new_datalen);
+
+ next_pagep = NULL;
+ for (next_pgno = NEXT_PGNO(hcp->pagep); next_pgno != PGNO_INVALID;
+ next_pgno = NEXT_PGNO(next_pagep)) {
+ if (next_pagep != NULL &&
+ (ret = __ham_put_page(dbp, next_pagep, 0)) != 0)
+ return (ret);
+
+ if ((ret =
+ __ham_get_page(dbp, next_pgno, &next_pagep)) != 0)
+ return (ret);
+
+ if (P_FREESPACE(next_pagep) >= new_datalen)
+ break;
+ }
+
+ /* No more pages, add one. */
+ if (next_pagep == NULL && (ret = __ham_add_ovflpage(dbc,
+ hcp->pagep, 0, &next_pagep)) != 0)
+ return (ret);
+
+ /* Add new page at the end of the chain. */
+ if (P_FREESPACE(next_pagep) < new_datalen && (ret =
+ __ham_add_ovflpage(dbc, next_pagep, 1, &next_pagep)) != 0)
+ return (ret);
+
+ /* Copy the item to the new page. */
+ if (DB_LOGGING(hcp->dbc)) {
+ rectype = PUTPAIR;
+ k.flags = 0;
+ d.flags = 0;
+ if (HPAGE_PTYPE(
+ H_PAIRKEY(hcp->pagep, hcp->bndx)) == H_OFFPAGE) {
+ rectype |= PAIR_KEYMASK;
+ k.data = H_PAIRKEY(hcp->pagep, hcp->bndx);
+ k.size = HOFFPAGE_SIZE;
+ } else {
+ k.data =
+ HKEYDATA_DATA(H_PAIRKEY(hcp->pagep, hcp->bndx));
+ k.size = LEN_HKEY(hcp->pagep,
+ hcp->hdr->pagesize, hcp->bndx);
+ }
+
+ if (HPAGE_PTYPE(hk) == H_OFFPAGE) {
+ rectype |= PAIR_DATAMASK;
+ d.data = H_PAIRDATA(hcp->pagep, hcp->bndx);
+ d.size = HOFFPAGE_SIZE;
+ } else {
+ d.data =
+ HKEYDATA_DATA(H_PAIRDATA(hcp->pagep, hcp->bndx));
+ d.size = LEN_HDATA(hcp->pagep,
+ hcp->hdr->pagesize, hcp->bndx);
+ }
+
+
+ if ((ret = __ham_insdel_log(dbp->dbenv->lg_info,
+ dbc->txn, &new_lsn, 0, rectype,
+ dbp->log_fileid, PGNO(next_pagep),
+ (u_int32_t)H_NUMPAIRS(next_pagep), &LSN(next_pagep),
+ &k, &d)) != 0)
+ return (ret);
+
+ /* Move lsn onto page. */
+ LSN(next_pagep) = new_lsn; /* Structure assignment. */
+ }
+
+ __ham_copy_item(dbp->pgsize,
+ hcp->pagep, H_KEYINDEX(hcp->bndx), next_pagep);
+ __ham_copy_item(dbp->pgsize,
+ hcp->pagep, H_DATAINDEX(hcp->bndx), next_pagep);
+
+ /* Now delete the pair from the current page. */
+ ret = __ham_del_pair(dbc, 0);
+
+ (void)__ham_put_page(dbp, hcp->pagep, 1);
+ hcp->pagep = next_pagep;
+ hcp->pgno = PGNO(hcp->pagep);
+ hcp->bndx = H_NUMPAIRS(hcp->pagep) - 1;
+ F_SET(hcp, H_EXPAND);
+ return (ret);
+}
+
+/*
+ * __ham_move_offpage --
+ * Replace an onpage set of duplicates with the OFFDUP structure
+ * that references the duplicate page.
+ *
+ * XXX
+ * This is really just a special case of __onpage_replace; we should
+ * probably combine them.
+ *
+ * PUBLIC: void __ham_move_offpage __P((DBC *, PAGE *, u_int32_t, db_pgno_t));
+ */
+void
+__ham_move_offpage(dbc, pagep, ndx, pgno)
+ DBC *dbc;
+ PAGE *pagep;
+ u_int32_t ndx;
+ db_pgno_t pgno;
+{
+ DB *dbp;
+ HASH_CURSOR *hcp;
+ DBT new_dbt;
+ DBT old_dbt;
+ HOFFDUP od;
+ db_indx_t i;
+ int32_t shrink;
+ u_int8_t *src;
+
+ dbp = dbc->dbp;
+ hcp = (HASH_CURSOR *)dbc->internal;
+ od.type = H_OFFDUP;
+ UMRW(od.unused[0]);
+ UMRW(od.unused[1]);
+ UMRW(od.unused[2]);
+ od.pgno = pgno;
+
+ if (DB_LOGGING(dbc)) {
+ new_dbt.data = &od;
+ new_dbt.size = HOFFDUP_SIZE;
+ old_dbt.data = P_ENTRY(pagep, ndx);
+ old_dbt.size = LEN_HITEM(pagep, hcp->hdr->pagesize, ndx);
+ (void)__ham_replace_log(dbp->dbenv->lg_info,
+ dbc->txn, &LSN(pagep), 0, dbp->log_fileid,
+ PGNO(pagep), (u_int32_t)ndx, &LSN(pagep), -1,
+ &old_dbt, &new_dbt, 0);
+ }
+
+ shrink =
+ LEN_HITEM(pagep, hcp->hdr->pagesize, ndx) - HOFFDUP_SIZE;
+
+ if (shrink != 0) {
+ /* Copy data. */
+ src = (u_int8_t *)(pagep) + HOFFSET(pagep);
+ memmove(src + shrink, src, pagep->inp[ndx] - HOFFSET(pagep));
+ HOFFSET(pagep) += shrink;
+
+ /* Update index table. */
+ for (i = ndx; i < NUM_ENT(pagep); i++)
+ pagep->inp[i] += shrink;
+ }
+
+ /* Now copy the offdup entry onto the page. */
+ memcpy(P_ENTRY(pagep, ndx), &od, HOFFDUP_SIZE);
+}
+
+/*
+ * __ham_dsearch:
+ * Locate a particular duplicate in a duplicate set.
+ *
+ * PUBLIC: void __ham_dsearch __P((DBC *, DBT *, u_int32_t *, int *));
+ */
+void
+__ham_dsearch(dbc, dbt, offp, cmpp)
+ DBC *dbc;
+ DBT *dbt;
+ u_int32_t *offp;
+ int *cmpp;
+{
+ DB *dbp;
+ HASH_CURSOR *hcp;
+ DBT cur;
+ db_indx_t i, len;
+ int (*func) __P((const DBT *, const DBT *));
+ u_int8_t *data;
+
+ dbp = dbc->dbp;
+ hcp = (HASH_CURSOR *)dbc->internal;
+ if (dbp->dup_compare == NULL)
+ func = __bam_defcmp;
+ else
+ func = dbp->dup_compare;
+
+ i = F_ISSET(dbc, DBC_CONTINUE) ? hcp->dup_off: 0;
+ data = HKEYDATA_DATA(H_PAIRDATA(hcp->pagep, hcp->bndx)) + i;
+ while (i < LEN_HDATA(hcp->pagep, hcp->hdr->pagesize, hcp->bndx)) {
+ memcpy(&len, data, sizeof(db_indx_t));
+ data += sizeof(db_indx_t);
+ cur.data = data;
+ cur.size = (u_int32_t)len;
+ *cmpp = func(dbt, &cur);
+ if (*cmpp == 0 || (*cmpp < 0 && dbp->dup_compare != NULL))
+ break;
+ i += len + 2 * sizeof(db_indx_t);
+ data += len + sizeof(db_indx_t);
+ }
+ *offp = i;
+}
diff --git a/usr/src/cmd/sendmail/db/hash/hash_func.c b/usr/src/cmd/sendmail/db/hash/hash_func.c
new file mode 100644
index 0000000000..4809a8098e
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/hash/hash_func.c
@@ -0,0 +1,226 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997
+ * Sleepycat Software. All rights reserved.
+ */
+/*
+ * Copyright (c) 1990, 1993
+ * Margo Seltzer. All rights reserved.
+ */
+/*
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Margo Seltzer.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+/*
+ * Copyright (c) 1998 by Sun Microsystems, Inc.
+ * All rights reserved.
+ */
+
+#include "config.h"
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#ifndef lint
+static const char sccsid[] = "@(#)hash_func.c 10.7 (Sleepycat) 9/16/97";
+static const char sccsi2[] = "%W% (Sun) %G%";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+#endif
+
+#include "db_int.h"
+#include "db_page.h"
+#include "hash.h"
+
+/*
+ * __ham_func2 --
+ * Phong Vo's linear congruential hash.
+ *
+ * PUBLIC: u_int32_t __ham_func2 __P((const void *, u_int32_t));
+ */
+#define DCHARHASH(h, c) ((h) = 0x63c63cd9*(h) + 0x9c39c33d + (c))
+
+u_int32_t
+__ham_func2(key, len)
+ const void *key;
+ u_int32_t len;
+{
+ const u_int8_t *e, *k;
+ u_int32_t h;
+ u_int8_t c;
+
+ k = key;
+ e = k + len;
+ for (h = 0; k != e;) {
+ c = *k++;
+ if (!c && k > e)
+ break;
+ DCHARHASH(h, c);
+ }
+ return (h);
+}
+
+/*
+ * __ham_func3 --
+ * Ozan Yigit's original sdbm hash.
+ *
+ * Ugly, but fast. Break the string up into 8 byte units. On the first time
+ * through the loop get the "leftover bytes" (strlen % 8). On every other
+ * iteration, perform 8 HASHC's so we handle all 8 bytes. Essentially, this
+ * saves us 7 cmp & branch instructions.
+ *
+ * PUBLIC: u_int32_t __ham_func3 __P((const void *, u_int32_t));
+ */
+u_int32_t
+__ham_func3(key, len)
+ const void *key;
+ u_int32_t len;
+{
+ const u_int8_t *k;
+ u_int32_t n, loop;
+
+ if (len == 0)
+ return (0);
+
+#define HASHC n = *k++ + 65599 * n
+ n = 0;
+ k = key;
+
+ loop = (len + 8 - 1) >> 3;
+ switch (len & (8 - 1)) {
+ case 0:
+ do {
+ HASHC;
+ case 7:
+ HASHC;
+ case 6:
+ HASHC;
+ case 5:
+ HASHC;
+ case 4:
+ HASHC;
+ case 3:
+ HASHC;
+ case 2:
+ HASHC;
+ case 1:
+ HASHC;
+ } while (--loop);
+ }
+ return (n);
+}
+
+/*
+ * __ham_func4 --
+ * Chris Torek's hash function. Although this function performs only
+ * slightly worse than __ham_func5 on strings, it performs horribly on
+ * numbers.
+ *
+ * PUBLIC: u_int32_t __ham_func4 __P((const void *, u_int32_t));
+ */
+u_int32_t
+__ham_func4(key, len)
+ const void *key;
+ u_int32_t len;
+{
+ const u_int8_t *k;
+ u_int32_t h, loop;
+
+ if (len == 0)
+ return (0);
+
+#define HASH4a h = (h << 5) - h + *k++;
+#define HASH4b h = (h << 5) + h + *k++;
+#define HASH4 HASH4b
+ h = 0;
+ k = key;
+
+ loop = (len + 8 - 1) >> 3;
+ switch (len & (8 - 1)) {
+ case 0:
+ do {
+ HASH4;
+ case 7:
+ HASH4;
+ case 6:
+ HASH4;
+ case 5:
+ HASH4;
+ case 4:
+ HASH4;
+ case 3:
+ HASH4;
+ case 2:
+ HASH4;
+ case 1:
+ HASH4;
+ } while (--loop);
+ }
+ return (h);
+}
+
+/*
+ * Fowler/Noll/Vo hash
+ *
+ * The basis of the hash algorithm was taken from an idea sent by email to the
+ * IEEE Posix P1003.2 mailing list from Phong Vo (kpv@research.att.com) and
+ * Glenn Fowler (gsf@research.att.com). Landon Curt Noll (chongo@toad.com)
+ * later improved on their algorithm.
+ *
+ * The magic is in the interesting relationship between the special prime
+ * 16777619 (2^24 + 403) and 2^32 and 2^8.
+ *
+ * This hash produces the fewest collisions of any function that we've seen so
+ * far, and works well on both numbers and strings.
+ *
+ * PUBLIC: u_int32_t __ham_func5 __P((const void *, u_int32_t));
+ */
+u_int32_t
+__ham_func5(key, len)
+ const void *key;
+ u_int32_t len;
+{
+ const u_int8_t *k, *e;
+ u_int32_t h;
+
+ k = key;
+ e = k + len;
+ for (h = 0; k < e; ++k) {
+ h *= 16777619;
+ h ^= *k;
+ }
+ return (h);
+}
diff --git a/usr/src/cmd/sendmail/db/hash/hash_page.c b/usr/src/cmd/sendmail/db/hash/hash_page.c
new file mode 100644
index 0000000000..f7a9c91f2f
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/hash/hash_page.c
@@ -0,0 +1,1929 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+/*
+ * Copyright (c) 1990, 1993, 1994
+ * Margo Seltzer. All rights reserved.
+ */
+/*
+ * Copyright (c) 1990, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Margo Seltzer.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)hash_page.c 10.55 (Sleepycat) 1/3/99";
+#endif /* not lint */
+
+/*
+ * PACKAGE: hashing
+ *
+ * DESCRIPTION:
+ * Page manipulation for hashing package.
+ *
+ * ROUTINES:
+ *
+ * External
+ * __get_page
+ * __add_ovflpage
+ * __overflow_page
+ * Internal
+ * open_temp
+ */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <errno.h>
+#include <string.h>
+#endif
+
+#include "db_int.h"
+#include "db_page.h"
+#include "hash.h"
+
+static int __ham_lock_bucket __P((DBC *, db_lockmode_t));
+
+#ifdef DEBUG_SLOW
+static void __account_page(DB *, db_pgno_t, int);
+#endif
+
+/*
+ * PUBLIC: int __ham_item __P((DBC *, db_lockmode_t));
+ */
+int
+__ham_item(dbc, mode)
+ DBC *dbc;
+ db_lockmode_t mode;
+{
+ DB *dbp;
+ HASH_CURSOR *hcp;
+ db_pgno_t next_pgno;
+ int ret;
+
+ dbp = dbc->dbp;
+ hcp = (HASH_CURSOR *)dbc->internal;
+
+ if (F_ISSET(hcp, H_DELETED))
+ return (EINVAL);
+ F_CLR(hcp, H_OK | H_NOMORE);
+
+ /* Check if we need to get a page for this cursor. */
+ if ((ret = __ham_get_cpage(dbc, mode)) != 0)
+ return (ret);
+
+ /* Check if we are looking for space in which to insert an item. */
+ if (hcp->seek_size && hcp->seek_found_page == PGNO_INVALID
+ && hcp->seek_size < P_FREESPACE(hcp->pagep))
+ hcp->seek_found_page = hcp->pgno;
+
+ /* Check if we need to go on to the next page. */
+ if (F_ISSET(hcp, H_ISDUP) && hcp->dpgno == PGNO_INVALID)
+ /*
+ * ISDUP is set, and offset is at the beginning of the datum.
+ * We need to grab the length of the datum, then set the datum
+ * pointer to be the beginning of the datum.
+ */
+ memcpy(&hcp->dup_len,
+ HKEYDATA_DATA(H_PAIRDATA(hcp->pagep, hcp->bndx)) +
+ hcp->dup_off, sizeof(db_indx_t));
+ else if (F_ISSET(hcp, H_ISDUP)) {
+ /* Make sure we're not about to run off the page. */
+ if (hcp->dpagep == NULL && (ret = __ham_get_page(dbp,
+ hcp->dpgno, &hcp->dpagep)) != 0)
+ return (ret);
+
+ if (hcp->dndx >= NUM_ENT(hcp->dpagep)) {
+ if (NEXT_PGNO(hcp->dpagep) == PGNO_INVALID) {
+ if (F_ISSET(hcp, H_DUPONLY)) {
+ F_CLR(hcp, H_OK);
+ F_SET(hcp, H_NOMORE);
+ return (0);
+ }
+ if ((ret = __ham_put_page(dbp,
+ hcp->dpagep, 0)) != 0)
+ return (ret);
+ F_CLR(hcp, H_ISDUP);
+ hcp->dpagep = NULL;
+ hcp->dpgno = PGNO_INVALID;
+ hcp->dndx = NDX_INVALID;
+ hcp->bndx++;
+ } else if ((ret = __ham_next_cpage(dbc,
+ NEXT_PGNO(hcp->dpagep), 0, H_ISDUP)) != 0)
+ return (ret);
+ }
+ }
+
+ if (hcp->bndx >= (db_indx_t)H_NUMPAIRS(hcp->pagep)) {
+ /* Fetch next page. */
+ if (NEXT_PGNO(hcp->pagep) == PGNO_INVALID) {
+ F_SET(hcp, H_NOMORE);
+ if (hcp->dpagep != NULL &&
+ (ret = __ham_put_page(dbp, hcp->dpagep, 0)) != 0)
+ return (ret);
+ hcp->dpgno = PGNO_INVALID;
+ return (DB_NOTFOUND);
+ }
+ next_pgno = NEXT_PGNO(hcp->pagep);
+ hcp->bndx = 0;
+ if ((ret = __ham_next_cpage(dbc, next_pgno, 0, 0)) != 0)
+ return (ret);
+ }
+
+ F_SET(hcp, H_OK);
+ return (0);
+}
+
+/*
+ * PUBLIC: int __ham_item_reset __P((DBC *));
+ */
+int
+__ham_item_reset(dbc)
+ DBC *dbc;
+{
+ HASH_CURSOR *hcp;
+ DB *dbp;
+ int ret;
+
+ ret = 0;
+ dbp = dbc->dbp;
+ hcp = (HASH_CURSOR *)dbc->internal;
+ if (hcp->pagep != NULL)
+ ret = __ham_put_page(dbp, hcp->pagep, 0);
+ if (ret == 0 && hcp->dpagep != NULL)
+ ret = __ham_put_page(dbp, hcp->dpagep, 0);
+
+ __ham_item_init(hcp);
+ return (ret);
+}
+
+/*
+ * PUBLIC: void __ham_item_init __P((HASH_CURSOR *));
+ */
+void
+__ham_item_init(hcp)
+ HASH_CURSOR *hcp;
+{
+ /*
+ * If this cursor still holds any locks, we must
+ * release them if we are not running with transactions.
+ */
+ if (hcp->lock && hcp->dbc->txn == NULL)
+ (void)lock_put(hcp->dbc->dbp->dbenv->lk_info, hcp->lock);
+
+ /*
+ * The following fields must *not* be initialized here
+ * because they may have meaning across inits.
+ * hlock, hdr, split_buf, stats
+ */
+ hcp->bucket = BUCKET_INVALID;
+ hcp->lbucket = BUCKET_INVALID;
+ hcp->lock = 0;
+ hcp->pagep = NULL;
+ hcp->pgno = PGNO_INVALID;
+ hcp->bndx = NDX_INVALID;
+ hcp->dpagep = NULL;
+ hcp->dpgno = PGNO_INVALID;
+ hcp->dndx = NDX_INVALID;
+ hcp->dup_off = 0;
+ hcp->dup_len = 0;
+ hcp->dup_tlen = 0;
+ hcp->seek_size = 0;
+ hcp->seek_found_page = PGNO_INVALID;
+ hcp->flags = 0;
+}
+
+/*
+ * PUBLIC: int __ham_item_done __P((DBC *, int));
+ */
+int
+__ham_item_done(dbc, dirty)
+ DBC *dbc;
+ int dirty;
+{
+ DB *dbp;
+ HASH_CURSOR *hcp;
+ int ret, t_ret;
+
+ dbp = dbc->dbp;
+ hcp = (HASH_CURSOR *)dbc->internal;
+ t_ret = ret = 0;
+
+ if (hcp->pagep)
+ ret = __ham_put_page(dbp, hcp->pagep,
+ dirty && hcp->dpagep == NULL);
+ hcp->pagep = NULL;
+
+ if (hcp->dpagep)
+ t_ret = __ham_put_page(dbp, hcp->dpagep, dirty);
+ hcp->dpagep = NULL;
+
+ if (ret == 0 && t_ret != 0)
+ ret = t_ret;
+
+ /*
+ * We don't throw out the page number since we might want to
+ * continue getting on this page.
+ */
+ return (ret != 0 ? ret : t_ret);
+}
+
+/*
+ * Returns the last item in a bucket.
+ *
+ * PUBLIC: int __ham_item_last __P((DBC *, db_lockmode_t));
+ */
+int
+__ham_item_last(dbc, mode)
+ DBC *dbc;
+ db_lockmode_t mode;
+{
+ HASH_CURSOR *hcp;
+ int ret;
+
+ hcp = (HASH_CURSOR *)dbc->internal;
+ if ((ret = __ham_item_reset(dbc)) != 0)
+ return (ret);
+
+ hcp->bucket = hcp->hdr->max_bucket;
+ F_SET(hcp, H_OK);
+ return (__ham_item_prev(dbc, mode));
+}
+
+/*
+ * PUBLIC: int __ham_item_first __P((DBC *, db_lockmode_t));
+ */
+int
+__ham_item_first(dbc, mode)
+ DBC *dbc;
+ db_lockmode_t mode;
+{
+ HASH_CURSOR *hcp;
+ int ret;
+
+ hcp = (HASH_CURSOR *)dbc->internal;
+ if ((ret = __ham_item_reset(dbc)) != 0)
+ return (ret);
+ F_SET(hcp, H_OK);
+ hcp->bucket = 0;
+ return (__ham_item_next(dbc, mode));
+}
+
+/*
+ * __ham_item_prev --
+ * Returns a pointer to key/data pair on a page. In the case of
+ * bigkeys, just returns the page number and index of the bigkey
+ * pointer pair.
+ *
+ * PUBLIC: int __ham_item_prev __P((DBC *, db_lockmode_t));
+ */
+int
+__ham_item_prev(dbc, mode)
+ DBC *dbc;
+ db_lockmode_t mode;
+{
+ DB *dbp;
+ HASH_CURSOR *hcp;
+ db_pgno_t next_pgno;
+ int ret;
+
+ dbp = dbc->dbp;
+ hcp = (HASH_CURSOR *)dbc->internal;
+ /*
+ * There are N cases for backing up in a hash file.
+ * Case 1: In the middle of a page, no duplicates, just dec the index.
+ * Case 2: In the middle of a duplicate set, back up one.
+ * Case 3: At the beginning of a duplicate set, get out of set and
+ * back up to next key.
+ * Case 4: At the beginning of a page; go to previous page.
+ * Case 5: At the beginning of a bucket; go to prev bucket.
+ */
+ F_CLR(hcp, H_OK | H_NOMORE | H_DELETED);
+
+ /*
+ * First handle the duplicates. Either you'll get the key here
+ * or you'll exit the duplicate set and drop into the code below
+ * to handle backing up through keys.
+ */
+ if (F_ISSET(hcp, H_ISDUP)) {
+ if (hcp->dpgno == PGNO_INVALID) {
+ /* Duplicates are on-page. */
+ if (hcp->dup_off != 0)
+ if ((ret = __ham_get_cpage(dbc, mode)) != 0)
+ return (ret);
+ else {
+ HASH_CURSOR *h;
+ h = hcp;
+ memcpy(&h->dup_len, HKEYDATA_DATA(
+ H_PAIRDATA(h->pagep, h->bndx))
+ + h->dup_off - sizeof(db_indx_t),
+ sizeof(db_indx_t));
+ hcp->dup_off -=
+ DUP_SIZE(hcp->dup_len);
+ hcp->dndx--;
+ return (__ham_item(dbc, mode));
+ }
+ } else if (hcp->dndx > 0) { /* Duplicates are off-page. */
+ hcp->dndx--;
+ return (__ham_item(dbc, mode));
+ } else if ((ret = __ham_get_cpage(dbc, mode)) != 0)
+ return (ret);
+ else if (PREV_PGNO(hcp->dpagep) == PGNO_INVALID) {
+ if (F_ISSET(hcp, H_DUPONLY)) {
+ F_CLR(hcp, H_OK);
+ F_SET(hcp, H_NOMORE);
+ return (0);
+ } else {
+ F_CLR(hcp, H_ISDUP); /* End of dups */
+ hcp->dpgno = PGNO_INVALID;
+ if (hcp->dpagep != NULL)
+ (void)__ham_put_page(dbp,
+ hcp->dpagep, 0);
+ hcp->dpagep = NULL;
+ }
+ } else if ((ret = __ham_next_cpage(dbc,
+ PREV_PGNO(hcp->dpagep), 0, H_ISDUP)) != 0)
+ return (ret);
+ else {
+ hcp->dndx = NUM_ENT(hcp->pagep) - 1;
+ return (__ham_item(dbc, mode));
+ }
+ }
+
+ /*
+ * If we get here, we are not in a duplicate set, and just need
+ * to back up the cursor. There are still three cases:
+ * midpage, beginning of page, beginning of bucket.
+ */
+
+ if (F_ISSET(hcp, H_DUPONLY)) {
+ F_CLR(hcp, H_OK);
+ F_SET(hcp, H_NOMORE);
+ return (0);
+ }
+
+ if (hcp->bndx == 0) { /* Beginning of page. */
+ if ((ret = __ham_get_cpage(dbc, mode)) != 0)
+ return (ret);
+ hcp->pgno = PREV_PGNO(hcp->pagep);
+ if (hcp->pgno == PGNO_INVALID) {
+ /* Beginning of bucket. */
+ F_SET(hcp, H_NOMORE);
+ return (DB_NOTFOUND);
+ } else if ((ret =
+ __ham_next_cpage(dbc, hcp->pgno, 0, 0)) != 0)
+ return (ret);
+ else
+ hcp->bndx = H_NUMPAIRS(hcp->pagep);
+ }
+
+ /*
+ * Either we've got the cursor set up to be decremented, or we
+ * have to find the end of a bucket.
+ */
+ if (hcp->bndx == NDX_INVALID) {
+ if (hcp->pagep == NULL)
+ next_pgno = BUCKET_TO_PAGE(hcp, hcp->bucket);
+ else
+ goto got_page;
+
+ do {
+ if ((ret = __ham_next_cpage(dbc, next_pgno, 0, 0)) != 0)
+ return (ret);
+got_page: next_pgno = NEXT_PGNO(hcp->pagep);
+ hcp->bndx = H_NUMPAIRS(hcp->pagep);
+ } while (next_pgno != PGNO_INVALID);
+
+ if (hcp->bndx == 0) {
+ /* Bucket was empty. */
+ F_SET(hcp, H_NOMORE);
+ return (DB_NOTFOUND);
+ }
+ }
+
+ hcp->bndx--;
+
+ return (__ham_item(dbc, mode));
+}
+
+/*
+ * Sets the cursor to the next key/data pair on a page.
+ *
+ * PUBLIC: int __ham_item_next __P((DBC *, db_lockmode_t));
+ */
+int
+__ham_item_next(dbc, mode)
+ DBC *dbc;
+ db_lockmode_t mode;
+{
+ HASH_CURSOR *hcp;
+
+ hcp = (HASH_CURSOR *)dbc->internal;
+ /*
+ * Deleted on-page duplicates are a weird case. If we delete the last
+ * one, then our cursor is at the very end of a duplicate set and
+ * we actually need to go on to the next key.
+ */
+ if (F_ISSET(hcp, H_DELETED)) {
+ if (hcp->bndx != NDX_INVALID &&
+ F_ISSET(hcp, H_ISDUP) &&
+ hcp->dpgno == PGNO_INVALID &&
+ hcp->dup_tlen == hcp->dup_off) {
+ if (F_ISSET(hcp, H_DUPONLY)) {
+ F_CLR(hcp, H_OK);
+ F_SET(hcp, H_NOMORE);
+ return (0);
+ } else {
+ F_CLR(hcp, H_ISDUP);
+ hcp->dpgno = PGNO_INVALID;
+ hcp->bndx++;
+ }
+ } else if (!F_ISSET(hcp, H_ISDUP) &&
+ F_ISSET(hcp, H_DUPONLY)) {
+ F_CLR(hcp, H_OK);
+ F_SET(hcp, H_NOMORE);
+ return (0);
+ }
+ F_CLR(hcp, H_DELETED);
+ } else if (hcp->bndx == NDX_INVALID) {
+ hcp->bndx = 0;
+ hcp->dpgno = PGNO_INVALID;
+ F_CLR(hcp, H_ISDUP);
+ } else if (F_ISSET(hcp, H_ISDUP) && hcp->dpgno != PGNO_INVALID)
+ hcp->dndx++;
+ else if (F_ISSET(hcp, H_ISDUP)) {
+ if (hcp->dup_off + DUP_SIZE(hcp->dup_len) >=
+ hcp->dup_tlen && F_ISSET(hcp, H_DUPONLY)) {
+ F_CLR(hcp, H_OK);
+ F_SET(hcp, H_NOMORE);
+ return (0);
+ }
+ hcp->dndx++;
+ hcp->dup_off += DUP_SIZE(hcp->dup_len);
+ if (hcp->dup_off >= hcp->dup_tlen) {
+ F_CLR(hcp, H_ISDUP);
+ hcp->dpgno = PGNO_INVALID;
+ hcp->bndx++;
+ }
+ } else if (F_ISSET(hcp, H_DUPONLY)) {
+ F_CLR(hcp, H_OK);
+ F_SET(hcp, H_NOMORE);
+ return (0);
+ } else
+ hcp->bndx++;
+
+ return (__ham_item(dbc, mode));
+}
+
+/*
+ * PUBLIC: void __ham_putitem __P((PAGE *p, const DBT *, int));
+ *
+ * This is a little bit sleazy in that we're overloading the meaning
+ * of the H_OFFPAGE type here. When we recover deletes, we have the
+ * entire entry instead of having only the DBT, so we'll pass type
+ * H_OFFPAGE to mean, "copy the whole entry" as opposed to constructing
+ * an H_KEYDATA around it.
+ */
+void
+__ham_putitem(p, dbt, type)
+ PAGE *p;
+ const DBT *dbt;
+ int type;
+{
+ u_int16_t n, off;
+
+ n = NUM_ENT(p);
+
+ /* Put the item element on the page. */
+ if (type == H_OFFPAGE) {
+ off = HOFFSET(p) - dbt->size;
+ HOFFSET(p) = p->inp[n] = off;
+ memcpy(P_ENTRY(p, n), dbt->data, dbt->size);
+ } else {
+ off = HOFFSET(p) - HKEYDATA_SIZE(dbt->size);
+ HOFFSET(p) = p->inp[n] = off;
+ PUT_HKEYDATA(P_ENTRY(p, n), dbt->data, dbt->size, type);
+ }
+
+ /* Adjust page info. */
+ NUM_ENT(p) += 1;
+}
+
+/*
+ * PUBLIC: void __ham_reputpair
+ * PUBLIC: __P((PAGE *p, u_int32_t, u_int32_t, const DBT *, const DBT *));
+ *
+ * This is a special case to restore a key/data pair to its original
+ * location during recovery. We are guaranteed that the pair fits
+ * on the page and is not the last pair on the page (because if it's
+ * the last pair, the normal insert works).
+ */
+void
+__ham_reputpair(p, psize, ndx, key, data)
+ PAGE *p;
+ u_int32_t psize, ndx;
+ const DBT *key, *data;
+{
+ db_indx_t i, movebytes, newbytes;
+ u_int8_t *from;
+
+ /* First shuffle the existing items up on the page. */
+ movebytes =
+ (ndx == 0 ? psize : p->inp[H_DATAINDEX(ndx - 1)]) - HOFFSET(p);
+ newbytes = key->size + data->size;
+ from = (u_int8_t *)p + HOFFSET(p);
+ memmove(from - newbytes, from, movebytes);
+
+ /*
+ * Adjust the indices and move them up 2 spaces. Note that we
+ * have to check the exit condition inside the loop just in case
+ * we are dealing with index 0 (db_indx_t's are unsigned).
+ */
+ for (i = NUM_ENT(p) - 1; ; i-- ) {
+ p->inp[i + 2] = p->inp[i] - newbytes;
+ if (i == H_KEYINDEX(ndx))
+ break;
+ }
+
+ /* Put the key and data on the page. */
+ p->inp[H_KEYINDEX(ndx)] =
+ (ndx == 0 ? psize : p->inp[H_DATAINDEX(ndx - 1)]) - key->size;
+ p->inp[H_DATAINDEX(ndx)] = p->inp[H_KEYINDEX(ndx)] - data->size;
+ memcpy(P_ENTRY(p, H_KEYINDEX(ndx)), key->data, key->size);
+ memcpy(P_ENTRY(p, H_DATAINDEX(ndx)), data->data, data->size);
+
+ /* Adjust page info. */
+ HOFFSET(p) -= newbytes;
+ NUM_ENT(p) += 2;
+}
+
+
+/*
+ * PUBLIC: int __ham_del_pair __P((DBC *, int));
+ */
+int
+__ham_del_pair(dbc, reclaim_page)
+ DBC *dbc;
+ int reclaim_page;
+{
+ DB *dbp;
+ HASH_CURSOR *hcp;
+ DBT data_dbt, key_dbt;
+ DB_ENV *dbenv;
+ DB_LSN new_lsn, *n_lsn, tmp_lsn;
+ PAGE *p;
+ db_indx_t ndx;
+ db_pgno_t chg_pgno, pgno;
+ int ret, tret;
+
+ dbp = dbc->dbp;
+ hcp = (HASH_CURSOR *)dbc->internal;
+
+ dbenv = dbp->dbenv;
+ ndx = hcp->bndx;
+ if (hcp->pagep == NULL &&
+ (ret = __ham_get_page(dbp, hcp->pgno, &hcp->pagep)) != 0)
+ return (ret);
+
+ p = hcp->pagep;
+
+ /*
+ * We optimize for the normal case which is when neither the key nor
+ * the data are large. In this case, we write a single log record
+ * and do the delete. If either is large, we'll call __big_delete
+ * to remove the big item and then update the page to remove the
+ * entry referring to the big item.
+ */
+ ret = 0;
+ if (HPAGE_PTYPE(H_PAIRKEY(p, ndx)) == H_OFFPAGE) {
+ memcpy(&pgno, HOFFPAGE_PGNO(P_ENTRY(p, H_KEYINDEX(ndx))),
+ sizeof(db_pgno_t));
+ ret = __db_doff(dbc, pgno, __ham_del_page);
+ }
+
+ if (ret == 0)
+ switch (HPAGE_PTYPE(H_PAIRDATA(p, ndx))) {
+ case H_OFFPAGE:
+ memcpy(&pgno,
+ HOFFPAGE_PGNO(P_ENTRY(p, H_DATAINDEX(ndx))),
+ sizeof(db_pgno_t));
+ ret = __db_doff(dbc, pgno, __ham_del_page);
+ break;
+ case H_OFFDUP:
+ memcpy(&pgno,
+ HOFFDUP_PGNO(P_ENTRY(p, H_DATAINDEX(ndx))),
+ sizeof(db_pgno_t));
+ ret = __db_ddup(dbc, pgno, __ham_del_page);
+ F_CLR(hcp, H_ISDUP);
+ break;
+ case H_DUPLICATE:
+ /*
+ * If we delete a pair that is/was a duplicate, then
+ * we had better clear the flag so that we update the
+ * cursor appropriately.
+ */
+ F_CLR(hcp, H_ISDUP);
+ break;
+ }
+
+ if (ret)
+ return (ret);
+
+ /* Now log the delete off this page. */
+ if (DB_LOGGING(dbc)) {
+ key_dbt.data = P_ENTRY(p, H_KEYINDEX(ndx));
+ key_dbt.size =
+ LEN_HITEM(p, hcp->hdr->pagesize, H_KEYINDEX(ndx));
+ data_dbt.data = P_ENTRY(p, H_DATAINDEX(ndx));
+ data_dbt.size =
+ LEN_HITEM(p, hcp->hdr->pagesize, H_DATAINDEX(ndx));
+
+ if ((ret = __ham_insdel_log(dbenv->lg_info,
+ dbc->txn, &new_lsn, 0, DELPAIR,
+ dbp->log_fileid, PGNO(p), (u_int32_t)ndx,
+ &LSN(p), &key_dbt, &data_dbt)) != 0)
+ return (ret);
+
+ /* Move lsn onto page. */
+ LSN(p) = new_lsn;
+ }
+
+ __ham_dpair(dbp, p, ndx);
+
+ /*
+ * If we are locking, we will not maintain this, because it is
+ * a hot spot.
+ * XXX perhaps we can retain incremental numbers and apply them
+ * later.
+ */
+ if (!F_ISSET(dbp, DB_AM_LOCKING))
+ --hcp->hdr->nelem;
+
+ /*
+ * If we need to reclaim the page, then check if the page is empty.
+ * There are two cases. If it's empty and it's not the first page
+ * in the bucket (i.e., the bucket page) then we can simply remove
+ * it. If it is the first chain in the bucket, then we need to copy
+ * the second page into it and remove the second page.
+ */
+ if (reclaim_page && NUM_ENT(p) == 0 && PREV_PGNO(p) == PGNO_INVALID &&
+ NEXT_PGNO(p) != PGNO_INVALID) {
+ PAGE *n_pagep, *nn_pagep;
+ db_pgno_t tmp_pgno;
+
+ /*
+ * First page in chain is empty and we know that there
+ * are more pages in the chain.
+ */
+ if ((ret =
+ __ham_get_page(dbp, NEXT_PGNO(p), &n_pagep)) != 0)
+ return (ret);
+
+ if (NEXT_PGNO(n_pagep) != PGNO_INVALID) {
+ if ((ret =
+ __ham_get_page(dbp, NEXT_PGNO(n_pagep),
+ &nn_pagep)) != 0) {
+ (void) __ham_put_page(dbp, n_pagep, 0);
+ return (ret);
+ }
+ }
+
+ if (DB_LOGGING(dbc)) {
+ key_dbt.data = n_pagep;
+ key_dbt.size = hcp->hdr->pagesize;
+ if ((ret = __ham_copypage_log(dbenv->lg_info,
+ dbc->txn, &new_lsn, 0, dbp->log_fileid, PGNO(p),
+ &LSN(p), PGNO(n_pagep), &LSN(n_pagep),
+ NEXT_PGNO(n_pagep),
+ NEXT_PGNO(n_pagep) == PGNO_INVALID ? NULL :
+ &LSN(nn_pagep), &key_dbt)) != 0)
+ return (ret);
+
+ /* Move lsn onto page. */
+ LSN(p) = new_lsn; /* Structure assignment. */
+ LSN(n_pagep) = new_lsn;
+ if (NEXT_PGNO(n_pagep) != PGNO_INVALID)
+ LSN(nn_pagep) = new_lsn;
+ }
+ if (NEXT_PGNO(n_pagep) != PGNO_INVALID) {
+ PREV_PGNO(nn_pagep) = PGNO(p);
+ (void)__ham_put_page(dbp, nn_pagep, 1);
+ }
+
+ tmp_pgno = PGNO(p);
+ tmp_lsn = LSN(p);
+ memcpy(p, n_pagep, hcp->hdr->pagesize);
+ PGNO(p) = tmp_pgno;
+ LSN(p) = tmp_lsn;
+ PREV_PGNO(p) = PGNO_INVALID;
+
+ /*
+ * Cursor is advanced to the beginning of the next page.
+ */
+ hcp->bndx = 0;
+ hcp->pgno = PGNO(p);
+ F_SET(hcp, H_DELETED);
+ chg_pgno = PGNO(p);
+ if ((ret = __ham_dirty_page(dbp, p)) != 0 ||
+ (ret = __ham_del_page(dbc, n_pagep)) != 0)
+ return (ret);
+ } else if (reclaim_page &&
+ NUM_ENT(p) == 0 && PREV_PGNO(p) != PGNO_INVALID) {
+ PAGE *n_pagep, *p_pagep;
+
+ if ((ret =
+ __ham_get_page(dbp, PREV_PGNO(p), &p_pagep)) != 0)
+ return (ret);
+
+ if (NEXT_PGNO(p) != PGNO_INVALID) {
+ if ((ret = __ham_get_page(dbp,
+ NEXT_PGNO(p), &n_pagep)) != 0) {
+ (void)__ham_put_page(dbp, p_pagep, 0);
+ return (ret);
+ }
+ n_lsn = &LSN(n_pagep);
+ } else {
+ n_pagep = NULL;
+ n_lsn = NULL;
+ }
+
+ NEXT_PGNO(p_pagep) = NEXT_PGNO(p);
+ if (n_pagep != NULL)
+ PREV_PGNO(n_pagep) = PGNO(p_pagep);
+
+ if (DB_LOGGING(dbc)) {
+ if ((ret = __ham_newpage_log(dbenv->lg_info,
+ dbc->txn, &new_lsn, 0, DELOVFL,
+ dbp->log_fileid, PREV_PGNO(p), &LSN(p_pagep),
+ PGNO(p), &LSN(p), NEXT_PGNO(p), n_lsn)) != 0)
+ return (ret);
+
+ /* Move lsn onto page. */
+ LSN(p_pagep) = new_lsn; /* Structure assignment. */
+ if (n_pagep)
+ LSN(n_pagep) = new_lsn;
+ LSN(p) = new_lsn;
+ }
+ hcp->pgno = NEXT_PGNO(p);
+ hcp->bndx = 0;
+ /*
+ * Since we are about to delete the cursor page and we have
+ * just moved the cursor, we need to make sure that the
+ * old page pointer isn't left hanging around in the cursor.
+ */
+ hcp->pagep = NULL;
+ chg_pgno = PGNO(p);
+ ret = __ham_del_page(dbc, p);
+ if ((tret = __ham_put_page(dbp, p_pagep, 1)) != 0 &&
+ ret == 0)
+ ret = tret;
+ if (n_pagep != NULL &&
+ (tret = __ham_put_page(dbp, n_pagep, 1)) != 0 &&
+ ret == 0)
+ ret = tret;
+ if (ret != 0)
+ return (ret);
+ } else {
+ /*
+ * Mark item deleted so that we don't try to return it, and
+ * so that we update the cursor correctly on the next call
+ * to next.
+ */
+ F_SET(hcp, H_DELETED);
+ chg_pgno = hcp->pgno;
+ ret = __ham_dirty_page(dbp, p);
+ }
+ __ham_c_update(hcp, chg_pgno, 0, 0, 0);
+
+ /*
+ * Since we just deleted a pair from the master page, anything
+ * in hcp->dpgno should be cleared.
+ */
+ hcp->dpgno = PGNO_INVALID;
+
+ F_CLR(hcp, H_OK);
+ return (ret);
+}
+
+/*
+ * __ham_replpair --
+ * Given the key data indicated by the cursor, replace part/all of it
+ * according to the fields in the dbt.
+ *
+ * PUBLIC: int __ham_replpair __P((DBC *, DBT *, u_int32_t));
+ */
+int
+__ham_replpair(dbc, dbt, make_dup)
+ DBC *dbc;
+ DBT *dbt;
+ u_int32_t make_dup;
+{
+ DB *dbp;
+ HASH_CURSOR *hcp;
+ DBT old_dbt, tdata, tmp;
+ DB_LSN new_lsn;
+ int32_t change; /* XXX: Possible overflow. */
+ u_int32_t len;
+ int is_big, ret, type;
+ u_int8_t *beg, *dest, *end, *hk, *src;
+
+ /*
+ * Big item replacements are handled in generic code.
+ * Items that fit on the current page fall into 4 classes.
+ * 1. On-page element, same size
+ * 2. On-page element, new is bigger (fits)
+ * 3. On-page element, new is bigger (does not fit)
+ * 4. On-page element, old is bigger
+ * Numbers 1, 2, and 4 are essentially the same (and should
+ * be the common case). We handle case 3 as a delete and
+ * add.
+ */
+ dbp = dbc->dbp;
+ hcp = (HASH_CURSOR *)dbc->internal;
+
+ /*
+ * We need to compute the number of bytes that we are adding or
+ * removing from the entry. Normally, we can simply substract
+ * the number of bytes we are replacing (dbt->dlen) from the
+ * number of bytes we are inserting (dbt->size). However, if
+ * we are doing a partial put off the end of a record, then this
+ * formula doesn't work, because we are essentially adding
+ * new bytes.
+ */
+ change = dbt->size - dbt->dlen;
+
+ hk = H_PAIRDATA(hcp->pagep, hcp->bndx);
+ is_big = HPAGE_PTYPE(hk) == H_OFFPAGE;
+
+ if (is_big)
+ memcpy(&len, HOFFPAGE_TLEN(hk), sizeof(u_int32_t));
+ else
+ len = LEN_HKEYDATA(hcp->pagep,
+ dbp->pgsize, H_DATAINDEX(hcp->bndx));
+
+ if (dbt->doff + dbt->dlen > len)
+ change += dbt->doff + dbt->dlen - len;
+
+
+ if (change > (int32_t)P_FREESPACE(hcp->pagep) || is_big) {
+ /*
+ * Case 3 -- two subcases.
+ * A. This is not really a partial operation, but an overwrite.
+ * Simple del and add works.
+ * B. This is a partial and we need to construct the data that
+ * we are really inserting (yuck).
+ * In both cases, we need to grab the key off the page (in
+ * some cases we could do this outside of this routine; for
+ * cleanliness we do it here. If you happen to be on a big
+ * key, this could be a performance hit).
+ */
+ tmp.flags = 0;
+ F_SET(&tmp, DB_DBT_MALLOC | DB_DBT_INTERNAL);
+ if ((ret =
+ __db_ret(dbp, hcp->pagep, H_KEYINDEX(hcp->bndx),
+ &tmp, &dbc->rkey.data, &dbc->rkey.size)) != 0)
+ return (ret);
+
+ if (dbt->doff == 0 && dbt->dlen == len) {
+ ret = __ham_del_pair(dbc, 0);
+ if (ret == 0)
+ ret = __ham_add_el(dbc, &tmp, dbt, H_KEYDATA);
+ } else { /* Case B */
+ type = HPAGE_PTYPE(hk) != H_OFFPAGE ?
+ HPAGE_PTYPE(hk) : H_KEYDATA;
+ tdata.flags = 0;
+ F_SET(&tdata, DB_DBT_MALLOC | DB_DBT_INTERNAL);
+
+ if ((ret = __db_ret(dbp, hcp->pagep,
+ H_DATAINDEX(hcp->bndx), &tdata, &dbc->rdata.data,
+ &dbc->rdata.size)) != 0)
+ goto err;
+
+ /* Now we can delete the item. */
+ if ((ret = __ham_del_pair(dbc, 0)) != 0) {
+ __os_free(tdata.data, tdata.size);
+ goto err;
+ }
+
+ /* Now shift old data around to make room for new. */
+ if (change > 0) {
+ if ((ret = __os_realloc(&tdata.data,
+ tdata.size + change)) != 0)
+ return (ret);
+ memset((u_int8_t *)tdata.data + tdata.size,
+ 0, change);
+ }
+ end = (u_int8_t *)tdata.data + tdata.size;
+
+ src = (u_int8_t *)tdata.data + dbt->doff + dbt->dlen;
+ if (src < end && tdata.size > dbt->doff + dbt->dlen) {
+ len = tdata.size - dbt->doff - dbt->dlen;
+ dest = src + change;
+ memmove(dest, src, len);
+ }
+ memcpy((u_int8_t *)tdata.data + dbt->doff,
+ dbt->data, dbt->size);
+ tdata.size += change;
+
+ /* Now add the pair. */
+ ret = __ham_add_el(dbc, &tmp, &tdata, type);
+ __os_free(tdata.data, tdata.size);
+ }
+err: __os_free(tmp.data, tmp.size);
+ return (ret);
+ }
+
+ /*
+ * Set up pointer into existing data. Do it before the log
+ * message so we can use it inside of the log setup.
+ */
+ beg = HKEYDATA_DATA(H_PAIRDATA(hcp->pagep, hcp->bndx));
+ beg += dbt->doff;
+
+ /*
+ * If we are going to have to move bytes at all, figure out
+ * all the parameters here. Then log the call before moving
+ * anything around.
+ */
+ if (DB_LOGGING(dbc)) {
+ old_dbt.data = beg;
+ old_dbt.size = dbt->dlen;
+ if ((ret = __ham_replace_log(dbp->dbenv->lg_info,
+ dbc->txn, &new_lsn, 0, dbp->log_fileid, PGNO(hcp->pagep),
+ (u_int32_t)H_DATAINDEX(hcp->bndx), &LSN(hcp->pagep),
+ (u_int32_t)dbt->doff, &old_dbt, dbt, make_dup)) != 0)
+ return (ret);
+
+ LSN(hcp->pagep) = new_lsn; /* Structure assignment. */
+ }
+
+ __ham_onpage_replace(hcp->pagep, dbp->pgsize,
+ (u_int32_t)H_DATAINDEX(hcp->bndx), (int32_t)dbt->doff, change, dbt);
+
+ return (0);
+}
+
+/*
+ * Replace data on a page with new data, possibly growing or shrinking what's
+ * there. This is called on two different occasions. On one (from replpair)
+ * we are interested in changing only the data. On the other (from recovery)
+ * we are replacing the entire data (header and all) with a new element. In
+ * the latter case, the off argument is negative.
+ * pagep: the page that we're changing
+ * ndx: page index of the element that is growing/shrinking.
+ * off: Offset at which we are beginning the replacement.
+ * change: the number of bytes (+ or -) that the element is growing/shrinking.
+ * dbt: the new data that gets written at beg.
+ * PUBLIC: void __ham_onpage_replace __P((PAGE *, size_t, u_int32_t, int32_t,
+ * PUBLIC: int32_t, DBT *));
+ */
+void
+__ham_onpage_replace(pagep, pgsize, ndx, off, change, dbt)
+ PAGE *pagep;
+ size_t pgsize;
+ u_int32_t ndx;
+ int32_t off;
+ int32_t change;
+ DBT *dbt;
+{
+ db_indx_t i;
+ int32_t len;
+ u_int8_t *src, *dest;
+ int zero_me;
+
+ if (change != 0) {
+ zero_me = 0;
+ src = (u_int8_t *)(pagep) + HOFFSET(pagep);
+ if (off < 0)
+ len = pagep->inp[ndx] - HOFFSET(pagep);
+ else if ((u_int32_t)off >= LEN_HKEYDATA(pagep, pgsize, ndx)) {
+ len = HKEYDATA_DATA(P_ENTRY(pagep, ndx)) +
+ LEN_HKEYDATA(pagep, pgsize, ndx) - src;
+ zero_me = 1;
+ } else
+ len = (HKEYDATA_DATA(P_ENTRY(pagep, ndx)) + off) - src;
+ dest = src - change;
+ memmove(dest, src, len);
+ if (zero_me)
+ memset(dest + len, 0, change);
+
+ /* Now update the indices. */
+ for (i = ndx; i < NUM_ENT(pagep); i++)
+ pagep->inp[i] -= change;
+ HOFFSET(pagep) -= change;
+ }
+ if (off >= 0)
+ memcpy(HKEYDATA_DATA(P_ENTRY(pagep, ndx)) + off,
+ dbt->data, dbt->size);
+ else
+ memcpy(P_ENTRY(pagep, ndx), dbt->data, dbt->size);
+}
+
+/*
+ * PUBLIC: int __ham_split_page __P((DBC *, u_int32_t, u_int32_t));
+ */
+int
+__ham_split_page(dbc, obucket, nbucket)
+ DBC *dbc;
+ u_int32_t obucket, nbucket;
+{
+ DB *dbp;
+ HASH_CURSOR *hcp;
+ DBT key, page_dbt;
+ DB_ENV *dbenv;
+ DB_LSN new_lsn;
+ PAGE **pp, *old_pagep, *temp_pagep, *new_pagep;
+ db_indx_t n;
+ db_pgno_t bucket_pgno, next_pgno;
+ u_int32_t big_len, len;
+ int ret, tret;
+ void *big_buf;
+
+ dbp = dbc->dbp;
+ hcp = (HASH_CURSOR *)dbc->internal;
+ dbenv = dbp->dbenv;
+ temp_pagep = old_pagep = new_pagep = NULL;
+
+ bucket_pgno = BUCKET_TO_PAGE(hcp, obucket);
+ if ((ret = __ham_get_page(dbp, bucket_pgno, &old_pagep)) != 0)
+ return (ret);
+ if ((ret = __ham_new_page(dbp, BUCKET_TO_PAGE(hcp, nbucket), P_HASH,
+ &new_pagep)) != 0)
+ goto err;
+
+ temp_pagep = hcp->split_buf;
+ memcpy(temp_pagep, old_pagep, hcp->hdr->pagesize);
+
+ if (DB_LOGGING(dbc)) {
+ page_dbt.size = hcp->hdr->pagesize;
+ page_dbt.data = old_pagep;
+ if ((ret = __ham_splitdata_log(dbenv->lg_info,
+ dbc->txn, &new_lsn, 0, dbp->log_fileid, SPLITOLD,
+ PGNO(old_pagep), &page_dbt, &LSN(old_pagep))) != 0)
+ goto err;
+ }
+
+ P_INIT(old_pagep, hcp->hdr->pagesize, PGNO(old_pagep), PGNO_INVALID,
+ PGNO_INVALID, 0, P_HASH);
+
+ if (DB_LOGGING(dbc))
+ LSN(old_pagep) = new_lsn; /* Structure assignment. */
+
+ big_len = 0;
+ big_buf = NULL;
+ key.flags = 0;
+ while (temp_pagep != NULL) {
+ for (n = 0; n < (db_indx_t)H_NUMPAIRS(temp_pagep); n++) {
+ if ((ret =
+ __db_ret(dbp, temp_pagep, H_KEYINDEX(n),
+ &key, &big_buf, &big_len)) != 0)
+ goto err;
+
+ if (__ham_call_hash(hcp, key.data, key.size)
+ == obucket)
+ pp = &old_pagep;
+ else
+ pp = &new_pagep;
+
+ /*
+ * Figure out how many bytes we need on the new
+ * page to store the key/data pair.
+ */
+
+ len = LEN_HITEM(temp_pagep, hcp->hdr->pagesize,
+ H_DATAINDEX(n)) +
+ LEN_HITEM(temp_pagep, hcp->hdr->pagesize,
+ H_KEYINDEX(n)) +
+ 2 * sizeof(db_indx_t);
+
+ if (P_FREESPACE(*pp) < len) {
+ if (DB_LOGGING(dbc)) {
+ page_dbt.size = hcp->hdr->pagesize;
+ page_dbt.data = *pp;
+ if ((ret = __ham_splitdata_log(
+ dbenv->lg_info, dbc->txn,
+ &new_lsn, 0, dbp->log_fileid,
+ SPLITNEW, PGNO(*pp), &page_dbt,
+ &LSN(*pp))) != 0)
+ goto err;
+ LSN(*pp) = new_lsn;
+ }
+ if ((ret =
+ __ham_add_ovflpage(dbc, *pp, 1, pp)) != 0)
+ goto err;
+ }
+ __ham_copy_item(dbp->pgsize,
+ temp_pagep, H_KEYINDEX(n), *pp);
+ __ham_copy_item(dbp->pgsize,
+ temp_pagep, H_DATAINDEX(n), *pp);
+ }
+ next_pgno = NEXT_PGNO(temp_pagep);
+
+ /* Clear temp_page; if it's a link overflow page, free it. */
+ if (PGNO(temp_pagep) != bucket_pgno && (ret =
+ __ham_del_page(dbc, temp_pagep)) != 0)
+ goto err;
+
+ if (next_pgno == PGNO_INVALID)
+ temp_pagep = NULL;
+ else if ((ret =
+ __ham_get_page(dbp, next_pgno, &temp_pagep)) != 0)
+ goto err;
+
+ if (temp_pagep != NULL && DB_LOGGING(dbc)) {
+ page_dbt.size = hcp->hdr->pagesize;
+ page_dbt.data = temp_pagep;
+ if ((ret = __ham_splitdata_log(dbenv->lg_info,
+ dbc->txn, &new_lsn, 0, dbp->log_fileid,
+ SPLITOLD, PGNO(temp_pagep),
+ &page_dbt, &LSN(temp_pagep))) != 0)
+ goto err;
+ LSN(temp_pagep) = new_lsn;
+ }
+ }
+ if (big_buf != NULL)
+ __os_free(big_buf, big_len);
+
+ /*
+ * If the original bucket spanned multiple pages, then we've got
+ * a pointer to a page that used to be on the bucket chain. It
+ * should be deleted.
+ */
+ if (temp_pagep != NULL && PGNO(temp_pagep) != bucket_pgno &&
+ (ret = __ham_del_page(dbc, temp_pagep)) != 0)
+ goto err;
+
+ /*
+ * Write new buckets out.
+ */
+ if (DB_LOGGING(dbc)) {
+ page_dbt.size = hcp->hdr->pagesize;
+ page_dbt.data = old_pagep;
+ if ((ret = __ham_splitdata_log(dbenv->lg_info,
+ dbc->txn, &new_lsn, 0, dbp->log_fileid,
+ SPLITNEW, PGNO(old_pagep),
+ &page_dbt, &LSN(old_pagep))) != 0)
+ goto err;
+ LSN(old_pagep) = new_lsn;
+
+ page_dbt.data = new_pagep;
+ if ((ret = __ham_splitdata_log(dbenv->lg_info,
+ dbc->txn, &new_lsn, 0, dbp->log_fileid,
+ SPLITNEW, PGNO(new_pagep), &page_dbt, &LSN(new_pagep))) != 0)
+ goto err;
+ LSN(new_pagep) = new_lsn;
+ }
+ ret = __ham_put_page(dbp, old_pagep, 1);
+ if ((tret = __ham_put_page(dbp, new_pagep, 1)) != 0 &&
+ ret == 0)
+ ret = tret;
+
+ if (0) {
+err: if (old_pagep != NULL)
+ (void)__ham_put_page(dbp, old_pagep, 1);
+ if (new_pagep != NULL)
+ (void)__ham_put_page(dbp, new_pagep, 1);
+ if (temp_pagep != NULL && PGNO(temp_pagep) != bucket_pgno)
+ (void)__ham_put_page(dbp, temp_pagep, 1);
+ }
+ return (ret);
+}
+
+/*
+ * Add the given pair to the page. The page in question may already be
+ * held (i.e. it was already gotten). If it is, then the page is passed
+ * in via the pagep parameter. On return, pagep will contain the page
+ * to which we just added something. This allows us to link overflow
+ * pages and return the new page having correctly put the last page.
+ *
+ * PUBLIC: int __ham_add_el __P((DBC *, const DBT *, const DBT *, int));
+ */
+int
+__ham_add_el(dbc, key, val, type)
+ DBC *dbc;
+ const DBT *key, *val;
+ int type;
+{
+ DB *dbp;
+ HASH_CURSOR *hcp;
+ const DBT *pkey, *pdata;
+ DBT key_dbt, data_dbt;
+ DB_LSN new_lsn;
+ HOFFPAGE doff, koff;
+ db_pgno_t next_pgno;
+ u_int32_t data_size, key_size, pairsize, rectype;
+ int do_expand, is_keybig, is_databig, ret;
+ int key_type, data_type;
+
+ dbp = dbc->dbp;
+ hcp = (HASH_CURSOR *)dbc->internal;
+ do_expand = 0;
+
+ if (hcp->pagep == NULL && (ret = __ham_get_page(dbp,
+ hcp->seek_found_page != PGNO_INVALID ? hcp->seek_found_page :
+ hcp->pgno, &hcp->pagep)) != 0)
+ return (ret);
+
+ key_size = HKEYDATA_PSIZE(key->size);
+ data_size = HKEYDATA_PSIZE(val->size);
+ is_keybig = ISBIG(hcp, key->size);
+ is_databig = ISBIG(hcp, val->size);
+ if (is_keybig)
+ key_size = HOFFPAGE_PSIZE;
+ if (is_databig)
+ data_size = HOFFPAGE_PSIZE;
+
+ pairsize = key_size + data_size;
+
+ /* Advance to first page in chain with room for item. */
+ while (H_NUMPAIRS(hcp->pagep) && NEXT_PGNO(hcp->pagep) !=
+ PGNO_INVALID) {
+ /*
+ * This may not be the end of the chain, but the pair may fit
+ * anyway. Check if it's a bigpair that fits or a regular
+ * pair that fits.
+ */
+ if (P_FREESPACE(hcp->pagep) >= pairsize)
+ break;
+ next_pgno = NEXT_PGNO(hcp->pagep);
+ if ((ret =
+ __ham_next_cpage(dbc, next_pgno, 0, 0)) != 0)
+ return (ret);
+ }
+
+ /*
+ * Check if we need to allocate a new page.
+ */
+ if (P_FREESPACE(hcp->pagep) < pairsize) {
+ do_expand = 1;
+ if ((ret = __ham_add_ovflpage(dbc,
+ hcp->pagep, 1, &hcp->pagep)) != 0)
+ return (ret);
+ hcp->pgno = PGNO(hcp->pagep);
+ }
+
+ /*
+ * Update cursor.
+ */
+ hcp->bndx = H_NUMPAIRS(hcp->pagep);
+ F_CLR(hcp, H_DELETED);
+ if (is_keybig) {
+ koff.type = H_OFFPAGE;
+ UMRW(koff.unused[0]);
+ UMRW(koff.unused[1]);
+ UMRW(koff.unused[2]);
+ if ((ret = __db_poff(dbc,
+ key, &koff.pgno, __ham_overflow_page)) != 0)
+ return (ret);
+ koff.tlen = key->size;
+ key_dbt.data = &koff;
+ key_dbt.size = sizeof(koff);
+ pkey = &key_dbt;
+ key_type = H_OFFPAGE;
+ } else {
+ pkey = key;
+ key_type = H_KEYDATA;
+ }
+
+ if (is_databig) {
+ doff.type = H_OFFPAGE;
+ UMRW(doff.unused[0]);
+ UMRW(doff.unused[1]);
+ UMRW(doff.unused[2]);
+ if ((ret = __db_poff(dbc,
+ val, &doff.pgno, __ham_overflow_page)) != 0)
+ return (ret);
+ doff.tlen = val->size;
+ data_dbt.data = &doff;
+ data_dbt.size = sizeof(doff);
+ pdata = &data_dbt;
+ data_type = H_OFFPAGE;
+ } else {
+ pdata = val;
+ data_type = type;
+ }
+
+ if (DB_LOGGING(dbc)) {
+ rectype = PUTPAIR;
+ if (is_databig)
+ rectype |= PAIR_DATAMASK;
+ if (is_keybig)
+ rectype |= PAIR_KEYMASK;
+
+ if ((ret = __ham_insdel_log(dbp->dbenv->lg_info,
+ dbc->txn, &new_lsn, 0, rectype,
+ dbp->log_fileid, PGNO(hcp->pagep),
+ (u_int32_t)H_NUMPAIRS(hcp->pagep),
+ &LSN(hcp->pagep), pkey, pdata)) != 0)
+ return (ret);
+
+ /* Move lsn onto page. */
+ LSN(hcp->pagep) = new_lsn; /* Structure assignment. */
+ }
+
+ __ham_putitem(hcp->pagep, pkey, key_type);
+ __ham_putitem(hcp->pagep, pdata, data_type);
+
+ /*
+ * For splits, we are going to update item_info's page number
+ * field, so that we can easily return to the same page the
+ * next time we come in here. For other operations, this shouldn't
+ * matter, since odds are this is the last thing that happens before
+ * we return to the user program.
+ */
+ hcp->pgno = PGNO(hcp->pagep);
+
+ /*
+ * XXX Maybe keep incremental numbers here
+ */
+ if (!F_ISSET(dbp, DB_AM_LOCKING))
+ hcp->hdr->nelem++;
+
+ if (do_expand || (hcp->hdr->ffactor != 0 &&
+ (u_int32_t)H_NUMPAIRS(hcp->pagep) > hcp->hdr->ffactor))
+ F_SET(hcp, H_EXPAND);
+ return (0);
+}
+
+
+/*
+ * Special __putitem call used in splitting -- copies one entry to
+ * another. Works for all types of hash entries (H_OFFPAGE, H_KEYDATA,
+ * H_DUPLICATE, H_OFFDUP). Since we log splits at a high level, we
+ * do not need to do any logging here.
+ *
+ * PUBLIC: void __ham_copy_item __P((size_t, PAGE *, u_int32_t, PAGE *));
+ */
+void
+__ham_copy_item(pgsize, src_page, src_ndx, dest_page)
+ size_t pgsize;
+ PAGE *src_page;
+ u_int32_t src_ndx;
+ PAGE *dest_page;
+{
+ u_int32_t len;
+ void *src, *dest;
+
+ /*
+ * Copy the key and data entries onto this new page.
+ */
+ src = P_ENTRY(src_page, src_ndx);
+
+ /* Set up space on dest. */
+ len = LEN_HITEM(src_page, pgsize, src_ndx);
+ HOFFSET(dest_page) -= len;
+ dest_page->inp[NUM_ENT(dest_page)] = HOFFSET(dest_page);
+ dest = P_ENTRY(dest_page, NUM_ENT(dest_page));
+ NUM_ENT(dest_page)++;
+
+ memcpy(dest, src, len);
+}
+
+/*
+ *
+ * Returns:
+ * pointer on success
+ * NULL on error
+ *
+ * PUBLIC: int __ham_add_ovflpage __P((DBC *, PAGE *, int, PAGE **));
+ */
+int
+__ham_add_ovflpage(dbc, pagep, release, pp)
+ DBC *dbc;
+ PAGE *pagep;
+ int release;
+ PAGE **pp;
+{
+ DB *dbp;
+ HASH_CURSOR *hcp;
+ DB_LSN new_lsn;
+ PAGE *new_pagep;
+ int ret;
+
+ dbp = dbc->dbp;
+ hcp = (HASH_CURSOR *)dbc->internal;
+
+ if ((ret = __ham_overflow_page(dbc, P_HASH, &new_pagep)) != 0)
+ return (ret);
+
+ if (DB_LOGGING(dbc)) {
+ if ((ret = __ham_newpage_log(dbp->dbenv->lg_info,
+ dbc->txn, &new_lsn, 0, PUTOVFL,
+ dbp->log_fileid, PGNO(pagep), &LSN(pagep),
+ PGNO(new_pagep), &LSN(new_pagep), PGNO_INVALID, NULL)) != 0)
+ return (ret);
+
+ /* Move lsn onto page. */
+ LSN(pagep) = LSN(new_pagep) = new_lsn;
+ }
+ NEXT_PGNO(pagep) = PGNO(new_pagep);
+ PREV_PGNO(new_pagep) = PGNO(pagep);
+
+ if (release)
+ ret = __ham_put_page(dbp, pagep, 1);
+
+ hcp->stats.hash_overflows++;
+ *pp = new_pagep;
+ return (ret);
+}
+
+
+/*
+ * PUBLIC: int __ham_new_page __P((DB *, u_int32_t, u_int32_t, PAGE **));
+ */
+int
+__ham_new_page(dbp, addr, type, pp)
+ DB *dbp;
+ u_int32_t addr, type;
+ PAGE **pp;
+{
+ PAGE *pagep;
+ int ret;
+
+ if ((ret = memp_fget(dbp->mpf,
+ &addr, DB_MPOOL_CREATE, &pagep)) != 0)
+ return (ret);
+
+ /* This should not be necessary because page-in should do it. */
+ P_INIT(pagep, dbp->pgsize, addr, PGNO_INVALID, PGNO_INVALID, 0, type);
+
+ *pp = pagep;
+ return (0);
+}
+
+/*
+ * PUBLIC: int __ham_del_page __P((DBC *, PAGE *));
+ */
+int
+__ham_del_page(dbc, pagep)
+ DBC *dbc;
+ PAGE *pagep;
+{
+ DB *dbp;
+ HASH_CURSOR *hcp;
+ DB_LSN new_lsn;
+ int ret;
+
+ dbp = dbc->dbp;
+ hcp = (HASH_CURSOR *)dbc->internal;
+ ret = 0;
+ DIRTY_META(dbp, hcp, ret);
+ if (ret != 0) {
+ if (ret != EAGAIN)
+ __db_err(dbp->dbenv,
+ "free_ovflpage: unable to lock meta data page %s\n",
+ strerror(ret));
+ /*
+ * If we are going to return an error, then we should free
+ * the page, so it doesn't stay pinned forever.
+ */
+ (void)__ham_put_page(dbp, pagep, 0);
+ return (ret);
+ }
+
+ if (DB_LOGGING(dbc)) {
+ if ((ret = __ham_newpgno_log(dbp->dbenv->lg_info,
+ dbc->txn, &new_lsn, 0, DELPGNO,
+ dbp->log_fileid, PGNO(pagep), hcp->hdr->last_freed,
+ (u_int32_t)TYPE(pagep), NEXT_PGNO(pagep), P_INVALID,
+ &LSN(pagep), &hcp->hdr->lsn)) != 0)
+ return (ret);
+
+ hcp->hdr->lsn = new_lsn;
+ LSN(pagep) = new_lsn;
+ }
+
+#ifdef DIAGNOSTIC
+ {
+ db_pgno_t __pgno;
+ DB_LSN __lsn;
+ __pgno = pagep->pgno;
+ __lsn = pagep->lsn;
+ memset(pagep, 0xdb, dbp->pgsize);
+ pagep->pgno = __pgno;
+ pagep->lsn = __lsn;
+ }
+#endif
+ TYPE(pagep) = P_INVALID;
+ NEXT_PGNO(pagep) = hcp->hdr->last_freed;
+ hcp->hdr->last_freed = PGNO(pagep);
+
+ return (__ham_put_page(dbp, pagep, 1));
+}
+
+
+/*
+ * PUBLIC: int __ham_put_page __P((DB *, PAGE *, int32_t));
+ */
+int
+__ham_put_page(dbp, pagep, is_dirty)
+ DB *dbp;
+ PAGE *pagep;
+ int32_t is_dirty;
+{
+#ifdef DEBUG_SLOW
+ __account_page(dbp, ((BKT *)((char *)pagep - sizeof(BKT)))->pgno, -1);
+#endif
+ return (memp_fput(dbp->mpf, pagep, (is_dirty ? DB_MPOOL_DIRTY : 0)));
+}
+
+/*
+ * __ham_dirty_page --
+ * Mark a page dirty.
+ *
+ * PUBLIC: int __ham_dirty_page __P((DB *, PAGE *));
+ */
+int
+__ham_dirty_page(dbp, pagep)
+ DB *dbp;
+ PAGE *pagep;
+{
+ return (memp_fset(dbp->mpf, pagep, DB_MPOOL_DIRTY));
+}
+
+/*
+ * PUBLIC: int __ham_get_page __P((DB *, db_pgno_t, PAGE **));
+ */
+int
+__ham_get_page(dbp, addr, pagep)
+ DB *dbp;
+ db_pgno_t addr;
+ PAGE **pagep;
+{
+ int ret;
+
+ ret = memp_fget(dbp->mpf, &addr, DB_MPOOL_CREATE, pagep);
+#ifdef DEBUG_SLOW
+ if (*pagep != NULL)
+ __account_page(dbp, addr, 1);
+#endif
+ return (ret);
+}
+
+/*
+ * PUBLIC: int __ham_overflow_page
+ * PUBLIC: __P((DBC *, u_int32_t, PAGE **));
+ */
+int
+__ham_overflow_page(dbc, type, pp)
+ DBC *dbc;
+ u_int32_t type;
+ PAGE **pp;
+{
+ DB *dbp;
+ HASH_CURSOR *hcp;
+ DB_LSN *lsnp, new_lsn;
+ PAGE *p;
+ db_pgno_t new_addr, next_free, newalloc_flag;
+ u_int32_t offset, splitnum;
+ int ret;
+
+ ret = 0;
+ dbp = dbc->dbp;
+ hcp = (HASH_CURSOR *)dbc->internal;
+ DIRTY_META(dbp, hcp, ret);
+ if (ret != 0)
+ return (ret);
+
+ /*
+ * This routine is split up into two parts. First we have
+ * to figure out the address of the new page that we are
+ * allocating. Then we have to log the allocation. Only
+ * after the log do we get to complete allocation of the
+ * new page.
+ */
+ new_addr = hcp->hdr->last_freed;
+ if (new_addr != PGNO_INVALID) {
+ if ((ret = __ham_get_page(dbp, new_addr, &p)) != 0)
+ return (ret);
+ next_free = NEXT_PGNO(p);
+ lsnp = &LSN(p);
+ newalloc_flag = 0;
+ } else {
+ splitnum = hcp->hdr->ovfl_point;
+ hcp->hdr->spares[splitnum]++;
+ offset = hcp->hdr->spares[splitnum] -
+ (splitnum ? hcp->hdr->spares[splitnum - 1] : 0);
+ new_addr = PGNO_OF(hcp, hcp->hdr->ovfl_point, offset);
+ if (new_addr > MAX_PAGES(hcp)) {
+ __db_err(dbp->dbenv, "hash: out of file pages");
+ hcp->hdr->spares[splitnum]--;
+ return (ENOMEM);
+ }
+ next_free = PGNO_INVALID;
+ p = NULL;
+ lsnp = NULL;
+ newalloc_flag = 1;
+ }
+
+ if (DB_LOGGING(dbc)) {
+ if ((ret = __ham_newpgno_log(dbp->dbenv->lg_info,
+ dbc->txn, &new_lsn, 0, ALLOCPGNO,
+ dbp->log_fileid, new_addr, next_free,
+ 0, newalloc_flag, type, lsnp, &hcp->hdr->lsn)) != 0)
+ return (ret);
+
+ hcp->hdr->lsn = new_lsn;
+ if (lsnp != NULL)
+ *lsnp = new_lsn;
+ }
+
+ if (p != NULL) {
+ /* We just took something off the free list, initialize it. */
+ hcp->hdr->last_freed = next_free;
+ P_INIT(p, hcp->hdr->pagesize, PGNO(p), PGNO_INVALID,
+ PGNO_INVALID, 0, (u_int8_t)type);
+ } else {
+ /* Get the new page. */
+ if ((ret = __ham_new_page(dbp, new_addr, type, &p)) != 0)
+ return (ret);
+ }
+ if (DB_LOGGING(dbc))
+ LSN(p) = new_lsn;
+
+ *pp = p;
+ return (0);
+}
+
+#ifdef DEBUG
+/*
+ * PUBLIC: #ifdef DEBUG
+ * PUBLIC: db_pgno_t __bucket_to_page __P((HASH_CURSOR *, db_pgno_t));
+ * PUBLIC: #endif
+ */
+db_pgno_t
+__bucket_to_page(hcp, n)
+ HASH_CURSOR *hcp;
+ db_pgno_t n;
+{
+ int ret_val;
+
+ ret_val = n + 1;
+ if (n != 0)
+ ret_val += hcp->hdr->spares[__db_log2(n + 1) - 1];
+ return (ret_val);
+}
+#endif
+
+/*
+ * Create a bunch of overflow pages at the current split point.
+ * PUBLIC: void __ham_init_ovflpages __P((DBC *));
+ */
+void
+__ham_init_ovflpages(dbc)
+ DBC *dbc;
+{
+ DB *dbp;
+ HASH_CURSOR *hcp;
+ DB_LSN new_lsn;
+ PAGE *p;
+ db_pgno_t last_pgno, new_pgno;
+ u_int32_t i, curpages, numpages;
+
+ dbp = dbc->dbp;
+ hcp = (HASH_CURSOR *)dbc->internal;
+
+ curpages = hcp->hdr->spares[hcp->hdr->ovfl_point] -
+ hcp->hdr->spares[hcp->hdr->ovfl_point - 1];
+ numpages = hcp->hdr->ovfl_point + 1 - curpages;
+
+ last_pgno = hcp->hdr->last_freed;
+ new_pgno = PGNO_OF(hcp, hcp->hdr->ovfl_point, curpages + 1);
+ if (DB_LOGGING(dbc)) {
+ (void)__ham_ovfl_log(dbp->dbenv->lg_info,
+ dbc->txn, &new_lsn, 0, dbp->log_fileid, new_pgno,
+ numpages, last_pgno, hcp->hdr->ovfl_point, &hcp->hdr->lsn);
+ hcp->hdr->lsn = new_lsn;
+ } else
+ ZERO_LSN(new_lsn);
+
+ hcp->hdr->spares[hcp->hdr->ovfl_point] += numpages;
+ for (i = numpages; i > 0; i--) {
+ if (__ham_new_page(dbp,
+ PGNO_OF(hcp, hcp->hdr->ovfl_point, curpages + i),
+ P_INVALID, &p) != 0)
+ break;
+ LSN(p) = new_lsn;
+ NEXT_PGNO(p) = last_pgno;
+ last_pgno = PGNO(p);
+ (void)__ham_put_page(dbp, p, 1);
+ }
+ hcp->hdr->last_freed = last_pgno;
+}
+
+/*
+ * PUBLIC: int __ham_get_cpage __P((DBC *, db_lockmode_t));
+ */
+int
+__ham_get_cpage(dbc, mode)
+ DBC *dbc;
+ db_lockmode_t mode;
+{
+ DB *dbp;
+ HASH_CURSOR *hcp;
+ int ret;
+
+ dbp = dbc->dbp;
+ hcp = (HASH_CURSOR *)dbc->internal;
+
+ /*
+ * There are three cases with respect to buckets and locks. If there
+ * is no lock held, then if we are locking, we should get the lock.
+ * If there is a lock held and it's for the current bucket, we don't
+ * need to do anything. If there is a lock, but it's for a different
+ * bucket, then we need to release and get.
+ */
+ if (F_ISSET(dbp, DB_AM_LOCKING)) {
+ if (hcp->lock != 0 && hcp->lbucket != hcp->bucket) {
+ /*
+ * If this is the original lock, don't release it,
+ * because we may need to restore it upon exit.
+ */
+ if (dbc->txn == NULL &&
+ !F_ISSET(hcp, H_ORIGINAL) && (ret =
+ lock_put(dbp->dbenv->lk_info, hcp->lock)) != 0)
+ return (ret);
+ F_CLR(hcp, H_ORIGINAL);
+ hcp->lock = 0;
+ }
+ if (hcp->lock == 0 && (ret = __ham_lock_bucket(dbc, mode)) != 0)
+ return (ret);
+ hcp->lbucket = hcp->bucket;
+ }
+
+ if (hcp->pagep == NULL) {
+ if (hcp->pgno == PGNO_INVALID) {
+ hcp->pgno = BUCKET_TO_PAGE(hcp, hcp->bucket);
+ hcp->bndx = 0;
+ }
+
+ if ((ret =
+ __ham_get_page(dbp, hcp->pgno, &hcp->pagep)) != 0)
+ return (ret);
+ }
+
+ if (hcp->dpgno != PGNO_INVALID && hcp->dpagep == NULL)
+ if ((ret =
+ __ham_get_page(dbp, hcp->dpgno, &hcp->dpagep)) != 0)
+ return (ret);
+ return (0);
+}
+
+/*
+ * Get a new page at the cursor, putting the last page if necessary.
+ * If the flag is set to H_ISDUP, then we are talking about the
+ * duplicate page, not the main page.
+ *
+ * PUBLIC: int __ham_next_cpage __P((DBC *, db_pgno_t, int, u_int32_t));
+ */
+int
+__ham_next_cpage(dbc, pgno, dirty, flags)
+ DBC *dbc;
+ db_pgno_t pgno;
+ int dirty;
+ u_int32_t flags;
+{
+ DB *dbp;
+ HASH_CURSOR *hcp;
+ PAGE *p;
+ int ret;
+
+ dbp = dbc->dbp;
+ hcp = (HASH_CURSOR *)dbc->internal;
+ if (LF_ISSET(H_ISDUP) && hcp->dpagep != NULL &&
+ (ret = __ham_put_page(dbp, hcp->dpagep, dirty)) != 0)
+ return (ret);
+ else if (!LF_ISSET(H_ISDUP) && hcp->pagep != NULL &&
+ (ret = __ham_put_page(dbp, hcp->pagep, dirty)) != 0)
+ return (ret);
+
+ if ((ret = __ham_get_page(dbp, pgno, &p)) != 0)
+ return (ret);
+
+ if (LF_ISSET(H_ISDUP)) {
+ hcp->dpagep = p;
+ hcp->dpgno = pgno;
+ hcp->dndx = 0;
+ } else {
+ hcp->pagep = p;
+ hcp->pgno = pgno;
+ hcp->bndx = 0;
+ }
+
+ return (0);
+}
+
+/*
+ * __ham_lock_bucket --
+ * Get the lock on a particular bucket.
+ */
+static int
+__ham_lock_bucket(dbc, mode)
+ DBC *dbc;
+ db_lockmode_t mode;
+{
+ HASH_CURSOR *hcp;
+ int ret;
+
+ hcp = (HASH_CURSOR *)dbc->internal;
+ dbc->lock.pgno = (db_pgno_t)(hcp->bucket);
+ if (dbc->txn == NULL)
+ ret = lock_get(dbc->dbp->dbenv->lk_info, dbc->locker, 0,
+ &dbc->lock_dbt, mode, &hcp->lock);
+ else
+ ret = lock_tget(dbc->dbp->dbenv->lk_info, dbc->txn, 0,
+ &dbc->lock_dbt, mode, &hcp->lock);
+
+ return (ret < 0 ? EAGAIN : ret);
+}
+
+/*
+ * __ham_dpair --
+ * Delete a pair on a page, paying no attention to what the pair
+ * represents. The caller is responsible for freeing up duplicates
+ * or offpage entries that might be referenced by this pair.
+ *
+ * PUBLIC: void __ham_dpair __P((DB *, PAGE *, u_int32_t));
+ */
+void
+__ham_dpair(dbp, p, pndx)
+ DB *dbp;
+ PAGE *p;
+ u_int32_t pndx;
+{
+ db_indx_t delta, n;
+ u_int8_t *dest, *src;
+
+ /*
+ * Compute "delta", the amount we have to shift all of the
+ * offsets. To find the delta, we just need to calculate
+ * the size of the pair of elements we are removing.
+ */
+ delta = H_PAIRSIZE(p, dbp->pgsize, pndx);
+
+ /*
+ * The hard case: we want to remove something other than
+ * the last item on the page. We need to shift data and
+ * offsets down.
+ */
+ if ((db_indx_t)pndx != H_NUMPAIRS(p) - 1) {
+ /*
+ * Move the data: src is the first occupied byte on
+ * the page. (Length is delta.)
+ */
+ src = (u_int8_t *)p + HOFFSET(p);
+
+ /*
+ * Destination is delta bytes beyond src. This might
+ * be an overlapping copy, so we have to use memmove.
+ */
+ dest = src + delta;
+ memmove(dest, src, p->inp[H_DATAINDEX(pndx)] - HOFFSET(p));
+ }
+
+ /* Adjust the offsets. */
+ for (n = (db_indx_t)pndx; n < (db_indx_t)(H_NUMPAIRS(p) - 1); n++) {
+ p->inp[H_KEYINDEX(n)] = p->inp[H_KEYINDEX(n+1)] + delta;
+ p->inp[H_DATAINDEX(n)] = p->inp[H_DATAINDEX(n+1)] + delta;
+ }
+
+ /* Adjust page metadata. */
+ HOFFSET(p) = HOFFSET(p) + delta;
+ NUM_ENT(p) = NUM_ENT(p) - 2;
+}
+
diff --git a/usr/src/cmd/sendmail/db/hash/hash_rec.c b/usr/src/cmd/sendmail/db/hash/hash_rec.c
new file mode 100644
index 0000000000..0419e9c991
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/hash/hash_rec.c
@@ -0,0 +1,977 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+/*
+ * Copyright (c) 1995, 1996
+ * Margo Seltzer. All rights reserved.
+ */
+/*
+ * Copyright (c) 1995, 1996
+ * The President and Fellows of Harvard University. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Margo Seltzer.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)hash_rec.c 10.22 (Sleepycat) 10/21/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <errno.h>
+#include <string.h>
+#endif
+
+#include "db_int.h"
+#include "shqueue.h"
+#include "db_page.h"
+#include "hash.h"
+#include "btree.h"
+#include "log.h"
+#include "common_ext.h"
+
+/*
+ * __ham_insdel_recover --
+ *
+ * PUBLIC: int __ham_insdel_recover
+ * PUBLIC: __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+ */
+int
+__ham_insdel_recover(logp, dbtp, lsnp, redo, info)
+ DB_LOG *logp;
+ DBT *dbtp;
+ DB_LSN *lsnp;
+ int redo;
+ void *info;
+{
+ __ham_insdel_args *argp;
+ DB *file_dbp;
+ DBC *dbc;
+ HASH_CURSOR *hcp;
+ DB_MPOOLFILE *mpf;
+ PAGE *pagep;
+ u_int32_t op;
+ int cmp_n, cmp_p, getmeta, ret;
+
+ getmeta = 0;
+ hcp = NULL;
+ REC_PRINT(__ham_insdel_print);
+ REC_INTRO(__ham_insdel_read);
+ hcp = (HASH_CURSOR *)dbc->internal;
+
+ ret = memp_fget(mpf, &argp->pgno, 0, &pagep);
+ if (ret != 0)
+ if (!redo) {
+ /*
+ * We are undoing and the page doesn't exist. That
+ * is equivalent to having a pagelsn of 0, so we
+ * would not have to undo anything. In this case,
+ * don't bother creating a page.
+ */
+ goto done;
+ } else if ((ret = memp_fget(mpf, &argp->pgno,
+ DB_MPOOL_CREATE, &pagep)) != 0)
+ goto out;
+
+
+ GET_META(file_dbp, hcp, ret);
+ if (ret != 0)
+ goto out;
+ getmeta = 1;
+
+ cmp_n = log_compare(lsnp, &LSN(pagep));
+ cmp_p = log_compare(&LSN(pagep), &argp->pagelsn);
+ /*
+ * Two possible things going on:
+ * redo a delete/undo a put: delete the item from the page.
+ * redo a put/undo a delete: add the item to the page.
+ * If we are undoing a delete, then the information logged is the
+ * entire entry off the page, not just the data of a dbt. In
+ * this case, we want to copy it back onto the page verbatim.
+ * We do this by calling __putitem with the type H_OFFPAGE instead
+ * of H_KEYDATA.
+ */
+ op = OPCODE_OF(argp->opcode);
+
+ if ((op == DELPAIR && cmp_n == 0 && !redo) ||
+ (op == PUTPAIR && cmp_p == 0 && redo)) {
+ /*
+ * Need to redo a PUT or undo a delete. If we are undoing a
+ * delete, we've got to restore the item back to its original
+ * position. That's a royal pain in the butt (because we do
+ * not store item lengths on the page), but there's no choice.
+ */
+ if (op != DELPAIR ||
+ argp->ndx == (u_int32_t)H_NUMPAIRS(pagep)) {
+ __ham_putitem(pagep, &argp->key,
+ !redo || PAIR_ISKEYBIG(argp->opcode) ?
+ H_OFFPAGE : H_KEYDATA);
+ __ham_putitem(pagep, &argp->data,
+ !redo || PAIR_ISDATABIG(argp->opcode) ?
+ H_OFFPAGE : H_KEYDATA);
+ } else
+ (void) __ham_reputpair(pagep, hcp->hdr->pagesize,
+ argp->ndx, &argp->key, &argp->data);
+
+ LSN(pagep) = redo ? *lsnp : argp->pagelsn;
+ if ((ret = __ham_put_page(file_dbp, pagep, 1)) != 0)
+ goto out;
+
+ } else if ((op == DELPAIR && cmp_p == 0 && redo)
+ || (op == PUTPAIR && cmp_n == 0 && !redo)) {
+ /* Need to undo a put or redo a delete. */
+ __ham_dpair(file_dbp, pagep, argp->ndx);
+ LSN(pagep) = redo ? *lsnp : argp->pagelsn;
+ if ((ret = __ham_put_page(file_dbp, (PAGE *)pagep, 1)) != 0)
+ goto out;
+ } else
+ if ((ret = __ham_put_page(file_dbp, (PAGE *)pagep, 0)) != 0)
+ goto out;
+
+ /* Return the previous LSN. */
+done: *lsnp = argp->prev_lsn;
+ ret = 0;
+
+out: if (getmeta)
+ RELEASE_META(file_dbp, hcp);
+ REC_CLOSE;
+}
+
+/*
+ * __ham_newpage_recover --
+ * This log message is used when we add/remove overflow pages. This
+ * message takes care of the pointer chains, not the data on the pages.
+ *
+ * PUBLIC: int __ham_newpage_recover
+ * PUBLIC: __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+ */
+int
+__ham_newpage_recover(logp, dbtp, lsnp, redo, info)
+ DB_LOG *logp;
+ DBT *dbtp;
+ DB_LSN *lsnp;
+ int redo;
+ void *info;
+{
+ __ham_newpage_args *argp;
+ DB *file_dbp;
+ DBC *dbc;
+ HASH_CURSOR *hcp;
+ DB_MPOOLFILE *mpf;
+ PAGE *pagep;
+ int cmp_n, cmp_p, change, getmeta, ret;
+
+ getmeta = 0;
+ hcp = NULL;
+ REC_PRINT(__ham_newpage_print);
+ REC_INTRO(__ham_newpage_read);
+ hcp = (HASH_CURSOR *)dbc->internal;
+
+ ret = memp_fget(mpf, &argp->new_pgno, 0, &pagep);
+ if (ret != 0)
+ if (!redo) {
+ /*
+ * We are undoing and the page doesn't exist. That
+ * is equivalent to having a pagelsn of 0, so we
+ * would not have to undo anything. In this case,
+ * don't bother creating a page.
+ */
+ ret = 0;
+ goto ppage;
+ } else if ((ret = memp_fget(mpf, &argp->new_pgno,
+ DB_MPOOL_CREATE, &pagep)) != 0)
+ goto out;
+
+ GET_META(file_dbp, (HASH_CURSOR *)dbc->internal, ret);
+ if (ret != 0)
+ goto out;
+ getmeta = 1;
+
+ /*
+ * There are potentially three pages we need to check: the one
+ * that we created/deleted, the one before it and the one after
+ * it.
+ */
+
+ cmp_n = log_compare(lsnp, &LSN(pagep));
+ cmp_p = log_compare(&LSN(pagep), &argp->pagelsn);
+ change = 0;
+
+ if ((cmp_p == 0 && redo && argp->opcode == PUTOVFL) ||
+ (cmp_n == 0 && !redo && argp->opcode == DELOVFL)) {
+ /* Redo a create new page or undo a delete new page. */
+ P_INIT(pagep, file_dbp->pgsize, argp->new_pgno,
+ argp->prev_pgno, argp->next_pgno, 0, P_HASH);
+ change = 1;
+ } else if ((cmp_p == 0 && redo && argp->opcode == DELOVFL) ||
+ (cmp_n == 0 && !redo && argp->opcode == PUTOVFL)) {
+ /*
+ * Redo a delete or undo a create new page. All we
+ * really need to do is change the LSN.
+ */
+ change = 1;
+ }
+
+ if (!change) {
+ if ((ret = __ham_put_page(file_dbp, (PAGE *)pagep, 0)) != 0)
+ goto out;
+ } else {
+ LSN(pagep) = redo ? *lsnp : argp->pagelsn;
+ if ((ret = __ham_put_page(file_dbp, (PAGE *)pagep, 1)) != 0)
+ goto out;
+ }
+
+ /* Now do the prev page. */
+ppage: if (argp->prev_pgno != PGNO_INVALID) {
+ ret = memp_fget(mpf, &argp->prev_pgno, 0, &pagep);
+
+ if (ret != 0)
+ if (!redo) {
+ /*
+ * We are undoing and the page doesn't exist.
+ * That is equivalent to having a pagelsn of 0,
+ * so we would not have to undo anything. In
+ * this case, don't bother creating a page.
+ */
+ ret = 0;
+ goto npage;
+ } else if ((ret =
+ memp_fget(mpf, &argp->prev_pgno,
+ DB_MPOOL_CREATE, &pagep)) != 0)
+ goto out;
+
+ cmp_n = log_compare(lsnp, &LSN(pagep));
+ cmp_p = log_compare(&LSN(pagep), &argp->prevlsn);
+ change = 0;
+
+ if ((cmp_p == 0 && redo && argp->opcode == PUTOVFL) ||
+ (cmp_n == 0 && !redo && argp->opcode == DELOVFL)) {
+ /* Redo a create new page or undo a delete new page. */
+ pagep->next_pgno = argp->new_pgno;
+ change = 1;
+ } else if ((cmp_p == 0 && redo && argp->opcode == DELOVFL) ||
+ (cmp_n == 0 && !redo && argp->opcode == PUTOVFL)) {
+ /* Redo a delete or undo a create new page. */
+ pagep->next_pgno = argp->next_pgno;
+ change = 1;
+ }
+
+ if (!change) {
+ if ((ret =
+ __ham_put_page(file_dbp, (PAGE *)pagep, 0)) != 0)
+ goto out;
+ } else {
+ LSN(pagep) = redo ? *lsnp : argp->prevlsn;
+ if ((ret =
+ __ham_put_page(file_dbp, (PAGE *)pagep, 1)) != 0)
+ goto out;
+ }
+ }
+
+ /* Now time to do the next page */
+npage: if (argp->next_pgno != PGNO_INVALID) {
+ ret = memp_fget(mpf, &argp->next_pgno, 0, &pagep);
+
+ if (ret != 0)
+ if (!redo) {
+ /*
+ * We are undoing and the page doesn't exist.
+ * That is equivalent to having a pagelsn of 0,
+ * so we would not have to undo anything. In
+ * this case, don't bother creating a page.
+ */
+ goto done;
+ } else if ((ret =
+ memp_fget(mpf, &argp->next_pgno,
+ DB_MPOOL_CREATE, &pagep)) != 0)
+ goto out;
+
+ cmp_n = log_compare(lsnp, &LSN(pagep));
+ cmp_p = log_compare(&LSN(pagep), &argp->nextlsn);
+ change = 0;
+
+ if ((cmp_p == 0 && redo && argp->opcode == PUTOVFL) ||
+ (cmp_n == 0 && !redo && argp->opcode == DELOVFL)) {
+ /* Redo a create new page or undo a delete new page. */
+ pagep->prev_pgno = argp->new_pgno;
+ change = 1;
+ } else if ((cmp_p == 0 && redo && argp->opcode == DELOVFL) ||
+ (cmp_n == 0 && !redo && argp->opcode == PUTOVFL)) {
+ /* Redo a delete or undo a create new page. */
+ pagep->prev_pgno = argp->prev_pgno;
+ change = 1;
+ }
+
+ if (!change) {
+ if ((ret =
+ __ham_put_page(file_dbp, (PAGE *)pagep, 0)) != 0)
+ goto out;
+ } else {
+ LSN(pagep) = redo ? *lsnp : argp->nextlsn;
+ if ((ret =
+ __ham_put_page(file_dbp, (PAGE *)pagep, 1)) != 0)
+ goto out;
+ }
+ }
+done: *lsnp = argp->prev_lsn;
+ ret = 0;
+
+out: if (getmeta)
+ RELEASE_META(file_dbp, hcp);
+ REC_CLOSE;
+}
+
+
+/*
+ * __ham_replace_recover --
+ * This log message refers to partial puts that are local to a single
+ * page. You can think of them as special cases of the more general
+ * insdel log message.
+ *
+ * PUBLIC: int __ham_replace_recover
+ * PUBLIC: __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+ */
+int
+__ham_replace_recover(logp, dbtp, lsnp, redo, info)
+ DB_LOG *logp;
+ DBT *dbtp;
+ DB_LSN *lsnp;
+ int redo;
+ void *info;
+{
+ __ham_replace_args *argp;
+ DB *file_dbp;
+ DBC *dbc;
+ HASH_CURSOR *hcp;
+ DB_MPOOLFILE *mpf;
+ DBT dbt;
+ PAGE *pagep;
+ int32_t grow;
+ int change, cmp_n, cmp_p, getmeta, ret;
+ u_int8_t *hk;
+
+ getmeta = 0;
+ hcp = NULL;
+ REC_PRINT(__ham_replace_print);
+ REC_INTRO(__ham_replace_read);
+ hcp = (HASH_CURSOR *)dbc->internal;
+
+ ret = memp_fget(mpf, &argp->pgno, 0, &pagep);
+ if (ret != 0)
+ if (!redo) {
+ /*
+ * We are undoing and the page doesn't exist. That
+ * is equivalent to having a pagelsn of 0, so we
+ * would not have to undo anything. In this case,
+ * don't bother creating a page.
+ */
+ goto done;
+ } else if ((ret = memp_fget(mpf, &argp->pgno,
+ DB_MPOOL_CREATE, &pagep)) != 0)
+ goto out;
+
+ GET_META(file_dbp, (HASH_CURSOR *)dbc->internal, ret);
+ if (ret != 0)
+ goto out;
+ getmeta = 1;
+
+ cmp_n = log_compare(lsnp, &LSN(pagep));
+ cmp_p = log_compare(&LSN(pagep), &argp->pagelsn);
+
+ if (cmp_p == 0 && redo) {
+ change = 1;
+ /* Reapply the change as specified. */
+ dbt.data = argp->newitem.data;
+ dbt.size = argp->newitem.size;
+ grow = argp->newitem.size - argp->olditem.size;
+ LSN(pagep) = *lsnp;
+ } else if (cmp_n == 0 && !redo) {
+ change = 1;
+ /* Undo the already applied change. */
+ dbt.data = argp->olditem.data;
+ dbt.size = argp->olditem.size;
+ grow = argp->olditem.size - argp->newitem.size;
+ LSN(pagep) = argp->pagelsn;
+ } else {
+ change = 0;
+ grow = 0;
+ }
+
+ if (change) {
+ __ham_onpage_replace(pagep,
+ file_dbp->pgsize, argp->ndx, argp->off, grow, &dbt);
+ if (argp->makedup) {
+ hk = P_ENTRY(pagep, argp->ndx);
+ if (redo)
+ HPAGE_PTYPE(hk) = H_DUPLICATE;
+ else
+ HPAGE_PTYPE(hk) = H_KEYDATA;
+ }
+ }
+
+ if ((ret = __ham_put_page(file_dbp, pagep, change)) != 0)
+ goto out;
+
+done: *lsnp = argp->prev_lsn;
+ ret = 0;
+
+out: if (getmeta)
+ RELEASE_META(file_dbp, hcp);
+ REC_CLOSE;
+}
+
+/*
+ * __ham_newpgno_recover --
+ * This log message is used when allocating or deleting an overflow
+ * page. It takes care of modifying the meta data.
+ *
+ * PUBLIC: int __ham_newpgno_recover
+ * PUBLIC: __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+ */
+int
+__ham_newpgno_recover(logp, dbtp, lsnp, redo, info)
+ DB_LOG *logp;
+ DBT *dbtp;
+ DB_LSN *lsnp;
+ int redo;
+ void *info;
+{
+ __ham_newpgno_args *argp;
+ DB *file_dbp;
+ DBC *dbc;
+ HASH_CURSOR *hcp;
+ DB_MPOOLFILE *mpf;
+ PAGE *pagep;
+ int change, cmp_n, cmp_p, getmeta, ret;
+
+ getmeta = 0;
+ hcp = NULL;
+ REC_PRINT(__ham_newpgno_print);
+ REC_INTRO(__ham_newpgno_read);
+ hcp = (HASH_CURSOR *)dbc->internal;
+
+ GET_META(file_dbp, (HASH_CURSOR *)dbc->internal, ret);
+ if (ret != 0)
+ goto out;
+ getmeta = 1;
+
+ /*
+ * There are two phases to the recovery here. First we need
+ * to update the meta data; then we need to update the page.
+ * We'll do the meta-data first.
+ */
+ cmp_n = log_compare(lsnp, &hcp->hdr->lsn);
+ cmp_p = log_compare(&hcp->hdr->lsn, &argp->metalsn);
+
+ change = 0;
+ if ((cmp_p == 0 && redo && argp->opcode == ALLOCPGNO) ||
+ (cmp_n == 0 && !redo && argp->opcode == DELPGNO)) {
+ /* Need to redo an allocation or undo a deletion. */
+ hcp->hdr->last_freed = argp->free_pgno;
+ if (redo && argp->old_pgno != 0) /* Must be ALLOCPGNO */
+ hcp->hdr->spares[hcp->hdr->ovfl_point]++;
+ change = 1;
+ } else if (cmp_p == 0 && redo && argp->opcode == DELPGNO) {
+ /* Need to redo a deletion */
+ hcp->hdr->last_freed = argp->pgno;
+ change = 1;
+ } else if (cmp_n == 0 && !redo && argp->opcode == ALLOCPGNO) {
+ /* undo an allocation. */
+ if (argp->old_pgno == 0)
+ hcp->hdr->last_freed = argp->pgno;
+ else {
+ hcp->hdr->spares[hcp->hdr->ovfl_point]--;
+ hcp->hdr->last_freed = 0;
+ }
+ change = 1;
+ }
+ if (change) {
+ hcp->hdr->lsn = redo ? *lsnp : argp->metalsn;
+ F_SET(hcp, H_DIRTY);
+ }
+
+
+ /* Now check the newly allocated/freed page. */
+ ret = memp_fget(mpf, &argp->pgno, 0, &pagep);
+
+ if (ret != 0)
+ if (!redo) {
+ /*
+ * We are undoing and the page doesn't exist. That
+ * is equivalent to having a pagelsn of 0, so we
+ * would not have to undo anything. In this case,
+ * don't bother creating a page.
+ */
+ goto done;
+ } else if ((ret = memp_fget(mpf, &argp->pgno,
+ DB_MPOOL_CREATE, &pagep)) != 0)
+ goto out;
+
+ cmp_n = log_compare(lsnp, &LSN(pagep));
+ cmp_p = log_compare(&LSN(pagep), &argp->pagelsn);
+
+ change = 0;
+ if (cmp_p == 0 && redo && argp->opcode == ALLOCPGNO) {
+ /* Need to redo an allocation. */
+ P_INIT(pagep, file_dbp->pgsize, argp->pgno, PGNO_INVALID,
+ PGNO_INVALID, 0, argp->new_type);
+ change = 1;
+ } else if (cmp_n == 0 && !redo && argp->opcode == DELPGNO) {
+ /* Undoing a delete. */
+ P_INIT(pagep, file_dbp->pgsize, argp->pgno, PGNO_INVALID,
+ argp->old_pgno, 0, argp->old_type);
+ change = 1;
+ } else if ((cmp_p == 0 && redo && argp->opcode == DELPGNO) ||
+ (cmp_n == 0 && !redo && argp->opcode == ALLOCPGNO)) {
+ /* Need to redo a deletion or undo an allocation. */
+ NEXT_PGNO(pagep) = argp->free_pgno;
+ TYPE(pagep) = P_INVALID;
+ change = 1;
+ }
+ if (change)
+ LSN(pagep) = redo ? *lsnp : argp->pagelsn;
+
+ if ((ret = __ham_put_page(file_dbp, pagep, change)) != 0)
+ goto out;
+
+done: *lsnp = argp->prev_lsn;
+ ret = 0;
+
+out: if (getmeta)
+ RELEASE_META(file_dbp, hcp);
+ REC_CLOSE;
+
+}
+
+/*
+ * __ham_splitmeta_recover --
+ * This is the meta-data part of the split. Records the new and old
+ * bucket numbers and the new/old mask information.
+ *
+ * PUBLIC: int __ham_splitmeta_recover
+ * PUBLIC: __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+ */
+int
+__ham_splitmeta_recover(logp, dbtp, lsnp, redo, info)
+ DB_LOG *logp;
+ DBT *dbtp;
+ DB_LSN *lsnp;
+ int redo;
+ void *info;
+{
+ __ham_splitmeta_args *argp;
+ DB *file_dbp;
+ DBC *dbc;
+ HASH_CURSOR *hcp;
+ DB_MPOOLFILE *mpf;
+ int change, cmp_n, cmp_p, getmeta, ret;
+ u_int32_t pow;
+
+ getmeta = 0;
+ hcp = NULL;
+ REC_PRINT(__ham_splitmeta_print);
+ REC_INTRO(__ham_splitmeta_read);
+ hcp = (HASH_CURSOR *)dbc->internal;
+
+ GET_META(file_dbp, (HASH_CURSOR *)dbc->internal, ret);
+ if (ret != 0)
+ goto out;
+ getmeta = 1;
+
+ /*
+ * There are two phases to the recovery here. First we need
+ * to update the meta data; then we need to update the page.
+ * We'll do the meta-data first.
+ */
+ cmp_n = log_compare(lsnp, &hcp->hdr->lsn);
+ cmp_p = log_compare(&hcp->hdr->lsn, &argp->metalsn);
+
+ change = 0;
+ if (cmp_p == 0 && redo) {
+ /* Need to redo the split information. */
+ hcp->hdr->max_bucket = argp->bucket + 1;
+ pow = __db_log2(hcp->hdr->max_bucket + 1);
+ if (pow > hcp->hdr->ovfl_point) {
+ hcp->hdr->spares[pow] =
+ hcp->hdr->spares[hcp->hdr->ovfl_point];
+ hcp->hdr->ovfl_point = pow;
+ }
+ if (hcp->hdr->max_bucket > hcp->hdr->high_mask) {
+ hcp->hdr->low_mask = hcp->hdr->high_mask;
+ hcp->hdr->high_mask =
+ hcp->hdr->max_bucket | hcp->hdr->low_mask;
+ }
+ change = 1;
+ } else if (cmp_n == 0 && !redo) {
+ /* Need to undo the split information. */
+ hcp->hdr->max_bucket = argp->bucket;
+ hcp->hdr->ovfl_point = argp->ovflpoint;
+ hcp->hdr->spares[hcp->hdr->ovfl_point] = argp->spares;
+ pow = 1 << __db_log2(hcp->hdr->max_bucket + 1);
+ hcp->hdr->high_mask = pow - 1;
+ hcp->hdr->low_mask = (pow >> 1) - 1;
+ change = 1;
+ }
+ if (change) {
+ hcp->hdr->lsn = redo ? *lsnp : argp->metalsn;
+ F_SET(hcp, H_DIRTY);
+ }
+
+done: *lsnp = argp->prev_lsn;
+ ret = 0;
+
+out: if (getmeta)
+ RELEASE_META(file_dbp, hcp);
+ REC_CLOSE;
+}
+
+/*
+ * __ham_splitdata_recover --
+ *
+ * PUBLIC: int __ham_splitdata_recover
+ * PUBLIC: __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+ */
+int
+__ham_splitdata_recover(logp, dbtp, lsnp, redo, info)
+ DB_LOG *logp;
+ DBT *dbtp;
+ DB_LSN *lsnp;
+ int redo;
+ void *info;
+{
+ __ham_splitdata_args *argp;
+ DB *file_dbp;
+ DBC *dbc;
+ HASH_CURSOR *hcp;
+ DB_MPOOLFILE *mpf;
+ PAGE *pagep;
+ int change, cmp_n, cmp_p, getmeta, ret;
+
+ getmeta = 0;
+ hcp = NULL;
+ REC_PRINT(__ham_splitdata_print);
+ REC_INTRO(__ham_splitdata_read);
+ hcp = (HASH_CURSOR *)dbc->internal;
+
+ ret = memp_fget(mpf, &argp->pgno, 0, &pagep);
+ if (ret != 0)
+ if (!redo) {
+ /*
+ * We are undoing and the page doesn't exist. That
+ * is equivalent to having a pagelsn of 0, so we
+ * would not have to undo anything. In this case,
+ * don't bother creating a page.
+ */
+ goto done;
+ } else if ((ret = memp_fget(mpf, &argp->pgno,
+ DB_MPOOL_CREATE, &pagep)) != 0)
+ goto out;
+
+ GET_META(file_dbp, (HASH_CURSOR *)dbc->internal, ret);
+ if (ret != 0)
+ goto out;
+ getmeta = 1;
+
+ cmp_n = log_compare(lsnp, &LSN(pagep));
+ cmp_p = log_compare(&LSN(pagep), &argp->pagelsn);
+
+ /*
+ * There are two types of log messages here, one for the old page
+ * and one for the new pages created. The original image in the
+ * SPLITOLD record is used for undo. The image in the SPLITNEW
+ * is used for redo. We should never have a case where there is
+ * a redo operation and the SPLITOLD record is on disk, but not
+ * the SPLITNEW record. Therefore, we only have work to do when
+ * redo NEW messages and undo OLD messages, but we have to update
+ * LSNs in both cases.
+ */
+ change = 0;
+ if (cmp_p == 0 && redo) {
+ if (argp->opcode == SPLITNEW)
+ /* Need to redo the split described. */
+ memcpy(pagep, argp->pageimage.data,
+ argp->pageimage.size);
+ LSN(pagep) = *lsnp;
+ change = 1;
+ } else if (cmp_n == 0 && !redo) {
+ if (argp->opcode == SPLITOLD) {
+ /* Put back the old image. */
+ memcpy(pagep, argp->pageimage.data,
+ argp->pageimage.size);
+ } else
+ P_INIT(pagep, file_dbp->pgsize, argp->pgno,
+ PGNO_INVALID, PGNO_INVALID, 0, P_HASH);
+ LSN(pagep) = argp->pagelsn;
+ change = 1;
+ }
+ if ((ret = __ham_put_page(file_dbp, pagep, change)) != 0)
+ goto out;
+
+done: *lsnp = argp->prev_lsn;
+ ret = 0;
+
+out: if (getmeta)
+ RELEASE_META(file_dbp, hcp);
+ REC_CLOSE;
+}
+
+/*
+ * __ham_ovfl_recover --
+ * This message is generated when we initialize a set of overflow pages.
+ *
+ * PUBLIC: int __ham_ovfl_recover
+ * PUBLIC: __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+ */
+int
+__ham_ovfl_recover(logp, dbtp, lsnp, redo, info)
+ DB_LOG *logp;
+ DBT *dbtp;
+ DB_LSN *lsnp;
+ int redo;
+ void *info;
+{
+ __ham_ovfl_args *argp;
+ DB *file_dbp;
+ DBC *dbc;
+ HASH_CURSOR *hcp;
+ DB_MPOOLFILE *mpf;
+ PAGE *pagep;
+ db_pgno_t max_pgno, pgno;
+ int cmp_n, cmp_p, getmeta, ret;
+
+ getmeta = 0;
+ hcp = NULL;
+ REC_PRINT(__ham_ovfl_print);
+ REC_INTRO(__ham_ovfl_read);
+ hcp = (HASH_CURSOR *)dbc->internal;
+
+ GET_META(file_dbp, (HASH_CURSOR *)dbc->internal, ret);
+ if (ret != 0)
+ goto out;
+ getmeta = 1;
+
+ cmp_n = log_compare(lsnp, &hcp->hdr->lsn);
+ cmp_p = log_compare(&hcp->hdr->lsn, &argp->metalsn);
+
+ if (cmp_p == 0 && redo) {
+ /* Redo the allocation. */
+ hcp->hdr->last_freed = argp->start_pgno;
+ hcp->hdr->spares[argp->ovflpoint] += argp->npages;
+ hcp->hdr->lsn = *lsnp;
+ F_SET(hcp, H_DIRTY);
+ } else if (cmp_n == 0 && !redo) {
+ hcp->hdr->last_freed = argp->free_pgno;
+ hcp->hdr->spares[argp->ovflpoint] -= argp->npages;
+ hcp->hdr->lsn = argp->metalsn;
+ F_SET(hcp, H_DIRTY);
+ }
+
+ max_pgno = argp->start_pgno + argp->npages - 1;
+ ret = 0;
+ for (pgno = argp->start_pgno; pgno <= max_pgno; pgno++) {
+ if ((ret = memp_fget(mpf, &pgno, 0, &pagep)) != 0) {
+ if (!redo) {
+ ret = 0;
+ continue;
+ }
+ if ((ret = memp_fget(mpf,
+ &pgno, DB_MPOOL_CREATE, &pagep)) != 0)
+ goto out;
+ }
+ if (redo && log_compare((const DB_LSN *)lsnp,
+ (const DB_LSN *)&LSN(pagep)) > 0) {
+ P_INIT(pagep, file_dbp->pgsize, pgno, PGNO_INVALID,
+ pgno == max_pgno ? argp->free_pgno : pgno + 1,
+ 0, P_HASH);
+ LSN(pagep) = *lsnp;
+ ret = __ham_put_page(file_dbp, pagep, 1);
+ } else if (!redo) {
+ ZERO_LSN(pagep->lsn);
+ ret = __ham_put_page(file_dbp, pagep, 1);
+ } else
+ ret = __ham_put_page(file_dbp, pagep, 0);
+ if (ret)
+ goto out;
+ }
+
+done: *lsnp = argp->prev_lsn;
+ ret = 0;
+
+out: if (getmeta)
+ RELEASE_META(file_dbp, hcp);
+ REC_CLOSE;
+}
+
+/*
+ * __ham_copypage_recover --
+ * Recovery function for copypage.
+ *
+ * PUBLIC: int __ham_copypage_recover
+ * PUBLIC: __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+ */
+int
+__ham_copypage_recover(logp, dbtp, lsnp, redo, info)
+ DB_LOG *logp;
+ DBT *dbtp;
+ DB_LSN *lsnp;
+ int redo;
+ void *info;
+{
+ __ham_copypage_args *argp;
+ DB *file_dbp;
+ DBC *dbc;
+ HASH_CURSOR *hcp;
+ DB_MPOOLFILE *mpf;
+ PAGE *pagep;
+ int cmp_n, cmp_p, getmeta, modified, ret;
+
+ getmeta = 0;
+ hcp = NULL;
+ REC_PRINT(__ham_copypage_print);
+ REC_INTRO(__ham_copypage_read);
+ hcp = (HASH_CURSOR *)dbc->internal;
+
+ GET_META(file_dbp, (HASH_CURSOR *)dbc->internal, ret);
+ if (ret != 0)
+ goto out;
+ getmeta = 1;
+ modified = 0;
+
+ /* This is the bucket page. */
+ ret = memp_fget(mpf, &argp->pgno, 0, &pagep);
+ if (ret != 0)
+ if (!redo) {
+ /*
+ * We are undoing and the page doesn't exist. That
+ * is equivalent to having a pagelsn of 0, so we
+ * would not have to undo anything. In this case,
+ * don't bother creating a page.
+ */
+ ret = 0;
+ goto donext;
+ } else if ((ret = memp_fget(mpf, &argp->pgno,
+ DB_MPOOL_CREATE, &pagep)) != 0)
+ goto out;
+
+ cmp_n = log_compare(lsnp, &LSN(pagep));
+ cmp_p = log_compare(&LSN(pagep), &argp->pagelsn);
+
+ if (cmp_p == 0 && redo) {
+ /* Need to redo update described. */
+ memcpy(pagep, argp->page.data, argp->page.size);
+ LSN(pagep) = *lsnp;
+ modified = 1;
+ } else if (cmp_n == 0 && !redo) {
+ /* Need to undo update described. */
+ P_INIT(pagep, hcp->hdr->pagesize, argp->pgno, PGNO_INVALID,
+ argp->next_pgno, 0, P_HASH);
+ LSN(pagep) = argp->pagelsn;
+ modified = 1;
+ }
+ if ((ret = memp_fput(mpf, pagep, modified ? DB_MPOOL_DIRTY : 0)) != 0)
+ goto out;
+
+ /* Now fix up the "next" page. */
+donext: ret = memp_fget(mpf, &argp->next_pgno, 0, &pagep);
+ if (ret != 0)
+ if (!redo) {
+ /*
+ * We are undoing and the page doesn't exist. That
+ * is equivalent to having a pagelsn of 0, so we
+ * would not have to undo anything. In this case,
+ * don't bother creating a page.
+ */
+ ret = 0;
+ goto do_nn;
+ } else if ((ret = memp_fget(mpf, &argp->next_pgno,
+ DB_MPOOL_CREATE, &pagep)) != 0)
+ goto out;
+
+ /* There is nothing to do in the REDO case; only UNDO. */
+
+ cmp_n = log_compare(lsnp, &LSN(pagep));
+ if (cmp_n == 0 && !redo) {
+ /* Need to undo update described. */
+ memcpy(pagep, argp->page.data, argp->page.size);
+ modified = 1;
+ }
+ if ((ret = memp_fput(mpf, pagep, modified ? DB_MPOOL_DIRTY : 0)) != 0)
+ goto out;
+
+ /* Now fix up the next's next page. */
+do_nn: if (argp->nnext_pgno == PGNO_INVALID)
+ goto done;
+
+ ret = memp_fget(mpf, &argp->nnext_pgno, 0, &pagep);
+ if (ret != 0)
+ if (!redo) {
+ /*
+ * We are undoing and the page doesn't exist. That
+ * is equivalent to having a pagelsn of 0, so we
+ * would not have to undo anything. In this case,
+ * don't bother creating a page.
+ */
+ goto done;
+ } else if ((ret = memp_fget(mpf, &argp->nnext_pgno,
+ DB_MPOOL_CREATE, &pagep)) != 0)
+ goto out;
+
+ cmp_n = log_compare(lsnp, &LSN(pagep));
+ cmp_p = log_compare(&LSN(pagep), &argp->nnextlsn);
+
+ if (cmp_p == 0 && redo) {
+ /* Need to redo update described. */
+ PREV_PGNO(pagep) = argp->pgno;
+ LSN(pagep) = *lsnp;
+ modified = 1;
+ } else if (cmp_n == 0 && !redo) {
+ /* Need to undo update described. */
+ PREV_PGNO(pagep) = argp->next_pgno;
+ LSN(pagep) = argp->nnextlsn;
+ modified = 1;
+ }
+ if ((ret = memp_fput(mpf, pagep, modified ? DB_MPOOL_DIRTY : 0)) != 0)
+ goto out;
+
+done: *lsnp = argp->prev_lsn;
+ ret = 0;
+
+out: if (getmeta)
+ RELEASE_META(file_dbp, hcp);
+ REC_CLOSE;
+}
diff --git a/usr/src/cmd/sendmail/db/hash/hash_stat.c b/usr/src/cmd/sendmail/db/hash/hash_stat.c
new file mode 100644
index 0000000000..1b493d5f40
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/hash/hash_stat.c
@@ -0,0 +1,44 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)hash_stat.c 10.12 (Sleepycat) 12/19/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <errno.h>
+#endif
+
+#include "db_int.h"
+#include "db_page.h"
+#include "hash.h"
+
+/*
+ * __ham_stat --
+ * Gather/print the hash statistics
+ *
+ * PUBLIC: int __ham_stat __P((DB *, void *, void *(*)(size_t), u_int32_t));
+ */
+int
+__ham_stat(dbp, spp, db_malloc, flags)
+ DB *dbp;
+ void *spp;
+ void *(*db_malloc) __P((size_t));
+ u_int32_t flags;
+{
+ COMPQUIET(spp, NULL);
+ COMPQUIET(db_malloc, NULL);
+ COMPQUIET(flags, 0);
+
+ DB_PANIC_CHECK(dbp);
+
+ return (__db_eopnotsup(dbp->dbenv));
+}
diff --git a/usr/src/cmd/sendmail/db/hsearch/hsearch.c b/usr/src/cmd/sendmail/db/hsearch/hsearch.c
new file mode 100644
index 0000000000..c375a88fd6
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/hsearch/hsearch.c
@@ -0,0 +1,142 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+/*
+ * Copyright (c) 1990, 1993
+ * Margo Seltzer. All rights reserved.
+ */
+/*
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Margo Seltzer.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)hsearch.c 10.9 (Sleepycat) 4/18/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <errno.h>
+#include <string.h>
+#endif
+
+#define DB_DBM_HSEARCH 1
+#include "db_int.h"
+
+static DB *dbp;
+static ENTRY retval;
+
+int
+__db_hcreate(nel)
+ size_t nel;
+{
+ DB_INFO dbinfo;
+
+ memset(&dbinfo, 0, sizeof(dbinfo));
+ dbinfo.db_pagesize = 512;
+ dbinfo.h_ffactor = 16;
+ dbinfo.h_nelem = (u_int32_t)nel; /* XXX: Possible overflow. */
+
+ errno = db_open(NULL,
+ DB_HASH, DB_CREATE, __db_omode("rw----"), NULL, &dbinfo, &dbp);
+ return (errno == 0 ? 1 : 0);
+}
+
+ENTRY *
+__db_hsearch(item, action)
+ ENTRY item;
+ ACTION action;
+{
+ DBT key, val;
+
+ if (dbp == NULL) {
+ errno = EINVAL;
+ return (NULL);
+ }
+ memset(&key, 0, sizeof(key));
+ memset(&val, 0, sizeof(val));
+ key.data = item.key;
+ key.size = strlen(item.key) + 1;
+
+ switch (action) {
+ case ENTER:
+ val.data = item.data;
+ val.size = strlen(item.data) + 1;
+
+ /*
+ * Try and add the key to the database. If we fail because
+ * the key already exists, return the existing key.
+ */
+ if ((errno =
+ dbp->put(dbp, NULL, &key, &val, DB_NOOVERWRITE)) == 0)
+ break;
+ if (errno != DB_KEYEXIST)
+ return (NULL);
+ if ((errno = dbp->get(dbp, NULL, &key, &val, 0)) == 0)
+ break;
+
+ if (errno == DB_NOTFOUND) /* XXX: can't happen. */
+ errno = EINVAL;
+ break;
+ case FIND:
+ if ((errno = dbp->get(dbp, NULL, &key, &val, 0)) != 0) {
+ if (errno == DB_NOTFOUND)
+ errno = 0;
+ return (NULL);
+ }
+ item.data = (char *)val.data;
+ break;
+ default:
+ errno = EINVAL;
+ return (NULL);
+ }
+ retval.key = item.key;
+ retval.data = item.data;
+ return (&retval);
+}
+
+void
+__db_hdestroy()
+{
+ if (dbp != NULL) {
+ (void)dbp->close(dbp, 0);
+ dbp = NULL;
+ }
+}
diff --git a/usr/src/cmd/sendmail/db/include/btree.h b/usr/src/cmd/sendmail/db/include/btree.h
new file mode 100644
index 0000000000..b0c04b1508
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/include/btree.h
@@ -0,0 +1,263 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+/*
+ * Copyright (c) 1990, 1993, 1994, 1995, 1996
+ * Keith Bostic. All rights reserved.
+ */
+/*
+ * Copyright (c) 1990, 1993, 1994, 1995
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Mike Olson.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)btree.h 10.26 (Sleepycat) 12/16/98
+ */
+
+/* Forward structure declarations. */
+struct __btree; typedef struct __btree BTREE;
+struct __cursor; typedef struct __cursor CURSOR;
+struct __epg; typedef struct __epg EPG;
+struct __recno; typedef struct __recno RECNO;
+
+#define DEFMINKEYPAGE (2)
+
+#define ISINTERNAL(p) (TYPE(p) == P_IBTREE || TYPE(p) == P_IRECNO)
+#define ISLEAF(p) (TYPE(p) == P_LBTREE || TYPE(p) == P_LRECNO)
+
+/*
+ * If doing transactions we have to hold the locks associated with a data item
+ * from a page for the entire transaction. However, we don't have to hold the
+ * locks associated with walking the tree. Distinguish between the two so that
+ * we don't tie up the internal pages of the tree longer than necessary.
+ */
+#define __BT_LPUT(dbc, lock) \
+ (F_ISSET((dbc)->dbp, DB_AM_LOCKING) ? \
+ lock_put((dbc)->dbp->dbenv->lk_info, lock) : 0)
+#define __BT_TLPUT(dbc, lock) \
+ (F_ISSET((dbc)->dbp, DB_AM_LOCKING) && (dbc)->txn == NULL ? \
+ lock_put((dbc)->dbp->dbenv->lk_info, lock) : 0)
+
+/*
+ * Flags to __bam_search() and __bam_rsearch().
+ *
+ * Note, internal page searches must find the largest record less than key in
+ * the tree so that descents work. Leaf page searches must find the smallest
+ * record greater than key so that the returned index is the record's correct
+ * position for insertion.
+ *
+ * The flags parameter to the search routines describes three aspects of the
+ * search: the type of locking required (including if we're locking a pair of
+ * pages), the item to return in the presence of duplicates and whether or not
+ * to return deleted entries. To simplify both the mnemonic representation
+ * and the code that checks for various cases, we construct a set of bitmasks.
+ */
+#define S_READ 0x00001 /* Read locks. */
+#define S_WRITE 0x00002 /* Write locks. */
+
+#define S_APPEND 0x00040 /* Append to the tree. */
+#define S_DELNO 0x00080 /* Don't return deleted items. */
+#define S_DUPFIRST 0x00100 /* Return first duplicate. */
+#define S_DUPLAST 0x00200 /* Return last duplicate. */
+#define S_EXACT 0x00400 /* Exact items only. */
+#define S_PARENT 0x00800 /* Lock page pair. */
+#define S_STACK 0x01000 /* Need a complete stack. */
+#define S_PAST_EOF 0x02000 /* If doing insert search (or keyfirst
+ * or keylast operations), or a split
+ * on behalf of an insert, it's okay to
+ * return an entry one past end-of-page.
+ */
+
+#define S_DELETE (S_WRITE | S_DUPFIRST | S_DELNO | S_EXACT | S_STACK)
+#define S_FIND (S_READ | S_DUPFIRST | S_DELNO)
+#define S_FIND_WR (S_WRITE | S_DUPFIRST | S_DELNO)
+#define S_INSERT (S_WRITE | S_DUPLAST | S_PAST_EOF | S_STACK)
+#define S_KEYFIRST (S_WRITE | S_DUPFIRST | S_PAST_EOF | S_STACK)
+#define S_KEYLAST (S_WRITE | S_DUPLAST | S_PAST_EOF | S_STACK)
+#define S_WRPAIR (S_WRITE | S_DUPLAST | S_PAST_EOF | S_PARENT)
+
+/*
+ * Flags to __bam_iitem().
+ */
+#define BI_DELETED 0x01 /* Key/data pair only placeholder. */
+#define BI_DOINCR 0x02 /* Increment the record count. */
+#define BI_NEWKEY 0x04 /* New key. */
+
+/*
+ * Various routines pass around page references. A page reference can be a
+ * pointer to the page or a page number; for either, an indx can designate
+ * an item on the page.
+ */
+struct __epg {
+ PAGE *page; /* The page. */
+ db_indx_t indx; /* The index on the page. */
+ DB_LOCK lock; /* The page's lock. */
+};
+
+/*
+ * We maintain a stack of the pages that we're locking in the tree. Btree's
+ * (currently) only save two levels of the tree at a time, so the default
+ * stack is always large enough. Recno trees have to lock the entire tree to
+ * do inserts/deletes, however. Grow the stack as necessary.
+ */
+#define BT_STK_CLR(c) \
+ ((c)->csp = (c)->sp)
+
+#define BT_STK_ENTER(c, pagep, page_indx, lock, ret) do { \
+ if ((ret = \
+ (c)->csp == (c)->esp ? __bam_stkgrow(c) : 0) == 0) { \
+ (c)->csp->page = pagep; \
+ (c)->csp->indx = page_indx; \
+ (c)->csp->lock = lock; \
+ } \
+} while (0)
+
+#define BT_STK_PUSH(c, pagep, page_indx, lock, ret) do { \
+ BT_STK_ENTER(c, pagep, page_indx, lock, ret); \
+ ++(c)->csp; \
+} while (0)
+
+#define BT_STK_POP(c) \
+ ((c)->csp == (c)->stack ? NULL : --(c)->csp)
+
+/*
+ * Arguments passed to __bam_ca_replace().
+ */
+typedef enum {
+ REPLACE_SETUP,
+ REPLACE_SUCCESS,
+ REPLACE_FAILED
+} ca_replace_arg;
+
+/* Arguments passed to __ram_ca(). */
+typedef enum {
+ CA_DELETE,
+ CA_IAFTER,
+ CA_IBEFORE
+} ca_recno_arg;
+
+#define RECNO_OOB 0 /* Illegal record number. */
+
+/* Btree/Recno cursor. */
+struct __cursor {
+ DBC *dbc; /* Enclosing DBC. */
+
+ /* Per-thread information: shared by btree/recno. */
+ EPG *sp; /* Stack pointer. */
+ EPG *csp; /* Current stack entry. */
+ EPG *esp; /* End stack pointer. */
+ EPG stack[5];
+
+ /* Per-thread information: btree private. */
+ PAGE *page; /* Cursor page. */
+
+ db_pgno_t pgno; /* Page. */
+ db_indx_t indx; /* Page item ref'd by the cursor. */
+
+ db_pgno_t dpgno; /* Duplicate page. */
+ db_indx_t dindx; /* Page item ref'd by the cursor. */
+
+ DB_LOCK lock; /* Cursor read lock. */
+ db_lockmode_t mode; /* Lock mode. */
+
+ /* Per-thread information: recno private. */
+ db_recno_t recno; /* Current record number. */
+
+ /*
+ * Btree:
+ * We set a flag in the cursor structure if the underlying object has
+ * been deleted. It's not strictly necessary, we could get the same
+ * information by looking at the page itself.
+ *
+ * Recno:
+ * When renumbering recno databases during deletes, cursors referencing
+ * "deleted" records end up positioned between two records, and so must
+ * be specially adjusted on the next operation.
+ */
+#define C_DELETED 0x0001 /* Record was deleted. */
+ u_int32_t flags;
+};
+
+/*
+ * The in-memory recno data structure.
+ *
+ * !!!
+ * These fields are ignored as far as multi-threading is concerned. There
+ * are no transaction semantics associated with backing files, nor is there
+ * any thread protection.
+ */
+struct __recno {
+ int re_delim; /* Variable-length delimiting byte. */
+ int re_pad; /* Fixed-length padding byte. */
+ u_int32_t re_len; /* Length for fixed-length records. */
+
+ char *re_source; /* Source file name. */
+ int re_fd; /* Source file descriptor */
+ db_recno_t re_last; /* Last record number read. */
+ void *re_cmap; /* Current point in mapped space. */
+ void *re_smap; /* Start of mapped space. */
+ void *re_emap; /* End of mapped space. */
+ size_t re_msize; /* Size of mapped region. */
+ /* Recno input function. */
+ int (*re_irec) __P((DBC *, db_recno_t));
+
+#define RECNO_EOF 0x0001 /* EOF on backing source file. */
+#define RECNO_MODIFIED 0x0002 /* Tree was modified. */
+ u_int32_t flags;
+};
+
+/*
+ * The in-memory, per-tree btree data structure.
+ */
+struct __btree {
+ db_pgno_t bt_lpgno; /* Last insert location. */
+
+ db_indx_t bt_maxkey; /* Maximum keys per page. */
+ db_indx_t bt_minkey; /* Minimum keys per page. */
+
+ int (*bt_compare) /* Comparison function. */
+ __P((const DBT *, const DBT *));
+ size_t(*bt_prefix) /* Prefix function. */
+ __P((const DBT *, const DBT *));
+
+ db_indx_t bt_ovflsize; /* Maximum key/data on-page size. */
+
+ RECNO *recno; /* Private recno structure. */
+};
+
+#include "btree_auto.h"
+#include "btree_ext.h"
+#include "db_am.h"
+#include "common_ext.h"
diff --git a/usr/src/cmd/sendmail/db/include/btree_auto.h b/usr/src/cmd/sendmail/db/include/btree_auto.h
new file mode 100644
index 0000000000..9891e15484
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/include/btree_auto.h
@@ -0,0 +1,135 @@
+/* Do not edit: automatically built by dist/db_gen.sh. */
+
+/*
+ * Copyright (c) 1998 by Sun Microsystems, Inc.
+ * All rights reserved.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#ifndef bam_AUTO_H
+#define bam_AUTO_H
+
+#define DB_bam_pg_alloc (DB_bam_BEGIN + 1)
+
+typedef struct _bam_pg_alloc_args {
+ u_int32_t type;
+ DB_TXN *txnid;
+ DB_LSN prev_lsn;
+ u_int32_t fileid;
+ DB_LSN meta_lsn;
+ DB_LSN page_lsn;
+ db_pgno_t pgno;
+ u_int32_t ptype;
+ db_pgno_t next;
+} __bam_pg_alloc_args;
+
+
+#define DB_bam_pg_free (DB_bam_BEGIN + 2)
+
+typedef struct _bam_pg_free_args {
+ u_int32_t type;
+ DB_TXN *txnid;
+ DB_LSN prev_lsn;
+ u_int32_t fileid;
+ db_pgno_t pgno;
+ DB_LSN meta_lsn;
+ DBT header;
+ db_pgno_t next;
+} __bam_pg_free_args;
+
+
+#define DB_bam_split (DB_bam_BEGIN + 3)
+
+typedef struct _bam_split_args {
+ u_int32_t type;
+ DB_TXN *txnid;
+ DB_LSN prev_lsn;
+ u_int32_t fileid;
+ db_pgno_t left;
+ DB_LSN llsn;
+ db_pgno_t right;
+ DB_LSN rlsn;
+ u_int32_t indx;
+ db_pgno_t npgno;
+ DB_LSN nlsn;
+ DBT pg;
+} __bam_split_args;
+
+
+#define DB_bam_rsplit (DB_bam_BEGIN + 4)
+
+typedef struct _bam_rsplit_args {
+ u_int32_t type;
+ DB_TXN *txnid;
+ DB_LSN prev_lsn;
+ u_int32_t fileid;
+ db_pgno_t pgno;
+ DBT pgdbt;
+ db_pgno_t nrec;
+ DBT rootent;
+ DB_LSN rootlsn;
+} __bam_rsplit_args;
+
+
+#define DB_bam_adj (DB_bam_BEGIN + 5)
+
+typedef struct _bam_adj_args {
+ u_int32_t type;
+ DB_TXN *txnid;
+ DB_LSN prev_lsn;
+ u_int32_t fileid;
+ db_pgno_t pgno;
+ DB_LSN lsn;
+ u_int32_t indx;
+ u_int32_t indx_copy;
+ u_int32_t is_insert;
+} __bam_adj_args;
+
+
+#define DB_bam_cadjust (DB_bam_BEGIN + 6)
+
+typedef struct _bam_cadjust_args {
+ u_int32_t type;
+ DB_TXN *txnid;
+ DB_LSN prev_lsn;
+ u_int32_t fileid;
+ db_pgno_t pgno;
+ DB_LSN lsn;
+ u_int32_t indx;
+ int32_t adjust;
+ int32_t total;
+} __bam_cadjust_args;
+
+
+#define DB_bam_cdel (DB_bam_BEGIN + 7)
+
+typedef struct _bam_cdel_args {
+ u_int32_t type;
+ DB_TXN *txnid;
+ DB_LSN prev_lsn;
+ u_int32_t fileid;
+ db_pgno_t pgno;
+ DB_LSN lsn;
+ u_int32_t indx;
+} __bam_cdel_args;
+
+
+#define DB_bam_repl (DB_bam_BEGIN + 8)
+
+typedef struct _bam_repl_args {
+ u_int32_t type;
+ DB_TXN *txnid;
+ DB_LSN prev_lsn;
+ u_int32_t fileid;
+ db_pgno_t pgno;
+ DB_LSN lsn;
+ u_int32_t indx;
+ u_int32_t isdeleted;
+ DBT orig;
+ DBT repl;
+ u_int32_t prefix;
+ u_int32_t suffix;
+} __bam_repl_args;
+
+#endif
diff --git a/usr/src/cmd/sendmail/db/include/btree_ext.h b/usr/src/cmd/sendmail/db/include/btree_ext.h
new file mode 100644
index 0000000000..fbc2ed958f
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/include/btree_ext.h
@@ -0,0 +1,132 @@
+/* DO NOT EDIT: automatically built by dist/distrib. */
+#ifndef _btree_ext_h_
+#define _btree_ext_h_
+int __bam_cmp __P((DB *, const DBT *,
+ PAGE *, u_int32_t, int (*)(const DBT *, const DBT *)));
+int __bam_defcmp __P((const DBT *, const DBT *));
+size_t __bam_defpfx __P((const DBT *, const DBT *));
+int __bam_pgin __P((db_pgno_t, void *, DBT *));
+int __bam_pgout __P((db_pgno_t, void *, DBT *));
+int __bam_mswap __P((PAGE *));
+int __bam_cprint __P((DB *));
+int __bam_ca_delete __P((DB *, db_pgno_t, u_int32_t, int));
+void __bam_ca_di __P((DB *, db_pgno_t, u_int32_t, int));
+void __bam_ca_dup __P((DB *,
+ db_pgno_t, u_int32_t, u_int32_t, db_pgno_t, u_int32_t));
+void __bam_ca_rsplit __P((DB *, db_pgno_t, db_pgno_t));
+void __bam_ca_split __P((DB *,
+ db_pgno_t, db_pgno_t, db_pgno_t, u_int32_t, int));
+int __bam_c_init __P((DBC *));
+int __bam_dup __P((DBC *, CURSOR *, u_int32_t, int));
+int __bam_delete __P((DB *, DB_TXN *, DBT *, u_int32_t));
+int __bam_ditem __P((DBC *, PAGE *, u_int32_t));
+int __bam_adjindx __P((DBC *, PAGE *, u_int32_t, u_int32_t, int));
+int __bam_dpage __P((DBC *, const DBT *));
+int __bam_dpages __P((DBC *));
+int __bam_open __P((DB *, DB_INFO *));
+int __bam_close __P((DB *));
+void __bam_setovflsize __P((DB *));
+int __bam_read_root __P((DB *));
+int __bam_new __P((DBC *, u_int32_t, PAGE **));
+int __bam_lput __P((DBC *, DB_LOCK));
+int __bam_free __P((DBC *, PAGE *));
+int __bam_lt __P((DBC *));
+int __bam_lget
+ __P((DBC *, int, db_pgno_t, db_lockmode_t, DB_LOCK *));
+int __bam_iitem __P((DBC *,
+ PAGE **, db_indx_t *, DBT *, DBT *, u_int32_t, u_int32_t));
+int __bam_ritem __P((DBC *, PAGE *, u_int32_t, DBT *));
+int __bam_pg_alloc_recover
+ __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+int __bam_pg_free_recover
+ __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+int __bam_split_recover
+ __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+int __bam_rsplit_recover
+ __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+int __bam_adj_recover
+ __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+int __bam_cadjust_recover
+ __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+int __bam_cdel_recover
+ __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+int __bam_repl_recover
+ __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+int __ram_open __P((DB *, DB_INFO *));
+int __ram_close __P((DB *));
+int __ram_c_del __P((DBC *, u_int32_t));
+int __ram_c_get __P((DBC *, DBT *, DBT *, u_int32_t));
+int __ram_c_put __P((DBC *, DBT *, DBT *, u_int32_t));
+void __ram_ca __P((DB *, db_recno_t, ca_recno_arg));
+int __ram_getno __P((DBC *, const DBT *, db_recno_t *, int));
+int __bam_rsearch __P((DBC *, db_recno_t *, u_int32_t, int, int *));
+int __bam_adjust __P((DBC *, int32_t));
+int __bam_nrecs __P((DBC *, db_recno_t *));
+db_recno_t __bam_total __P((PAGE *));
+int __bam_search __P((DBC *,
+ const DBT *, u_int32_t, int, db_recno_t *, int *));
+int __bam_stkrel __P((DBC *, int));
+int __bam_stkgrow __P((CURSOR *));
+int __bam_split __P((DBC *, void *));
+int __bam_copy __P((DB *, PAGE *, PAGE *, u_int32_t, u_int32_t));
+int __bam_stat __P((DB *, void *, void *(*)(size_t), u_int32_t));
+int __bam_pg_alloc_log
+ __P((DB_LOG *, DB_TXN *, DB_LSN *, u_int32_t,
+ u_int32_t, DB_LSN *, DB_LSN *, db_pgno_t,
+ u_int32_t, db_pgno_t));
+int __bam_pg_alloc_print
+ __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+int __bam_pg_alloc_read __P((void *, __bam_pg_alloc_args **));
+int __bam_pg_free_log
+ __P((DB_LOG *, DB_TXN *, DB_LSN *, u_int32_t,
+ u_int32_t, db_pgno_t, DB_LSN *, const DBT *,
+ db_pgno_t));
+int __bam_pg_free_print
+ __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+int __bam_pg_free_read __P((void *, __bam_pg_free_args **));
+int __bam_split_log
+ __P((DB_LOG *, DB_TXN *, DB_LSN *, u_int32_t,
+ u_int32_t, db_pgno_t, DB_LSN *, db_pgno_t,
+ DB_LSN *, u_int32_t, db_pgno_t, DB_LSN *,
+ const DBT *));
+int __bam_split_print
+ __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+int __bam_split_read __P((void *, __bam_split_args **));
+int __bam_rsplit_log
+ __P((DB_LOG *, DB_TXN *, DB_LSN *, u_int32_t,
+ u_int32_t, db_pgno_t, const DBT *, db_pgno_t,
+ const DBT *, DB_LSN *));
+int __bam_rsplit_print
+ __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+int __bam_rsplit_read __P((void *, __bam_rsplit_args **));
+int __bam_adj_log
+ __P((DB_LOG *, DB_TXN *, DB_LSN *, u_int32_t,
+ u_int32_t, db_pgno_t, DB_LSN *, u_int32_t,
+ u_int32_t, u_int32_t));
+int __bam_adj_print
+ __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+int __bam_adj_read __P((void *, __bam_adj_args **));
+int __bam_cadjust_log
+ __P((DB_LOG *, DB_TXN *, DB_LSN *, u_int32_t,
+ u_int32_t, db_pgno_t, DB_LSN *, u_int32_t,
+ int32_t, int32_t));
+int __bam_cadjust_print
+ __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+int __bam_cadjust_read __P((void *, __bam_cadjust_args **));
+int __bam_cdel_log
+ __P((DB_LOG *, DB_TXN *, DB_LSN *, u_int32_t,
+ u_int32_t, db_pgno_t, DB_LSN *, u_int32_t));
+int __bam_cdel_print
+ __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+int __bam_cdel_read __P((void *, __bam_cdel_args **));
+int __bam_repl_log
+ __P((DB_LOG *, DB_TXN *, DB_LSN *, u_int32_t,
+ u_int32_t, db_pgno_t, DB_LSN *, u_int32_t,
+ u_int32_t, const DBT *, const DBT *, u_int32_t,
+ u_int32_t));
+int __bam_repl_print
+ __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+int __bam_repl_read __P((void *, __bam_repl_args **));
+int __bam_init_print __P((DB_ENV *));
+int __bam_init_recover __P((DB_ENV *));
+#endif /* _btree_ext_h_ */
diff --git a/usr/src/cmd/sendmail/db/include/clib_ext.h b/usr/src/cmd/sendmail/db/include/clib_ext.h
new file mode 100644
index 0000000000..2566b849ce
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/include/clib_ext.h
@@ -0,0 +1,59 @@
+/* DO NOT EDIT: automatically built by dist/distrib. */
+#ifndef _clib_ext_h_
+#define _clib_ext_h_
+#ifdef __STDC__
+void err __P((int eval, const char *, ...));
+#else
+void err();
+#endif
+#ifdef __STDC__
+void errx __P((int eval, const char *, ...));
+#else
+void errx();
+#endif
+#ifdef __STDC__
+void warn __P((const char *, ...));
+#else
+void warn();
+#endif
+#ifdef __STDC__
+void warnx __P((const char *, ...));
+#else
+void warnx();
+#endif
+#ifndef HAVE_GETCWD
+char *getcwd __P((char *, size_t));
+#endif
+void get_long __P((char *, long, long, long *));
+#ifndef HAVE_GETOPT
+int getopt __P((int, char * const *, const char *));
+#endif
+#ifndef HAVE_MEMCMP
+int memcmp __P((const void *, const void *, size_t));
+#endif
+#ifndef HAVE_MEMCPY
+void *memcpy __P((void *, const void *, size_t));
+#endif
+#ifndef HAVE_MEMMOVE
+void *memmove __P((void *, const void *, size_t));
+#endif
+#ifndef HAVE_RAISE
+int raise __P((int));
+#endif
+#ifndef HAVE_SNPRINTF
+#ifdef __STDC__
+int snprintf __P((char *, size_t, const char *, ...));
+#else
+int snprintf();
+#endif
+#endif
+#ifndef HAVE_STRERROR
+char *strerror __P((int));
+#endif
+#ifndef HAVE_STRSEP
+char *strsep __P((char **, const char *));
+#endif
+#ifndef HAVE_VSNPRINTF
+int vsnprintf();
+#endif
+#endif /* _clib_ext_h_ */
diff --git a/usr/src/cmd/sendmail/db/include/common_ext.h b/usr/src/cmd/sendmail/db/include/common_ext.h
new file mode 100644
index 0000000000..33fb0cb218
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/include/common_ext.h
@@ -0,0 +1,34 @@
+/* DO NOT EDIT: automatically built by dist/distrib. */
+#ifndef _common_ext_h_
+#define _common_ext_h_
+int __db_appname __P((DB_ENV *,
+ APPNAME, const char *, const char *, u_int32_t, int *, char **));
+int __db_apprec __P((DB_ENV *, u_int32_t));
+int __db_byteorder __P((DB_ENV *, int));
+int __db_fchk __P((DB_ENV *, const char *, u_int32_t, u_int32_t));
+int __db_fcchk
+ __P((DB_ENV *, const char *, u_int32_t, u_int32_t, u_int32_t));
+int __db_ferr __P((const DB_ENV *, const char *, int));
+#ifdef __STDC__
+void __db_err __P((const DB_ENV *dbenv, const char *fmt, ...));
+#else
+void __db_err();
+#endif
+int __db_pgerr __P((DB *, db_pgno_t));
+int __db_pgfmt __P((DB *, db_pgno_t));
+int __db_panic __P((DB_ENV *, int));
+u_int32_t __db_log2 __P((u_int32_t));
+int __db_rattach __P((REGINFO *));
+int __db_rdetach __P((REGINFO *));
+int __db_runlink __P((REGINFO *, int));
+int __db_rgrow __P((REGINFO *, size_t));
+int __db_rreattach __P((REGINFO *, size_t));
+void __db_shalloc_init __P((void *, size_t));
+int __db_shalloc __P((void *, size_t, size_t, void *));
+void __db_shalloc_free __P((void *, void *));
+size_t __db_shalloc_count __P((void *));
+size_t __db_shsizeof __P((void *));
+void __db_shalloc_dump __P((void *, FILE *));
+int __db_tablesize __P((u_int32_t));
+void __db_hashinit __P((void *, u_int32_t));
+#endif /* _common_ext_h_ */
diff --git a/usr/src/cmd/sendmail/db/include/db_am.h b/usr/src/cmd/sendmail/db/include/db_am.h
new file mode 100644
index 0000000000..fe2176d772
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/include/db_am.h
@@ -0,0 +1,86 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ *
+ * @(#)db_am.h 10.15 (Sleepycat) 11/22/98
+ */
+#ifndef _DB_AM_H
+#define _DB_AM_H
+
+#define DB_ISBIG 0x01
+#define DB_ADD_DUP 0x10
+#define DB_REM_DUP 0x20
+#define DB_ADD_BIG 0x30
+#define DB_REM_BIG 0x40
+#define DB_SPLITOLD 0x50
+#define DB_SPLITNEW 0x60
+#define DB_ADD_PAGE 0x70
+#define DB_REM_PAGE 0x80
+
+/*
+ * Standard initialization and shutdown macros for all recovery functions.
+ *
+ * Requires the following local variables:
+ *
+ * DB *file_dbp, *mdbp;
+ * DB_MPOOLFILE *mpf;
+ * int ret;
+ */
+#define REC_INTRO(func) { \
+ file_dbp = NULL; \
+ dbc = NULL; \
+ if ((ret = func(dbtp->data, &argp)) != 0) \
+ goto out; \
+ if ((ret = \
+ __db_fileid_to_db(logp, &file_dbp, argp->fileid)) != 0) { \
+ if (ret == DB_DELETED) { \
+ ret = 0; \
+ goto done; \
+ } \
+ goto out; \
+ } \
+ if (file_dbp == NULL) \
+ goto out; \
+ if ((ret = file_dbp->cursor(file_dbp, NULL, &dbc, 0)) != 0) \
+ goto out; \
+ F_SET(dbc, DBC_RECOVER); \
+ mpf = file_dbp->mpf; \
+}
+
+#define REC_CLOSE { \
+ if (argp != NULL) \
+ __os_free(argp, sizeof(*argp)); \
+ if (dbc != NULL) \
+ dbc->c_close(dbc); \
+ return (ret); \
+}
+
+/*
+ * No-op versions of the same macros.
+ */
+#define REC_NOOP_INTRO(func) { \
+ if ((ret = func(dbtp->data, &argp)) != 0) \
+ return (ret); \
+}
+#define REC_NOOP_CLOSE { \
+ if (argp != NULL) \
+ __os_free(argp, sizeof(*argp)); \
+ return (ret); \
+}
+
+/*
+ * Standard debugging macro for all recovery functions.
+ */
+#ifdef DEBUG_RECOVER
+#define REC_PRINT(func) \
+ (void)func(logp, dbtp, lsnp, redo, info);
+#else
+#define REC_PRINT(func) \
+ COMPQUIET(info, NULL);
+#endif
+
+#include "db_auto.h"
+#include "db_ext.h"
+#endif
diff --git a/usr/src/cmd/sendmail/db/include/db_auto.h b/usr/src/cmd/sendmail/db/include/db_auto.h
new file mode 100644
index 0000000000..0d1e43a26a
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/include/db_auto.h
@@ -0,0 +1,111 @@
+/* Do not edit: automatically built by dist/db_gen.sh. */
+#ifndef db_AUTO_H
+#define db_AUTO_H
+
+#define DB_db_addrem (DB_db_BEGIN + 1)
+
+typedef struct _db_addrem_args {
+ u_int32_t type;
+ DB_TXN *txnid;
+ DB_LSN prev_lsn;
+ u_int32_t opcode;
+ u_int32_t fileid;
+ db_pgno_t pgno;
+ u_int32_t indx;
+ size_t nbytes;
+ DBT hdr;
+ DBT dbt;
+ DB_LSN pagelsn;
+} __db_addrem_args;
+
+
+#define DB_db_split (DB_db_BEGIN + 2)
+
+typedef struct _db_split_args {
+ u_int32_t type;
+ DB_TXN *txnid;
+ DB_LSN prev_lsn;
+ u_int32_t opcode;
+ u_int32_t fileid;
+ db_pgno_t pgno;
+ DBT pageimage;
+ DB_LSN pagelsn;
+} __db_split_args;
+
+
+#define DB_db_big (DB_db_BEGIN + 3)
+
+typedef struct _db_big_args {
+ u_int32_t type;
+ DB_TXN *txnid;
+ DB_LSN prev_lsn;
+ u_int32_t opcode;
+ u_int32_t fileid;
+ db_pgno_t pgno;
+ db_pgno_t prev_pgno;
+ db_pgno_t next_pgno;
+ DBT dbt;
+ DB_LSN pagelsn;
+ DB_LSN prevlsn;
+ DB_LSN nextlsn;
+} __db_big_args;
+
+
+#define DB_db_ovref (DB_db_BEGIN + 4)
+
+typedef struct _db_ovref_args {
+ u_int32_t type;
+ DB_TXN *txnid;
+ DB_LSN prev_lsn;
+ u_int32_t fileid;
+ db_pgno_t pgno;
+ int32_t adjust;
+ DB_LSN lsn;
+} __db_ovref_args;
+
+
+#define DB_db_relink (DB_db_BEGIN + 5)
+
+typedef struct _db_relink_args {
+ u_int32_t type;
+ DB_TXN *txnid;
+ DB_LSN prev_lsn;
+ u_int32_t opcode;
+ u_int32_t fileid;
+ db_pgno_t pgno;
+ DB_LSN lsn;
+ db_pgno_t prev;
+ DB_LSN lsn_prev;
+ db_pgno_t next;
+ DB_LSN lsn_next;
+} __db_relink_args;
+
+
+#define DB_db_addpage (DB_db_BEGIN + 6)
+
+typedef struct _db_addpage_args {
+ u_int32_t type;
+ DB_TXN *txnid;
+ DB_LSN prev_lsn;
+ u_int32_t fileid;
+ db_pgno_t pgno;
+ DB_LSN lsn;
+ db_pgno_t nextpgno;
+ DB_LSN nextlsn;
+} __db_addpage_args;
+
+
+#define DB_db_debug (DB_db_BEGIN + 7)
+
+typedef struct _db_debug_args {
+ u_int32_t type;
+ DB_TXN *txnid;
+ DB_LSN prev_lsn;
+ DBT op;
+ u_int32_t fileid;
+ DBT key;
+ DBT data;
+ u_int32_t arg_flags;
+} __db_debug_args;
+
+#endif
diff --git a/usr/src/cmd/sendmail/db/include/db_dispatch.h b/usr/src/cmd/sendmail/db/include/db_dispatch.h
new file mode 100644
index 0000000000..8f5e217402
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/include/db_dispatch.h
@@ -0,0 +1,77 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+/*
+ * Copyright (c) 1995, 1996
+ * The President and Fellows of Harvard University. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)db_dispatch.h 10.4 (Sleepycat) 5/3/98
+ */
+
+#ifndef _DB_DISPATCH_H
+#define _DB_DISPATCH_H
+
+struct __db_txnhead; typedef struct __db_txnhead DB_TXNHEAD;
+struct __db_txnlist; typedef struct __db_txnlist DB_TXNLIST;
+
+/*
+ * Declarations and typedefs for the list of transaction IDs used during
+ * recovery.
+ */
+struct __db_txnhead {
+ LIST_HEAD(__db_headlink, __db_txnlist) head;
+ u_int32_t maxid;
+ int32_t generation;
+};
+
+struct __db_txnlist {
+ LIST_ENTRY(__db_txnlist) links;
+ u_int32_t txnid;
+ int32_t generation;
+};
+
+#define DB_log_BEGIN 0
+#define DB_txn_BEGIN 5
+#define DB_ham_BEGIN 20
+#define DB_db_BEGIN 40
+#define DB_bam_BEGIN 50
+#define DB_ram_BEGIN 100
+#define DB_user_BEGIN 150
+
+#define TXN_UNDO 0
+#define TXN_REDO 1
+#define TXN_BACKWARD_ROLL -1
+#define TXN_FORWARD_ROLL -2
+#define TXN_OPENFILES -3
+#endif
diff --git a/usr/src/cmd/sendmail/db/include/db_ext.h b/usr/src/cmd/sendmail/db/include/db_ext.h
new file mode 100644
index 0000000000..1ad1643bfa
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/include/db_ext.h
@@ -0,0 +1,132 @@
+/* DO NOT EDIT: automatically built by dist/distrib. */
+#ifndef _db_ext_h_
+#define _db_ext_h_
+int __db_close __P((DB *, u_int32_t));
+int __db_init_wrapper __P((DB *));
+int __db_cprint __P((DB *));
+int __db_c_destroy __P((DBC *));
+int __db_sync __P((DB *, u_int32_t));
+int __db_addrem_log
+ __P((DB_LOG *, DB_TXN *, DB_LSN *, u_int32_t,
+ u_int32_t, u_int32_t, db_pgno_t, u_int32_t,
+ size_t, const DBT *, const DBT *, DB_LSN *));
+int __db_addrem_print
+ __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+int __db_addrem_read __P((void *, __db_addrem_args **));
+int __db_split_log
+ __P((DB_LOG *, DB_TXN *, DB_LSN *, u_int32_t,
+ u_int32_t, u_int32_t, db_pgno_t, const DBT *,
+ DB_LSN *));
+int __db_split_print
+ __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+int __db_split_read __P((void *, __db_split_args **));
+int __db_big_log
+ __P((DB_LOG *, DB_TXN *, DB_LSN *, u_int32_t,
+ u_int32_t, u_int32_t, db_pgno_t, db_pgno_t,
+ db_pgno_t, const DBT *, DB_LSN *, DB_LSN *,
+ DB_LSN *));
+int __db_big_print
+ __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+int __db_big_read __P((void *, __db_big_args **));
+int __db_ovref_log
+ __P((DB_LOG *, DB_TXN *, DB_LSN *, u_int32_t,
+ u_int32_t, db_pgno_t, int32_t, DB_LSN *));
+int __db_ovref_print
+ __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+int __db_ovref_read __P((void *, __db_ovref_args **));
+int __db_relink_log
+ __P((DB_LOG *, DB_TXN *, DB_LSN *, u_int32_t,
+ u_int32_t, u_int32_t, db_pgno_t, DB_LSN *,
+ db_pgno_t, DB_LSN *, db_pgno_t, DB_LSN *));
+int __db_relink_print
+ __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+int __db_relink_read __P((void *, __db_relink_args **));
+int __db_addpage_log
+ __P((DB_LOG *, DB_TXN *, DB_LSN *, u_int32_t,
+ u_int32_t, db_pgno_t, DB_LSN *, db_pgno_t,
+ DB_LSN *));
+int __db_addpage_print
+ __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+int __db_addpage_read __P((void *, __db_addpage_args **));
+int __db_debug_log
+ __P((DB_LOG *, DB_TXN *, DB_LSN *, u_int32_t,
+ const DBT *, u_int32_t, const DBT *, const DBT *,
+ u_int32_t));
+int __db_debug_print
+ __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+int __db_debug_read __P((void *, __db_debug_args **));
+int __db_init_print __P((DB_ENV *));
+int __db_init_recover __P((DB_ENV *));
+int __db_pgin __P((db_pgno_t, size_t, void *));
+int __db_pgout __P((db_pgno_t, size_t, void *));
+int __db_dispatch __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+int __db_add_recovery __P((DB_ENV *,
+ int (*)(DB_LOG *, DBT *, DB_LSN *, int, void *), u_int32_t));
+int __db_txnlist_init __P((void *));
+int __db_txnlist_add __P((void *, u_int32_t));
+int __db_txnlist_find __P((void *, u_int32_t));
+void __db_txnlist_end __P((void *));
+void __db_txnlist_gen __P((void *, int));
+void __db_txnlist_print __P((void *));
+int __db_dput __P((DBC *, DBT *,
+ PAGE **, db_indx_t *, int (*)(DBC *, u_int32_t, PAGE **)));
+int __db_drem __P((DBC *,
+ PAGE **, u_int32_t, int (*)(DBC *, PAGE *)));
+int __db_dend __P((DBC *, db_pgno_t, PAGE **));
+ int __db_ditem __P((DBC *, PAGE *, u_int32_t, u_int32_t));
+int __db_pitem
+ __P((DBC *, PAGE *, u_int32_t, u_int32_t, DBT *, DBT *));
+int __db_relink __P((DBC *, u_int32_t, PAGE *, PAGE **, int));
+int __db_ddup __P((DBC *, db_pgno_t, int (*)(DBC *, PAGE *)));
+int __db_dsearch __P((DBC *,
+ int, DBT *, db_pgno_t, db_indx_t *, PAGE **, int *));
+int __db_cdelchk __P((const DB *, u_int32_t, int, int));
+int __db_cgetchk __P((const DB *, DBT *, DBT *, u_int32_t, int));
+int __db_cputchk __P((const DB *,
+ const DBT *, DBT *, u_int32_t, int, int));
+int __db_closechk __P((const DB *, u_int32_t));
+int __db_delchk __P((const DB *, DBT *, u_int32_t, int));
+int __db_getchk __P((const DB *, const DBT *, DBT *, u_int32_t));
+int __db_joinchk __P((const DB *, u_int32_t));
+int __db_putchk
+ __P((const DB *, DBT *, const DBT *, u_int32_t, int, int));
+int __db_statchk __P((const DB *, u_int32_t));
+int __db_syncchk __P((const DB *, u_int32_t));
+int __db_eopnotsup __P((const DB_ENV *));
+int __db_join __P((DB *, DBC **, u_int32_t, DBC **));
+int __db_goff __P((DB *, DBT *,
+ u_int32_t, db_pgno_t, void **, u_int32_t *));
+int __db_poff __P((DBC *, const DBT *, db_pgno_t *,
+ int (*)(DBC *, u_int32_t, PAGE **)));
+int __db_ovref __P((DBC *, db_pgno_t, int32_t));
+int __db_doff __P((DBC *, db_pgno_t, int (*)(DBC *, PAGE *)));
+int __db_moff __P((DB *, const DBT *, db_pgno_t, u_int32_t,
+ int (*)(const DBT *, const DBT *), int *));
+void __db_loadme __P((void));
+FILE *__db_prinit __P((FILE *));
+int __db_dump __P((DB *, char *, int));
+int __db_prdb __P((DB *));
+int __db_prbtree __P((DB *));
+int __db_prhash __P((DB *));
+int __db_prtree __P((DB_MPOOLFILE *, int));
+int __db_prnpage __P((DB_MPOOLFILE *, db_pgno_t));
+int __db_prpage __P((PAGE *, int));
+int __db_isbad __P((PAGE *, int));
+void __db_pr __P((u_int8_t *, u_int32_t));
+int __db_prdbt __P((DBT *, int, FILE *));
+void __db_prflags __P((u_int32_t, const FN *, FILE *));
+int __db_addrem_recover
+ __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+int __db_split_recover __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+int __db_big_recover __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+int __db_ovref_recover __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+int __db_relink_recover
+ __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+int __db_addpage_recover
+ __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+int __db_debug_recover __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+int __db_ret __P((DB *,
+ PAGE *, u_int32_t, DBT *, void **, u_int32_t *));
+int __db_retcopy __P((DBT *,
+ void *, u_int32_t, void **, u_int32_t *, void *(*)(size_t)));
+#endif /* _db_ext_h_ */
diff --git a/usr/src/cmd/sendmail/db/include/db_join.h b/usr/src/cmd/sendmail/db/include/db_join.h
new file mode 100644
index 0000000000..609acf7d19
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/include/db_join.h
@@ -0,0 +1,25 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1998
+ * Sleepycat Software. All rights reserved.
+ *
+ * @(#)db_join.h 10.2 (Sleepycat) 10/4/98
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#ifndef _DB_JOIN_H
+#define _DB_JOIN_H
+/*
+ * Joins use a join cursor that is similar to a regular DB cursor except
+ * that it only supports c_get and c_close functionality. Also, it does
+ * not support the full range of flags for get.
+ */
+typedef struct __join_cursor {
+ u_int32_t j_init; /* Set when cursor is initialized. */
+ DBC **j_curslist; /* Array of cursors in the join. */
+ DB *j_primary; /* Primary dbp. */
+ DBT j_key; /* Used to do lookups. */
+} JOIN_CURSOR;
+#endif
diff --git a/usr/src/cmd/sendmail/db/include/db_page.h b/usr/src/cmd/sendmail/db/include/db_page.h
new file mode 100644
index 0000000000..5c9ca674f1
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/include/db_page.h
@@ -0,0 +1,512 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ *
+ * @(#)db_page.h 10.18 (Sleepycat) 12/2/98
+ */
+
+#ifndef _DB_PAGE_H_
+#define _DB_PAGE_H_
+
+/*
+ * DB page formats.
+ *
+ * This implementation requires that values within the following structures
+ * NOT be padded -- note, ANSI C permits random padding within structures.
+ * If your compiler pads randomly you can just forget ever making DB run on
+ * your system. In addition, no data type can require larger alignment than
+ * its own size, e.g., a 4-byte data element may not require 8-byte alignment.
+ *
+ * Note that key/data lengths are often stored in db_indx_t's -- this is
+ * not accidental, nor does it limit the key/data size. If the key/data
+ * item fits on a page, it's guaranteed to be small enough to fit into a
+ * db_indx_t, and storing it in one saves space.
+ */
+
+#define PGNO_METADATA 0 /* Metadata page number. */
+#define PGNO_INVALID 0 /* Metadata page number, therefore illegal. */
+#define PGNO_ROOT 1 /* Root is page #1. */
+
+/*
+ * When we create pages in mpool, we ask mpool to clear some number of bytes
+ * in the header. This number must be at least as big as the regular page
+ * headers and cover enough of the btree and hash meta-data pages to obliterate
+ * the magic and version numbers.
+ */
+#define DB_PAGE_CLEAR_LEN 32
+
+/************************************************************************
+ BTREE METADATA PAGE LAYOUT
+ ************************************************************************/
+
+/*
+ * Btree metadata page layout:
+ */
+typedef struct _btmeta {
+ DB_LSN lsn; /* 00-07: LSN. */
+ db_pgno_t pgno; /* 08-11: Current page number. */
+ u_int32_t magic; /* 12-15: Magic number. */
+ u_int32_t version; /* 16-19: Version. */
+ u_int32_t pagesize; /* 20-23: Pagesize. */
+ u_int32_t maxkey; /* 24-27: Btree: Maxkey. */
+ u_int32_t minkey; /* 28-31: Btree: Minkey. */
+ u_int32_t free; /* 32-35: Free list page number. */
+#define BTM_DUP 0x001 /* Duplicates. */
+#define BTM_RECNO 0x002 /* Recno tree. */
+#define BTM_RECNUM 0x004 /* Btree: maintain record count. */
+#define BTM_FIXEDLEN 0x008 /* Recno: fixed length records. */
+#define BTM_RENUMBER 0x010 /* Recno: renumber on insert/delete. */
+#define BTM_MASK 0x01f
+ u_int32_t flags; /* 36-39: Flags. */
+ u_int32_t re_len; /* 40-43: Recno: fixed-length record length. */
+ u_int32_t re_pad; /* 44-47: Recno: fixed-length record pad. */
+ /* 48-67: Unique file ID. */
+ u_int8_t uid[DB_FILE_ID_LEN];
+} BTMETA;
+
+/************************************************************************
+ HASH METADATA PAGE LAYOUT
+ ************************************************************************/
+
+/*
+ * Hash metadata page layout:
+ */
+/* Hash Table Information */
+typedef struct hashhdr { /* Disk resident portion */
+ DB_LSN lsn; /* 00-07: LSN of the header page */
+ db_pgno_t pgno; /* 08-11: Page number (btree compatibility). */
+ u_int32_t magic; /* 12-15: Magic NO for hash tables */
+ u_int32_t version; /* 16-19: Version ID */
+ u_int32_t pagesize; /* 20-23: Bucket/Page Size */
+ u_int32_t ovfl_point; /* 24-27: Overflow page allocation location */
+ u_int32_t last_freed; /* 28-31: Last freed overflow page pgno */
+ u_int32_t max_bucket; /* 32-35: ID of Maximum bucket in use */
+ u_int32_t high_mask; /* 36-39: Modulo mask into table */
+ u_int32_t low_mask; /* 40-43: Modulo mask into table lower half */
+ u_int32_t ffactor; /* 44-47: Fill factor */
+ u_int32_t nelem; /* 48-51: Number of keys in hash table */
+ u_int32_t h_charkey; /* 52-55: Value of hash(CHARKEY) */
+#define DB_HASH_DUP 0x01
+ u_int32_t flags; /* 56-59: Allow duplicates. */
+#define NCACHED 32 /* number of spare points */
+ /* 60-187: Spare pages for overflow */
+ u_int32_t spares[NCACHED];
+ /* 188-207: Unique file ID. */
+ u_int8_t uid[DB_FILE_ID_LEN];
+
+ /*
+ * Minimum page size is 256.
+ */
+} HASHHDR;
+
+/************************************************************************
+ MAIN PAGE LAYOUT
+ ************************************************************************/
+
+/*
+ * +-----------------------------------+
+ * | lsn | pgno | prev pgno |
+ * +-----------------------------------+
+ * | next pgno | entries | hf offset |
+ * +-----------------------------------+
+ * | level | type | index |
+ * +-----------------------------------+
+ * | index | free --> |
+ * +-----------+-----------------------+
+ * | F R E E A R E A |
+ * +-----------------------------------+
+ * | <-- free | item |
+ * +-----------------------------------+
+ * | item | item | item |
+ * +-----------------------------------+
+ *
+ * sizeof(PAGE) == 26 bytes, and the following indices are guaranteed to be
+ * two-byte aligned.
+ *
+ * For hash and btree leaf pages, index items are paired, e.g., inp[0] is the
+ * key for inp[1]'s data. All other types of pages only contain single items.
+ */
+typedef struct _db_page {
+ DB_LSN lsn; /* 00-07: Log sequence number. */
+ db_pgno_t pgno; /* 08-11: Current page number. */
+ db_pgno_t prev_pgno; /* 12-15: Previous page number. */
+ db_pgno_t next_pgno; /* 16-19: Next page number. */
+ db_indx_t entries; /* 20-21: Number of item pairs on the page. */
+ db_indx_t hf_offset; /* 22-23: High free byte page offset. */
+
+ /*
+ * The btree levels are numbered from the leaf to the root, starting
+ * with 1, so the leaf is level 1, its parent is level 2, and so on.
+ * We maintain this level on all btree pages, but the only place that
+ * we actually need it is on the root page. It would not be difficult
+ * to hide the byte on the root page once it becomes an internal page,
+ * so we could get this byte back if we needed it for something else.
+ */
+#define LEAFLEVEL 1
+#define MAXBTREELEVEL 255
+ u_int8_t level; /* 24: Btree tree level. */
+
+#define P_INVALID 0 /* Invalid page type. */
+#define P_DUPLICATE 1 /* Duplicate. */
+#define P_HASH 2 /* Hash. */
+#define P_IBTREE 3 /* Btree internal. */
+#define P_IRECNO 4 /* Recno internal. */
+#define P_LBTREE 5 /* Btree leaf. */
+#define P_LRECNO 6 /* Recno leaf. */
+#define P_OVERFLOW 7 /* Overflow. */
+ u_int8_t type; /* 25: Page type. */
+ db_indx_t inp[1]; /* Variable length index of items. */
+} PAGE;
+
+/* Element macros. */
+#define LSN(p) (((PAGE *)p)->lsn)
+#define PGNO(p) (((PAGE *)p)->pgno)
+#define PREV_PGNO(p) (((PAGE *)p)->prev_pgno)
+#define NEXT_PGNO(p) (((PAGE *)p)->next_pgno)
+#define NUM_ENT(p) (((PAGE *)p)->entries)
+#define HOFFSET(p) (((PAGE *)p)->hf_offset)
+#define LEVEL(p) (((PAGE *)p)->level)
+#define TYPE(p) (((PAGE *)p)->type)
+
+/*
+ * !!!
+ * The next_pgno and prev_pgno fields are not maintained for btree and recno
+ * internal pages. It's a minor performance improvement, and more, it's
+ * hard to do when deleting internal pages, and it decreases the chance of
+ * deadlock during deletes and splits.
+ *
+ * !!!
+ * The btree/recno access method needs db_recno_t bytes of space on the root
+ * page to specify how many records are stored in the tree. (The alternative
+ * is to store the number of records in the meta-data page, which will create
+ * a second hot spot in trees being actively modified, or recalculate it from
+ * the BINTERNAL fields on each access.) Overload the prev_pgno field.
+ */
+#define RE_NREC(p) \
+ (TYPE(p) == P_LBTREE ? NUM_ENT(p) / 2 : \
+ TYPE(p) == P_LRECNO ? NUM_ENT(p) : PREV_PGNO(p))
+#define RE_NREC_ADJ(p, adj) \
+ PREV_PGNO(p) += adj;
+#define RE_NREC_SET(p, num) \
+ PREV_PGNO(p) = num;
+
+/*
+ * Initialize a page.
+ *
+ * !!!
+ * Don't modify the page's LSN, code depends on it being unchanged after a
+ * P_INIT call.
+ */
+#define P_INIT(pg, pg_size, n, pg_prev, pg_next, btl, pg_type) do { \
+ PGNO(pg) = n; \
+ PREV_PGNO(pg) = pg_prev; \
+ NEXT_PGNO(pg) = pg_next; \
+ NUM_ENT(pg) = 0; \
+ HOFFSET(pg) = pg_size; \
+ LEVEL(pg) = btl; \
+ TYPE(pg) = pg_type; \
+} while (0)
+
+/* Page header length (offset to first index). */
+#define P_OVERHEAD (SSZA(PAGE, inp))
+
+/* First free byte. */
+#define LOFFSET(pg) (P_OVERHEAD + NUM_ENT(pg) * sizeof(db_indx_t))
+
+/* Free space on the page. */
+#define P_FREESPACE(pg) (HOFFSET(pg) - LOFFSET(pg))
+
+/* Get a pointer to the bytes at a specific index. */
+#define P_ENTRY(pg, indx) ((u_int8_t *)pg + ((PAGE *)pg)->inp[indx])
+
+/************************************************************************
+ OVERFLOW PAGE LAYOUT
+ ************************************************************************/
+
+/*
+ * Overflow items are referenced by HOFFPAGE and BOVERFLOW structures, which
+ * store a page number (the first page of the overflow item) and a length
+ * (the total length of the overflow item). The overflow item consists of
+ * some number of overflow pages, linked by the next_pgno field of the page.
+ * A next_pgno field of PGNO_INVALID flags the end of the overflow item.
+ *
+ * Overflow page overloads:
+ * The amount of overflow data stored on each page is stored in the
+ * hf_offset field.
+ *
+ * The implementation reference counts overflow items as it's possible
+ * for them to be promoted onto btree internal pages. The reference
+ * count is stored in the entries field.
+ */
+#define OV_LEN(p) (((PAGE *)p)->hf_offset)
+#define OV_REF(p) (((PAGE *)p)->entries)
+
+/* Maximum number of bytes that you can put on an overflow page. */
+#define P_MAXSPACE(psize) ((psize) - P_OVERHEAD)
+
+/************************************************************************
+ HASH PAGE LAYOUT
+ ************************************************************************/
+
+/* Each index references a group of bytes on the page. */
+#define H_KEYDATA 1 /* Key/data item. */
+#define H_DUPLICATE 2 /* Duplicate key/data item. */
+#define H_OFFPAGE 3 /* Overflow key/data item. */
+#define H_OFFDUP 4 /* Overflow page of duplicates. */
+
+/*
+ * !!!
+ * Items on hash pages are (potentially) unaligned, so we can never cast the
+ * (page + offset) pointer to an HKEYDATA, HOFFPAGE or HOFFDUP structure, as
+ * we do with B+tree on-page structures. Because we frequently want the type
+ * field, it requires no alignment, and it's in the same location in all three
+ * structures, there's a pair of macros.
+ */
+#define HPAGE_PTYPE(p) (*(u_int8_t *)p)
+#define HPAGE_TYPE(pg, indx) (*P_ENTRY(pg, indx))
+
+/*
+ * The first and second types are H_KEYDATA and H_DUPLICATE, represented
+ * by the HKEYDATA structure:
+ *
+ * +-----------------------------------+
+ * | type | key/data ... |
+ * +-----------------------------------+
+ *
+ * For duplicates, the data field encodes duplicate elements in the data
+ * field:
+ *
+ * +---------------------------------------------------------------+
+ * | type | len1 | element1 | len1 | len2 | element2 | len2 |
+ * +---------------------------------------------------------------+
+ *
+ * Thus, by keeping track of the offset in the element, we can do both
+ * backward and forward traversal.
+ */
+typedef struct _hkeydata {
+ u_int8_t type; /* 00: Page type. */
+ u_int8_t data[1]; /* Variable length key/data item. */
+} HKEYDATA;
+#define HKEYDATA_DATA(p) (((u_int8_t *)p) + SSZA(HKEYDATA, data))
+
+/*
+ * The length of any HKEYDATA item. Note that indx is an element index,
+ * not a PAIR index.
+ */
+#define LEN_HITEM(pg, pgsize, indx) \
+ (((indx) == 0 ? pgsize : pg->inp[indx - 1]) - pg->inp[indx])
+
+#define LEN_HKEYDATA(pg, psize, indx) \
+ (((indx) == 0 ? psize : pg->inp[indx - 1]) - \
+ pg->inp[indx] - HKEYDATA_SIZE(0))
+
+/*
+ * Page space required to add a new HKEYDATA item to the page, with and
+ * without the index value.
+ */
+#define HKEYDATA_SIZE(len) \
+ ((len) + SSZA(HKEYDATA, data))
+#define HKEYDATA_PSIZE(len) \
+ (HKEYDATA_SIZE(len) + sizeof(db_indx_t))
+
+/* Put a HKEYDATA item at the location referenced by a page entry. */
+#define PUT_HKEYDATA(pe, kd, len, type) { \
+ ((HKEYDATA *)pe)->type = type; \
+ memcpy((u_int8_t *)pe + sizeof(u_int8_t), kd, len); \
+}
+
+/*
+ * Macros the describe the page layout in terms of key-data pairs.
+ * The use of "pindex" indicates that the argument is the index
+ * expressed in pairs instead of individual elements.
+ */
+#define H_NUMPAIRS(pg) (NUM_ENT(pg) / 2)
+#define H_KEYINDEX(pindx) (2 * (pindx))
+#define H_DATAINDEX(pindx) ((2 * (pindx)) + 1)
+#define H_PAIRKEY(pg, pindx) P_ENTRY(pg, H_KEYINDEX(pindx))
+#define H_PAIRDATA(pg, pindx) P_ENTRY(pg, H_DATAINDEX(pindx))
+#define H_PAIRSIZE(pg, psize, pindx) \
+ (LEN_HITEM(pg, psize, H_KEYINDEX(pindx)) + \
+ LEN_HITEM(pg, psize, H_DATAINDEX(pindx)))
+#define LEN_HDATA(p, psize, pindx) LEN_HKEYDATA(p, psize, H_DATAINDEX(pindx))
+#define LEN_HKEY(p, psize, pindx) LEN_HKEYDATA(p, psize, H_KEYINDEX(pindx))
+
+/*
+ * The third type is the H_OFFPAGE, represented by the HOFFPAGE structure:
+ */
+typedef struct _hoffpage {
+ u_int8_t type; /* 00: Page type and delete flag. */
+ u_int8_t unused[3]; /* 01-03: Padding, unused. */
+ db_pgno_t pgno; /* 04-07: Offpage page number. */
+ u_int32_t tlen; /* 08-11: Total length of item. */
+} HOFFPAGE;
+
+#define HOFFPAGE_PGNO(p) (((u_int8_t *)p) + SSZ(HOFFPAGE, pgno))
+#define HOFFPAGE_TLEN(p) (((u_int8_t *)p) + SSZ(HOFFPAGE, tlen))
+
+/*
+ * Page space required to add a new HOFFPAGE item to the page, with and
+ * without the index value.
+ */
+#define HOFFPAGE_SIZE (sizeof(HOFFPAGE))
+#define HOFFPAGE_PSIZE (HOFFPAGE_SIZE + sizeof(db_indx_t))
+
+/*
+ * The fourth type is H_OFFDUP represented by the HOFFDUP structure:
+ */
+typedef struct _hoffdup {
+ u_int8_t type; /* 00: Page type and delete flag. */
+ u_int8_t unused[3]; /* 01-03: Padding, unused. */
+ db_pgno_t pgno; /* 04-07: Offpage page number. */
+} HOFFDUP;
+#define HOFFDUP_PGNO(p) (((u_int8_t *)p) + SSZ(HOFFDUP, pgno))
+
+/*
+ * Page space required to add a new HOFFDUP item to the page, with and
+ * without the index value.
+ */
+#define HOFFDUP_SIZE (sizeof(HOFFDUP))
+#define HOFFDUP_PSIZE (HOFFDUP_SIZE + sizeof(db_indx_t))
+
+/************************************************************************
+ BTREE PAGE LAYOUT
+ ************************************************************************/
+
+/* Each index references a group of bytes on the page. */
+#define B_KEYDATA 1 /* Key/data item. */
+#define B_DUPLICATE 2 /* Duplicate key/data item. */
+#define B_OVERFLOW 3 /* Overflow key/data item. */
+
+/*
+ * We have to store a deleted entry flag in the page. The reason is complex,
+ * but the simple version is that we can't delete on-page items referenced by
+ * a cursor -- the return order of subsequent insertions might be wrong. The
+ * delete flag is an overload of the top bit of the type byte.
+ */
+#define B_DELETE (0x80)
+#define B_DCLR(t) (t) &= ~B_DELETE
+#define B_DSET(t) (t) |= B_DELETE
+#define B_DISSET(t) ((t) & B_DELETE)
+
+#define B_TYPE(t) ((t) & ~B_DELETE)
+#define B_TSET(t, type, deleted) { \
+ (t) = (type); \
+ if (deleted) \
+ B_DSET(t); \
+}
+
+/*
+ * The first type is B_KEYDATA, represented by the BKEYDATA structure:
+ */
+typedef struct _bkeydata {
+ db_indx_t len; /* 00-01: Key/data item length. */
+ u_int8_t type; /* 02: Page type AND DELETE FLAG. */
+ u_int8_t data[1]; /* Variable length key/data item. */
+} BKEYDATA;
+
+/* Get a BKEYDATA item for a specific index. */
+#define GET_BKEYDATA(pg, indx) \
+ ((BKEYDATA *)P_ENTRY(pg, indx))
+
+/*
+ * Page space required to add a new BKEYDATA item to the page, with and
+ * without the index value.
+ */
+#define BKEYDATA_SIZE(len) \
+ ALIGN((len) + SSZA(BKEYDATA, data), 4)
+#define BKEYDATA_PSIZE(len) \
+ (BKEYDATA_SIZE(len) + sizeof(db_indx_t))
+
+/*
+ * The second and third types are B_DUPLICATE and B_OVERFLOW, represented
+ * by the BOVERFLOW structure.
+ */
+typedef struct _boverflow {
+ db_indx_t unused1; /* 00-01: Padding, unused. */
+ u_int8_t type; /* 02: Page type AND DELETE FLAG. */
+ u_int8_t unused2; /* 03: Padding, unused. */
+ db_pgno_t pgno; /* 04-07: Next page number. */
+ u_int32_t tlen; /* 08-11: Total length of item. */
+} BOVERFLOW;
+
+/* Get a BOVERFLOW item for a specific index. */
+#define GET_BOVERFLOW(pg, indx) \
+ ((BOVERFLOW *)P_ENTRY(pg, indx))
+
+/*
+ * Page space required to add a new BOVERFLOW item to the page, with and
+ * without the index value.
+ */
+#define BOVERFLOW_SIZE \
+ ALIGN(sizeof(BOVERFLOW), 4)
+#define BOVERFLOW_PSIZE \
+ (BOVERFLOW_SIZE + sizeof(db_indx_t))
+
+/*
+ * Btree leaf and hash page layouts group indices in sets of two, one
+ * for the key and one for the data. Everything else does it in sets
+ * of one to save space. I use the following macros so that it's real
+ * obvious what's going on...
+ */
+#define O_INDX 1
+#define P_INDX 2
+
+/************************************************************************
+ BTREE INTERNAL PAGE LAYOUT
+ ************************************************************************/
+
+/*
+ * Btree internal entry.
+ */
+typedef struct _binternal {
+ db_indx_t len; /* 00-01: Key/data item length. */
+ u_int8_t type; /* 02: Page type AND DELETE FLAG. */
+ u_int8_t unused; /* 03: Padding, unused. */
+ db_pgno_t pgno; /* 04-07: Page number of referenced page. */
+ db_recno_t nrecs; /* 08-11: Subtree record count. */
+ u_int8_t data[1]; /* Variable length key item. */
+} BINTERNAL;
+
+/* Get a BINTERNAL item for a specific index. */
+#define GET_BINTERNAL(pg, indx) \
+ ((BINTERNAL *)P_ENTRY(pg, indx))
+
+/*
+ * Page space required to add a new BINTERNAL item to the page, with and
+ * without the index value.
+ */
+#define BINTERNAL_SIZE(len) \
+ ALIGN((len) + SSZA(BINTERNAL, data), 4)
+#define BINTERNAL_PSIZE(len) \
+ (BINTERNAL_SIZE(len) + sizeof(db_indx_t))
+
+/************************************************************************
+ RECNO INTERNAL PAGE LAYOUT
+ ************************************************************************/
+
+/*
+ * The recno internal entry.
+ *
+ * XXX
+ * Why not fold this into the db_indx_t structure, it's fixed length?
+ */
+typedef struct _rinternal {
+ db_pgno_t pgno; /* 00-03: Page number of referenced page. */
+ db_recno_t nrecs; /* 04-07: Subtree record count. */
+} RINTERNAL;
+
+/* Get a RINTERNAL item for a specific index. */
+#define GET_RINTERNAL(pg, indx) \
+ ((RINTERNAL *)P_ENTRY(pg, indx))
+
+/*
+ * Page space required to add a new RINTERNAL item to the page, with and
+ * without the index value.
+ */
+#define RINTERNAL_SIZE \
+ ALIGN(sizeof(RINTERNAL), 4)
+#define RINTERNAL_PSIZE \
+ (RINTERNAL_SIZE + sizeof(db_indx_t))
+#endif /* _DB_PAGE_H_ */
diff --git a/usr/src/cmd/sendmail/db/include/db_shash.h b/usr/src/cmd/sendmail/db/include/db_shash.h
new file mode 100644
index 0000000000..35ade395fc
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/include/db_shash.h
@@ -0,0 +1,106 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ *
+ * @(#)db_shash.h 10.3 (Sleepycat) 4/10/98
+ */
+
+/* Hash Headers */
+typedef SH_TAILQ_HEAD(hash_head) DB_HASHTAB;
+
+/*
+ * HASHLOOKUP --
+ *
+ * Look up something in a shared memory hash table. The "elt" argument
+ * should be a key, and cmp_func must know how to compare a key to whatever
+ * structure it is that appears in the hash table. The comparison function
+ * cmp_func is called as: cmp_func(lookup_elt, table_elt);
+ * begin: address of the beginning of the hash table.
+ * type: the structure type of the elements that are linked in each bucket.
+ * field: the name of the field by which the "type" structures are linked.
+ * elt: the item for which we are searching in the hash table.
+ * result: the variable into which we'll store the element if we find it.
+ * nelems: the number of buckets in the hash table.
+ * hash_func: the hash function that operates on elements of the type of elt
+ * cmp_func: compare elements of the type of elt with those in the table (of
+ * type "type").
+ *
+ * If the element is not in the hash table, this macro exits with result
+ * set to NULL.
+ */
+#define HASHLOOKUP(begin, type, field, elt, r, n, hash, cmp) do { \
+ DB_HASHTAB *__bucket; \
+ u_int32_t __ndx; \
+ \
+ __ndx = hash(elt) % (n); \
+ __bucket = &begin[__ndx]; \
+ for (r = SH_TAILQ_FIRST(__bucket, type); \
+ r != NULL; r = SH_TAILQ_NEXT(r, field, type)) \
+ if (cmp(elt, r)) \
+ break; \
+} while(0)
+
+/*
+ * HASHINSERT --
+ *
+ * Insert a new entry into the hash table. This assumes that lookup has
+ * failed; don't call it if you haven't already called HASHLOOKUP.
+ * begin: the beginning address of the hash table.
+ * type: the structure type of the elements that are linked in each bucket.
+ * field: the name of the field by which the "type" structures are linked.
+ * elt: the item to be inserted.
+ * nelems: the number of buckets in the hash table.
+ * hash_func: the hash function that operates on elements of the type of elt
+ */
+#define HASHINSERT(begin, type, field, elt, n, hash) do { \
+ u_int32_t __ndx; \
+ DB_HASHTAB *__bucket; \
+ \
+ __ndx = hash(elt) % (n); \
+ __bucket = &begin[__ndx]; \
+ SH_TAILQ_INSERT_HEAD(__bucket, elt, field, type); \
+} while(0)
+
+/*
+ * HASHREMOVE --
+ * Remove the entry with a key == elt.
+ * begin: address of the beginning of the hash table.
+ * type: the structure type of the elements that are linked in each bucket.
+ * field: the name of the field by which the "type" structures are linked.
+ * elt: the item to be deleted.
+ * nelems: the number of buckets in the hash table.
+ * hash_func: the hash function that operates on elements of the type of elt
+ * cmp_func: compare elements of the type of elt with those in the table (of
+ * type "type").
+ */
+#define HASHREMOVE(begin, type, field, elt, n, hash, cmp) { \
+ u_int32_t __ndx; \
+ DB_HASHTAB *__bucket; \
+ SH_TAILQ_ENTRY *__entp; \
+ \
+ __ndx = hash(elt) % (n); \
+ __bucket = &begin[__ndx]; \
+ HASHLOOKUP(begin, type, field, elt, __entp, n, hash, cmp); \
+ SH_TAILQ_REMOVE(__bucket, __entp, field, type); \
+}
+
+/*
+ * HASHREMOVE_EL --
+ * Given the object "obj" in the table, remove it.
+ * begin: address of the beginning of the hash table.
+ * type: the structure type of the elements that are linked in each bucket.
+ * field: the name of the field by which the "type" structures are linked.
+ * obj: the object in the table that we with to delete.
+ * nelems: the number of buckets in the hash table.
+ * hash_func: the hash function that operates on elements of the type of elt
+ */
+#define HASHREMOVE_EL(begin, type, field, obj, n, hash) { \
+ u_int32_t __ndx; \
+ DB_HASHTAB *__bucket; \
+ \
+ __ndx = hash(obj) % (n); \
+ __bucket = &begin[__ndx]; \
+ SH_TAILQ_REMOVE(__bucket, obj, field, type); \
+}
diff --git a/usr/src/cmd/sendmail/db/include/db_swap.h b/usr/src/cmd/sendmail/db/include/db_swap.h
new file mode 100644
index 0000000000..9f94ed721b
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/include/db_swap.h
@@ -0,0 +1,105 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+/*
+ * Copyright (c) 1990, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)db_swap.h 10.5 (Sleepycat) 4/10/98
+ */
+
+#ifndef _DB_SWAP_H_
+#define _DB_SWAP_H_
+
+/*
+ * Little endian <==> big endian 32-bit swap macros.
+ * M_32_SWAP swap a memory location
+ * P_32_COPY copy potentially unaligned 4 byte quantities
+ * P_32_SWAP swap a referenced memory location
+ */
+#define M_32_SWAP(a) { \
+ u_int32_t _tmp; \
+ _tmp = a; \
+ ((u_int8_t *)&a)[0] = ((u_int8_t *)&_tmp)[3]; \
+ ((u_int8_t *)&a)[1] = ((u_int8_t *)&_tmp)[2]; \
+ ((u_int8_t *)&a)[2] = ((u_int8_t *)&_tmp)[1]; \
+ ((u_int8_t *)&a)[3] = ((u_int8_t *)&_tmp)[0]; \
+}
+#define P_32_COPY(a, b) { \
+ ((u_int8_t *)b)[0] = ((u_int8_t *)a)[0]; \
+ ((u_int8_t *)b)[1] = ((u_int8_t *)a)[1]; \
+ ((u_int8_t *)b)[2] = ((u_int8_t *)a)[2]; \
+ ((u_int8_t *)b)[3] = ((u_int8_t *)a)[3]; \
+}
+#define P_32_SWAP(a) { \
+ u_int32_t _tmp; \
+ P_32_COPY(a, &_tmp); \
+ ((u_int8_t *)a)[0] = ((u_int8_t *)&_tmp)[3]; \
+ ((u_int8_t *)a)[1] = ((u_int8_t *)&_tmp)[2]; \
+ ((u_int8_t *)a)[2] = ((u_int8_t *)&_tmp)[1]; \
+ ((u_int8_t *)a)[3] = ((u_int8_t *)&_tmp)[0]; \
+}
+
+/*
+ * Little endian <==> big endian 16-bit swap macros.
+ * M_16_SWAP swap a memory location
+ * P_16_COPY copy potentially unaligned 2 byte quantities
+ * P_16_SWAP swap a referenced memory location
+ */
+#define M_16_SWAP(a) { \
+ u_int16_t _tmp; \
+ _tmp = (u_int16_t)a; \
+ ((u_int8_t *)&a)[0] = ((u_int8_t *)&_tmp)[1]; \
+ ((u_int8_t *)&a)[1] = ((u_int8_t *)&_tmp)[0]; \
+}
+#define P_16_COPY(a, b) { \
+ ((u_int8_t *)b)[0] = ((u_int8_t *)a)[0]; \
+ ((u_int8_t *)b)[1] = ((u_int8_t *)a)[1]; \
+}
+#define P_16_SWAP(a) { \
+ u_int16_t _tmp; \
+ P_16_COPY(a, &_tmp); \
+ ((u_int8_t *)a)[0] = ((u_int8_t *)&_tmp)[1]; \
+ ((u_int8_t *)a)[1] = ((u_int8_t *)&_tmp)[0]; \
+}
+
+#define SWAP32(p) { \
+ P_32_SWAP(p); \
+ (p) += sizeof(u_int32_t); \
+}
+#define SWAP16(p) { \
+ P_16_SWAP(p); \
+ (p) += sizeof(u_int16_t); \
+}
+#endif /* !_DB_SWAP_H_ */
diff --git a/usr/src/cmd/sendmail/db/include/hash.h b/usr/src/cmd/sendmail/db/include/hash.h
new file mode 100644
index 0000000000..5d85a2a3a7
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/include/hash.h
@@ -0,0 +1,199 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+/*
+ * Copyright (c) 1990, 1993, 1994
+ * Margo Seltzer. All rights reserved.
+ */
+/*
+ * Copyright (c) 1990, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Margo Seltzer.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)hash.h 10.14 (Sleepycat) 10/4/98
+ */
+
+/* Cursor structure definitions. */
+typedef struct cursor_t {
+ DBC *dbc;
+
+ /* Per-thread information */
+ DB_LOCK hlock; /* Metadata page lock. */
+ HASHHDR *hdr; /* Pointer to meta-data page. */
+ PAGE *split_buf; /* Temporary buffer for splits. */
+ struct __db_h_stat stats; /* Hash statistics. */
+
+ /* Hash cursor information */
+ db_pgno_t bucket; /* Bucket we are traversing. */
+ db_pgno_t lbucket; /* Bucket for which we are locked. */
+ DB_LOCK lock; /* Lock held on the current bucket. */
+ PAGE *pagep; /* The current page. */
+ db_pgno_t pgno; /* Current page number. */
+ db_indx_t bndx; /* Index within the current page. */
+ PAGE *dpagep; /* Duplicate page pointer. */
+ db_pgno_t dpgno; /* Duplicate page number. */
+ db_indx_t dndx; /* Index within a duplicate set. */
+ db_indx_t dup_off; /* Offset within a duplicate set. */
+ db_indx_t dup_len; /* Length of current duplicate. */
+ db_indx_t dup_tlen; /* Total length of duplicate entry. */
+ u_int32_t seek_size; /* Number of bytes we need for add. */
+ db_pgno_t seek_found_page;/* Page on which we can insert. */
+
+#define H_DELETED 0x0001 /* Cursor item is deleted. */
+#define H_DUPONLY 0x0002 /* Dups only; do not change key. */
+#define H_EXPAND 0x0004 /* Table expanded. */
+#define H_ISDUP 0x0008 /* Cursor is within duplicate set. */
+#define H_NOMORE 0x0010 /* No more entries in bucket. */
+#define H_OK 0x0020 /* Request succeeded. */
+#define H_DIRTY 0x0040 /* Meta-data page needs to be written */
+#define H_ORIGINAL 0x0080 /* Bucket lock existed on entry. */
+ u_int32_t flags;
+} HASH_CURSOR;
+
+#define IS_VALID(C) ((C)->bucket != BUCKET_INVALID)
+
+#define SAVE_CURSOR(ORIG, COPY) { \
+ F_SET((ORIG), H_ORIGINAL); \
+ *(COPY) = *(ORIG); \
+}
+
+#define RESTORE_CURSOR(D, ORIG, COPY, RET) { \
+ if ((RET) == 0) { \
+ if ((ORIG)->dbc->txn == NULL && \
+ (COPY)->lock != 0 && (ORIG)->lock != (COPY)->lock) \
+ (void)lock_put((D)->dbenv->lk_info, (COPY)->lock); \
+ } else { \
+ if ((ORIG)->dbc->txn == NULL && \
+ (ORIG)->lock != 0 && (ORIG)->lock != (COPY)->lock) \
+ (void)lock_put((D)->dbenv->lk_info, (ORIG)->lock); \
+ *ORIG = *COPY; \
+ } \
+}
+
+/*
+ * More interface macros used to get/release the meta data page.
+ */
+#define GET_META(D, I, R) { \
+ if (F_ISSET(D, DB_AM_LOCKING) && \
+ !F_ISSET((I)->dbc, DBC_RECOVER)) { \
+ (I)->dbc->lock.pgno = BUCKET_INVALID; \
+ (R) = lock_get((D)->dbenv->lk_info, (I)->dbc->locker, \
+ 0, &(I)->dbc->lock_dbt, DB_LOCK_READ, &(I)->hlock); \
+ (R) = (R) < 0 ? EAGAIN : (R); \
+ } \
+ if ((R) == 0 && \
+ ((R) = __ham_get_page(D, 0, (PAGE **)&((I)->hdr))) != 0 && \
+ (I)->hlock != LOCK_INVALID) { \
+ (void)lock_put((D)->dbenv->lk_info, (I)->hlock); \
+ (I)->hlock = LOCK_INVALID; \
+ } \
+}
+
+#define RELEASE_META(D, I) { \
+ if ((I)->hdr) \
+ (void)__ham_put_page(D, (PAGE *)(I)->hdr, \
+ F_ISSET(I, H_DIRTY) ? 1 : 0); \
+ (I)->hdr = NULL; \
+ if (!F_ISSET((I)->dbc, DBC_RECOVER) && \
+ (I)->dbc->txn == NULL && (I)->hlock) \
+ (void)lock_put((D)->dbenv->lk_info, (I)->hlock); \
+ (I)->hlock = LOCK_INVALID; \
+ F_CLR(I, H_DIRTY); \
+}
+
+#define DIRTY_META(D, I, R) { \
+ if (F_ISSET(D, DB_AM_LOCKING) && \
+ !F_ISSET((I)->dbc, DBC_RECOVER)) { \
+ DB_LOCK _tmp; \
+ (I)->dbc->lock.pgno = BUCKET_INVALID; \
+ if (((R) = lock_get((D)->dbenv->lk_info, \
+ (I)->dbc->locker, 0, &(I)->dbc->lock_dbt, \
+ DB_LOCK_WRITE, &_tmp)) == 0) \
+ (R) = lock_put((D)->dbenv->lk_info, (I)->hlock);\
+ else if ((R) < 0) \
+ (R) = EAGAIN; \
+ (I)->hlock = _tmp; \
+ } \
+ F_SET((I), H_DIRTY); \
+}
+
+/* Test string. */
+#define CHARKEY "%$sniglet^&"
+
+/* Overflow management */
+/*
+ * Overflow page numbers are allocated per split point. At each doubling of
+ * the table, we can allocate extra pages. We keep track of how many pages
+ * we've allocated at each point to calculate bucket to page number mapping.
+ */
+#define BUCKET_TO_PAGE(I, B) \
+ ((B) + 1 + ((B) ? (I)->hdr->spares[__db_log2((B)+1)-1] : 0))
+
+#define PGNO_OF(I, S, O) (BUCKET_TO_PAGE((I), (1 << (S)) - 1) + (O))
+
+/* Constraints about number of pages and how much data goes on a page. */
+
+#define MAX_PAGES(H) UINT32_T_MAX
+#define MINFILL 4
+#define ISBIG(I, N) (((N) > ((I)->hdr->pagesize / MINFILL)) ? 1 : 0)
+
+/* Shorthands for accessing structure */
+#define NDX_INVALID 0xFFFF
+#define BUCKET_INVALID 0xFFFFFFFF
+
+/* On page duplicates are stored as a string of size-data-size triples. */
+#define DUP_SIZE(len) ((len) + 2 * sizeof(db_indx_t))
+
+/* Log messages types (these are subtypes within a record type) */
+#define PAIR_KEYMASK 0x1
+#define PAIR_DATAMASK 0x2
+#define PAIR_ISKEYBIG(N) (N & PAIR_KEYMASK)
+#define PAIR_ISDATABIG(N) (N & PAIR_DATAMASK)
+#define OPCODE_OF(N) (N & ~(PAIR_KEYMASK | PAIR_DATAMASK))
+
+#define PUTPAIR 0x20
+#define DELPAIR 0x30
+#define PUTOVFL 0x40
+#define DELOVFL 0x50
+#define ALLOCPGNO 0x60
+#define DELPGNO 0x70
+#define SPLITOLD 0x80
+#define SPLITNEW 0x90
+
+#include "hash_auto.h"
+#include "hash_ext.h"
+#include "db_am.h"
+#include "common_ext.h"
diff --git a/usr/src/cmd/sendmail/db/include/hash_auto.h b/usr/src/cmd/sendmail/db/include/hash_auto.h
new file mode 100644
index 0000000000..6adad41e6d
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/include/hash_auto.h
@@ -0,0 +1,140 @@
+/* Do not edit: automatically built by dist/db_gen.sh. */
+
+/*
+ * Copyright (c) 1998 by Sun Microsystems, Inc.
+ * All rights reserved.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#ifndef ham_AUTO_H
+#define ham_AUTO_H
+
+#define DB_ham_insdel (DB_ham_BEGIN + 1)
+
+typedef struct _ham_insdel_args {
+ u_int32_t type;
+ DB_TXN *txnid;
+ DB_LSN prev_lsn;
+ u_int32_t opcode;
+ u_int32_t fileid;
+ db_pgno_t pgno;
+ u_int32_t ndx;
+ DB_LSN pagelsn;
+ DBT key;
+ DBT data;
+} __ham_insdel_args;
+
+
+#define DB_ham_newpage (DB_ham_BEGIN + 2)
+
+typedef struct _ham_newpage_args {
+ u_int32_t type;
+ DB_TXN *txnid;
+ DB_LSN prev_lsn;
+ u_int32_t opcode;
+ u_int32_t fileid;
+ db_pgno_t prev_pgno;
+ DB_LSN prevlsn;
+ db_pgno_t new_pgno;
+ DB_LSN pagelsn;
+ db_pgno_t next_pgno;
+ DB_LSN nextlsn;
+} __ham_newpage_args;
+
+
+#define DB_ham_splitmeta (DB_ham_BEGIN + 3)
+
+typedef struct _ham_splitmeta_args {
+ u_int32_t type;
+ DB_TXN *txnid;
+ DB_LSN prev_lsn;
+ u_int32_t fileid;
+ u_int32_t bucket;
+ u_int32_t ovflpoint;
+ u_int32_t spares;
+ DB_LSN metalsn;
+} __ham_splitmeta_args;
+
+
+#define DB_ham_splitdata (DB_ham_BEGIN + 4)
+
+typedef struct _ham_splitdata_args {
+ u_int32_t type;
+ DB_TXN *txnid;
+ DB_LSN prev_lsn;
+ u_int32_t fileid;
+ u_int32_t opcode;
+ db_pgno_t pgno;
+ DBT pageimage;
+ DB_LSN pagelsn;
+} __ham_splitdata_args;
+
+
+#define DB_ham_replace (DB_ham_BEGIN + 5)
+
+typedef struct _ham_replace_args {
+ u_int32_t type;
+ DB_TXN *txnid;
+ DB_LSN prev_lsn;
+ u_int32_t fileid;
+ db_pgno_t pgno;
+ u_int32_t ndx;
+ DB_LSN pagelsn;
+ int32_t off;
+ DBT olditem;
+ DBT newitem;
+ u_int32_t makedup;
+} __ham_replace_args;
+
+
+#define DB_ham_newpgno (DB_ham_BEGIN + 6)
+
+typedef struct _ham_newpgno_args {
+ u_int32_t type;
+ DB_TXN *txnid;
+ DB_LSN prev_lsn;
+ u_int32_t opcode;
+ u_int32_t fileid;
+ db_pgno_t pgno;
+ db_pgno_t free_pgno;
+ u_int32_t old_type;
+ db_pgno_t old_pgno;
+ u_int32_t new_type;
+ DB_LSN pagelsn;
+ DB_LSN metalsn;
+} __ham_newpgno_args;
+
+
+#define DB_ham_ovfl (DB_ham_BEGIN + 7)
+
+typedef struct _ham_ovfl_args {
+ u_int32_t type;
+ DB_TXN *txnid;
+ DB_LSN prev_lsn;
+ u_int32_t fileid;
+ db_pgno_t start_pgno;
+ u_int32_t npages;
+ db_pgno_t free_pgno;
+ u_int32_t ovflpoint;
+ DB_LSN metalsn;
+} __ham_ovfl_args;
+
+
+#define DB_ham_copypage (DB_ham_BEGIN + 8)
+
+typedef struct _ham_copypage_args {
+ u_int32_t type;
+ DB_TXN *txnid;
+ DB_LSN prev_lsn;
+ u_int32_t fileid;
+ db_pgno_t pgno;
+ DB_LSN pagelsn;
+ db_pgno_t next_pgno;
+ DB_LSN nextlsn;
+ db_pgno_t nnext_pgno;
+ DB_LSN nnextlsn;
+ DBT page;
+} __ham_copypage_args;
+
+#endif
diff --git a/usr/src/cmd/sendmail/db/include/hash_ext.h b/usr/src/cmd/sendmail/db/include/hash_ext.h
new file mode 100644
index 0000000000..fe17dc7b39
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/include/hash_ext.h
@@ -0,0 +1,130 @@
+/* DO NOT EDIT: automatically built by dist/distrib. */
+#ifndef _hash_ext_h_
+#define _hash_ext_h_
+int __ham_open __P((DB *, DB_INFO *));
+int __ham_close __P((DB *));
+int __ham_c_init __P((DBC *));
+u_int32_t __ham_call_hash __P((HASH_CURSOR *, u_int8_t *, int32_t));
+int __ham_init_dbt __P((DBT *, u_int32_t, void **, u_int32_t *));
+void __ham_c_update
+ __P((HASH_CURSOR *, db_pgno_t, u_int32_t, int, int));
+int __ham_insdel_log
+ __P((DB_LOG *, DB_TXN *, DB_LSN *, u_int32_t,
+ u_int32_t, u_int32_t, db_pgno_t, u_int32_t,
+ DB_LSN *, const DBT *, const DBT *));
+int __ham_insdel_print
+ __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+int __ham_insdel_read __P((void *, __ham_insdel_args **));
+int __ham_newpage_log
+ __P((DB_LOG *, DB_TXN *, DB_LSN *, u_int32_t,
+ u_int32_t, u_int32_t, db_pgno_t, DB_LSN *,
+ db_pgno_t, DB_LSN *, db_pgno_t, DB_LSN *));
+int __ham_newpage_print
+ __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+int __ham_newpage_read __P((void *, __ham_newpage_args **));
+int __ham_splitmeta_log
+ __P((DB_LOG *, DB_TXN *, DB_LSN *, u_int32_t,
+ u_int32_t, u_int32_t, u_int32_t, u_int32_t,
+ DB_LSN *));
+int __ham_splitmeta_print
+ __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+int __ham_splitmeta_read __P((void *, __ham_splitmeta_args **));
+int __ham_splitdata_log
+ __P((DB_LOG *, DB_TXN *, DB_LSN *, u_int32_t,
+ u_int32_t, u_int32_t, db_pgno_t, const DBT *,
+ DB_LSN *));
+int __ham_splitdata_print
+ __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+int __ham_splitdata_read __P((void *, __ham_splitdata_args **));
+int __ham_replace_log
+ __P((DB_LOG *, DB_TXN *, DB_LSN *, u_int32_t,
+ u_int32_t, db_pgno_t, u_int32_t, DB_LSN *,
+ int32_t, const DBT *, const DBT *, u_int32_t));
+int __ham_replace_print
+ __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+int __ham_replace_read __P((void *, __ham_replace_args **));
+int __ham_newpgno_log
+ __P((DB_LOG *, DB_TXN *, DB_LSN *, u_int32_t,
+ u_int32_t, u_int32_t, db_pgno_t, db_pgno_t,
+ u_int32_t, db_pgno_t, u_int32_t, DB_LSN *,
+ DB_LSN *));
+int __ham_newpgno_print
+ __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+int __ham_newpgno_read __P((void *, __ham_newpgno_args **));
+int __ham_ovfl_log
+ __P((DB_LOG *, DB_TXN *, DB_LSN *, u_int32_t,
+ u_int32_t, db_pgno_t, u_int32_t, db_pgno_t,
+ u_int32_t, DB_LSN *));
+int __ham_ovfl_print
+ __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+int __ham_ovfl_read __P((void *, __ham_ovfl_args **));
+int __ham_copypage_log
+ __P((DB_LOG *, DB_TXN *, DB_LSN *, u_int32_t,
+ u_int32_t, db_pgno_t, DB_LSN *, db_pgno_t,
+ DB_LSN *, db_pgno_t, DB_LSN *, const DBT *));
+int __ham_copypage_print
+ __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+int __ham_copypage_read __P((void *, __ham_copypage_args **));
+int __ham_init_print __P((DB_ENV *));
+int __ham_init_recover __P((DB_ENV *));
+int __ham_pgin __P((db_pgno_t, void *, DBT *));
+int __ham_pgout __P((db_pgno_t, void *, DBT *));
+int __ham_mswap __P((void *));
+int __ham_add_dup __P((DBC *, DBT *, u_int32_t));
+void __ham_move_offpage __P((DBC *, PAGE *, u_int32_t, db_pgno_t));
+void __ham_dsearch __P((DBC *, DBT *, u_int32_t *, int *));
+u_int32_t __ham_func2 __P((const void *, u_int32_t));
+u_int32_t __ham_func3 __P((const void *, u_int32_t));
+u_int32_t __ham_func4 __P((const void *, u_int32_t));
+u_int32_t __ham_func5 __P((const void *, u_int32_t));
+int __ham_item __P((DBC *, db_lockmode_t));
+int __ham_item_reset __P((DBC *));
+void __ham_item_init __P((HASH_CURSOR *));
+int __ham_item_done __P((DBC *, int));
+int __ham_item_last __P((DBC *, db_lockmode_t));
+int __ham_item_first __P((DBC *, db_lockmode_t));
+int __ham_item_prev __P((DBC *, db_lockmode_t));
+int __ham_item_next __P((DBC *, db_lockmode_t));
+void __ham_putitem __P((PAGE *p, const DBT *, int));
+void __ham_reputpair
+ __P((PAGE *p, u_int32_t, u_int32_t, const DBT *, const DBT *));
+int __ham_del_pair __P((DBC *, int));
+int __ham_replpair __P((DBC *, DBT *, u_int32_t));
+void __ham_onpage_replace __P((PAGE *, size_t, u_int32_t, int32_t,
+ int32_t, DBT *));
+int __ham_split_page __P((DBC *, u_int32_t, u_int32_t));
+int __ham_add_el __P((DBC *, const DBT *, const DBT *, int));
+void __ham_copy_item __P((size_t, PAGE *, u_int32_t, PAGE *));
+int __ham_add_ovflpage __P((DBC *, PAGE *, int, PAGE **));
+int __ham_new_page __P((DB *, u_int32_t, u_int32_t, PAGE **));
+int __ham_del_page __P((DBC *, PAGE *));
+int __ham_put_page __P((DB *, PAGE *, int32_t));
+int __ham_dirty_page __P((DB *, PAGE *));
+int __ham_get_page __P((DB *, db_pgno_t, PAGE **));
+int __ham_overflow_page
+ __P((DBC *, u_int32_t, PAGE **));
+#ifdef DEBUG
+db_pgno_t __bucket_to_page __P((HASH_CURSOR *, db_pgno_t));
+#endif
+void __ham_init_ovflpages __P((DBC *));
+int __ham_get_cpage __P((DBC *, db_lockmode_t));
+int __ham_next_cpage __P((DBC *, db_pgno_t, int, u_int32_t));
+void __ham_dpair __P((DB *, PAGE *, u_int32_t));
+int __ham_insdel_recover
+ __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+int __ham_newpage_recover
+ __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+int __ham_replace_recover
+ __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+int __ham_newpgno_recover
+ __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+int __ham_splitmeta_recover
+ __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+int __ham_splitdata_recover
+ __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+int __ham_ovfl_recover
+ __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+int __ham_copypage_recover
+ __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+int __ham_stat __P((DB *, void *, void *(*)(size_t), u_int32_t));
+#endif /* _hash_ext_h_ */
diff --git a/usr/src/cmd/sendmail/db/include/lock.h b/usr/src/cmd/sendmail/db/include/lock.h
new file mode 100644
index 0000000000..13364ca7a5
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/include/lock.h
@@ -0,0 +1,197 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ *
+ * @(#)lock.h 10.17 (Sleepycat) 1/3/99
+ */
+
+typedef struct __db_lockobj DB_LOCKOBJ;
+
+#define DB_DEFAULT_LOCK_FILE "__db_lock.share"
+
+#ifndef DB_LOCK_DEFAULT_N
+#define DB_LOCK_DEFAULT_N 5000 /* Default # of locks in region. */
+#endif
+
+/*
+ * The locker id space is divided between the transaction manager and the lock
+ * manager. Lockid's start at 0 and go to DB_LOCK_MAXID. Txn Id's start at
+ * DB_LOCK_MAXID + 1 and go up to TXN_INVALID.
+ */
+#define DB_LOCK_MAXID 0x7fffffff
+
+/* Check for region catastrophic shutdown. */
+#define LOCK_PANIC_CHECK(lt) { \
+ if ((lt)->region->hdr.panic) \
+ return (DB_RUNRECOVERY); \
+}
+
+/*
+ * The lock region consists of:
+ * The DB_LOCKREGION structure (sizeof(DB_LOCKREGION)).
+ * The conflict matrix of nmodes * nmodes bytes (nmodes * nmodes).
+ * The hash table for object lookup (hashsize * sizeof(DB_OBJ *)).
+ * The locks themselves (maxlocks * sizeof(struct __db_lock).
+ * The objects being locked (maxlocks * sizeof(DB_OBJ)).
+ * String space to represent the DBTs that are the objects being locked.
+ */
+struct __db_lockregion {
+ RLAYOUT hdr; /* Shared region header. */
+ u_int32_t magic; /* lock magic number */
+ u_int32_t version; /* version number */
+ u_int32_t id; /* unique id generator */
+ u_int32_t need_dd; /* flag for deadlock detector */
+ u_int32_t detect; /* run dd on every conflict */
+ SH_TAILQ_HEAD(lock_header) free_locks; /* free lock header */
+ SH_TAILQ_HEAD(obj_header) free_objs; /* free obj header */
+ u_int32_t maxlocks; /* maximum number of locks in table */
+ u_int32_t table_size; /* size of hash table */
+ u_int32_t nmodes; /* number of lock modes */
+ u_int32_t numobjs; /* number of objects */
+ u_int32_t nlockers; /* number of lockers */
+ size_t increment; /* how much to grow region */
+ size_t hash_off; /* offset of hash table */
+ size_t mem_off; /* offset of memory region */
+ size_t mem_bytes; /* number of bytes in memory region */
+ u_int32_t nconflicts; /* number of lock conflicts */
+ u_int32_t nrequests; /* number of lock gets */
+ u_int32_t nreleases; /* number of lock puts */
+ u_int32_t ndeadlocks; /* number of deadlocks */
+};
+
+/* Macros to lock/unlock the region. */
+#define LOCK_LOCKREGION(lt) \
+ (void)__db_mutex_lock(&(lt)->region->hdr.lock, (lt)->reginfo.fd)
+#define UNLOCK_LOCKREGION(lt) \
+ (void)__db_mutex_unlock(&(lt)->region->hdr.lock, (lt)->reginfo.fd)
+
+/*
+ * Since we will be keeping DBTs in shared memory, we need the equivalent
+ * of a DBT that will work in shared memory.
+ */
+typedef struct __sh_dbt {
+ u_int32_t size;
+ ssize_t off;
+} SH_DBT;
+
+#define SH_DBT_PTR(p) ((void *)(((u_int8_t *)(p)) + (p)->off))
+
+struct __db_lockobj {
+ SH_DBT lockobj; /* Identifies object locked. */
+ SH_TAILQ_ENTRY links; /* Links for free list. */
+ union {
+ SH_TAILQ_HEAD(_wait) _waiters; /* List of waiting locks. */
+ u_int32_t _dd_id; /* Deadlock detector id. */
+ } wlinks;
+ union {
+ SH_LIST_HEAD(_held) _heldby; /* Locks held by this locker. */
+ SH_TAILQ_HEAD(_hold) _holders; /* List of held locks. */
+ } dlinks;
+#define DB_LOCK_OBJTYPE 1
+#define DB_LOCK_LOCKER 2
+ /* Allocate room in the object to
+ * hold typical DB lock structures
+ * so that we do not have to
+ * allocate them from shalloc. */
+ u_int8_t objdata[sizeof(struct __db_ilock)];
+ u_int8_t type; /* Real object or locker id. */
+};
+
+#define dd_id wlinks._dd_id
+#define waiters wlinks._waiters
+#define holders dlinks._holders
+#define heldby dlinks._heldby
+
+/*
+ * The lock table is the per-process cookie returned from a lock_open call.
+ */
+struct __db_locktab {
+ DB_ENV *dbenv; /* Environment. */
+ REGINFO reginfo; /* Region information. */
+ DB_LOCKREGION *region; /* Address of shared memory region. */
+ DB_HASHTAB *hashtab; /* Beginning of hash table. */
+ void *mem; /* Beginning of string space. */
+ u_int8_t *conflicts; /* Pointer to conflict matrix. */
+};
+
+/* Test for conflicts. */
+#define CONFLICTS(T, HELD, WANTED) \
+ T->conflicts[HELD * T->region->nmodes + WANTED]
+
+/*
+ * Resources in the lock region. Used to indicate which resource
+ * is running low when we need to grow the region.
+ */
+typedef enum {
+ DB_LOCK_MEM, DB_LOCK_OBJ, DB_LOCK_LOCK
+} db_resource_t;
+
+struct __db_lock {
+ /*
+ * Wait on mutex to wait on lock. You reference your own mutex with
+ * ID 0 and others reference your mutex with ID 1.
+ */
+ db_mutex_t mutex;
+
+ u_int32_t holder; /* Who holds this lock. */
+ SH_TAILQ_ENTRY links; /* Free or holder/waiter list. */
+ SH_LIST_ENTRY locker_links; /* List of locks held by a locker. */
+ u_int32_t refcount; /* Reference count the lock. */
+ db_lockmode_t mode; /* What sort of lock. */
+ ssize_t obj; /* Relative offset of object struct. */
+ size_t txnoff; /* Offset of holding transaction. */
+ db_status_t status; /* Status of this lock. */
+};
+
+/*
+ * This is a serious layering violation. To support nested transactions, we
+ * need to be able to tell that a lock is held by a transaction (as opposed to
+ * some other locker) and to be able to traverse the parent/descendent chain.
+ * In order to do this, each lock held by a transaction maintains a reference
+ * to the shared memory transaction structure so it can be accessed during lock
+ * promotion. As the structure is in shared memory, we cannot store a pointer
+ * to it, so we use the offset within the region. As nothing lives at region
+ * offset 0, we use that to indicate that there is no transaction associated
+ * with the current lock.
+ */
+#define TXN_IS_HOLDING(L) ((L)->txnoff != 0 /* INVALID_REG_OFFSET */)
+
+/*
+ * We cannot return pointers to the user (else we cannot easily grow regions),
+ * so we return offsets in the region. These must be converted to and from
+ * regular pointers. Always use the macros below.
+ */
+#define OFFSET_TO_LOCK(lt, off) \
+ ((struct __db_lock *)((u_int8_t *)((lt)->region) + (off)))
+#define LOCK_TO_OFFSET(lt, lock) \
+ ((size_t)((u_int8_t *)(lock) - (u_int8_t *)lt->region))
+#define OFFSET_TO_OBJ(lt, off) \
+ ((DB_LOCKOBJ *)((u_int8_t *)((lt)->region) + (off)))
+#define OBJ_TO_OFFSET(lt, obj) \
+ ((size_t)((u_int8_t *)(obj) - (u_int8_t *)lt->region))
+
+/*
+ * The lock header contains the region structure and the conflict matrix.
+ * Aligned to a large boundary because we don't know what the underlying
+ * type of the hash table elements are.
+ */
+#define LOCK_HASH_ALIGN 8
+#define LOCK_HEADER_SIZE(M) \
+ ((size_t)(sizeof(DB_LOCKREGION) + ALIGN((M * M), LOCK_HASH_ALIGN)))
+
+/*
+ * For the full region, we need to add the locks, the objects, the hash table
+ * and the string space (which is 16 bytes per lock).
+ */
+#define STRING_SIZE(N) (16 * N)
+
+#define LOCK_REGION_SIZE(M, N, H) \
+ (ALIGN(LOCK_HEADER_SIZE(M) + \
+ (H) * sizeof(DB_HASHTAB), MUTEX_ALIGNMENT) + \
+ (N) * ALIGN(sizeof(struct __db_lock), MUTEX_ALIGNMENT) + \
+ ALIGN((N) * sizeof(DB_LOCKOBJ), sizeof(size_t)) + \
+ ALIGN(STRING_SIZE(N), sizeof(size_t)))
+
+#include "lock_ext.h"
diff --git a/usr/src/cmd/sendmail/db/include/lock_ext.h b/usr/src/cmd/sendmail/db/include/lock_ext.h
new file mode 100644
index 0000000000..ce7994774a
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/include/lock_ext.h
@@ -0,0 +1,20 @@
+/* DO NOT EDIT: automatically built by dist/distrib. */
+#ifndef _lock_ext_h_
+#define _lock_ext_h_
+int __lock_is_locked
+ __P((DB_LOCKTAB *, u_int32_t, DBT *, db_lockmode_t));
+void __lock_printlock __P((DB_LOCKTAB *, struct __db_lock *, int));
+int __lock_getobj __P((DB_LOCKTAB *,
+ u_int32_t, const DBT *, u_int32_t type, DB_LOCKOBJ **));
+int __lock_downgrade __P((DB_LOCKTAB *,
+ DB_LOCK, db_lockmode_t, u_int32_t));
+void __lock_panic __P((DB_ENV *));
+int __lock_validate_region __P((DB_LOCKTAB *));
+int __lock_grow_region __P((DB_LOCKTAB *, int, size_t));
+void __lock_dump_region __P((DB_LOCKTAB *, char *, FILE *));
+int __lock_cmp __P((const DBT *, DB_LOCKOBJ *));
+int __lock_locker_cmp __P((u_int32_t, DB_LOCKOBJ *));
+u_int32_t __lock_ohash __P((const DBT *));
+u_int32_t __lock_lhash __P((DB_LOCKOBJ *));
+u_int32_t __lock_locker_hash __P((u_int32_t));
+#endif /* _lock_ext_h_ */
diff --git a/usr/src/cmd/sendmail/db/include/log.h b/usr/src/cmd/sendmail/db/include/log.h
new file mode 100644
index 0000000000..1e10e20bcf
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/include/log.h
@@ -0,0 +1,205 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ *
+ * @(#)log.h 10.30 (Sleepycat) 10/11/98
+ */
+
+#ifndef _LOG_H_
+#define _LOG_H_
+
+struct __fname; typedef struct __fname FNAME;
+struct __hdr; typedef struct __hdr HDR;
+struct __log; typedef struct __log LOG;
+struct __log_persist; typedef struct __log_persist LOGP;
+
+#ifndef MAXLFNAME
+#define LFPREFIX "log." /* Log file name prefix. */
+#define LFNAME "log.%010d" /* Log file name template. */
+#define LFNAME_V1 "log.%05d" /* Log file name template, rev 1. */
+#define MAXLFNAME 2000000000 /* Maximum log file name. */
+#endif
+ /* Default log name. */
+#define DB_DEFAULT_LOG_FILE "__db_log.share"
+
+#define DEFAULT_MAX (10 * MEGABYTE) /* 10 Mb. */
+
+/* Macros to lock/unlock the region and threads. */
+#define LOCK_LOGTHREAD(dblp) \
+ if (F_ISSET(dblp, DB_AM_THREAD)) \
+ (void)__db_mutex_lock((dblp)->mutexp, -1)
+#define UNLOCK_LOGTHREAD(dblp) \
+ if (F_ISSET(dblp, DB_AM_THREAD)) \
+ (void)__db_mutex_unlock((dblp)->mutexp, -1);
+#define LOCK_LOGREGION(dblp) \
+ (void)__db_mutex_lock(&((RLAYOUT *)(dblp)->lp)->lock, \
+ (dblp)->reginfo.fd)
+#define UNLOCK_LOGREGION(dblp) \
+ (void)__db_mutex_unlock(&((RLAYOUT *)(dblp)->lp)->lock, \
+ (dblp)->reginfo.fd)
+
+/* Check for region catastrophic shutdown. */
+#define LOG_PANIC_CHECK(dblp) { \
+ if ((dblp)->lp->rlayout.panic) \
+ return (DB_RUNRECOVERY); \
+}
+
+/*
+ * The per-process table that maps log file-id's to DB structures.
+ */
+typedef struct __db_entry {
+ DB *dbp; /* Associated DB structure. */
+ char *name; /* File name. */
+ u_int32_t refcount; /* Reference counted. */
+ int deleted; /* File was not found during open. */
+} DB_ENTRY;
+
+/*
+ * DB_LOG
+ * Per-process log structure.
+ */
+struct __db_log {
+/* These fields need to be protected for multi-threaded support. */
+ db_mutex_t *mutexp; /* Mutex for thread protection. */
+
+ DB_ENTRY *dbentry; /* Recovery file-id mapping. */
+#define DB_GROW_SIZE 64
+ u_int32_t dbentry_cnt; /* Entries. Grows by DB_GROW_SIZE. */
+
+/*
+ * These fields are always accessed while the region lock is held, so they do
+ * not have to be protected by the thread lock as well OR, they are only used
+ * when threads are not being used, i.e. most cursor operations are disallowed
+ * on threaded logs.
+ */
+ u_int32_t lfname; /* Log file "name". */
+ int lfd; /* Log file descriptor. */
+
+ DB_LSN c_lsn; /* Cursor: current LSN. */
+ DBT c_dbt; /* Cursor: return DBT structure. */
+ int c_fd; /* Cursor: file descriptor. */
+ u_int32_t c_off; /* Cursor: previous record offset. */
+ u_int32_t c_len; /* Cursor: current record length. */
+
+/* These fields are not protected. */
+ LOG *lp; /* Address of the shared LOG. */
+
+ DB_ENV *dbenv; /* Reference to error information. */
+ REGINFO reginfo; /* Region information. */
+
+ void *addr; /* Address of shalloc() region. */
+
+ char *dir; /* Directory argument. */
+
+/*
+ * These fields are used by XA; since XA forbids threaded execution, these
+ * do not have to be protected.
+ */
+ void *xa_info; /* Committed transaction list that
+ * has to be carried between calls
+ * to xa_recover. */
+ DB_LSN xa_lsn; /* Position of an XA recovery scan. */
+ DB_LSN xa_first; /* LSN to which we need to roll back
+ for this XA recovery scan. */
+
+ /*
+ * !!!
+ * Currently used to hold:
+ * DB_AM_THREAD (a DB flag)
+ * DBC_RECOVER (a DBC flag)
+ * If they are ever the same bits, we're in serious trouble.
+ */
+#if DB_AM_THREAD == DBC_RECOVER
+ DB_AM_THREAD, DBC_RECOVER, FLAG MISMATCH
+#endif
+ u_int32_t flags;
+};
+
+/*
+ * HDR --
+ * Log record header.
+ */
+struct __hdr {
+ u_int32_t prev; /* Previous offset. */
+ u_int32_t cksum; /* Current checksum. */
+ u_int32_t len; /* Current length. */
+};
+
+struct __log_persist {
+ u_int32_t magic; /* DB_LOGMAGIC */
+ u_int32_t version; /* DB_LOGVERSION */
+
+ u_int32_t lg_max; /* Maximum file size. */
+ int mode; /* Log file mode. */
+};
+
+/*
+ * LOG --
+ * Shared log region. One of these is allocated in shared memory,
+ * and describes the log.
+ */
+struct __log {
+ RLAYOUT rlayout; /* General region information. */
+
+ LOGP persist; /* Persistent information. */
+
+ SH_TAILQ_HEAD(__fq) fq; /* List of file names. */
+
+ /*
+ * The lsn LSN is the file offset that we're about to write and which
+ * we will return to the user.
+ */
+ DB_LSN lsn; /* LSN at current file offset. */
+
+ /*
+ * The s_lsn LSN is the last LSN that we know is on disk, not just
+ * written, but synced.
+ */
+ DB_LSN s_lsn; /* LSN of the last sync. */
+
+ u_int32_t len; /* Length of the last record. */
+
+ u_int32_t w_off; /* Current write offset in the file. */
+
+ DB_LSN chkpt_lsn; /* LSN of the last checkpoint. */
+ time_t chkpt; /* Time of the last checkpoint. */
+
+ DB_LOG_STAT stat; /* Log statistics. */
+
+ /*
+ * The f_lsn LSN is the LSN (returned to the user) that "owns" the
+ * first byte of the buffer. If the record associated with the LSN
+ * spans buffers, it may not reflect the physical file location of
+ * the first byte of the buffer.
+ */
+ DB_LSN f_lsn; /* LSN of first byte in the buffer. */
+ size_t b_off; /* Current offset in the buffer. */
+ u_int8_t buf[4 * 1024]; /* Log buffer. */
+};
+
+/*
+ * FNAME --
+ * File name and id.
+ */
+struct __fname {
+ SH_TAILQ_ENTRY q; /* File name queue. */
+
+ u_int16_t ref; /* Reference count. */
+
+ u_int32_t id; /* Logging file id. */
+ DBTYPE s_type; /* Saved DB type. */
+
+ size_t name_off; /* Name offset. */
+ u_int8_t ufid[DB_FILE_ID_LEN]; /* Unique file id. */
+};
+
+/* File open/close register log record opcodes. */
+#define LOG_CHECKPOINT 1 /* Checkpoint: file name/id dump. */
+#define LOG_CLOSE 2 /* File close. */
+#define LOG_OPEN 3 /* File open. */
+
+#include "log_auto.h"
+#include "log_ext.h"
+#endif /* _LOG_H_ */
diff --git a/usr/src/cmd/sendmail/db/include/log_auto.h b/usr/src/cmd/sendmail/db/include/log_auto.h
new file mode 100644
index 0000000000..dbc18b2dd6
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/include/log_auto.h
@@ -0,0 +1,26 @@
+/* Do not edit: automatically built by dist/db_gen.sh. */
+
+/*
+ * Copyright (c) 1998 by Sun Microsystems, Inc.
+ * All rights reserved.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#ifndef log_AUTO_H
+#define log_AUTO_H
+
+#define DB_log_register (DB_log_BEGIN + 1)
+
+typedef struct _log_register_args {
+ u_int32_t type;
+ DB_TXN *txnid;
+ DB_LSN prev_lsn;
+ u_int32_t opcode;
+ DBT name;
+ DBT uid;
+ u_int32_t id;
+ DBTYPE ftype;
+} __log_register_args;
+
+#endif
diff --git a/usr/src/cmd/sendmail/db/include/log_ext.h b/usr/src/cmd/sendmail/db/include/log_ext.h
new file mode 100644
index 0000000000..0eb414bd85
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/include/log_ext.h
@@ -0,0 +1,26 @@
+/* DO NOT EDIT: automatically built by dist/distrib. */
+#ifndef _log_ext_h_
+#define _log_ext_h_
+void __log_panic __P((DB_ENV *));
+int __log_find __P((DB_LOG *, int, int *));
+int __log_valid __P((DB_LOG *, u_int32_t, int));
+int __log_register_log
+ __P((DB_LOG *, DB_TXN *, DB_LSN *, u_int32_t,
+ u_int32_t, const DBT *, const DBT *, u_int32_t,
+ DBTYPE));
+int __log_register_print
+ __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+int __log_register_read __P((void *, __log_register_args **));
+int __log_init_print __P((DB_ENV *));
+int __log_init_recover __P((DB_ENV *));
+int __log_findckp __P((DB_LOG *, DB_LSN *));
+int __log_get __P((DB_LOG *, DB_LSN *, DBT *, u_int32_t, int));
+int __log_put __P((DB_LOG *, DB_LSN *, const DBT *, u_int32_t));
+int __log_name __P((DB_LOG *, u_int32_t, char **, int *, u_int32_t));
+int __log_register_recover
+ __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+int __log_add_logid __P((DB_LOG *, DB *, const char *, u_int32_t));
+int __db_fileid_to_db __P((DB_LOG *, DB **, u_int32_t));
+void __log_close_files __P((DB_LOG *));
+void __log_rem_logid __P((DB_LOG *, u_int32_t));
+#endif /* _log_ext_h_ */
diff --git a/usr/src/cmd/sendmail/db/include/mp.h b/usr/src/cmd/sendmail/db/include/mp.h
new file mode 100644
index 0000000000..904bccfe98
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/include/mp.h
@@ -0,0 +1,299 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ *
+ * @(#)mp.h 10.37 (Sleepycat) 1/1/99
+ */
+
+struct __bh; typedef struct __bh BH;
+struct __db_mpreg; typedef struct __db_mpreg DB_MPREG;
+struct __mpool; typedef struct __mpool MPOOL;
+struct __mpoolfile; typedef struct __mpoolfile MPOOLFILE;
+
+ /* Default mpool name. */
+#define DB_DEFAULT_MPOOL_FILE "__db_mpool.share"
+
+/*
+ * We default to 256K (32 8K pages) if the user doesn't specify, and
+ * require a minimum of 20K.
+ */
+#ifndef DB_CACHESIZE_DEF
+#define DB_CACHESIZE_DEF (256 * 1024)
+#endif
+#define DB_CACHESIZE_MIN ( 20 * 1024)
+
+#define INVALID 0 /* Invalid shared memory offset. */
+
+/*
+ * There are three ways we do locking in the mpool code:
+ *
+ * Locking a handle mutex to provide concurrency for DB_THREAD operations.
+ * Locking the region mutex to provide mutual exclusion while reading and
+ * writing structures in the shared region.
+ * Locking buffer header mutexes during I/O.
+ *
+ * The first will not be further described here. We use the shared mpool
+ * region lock to provide mutual exclusion while reading/modifying all of
+ * the data structures, including the buffer headers. We use a per-buffer
+ * header lock to wait on buffer I/O. The order of locking is as follows:
+ *
+ * Searching for a buffer:
+ * Acquire the region lock.
+ * Find the buffer header.
+ * Increment the reference count (guarantee the buffer stays).
+ * While the BH_LOCKED flag is set (I/O is going on) {
+ * Release the region lock.
+ * Explicitly yield the processor if it's not the first pass
+ * through this loop, otherwise, we can simply spin because
+ * we'll be simply switching between the two locks.
+ * Request the buffer lock.
+ * The I/O will complete...
+ * Acquire the buffer lock.
+ * Release the buffer lock.
+ * Acquire the region lock.
+ * }
+ * Return the buffer.
+ *
+ * Reading/writing a buffer:
+ * Acquire the region lock.
+ * Find/create the buffer header.
+ * If reading, increment the reference count (guarantee the buffer stays).
+ * Set the BH_LOCKED flag.
+ * Acquire the buffer lock (guaranteed not to block).
+ * Release the region lock.
+ * Do the I/O and/or initialize the buffer contents.
+ * Release the buffer lock.
+ * At this point, the buffer lock is available, but the logical
+ * operation (flagged by BH_LOCKED) is not yet completed. For
+ * this reason, among others, threads checking the BH_LOCKED flag
+ * must loop around their test.
+ * Acquire the region lock.
+ * Clear the BH_LOCKED flag.
+ * Release the region lock.
+ * Return/discard the buffer.
+ *
+ * Pointers to DB_MPOOL, MPOOL, DB_MPOOLFILE and MPOOLFILE structures are not
+ * reacquired when a region lock is reacquired because they couldn't have been
+ * closed/discarded and because they never move in memory.
+ */
+#define LOCKINIT(dbmp, mutexp) \
+ if (F_ISSET(dbmp, MP_LOCKHANDLE | MP_LOCKREGION)) \
+ (void)__db_mutex_init(mutexp, \
+ MUTEX_LOCK_OFFSET((dbmp)->reginfo.addr, mutexp))
+
+#define LOCKHANDLE(dbmp, mutexp) \
+ if (F_ISSET(dbmp, MP_LOCKHANDLE)) \
+ (void)__db_mutex_lock(mutexp, (dbmp)->reginfo.fd)
+#define UNLOCKHANDLE(dbmp, mutexp) \
+ if (F_ISSET(dbmp, MP_LOCKHANDLE)) \
+ (void)__db_mutex_unlock(mutexp, (dbmp)->reginfo.fd)
+
+#define LOCKREGION(dbmp) \
+ if (F_ISSET(dbmp, MP_LOCKREGION)) \
+ (void)__db_mutex_lock(&((RLAYOUT *)(dbmp)->mp)->lock, \
+ (dbmp)->reginfo.fd)
+#define UNLOCKREGION(dbmp) \
+ if (F_ISSET(dbmp, MP_LOCKREGION)) \
+ (void)__db_mutex_unlock(&((RLAYOUT *)(dbmp)->mp)->lock, \
+ (dbmp)->reginfo.fd)
+
+#define LOCKBUFFER(dbmp, bhp) \
+ if (F_ISSET(dbmp, MP_LOCKREGION)) \
+ (void)__db_mutex_lock(&(bhp)->mutex, (dbmp)->reginfo.fd)
+#define UNLOCKBUFFER(dbmp, bhp) \
+ if (F_ISSET(dbmp, MP_LOCKREGION)) \
+ (void)__db_mutex_unlock(&(bhp)->mutex, (dbmp)->reginfo.fd)
+
+/* Check for region catastrophic shutdown. */
+#define MP_PANIC_CHECK(dbmp) { \
+ if ((dbmp)->mp->rlayout.panic) \
+ return (DB_RUNRECOVERY); \
+}
+
+/*
+ * DB_MPOOL --
+ * Per-process memory pool structure.
+ */
+struct __db_mpool {
+/* These fields need to be protected for multi-threaded support. */
+ db_mutex_t *mutexp; /* Structure lock. */
+
+ /* List of pgin/pgout routines. */
+ LIST_HEAD(__db_mpregh, __db_mpreg) dbregq;
+
+ /* List of DB_MPOOLFILE's. */
+ TAILQ_HEAD(__db_mpoolfileh, __db_mpoolfile) dbmfq;
+
+/* These fields are not protected. */
+ DB_ENV *dbenv; /* Reference to error information. */
+ REGINFO reginfo; /* Region information. */
+
+ MPOOL *mp; /* Address of the shared MPOOL. */
+
+ void *addr; /* Address of shalloc() region. */
+
+ DB_HASHTAB *htab; /* Hash table of bucket headers. */
+
+#define MP_LOCKHANDLE 0x01 /* Threaded, lock handles and region. */
+#define MP_LOCKREGION 0x02 /* Concurrent access, lock region. */
+ u_int32_t flags;
+};
+
+/*
+ * DB_MPREG --
+ * DB_MPOOL registry of pgin/pgout functions.
+ */
+struct __db_mpreg {
+ LIST_ENTRY(__db_mpreg) q; /* Linked list. */
+
+ int ftype; /* File type. */
+ /* Pgin, pgout routines. */
+ int (DB_CALLBACK *pgin) __P((db_pgno_t, void *, DBT *));
+ int (DB_CALLBACK *pgout) __P((db_pgno_t, void *, DBT *));
+};
+
+/*
+ * DB_MPOOLFILE --
+ * Per-process DB_MPOOLFILE information.
+ */
+struct __db_mpoolfile {
+/* These fields need to be protected for multi-threaded support. */
+ db_mutex_t *mutexp; /* Structure lock. */
+
+ int fd; /* Underlying file descriptor. */
+
+ u_int32_t ref; /* Reference count. */
+
+ /*
+ * !!!
+ * This field is a special case -- it's protected by the region lock
+ * NOT the thread lock. The reason for this is that we always have
+ * the region lock immediately before or after we modify the field,
+ * and we don't want to use the structure lock to protect it because
+ * then I/O (which is done with the structure lock held because of
+ * the race between the seek and write of the file descriptor) will
+ * block any other put/get calls using this DB_MPOOLFILE structure.
+ */
+ u_int32_t pinref; /* Pinned block reference count. */
+
+/* These fields are not protected. */
+ TAILQ_ENTRY(__db_mpoolfile) q; /* Linked list of DB_MPOOLFILE's. */
+
+ DB_MPOOL *dbmp; /* Overlying DB_MPOOL. */
+ MPOOLFILE *mfp; /* Underlying MPOOLFILE. */
+
+ void *addr; /* Address of mmap'd region. */
+ size_t len; /* Length of mmap'd region. */
+
+/* These fields need to be protected for multi-threaded support. */
+#define MP_READONLY 0x01 /* File is readonly. */
+#define MP_UPGRADE 0x02 /* File descriptor is readwrite. */
+#define MP_UPGRADE_FAIL 0x04 /* Upgrade wasn't possible. */
+ u_int32_t flags;
+};
+
+/*
+ * MPOOL --
+ * Shared memory pool region. One of these is allocated in shared
+ * memory, and describes the pool.
+ */
+struct __mpool {
+ RLAYOUT rlayout; /* General region information. */
+
+ SH_TAILQ_HEAD(__bhq) bhq; /* LRU list of buckets. */
+ SH_TAILQ_HEAD(__bhfq) bhfq; /* Free buckets. */
+ SH_TAILQ_HEAD(__mpfq) mpfq; /* List of MPOOLFILEs. */
+
+ /*
+ * We make the assumption that the early pages of the file are far
+ * more likely to be retrieved than the later pages, which means
+ * that the top bits are more interesting for hashing since they're
+ * less likely to collide. On the other hand, since 512 4K pages
+ * represents a 2MB file, only the bottom 9 bits of the page number
+ * are likely to be set. We XOR in the offset in the MPOOL of the
+ * MPOOLFILE that backs this particular page, since that should also
+ * be unique for the page.
+ */
+#define BUCKET(mp, mf_offset, pgno) \
+ (((pgno) ^ ((mf_offset) << 9)) % (mp)->htab_buckets)
+
+ size_t htab; /* Hash table offset. */
+ size_t htab_buckets; /* Number of hash table entries. */
+
+ DB_LSN lsn; /* Maximum checkpoint LSN. */
+ u_int32_t lsn_cnt; /* Checkpoint buffers left to write. */
+
+ DB_MPOOL_STAT stat; /* Global mpool statistics. */
+
+#define MP_LSN_RETRY 0x01 /* Retry all BH_WRITE buffers. */
+ u_int32_t flags;
+};
+
+/*
+ * MPOOLFILE --
+ * Shared DB_MPOOLFILE information.
+ */
+struct __mpoolfile {
+ SH_TAILQ_ENTRY q; /* List of MPOOLFILEs */
+
+ u_int32_t ref; /* Reference count. */
+
+ int ftype; /* File type. */
+
+ int32_t lsn_off; /* Page's LSN offset. */
+ u_int32_t clear_len; /* Bytes to clear on page create. */
+
+ size_t path_off; /* File name location. */
+ size_t fileid_off; /* File identification location. */
+
+ size_t pgcookie_len; /* Pgin/pgout cookie length. */
+ size_t pgcookie_off; /* Pgin/pgout cookie location. */
+
+ u_int32_t lsn_cnt; /* Checkpoint buffers left to write. */
+
+ db_pgno_t last_pgno; /* Last page in the file. */
+ db_pgno_t orig_last_pgno; /* Original last page in the file. */
+
+#define MP_CAN_MMAP 0x01 /* If the file can be mmap'd. */
+#define MP_TEMP 0x02 /* Backing file is a temporary. */
+ u_int32_t flags;
+
+ DB_MPOOL_FSTAT stat; /* Per-file mpool statistics. */
+};
+
+/*
+ * BH --
+ * Buffer header.
+ */
+struct __bh {
+ db_mutex_t mutex; /* Structure lock. */
+
+ u_int16_t ref; /* Reference count. */
+
+#define BH_CALLPGIN 0x001 /* Page needs to be reworked... */
+#define BH_DIRTY 0x002 /* Page was modified. */
+#define BH_DISCARD 0x004 /* Page is useless. */
+#define BH_LOCKED 0x008 /* Page is locked (I/O in progress). */
+#define BH_TRASH 0x010 /* Page is garbage. */
+#define BH_WRITE 0x020 /* Page scheduled for writing. */
+ u_int16_t flags;
+
+ SH_TAILQ_ENTRY q; /* LRU queue. */
+ SH_TAILQ_ENTRY hq; /* MPOOL hash bucket queue. */
+
+ db_pgno_t pgno; /* Underlying MPOOLFILE page number. */
+ size_t mf_offset; /* Associated MPOOLFILE offset. */
+
+ /*
+ * !!!
+ * This array must be size_t aligned -- the DB access methods put PAGE
+ * and other structures into it, and expect to be able to access them
+ * directly. (We guarantee size_t alignment in the db_mpool(3) manual
+ * page as well.)
+ */
+ u_int8_t buf[1]; /* Variable length data. */
+};
+
+#include "mp_ext.h"
diff --git a/usr/src/cmd/sendmail/db/include/mp_ext.h b/usr/src/cmd/sendmail/db/include/mp_ext.h
new file mode 100644
index 0000000000..8b46334408
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/include/mp_ext.h
@@ -0,0 +1,21 @@
+/* DO NOT EDIT: automatically built by dist/distrib. */
+#ifndef _mp_ext_h_
+#define _mp_ext_h_
+int __memp_bhwrite
+ __P((DB_MPOOL *, MPOOLFILE *, BH *, int *, int *));
+int __memp_pgread __P((DB_MPOOLFILE *, BH *, int));
+int __memp_pgwrite __P((DB_MPOOLFILE *, BH *, int *, int *));
+int __memp_pg __P((DB_MPOOLFILE *, BH *, int));
+void __memp_bhfree __P((DB_MPOOL *, MPOOLFILE *, BH *, int));
+int __memp_fopen __P((DB_MPOOL *, MPOOLFILE *, const char *,
+ u_int32_t, int, size_t, int, DB_MPOOL_FINFO *, DB_MPOOLFILE **));
+void __memp_panic __P((DB_ENV *));
+char * __memp_fn __P((DB_MPOOLFILE *));
+char * __memp_fns __P((DB_MPOOL *, MPOOLFILE *));
+void __memp_dump_region __P((DB_MPOOL *, char *, FILE *));
+int __memp_reg_alloc __P((DB_MPOOL *, size_t, size_t *, void *));
+int __memp_alloc __P((DB_MPOOL *, size_t, size_t *, void *));
+int __memp_ropen
+ __P((DB_MPOOL *, const char *, size_t, int, int, u_int32_t));
+int __mp_xxx_fd __P((DB_MPOOLFILE *, int *));
+#endif /* _mp_ext_h_ */
diff --git a/usr/src/cmd/sendmail/db/include/mutex_ext.h b/usr/src/cmd/sendmail/db/include/mutex_ext.h
new file mode 100644
index 0000000000..b48da5d2f4
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/include/mutex_ext.h
@@ -0,0 +1,7 @@
+/* DO NOT EDIT: automatically built by dist/distrib. */
+#ifndef _mutex_ext_h_
+#define _mutex_ext_h_
+int __db_mutex_init __P((db_mutex_t *, u_int32_t));
+int __db_mutex_lock __P((db_mutex_t *, int));
+int __db_mutex_unlock __P((db_mutex_t *, int));
+#endif /* _mutex_ext_h_ */
diff --git a/usr/src/cmd/sendmail/db/include/os.h b/usr/src/cmd/sendmail/db/include/os.h
new file mode 100644
index 0000000000..766f955460
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/include/os.h
@@ -0,0 +1,26 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ *
+ * @(#)os.h 10.11 (Sleepycat) 10/12/98
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+ * We group seek/write calls into a single function so that we can use
+ * pread(2)/pwrite(2) where they're available.
+ */
+#define DB_IO_READ 1
+#define DB_IO_WRITE 2
+typedef struct __io {
+ int fd_io; /* I/O file descriptor. */
+ int fd_lock; /* Locking file descriptor. */
+ db_mutex_t *mutexp; /* Mutex to lock. */
+ size_t pagesize; /* Page size. */
+ db_pgno_t pgno; /* Page number. */
+ u_int8_t *buf; /* Buffer. */
+ size_t bytes; /* Bytes read/written. */
+} DB_IO;
diff --git a/usr/src/cmd/sendmail/db/include/os_ext.h b/usr/src/cmd/sendmail/db/include/os_ext.h
new file mode 100644
index 0000000000..ca3c020aa5
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/include/os_ext.h
@@ -0,0 +1,40 @@
+/* DO NOT EDIT: automatically built by dist/distrib. */
+#ifndef _os_ext_h_
+#define _os_ext_h_
+int __os_abspath __P((const char *));
+int __os_strdup __P((const char *, void *));
+int __os_calloc __P((size_t, size_t, void *));
+int __os_malloc __P((size_t, void *(*)(size_t), void *));
+int __os_realloc __P((void *, size_t));
+void __os_free __P((void *, size_t));
+void __os_freestr __P((void *));
+int __os_dirlist __P((const char *, char ***, int *));
+void __os_dirfree __P((char **, int));
+int __os_fileid __P((DB_ENV *, const char *, int, u_int8_t *));
+int __os_fsync __P((int));
+int __db_mapanon_ok __P((int));
+int __db_mapinit __P((void));
+int __db_mapregion __P((char *, REGINFO *));
+int __db_unmapregion __P((REGINFO *));
+int __db_unlinkregion __P((char *, REGINFO *));
+int __db_mapfile __P((char *, int, size_t, int, void **));
+int __db_unmapfile __P((void *, size_t));
+u_int32_t __db_oflags __P((int));
+int __db_omode __P((const char *));
+int __db_open __P((const char *, u_int32_t, u_int32_t, int, int *));
+int __os_open __P((const char *, int, int, int *));
+int __os_close __P((int));
+char *__db_rpath __P((const char *));
+int __os_io __P((DB_IO *, int, ssize_t *));
+int __os_read __P((int, void *, size_t, ssize_t *));
+int __os_write __P((int, void *, size_t, ssize_t *));
+int __os_seek __P((int, size_t, db_pgno_t, u_int32_t, int, int));
+int __os_sleep __P((u_long, u_long));
+int __os_spin __P((void));
+void __os_yield __P((u_long));
+int __os_exists __P((const char *, int *));
+int __os_ioinfo
+ __P((const char *, int, u_int32_t *, u_int32_t *, u_int32_t *));
+int __os_tmpdir __P((DB_ENV *, u_int32_t));
+int __os_unlink __P((const char *));
+#endif /* _os_ext_h_ */
diff --git a/usr/src/cmd/sendmail/db/include/os_jump.h b/usr/src/cmd/sendmail/db/include/os_jump.h
new file mode 100644
index 0000000000..7909e53b6e
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/include/os_jump.h
@@ -0,0 +1,42 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ *
+ * @(#)os_jump.h 10.1 (Sleepycat) 10/17/98
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/* Calls which can be replaced by the application. */
+struct __db_jumptab {
+ int (*j_close) __P((int)); /* DB_FUNC_CLOSE */
+ void (*j_dirfree) __P((char **, int)); /* DB_FUNC_DIRFREE */
+ int (*j_dirlist) /* DB_FUNC_DIRLIST */
+ __P((const char *, char ***, int *));
+ int (*j_exists) /* DB_FUNC_EXISTS */
+ __P((const char *, int *));
+ void (*j_free) __P((void *)); /* DB_FUNC_FREE */
+ int (*j_fsync) __P((int)); /* DB_FUNC_FSYNC */
+ int (*j_ioinfo) __P((const char *, /* DB_FUNC_IOINFO */
+ int, u_int32_t *, u_int32_t *, u_int32_t *));
+ void *(*j_malloc) __P((size_t)); /* DB_FUNC_MALLOC */
+ int (*j_map) /* DB_FUNC_MAP */
+ __P((char *, int, size_t, int, int, int, void **));
+ int (*j_open) /* DB_FUNC_OPEN */
+ __P((const char *, int, ...));
+ ssize_t (*j_read) __P((int, void *, size_t)); /* DB_FUNC_READ */
+ void *(*j_realloc) __P((void *, size_t)); /* DB_FUNC_REALLOC */
+ int (*j_runlink) __P((char *)); /* DB_FUNC_RUNLINK */
+ int (*j_seek) /* DB_FUNC_SEEK */
+ __P((int, size_t, db_pgno_t, u_int32_t, int, int));
+ int (*j_sleep) __P((u_long, u_long)); /* DB_FUNC_SLEEP */
+ int (*j_unlink) __P((const char *)); /* DB_FUNC_UNLINK */
+ int (*j_unmap) __P((void *, size_t)); /* DB_FUNC_UNMAP */
+ ssize_t (*j_write) /* DB_FUNC_WRITE */
+ __P((int, const void *, size_t));
+ int (*j_yield) __P((void)); /* DB_FUNC_YIELD */
+};
+
+extern struct __db_jumptab __db_jump;
diff --git a/usr/src/cmd/sendmail/db/include/queue.h b/usr/src/cmd/sendmail/db/include/queue.h
new file mode 100644
index 0000000000..78452d84f5
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/include/queue.h
@@ -0,0 +1,282 @@
+/* BSDI $Id: queue.h,v 2.4 1996/07/02 13:22:11 bostic Exp $ */
+
+/*
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)queue.h 8.5 (Berkeley) 8/20/94
+ * %W% (Sun) %G%
+ */
+/*
+ * Copyright (c) 1998 by Sun Microsystems, Inc.
+ * All rights reserved.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#ifndef _SYS_QUEUE_H_
+#define _SYS_QUEUE_H_
+
+/*
+ * This file defines three types of data structures: lists, tail queues,
+ * and circular queues.
+ *
+ * A list is headed by a single forward pointer (or an array of forward
+ * pointers for a hash table header). The elements are doubly linked
+ * so that an arbitrary element can be removed without a need to
+ * traverse the list. New elements can be added to the list before
+ * or after an existing element or at the head of the list. A list
+ * may only be traversed in the forward direction.
+ *
+ * A tail queue is headed by a pair of pointers, one to the head of the
+ * list and the other to the tail of the list. The elements are doubly
+ * linked so that an arbitrary element can be removed without a need to
+ * traverse the list. New elements can be added to the list before or
+ * after an existing element, at the head of the list, or at the end of
+ * the list. A tail queue may only be traversed in the forward direction.
+ *
+ * A circle queue is headed by a pair of pointers, one to the head of the
+ * list and the other to the tail of the list. The elements are doubly
+ * linked so that an arbitrary element can be removed without a need to
+ * traverse the list. New elements can be added to the list before or after
+ * an existing element, at the head of the list, or at the end of the list.
+ * A circle queue may be traversed in either direction, but has a more
+ * complex end of list detection.
+ *
+ * For details on the use of these macros, see the queue(3) manual page.
+ */
+
+/*
+ * List definitions.
+ */
+#define LIST_HEAD(name, type) \
+struct name { \
+ struct type *lh_first; /* first element */ \
+}
+
+#define LIST_ENTRY(type) \
+struct { \
+ struct type *le_next; /* next element */ \
+ struct type **le_prev; /* address of previous next element */ \
+}
+
+#define LIST_FIRST(head) ((head)->lh_first)
+#define LIST_NEXT(elm, field) ((elm)->field.le_next)
+#define LIST_END(head) NULL
+
+/*
+ * List functions.
+ */
+#define LIST_INIT(head) { \
+ (head)->lh_first = NULL; \
+}
+
+#define LIST_INSERT_AFTER(listelm, elm, field) do { \
+ if (((elm)->field.le_next = (listelm)->field.le_next) != NULL) \
+ (listelm)->field.le_next->field.le_prev = \
+ &(elm)->field.le_next; \
+ (listelm)->field.le_next = (elm); \
+ (elm)->field.le_prev = &(listelm)->field.le_next; \
+} while (0)
+
+#define LIST_INSERT_BEFORE(listelm, elm, field) do { \
+ (elm)->field.le_prev = (listelm)->field.le_prev; \
+ (elm)->field.le_next = (listelm); \
+ *(listelm)->field.le_prev = (elm); \
+ (listelm)->field.le_prev = &(elm)->field.le_next; \
+} while (0)
+
+#define LIST_INSERT_HEAD(head, elm, field) do { \
+ if (((elm)->field.le_next = (head)->lh_first) != NULL) \
+ (head)->lh_first->field.le_prev = &(elm)->field.le_next;\
+ (head)->lh_first = (elm); \
+ (elm)->field.le_prev = &(head)->lh_first; \
+} while (0)
+
+#define LIST_REMOVE(elm, field) do { \
+ if ((elm)->field.le_next != NULL) \
+ (elm)->field.le_next->field.le_prev = \
+ (elm)->field.le_prev; \
+ *(elm)->field.le_prev = (elm)->field.le_next; \
+} while (0)
+
+/*
+ * Tail queue definitions.
+ */
+#define TAILQ_HEAD(name, type) \
+struct name { \
+ struct type *tqh_first; /* first element */ \
+ struct type **tqh_last; /* addr of last next element */ \
+}
+
+#define TAILQ_ENTRY(type) \
+struct { \
+ struct type *tqe_next; /* next element */ \
+ struct type **tqe_prev; /* address of previous next element */ \
+}
+
+#define TAILQ_FIRST(head) ((head)->tqh_first)
+#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next)
+#define TAILQ_END(head) NULL
+
+/*
+ * Tail queue functions.
+ */
+#define TAILQ_INIT(head) do { \
+ (head)->tqh_first = NULL; \
+ (head)->tqh_last = &(head)->tqh_first; \
+} while (0)
+
+#define TAILQ_INSERT_HEAD(head, elm, field) do { \
+ if (((elm)->field.tqe_next = (head)->tqh_first) != NULL) \
+ (head)->tqh_first->field.tqe_prev = \
+ &(elm)->field.tqe_next; \
+ else \
+ (head)->tqh_last = &(elm)->field.tqe_next; \
+ (head)->tqh_first = (elm); \
+ (elm)->field.tqe_prev = &(head)->tqh_first; \
+} while (0)
+
+#define TAILQ_INSERT_TAIL(head, elm, field) do { \
+ (elm)->field.tqe_next = NULL; \
+ (elm)->field.tqe_prev = (head)->tqh_last; \
+ *(head)->tqh_last = (elm); \
+ (head)->tqh_last = &(elm)->field.tqe_next; \
+} while (0)
+
+#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \
+ if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL)\
+ (elm)->field.tqe_next->field.tqe_prev = \
+ &(elm)->field.tqe_next; \
+ else \
+ (head)->tqh_last = &(elm)->field.tqe_next; \
+ (listelm)->field.tqe_next = (elm); \
+ (elm)->field.tqe_prev = &(listelm)->field.tqe_next; \
+} while (0)
+
+#define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \
+ (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \
+ (elm)->field.tqe_next = (listelm); \
+ *(listelm)->field.tqe_prev = (elm); \
+ (listelm)->field.tqe_prev = &(elm)->field.tqe_next; \
+} while (0)
+
+#define TAILQ_REMOVE(head, elm, field) do { \
+ if (((elm)->field.tqe_next) != NULL) \
+ (elm)->field.tqe_next->field.tqe_prev = \
+ (elm)->field.tqe_prev; \
+ else \
+ (head)->tqh_last = (elm)->field.tqe_prev; \
+ *(elm)->field.tqe_prev = (elm)->field.tqe_next; \
+} while (0)
+
+/*
+ * Circular queue definitions.
+ */
+#define CIRCLEQ_HEAD(name, type) \
+struct name { \
+ struct type *cqh_first; /* first element */ \
+ struct type *cqh_last; /* last element */ \
+}
+
+#define CIRCLEQ_ENTRY(type) \
+struct { \
+ struct type *cqe_next; /* next element */ \
+ struct type *cqe_prev; /* previous element */ \
+}
+
+#define CIRCLEQ_FIRST(head) ((head)->cqh_first)
+#define CIRCLEQ_LAST(head) ((head)->cqh_last)
+#define CIRCLEQ_END(head) ((void *)(head))
+#define CIRCLEQ_NEXT(elm, field) ((elm)->field.cqe_next)
+#define CIRCLEQ_PREV(elm, field) ((elm)->field.cqe_prev)
+
+/*
+ * Circular queue functions.
+ */
+#define CIRCLEQ_INIT(head) do { \
+ (head)->cqh_first = (void *)(head); \
+ (head)->cqh_last = (void *)(head); \
+} while (0)
+
+#define CIRCLEQ_INSERT_AFTER(head, listelm, elm, field) do { \
+ (elm)->field.cqe_next = (listelm)->field.cqe_next; \
+ (elm)->field.cqe_prev = (listelm); \
+ if ((listelm)->field.cqe_next == (void *)(head)) \
+ (head)->cqh_last = (elm); \
+ else \
+ (listelm)->field.cqe_next->field.cqe_prev = (elm); \
+ (listelm)->field.cqe_next = (elm); \
+} while (0)
+
+#define CIRCLEQ_INSERT_BEFORE(head, listelm, elm, field) do { \
+ (elm)->field.cqe_next = (listelm); \
+ (elm)->field.cqe_prev = (listelm)->field.cqe_prev; \
+ if ((listelm)->field.cqe_prev == (void *)(head)) \
+ (head)->cqh_first = (elm); \
+ else \
+ (listelm)->field.cqe_prev->field.cqe_next = (elm); \
+ (listelm)->field.cqe_prev = (elm); \
+} while (0)
+
+#define CIRCLEQ_INSERT_HEAD(head, elm, field) do { \
+ (elm)->field.cqe_next = (head)->cqh_first; \
+ (elm)->field.cqe_prev = (void *)(head); \
+ if ((head)->cqh_last == (void *)(head)) \
+ (head)->cqh_last = (elm); \
+ else \
+ (head)->cqh_first->field.cqe_prev = (elm); \
+ (head)->cqh_first = (elm); \
+} while (0)
+
+#define CIRCLEQ_INSERT_TAIL(head, elm, field) do { \
+ (elm)->field.cqe_next = (void *)(head); \
+ (elm)->field.cqe_prev = (head)->cqh_last; \
+ if ((head)->cqh_first == (void *)(head)) \
+ (head)->cqh_first = (elm); \
+ else \
+ (head)->cqh_last->field.cqe_next = (elm); \
+ (head)->cqh_last = (elm); \
+} while (0)
+
+#define CIRCLEQ_REMOVE(head, elm, field) do { \
+ if ((elm)->field.cqe_next == (void *)(head)) \
+ (head)->cqh_last = (elm)->field.cqe_prev; \
+ else \
+ (elm)->field.cqe_next->field.cqe_prev = \
+ (elm)->field.cqe_prev; \
+ if ((elm)->field.cqe_prev == (void *)(head)) \
+ (head)->cqh_first = (elm)->field.cqe_next; \
+ else \
+ (elm)->field.cqe_prev->field.cqe_next = \
+ (elm)->field.cqe_next; \
+} while (0)
+#endif /* !_SYS_QUEUE_H_ */
diff --git a/usr/src/cmd/sendmail/db/include/shqueue.h b/usr/src/cmd/sendmail/db/include/shqueue.h
new file mode 100644
index 0000000000..e92719bdd4
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/include/shqueue.h
@@ -0,0 +1,342 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997
+ * Sleepycat Software. All rights reserved.
+ *
+ * @(#)shqueue.h 8.12 (Sleepycat) 9/10/97
+ * %W% (Sun) %G%
+ */
+/*
+ * Copyright (c) 1998 by Sun Microsystems, Inc.
+ * All rights reserved.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#ifndef _SYS_SHQUEUE_H_
+#define _SYS_SHQUEUE_H_
+
+/*
+ * This file defines three types of data structures: lists, tail queues, and
+ * circular queues, similarly to the include file <sys/queue.h>.
+ *
+ * The difference is that this set of macros can be used for structures that
+ * reside in shared memory that may be mapped at different addresses in each
+ * process. In most cases, the macros for shared structures exactly mirror
+ * the normal macros, although the macro calls require an additional type
+ * parameter, only used by the HEAD and ENTRY macros of the standard macros.
+ *
+ * For details on the use of these macros, see the queue(3) manual page.
+ */
+
+/*
+ * Shared list definitions.
+ */
+#define SH_LIST_HEAD(name) \
+struct name { \
+ ssize_t slh_first; /* first element */ \
+}
+
+#define SH_LIST_ENTRY \
+struct { \
+ ssize_t sle_next; /* relative offset next element */ \
+ ssize_t sle_prev; /* relative offset of prev element */ \
+}
+
+/*
+ * Shared list functions. Since we use relative offsets for pointers,
+ * 0 is a valid offset. Therefore, we use -1 to indicate end of list.
+ * The macros ending in "P" return pointers without checking for end
+ * of list, the others check for end of list and evaluate to either a
+ * pointer or NULL.
+ */
+
+#define SH_LIST_FIRSTP(head, type) \
+ ((struct type *)(((u_int8_t *)(head)) + (head)->slh_first))
+
+#define SH_LIST_FIRST(head, type) \
+ ((head)->slh_first == -1 ? NULL : \
+ ((struct type *)(((u_int8_t *)(head)) + (head)->slh_first)))
+
+#define SH_LIST_NEXTP(elm, field, type) \
+ ((struct type *)(((u_int8_t *)(elm)) + (elm)->field.sle_next))
+
+#define SH_LIST_NEXT(elm, field, type) \
+ ((elm)->field.sle_next == -1 ? NULL : \
+ ((struct type *)(((u_int8_t *)(elm)) + (elm)->field.sle_next)))
+
+#define SH_LIST_PREV(elm, field) \
+ ((ssize_t *)(((u_int8_t *)(elm)) + (elm)->field.sle_prev))
+
+#define SH_PTR_TO_OFF(src, dest) \
+ ((ssize_t)(((u_int8_t *)(dest)) - ((u_int8_t *)(src))))
+
+#define SH_LIST_END(head) NULL
+
+/*
+ * Take the element's next pointer and calculate what the corresponding
+ * Prev pointer should be -- basically it is the negation plus the offset
+ * of the next field in the structure.
+ */
+#define SH_LIST_NEXT_TO_PREV(elm, field) \
+ (-(elm)->field.sle_next + SH_PTR_TO_OFF(elm, &(elm)->field.sle_next))
+
+#define SH_LIST_INIT(head) (head)->slh_first = -1
+
+#define SH_LIST_INSERT_AFTER(listelm, elm, field, type) do { \
+ if ((listelm)->field.sle_next != -1) { \
+ (elm)->field.sle_next = SH_PTR_TO_OFF(elm, \
+ SH_LIST_NEXTP(listelm, field, type)); \
+ SH_LIST_NEXTP(listelm, field, type)->field.sle_prev = \
+ SH_LIST_NEXT_TO_PREV(elm, field); \
+ } else \
+ (elm)->field.sle_next = -1; \
+ (listelm)->field.sle_next = SH_PTR_TO_OFF(listelm, elm); \
+ (elm)->field.sle_prev = SH_LIST_NEXT_TO_PREV(listelm, field); \
+} while (0)
+
+#define SH_LIST_INSERT_HEAD(head, elm, field, type) do { \
+ if ((head)->slh_first != -1) { \
+ (elm)->field.sle_next = \
+ (head)->slh_first - SH_PTR_TO_OFF(head, elm); \
+ SH_LIST_FIRSTP(head, type)->field.sle_prev = \
+ SH_LIST_NEXT_TO_PREV(elm, field); \
+ } else \
+ (elm)->field.sle_next = -1; \
+ (head)->slh_first = SH_PTR_TO_OFF(head, elm); \
+ (elm)->field.sle_prev = SH_PTR_TO_OFF(elm, &(head)->slh_first); \
+} while (0)
+
+#define SH_LIST_REMOVE(elm, field, type) do { \
+ if ((elm)->field.sle_next != -1) { \
+ SH_LIST_NEXTP(elm, field, type)->field.sle_prev = \
+ (elm)->field.sle_prev - (elm)->field.sle_next; \
+ *SH_LIST_PREV(elm, field) += (elm)->field.sle_next; \
+ } else \
+ *SH_LIST_PREV(elm, field) = -1; \
+} while (0)
+
+/*
+ * Shared tail queue definitions.
+ */
+#define SH_TAILQ_HEAD(name) \
+struct name { \
+ ssize_t stqh_first; /* relative offset of first element */ \
+ ssize_t stqh_last; /* relative offset of last's next */ \
+}
+
+#define SH_TAILQ_ENTRY \
+struct { \
+ ssize_t stqe_next; /* relative offset of next element */ \
+ ssize_t stqe_prev; /* relative offset of prev's next */ \
+}
+
+/*
+ * Shared tail queue functions.
+ */
+#define SH_TAILQ_FIRSTP(head, type) \
+ ((struct type *)((u_int8_t *)(head) + (head)->stqh_first))
+
+#define SH_TAILQ_FIRST(head, type) \
+ ((head)->stqh_first == -1 ? NULL : SH_TAILQ_FIRSTP(head, type))
+
+#define SH_TAILQ_NEXTP(elm, field, type) \
+ ((struct type *)((u_int8_t *)(elm) + (elm)->field.stqe_next))
+
+#define SH_TAILQ_NEXT(elm, field, type) \
+ ((elm)->field.stqe_next == -1 ? NULL : SH_TAILQ_NEXTP(elm, field, type))
+
+#define SH_TAILQ_PREVP(elm, field) \
+ ((ssize_t *)((u_int8_t *)(elm) + (elm)->field.stqe_prev))
+
+#define SH_TAILQ_LAST(head) \
+ ((ssize_t *)(((u_int8_t *)(head)) + (head)->stqh_last))
+
+#define SH_TAILQ_NEXT_TO_PREV(elm, field) \
+ (-(elm)->field.stqe_next + SH_PTR_TO_OFF(elm, &(elm)->field.stqe_next))
+
+#define SH_TAILQ_END(head) NULL
+
+#define SH_TAILQ_INIT(head) { \
+ (head)->stqh_first = -1; \
+ (head)->stqh_last = SH_PTR_TO_OFF(head, &(head)->stqh_first); \
+}
+
+#define SH_TAILQ_INSERT_HEAD(head, elm, field, type) do { \
+ if ((head)->stqh_first != -1) { \
+ (elm)->field.stqe_next = \
+ (head)->stqh_first - SH_PTR_TO_OFF(head, elm); \
+ SH_TAILQ_FIRSTP(head, type)->field.stqe_prev = \
+ SH_TAILQ_NEXT_TO_PREV(elm, field); \
+ } else { \
+ (elm)->field.stqe_next = -1; \
+ (head)->stqh_last = \
+ SH_PTR_TO_OFF(head, &(elm)->field.stqe_next); \
+ } \
+ (head)->stqh_first = SH_PTR_TO_OFF(head, elm); \
+ (elm)->field.stqe_prev = \
+ SH_PTR_TO_OFF(elm, &(head)->stqh_first); \
+} while (0)
+
+#define SH_TAILQ_INSERT_TAIL(head, elm, field) do { \
+ (elm)->field.stqe_next = -1; \
+ (elm)->field.stqe_prev = \
+ -SH_PTR_TO_OFF(head, elm) + (head)->stqh_last; \
+ if ((head)->stqh_last == \
+ SH_PTR_TO_OFF((head), &(head)->stqh_first)) \
+ (head)->stqh_first = SH_PTR_TO_OFF(head, elm); \
+ else \
+ *SH_TAILQ_LAST(head) = -(head)->stqh_last + \
+ SH_PTR_TO_OFF((elm), &(elm)->field.stqe_next) + \
+ SH_PTR_TO_OFF(head, elm); \
+ (head)->stqh_last = \
+ SH_PTR_TO_OFF(head, &((elm)->field.stqe_next)); \
+} while (0)
+
+#define SH_TAILQ_INSERT_AFTER(head, listelm, elm, field, type) do { \
+ if ((listelm)->field.stqe_next != -1) { \
+ (elm)->field.stqe_next = (listelm)->field.stqe_next - \
+ SH_PTR_TO_OFF(listelm, elm); \
+ SH_TAILQ_NEXTP(listelm, field, type)->field.stqe_prev = \
+ SH_TAILQ_NEXT_TO_PREV(elm, field); \
+ } else { \
+ (elm)->field.stqe_next = -1; \
+ (head)->stqh_last = \
+ SH_PTR_TO_OFF(head, &elm->field.stqe_next); \
+ } \
+ (listelm)->field.stqe_next = SH_PTR_TO_OFF(listelm, elm); \
+ (elm)->field.stqe_prev = SH_TAILQ_NEXT_TO_PREV(listelm, field); \
+} while (0)
+
+#define SH_TAILQ_REMOVE(head, elm, field, type) do { \
+ if ((elm)->field.stqe_next != -1) { \
+ SH_TAILQ_NEXTP(elm, field, type)->field.stqe_prev = \
+ (elm)->field.stqe_prev + \
+ SH_PTR_TO_OFF(SH_TAILQ_NEXTP(elm, \
+ field, type), elm); \
+ *SH_TAILQ_PREVP(elm, field) += elm->field.stqe_next; \
+ } else { \
+ (head)->stqh_last = (elm)->field.stqe_prev + \
+ SH_PTR_TO_OFF(head, elm); \
+ *SH_TAILQ_PREVP(elm, field) = -1; \
+ } \
+} while (0)
+
+/*
+ * Shared circular queue definitions.
+ */
+#define SH_CIRCLEQ_HEAD(name) \
+struct name { \
+ ssize_t scqh_first; /* first element */ \
+ ssize_t scqh_last; /* last element */ \
+}
+
+#define SH_CIRCLEQ_ENTRY \
+struct { \
+ ssize_t scqe_next; /* next element */ \
+ ssize_t scqe_prev; /* previous element */ \
+}
+
+/*
+ * Shared circular queue functions.
+ */
+#define SH_CIRCLEQ_FIRSTP(head, type) \
+ ((struct type *)(((u_int8_t *)(head)) + (head)->scqh_first))
+
+#define SH_CIRCLEQ_FIRST(head, type) \
+ ((head)->scqh_first == -1 ? \
+ (void *)head : SH_CIRCLEQ_FIRSTP(head, type))
+
+#define SH_CIRCLEQ_LASTP(head, type) \
+ ((struct type *)(((u_int8_t *)(head)) + (head)->scqh_last))
+
+#define SH_CIRCLEQ_LAST(head, type) \
+ ((head)->scqh_last == -1 ? (void *)head : SH_CIRCLEQ_LASTP(head, type))
+
+#define SH_CIRCLEQ_NEXTP(elm, field, type) \
+ ((struct type *)(((u_int8_t *)(elm)) + (elm)->field.scqe_next))
+
+#define SH_CIRCLEQ_NEXT(head, elm, field, type) \
+ ((elm)->field.scqe_next == SH_PTR_TO_OFF(elm, head) ? \
+ (void *)head : SH_CIRCLEQ_NEXTP(elm, field, type))
+
+#define SH_CIRCLEQ_PREVP(elm, field, type) \
+ ((struct type *)(((u_int8_t *)(elm)) + (elm)->field.scqe_prev))
+
+#define SH_CIRCLEQ_PREV(head, elm, field, type) \
+ ((elm)->field.scqe_prev == SH_PTR_TO_OFF(elm, head) ? \
+ (void *)head : SH_CIRCLEQ_PREVP(elm, field, type))
+
+#define SH_CIRCLEQ_END(head) ((void *)(head))
+
+#define SH_CIRCLEQ_INIT(head) { \
+ (head)->scqh_first = 0; \
+ (head)->scqh_last = 0; \
+}
+
+#define SH_CIRCLEQ_INSERT_AFTER(head, listelm, elm, field, type) do { \
+ (elm)->field.scqe_prev = SH_PTR_TO_OFF(elm, listelm); \
+ (elm)->field.scqe_next = (listelm)->field.scqe_next + \
+ (elm)->field.scqe_prev; \
+ if (SH_CIRCLEQ_NEXTP(listelm, field, type) == (void *)head) \
+ (head)->scqh_last = SH_PTR_TO_OFF(head, elm); \
+ else \
+ SH_CIRCLEQ_NEXTP(listelm, \
+ field, type)->field.scqe_prev = \
+ SH_PTR_TO_OFF(SH_CIRCLEQ_NEXTP(listelm, \
+ field, type), elm); \
+ (listelm)->field.scqe_next = -(elm)->field.scqe_prev; \
+} while (0)
+
+#define SH_CIRCLEQ_INSERT_BEFORE(head, listelm, elm, field, type) do { \
+ (elm)->field.scqe_next = SH_PTR_TO_OFF(elm, listelm); \
+ (elm)->field.scqe_prev = (elm)->field.scqe_next - \
+ SH_CIRCLEQ_PREVP(listelm, field, type)->field.scqe_next;\
+ if (SH_CIRCLEQ_PREVP(listelm, field, type) == (void *)(head)) \
+ (head)->scqh_first = SH_PTR_TO_OFF(head, elm); \
+ else \
+ SH_CIRCLEQ_PREVP(listelm, \
+ field, type)->field.scqe_next = \
+ SH_PTR_TO_OFF(SH_CIRCLEQ_PREVP(listelm, \
+ field, type), elm); \
+ (listelm)->field.scqe_prev = -(elm)->field.scqe_next; \
+} while (0)
+
+#define SH_CIRCLEQ_INSERT_HEAD(head, elm, field, type) do { \
+ (elm)->field.scqe_prev = SH_PTR_TO_OFF(elm, head); \
+ (elm)->field.scqe_next = (head)->scqh_first + \
+ (elm)->field.scqe_prev; \
+ if ((head)->scqh_last == 0) \
+ (head)->scqh_last = -(elm)->field.scqe_prev; \
+ else \
+ SH_CIRCLEQ_FIRSTP(head, type)->field.scqe_prev = \
+ SH_PTR_TO_OFF(SH_CIRCLEQ_FIRSTP(head, type), elm); \
+ (head)->scqh_first = -(elm)->field.scqe_prev; \
+} while (0)
+
+#define SH_CIRCLEQ_INSERT_TAIL(head, elm, field, type) do { \
+ (elm)->field.scqe_next = SH_PTR_TO_OFF(elm, head); \
+ (elm)->field.scqe_prev = (head)->scqh_last + \
+ (elm)->field.scqe_next; \
+ if ((head)->scqh_first == 0) \
+ (head)->scqh_first = -(elm)->field.scqe_next; \
+ else \
+ SH_CIRCLEQ_LASTP(head, type)->field.scqe_next = \
+ SH_PTR_TO_OFF(SH_CIRCLEQ_LASTP(head, type), elm); \
+ (head)->scqh_last = -(elm)->field.scqe_next; \
+} while (0)
+
+#define SH_CIRCLEQ_REMOVE(head, elm, field, type) do { \
+ if (SH_CIRCLEQ_NEXTP(elm, field, type) == (void *)(head)) \
+ (head)->scqh_last += (elm)->field.scqe_prev; \
+ else \
+ SH_CIRCLEQ_NEXTP(elm, field, type)->field.scqe_prev += \
+ (elm)->field.scqe_prev; \
+ if (SH_CIRCLEQ_PREVP(elm, field, type) == (void *)(head)) \
+ (head)->scqh_first += (elm)->field.scqe_next; \
+ else \
+ SH_CIRCLEQ_PREVP(elm, field, type)->field.scqe_next += \
+ (elm)->field.scqe_next; \
+} while (0)
+#endif /* !_SYS_SHQUEUE_H_ */
diff --git a/usr/src/cmd/sendmail/db/include/txn.h b/usr/src/cmd/sendmail/db/include/txn.h
new file mode 100644
index 0000000000..a6fa4db8de
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/include/txn.h
@@ -0,0 +1,148 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ *
+ * @(#)txn.h 10.18 (Sleepycat) 1/3/99
+ */
+#ifndef _TXN_H_
+#define _TXN_H_
+
+#include "xa.h"
+
+/*
+ * The name of the transaction shared memory region is DEFAULT_TXN_FILE and
+ * the region is always created group RW of the group owning the directory.
+ */
+#define DEFAULT_TXN_FILE "__db_txn.share"
+/* TXN_MINIMUM = (DB_LOCK_MAXID + 1) but this makes compilers complain. */
+#define TXN_MINIMUM 0x80000000
+#define TXN_INVALID 0xffffffff /* Maximum number of txn ids. */
+
+/*
+ * Transaction type declarations.
+ */
+
+/*
+ * Internal data maintained in shared memory for each transaction.
+ */
+typedef char DB_XID[XIDDATASIZE];
+
+typedef struct __txn_detail {
+ u_int32_t txnid; /* current transaction id
+ used to link free list also */
+ DB_LSN last_lsn; /* last lsn written for this txn */
+ DB_LSN begin_lsn; /* lsn of begin record */
+ size_t last_lock; /* offset in lock region of last lock
+ for this transaction. */
+ size_t parent; /* Offset of transaction's parent. */
+#define TXN_UNALLOC 0
+#define TXN_RUNNING 1
+#define TXN_ABORTED 2
+#define TXN_PREPARED 3
+#define TXN_COMMITTED 4
+ u_int32_t status; /* status of the transaction */
+ SH_TAILQ_ENTRY links; /* free/active list */
+
+#define TXN_XA_ABORTED 1
+#define TXN_XA_DEADLOCKED 2
+#define TXN_XA_ENDED 3
+#define TXN_XA_PREPARED 4
+#define TXN_XA_STARTED 5
+#define TXN_XA_SUSPENDED 6
+ u_int32_t xa_status; /* XA status */
+
+ /*
+ * XID (xid_t) structure: because these fields are logged, the
+ * sizes have to be explicit.
+ */
+ DB_XID xid; /* XA global transaction id */
+ u_int32_t bqual; /* bqual_length from XID */
+ u_int32_t gtrid; /* gtrid_length from XID */
+ int32_t format; /* XA format */
+} TXN_DETAIL;
+
+/*
+ * The transaction manager encapsulates the transaction system. It contains
+ * references to the log and lock managers as well as the state that keeps
+ * track of the shared memory region.
+ */
+struct __db_txnmgr {
+/* These fields need to be protected for multi-threaded support. */
+ db_mutex_t *mutexp; /* Synchronization. */
+ /* list of active transactions */
+ TAILQ_HEAD(_chain, __db_txn) txn_chain;
+
+/* These fields are not protected. */
+ REGINFO reginfo; /* Region information. */
+ DB_ENV *dbenv; /* Environment. */
+ int (*recover) /* Recovery dispatch routine */
+ __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+ u_int32_t flags; /* DB_TXN_NOSYNC, DB_THREAD */
+ DB_TXNREGION *region; /* address of shared memory region */
+ void *mem; /* address of the shalloc space */
+};
+
+/*
+ * Layout of the shared memory region.
+ * The region consists of a DB_TXNREGION structure followed by a large
+ * pool of shalloc'd memory which is used to hold TXN_DETAIL structures
+ * and thread mutexes (which are dynamically allocated).
+ */
+struct __db_txnregion {
+ RLAYOUT hdr; /* Shared memory region header. */
+ u_int32_t magic; /* transaction magic number */
+ u_int32_t version; /* version number */
+ u_int32_t maxtxns; /* maximum number of active txns */
+ u_int32_t last_txnid; /* last transaction id given out */
+ DB_LSN pending_ckp; /* last checkpoint did not finish */
+ DB_LSN last_ckp; /* lsn of the last checkpoint */
+ time_t time_ckp; /* time of last checkpoint */
+ u_int32_t logtype; /* type of logging */
+ u_int32_t locktype; /* lock type */
+ u_int32_t naborts; /* number of aborted transactions */
+ u_int32_t ncommits; /* number of committed transactions */
+ u_int32_t nbegins; /* number of begun transactions */
+ SH_TAILQ_HEAD(_active) active_txn; /* active transaction list */
+};
+
+/*
+ * Make the region large enough to hold N transaction detail structures
+ * plus some space to hold thread handles and the beginning of the shalloc
+ * region.
+ */
+#define TXN_REGION_SIZE(N) \
+ (sizeof(DB_TXNREGION) + N * sizeof(TXN_DETAIL) + 1000)
+
+/* Macros to lock/unlock the region and threads. */
+#define LOCK_TXNTHREAD(tmgrp) \
+ if (F_ISSET(tmgrp, DB_THREAD)) \
+ (void)__db_mutex_lock((tmgrp)->mutexp, -1)
+#define UNLOCK_TXNTHREAD(tmgrp) \
+ if (F_ISSET(tmgrp, DB_THREAD)) \
+ (void)__db_mutex_unlock((tmgrp)->mutexp, -1)
+
+#define LOCK_TXNREGION(tmgrp) \
+ (void)__db_mutex_lock(&(tmgrp)->region->hdr.lock, (tmgrp)->reginfo.fd)
+#define UNLOCK_TXNREGION(tmgrp) \
+ (void)__db_mutex_unlock(&(tmgrp)->region->hdr.lock, (tmgrp)->reginfo.fd)
+
+/* Check for region catastrophic shutdown. */
+#define TXN_PANIC_CHECK(tmgrp) { \
+ if ((tmgrp)->region->hdr.panic) \
+ return (DB_RUNRECOVERY); \
+}
+
+/*
+ * Log record types.
+ */
+#define TXN_COMMIT 1
+#define TXN_PREPARE 2
+#define TXN_CHECKPOINT 3
+
+#include "txn_auto.h"
+#include "txn_ext.h"
+
+#include "xa_ext.h"
+#endif /* !_TXN_H_ */
diff --git a/usr/src/cmd/sendmail/db/include/txn_auto.h b/usr/src/cmd/sendmail/db/include/txn_auto.h
new file mode 100644
index 0000000000..bb3de4eb17
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/include/txn_auto.h
@@ -0,0 +1,51 @@
+/* Do not edit: automatically built by dist/db_gen.sh. */
+#ifndef txn_AUTO_H
+#define txn_AUTO_H
+
+#define DB_txn_regop (DB_txn_BEGIN + 1)
+
+typedef struct _txn_regop_args {
+ u_int32_t type;
+ DB_TXN *txnid;
+ DB_LSN prev_lsn;
+ u_int32_t opcode;
+} __txn_regop_args;
+
+
+#define DB_txn_ckp (DB_txn_BEGIN + 2)
+
+typedef struct _txn_ckp_args {
+ u_int32_t type;
+ DB_TXN *txnid;
+ DB_LSN prev_lsn;
+ DB_LSN ckp_lsn;
+ DB_LSN last_ckp;
+} __txn_ckp_args;
+
+
+#define DB_txn_xa_regop (DB_txn_BEGIN + 3)
+
+typedef struct _txn_xa_regop_args {
+ u_int32_t type;
+ DB_TXN *txnid;
+ DB_LSN prev_lsn;
+ u_int32_t opcode;
+ DBT xid;
+ int32_t formatID;
+ u_int32_t gtrid;
+ u_int32_t bqual;
+ DB_LSN begin_lsn;
+} __txn_xa_regop_args;
+
+
+#define DB_txn_child (DB_txn_BEGIN + 4)
+
+typedef struct _txn_child_args {
+ u_int32_t type;
+ DB_TXN *txnid;
+ DB_LSN prev_lsn;
+ u_int32_t opcode;
+ u_int32_t parent;
+} __txn_child_args;
+
+#endif
diff --git a/usr/src/cmd/sendmail/db/include/txn_ext.h b/usr/src/cmd/sendmail/db/include/txn_ext.h
new file mode 100644
index 0000000000..e0d69c360d
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/include/txn_ext.h
@@ -0,0 +1,41 @@
+/* DO NOT EDIT: automatically built by dist/distrib. */
+#ifndef _txn_ext_h_
+#define _txn_ext_h_
+void __txn_panic __P((DB_ENV *));
+int __txn_xa_begin __P((DB_ENV *, DB_TXN *));
+int __txn_is_ancestor __P((DB_TXNMGR *, size_t, size_t));
+int __txn_regop_log
+ __P((DB_LOG *, DB_TXN *, DB_LSN *, u_int32_t,
+ u_int32_t));
+int __txn_regop_print
+ __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+int __txn_regop_read __P((void *, __txn_regop_args **));
+int __txn_ckp_log
+ __P((DB_LOG *, DB_TXN *, DB_LSN *, u_int32_t,
+ DB_LSN *, DB_LSN *));
+int __txn_ckp_print
+ __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+int __txn_ckp_read __P((void *, __txn_ckp_args **));
+int __txn_xa_regop_log
+ __P((DB_LOG *, DB_TXN *, DB_LSN *, u_int32_t,
+ u_int32_t, const DBT *, int32_t, u_int32_t,
+ u_int32_t, DB_LSN *));
+int __txn_xa_regop_print
+ __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+int __txn_xa_regop_read __P((void *, __txn_xa_regop_args **));
+int __txn_child_log
+ __P((DB_LOG *, DB_TXN *, DB_LSN *, u_int32_t,
+ u_int32_t, u_int32_t));
+int __txn_child_print
+ __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+int __txn_child_read __P((void *, __txn_child_args **));
+int __txn_init_print __P((DB_ENV *));
+int __txn_init_recover __P((DB_ENV *));
+int __txn_regop_recover
+ __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+int __txn_xa_regop_recover
+ __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+int __txn_ckp_recover __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+int __txn_child_recover
+ __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+#endif /* _txn_ext_h_ */
diff --git a/usr/src/cmd/sendmail/db/include/xa.h b/usr/src/cmd/sendmail/db/include/xa.h
new file mode 100644
index 0000000000..c630044fb5
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/include/xa.h
@@ -0,0 +1,182 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1998
+ * Sleepycat Software. All rights reserved.
+ *
+ * @(#)xa.h 10.1 (Sleepycat) 6/22/98
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+ * Start of xa.h header
+ *
+ * Define a symbol to prevent multiple inclusions of this header file
+ */
+#ifndef XA_H
+#define XA_H
+
+/*
+ * Transaction branch identification: XID and NULLXID:
+ */
+#define XIDDATASIZE 128 /* size in bytes */
+#define MAXGTRIDSIZE 64 /* maximum size in bytes of gtrid */
+#define MAXBQUALSIZE 64 /* maximum size in bytes of bqual */
+
+struct xid_t {
+ long formatID; /* format identifier */
+ long gtrid_length; /* value from 1 through 64 */
+ long bqual_length; /* value from 1 through 64 */
+ char data[XIDDATASIZE];
+};
+typedef struct xid_t XID;
+/*
+ * A value of -1 in formatID means that the XID is null.
+ */
+
+/*
+ * Declarations of routines by which RMs call TMs:
+ */
+extern int ax_reg __P((int, XID *, long));
+extern int ax_unreg __P((int, long));
+
+/*
+ * XA Switch Data Structure
+ */
+#define RMNAMESZ 32 /* length of resource manager name, */
+ /* including the null terminator */
+#define MAXINFOSIZE 256 /* maximum size in bytes of xa_info */
+ /* strings, including the null
+ terminator */
+struct xa_switch_t {
+ char name[RMNAMESZ]; /* name of resource manager */
+ long flags; /* resource manager specific options */
+ long version; /* must be 0 */
+ int (*xa_open_entry) /* xa_open function pointer */
+ __P((char *, int, long));
+ int (*xa_close_entry) /* xa_close function pointer */
+ __P((char *, int, long));
+ int (*xa_start_entry) /* xa_start function pointer */
+ __P((XID *, int, long));
+ int (*xa_end_entry) /* xa_end function pointer */
+ __P((XID *, int, long));
+ int (*xa_rollback_entry) /* xa_rollback function pointer */
+ __P((XID *, int, long));
+ int (*xa_prepare_entry) /* xa_prepare function pointer */
+ __P((XID *, int, long));
+ int (*xa_commit_entry) /* xa_commit function pointer */
+ __P((XID *, int, long));
+ int (*xa_recover_entry) /* xa_recover function pointer */
+ __P((XID *, long, int, long));
+ int (*xa_forget_entry) /* xa_forget function pointer */
+ __P((XID *, int, long));
+ int (*xa_complete_entry) /* xa_complete function pointer */
+ __P((int *, int *, int, long));
+};
+
+/*
+ * Flag definitions for the RM switch
+ */
+#define TMNOFLAGS 0x00000000L /* no resource manager features
+ selected */
+#define TMREGISTER 0x00000001L /* resource manager dynamically
+ registers */
+#define TMNOMIGRATE 0x00000002L /* resource manager does not support
+ association migration */
+#define TMUSEASYNC 0x00000004L /* resource manager supports
+ asynchronous operations */
+/*
+ * Flag definitions for xa_ and ax_ routines
+ */
+/* use TMNOFLAGGS, defined above, when not specifying other flags */
+#define TMASYNC 0x80000000L /* perform routine asynchronously */
+#define TMONEPHASE 0x40000000L /* caller is using one-phase commit
+ optimisation */
+#define TMFAIL 0x20000000L /* dissociates caller and marks
+ transaction branch rollback-only */
+#define TMNOWAIT 0x10000000L /* return if blocking condition
+ exists */
+#define TMRESUME 0x08000000L /* caller is resuming association with
+ suspended transaction branch */
+#define TMSUCCESS 0x04000000L /* dissociate caller from transaction
+ branch */
+#define TMSUSPEND 0x02000000L /* caller is suspending, not ending,
+ association */
+#define TMSTARTRSCAN 0x01000000L /* start a recovery scan */
+#define TMENDRSCAN 0x00800000L /* end a recovery scan */
+#define TMMULTIPLE 0x00400000L /* wait for any asynchronous
+ operation */
+#define TMJOIN 0x00200000L /* caller is joining existing
+ transaction branch */
+#define TMMIGRATE 0x00100000L /* caller intends to perform
+ migration */
+
+/*
+ * ax_() return codes (transaction manager reports to resource manager)
+ */
+#define TM_JOIN 2 /* caller is joining existing
+ transaction branch */
+#define TM_RESUME 1 /* caller is resuming association with
+ suspended transaction branch */
+#define TM_OK 0 /* normal execution */
+#define TMER_TMERR -1 /* an error occurred in the transaction
+ manager */
+#define TMER_INVAL -2 /* invalid arguments were given */
+#define TMER_PROTO -3 /* routine invoked in an improper
+ context */
+
+/*
+ * xa_() return codes (resource manager reports to transaction manager)
+ */
+#define XA_RBBASE 100 /* The inclusive lower bound of the
+ rollback codes */
+#define XA_RBROLLBACK XA_RBBASE /* The rollback was caused by an
+ unspecified reason */
+#define XA_RBCOMMFAIL XA_RBBASE+1 /* The rollback was caused by a
+ communication failure */
+#define XA_RBDEADLOCK XA_RBBASE+2 /* A deadlock was detected */
+#define XA_RBINTEGRITY XA_RBBASE+3 /* A condition that violates the
+ integrity of the resources was
+ detected */
+#define XA_RBOTHER XA_RBBASE+4 /* The resource manager rolled back the
+ transaction branch for a reason not
+ on this list */
+#define XA_RBPROTO XA_RBBASE+5 /* A protocol error occurred in the
+ resource manager */
+#define XA_RBTIMEOUT XA_RBBASE+6 /* A transaction branch took too long */
+#define XA_RBTRANSIENT XA_RBBASE+7 /* May retry the transaction branch */
+#define XA_RBEND XA_RBTRANSIENT /* The inclusive upper bound of the
+ rollback codes */
+#define XA_NOMIGRATE 9 /* resumption must occur where
+ suspension occurred */
+#define XA_HEURHAZ 8 /* the transaction branch may have
+ been heuristically completed */
+#define XA_HEURCOM 7 /* the transaction branch has been
+ heuristically committed */
+#define XA_HEURRB 6 /* the transaction branch has been
+ heuristically rolled back */
+#define XA_HEURMIX 5 /* the transaction branch has been
+ heuristically committed and rolled
+ back */
+#define XA_RETRY 4 /* routine returned with no effect and
+ may be re-issued */
+#define XA_RDONLY 3 /* the transaction branch was read-only
+ and has been committed */
+#define XA_OK 0 /* normal execution */
+#define XAER_ASYNC -2 /* asynchronous operation already
+ outstanding */
+#define XAER_RMERR -3 /* a resource manager error occurred in
+ the transaction branch */
+#define XAER_NOTA -4 /* the XID is not valid */
+#define XAER_INVAL -5 /* invalid arguments were given */
+#define XAER_PROTO -6 /* routine invoked in an improper
+ context */
+#define XAER_RMFAIL -7 /* resource manager unavailable */
+#define XAER_DUPID -8 /* the XID already exists */
+#define XAER_OUTSIDE -9 /* resource manager doing work outside
+ transaction */
+#endif /* ifndef XA_H */
+/*
+ * End of xa.h header
+ */
diff --git a/usr/src/cmd/sendmail/db/include/xa_ext.h b/usr/src/cmd/sendmail/db/include/xa_ext.h
new file mode 100644
index 0000000000..f93f19c30a
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/include/xa_ext.h
@@ -0,0 +1,15 @@
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/* DO NOT EDIT: automatically built by dist/distrib. */
+#ifndef _xa_ext_h_
+#define _xa_ext_h_
+int __db_rmid_to_env __P((int rmid, DB_ENV **envp, int open_ok));
+int __db_xid_to_txn __P((DB_ENV *, XID *, size_t *));
+int __db_map_rmid __P((int, DB_ENV *));
+int __db_unmap_rmid __P((int));
+int __db_map_xid __P((DB_ENV *, XID *, size_t));
+void __db_unmap_xid __P((DB_ENV *, XID *, size_t));
+int __db_map_rmid_name __P((int, char *));
+int __db_rmid_to_name __P((int, char **));
+ void __db_unmap_rmid_name __P((int));
+#endif /* _xa_ext_h_ */
diff --git a/usr/src/cmd/sendmail/db/lock/lock.c b/usr/src/cmd/sendmail/db/lock/lock.c
new file mode 100644
index 0000000000..4cf1d9ecca
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/lock/lock.c
@@ -0,0 +1,1034 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)lock.c 10.61 (Sleepycat) 1/3/99";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <errno.h>
+#include <string.h>
+#endif
+
+#include "db_int.h"
+#include "shqueue.h"
+#include "db_page.h"
+#include "db_shash.h"
+#include "lock.h"
+#include "db_am.h"
+#include "txn_auto.h"
+#include "txn_ext.h"
+#include "common_ext.h"
+
+static void __lock_checklocker __P((DB_LOCKTAB *, struct __db_lock *, int));
+static void __lock_freeobj __P((DB_LOCKTAB *, DB_LOCKOBJ *));
+static int __lock_get_internal __P((DB_LOCKTAB *, u_int32_t, DB_TXN *,
+ u_int32_t, const DBT *, db_lockmode_t, struct __db_lock **));
+static int __lock_is_parent __P((u_int32_t, DB_TXN *));
+static int __lock_promote __P((DB_LOCKTAB *, DB_LOCKOBJ *));
+static int __lock_put_internal __P((DB_LOCKTAB *, struct __db_lock *, int));
+static void __lock_remove_waiter
+ __P((DB_LOCKTAB *, DB_LOCKOBJ *, struct __db_lock *, db_status_t));
+static int __lock_vec_internal __P((DB_LOCKTAB *, u_int32_t, DB_TXN *,
+ u_int32_t, DB_LOCKREQ *, int, DB_LOCKREQ **elistp));
+
+int
+lock_id(lt, idp)
+ DB_LOCKTAB *lt;
+ u_int32_t *idp;
+{
+ u_int32_t id;
+
+ LOCK_PANIC_CHECK(lt);
+
+ LOCK_LOCKREGION(lt);
+ if (lt->region->id >= DB_LOCK_MAXID)
+ lt->region->id = 0;
+ id = ++lt->region->id;
+ UNLOCK_LOCKREGION(lt);
+
+ *idp = id;
+ return (0);
+}
+
+int
+lock_vec(lt, locker, flags, list, nlist, elistp)
+ DB_LOCKTAB *lt;
+ u_int32_t locker, flags;
+ int nlist;
+ DB_LOCKREQ *list, **elistp;
+{
+ return (__lock_vec_internal(lt,
+ locker, NULL, flags, list, nlist, elistp));
+}
+
+int
+lock_tvec(lt, txn, flags, list, nlist, elistp)
+ DB_LOCKTAB *lt;
+ DB_TXN *txn;
+ u_int32_t flags;
+ int nlist;
+ DB_LOCKREQ *list, **elistp;
+{
+ return (__lock_vec_internal(lt,
+ txn->txnid, txn, flags, list, nlist, elistp));
+}
+
+static int
+__lock_vec_internal(lt, locker, txn, flags, list, nlist, elistp)
+ DB_LOCKTAB *lt;
+ u_int32_t locker;
+ DB_TXN *txn;
+ u_int32_t flags;
+ int nlist;
+ DB_LOCKREQ *list, **elistp;
+{
+ struct __db_lock *lp;
+ DB_LOCKOBJ *sh_obj, *sh_locker, *sh_parent;
+ int i, ret, run_dd;
+
+ LOCK_PANIC_CHECK(lt);
+
+ /* Validate arguments. */
+ if ((ret =
+ __db_fchk(lt->dbenv, "lock_vec", flags, DB_LOCK_NOWAIT)) != 0)
+ return (ret);
+
+ LOCK_LOCKREGION(lt);
+
+ if ((ret = __lock_validate_region(lt)) != 0) {
+ UNLOCK_LOCKREGION(lt);
+ return (ret);
+ }
+
+ ret = 0;
+ for (i = 0; i < nlist && ret == 0; i++) {
+ switch (list[i].op) {
+ case DB_LOCK_GET:
+ ret = __lock_get_internal(lt, locker, txn, flags,
+ list[i].obj, list[i].mode, &lp);
+ if (ret == 0) {
+ list[i].lock = LOCK_TO_OFFSET(lt, lp);
+ lt->region->nrequests++;
+ }
+ break;
+ case DB_LOCK_INHERIT:
+ /* Find the locker. */
+ if ((ret = __lock_getobj(lt, locker,
+ NULL, DB_LOCK_LOCKER, &sh_locker)) != 0)
+ break;
+ if (txn == NULL || txn->parent == NULL) {
+ ret = EINVAL;
+ break;
+ }
+
+ if ((ret = __lock_getobj(lt, txn->parent->txnid,
+ NULL, DB_LOCK_LOCKER, &sh_parent)) != 0)
+ break;
+
+ /*
+ * Traverse all the locks held by this locker. Remove
+ * the locks from the locker's list and put them on the
+ * parent's list.
+ */
+ for (lp = SH_LIST_FIRST(&sh_locker->heldby, __db_lock);
+ lp != NULL;
+ lp = SH_LIST_FIRST(&sh_locker->heldby, __db_lock)) {
+ SH_LIST_REMOVE(lp, locker_links, __db_lock);
+ SH_LIST_INSERT_HEAD(&sh_parent->heldby, lp,
+ locker_links, __db_lock);
+ lp->holder = txn->parent->txnid;
+ }
+ __lock_freeobj(lt, sh_locker);
+ lt->region->nlockers--;
+ break;
+ case DB_LOCK_PUT:
+ lp = OFFSET_TO_LOCK(lt, list[i].lock);
+ if (lp->holder != locker) {
+ ret = DB_LOCK_NOTHELD;
+ break;
+ }
+ list[i].mode = lp->mode;
+
+ ret = __lock_put_internal(lt, lp, 0);
+ __lock_checklocker(lt, lp, 0);
+ break;
+ case DB_LOCK_PUT_ALL:
+ /* Find the locker. */
+ if ((ret = __lock_getobj(lt, locker,
+ NULL, DB_LOCK_LOCKER, &sh_locker)) != 0)
+ break;
+
+ for (lp = SH_LIST_FIRST(&sh_locker->heldby, __db_lock);
+ lp != NULL;
+ lp = SH_LIST_FIRST(&sh_locker->heldby, __db_lock)) {
+ if ((ret = __lock_put_internal(lt, lp, 1)) != 0)
+ break;
+ }
+ __lock_freeobj(lt, sh_locker);
+ lt->region->nlockers--;
+ break;
+ case DB_LOCK_PUT_OBJ:
+
+ /* Look up the object in the hash table. */
+ HASHLOOKUP(lt->hashtab, __db_lockobj, links,
+ list[i].obj, sh_obj, lt->region->table_size,
+ __lock_ohash, __lock_cmp);
+ if (sh_obj == NULL) {
+ ret = EINVAL;
+ break;
+ }
+ /*
+ * Release waiters first, because they won't cause
+ * anyone else to be awakened. If we release the
+ * lockers first, all the waiters get awakened
+ * needlessly.
+ */
+ for (lp = SH_TAILQ_FIRST(&sh_obj->waiters, __db_lock);
+ lp != NULL;
+ lp = SH_TAILQ_FIRST(&sh_obj->waiters, __db_lock)) {
+ lt->region->nreleases += lp->refcount;
+ __lock_remove_waiter(lt, sh_obj, lp,
+ DB_LSTAT_NOGRANT);
+ __lock_checklocker(lt, lp, 1);
+ }
+
+ for (lp = SH_TAILQ_FIRST(&sh_obj->holders, __db_lock);
+ lp != NULL;
+ lp = SH_TAILQ_FIRST(&sh_obj->holders, __db_lock)) {
+
+ lt->region->nreleases += lp->refcount;
+ SH_LIST_REMOVE(lp, locker_links, __db_lock);
+ SH_TAILQ_REMOVE(&sh_obj->holders, lp, links,
+ __db_lock);
+ lp->status = DB_LSTAT_FREE;
+ SH_TAILQ_INSERT_HEAD(&lt->region->free_locks,
+ lp, links, __db_lock);
+ }
+
+ /* Now free the object. */
+ __lock_freeobj(lt, sh_obj);
+ break;
+#ifdef DEBUG
+ case DB_LOCK_DUMP:
+ /* Find the locker. */
+ if ((ret = __lock_getobj(lt, locker,
+ NULL, DB_LOCK_LOCKER, &sh_locker)) != 0)
+ break;
+
+ for (lp = SH_LIST_FIRST(&sh_locker->heldby, __db_lock);
+ lp != NULL;
+ lp = SH_LIST_NEXT(lp, locker_links, __db_lock)) {
+ __lock_printlock(lt, lp, 1);
+ ret = EINVAL;
+ }
+ if (ret == 0) {
+ __lock_freeobj(lt, sh_locker);
+ lt->region->nlockers--;
+ }
+ break;
+#endif
+ default:
+ ret = EINVAL;
+ break;
+ }
+ }
+
+ if (lt->region->need_dd && lt->region->detect != DB_LOCK_NORUN) {
+ run_dd = 1;
+ lt->region->need_dd = 0;
+ } else
+ run_dd = 0;
+
+ UNLOCK_LOCKREGION(lt);
+
+ if (ret == 0 && run_dd)
+ lock_detect(lt, 0, lt->region->detect);
+
+ if (elistp && ret != 0)
+ *elistp = &list[i - 1];
+ return (ret);
+}
+
+int
+lock_get(lt, locker, flags, obj, lock_mode, lock)
+ DB_LOCKTAB *lt;
+ u_int32_t locker, flags;
+ const DBT *obj;
+ db_lockmode_t lock_mode;
+ DB_LOCK *lock;
+{
+ struct __db_lock *lockp;
+ int ret;
+
+ LOCK_PANIC_CHECK(lt);
+
+ /* Validate arguments. */
+ if ((ret = __db_fchk(lt->dbenv,
+ "lock_get", flags, DB_LOCK_NOWAIT | DB_LOCK_UPGRADE)) != 0)
+ return (ret);
+
+ LOCK_LOCKREGION(lt);
+
+ if ((ret = __lock_validate_region(lt)) == 0) {
+ if (LF_ISSET(DB_LOCK_UPGRADE))
+ lockp = OFFSET_TO_LOCK(lt, *lock);
+
+ if ((ret = __lock_get_internal(lt,
+ locker, NULL, flags, obj, lock_mode, &lockp)) == 0) {
+ if (!LF_ISSET(DB_LOCK_UPGRADE))
+ *lock = LOCK_TO_OFFSET(lt, lockp);
+ lt->region->nrequests++;
+ }
+ }
+
+ UNLOCK_LOCKREGION(lt);
+ return (ret);
+}
+
+int
+lock_tget(lt, txn, flags, obj, lock_mode, lock)
+ DB_LOCKTAB *lt;
+ DB_TXN *txn;
+ u_int32_t flags;
+ const DBT *obj;
+ db_lockmode_t lock_mode;
+ DB_LOCK *lock;
+{
+ struct __db_lock *lockp;
+ int ret;
+
+ LOCK_PANIC_CHECK(lt);
+
+ /* Validate arguments. */
+ if ((ret = __db_fchk(lt->dbenv,
+ "lock_get", flags, DB_LOCK_NOWAIT | DB_LOCK_UPGRADE)) != 0)
+ return (ret);
+
+ LOCK_LOCKREGION(lt);
+
+ if ((ret = __lock_validate_region(lt)) == 0) {
+ if (LF_ISSET(DB_LOCK_UPGRADE))
+ lockp = OFFSET_TO_LOCK(lt, *lock);
+
+ if ((ret = __lock_get_internal(lt,
+ txn->txnid, txn, flags, obj, lock_mode, &lockp)) == 0) {
+ if (!LF_ISSET(DB_LOCK_UPGRADE))
+ *lock = LOCK_TO_OFFSET(lt, lockp);
+ lt->region->nrequests++;
+ }
+ }
+
+ UNLOCK_LOCKREGION(lt);
+ return (ret);
+}
+int
+lock_put(lt, lock)
+ DB_LOCKTAB *lt;
+ DB_LOCK lock;
+{
+ struct __db_lock *lockp;
+ int ret, run_dd;
+
+ LOCK_PANIC_CHECK(lt);
+
+ LOCK_LOCKREGION(lt);
+
+ if ((ret = __lock_validate_region(lt)) != 0)
+ return (ret);
+ else {
+ lockp = OFFSET_TO_LOCK(lt, lock);
+ ret = __lock_put_internal(lt, lockp, 0);
+ }
+
+ __lock_checklocker(lt, lockp, 0);
+
+ if (lt->region->need_dd && lt->region->detect != DB_LOCK_NORUN) {
+ run_dd = 1;
+ lt->region->need_dd = 0;
+ } else
+ run_dd = 0;
+
+ UNLOCK_LOCKREGION(lt);
+
+ if (ret == 0 && run_dd)
+ lock_detect(lt, 0, lt->region->detect);
+
+ return (ret);
+}
+
+static int
+__lock_put_internal(lt, lockp, do_all)
+ DB_LOCKTAB *lt;
+ struct __db_lock *lockp;
+ int do_all;
+{
+ DB_LOCKOBJ *sh_obj;
+ int state_changed;
+
+ if (lockp->refcount == 0 || (lockp->status != DB_LSTAT_HELD &&
+ lockp->status != DB_LSTAT_WAITING) || lockp->obj == 0) {
+ __db_err(lt->dbenv, "lock_put: invalid lock %lu",
+ (u_long)((u_int8_t *)lockp - (u_int8_t *)lt->region));
+ return (EINVAL);
+ }
+
+ if (do_all)
+ lt->region->nreleases += lockp->refcount;
+ else
+ lt->region->nreleases++;
+ if (do_all == 0 && lockp->refcount > 1) {
+ lockp->refcount--;
+ return (0);
+ }
+
+ /* Get the object associated with this lock. */
+ sh_obj = (DB_LOCKOBJ *)((u_int8_t *)lockp + lockp->obj);
+
+ /* Remove lock from locker list. */
+ SH_LIST_REMOVE(lockp, locker_links, __db_lock);
+
+ /* Remove this lock from its holders/waitlist. */
+ if (lockp->status != DB_LSTAT_HELD)
+ __lock_remove_waiter(lt, sh_obj, lockp, DB_LSTAT_FREE);
+ else
+ SH_TAILQ_REMOVE(&sh_obj->holders, lockp, links, __db_lock);
+
+ state_changed = __lock_promote(lt, sh_obj);
+
+ /* Check if object should be reclaimed. */
+ if (SH_TAILQ_FIRST(&sh_obj->holders, __db_lock) == NULL) {
+ HASHREMOVE_EL(lt->hashtab, __db_lockobj,
+ links, sh_obj, lt->region->table_size, __lock_lhash);
+ if (sh_obj->lockobj.size > sizeof(sh_obj->objdata))
+ __db_shalloc_free(lt->mem,
+ SH_DBT_PTR(&sh_obj->lockobj));
+ SH_TAILQ_INSERT_HEAD(&lt->region->free_objs, sh_obj, links,
+ __db_lockobj);
+ state_changed = 1;
+ }
+
+ /* Free lock. */
+ lockp->status = DB_LSTAT_FREE;
+ SH_TAILQ_INSERT_HEAD(&lt->region->free_locks, lockp, links, __db_lock);
+
+ /*
+ * If we did not promote anyone; we need to run the deadlock
+ * detector again.
+ */
+ if (state_changed == 0)
+ lt->region->need_dd = 1;
+
+ return (0);
+}
+
+static int
+__lock_get_internal(lt, locker, txn, flags, obj, lock_mode, lockp)
+ DB_LOCKTAB *lt;
+ u_int32_t locker, flags;
+ DB_TXN *txn;
+ const DBT *obj;
+ db_lockmode_t lock_mode;
+ struct __db_lock **lockp;
+{
+ struct __db_lock *newl, *lp;
+ DB_LOCKOBJ *sh_obj, *sh_locker;
+ DB_LOCKREGION *lrp;
+ size_t newl_off;
+ int ihold, no_dd, ret;
+
+ no_dd = ret = 0;
+
+ /*
+ * Check that lock mode is valid.
+ */
+ lrp = lt->region;
+ if ((u_int32_t)lock_mode >= lrp->nmodes) {
+ __db_err(lt->dbenv,
+ "lock_get: invalid lock mode %lu\n", (u_long)lock_mode);
+ return (EINVAL);
+ }
+
+ /* Allocate a new lock. Optimize for the common case of a grant. */
+ if ((newl = SH_TAILQ_FIRST(&lrp->free_locks, __db_lock)) == NULL) {
+ if ((ret = __lock_grow_region(lt, DB_LOCK_LOCK, 0)) != 0)
+ return (ret);
+ lrp = lt->region;
+ newl = SH_TAILQ_FIRST(&lrp->free_locks, __db_lock);
+ }
+ newl_off = LOCK_TO_OFFSET(lt, newl);
+
+ /* Optimize for common case of granting a lock. */
+ SH_TAILQ_REMOVE(&lrp->free_locks, newl, links, __db_lock);
+
+ newl->mode = lock_mode;
+ newl->status = DB_LSTAT_HELD;
+ newl->holder = locker;
+ newl->refcount = 1;
+
+ if ((ret = __lock_getobj(lt, 0, obj, DB_LOCK_OBJTYPE, &sh_obj)) != 0)
+ return (ret);
+
+ lrp = lt->region; /* getobj might have grown */
+ newl = OFFSET_TO_LOCK(lt, newl_off);
+
+ /* Now make new lock point to object */
+ newl->obj = SH_PTR_TO_OFF(newl, sh_obj);
+
+ /*
+ * Now we have a lock and an object and we need to see if we should
+ * grant the lock. We use a FIFO ordering so we can only grant a
+ * new lock if it does not conflict with anyone on the holders list
+ * OR anyone on the waiters list. The reason that we don't grant if
+ * there's a conflict is that this can lead to starvation (a writer
+ * waiting on a popularly read item will never be granted). The
+ * downside of this is that a waiting reader can prevent an upgrade
+ * from reader to writer, which is not uncommon.
+ *
+ * There is one exception to the no-conflict rule. If a lock is held
+ * by the requesting locker AND the new lock does not conflict with
+ * any other holders, then we grant the lock. The most common place
+ * this happens is when the holder has a WRITE lock and a READ lock
+ * request comes in for the same locker. If we do not grant the read
+ * lock, then we guarantee deadlock.
+ *
+ * In case of conflict, we put the new lock on the end of the waiters
+ * list, unless we are upgrading in which case the locker goes on the
+ * front of the list.
+ */
+ ihold = 0;
+ for (lp = SH_TAILQ_FIRST(&sh_obj->holders, __db_lock);
+ lp != NULL;
+ lp = SH_TAILQ_NEXT(lp, links, __db_lock)) {
+ if (locker == lp->holder ||
+ __lock_is_parent(lp->holder, txn)) {
+ if (lp->mode == lock_mode &&
+ lp->status == DB_LSTAT_HELD) {
+ if (LF_ISSET(DB_LOCK_UPGRADE))
+ goto upgrade;
+
+ /*
+ * Lock is held, so we can increment the
+ * reference count and return this lock.
+ */
+ lp->refcount++;
+ *lockp = lp;
+ SH_TAILQ_INSERT_HEAD(&lrp->free_locks,
+ newl, links, __db_lock);
+ return (0);
+ } else
+ ihold = 1;
+ } else if (CONFLICTS(lt, lp->mode, lock_mode))
+ break;
+ }
+
+ /*
+ * If we are upgrading, then there are two scenarios. Either
+ * we had no conflicts, so we can do the upgrade. Or, there
+ * is a conflict and we should wait at the HEAD of the waiters
+ * list.
+ */
+ if (LF_ISSET(DB_LOCK_UPGRADE)) {
+ if (lp == NULL)
+ goto upgrade;
+
+ /* There was a conflict, wait. */
+ SH_TAILQ_INSERT_HEAD(&sh_obj->waiters, newl, links, __db_lock);
+ goto wait;
+ }
+
+ if (lp == NULL && !ihold)
+ for (lp = SH_TAILQ_FIRST(&sh_obj->waiters, __db_lock);
+ lp != NULL;
+ lp = SH_TAILQ_NEXT(lp, links, __db_lock)) {
+ if (CONFLICTS(lt, lp->mode, lock_mode) &&
+ locker != lp->holder)
+ break;
+ }
+ if (lp == NULL)
+ SH_TAILQ_INSERT_TAIL(&sh_obj->holders, newl, links);
+ else if (!(flags & DB_LOCK_NOWAIT))
+ SH_TAILQ_INSERT_TAIL(&sh_obj->waiters, newl, links);
+ else {
+ /* Free the lock and return an error. */
+ newl->status = DB_LSTAT_FREE;
+ SH_TAILQ_INSERT_HEAD(&lrp->free_locks, newl, links, __db_lock);
+ return (DB_LOCK_NOTGRANTED);
+ }
+
+ /*
+ * Now, insert the lock onto its locker's list. If the locker does
+ * not currently hold any locks, there's no reason to run a deadlock
+ * detector, save that information.
+ */
+ if ((ret =
+ __lock_getobj(lt, locker, NULL, DB_LOCK_LOCKER, &sh_locker)) != 0)
+ return (ret);
+ no_dd = SH_LIST_FIRST(&sh_locker->heldby, __db_lock) == NULL;
+
+ lrp = lt->region;
+ SH_LIST_INSERT_HEAD(&sh_locker->heldby, newl, locker_links, __db_lock);
+
+ if (lp != NULL) {
+ /*
+ * This is really a blocker for the process, so initialize it
+ * set. That way the current process will block when it tries
+ * to get it and the waking process will release it.
+ */
+wait: (void)__db_mutex_init(&newl->mutex,
+ MUTEX_LOCK_OFFSET(lt->region, &newl->mutex));
+ (void)__db_mutex_lock(&newl->mutex, lt->reginfo.fd);
+
+ newl->status = DB_LSTAT_WAITING;
+ lrp->nconflicts++;
+
+ /*
+ * We are about to wait; must release the region mutex. Then,
+ * when we wakeup, we need to reacquire the region mutex before
+ * continuing.
+ */
+ if (lrp->detect == DB_LOCK_NORUN)
+ lt->region->need_dd = 1;
+ UNLOCK_LOCKREGION(lt);
+
+ /*
+ * We are about to wait; before waiting, see if the deadlock
+ * detector should be run.
+ */
+ if (lrp->detect != DB_LOCK_NORUN && !no_dd)
+ (void)lock_detect(lt, 0, lrp->detect);
+
+ (void)__db_mutex_lock(&newl->mutex, lt->reginfo.fd);
+
+ LOCK_LOCKREGION(lt);
+ if (newl->status != DB_LSTAT_PENDING) {
+ /*
+ * If this lock errored due to a deadlock, then
+ * we have waiters that require promotion.
+ */
+ if (newl->status == DB_LSTAT_ABORTED)
+ (void)__lock_promote(lt, sh_obj);
+ /* Return to free list. */
+ __lock_checklocker(lt, newl, 0);
+ SH_TAILQ_INSERT_HEAD(&lrp->free_locks, newl, links,
+ __db_lock);
+ switch (newl->status) {
+ case DB_LSTAT_ABORTED:
+ ret = DB_LOCK_DEADLOCK;
+ break;
+ case DB_LSTAT_NOGRANT:
+ ret = DB_LOCK_NOTGRANTED;
+ break;
+ default:
+ ret = EINVAL;
+ break;
+ }
+ newl->status = DB_LSTAT_FREE;
+ newl = NULL;
+ } else if (LF_ISSET(DB_LOCK_UPGRADE)) {
+ /*
+ * The lock that was just granted got put on the
+ * holders list. Since we're upgrading some other
+ * lock, we've got to remove it here.
+ */
+ SH_TAILQ_REMOVE(&sh_obj->holders,
+ newl, links, __db_lock);
+ goto upgrade;
+ } else
+ newl->status = DB_LSTAT_HELD;
+ }
+
+ *lockp = newl;
+ return (ret);
+
+upgrade:
+ /*
+ * This was an upgrade, so return the new lock to the free list and
+ * upgrade the mode.
+ */
+ (*lockp)->mode = lock_mode;
+ newl->status = DB_LSTAT_FREE;
+ SH_TAILQ_INSERT_HEAD(&lrp->free_locks, newl, links, __db_lock);
+ return (0);
+}
+
+/*
+ * __lock_is_locked --
+ *
+ * PUBLIC: int __lock_is_locked
+ * PUBLIC: __P((DB_LOCKTAB *, u_int32_t, DBT *, db_lockmode_t));
+ */
+int
+__lock_is_locked(lt, locker, dbt, mode)
+ DB_LOCKTAB *lt;
+ u_int32_t locker;
+ DBT *dbt;
+ db_lockmode_t mode;
+{
+ struct __db_lock *lp;
+ DB_LOCKOBJ *sh_obj;
+ DB_LOCKREGION *lrp;
+
+ lrp = lt->region;
+
+ /* Look up the object in the hash table. */
+ HASHLOOKUP(lt->hashtab, __db_lockobj, links,
+ dbt, sh_obj, lrp->table_size, __lock_ohash, __lock_cmp);
+ if (sh_obj == NULL)
+ return (0);
+
+ for (lp = SH_TAILQ_FIRST(&sh_obj->holders, __db_lock);
+ lp != NULL;
+ lp = SH_TAILQ_FIRST(&sh_obj->holders, __db_lock)) {
+ if (lp->holder == locker && lp->mode == mode)
+ return (1);
+ }
+
+ return (0);
+}
+
+/*
+ * __lock_printlock --
+ *
+ * PUBLIC: void __lock_printlock __P((DB_LOCKTAB *, struct __db_lock *, int));
+ */
+void
+__lock_printlock(lt, lp, ispgno)
+ DB_LOCKTAB *lt;
+ struct __db_lock *lp;
+ int ispgno;
+{
+ DB_LOCKOBJ *lockobj;
+ db_pgno_t pgno;
+ size_t obj;
+ u_int8_t *ptr;
+ const char *mode, *status;
+
+ switch (lp->mode) {
+ case DB_LOCK_IREAD:
+ mode = "IREAD";
+ break;
+ case DB_LOCK_IWR:
+ mode = "IWR";
+ break;
+ case DB_LOCK_IWRITE:
+ mode = "IWRITE";
+ break;
+ case DB_LOCK_NG:
+ mode = "NG";
+ break;
+ case DB_LOCK_READ:
+ mode = "READ";
+ break;
+ case DB_LOCK_WRITE:
+ mode = "WRITE";
+ break;
+ default:
+ mode = "UNKNOWN";
+ break;
+ }
+ switch (lp->status) {
+ case DB_LSTAT_ABORTED:
+ status = "ABORT";
+ break;
+ case DB_LSTAT_ERR:
+ status = "ERROR";
+ break;
+ case DB_LSTAT_FREE:
+ status = "FREE";
+ break;
+ case DB_LSTAT_HELD:
+ status = "HELD";
+ break;
+ case DB_LSTAT_NOGRANT:
+ status = "NONE";
+ break;
+ case DB_LSTAT_WAITING:
+ status = "WAIT";
+ break;
+ case DB_LSTAT_PENDING:
+ status = "PENDING";
+ break;
+ default:
+ status = "UNKNOWN";
+ break;
+ }
+ printf("\t%lx\t%s\t%lu\t%s\t",
+ (u_long)lp->holder, mode, (u_long)lp->refcount, status);
+
+ lockobj = (DB_LOCKOBJ *)((u_int8_t *)lp + lp->obj);
+ ptr = SH_DBT_PTR(&lockobj->lockobj);
+ if (ispgno) {
+ /* Assume this is a DBT lock. */
+ memcpy(&pgno, ptr, sizeof(db_pgno_t));
+ printf("page %lu\n", (u_long)pgno);
+ } else {
+ obj = (u_int8_t *)lp + lp->obj - (u_int8_t *)lt->region;
+ printf("0x%lx ", (u_long)obj);
+ __db_pr(ptr, lockobj->lockobj.size);
+ printf("\n");
+ }
+}
+
+/*
+ * PUBLIC: int __lock_getobj __P((DB_LOCKTAB *,
+ * PUBLIC: u_int32_t, const DBT *, u_int32_t type, DB_LOCKOBJ **));
+ */
+int
+__lock_getobj(lt, locker, dbt, type, objp)
+ DB_LOCKTAB *lt;
+ u_int32_t locker, type;
+ const DBT *dbt;
+ DB_LOCKOBJ **objp;
+{
+ DB_LOCKREGION *lrp;
+ DB_LOCKOBJ *sh_obj;
+ u_int32_t obj_size;
+ int ret;
+ void *p, *src;
+
+ lrp = lt->region;
+
+ /* Look up the object in the hash table. */
+ if (type == DB_LOCK_OBJTYPE) {
+ HASHLOOKUP(lt->hashtab, __db_lockobj, links, dbt, sh_obj,
+ lrp->table_size, __lock_ohash, __lock_cmp);
+ obj_size = dbt->size;
+ } else {
+ HASHLOOKUP(lt->hashtab, __db_lockobj, links, locker,
+ sh_obj, lrp->table_size, __lock_locker_hash,
+ __lock_locker_cmp);
+ obj_size = sizeof(locker);
+ }
+
+ /*
+ * If we found the object, then we can just return it. If
+ * we didn't find the object, then we need to create it.
+ */
+ if (sh_obj == NULL) {
+ /* Create new object and then insert it into hash table. */
+ if ((sh_obj =
+ SH_TAILQ_FIRST(&lrp->free_objs, __db_lockobj)) == NULL) {
+ if ((ret = __lock_grow_region(lt, DB_LOCK_OBJ, 0)) != 0)
+ return (ret);
+ lrp = lt->region;
+ sh_obj = SH_TAILQ_FIRST(&lrp->free_objs, __db_lockobj);
+ }
+
+ /*
+ * If we can fit this object in the structure, do so instead
+ * of shalloc-ing space for it.
+ */
+ if (obj_size <= sizeof(sh_obj->objdata))
+ p = sh_obj->objdata;
+ else
+ if ((ret =
+ __db_shalloc(lt->mem, obj_size, 0, &p)) != 0) {
+ if ((ret = __lock_grow_region(lt,
+ DB_LOCK_MEM, obj_size)) != 0)
+ return (ret);
+ lrp = lt->region;
+ /* Reacquire the head of the list. */
+ sh_obj = SH_TAILQ_FIRST(&lrp->free_objs,
+ __db_lockobj);
+ (void)__db_shalloc(lt->mem, obj_size, 0, &p);
+ }
+
+ src = type == DB_LOCK_OBJTYPE ? dbt->data : (void *)&locker;
+ memcpy(p, src, obj_size);
+
+ sh_obj->type = type;
+ SH_TAILQ_REMOVE(&lrp->free_objs, sh_obj, links, __db_lockobj);
+
+ SH_TAILQ_INIT(&sh_obj->waiters);
+ if (type == DB_LOCK_LOCKER)
+ SH_LIST_INIT(&sh_obj->heldby);
+ else
+ SH_TAILQ_INIT(&sh_obj->holders);
+ sh_obj->lockobj.size = obj_size;
+ sh_obj->lockobj.off = SH_PTR_TO_OFF(&sh_obj->lockobj, p);
+
+ HASHINSERT(lt->hashtab,
+ __db_lockobj, links, sh_obj, lrp->table_size, __lock_lhash);
+
+ if (type == DB_LOCK_LOCKER)
+ lrp->nlockers++;
+ }
+
+ *objp = sh_obj;
+ return (0);
+}
+
+/*
+ * Any lock on the waitlist has a process waiting for it. Therefore, we
+ * can't return the lock to the freelist immediately. Instead, we can
+ * remove the lock from the list of waiters, set the status field of the
+ * lock, and then let the process waking up return the lock to the
+ * free list.
+ */
+static void
+__lock_remove_waiter(lt, sh_obj, lockp, status)
+ DB_LOCKTAB *lt;
+ DB_LOCKOBJ *sh_obj;
+ struct __db_lock *lockp;
+ db_status_t status;
+{
+ SH_TAILQ_REMOVE(&sh_obj->waiters, lockp, links, __db_lock);
+ lockp->status = status;
+
+ /* Wake whoever is waiting on this lock. */
+ (void)__db_mutex_unlock(&lockp->mutex, lt->reginfo.fd);
+}
+
+static void
+__lock_checklocker(lt, lockp, do_remove)
+ DB_LOCKTAB *lt;
+ struct __db_lock *lockp;
+ int do_remove;
+{
+ DB_LOCKOBJ *sh_locker;
+
+ if (do_remove)
+ SH_LIST_REMOVE(lockp, locker_links, __db_lock);
+
+ /* if the locker list is NULL, free up the object. */
+ if (__lock_getobj(lt, lockp->holder, NULL, DB_LOCK_LOCKER, &sh_locker)
+ == 0 && SH_LIST_FIRST(&sh_locker->heldby, __db_lock) == NULL) {
+ __lock_freeobj(lt, sh_locker);
+ lt->region->nlockers--;
+ }
+}
+
+static void
+__lock_freeobj(lt, obj)
+ DB_LOCKTAB *lt;
+ DB_LOCKOBJ *obj;
+{
+ HASHREMOVE_EL(lt->hashtab,
+ __db_lockobj, links, obj, lt->region->table_size, __lock_lhash);
+ if (obj->lockobj.size > sizeof(obj->objdata))
+ __db_shalloc_free(lt->mem, SH_DBT_PTR(&obj->lockobj));
+ SH_TAILQ_INSERT_HEAD(&lt->region->free_objs, obj, links, __db_lockobj);
+}
+
+/*
+ * __lock_downgrade --
+ * Used by the concurrent access product to downgrade write locks
+ * back to iwrite locks.
+ *
+ * PUBLIC: int __lock_downgrade __P((DB_LOCKTAB *,
+ * PUBLIC: DB_LOCK, db_lockmode_t, u_int32_t));
+ */
+int
+__lock_downgrade(lt, lock, new_mode, flags)
+ DB_LOCKTAB *lt;
+ DB_LOCK lock;
+ db_lockmode_t new_mode;
+ u_int32_t flags;
+{
+ struct __db_lock *lockp;
+ DB_LOCKOBJ *obj;
+ int ret;
+
+ COMPQUIET(flags, 0);
+ LOCK_PANIC_CHECK(lt);
+ LOCK_LOCKREGION(lt);
+
+ if ((ret = __lock_validate_region(lt)) == 0) {
+ lockp = OFFSET_TO_LOCK(lt, lock);
+ lockp->mode = new_mode;
+
+ /* Get the object associated with this lock. */
+ obj = (DB_LOCKOBJ *)((u_int8_t *)lockp + lockp->obj);
+ (void)__lock_promote(lt, obj);
+ ++lt->region->nreleases;
+ }
+
+ UNLOCK_LOCKREGION(lt);
+
+ return (ret);
+}
+
+/*
+ * __lock_promote --
+ *
+ * Look through the waiters and holders lists and decide which (if any)
+ * locks can be promoted. Promote any that are eligible.
+ */
+static int
+__lock_promote(lt, obj)
+ DB_LOCKTAB *lt;
+ DB_LOCKOBJ *obj;
+{
+ struct __db_lock *lp_w, *lp_h, *next_waiter;
+ int state_changed, waiter_is_txn;
+
+ /*
+ * We need to do lock promotion. We also need to determine if
+ * we're going to need to run the deadlock detector again. If
+ * we release locks, and there are waiters, but no one gets promoted,
+ * then we haven't fundamentally changed the lockmgr state, so
+ * we may still have a deadlock and we have to run again. However,
+ * if there were no waiters, or we actually promoted someone, then
+ * we are OK and we don't have to run it immediately.
+ *
+ * During promotion, we look for state changes so we can return
+ * this information to the caller.
+ */
+ for (lp_w = SH_TAILQ_FIRST(&obj->waiters, __db_lock),
+ state_changed = lp_w == NULL;
+ lp_w != NULL;
+ lp_w = next_waiter) {
+ waiter_is_txn = TXN_IS_HOLDING(lp_w);
+ next_waiter = SH_TAILQ_NEXT(lp_w, links, __db_lock);
+ for (lp_h = SH_TAILQ_FIRST(&obj->holders, __db_lock);
+ lp_h != NULL;
+ lp_h = SH_TAILQ_NEXT(lp_h, links, __db_lock)) {
+ if (CONFLICTS(lt, lp_h->mode, lp_w->mode) &&
+ lp_h->holder != lp_w->holder &&
+ !(waiter_is_txn &&
+ TXN_IS_HOLDING(lp_h) &&
+ __txn_is_ancestor(lt->dbenv->tx_info,
+ lp_h->txnoff, lp_w->txnoff)))
+ break;
+ }
+ if (lp_h != NULL) /* Found a conflict. */
+ break;
+
+ /* No conflict, promote the waiting lock. */
+ SH_TAILQ_REMOVE(&obj->waiters, lp_w, links, __db_lock);
+ lp_w->status = DB_LSTAT_PENDING;
+ SH_TAILQ_INSERT_TAIL(&obj->holders, lp_w, links);
+
+ /* Wake up waiter. */
+ (void)__db_mutex_unlock(&lp_w->mutex, lt->reginfo.fd);
+ state_changed = 1;
+ }
+
+ return (state_changed);
+}
+
+static int
+__lock_is_parent(locker, txn)
+ u_int32_t locker;
+ DB_TXN *txn;
+{
+ DB_TXN *t;
+
+ if (txn == NULL)
+ return (0);
+
+ for (t = txn->parent; t != NULL; t = t->parent)
+ if (t->txnid == locker)
+ return (1);
+
+ return (0);
+}
diff --git a/usr/src/cmd/sendmail/db/lock/lock_conflict.c b/usr/src/cmd/sendmail/db/lock/lock_conflict.c
new file mode 100644
index 0000000000..4be858af7a
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/lock/lock_conflict.c
@@ -0,0 +1,39 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)lock_conflict.c 10.4 (Sleepycat) 11/20/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+#endif
+
+#include "db_int.h"
+
+/*
+ * The conflict arrays are set up such that the row is the lock you
+ * are holding and the column is the lock that is desired.
+ */
+const u_int8_t db_rw_conflicts[] = {
+ /* N R W */
+ /* N */ 0, 0, 0,
+ /* R */ 0, 0, 1,
+ /* W */ 0, 1, 1
+};
+
+const u_int8_t db_riw_conflicts[] = {
+ /* N S X IX IS SIX */
+ /* N */ 0, 0, 0, 0, 0, 0,
+ /* S */ 0, 0, 1, 1, 0, 1,
+ /* X */ 1, 1, 1, 1, 1, 1,
+ /* IX */ 0, 1, 1, 0, 0, 0,
+ /* IS */ 0, 0, 1, 0, 0, 0,
+ /* SIX */ 0, 1, 1, 0, 0, 0
+};
diff --git a/usr/src/cmd/sendmail/db/lock/lock_deadlock.c b/usr/src/cmd/sendmail/db/lock/lock_deadlock.c
new file mode 100644
index 0000000000..8b2f91bc9e
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/lock/lock_deadlock.c
@@ -0,0 +1,502 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)lock_deadlock.c 10.37 (Sleepycat) 10/4/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <errno.h>
+#include <string.h>
+#endif
+
+#include "db_int.h"
+#include "shqueue.h"
+#include "db_shash.h"
+#include "lock.h"
+#include "common_ext.h"
+
+#define ISSET_MAP(M, N) (M[(N) / 32] & (1 << (N) % 32))
+
+#define CLEAR_MAP(M, N) { \
+ u_int32_t __i; \
+ for (__i = 0; __i < (N); __i++) \
+ M[__i] = 0; \
+}
+
+#define SET_MAP(M, B) (M[(B) / 32] |= (1 << ((B) % 32)))
+#define CLR_MAP(M, B) (M[(B) / 32] &= ~(1 << ((B) % 32)))
+
+#define OR_MAP(D, S, N) { \
+ u_int32_t __i; \
+ for (__i = 0; __i < (N); __i++) \
+ D[__i] |= S[__i]; \
+}
+#define BAD_KILLID 0xffffffff
+
+typedef struct {
+ int valid;
+ u_int32_t id;
+ DB_LOCK last_lock;
+ db_pgno_t pgno;
+} locker_info;
+
+static int __dd_abort __P((DB_ENV *, locker_info *));
+static int __dd_build
+ __P((DB_ENV *, u_int32_t **, u_int32_t *, locker_info **));
+static u_int32_t
+ *__dd_find __P((u_int32_t *, locker_info *, u_int32_t));
+
+#ifdef DIAGNOSTIC
+static void __dd_debug __P((DB_ENV *, locker_info *, u_int32_t *, u_int32_t));
+#endif
+
+int
+lock_detect(lt, flags, atype)
+ DB_LOCKTAB *lt;
+ u_int32_t flags, atype;
+{
+ DB_ENV *dbenv;
+ locker_info *idmap;
+ u_int32_t *bitmap, *deadlock, i, killid, nentries, nlockers;
+ int do_pass, ret;
+
+ LOCK_PANIC_CHECK(lt);
+
+ /* Validate arguments. */
+ if ((ret =
+ __db_fchk(lt->dbenv, "lock_detect", flags, DB_LOCK_CONFLICT)) != 0)
+ return (ret);
+
+ /* Check if a detector run is necessary. */
+ dbenv = lt->dbenv;
+ if (LF_ISSET(DB_LOCK_CONFLICT)) {
+ /* Make a pass every time a lock waits. */
+ LOCK_LOCKREGION(lt);
+ do_pass = dbenv->lk_info->region->need_dd != 0;
+ UNLOCK_LOCKREGION(lt);
+
+ if (!do_pass)
+ return (0);
+ }
+
+ /* Build the waits-for bitmap. */
+ if ((ret = __dd_build(dbenv, &bitmap, &nlockers, &idmap)) != 0)
+ return (ret);
+
+ if (nlockers == 0)
+ return (0);
+#ifdef DIAGNOSTIC
+ if (dbenv->db_verbose != 0)
+ __dd_debug(dbenv, idmap, bitmap, nlockers);
+#endif
+ /* Find a deadlock. */
+ deadlock = __dd_find(bitmap, idmap, nlockers);
+ nentries = ALIGN(nlockers, 32) / 32;
+ killid = BAD_KILLID;
+ if (deadlock != NULL) {
+ /* Kill someone. */
+ switch (atype) {
+ case DB_LOCK_OLDEST:
+ /*
+ * Find the first bit set in the current
+ * array and then look for a lower tid in
+ * the array.
+ */
+ for (i = 0; i < nlockers; i++)
+ if (ISSET_MAP(deadlock, i))
+ killid = i;
+
+ if (killid == BAD_KILLID) {
+ __db_err(dbenv,
+ "warning: could not find locker to abort");
+ break;
+ }
+
+ /*
+ * The oldest transaction has the lowest
+ * transaction id.
+ */
+ for (i = killid + 1; i < nlockers; i++)
+ if (ISSET_MAP(deadlock, i) &&
+ idmap[i].id < idmap[killid].id)
+ killid = i;
+ break;
+ case DB_LOCK_DEFAULT:
+ case DB_LOCK_RANDOM:
+ /*
+ * We are trying to calculate the id of the
+ * locker whose entry is indicated by deadlock.
+ */
+ killid = (deadlock - bitmap) / nentries;
+ break;
+ case DB_LOCK_YOUNGEST:
+ /*
+ * Find the first bit set in the current
+ * array and then look for a lower tid in
+ * the array.
+ */
+ for (i = 0; i < nlockers; i++)
+ if (ISSET_MAP(deadlock, i))
+ killid = i;
+
+ if (killid == BAD_KILLID) {
+ __db_err(dbenv,
+ "warning: could not find locker to abort");
+ break;
+ }
+ /*
+ * The youngest transaction has the highest
+ * transaction id.
+ */
+ for (i = killid + 1; i < nlockers; i++)
+ if (ISSET_MAP(deadlock, i) &&
+ idmap[i].id > idmap[killid].id)
+ killid = i;
+ break;
+ default:
+ killid = BAD_KILLID;
+ ret = EINVAL;
+ }
+
+ /* Kill the locker with lockid idmap[killid]. */
+ if (dbenv->db_verbose != 0 && killid != BAD_KILLID)
+ __db_err(dbenv, "Aborting locker %lx",
+ (u_long)idmap[killid].id);
+
+ if (killid != BAD_KILLID &&
+ (ret = __dd_abort(dbenv, &idmap[killid])) != 0)
+ __db_err(dbenv,
+ "warning: unable to abort locker %lx",
+ (u_long)idmap[killid].id);
+ }
+ __os_free(bitmap, 0);
+ __os_free(idmap, 0);
+
+ return (ret);
+}
+
+/*
+ * ========================================================================
+ * Utilities
+ */
+static int
+__dd_build(dbenv, bmp, nlockers, idmap)
+ DB_ENV *dbenv;
+ u_int32_t **bmp, *nlockers;
+ locker_info **idmap;
+{
+ struct __db_lock *lp;
+ DB_LOCKTAB *lt;
+ DB_LOCKOBJ *op, *lo, *lockerp;
+ u_int8_t *pptr;
+ locker_info *id_array;
+ u_int32_t *bitmap, count, *entryp, i, id, nentries, *tmpmap;
+ int is_first, ret;
+
+ lt = dbenv->lk_info;
+
+ /*
+ * We'll check how many lockers there are, add a few more in for
+ * good measure and then allocate all the structures. Then we'll
+ * verify that we have enough room when we go back in and get the
+ * mutex the second time.
+ */
+ LOCK_LOCKREGION(lt);
+retry: count = lt->region->nlockers;
+ lt->region->need_dd = 0;
+ UNLOCK_LOCKREGION(lt);
+
+ if (count == 0) {
+ *nlockers = 0;
+ return (0);
+ }
+
+ if (dbenv->db_verbose)
+ __db_err(dbenv, "%lu lockers", (u_long)count);
+
+ count += 10;
+ nentries = ALIGN(count, 32) / 32;
+ /*
+ * Allocate enough space for a count by count bitmap matrix.
+ *
+ * XXX
+ * We can probably save the malloc's between iterations just
+ * reallocing if necessary because count grew by too much.
+ */
+ if ((ret = __os_calloc((size_t)count,
+ sizeof(u_int32_t) * nentries, &bitmap)) != 0)
+ return (ret);
+
+ if ((ret = __os_calloc(sizeof(u_int32_t), nentries, &tmpmap)) != 0) {
+ __os_free(bitmap, sizeof(u_int32_t) * nentries);
+ return (ret);
+ }
+
+ if ((ret =
+ __os_calloc((size_t)count, sizeof(locker_info), &id_array)) != 0) {
+ __os_free(bitmap, count * sizeof(u_int32_t) * nentries);
+ __os_free(tmpmap, sizeof(u_int32_t) * nentries);
+ return (ret);
+ }
+
+ /*
+ * Now go back in and actually fill in the matrix.
+ */
+ LOCK_LOCKREGION(lt);
+ if (lt->region->nlockers > count) {
+ __os_free(bitmap, count * sizeof(u_int32_t) * nentries);
+ __os_free(tmpmap, sizeof(u_int32_t) * nentries);
+ __os_free(id_array, count * sizeof(locker_info));
+ goto retry;
+ }
+
+ /*
+ * First we go through and assign each locker a deadlock detector id.
+ * Note that we fill in the idmap in the next loop since that's the
+ * only place where we conveniently have both the deadlock id and the
+ * actual locker.
+ */
+ for (id = 0, i = 0; i < lt->region->table_size; i++)
+ for (op = SH_TAILQ_FIRST(&lt->hashtab[i], __db_lockobj);
+ op != NULL; op = SH_TAILQ_NEXT(op, links, __db_lockobj))
+ if (op->type == DB_LOCK_LOCKER)
+ op->dd_id = id++;
+ /*
+ * We go through the hash table and find each object. For each object,
+ * we traverse the waiters list and add an entry in the waitsfor matrix
+ * for each waiter/holder combination.
+ */
+ for (i = 0; i < lt->region->table_size; i++) {
+ for (op = SH_TAILQ_FIRST(&lt->hashtab[i], __db_lockobj);
+ op != NULL; op = SH_TAILQ_NEXT(op, links, __db_lockobj)) {
+ if (op->type != DB_LOCK_OBJTYPE)
+ continue;
+ CLEAR_MAP(tmpmap, nentries);
+
+ /*
+ * First we go through and create a bit map that
+ * represents all the holders of this object.
+ */
+ for (lp = SH_TAILQ_FIRST(&op->holders, __db_lock);
+ lp != NULL;
+ lp = SH_TAILQ_NEXT(lp, links, __db_lock)) {
+ if (__lock_getobj(lt, lp->holder,
+ NULL, DB_LOCK_LOCKER, &lockerp) != 0) {
+ __db_err(dbenv,
+ "warning unable to find object");
+ continue;
+ }
+ id_array[lockerp->dd_id].id = lp->holder;
+ id_array[lockerp->dd_id].valid = 1;
+
+ /*
+ * If the holder has already been aborted, then
+ * we should ignore it for now.
+ */
+ if (lp->status == DB_LSTAT_HELD)
+ SET_MAP(tmpmap, lockerp->dd_id);
+ }
+
+ /*
+ * Next, for each waiter, we set its row in the matrix
+ * equal to the map of holders we set up above.
+ */
+ for (is_first = 1,
+ lp = SH_TAILQ_FIRST(&op->waiters, __db_lock);
+ lp != NULL;
+ is_first = 0,
+ lp = SH_TAILQ_NEXT(lp, links, __db_lock)) {
+ if (__lock_getobj(lt, lp->holder,
+ NULL, DB_LOCK_LOCKER, &lockerp) != 0) {
+ __db_err(dbenv,
+ "warning unable to find object");
+ continue;
+ }
+ id_array[lockerp->dd_id].id = lp->holder;
+ id_array[lockerp->dd_id].valid = 1;
+
+ /*
+ * If the transaction is pending abortion, then
+ * ignore it on this iteration.
+ */
+ if (lp->status != DB_LSTAT_WAITING)
+ continue;
+
+ entryp = bitmap + (nentries * lockerp->dd_id);
+ OR_MAP(entryp, tmpmap, nentries);
+ /*
+ * If this is the first waiter on the queue,
+ * then we remove the waitsfor relationship
+ * with oneself. However, if it's anywhere
+ * else on the queue, then we have to keep
+ * it and we have an automatic deadlock.
+ */
+ if (is_first)
+ CLR_MAP(entryp, lockerp->dd_id);
+ }
+ }
+ }
+
+ /* Now for each locker; record its last lock. */
+ for (id = 0; id < count; id++) {
+ if (!id_array[id].valid)
+ continue;
+ if (__lock_getobj(lt,
+ id_array[id].id, NULL, DB_LOCK_LOCKER, &lockerp) != 0) {
+ __db_err(dbenv,
+ "No locks for locker %lu", (u_long)id_array[id].id);
+ continue;
+ }
+ lp = SH_LIST_FIRST(&lockerp->heldby, __db_lock);
+ if (lp != NULL) {
+ id_array[id].last_lock = LOCK_TO_OFFSET(lt, lp);
+ lo = (DB_LOCKOBJ *)((u_int8_t *)lp + lp->obj);
+ pptr = SH_DBT_PTR(&lo->lockobj);
+ if (lo->lockobj.size >= sizeof(db_pgno_t))
+ memcpy(&id_array[id].pgno, pptr,
+ sizeof(db_pgno_t));
+ else
+ id_array[id].pgno = 0;
+ }
+ }
+
+ /* Pass complete, reset the deadlock detector bit. */
+ lt->region->need_dd = 0;
+ UNLOCK_LOCKREGION(lt);
+
+ /*
+ * Now we can release everything except the bitmap matrix that we
+ * created.
+ */
+ *nlockers = id;
+ *idmap = id_array;
+ *bmp = bitmap;
+ __os_free(tmpmap, sizeof(u_int32_t) * nentries);
+ return (0);
+}
+
+static u_int32_t *
+__dd_find(bmp, idmap, nlockers)
+ u_int32_t *bmp, nlockers;
+ locker_info *idmap;
+{
+ u_int32_t i, j, nentries, *mymap, *tmpmap;
+
+ /*
+ * For each locker, OR in the bits from the lockers on which that
+ * locker is waiting.
+ */
+ nentries = ALIGN(nlockers, 32) / 32;
+ for (mymap = bmp, i = 0; i < nlockers; i++, mymap += nentries) {
+ if (!idmap[i].valid)
+ continue;
+ for (j = 0; j < nlockers; j++) {
+ if (ISSET_MAP(mymap, j)) {
+ /* Find the map for this bit. */
+ tmpmap = bmp + (nentries * j);
+ OR_MAP(mymap, tmpmap, nentries);
+ if (ISSET_MAP(mymap, i))
+ return (mymap);
+ }
+ }
+ }
+ return (NULL);
+}
+
+static int
+__dd_abort(dbenv, info)
+ DB_ENV *dbenv;
+ locker_info *info;
+{
+ struct __db_lock *lockp;
+ DB_LOCKTAB *lt;
+ DB_LOCKOBJ *lockerp, *sh_obj;
+ int ret;
+
+ lt = dbenv->lk_info;
+ LOCK_LOCKREGION(lt);
+
+ /* Find the locker's last lock. */
+ if ((ret =
+ __lock_getobj(lt, info->id, NULL, DB_LOCK_LOCKER, &lockerp)) != 0)
+ goto out;
+
+ lockp = SH_LIST_FIRST(&lockerp->heldby, __db_lock);
+
+ /*
+ * It's possible that this locker was already aborted.
+ * If that's the case, make sure that we remove its
+ * locker from the hash table.
+ */
+ if (lockp == NULL) {
+ HASHREMOVE_EL(lt->hashtab, __db_lockobj,
+ links, lockerp, lt->region->table_size, __lock_lhash);
+ SH_TAILQ_INSERT_HEAD(&lt->region->free_objs,
+ lockerp, links, __db_lockobj);
+ lt->region->nlockers--;
+ goto out;
+ } else if (LOCK_TO_OFFSET(lt, lockp) != info->last_lock ||
+ lockp->status != DB_LSTAT_WAITING)
+ goto out;
+
+ /* Abort lock, take it off list, and wake up this lock. */
+ lockp->status = DB_LSTAT_ABORTED;
+ lt->region->ndeadlocks++;
+ SH_LIST_REMOVE(lockp, locker_links, __db_lock);
+ sh_obj = (DB_LOCKOBJ *)((u_int8_t *)lockp + lockp->obj);
+ SH_TAILQ_REMOVE(&sh_obj->waiters, lockp, links, __db_lock);
+ (void)__db_mutex_unlock(&lockp->mutex, lt->reginfo.fd);
+
+ ret = 0;
+
+out: UNLOCK_LOCKREGION(lt);
+ return (ret);
+}
+
+#ifdef DIAGNOSTIC
+static void
+__dd_debug(dbenv, idmap, bitmap, nlockers)
+ DB_ENV *dbenv;
+ locker_info *idmap;
+ u_int32_t *bitmap, nlockers;
+{
+ u_int32_t i, j, *mymap, nentries;
+ int ret;
+ char *msgbuf;
+
+ __db_err(dbenv, "Waitsfor array");
+ __db_err(dbenv, "waiter\twaiting on");
+
+ /* Allocate space to print 10 bytes per item waited on. */
+#undef MSGBUF_LEN
+#define MSGBUF_LEN ((nlockers + 1) * 10 + 64)
+ if ((ret = __os_malloc(MSGBUF_LEN, NULL, &msgbuf)) != 0)
+ return;
+
+ nentries = ALIGN(nlockers, 32) / 32;
+ for (mymap = bitmap, i = 0; i < nlockers; i++, mymap += nentries) {
+ if (!idmap[i].valid)
+ continue;
+ sprintf(msgbuf, /* Waiter. */
+ "%lx/%lu:\t", (u_long)idmap[i].id, (u_long)idmap[i].pgno);
+ for (j = 0; j < nlockers; j++)
+ if (ISSET_MAP(mymap, j))
+ sprintf(msgbuf, "%s %lx", msgbuf,
+ (u_long)idmap[j].id);
+ (void)sprintf(msgbuf,
+ "%s %lu", msgbuf, (u_long)idmap[i].last_lock);
+ __db_err(dbenv, msgbuf);
+ }
+
+ __os_free(msgbuf, MSGBUF_LEN);
+}
+#endif
diff --git a/usr/src/cmd/sendmail/db/lock/lock_region.c b/usr/src/cmd/sendmail/db/lock/lock_region.c
new file mode 100644
index 0000000000..8abe1a99f1
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/lock/lock_region.c
@@ -0,0 +1,745 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)lock_region.c 10.21 (Sleepycat) 10/19/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <string.h>
+#endif
+
+#include "db_int.h"
+#include "shqueue.h"
+#include "db_shash.h"
+#include "lock.h"
+#include "common_ext.h"
+
+static u_int32_t __lock_count_locks __P((DB_LOCKREGION *));
+static u_int32_t __lock_count_objs __P((DB_LOCKREGION *));
+static void __lock_dump_locker __P((DB_LOCKTAB *, DB_LOCKOBJ *, FILE *));
+static void __lock_dump_object __P((DB_LOCKTAB *, DB_LOCKOBJ *, FILE *));
+static const char *
+ __lock_dump_status __P((db_status_t));
+static void __lock_reset_region __P((DB_LOCKTAB *));
+static int __lock_tabinit __P((DB_ENV *, DB_LOCKREGION *));
+
+int
+lock_open(path, flags, mode, dbenv, ltp)
+ const char *path;
+ u_int32_t flags;
+ int mode;
+ DB_ENV *dbenv;
+ DB_LOCKTAB **ltp;
+{
+ DB_LOCKTAB *lt;
+ u_int32_t lock_modes, maxlocks, regflags;
+ int ret;
+
+ /* Validate arguments. */
+#ifdef HAVE_SPINLOCKS
+#define OKFLAGS (DB_CREATE | DB_THREAD)
+#else
+#define OKFLAGS (DB_CREATE)
+#endif
+ if ((ret = __db_fchk(dbenv, "lock_open", flags, OKFLAGS)) != 0)
+ return (ret);
+
+ /* Create the lock table structure. */
+ if ((ret = __os_calloc(1, sizeof(DB_LOCKTAB), &lt)) != 0)
+ return (ret);
+ lt->dbenv = dbenv;
+
+ /* Grab the values that we need to compute the region size. */
+ lock_modes = DB_LOCK_RW_N;
+ maxlocks = DB_LOCK_DEFAULT_N;
+ regflags = REGION_SIZEDEF;
+ if (dbenv != NULL) {
+ if (dbenv->lk_modes != 0) {
+ lock_modes = dbenv->lk_modes;
+ regflags = 0;
+ }
+ if (dbenv->lk_max != 0) {
+ maxlocks = dbenv->lk_max;
+ regflags = 0;
+ }
+ }
+
+ /* Join/create the lock region. */
+ lt->reginfo.dbenv = dbenv;
+ lt->reginfo.appname = DB_APP_NONE;
+ if (path == NULL)
+ lt->reginfo.path = NULL;
+ else
+ if ((ret = __os_strdup(path, &lt->reginfo.path)) != 0)
+ goto err;
+ lt->reginfo.file = DB_DEFAULT_LOCK_FILE;
+ lt->reginfo.mode = mode;
+ lt->reginfo.size =
+ LOCK_REGION_SIZE(lock_modes, maxlocks, __db_tablesize(maxlocks));
+ lt->reginfo.dbflags = flags;
+ lt->reginfo.addr = NULL;
+ lt->reginfo.fd = -1;
+ lt->reginfo.flags = regflags;
+
+ if ((ret = __db_rattach(&lt->reginfo)) != 0)
+ goto err;
+
+ /* Now set up the pointer to the region. */
+ lt->region = lt->reginfo.addr;
+
+ /* Initialize the region if we created it. */
+ if (F_ISSET(&lt->reginfo, REGION_CREATED)) {
+ lt->region->maxlocks = maxlocks;
+ lt->region->nmodes = lock_modes;
+ if ((ret = __lock_tabinit(dbenv, lt->region)) != 0)
+ goto err;
+ } else {
+ /* Check for an unexpected region. */
+ if (lt->region->magic != DB_LOCKMAGIC) {
+ __db_err(dbenv,
+ "lock_open: %s: bad magic number", path);
+ ret = EINVAL;
+ goto err;
+ }
+ }
+
+ /* Check for automatic deadlock detection. */
+ if (dbenv != NULL && dbenv->lk_detect != DB_LOCK_NORUN) {
+ if (lt->region->detect != DB_LOCK_NORUN &&
+ dbenv->lk_detect != DB_LOCK_DEFAULT &&
+ lt->region->detect != dbenv->lk_detect) {
+ __db_err(dbenv,
+ "lock_open: incompatible deadlock detector mode");
+ ret = EINVAL;
+ goto err;
+ }
+ if (lt->region->detect == DB_LOCK_NORUN)
+ lt->region->detect = dbenv->lk_detect;
+ }
+
+ /* Set up remaining pointers into region. */
+ lt->conflicts = (u_int8_t *)lt->region + sizeof(DB_LOCKREGION);
+ lt->hashtab =
+ (DB_HASHTAB *)((u_int8_t *)lt->region + lt->region->hash_off);
+ lt->mem = (void *)((u_int8_t *)lt->region + lt->region->mem_off);
+
+ UNLOCK_LOCKREGION(lt);
+ *ltp = lt;
+ return (0);
+
+err: if (lt->reginfo.addr != NULL) {
+ UNLOCK_LOCKREGION(lt);
+ (void)__db_rdetach(&lt->reginfo);
+ if (F_ISSET(&lt->reginfo, REGION_CREATED))
+ (void)lock_unlink(path, 1, dbenv);
+ }
+
+ if (lt->reginfo.path != NULL)
+ __os_freestr(lt->reginfo.path);
+ __os_free(lt, sizeof(*lt));
+ return (ret);
+}
+
+/*
+ * __lock_panic --
+ * Panic a lock region.
+ *
+ * PUBLIC: void __lock_panic __P((DB_ENV *));
+ */
+void
+__lock_panic(dbenv)
+ DB_ENV *dbenv;
+{
+ if (dbenv->lk_info != NULL)
+ dbenv->lk_info->region->hdr.panic = 1;
+}
+
+
+/*
+ * __lock_tabinit --
+ * Initialize the lock region.
+ */
+static int
+__lock_tabinit(dbenv, lrp)
+ DB_ENV *dbenv;
+ DB_LOCKREGION *lrp;
+{
+ struct __db_lock *lp;
+ struct lock_header *tq_head;
+ struct obj_header *obj_head;
+ DB_LOCKOBJ *op;
+ u_int32_t i, nelements;
+ const u_int8_t *conflicts;
+ u_int8_t *curaddr;
+
+ conflicts = dbenv == NULL || dbenv->lk_conflicts == NULL ?
+ db_rw_conflicts : dbenv->lk_conflicts;
+
+ lrp->table_size = __db_tablesize(lrp->maxlocks);
+ lrp->magic = DB_LOCKMAGIC;
+ lrp->version = DB_LOCKVERSION;
+ lrp->id = 0;
+ /*
+ * These fields (lrp->maxlocks, lrp->nmodes) are initialized
+ * in the caller, since we had to grab those values to size
+ * the region.
+ */
+ lrp->need_dd = 0;
+ lrp->detect = DB_LOCK_NORUN;
+ lrp->numobjs = lrp->maxlocks;
+ lrp->nlockers = 0;
+ lrp->mem_bytes = ALIGN(STRING_SIZE(lrp->maxlocks), sizeof(size_t));
+ lrp->increment = lrp->hdr.size / 2;
+ lrp->nconflicts = 0;
+ lrp->nrequests = 0;
+ lrp->nreleases = 0;
+ lrp->ndeadlocks = 0;
+
+ /*
+ * As we write the region, we've got to maintain the alignment
+ * for the structures that follow each chunk. This information
+ * ends up being encapsulated both in here as well as in the
+ * lock.h file for the XXX_SIZE macros.
+ */
+ /* Initialize conflict matrix. */
+ curaddr = (u_int8_t *)lrp + sizeof(DB_LOCKREGION);
+ memcpy(curaddr, conflicts, lrp->nmodes * lrp->nmodes);
+ curaddr += lrp->nmodes * lrp->nmodes;
+
+ /*
+ * Initialize hash table.
+ */
+ curaddr = (u_int8_t *)ALIGNP(curaddr, LOCK_HASH_ALIGN);
+ lrp->hash_off = curaddr - (u_int8_t *)lrp;
+ nelements = lrp->table_size;
+ __db_hashinit(curaddr, nelements);
+ curaddr += nelements * sizeof(DB_HASHTAB);
+
+ /*
+ * Initialize locks onto a free list. Since locks contains mutexes,
+ * we need to make sure that each lock is aligned on a MUTEX_ALIGNMENT
+ * boundary.
+ */
+ curaddr = (u_int8_t *)ALIGNP(curaddr, MUTEX_ALIGNMENT);
+ tq_head = &lrp->free_locks;
+ SH_TAILQ_INIT(tq_head);
+
+ for (i = 0; i++ < lrp->maxlocks;
+ curaddr += ALIGN(sizeof(struct __db_lock), MUTEX_ALIGNMENT)) {
+ lp = (struct __db_lock *)curaddr;
+ lp->status = DB_LSTAT_FREE;
+ SH_TAILQ_INSERT_HEAD(tq_head, lp, links, __db_lock);
+ }
+
+ /* Initialize objects onto a free list. */
+ obj_head = &lrp->free_objs;
+ SH_TAILQ_INIT(obj_head);
+
+ for (i = 0; i++ < lrp->maxlocks; curaddr += sizeof(DB_LOCKOBJ)) {
+ op = (DB_LOCKOBJ *)curaddr;
+ SH_TAILQ_INSERT_HEAD(obj_head, op, links, __db_lockobj);
+ }
+
+ /*
+ * Initialize the string space; as for all shared memory allocation
+ * regions, this requires size_t alignment, since we store the
+ * lengths of malloc'd areas in the area.
+ */
+ curaddr = (u_int8_t *)ALIGNP(curaddr, sizeof(size_t));
+ lrp->mem_off = curaddr - (u_int8_t *)lrp;
+ __db_shalloc_init(curaddr, lrp->mem_bytes);
+ return (0);
+}
+
+int
+lock_close(lt)
+ DB_LOCKTAB *lt;
+{
+ int ret;
+
+ LOCK_PANIC_CHECK(lt);
+
+ if ((ret = __db_rdetach(&lt->reginfo)) != 0)
+ return (ret);
+
+ if (lt->reginfo.path != NULL)
+ __os_freestr(lt->reginfo.path);
+ __os_free(lt, sizeof(*lt));
+
+ return (0);
+}
+
+int
+lock_unlink(path, force, dbenv)
+ const char *path;
+ int force;
+ DB_ENV *dbenv;
+{
+ REGINFO reginfo;
+ int ret;
+
+ memset(&reginfo, 0, sizeof(reginfo));
+ reginfo.dbenv = dbenv;
+ reginfo.appname = DB_APP_NONE;
+ if (path != NULL && (ret = __os_strdup(path, &reginfo.path)) != 0)
+ return (ret);
+ reginfo.file = DB_DEFAULT_LOCK_FILE;
+ ret = __db_runlink(&reginfo, force);
+ if (reginfo.path != NULL)
+ __os_freestr(reginfo.path);
+ return (ret);
+}
+
+/*
+ * __lock_validate_region --
+ * Called at every interface to verify if the region has changed size,
+ * and if so, to remap the region in and reset the process' pointers.
+ *
+ * PUBLIC: int __lock_validate_region __P((DB_LOCKTAB *));
+ */
+int
+__lock_validate_region(lt)
+ DB_LOCKTAB *lt;
+{
+ int ret;
+
+ if (lt->reginfo.size == lt->region->hdr.size)
+ return (0);
+
+ /* Detach/reattach the region. */
+ if ((ret = __db_rreattach(&lt->reginfo, lt->region->hdr.size)) != 0)
+ return (ret);
+
+ /* Reset region information. */
+ lt->region = lt->reginfo.addr;
+ __lock_reset_region(lt);
+
+ return (0);
+}
+
+/*
+ * __lock_grow_region --
+ * We have run out of space; time to grow the region.
+ *
+ * PUBLIC: int __lock_grow_region __P((DB_LOCKTAB *, int, size_t));
+ */
+int
+__lock_grow_region(lt, which, howmuch)
+ DB_LOCKTAB *lt;
+ int which;
+ size_t howmuch;
+{
+ struct __db_lock *newl;
+ struct lock_header *lock_head;
+ struct obj_header *obj_head;
+ DB_LOCKOBJ *op;
+ DB_LOCKREGION *lrp;
+ float lock_ratio, obj_ratio;
+ size_t incr, oldsize, used, usedmem;
+ u_int32_t i, newlocks, newmem, newobjs, usedlocks, usedobjs;
+ u_int8_t *curaddr;
+ int ret;
+
+ lrp = lt->region;
+ oldsize = lrp->hdr.size;
+ incr = lrp->increment;
+
+ /* Figure out how much of each sort of space we have. */
+ usedmem = lrp->mem_bytes - __db_shalloc_count(lt->mem);
+ usedobjs = lrp->numobjs - __lock_count_objs(lrp);
+ usedlocks = lrp->maxlocks - __lock_count_locks(lrp);
+
+ /*
+ * Figure out what fraction of the used space belongs to each
+ * different type of "thing" in the region. Then partition the
+ * new space up according to this ratio.
+ */
+ used = usedmem +
+ usedlocks * ALIGN(sizeof(struct __db_lock), MUTEX_ALIGNMENT) +
+ usedobjs * sizeof(DB_LOCKOBJ);
+
+ lock_ratio = usedlocks *
+ ALIGN(sizeof(struct __db_lock), MUTEX_ALIGNMENT) / (float)used;
+ obj_ratio = usedobjs * sizeof(DB_LOCKOBJ) / (float)used;
+
+ newlocks = (u_int32_t)(lock_ratio *
+ incr / ALIGN(sizeof(struct __db_lock), MUTEX_ALIGNMENT));
+ newobjs = (u_int32_t)(obj_ratio * incr / sizeof(DB_LOCKOBJ));
+ newmem = incr -
+ (newobjs * sizeof(DB_LOCKOBJ) +
+ newlocks * ALIGN(sizeof(struct __db_lock), MUTEX_ALIGNMENT));
+
+ /*
+ * Make sure we allocate enough memory for the object being
+ * requested.
+ */
+ switch (which) {
+ case DB_LOCK_LOCK:
+ if (newlocks == 0) {
+ newlocks = 10;
+ incr += newlocks * sizeof(struct __db_lock);
+ }
+ break;
+ case DB_LOCK_OBJ:
+ if (newobjs == 0) {
+ newobjs = 10;
+ incr += newobjs * sizeof(DB_LOCKOBJ);
+ }
+ break;
+ case DB_LOCK_MEM:
+ if (newmem < howmuch * 2) {
+ incr += howmuch * 2 - newmem;
+ newmem = howmuch * 2;
+ }
+ break;
+ }
+
+ newmem += ALIGN(incr, sizeof(size_t)) - incr;
+ incr = ALIGN(incr, sizeof(size_t));
+
+ /*
+ * Since we are going to be allocating locks at the beginning of the
+ * new chunk, we need to make sure that the chunk is MUTEX_ALIGNMENT
+ * aligned. We did not guarantee this when we created the region, so
+ * we may need to pad the old region by extra bytes to ensure this
+ * alignment.
+ */
+ incr += ALIGN(oldsize, MUTEX_ALIGNMENT) - oldsize;
+
+ __db_err(lt->dbenv,
+ "Growing lock region: %lu locks %lu objs %lu bytes",
+ (u_long)newlocks, (u_long)newobjs, (u_long)newmem);
+
+ if ((ret = __db_rgrow(&lt->reginfo, oldsize + incr)) != 0)
+ return (ret);
+ lt->region = lt->reginfo.addr;
+ __lock_reset_region(lt);
+
+ /* Update region parameters. */
+ lrp = lt->region;
+ lrp->increment = incr << 1;
+ lrp->maxlocks += newlocks;
+ lrp->numobjs += newobjs;
+ lrp->mem_bytes += newmem;
+
+ curaddr = (u_int8_t *)lrp + oldsize;
+ curaddr = (u_int8_t *)ALIGNP(curaddr, MUTEX_ALIGNMENT);
+
+ /* Put new locks onto the free list. */
+ lock_head = &lrp->free_locks;
+ for (i = 0; i++ < newlocks;
+ curaddr += ALIGN(sizeof(struct __db_lock), MUTEX_ALIGNMENT)) {
+ newl = (struct __db_lock *)curaddr;
+ SH_TAILQ_INSERT_HEAD(lock_head, newl, links, __db_lock);
+ }
+
+ /* Put new objects onto the free list. */
+ obj_head = &lrp->free_objs;
+ for (i = 0; i++ < newobjs; curaddr += sizeof(DB_LOCKOBJ)) {
+ op = (DB_LOCKOBJ *)curaddr;
+ SH_TAILQ_INSERT_HEAD(obj_head, op, links, __db_lockobj);
+ }
+
+ *((size_t *)curaddr) = newmem - sizeof(size_t);
+ curaddr += sizeof(size_t);
+ __db_shalloc_free(lt->mem, curaddr);
+
+ return (0);
+}
+
+static void
+__lock_reset_region(lt)
+ DB_LOCKTAB *lt;
+{
+ lt->conflicts = (u_int8_t *)lt->region + sizeof(DB_LOCKREGION);
+ lt->hashtab =
+ (DB_HASHTAB *)((u_int8_t *)lt->region + lt->region->hash_off);
+ lt->mem = (void *)((u_int8_t *)lt->region + lt->region->mem_off);
+}
+
+/*
+ * lock_stat --
+ * Return LOCK statistics.
+ */
+int
+lock_stat(lt, gspp, db_malloc)
+ DB_LOCKTAB *lt;
+ DB_LOCK_STAT **gspp;
+ void *(*db_malloc) __P((size_t));
+{
+ DB_LOCKREGION *rp;
+ int ret;
+
+ *gspp = NULL;
+
+ LOCK_PANIC_CHECK(lt);
+
+ if ((ret = __os_malloc(sizeof(**gspp), db_malloc, gspp)) != 0)
+ return (ret);
+
+ /* Copy out the global statistics. */
+ LOCK_LOCKREGION(lt);
+
+ rp = lt->region;
+ (*gspp)->st_magic = rp->magic;
+ (*gspp)->st_version = rp->version;
+ (*gspp)->st_maxlocks = rp->maxlocks;
+ (*gspp)->st_nmodes = rp->nmodes;
+ (*gspp)->st_numobjs = rp->numobjs;
+ (*gspp)->st_nlockers = rp->nlockers;
+ (*gspp)->st_nconflicts = rp->nconflicts;
+ (*gspp)->st_nrequests = rp->nrequests;
+ (*gspp)->st_nreleases = rp->nreleases;
+ (*gspp)->st_ndeadlocks = rp->ndeadlocks;
+ (*gspp)->st_region_nowait = rp->hdr.lock.mutex_set_nowait;
+ (*gspp)->st_region_wait = rp->hdr.lock.mutex_set_wait;
+ (*gspp)->st_refcnt = rp->hdr.refcnt;
+ (*gspp)->st_regsize = rp->hdr.size;
+
+ UNLOCK_LOCKREGION(lt);
+
+ return (0);
+}
+
+static u_int32_t
+__lock_count_locks(lrp)
+ DB_LOCKREGION *lrp;
+{
+ struct __db_lock *newl;
+ u_int32_t count;
+
+ count = 0;
+ for (newl = SH_TAILQ_FIRST(&lrp->free_locks, __db_lock);
+ newl != NULL;
+ newl = SH_TAILQ_NEXT(newl, links, __db_lock))
+ count++;
+
+ return (count);
+}
+
+static u_int32_t
+__lock_count_objs(lrp)
+ DB_LOCKREGION *lrp;
+{
+ DB_LOCKOBJ *obj;
+ u_int32_t count;
+
+ count = 0;
+ for (obj = SH_TAILQ_FIRST(&lrp->free_objs, __db_lockobj);
+ obj != NULL;
+ obj = SH_TAILQ_NEXT(obj, links, __db_lockobj))
+ count++;
+
+ return (count);
+}
+
+#define LOCK_DUMP_CONF 0x001 /* Conflict matrix. */
+#define LOCK_DUMP_FREE 0x002 /* Display lock free list. */
+#define LOCK_DUMP_LOCKERS 0x004 /* Display lockers. */
+#define LOCK_DUMP_MEM 0x008 /* Display region memory. */
+#define LOCK_DUMP_OBJECTS 0x010 /* Display objects. */
+#define LOCK_DUMP_ALL 0x01f /* Display all. */
+
+/*
+ * __lock_dump_region --
+ *
+ * PUBLIC: void __lock_dump_region __P((DB_LOCKTAB *, char *, FILE *));
+ */
+void
+__lock_dump_region(lt, area, fp)
+ DB_LOCKTAB *lt;
+ char *area;
+ FILE *fp;
+{
+ struct __db_lock *lp;
+ DB_LOCKOBJ *op;
+ DB_LOCKREGION *lrp;
+ u_int32_t flags, i, j;
+ int label;
+
+ /* Make it easy to call from the debugger. */
+ if (fp == NULL)
+ fp = stderr;
+
+ for (flags = 0; *area != '\0'; ++area)
+ switch (*area) {
+ case 'A':
+ LF_SET(LOCK_DUMP_ALL);
+ break;
+ case 'c':
+ LF_SET(LOCK_DUMP_CONF);
+ break;
+ case 'f':
+ LF_SET(LOCK_DUMP_FREE);
+ break;
+ case 'l':
+ LF_SET(LOCK_DUMP_LOCKERS);
+ break;
+ case 'm':
+ LF_SET(LOCK_DUMP_MEM);
+ break;
+ case 'o':
+ LF_SET(LOCK_DUMP_OBJECTS);
+ break;
+ }
+
+ lrp = lt->region;
+
+ fprintf(fp, "%s\nLock region parameters\n", DB_LINE);
+ fprintf(fp, "%s: %lu, %s: %lu, %s: %lu, %s: %lu\n%s: %lu, %s: %lu\n",
+ "table size", (u_long)lrp->table_size,
+ "hash_off", (u_long)lrp->hash_off,
+ "increment", (u_long)lrp->increment,
+ "mem_off", (u_long)lrp->mem_off,
+ "mem_bytes", (u_long)lrp->mem_bytes,
+ "need_dd", (u_long)lrp->need_dd);
+
+ if (LF_ISSET(LOCK_DUMP_CONF)) {
+ fprintf(fp, "\n%s\nConflict matrix\n", DB_LINE);
+ for (i = 0; i < lrp->nmodes; i++) {
+ for (j = 0; j < lrp->nmodes; j++)
+ fprintf(fp, "%lu\t",
+ (u_long)lt->conflicts[i * lrp->nmodes + j]);
+ fprintf(fp, "\n");
+ }
+ }
+
+ if (LF_ISSET(LOCK_DUMP_LOCKERS | LOCK_DUMP_OBJECTS)) {
+ fprintf(fp, "%s\nLock hash buckets\n", DB_LINE);
+ for (i = 0; i < lrp->table_size; i++) {
+ label = 1;
+ for (op = SH_TAILQ_FIRST(&lt->hashtab[i], __db_lockobj);
+ op != NULL;
+ op = SH_TAILQ_NEXT(op, links, __db_lockobj)) {
+ if (LF_ISSET(LOCK_DUMP_LOCKERS) &&
+ op->type == DB_LOCK_LOCKER) {
+ if (label) {
+ fprintf(fp,
+ "Bucket %lu:\n", (u_long)i);
+ label = 0;
+ }
+ __lock_dump_locker(lt, op, fp);
+ }
+ if (LF_ISSET(LOCK_DUMP_OBJECTS) &&
+ op->type == DB_LOCK_OBJTYPE) {
+ if (label) {
+ fprintf(fp,
+ "Bucket %lu:\n", (u_long)i);
+ label = 0;
+ }
+ __lock_dump_object(lt, op, fp);
+ }
+ }
+ }
+ }
+
+ if (LF_ISSET(LOCK_DUMP_FREE)) {
+ fprintf(fp, "%s\nLock free list\n", DB_LINE);
+ for (lp = SH_TAILQ_FIRST(&lrp->free_locks, __db_lock);
+ lp != NULL;
+ lp = SH_TAILQ_NEXT(lp, links, __db_lock))
+ fprintf(fp, "0x%lx: %lu\t%lu\t%s\t0x%lx\n", (u_long)lp,
+ (u_long)lp->holder, (u_long)lp->mode,
+ __lock_dump_status(lp->status), (u_long)lp->obj);
+
+ fprintf(fp, "%s\nObject free list\n", DB_LINE);
+ for (op = SH_TAILQ_FIRST(&lrp->free_objs, __db_lockobj);
+ op != NULL;
+ op = SH_TAILQ_NEXT(op, links, __db_lockobj))
+ fprintf(fp, "0x%lx\n", (u_long)op);
+ }
+
+ if (LF_ISSET(LOCK_DUMP_MEM))
+ __db_shalloc_dump(lt->mem, fp);
+}
+
+static void
+__lock_dump_locker(lt, op, fp)
+ DB_LOCKTAB *lt;
+ DB_LOCKOBJ *op;
+ FILE *fp;
+{
+ struct __db_lock *lp;
+ u_int32_t locker;
+ void *ptr;
+
+ ptr = SH_DBT_PTR(&op->lockobj);
+ memcpy(&locker, ptr, sizeof(u_int32_t));
+ fprintf(fp, "L %lx", (u_long)locker);
+
+ lp = SH_LIST_FIRST(&op->heldby, __db_lock);
+ if (lp == NULL) {
+ fprintf(fp, "\n");
+ return;
+ }
+ for (; lp != NULL; lp = SH_LIST_NEXT(lp, locker_links, __db_lock))
+ __lock_printlock(lt, lp, 0);
+}
+
+static void
+__lock_dump_object(lt, op, fp)
+ DB_LOCKTAB *lt;
+ DB_LOCKOBJ *op;
+ FILE *fp;
+{
+ struct __db_lock *lp;
+ u_int32_t j;
+ u_int8_t *ptr;
+ u_int ch;
+
+ ptr = SH_DBT_PTR(&op->lockobj);
+ for (j = 0; j < op->lockobj.size; ptr++, j++) {
+ ch = *ptr;
+ fprintf(fp, isprint(ch) ? "%c" : "\\%o", ch);
+ }
+ fprintf(fp, "\n");
+
+ fprintf(fp, "H:");
+ for (lp =
+ SH_TAILQ_FIRST(&op->holders, __db_lock);
+ lp != NULL;
+ lp = SH_TAILQ_NEXT(lp, links, __db_lock))
+ __lock_printlock(lt, lp, 0);
+ lp = SH_TAILQ_FIRST(&op->waiters, __db_lock);
+ if (lp != NULL) {
+ fprintf(fp, "\nW:");
+ for (; lp != NULL; lp = SH_TAILQ_NEXT(lp, links, __db_lock))
+ __lock_printlock(lt, lp, 0);
+ }
+}
+
+static const char *
+__lock_dump_status(status)
+ db_status_t status;
+{
+ switch (status) {
+ case DB_LSTAT_ABORTED:
+ return ("aborted");
+ case DB_LSTAT_ERR:
+ return ("err");
+ case DB_LSTAT_FREE:
+ return ("free");
+ case DB_LSTAT_HELD:
+ return ("held");
+ case DB_LSTAT_NOGRANT:
+ return ("nogrant");
+ case DB_LSTAT_PENDING:
+ return ("pending");
+ case DB_LSTAT_WAITING:
+ return ("waiting");
+ }
+ return ("unknown status");
+}
diff --git a/usr/src/cmd/sendmail/db/lock/lock_util.c b/usr/src/cmd/sendmail/db/lock/lock_util.c
new file mode 100644
index 0000000000..29da75b8a8
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/lock/lock_util.c
@@ -0,0 +1,152 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)lock_util.c 10.10 (Sleepycat) 9/20/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <string.h>
+#endif
+
+#include "db_int.h"
+#include "shqueue.h"
+#include "db_page.h"
+#include "db_shash.h"
+#include "hash.h"
+#include "lock.h"
+
+/*
+ * __lock_cmp --
+ * This function is used to compare a DBT that is about to be entered
+ * into a hash table with an object already in the hash table. Note
+ * that it just returns true on equal and 0 on not-equal. Therefore
+ * this function cannot be used as a sort function; its purpose is to
+ * be used as a hash comparison function.
+ *
+ * PUBLIC: int __lock_cmp __P((const DBT *, DB_LOCKOBJ *));
+ */
+int
+__lock_cmp(dbt, lock_obj)
+ const DBT *dbt;
+ DB_LOCKOBJ *lock_obj;
+{
+ void *obj_data;
+
+ if (lock_obj->type != DB_LOCK_OBJTYPE)
+ return (0);
+
+ obj_data = SH_DBT_PTR(&lock_obj->lockobj);
+ return (dbt->size == lock_obj->lockobj.size &&
+ memcmp(dbt->data, obj_data, dbt->size) == 0);
+}
+
+/*
+ * PUBLIC: int __lock_locker_cmp __P((u_int32_t, DB_LOCKOBJ *));
+ */
+int
+__lock_locker_cmp(locker, lock_obj)
+ u_int32_t locker;
+ DB_LOCKOBJ *lock_obj;
+{
+ void *obj_data;
+
+ if (lock_obj->type != DB_LOCK_LOCKER)
+ return (0);
+
+ obj_data = SH_DBT_PTR(&lock_obj->lockobj);
+ return (memcmp(&locker, obj_data, sizeof(u_int32_t)) == 0);
+}
+
+/*
+ * The next two functions are the hash functions used to store objects in the
+ * lock hash table. They are hashing the same items, but one (__lock_ohash)
+ * takes a DBT (used for hashing a parameter passed from the user) and the
+ * other (__lock_lhash) takes a DB_LOCKOBJ (used for hashing something that is
+ * already in the lock manager). In both cases, we have a special check to
+ * fast path the case where we think we are doing a hash on a DB page/fileid
+ * pair. If the size is right, then we do the fast hash.
+ *
+ * We know that DB uses DB_LOCK_ILOCK types for its lock objects. The first
+ * four bytes are the 4-byte page number and the next DB_FILE_ID_LEN bytes
+ * are a unique file id, where the first 4 bytes on UNIX systems are the file
+ * inode number, and the first 4 bytes on Windows systems are the FileIndexLow
+ * bytes. So, we use the XOR of the page number and the first four bytes of
+ * the file id to produce a 32-bit hash value.
+ *
+ * We have no particular reason to believe that this algorithm will produce
+ * a good hash, but we want a fast hash more than we want a good one, when
+ * we're coming through this code path.
+ */
+#define FAST_HASH(P) { \
+ u_int32_t __h; \
+ u_int8_t *__cp, *__hp; \
+ __hp = (u_int8_t *)&__h; \
+ __cp = (u_int8_t *)(P); \
+ __hp[0] = __cp[0] ^ __cp[4]; \
+ __hp[1] = __cp[1] ^ __cp[5]; \
+ __hp[2] = __cp[2] ^ __cp[6]; \
+ __hp[3] = __cp[3] ^ __cp[7]; \
+ return (__h); \
+}
+
+/*
+ * __lock_ohash --
+ *
+ * PUBLIC: u_int32_t __lock_ohash __P((const DBT *));
+ */
+u_int32_t
+__lock_ohash(dbt)
+ const DBT *dbt;
+{
+ if (dbt->size == sizeof(DB_LOCK_ILOCK))
+ FAST_HASH(dbt->data);
+
+ return (__ham_func5(dbt->data, dbt->size));
+}
+
+/*
+ * __lock_lhash --
+ *
+ * PUBLIC: u_int32_t __lock_lhash __P((DB_LOCKOBJ *));
+ */
+u_int32_t
+__lock_lhash(lock_obj)
+ DB_LOCKOBJ *lock_obj;
+{
+ u_int32_t tmp;
+ void *obj_data;
+
+ obj_data = SH_DBT_PTR(&lock_obj->lockobj);
+ if (lock_obj->type == DB_LOCK_LOCKER) {
+ memcpy(&tmp, obj_data, sizeof(u_int32_t));
+ return (tmp);
+ }
+
+ if (lock_obj->lockobj.size == sizeof(DB_LOCK_ILOCK))
+ FAST_HASH(obj_data);
+
+ return (__ham_func5(obj_data, lock_obj->lockobj.size));
+}
+
+/*
+ * __lock_locker_hash --
+ * Hash function for entering lockers into the hash table. Since these
+ * are simply 32-bit unsigned integers, just return the locker value.
+ *
+ * PUBLIC: u_int32_t __lock_locker_hash __P((u_int32_t));
+ */
+u_int32_t
+__lock_locker_hash(locker)
+ u_int32_t locker;
+{
+ return (locker);
+}
diff --git a/usr/src/cmd/sendmail/db/log/log.c b/usr/src/cmd/sendmail/db/log/log.c
new file mode 100644
index 0000000000..d8c59c06f8
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/log/log.c
@@ -0,0 +1,546 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)log.c 10.63 (Sleepycat) 10/10/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <errno.h>
+#include <shqueue.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#endif
+
+#include "db_int.h"
+#include "shqueue.h"
+#include "log.h"
+#include "db_dispatch.h"
+#include "txn.h"
+#include "txn_auto.h"
+#include "common_ext.h"
+
+static int __log_recover __P((DB_LOG *));
+
+/*
+ * log_open --
+ * Initialize and/or join a log.
+ */
+int
+log_open(path, flags, mode, dbenv, lpp)
+ const char *path;
+ u_int32_t flags;
+ int mode;
+ DB_ENV *dbenv;
+ DB_LOG **lpp;
+{
+ DB_LOG *dblp;
+ LOG *lp;
+ int ret;
+
+ /* Validate arguments. */
+#ifdef HAVE_SPINLOCKS
+#define OKFLAGS (DB_CREATE | DB_THREAD)
+#else
+#define OKFLAGS (DB_CREATE)
+#endif
+ if ((ret = __db_fchk(dbenv, "log_open", flags, OKFLAGS)) != 0)
+ return (ret);
+
+ /* Create and initialize the DB_LOG structure. */
+ if ((ret = __os_calloc(1, sizeof(DB_LOG), &dblp)) != 0)
+ return (ret);
+
+ if (path != NULL && (ret = __os_strdup(path, &dblp->dir)) != 0)
+ goto err;
+
+ dblp->dbenv = dbenv;
+ dblp->lfd = -1;
+ ZERO_LSN(dblp->c_lsn);
+ dblp->c_fd = -1;
+
+ /*
+ * The log region isn't fixed size because we store the registered
+ * file names there. Make it fairly large so that we don't have to
+ * grow it.
+ */
+#define DEF_LOG_SIZE (30 * 1024)
+
+ /* Map in the region. */
+ dblp->reginfo.dbenv = dbenv;
+ dblp->reginfo.appname = DB_APP_LOG;
+ if (path == NULL)
+ dblp->reginfo.path = NULL;
+ else
+ if ((ret = __os_strdup(path, &dblp->reginfo.path)) != 0)
+ goto err;
+ dblp->reginfo.file = DB_DEFAULT_LOG_FILE;
+ dblp->reginfo.mode = mode;
+ dblp->reginfo.size = DEF_LOG_SIZE;
+ dblp->reginfo.dbflags = flags;
+ dblp->reginfo.flags = REGION_SIZEDEF;
+ if ((ret = __db_rattach(&dblp->reginfo)) != 0)
+ goto err;
+
+ /*
+ * The LOG structure is first in the region, the rest of the region
+ * is free space.
+ */
+ dblp->lp = dblp->reginfo.addr;
+ dblp->addr = (u_int8_t *)dblp->lp + sizeof(LOG);
+
+ /* Initialize a created region. */
+ if (F_ISSET(&dblp->reginfo, REGION_CREATED)) {
+ __db_shalloc_init(dblp->addr, DEF_LOG_SIZE - sizeof(LOG));
+
+ /* Initialize the LOG structure. */
+ lp = dblp->lp;
+ lp->persist.lg_max = dbenv == NULL ? 0 : dbenv->lg_max;
+ if (lp->persist.lg_max == 0)
+ lp->persist.lg_max = DEFAULT_MAX;
+ lp->persist.magic = DB_LOGMAGIC;
+ lp->persist.version = DB_LOGVERSION;
+ lp->persist.mode = mode;
+ SH_TAILQ_INIT(&lp->fq);
+
+ /* Initialize LOG LSNs. */
+ lp->lsn.file = 1;
+ lp->lsn.offset = 0;
+ }
+
+ /* Initialize thread information, mutex. */
+ if (LF_ISSET(DB_THREAD)) {
+ F_SET(dblp, DB_AM_THREAD);
+ if ((ret = __db_shalloc(dblp->addr,
+ sizeof(db_mutex_t), MUTEX_ALIGNMENT, &dblp->mutexp)) != 0)
+ goto err;
+ (void)__db_mutex_init(dblp->mutexp, 0);
+ }
+
+ /*
+ * If doing recovery, try and recover any previous log files before
+ * releasing the lock.
+ */
+ if (F_ISSET(&dblp->reginfo, REGION_CREATED) &&
+ (ret = __log_recover(dblp)) != 0)
+ goto err;
+
+ UNLOCK_LOGREGION(dblp);
+ *lpp = dblp;
+ return (0);
+
+err: if (dblp->reginfo.addr != NULL) {
+ if (dblp->mutexp != NULL)
+ __db_shalloc_free(dblp->addr, dblp->mutexp);
+
+ UNLOCK_LOGREGION(dblp);
+ (void)__db_rdetach(&dblp->reginfo);
+ if (F_ISSET(&dblp->reginfo, REGION_CREATED))
+ (void)log_unlink(path, 1, dbenv);
+ }
+
+ if (dblp->reginfo.path != NULL)
+ __os_freestr(dblp->reginfo.path);
+ if (dblp->dir != NULL)
+ __os_freestr(dblp->dir);
+ __os_free(dblp, sizeof(*dblp));
+ return (ret);
+}
+
+/*
+ * __log_panic --
+ * Panic a log.
+ *
+ * PUBLIC: void __log_panic __P((DB_ENV *));
+ */
+void
+__log_panic(dbenv)
+ DB_ENV *dbenv;
+{
+ if (dbenv->lg_info != NULL)
+ dbenv->lg_info->lp->rlayout.panic = 1;
+}
+
+/*
+ * __log_recover --
+ * Recover a log.
+ */
+static int
+__log_recover(dblp)
+ DB_LOG *dblp;
+{
+ DBT dbt;
+ DB_LSN lsn;
+ LOG *lp;
+ u_int32_t chk;
+ int cnt, found_checkpoint, ret;
+
+ lp = dblp->lp;
+
+ /*
+ * Find a log file. If none exist, we simply return, leaving
+ * everything initialized to a new log.
+ */
+ if ((ret = __log_find(dblp, 0, &cnt)) != 0)
+ return (ret);
+ if (cnt == 0)
+ return (0);
+
+ /*
+ * We have the last useful log file and we've loaded any persistent
+ * information. Pretend that the log is larger than it can possibly
+ * be, and read the last file, looking for the last checkpoint and
+ * the log's end.
+ */
+ lp->lsn.file = cnt + 1;
+ lp->lsn.offset = 0;
+ lsn.file = cnt;
+ lsn.offset = 0;
+
+ /* Set the cursor. Shouldn't fail, leave error messages on. */
+ memset(&dbt, 0, sizeof(dbt));
+ if ((ret = __log_get(dblp, &lsn, &dbt, DB_SET, 0)) != 0)
+ return (ret);
+
+ /*
+ * Read to the end of the file, saving checkpoints. This will fail
+ * at some point, so turn off error messages.
+ */
+ found_checkpoint = 0;
+ while (__log_get(dblp, &lsn, &dbt, DB_NEXT, 1) == 0) {
+ if (dbt.size < sizeof(u_int32_t))
+ continue;
+ memcpy(&chk, dbt.data, sizeof(u_int32_t));
+ if (chk == DB_txn_ckp) {
+ lp->chkpt_lsn = lsn;
+ found_checkpoint = 1;
+ }
+ }
+
+ /*
+ * We now know where the end of the log is. Set the first LSN that
+ * we want to return to an application and the LSN of the last known
+ * record on disk.
+ */
+ lp->lsn = lp->s_lsn = lsn;
+ lp->lsn.offset += dblp->c_len;
+
+ /* Set up the current buffer information, too. */
+ lp->len = dblp->c_len;
+ lp->b_off = 0;
+ lp->w_off = lp->lsn.offset;
+
+ /*
+ * It's possible that we didn't find a checkpoint because there wasn't
+ * one in the last log file. Start searching.
+ */
+ while (!found_checkpoint && cnt > 1) {
+ lsn.file = --cnt;
+ lsn.offset = 0;
+
+ /* Set the cursor. Shouldn't fail, leave error messages on. */
+ if ((ret = __log_get(dblp, &lsn, &dbt, DB_SET, 0)) != 0)
+ return (ret);
+
+ /*
+ * Read to the end of the file, saving checkpoints. Shouldn't
+ * fail, leave error messages on.
+ */
+ while (__log_get(dblp, &lsn, &dbt, DB_NEXT, 0) == 0) {
+ if (dbt.size < sizeof(u_int32_t))
+ continue;
+ memcpy(&chk, dbt.data, sizeof(u_int32_t));
+ if (chk == DB_txn_ckp) {
+ lp->chkpt_lsn = lsn;
+ found_checkpoint = 1;
+ }
+ }
+ }
+ /*
+ * Reset the cursor lsn to the beginning of the log, so that an
+ * initial call to DB_NEXT does the right thing.
+ */
+ ZERO_LSN(dblp->c_lsn);
+
+ /* If we never find a checkpoint, that's okay, just 0 it out. */
+ if (!found_checkpoint)
+ ZERO_LSN(lp->chkpt_lsn);
+
+ /*
+ * !!!
+ * The test suite explicitly looks for this string -- don't change
+ * it here unless you also change it there.
+ */
+ __db_err(dblp->dbenv,
+ "Finding last valid log LSN: file: %lu offset %lu",
+ (u_long)lp->lsn.file, (u_long)lp->lsn.offset);
+
+ return (0);
+}
+
+/*
+ * __log_find --
+ * Try to find a log file. If find_first is set, valp will contain
+ * the number of the first log file, else it will contain the number of
+ * the last log file.
+ *
+ * PUBLIC: int __log_find __P((DB_LOG *, int, int *));
+ */
+int
+__log_find(dblp, find_first, valp)
+ DB_LOG *dblp;
+ int find_first, *valp;
+{
+ u_int32_t clv, logval;
+ int cnt, fcnt, ret;
+ const char *dir;
+ char **names, *p, *q;
+
+ *valp = 0;
+
+ /* Find the directory name. */
+ if ((ret = __log_name(dblp, 1, &p, NULL, 0)) != 0)
+ return (ret);
+ if ((q = __db_rpath(p)) == NULL)
+ dir = PATH_DOT;
+ else {
+ *q = '\0';
+ dir = p;
+ }
+
+ /* Get the list of file names. */
+ ret = __os_dirlist(dir, &names, &fcnt);
+ __os_freestr(p);
+ if (ret != 0) {
+ __db_err(dblp->dbenv, "%s: %s", dir, strerror(ret));
+ return (ret);
+ }
+
+ /*
+ * Search for a valid log file name, return a value of 0 on
+ * failure.
+ *
+ * XXX
+ * Assumes that atoi(3) returns a 32-bit number.
+ */
+ for (cnt = fcnt, clv = logval = 0; --cnt >= 0;) {
+ if (strncmp(names[cnt], LFPREFIX, sizeof(LFPREFIX) - 1) != 0)
+ continue;
+
+ clv = atoi(names[cnt] + (sizeof(LFPREFIX) - 1));
+ if (find_first) {
+ if (logval != 0 && clv > logval)
+ continue;
+ } else
+ if (logval != 0 && clv < logval)
+ continue;
+
+ if (__log_valid(dblp, clv, 1) == 0)
+ logval = clv;
+ }
+
+ *valp = logval;
+
+ /* Discard the list. */
+ __os_dirfree(names, fcnt);
+
+ return (0);
+}
+
+/*
+ * log_valid --
+ * Validate a log file.
+ *
+ * PUBLIC: int __log_valid __P((DB_LOG *, u_int32_t, int));
+ */
+int
+__log_valid(dblp, number, set_persist)
+ DB_LOG *dblp;
+ u_int32_t number;
+ int set_persist;
+{
+ LOGP persist;
+ ssize_t nw;
+ char *fname;
+ int fd, ret;
+
+ /* Try to open the log file. */
+ if ((ret = __log_name(dblp,
+ number, &fname, &fd, DB_RDONLY | DB_SEQUENTIAL)) != 0) {
+ __os_freestr(fname);
+ return (ret);
+ }
+
+ /* Try to read the header. */
+ if ((ret = __os_seek(fd, 0, 0, sizeof(HDR), 0, SEEK_SET)) != 0 ||
+ (ret = __os_read(fd, &persist, sizeof(LOGP), &nw)) != 0 ||
+ nw != sizeof(LOGP)) {
+ if (ret == 0)
+ ret = EIO;
+
+ (void)__os_close(fd);
+
+ __db_err(dblp->dbenv,
+ "Ignoring log file: %s: %s", fname, strerror(ret));
+ goto err;
+ }
+ (void)__os_close(fd);
+
+ /* Validate the header. */
+ if (persist.magic != DB_LOGMAGIC) {
+ __db_err(dblp->dbenv,
+ "Ignoring log file: %s: magic number %lx, not %lx",
+ fname, (u_long)persist.magic, (u_long)DB_LOGMAGIC);
+ ret = EINVAL;
+ goto err;
+ }
+ if (persist.version < DB_LOGOLDVER || persist.version > DB_LOGVERSION) {
+ __db_err(dblp->dbenv,
+ "Ignoring log file: %s: unsupported log version %lu",
+ fname, (u_long)persist.version);
+ ret = EINVAL;
+ goto err;
+ }
+
+ /*
+ * If we're going to use this log file, set the region's persistent
+ * information based on the headers.
+ */
+ if (set_persist) {
+ dblp->lp->persist.lg_max = persist.lg_max;
+ dblp->lp->persist.mode = persist.mode;
+ }
+ ret = 0;
+
+err: __os_freestr(fname);
+ return (ret);
+}
+
+/*
+ * log_close --
+ * Close a log.
+ */
+int
+log_close(dblp)
+ DB_LOG *dblp;
+{
+ u_int32_t i;
+ int ret, t_ret;
+
+ LOG_PANIC_CHECK(dblp);
+
+ /* We may have opened files as part of XA; if so, close them. */
+ __log_close_files(dblp);
+
+ /* Discard the per-thread pointer. */
+ if (dblp->mutexp != NULL) {
+ LOCK_LOGREGION(dblp);
+ __db_shalloc_free(dblp->addr, dblp->mutexp);
+ UNLOCK_LOGREGION(dblp);
+ }
+
+ /* Close the region. */
+ ret = __db_rdetach(&dblp->reginfo);
+
+ /* Close open files, release allocated memory. */
+ if (dblp->lfd != -1 && (t_ret = __os_close(dblp->lfd)) != 0 && ret == 0)
+ ret = t_ret;
+ if (dblp->c_dbt.data != NULL)
+ __os_free(dblp->c_dbt.data, dblp->c_dbt.ulen);
+ if (dblp->c_fd != -1 &&
+ (t_ret = __os_close(dblp->c_fd)) != 0 && ret == 0)
+ ret = t_ret;
+ if (dblp->dbentry != NULL) {
+ for (i = 0; i < dblp->dbentry_cnt; i++)
+ if (dblp->dbentry[i].name != NULL)
+ __os_freestr(dblp->dbentry[i].name);
+ __os_free(dblp->dbentry,
+ (dblp->dbentry_cnt * sizeof(DB_ENTRY)));
+ }
+
+ if (dblp->dir != NULL)
+ __os_freestr(dblp->dir);
+
+ if (dblp->reginfo.path != NULL)
+ __os_freestr(dblp->reginfo.path);
+ __os_free(dblp, sizeof(*dblp));
+
+ return (ret);
+}
+
+/*
+ * log_unlink --
+ * Exit a log.
+ */
+int
+log_unlink(path, force, dbenv)
+ const char *path;
+ int force;
+ DB_ENV *dbenv;
+{
+ REGINFO reginfo;
+ int ret;
+
+ memset(&reginfo, 0, sizeof(reginfo));
+ reginfo.dbenv = dbenv;
+ reginfo.appname = DB_APP_LOG;
+ if (path != NULL && (ret = __os_strdup(path, &reginfo.path)) != 0)
+ return (ret);
+ reginfo.file = DB_DEFAULT_LOG_FILE;
+ ret = __db_runlink(&reginfo, force);
+ if (reginfo.path != NULL)
+ __os_freestr(reginfo.path);
+ return (ret);
+}
+
+/*
+ * log_stat --
+ * Return LOG statistics.
+ */
+int
+log_stat(dblp, gspp, db_malloc)
+ DB_LOG *dblp;
+ DB_LOG_STAT **gspp;
+ void *(*db_malloc) __P((size_t));
+{
+ LOG *lp;
+ int ret;
+
+ *gspp = NULL;
+ lp = dblp->lp;
+
+ LOG_PANIC_CHECK(dblp);
+
+ if ((ret = __os_malloc(sizeof(**gspp), db_malloc, gspp)) != 0)
+ return (ret);
+
+ /* Copy out the global statistics. */
+ LOCK_LOGREGION(dblp);
+ **gspp = lp->stat;
+
+ (*gspp)->st_magic = lp->persist.magic;
+ (*gspp)->st_version = lp->persist.version;
+ (*gspp)->st_mode = lp->persist.mode;
+ (*gspp)->st_lg_max = lp->persist.lg_max;
+
+ (*gspp)->st_region_nowait = lp->rlayout.lock.mutex_set_nowait;
+ (*gspp)->st_region_wait = lp->rlayout.lock.mutex_set_wait;
+
+ (*gspp)->st_cur_file = lp->lsn.file;
+ (*gspp)->st_cur_offset = lp->lsn.offset;
+
+ (*gspp)->st_refcnt = lp->rlayout.refcnt;
+ (*gspp)->st_regsize = lp->rlayout.size;
+
+ UNLOCK_LOGREGION(dblp);
+
+ return (0);
+}
diff --git a/usr/src/cmd/sendmail/db/log/log_archive.c b/usr/src/cmd/sendmail/db/log/log_archive.c
new file mode 100644
index 0000000000..9f3b24d8e3
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/log/log_archive.c
@@ -0,0 +1,417 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)log_archive.c 10.44 (Sleepycat) 10/9/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#endif
+
+#include "db_int.h"
+#include "db_dispatch.h"
+#include "shqueue.h"
+#include "log.h"
+#include "common_ext.h"
+#include "clib_ext.h" /* XXX: needed for getcwd. */
+
+static int __absname __P((char *, char *, char **));
+static int __build_data __P((DB_LOG *, char *, char ***, void *(*)(size_t)));
+static int __cmpfunc __P((const void *, const void *));
+static int __usermem __P((char ***, void *(*)(size_t)));
+
+/*
+ * log_archive --
+ * Supporting function for db_archive(1).
+ */
+int
+log_archive(dblp, listp, flags, db_malloc)
+ DB_LOG *dblp;
+ char ***listp;
+ u_int32_t flags;
+ void *(*db_malloc) __P((size_t));
+{
+ DBT rec;
+ DB_LSN stable_lsn;
+ u_int32_t fnum;
+ int array_size, n, ret;
+ char **array, **arrayp, *name, *p, *pref, buf[MAXPATHLEN];
+
+ name = NULL;
+ COMPQUIET(fnum, 0);
+
+ LOG_PANIC_CHECK(dblp);
+
+#define OKFLAGS (DB_ARCH_ABS | DB_ARCH_DATA | DB_ARCH_LOG)
+ if (flags != 0) {
+ if ((ret =
+ __db_fchk(dblp->dbenv, "log_archive", flags, OKFLAGS)) != 0)
+ return (ret);
+ if ((ret =
+ __db_fcchk(dblp->dbenv,
+ "log_archive", flags, DB_ARCH_DATA, DB_ARCH_LOG)) != 0)
+ return (ret);
+ }
+
+ /*
+ * Get the absolute pathname of the current directory. It would
+ * be nice to get the shortest pathname of the database directory,
+ * but that's just not possible.
+ */
+ if (LF_ISSET(DB_ARCH_ABS)) {
+ errno = 0;
+ if ((pref = getcwd(buf, sizeof(buf))) == NULL)
+ return (errno == 0 ? ENOMEM : errno);
+ } else
+ pref = NULL;
+
+ switch (LF_ISSET(~DB_ARCH_ABS)) {
+ case DB_ARCH_DATA:
+ return (__build_data(dblp, pref, listp, db_malloc));
+ case DB_ARCH_LOG:
+ memset(&rec, 0, sizeof(rec));
+ if (F_ISSET(dblp, DB_AM_THREAD))
+ F_SET(&rec, DB_DBT_MALLOC);
+ if ((ret = log_get(dblp, &stable_lsn, &rec, DB_LAST)) != 0)
+ return (ret);
+ if (F_ISSET(dblp, DB_AM_THREAD))
+ __os_free(rec.data, rec.size);
+ fnum = stable_lsn.file;
+ break;
+ case 0:
+ if ((ret = __log_findckp(dblp, &stable_lsn)) != 0) {
+ /*
+ * A return of DB_NOTFOUND means that we didn't find
+ * any records in the log (so we are not going to be
+ * deleting any log files).
+ */
+ if (ret != DB_NOTFOUND)
+ return (ret);
+ *listp = NULL;
+ return (0);
+ }
+ /* Remove any log files before the last stable LSN. */
+ fnum = stable_lsn.file - 1;
+ break;
+ }
+
+#define LIST_INCREMENT 64
+ /* Get some initial space. */
+ array_size = 10;
+ if ((ret = __os_malloc(sizeof(char *) * array_size, NULL, &array)) != 0)
+ return (ret);
+ array[0] = NULL;
+
+ /* Build an array of the file names. */
+ for (n = 0; fnum > 0; --fnum) {
+ if ((ret = __log_name(dblp, fnum, &name, NULL, 0)) != 0)
+ goto err;
+ if (__os_exists(name, NULL) != 0) {
+ __os_freestr(name);
+ name = NULL;
+ break;
+ }
+
+ if (n >= array_size - 1) {
+ array_size += LIST_INCREMENT;
+ if ((ret = __os_realloc(&array,
+ sizeof(char *) * array_size)) != 0)
+ goto err;
+ }
+
+ if (LF_ISSET(DB_ARCH_ABS)) {
+ if ((ret = __absname(pref, name, &array[n])) != 0)
+ goto err;
+ __os_freestr(name);
+ } else if ((p = __db_rpath(name)) != NULL) {
+ if ((ret = __os_strdup(p + 1, &array[n])) != 0)
+ goto err;
+ __os_freestr(name);
+ } else
+ array[n] = name;
+
+ name = NULL;
+ array[++n] = NULL;
+ }
+
+ /* If there's nothing to return, we're done. */
+ if (n == 0) {
+ *listp = NULL;
+ ret = 0;
+ goto err;
+ }
+
+ /* Sort the list. */
+ qsort(array, (size_t)n, sizeof(char *), __cmpfunc);
+
+ /* Rework the memory. */
+ if ((ret = __usermem(&array, db_malloc)) != 0)
+ goto err;
+
+ *listp = array;
+ return (0);
+
+err: if (array != NULL) {
+ for (arrayp = array; *arrayp != NULL; ++arrayp)
+ __os_freestr(*arrayp);
+ __os_free(array, sizeof(char *) * array_size);
+ }
+ if (name != NULL)
+ __os_freestr(name);
+ return (ret);
+}
+
+/*
+ * __build_data --
+ * Build a list of datafiles for return.
+ */
+static int
+__build_data(dblp, pref, listp, db_malloc)
+ DB_LOG *dblp;
+ char *pref, ***listp;
+ void *(*db_malloc) __P((size_t));
+{
+ DBT rec;
+ DB_LSN lsn;
+ __log_register_args *argp;
+ u_int32_t rectype;
+ int array_size, last, n, nxt, ret;
+ char **array, **arrayp, *p, *real_name;
+
+ /* Get some initial space. */
+ array_size = 10;
+ if ((ret = __os_malloc(sizeof(char *) * array_size, NULL, &array)) != 0)
+ return (ret);
+ array[0] = NULL;
+
+ memset(&rec, 0, sizeof(rec));
+ if (F_ISSET(dblp, DB_AM_THREAD))
+ F_SET(&rec, DB_DBT_MALLOC);
+ for (n = 0, ret = log_get(dblp, &lsn, &rec, DB_FIRST);
+ ret == 0; ret = log_get(dblp, &lsn, &rec, DB_NEXT)) {
+ if (rec.size < sizeof(rectype)) {
+ ret = EINVAL;
+ __db_err(dblp->dbenv, "log_archive: bad log record");
+ goto lg_free;
+ }
+
+ memcpy(&rectype, rec.data, sizeof(rectype));
+ if (rectype != DB_log_register) {
+ if (F_ISSET(dblp, DB_AM_THREAD)) {
+ __os_free(rec.data, rec.size);
+ rec.data = NULL;
+ }
+ continue;
+ }
+ if ((ret = __log_register_read(rec.data, &argp)) != 0) {
+ ret = EINVAL;
+ __db_err(dblp->dbenv,
+ "log_archive: unable to read log record");
+ goto lg_free;
+ }
+
+ if (n >= array_size - 1) {
+ array_size += LIST_INCREMENT;
+ if ((ret = __os_realloc(&array,
+ sizeof(char *) * array_size)) != 0)
+ goto lg_free;
+ }
+
+ if ((ret = __os_strdup(argp->name.data, &array[n])) != 0) {
+lg_free: if (F_ISSET(&rec, DB_DBT_MALLOC) && rec.data != NULL)
+ __os_free(rec.data, rec.size);
+ goto err1;
+ }
+
+ array[++n] = NULL;
+ __os_free(argp, 0);
+
+ if (F_ISSET(dblp, DB_AM_THREAD)) {
+ __os_free(rec.data, rec.size);
+ rec.data = NULL;
+ }
+ }
+
+ /* If there's nothing to return, we're done. */
+ if (n == 0) {
+ ret = 0;
+ *listp = NULL;
+ goto err1;
+ }
+
+ /* Sort the list. */
+ qsort(array, (size_t)n, sizeof(char *), __cmpfunc);
+
+ /*
+ * Build the real pathnames, discarding nonexistent files and
+ * duplicates.
+ */
+ for (last = nxt = 0; nxt < n;) {
+ /*
+ * Discard duplicates. Last is the next slot we're going
+ * to return to the user, nxt is the next slot that we're
+ * going to consider.
+ */
+ if (last != nxt) {
+ array[last] = array[nxt];
+ array[nxt] = NULL;
+ }
+ for (++nxt; nxt < n &&
+ strcmp(array[last], array[nxt]) == 0; ++nxt) {
+ __os_freestr(array[nxt]);
+ array[nxt] = NULL;
+ }
+
+ /* Get the real name. */
+ if ((ret = __db_appname(dblp->dbenv,
+ DB_APP_DATA, NULL, array[last], 0, NULL, &real_name)) != 0)
+ goto err2;
+
+ /* If the file doesn't exist, ignore it. */
+ if (__os_exists(real_name, NULL) != 0) {
+ __os_freestr(real_name);
+ __os_freestr(array[last]);
+ array[last] = NULL;
+ continue;
+ }
+
+ /* Rework the name as requested by the user. */
+ __os_freestr(array[last]);
+ array[last] = NULL;
+ if (pref != NULL) {
+ ret = __absname(pref, real_name, &array[last]);
+ __os_freestr(real_name);
+ if (ret != 0)
+ goto err2;
+ } else if ((p = __db_rpath(real_name)) != NULL) {
+ ret = __os_strdup(p + 1, &array[last]);
+ __os_freestr(real_name);
+ if (ret != 0)
+ goto err2;
+ } else
+ array[last] = real_name;
+ ++last;
+ }
+
+ /* NULL-terminate the list. */
+ array[last] = NULL;
+
+ /* Rework the memory. */
+ if ((ret = __usermem(&array, db_malloc)) != 0)
+ goto err1;
+
+ *listp = array;
+ return (0);
+
+err2: /*
+ * XXX
+ * We've possibly inserted NULLs into the array list, so clean up a
+ * bit so that the other error processing works.
+ */
+ if (array != NULL)
+ for (; nxt < n; ++nxt)
+ __os_freestr(array[nxt]);
+ /* FALLTHROUGH */
+
+err1: if (array != NULL) {
+ for (arrayp = array; *arrayp != NULL; ++arrayp)
+ __os_freestr(*arrayp);
+ __os_free(array, array_size * sizeof(char *));
+ }
+ return (ret);
+}
+
+/*
+ * __absname --
+ * Return an absolute path name for the file.
+ */
+static int
+__absname(pref, name, newnamep)
+ char *pref, *name, **newnamep;
+{
+ size_t l_pref, l_name;
+ int isabspath, ret;
+ char *newname;
+
+ l_name = strlen(name);
+ isabspath = __os_abspath(name);
+ l_pref = isabspath ? 0 : strlen(pref);
+
+ /* Malloc space for concatenating the two. */
+ if ((ret = __os_malloc(l_pref + l_name + 2, NULL, &newname)) != 0)
+ return (ret);
+ *newnamep = newname;
+
+ /* Build the name. If `name' is an absolute path, ignore any prefix. */
+ if (!isabspath) {
+ memcpy(newname, pref, l_pref);
+ if (strchr(PATH_SEPARATOR, newname[l_pref - 1]) == NULL)
+ newname[l_pref++] = PATH_SEPARATOR[0];
+ }
+ memcpy(newname + l_pref, name, l_name + 1);
+
+ return (0);
+}
+
+/*
+ * __usermem --
+ * Create a single chunk of memory that holds the returned information.
+ * If the user has their own malloc routine, use it.
+ */
+static int
+__usermem(listp, db_malloc)
+ char ***listp;
+ void *(*db_malloc) __P((size_t));
+{
+ size_t len;
+ int ret;
+ char **array, **arrayp, **orig, *strp;
+
+ /* Find out how much space we need. */
+ for (len = 0, orig = *listp; *orig != NULL; ++orig)
+ len += sizeof(char *) + strlen(*orig) + 1;
+ len += sizeof(char *);
+
+ /* Allocate it and set up the pointers. */
+ if ((ret = __os_malloc(len, db_malloc, &array)) != 0)
+ return (ret);
+
+ strp = (char *)(array + (orig - *listp) + 1);
+
+ /* Copy the original information into the new memory. */
+ for (orig = *listp, arrayp = array; *orig != NULL; ++orig, ++arrayp) {
+ len = strlen(*orig);
+ memcpy(strp, *orig, len + 1);
+ *arrayp = strp;
+ strp += len + 1;
+
+ __os_freestr(*orig);
+ }
+
+ /* NULL-terminate the list. */
+ *arrayp = NULL;
+
+ __os_free(*listp, 0);
+ *listp = array;
+
+ return (0);
+}
+
+static int
+__cmpfunc(p1, p2)
+ const void *p1, *p2;
+{
+ return (strcmp(*((char * const *)p1), *((char * const *)p2)));
+}
diff --git a/usr/src/cmd/sendmail/db/log/log_auto.c b/usr/src/cmd/sendmail/db/log/log_auto.c
new file mode 100644
index 0000000000..92e682661c
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/log/log_auto.c
@@ -0,0 +1,231 @@
+/* Do not edit: automatically built by dist/db_gen.sh. */
+#include "config.h"
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <ctype.h>
+#include <errno.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#endif
+
+#include "db_int.h"
+#include "db_page.h"
+#include "db_dispatch.h"
+#include "log.h"
+#include "db_am.h"
+/*
+ * PUBLIC: int __log_register_log
+ * PUBLIC: __P((DB_LOG *, DB_TXN *, DB_LSN *, u_int32_t,
+ * PUBLIC: u_int32_t, const DBT *, const DBT *, u_int32_t,
+ * PUBLIC: DBTYPE));
+ */
+int __log_register_log(logp, txnid, ret_lsnp, flags,
+ opcode, name, uid, id, ftype)
+ DB_LOG *logp;
+ DB_TXN *txnid;
+ DB_LSN *ret_lsnp;
+ u_int32_t flags;
+ u_int32_t opcode;
+ const DBT *name;
+ const DBT *uid;
+ u_int32_t id;
+ DBTYPE ftype;
+{
+ DBT logrec;
+ DB_LSN *lsnp, null_lsn;
+ u_int32_t zero;
+ u_int32_t rectype, txn_num;
+ int ret;
+ u_int8_t *bp;
+
+ rectype = DB_log_register;
+ txn_num = txnid == NULL ? 0 : txnid->txnid;
+ if (txnid == NULL) {
+ ZERO_LSN(null_lsn);
+ lsnp = &null_lsn;
+ } else
+ lsnp = &txnid->last_lsn;
+ logrec.size = sizeof(rectype) + sizeof(txn_num) + sizeof(DB_LSN)
+ + sizeof(opcode)
+ + sizeof(u_int32_t) + (name == NULL ? 0 : name->size)
+ + sizeof(u_int32_t) + (uid == NULL ? 0 : uid->size)
+ + sizeof(id)
+ + sizeof(ftype);
+ if ((ret = __os_malloc(logrec.size, NULL, &logrec.data)) != 0)
+ return (ret);
+
+ bp = logrec.data;
+ memcpy(bp, &rectype, sizeof(rectype));
+ bp += sizeof(rectype);
+ memcpy(bp, &txn_num, sizeof(txn_num));
+ bp += sizeof(txn_num);
+ memcpy(bp, lsnp, sizeof(DB_LSN));
+ bp += sizeof(DB_LSN);
+ memcpy(bp, &opcode, sizeof(opcode));
+ bp += sizeof(opcode);
+ if (name == NULL) {
+ zero = 0;
+ memcpy(bp, &zero, sizeof(u_int32_t));
+ bp += sizeof(u_int32_t);
+ } else {
+ memcpy(bp, &name->size, sizeof(name->size));
+ bp += sizeof(name->size);
+ memcpy(bp, name->data, name->size);
+ bp += name->size;
+ }
+ if (uid == NULL) {
+ zero = 0;
+ memcpy(bp, &zero, sizeof(u_int32_t));
+ bp += sizeof(u_int32_t);
+ } else {
+ memcpy(bp, &uid->size, sizeof(uid->size));
+ bp += sizeof(uid->size);
+ memcpy(bp, uid->data, uid->size);
+ bp += uid->size;
+ }
+ memcpy(bp, &id, sizeof(id));
+ bp += sizeof(id);
+ memcpy(bp, &ftype, sizeof(ftype));
+ bp += sizeof(ftype);
+#ifdef DIAGNOSTIC
+ if ((u_int32_t)(bp - (u_int8_t *)logrec.data) != logrec.size)
+ fprintf(stderr, "Error in log record length");
+#endif
+ ret = __log_put(logp, ret_lsnp, (DBT *)&logrec, flags);
+ if (txnid != NULL)
+ txnid->last_lsn = *ret_lsnp;
+ __os_free(logrec.data, 0);
+ return (ret);
+}
+
+/*
+ * PUBLIC: int __log_register_print
+ * PUBLIC: __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+ */
+int
+__log_register_print(notused1, dbtp, lsnp, notused2, notused3)
+ DB_LOG *notused1;
+ DBT *dbtp;
+ DB_LSN *lsnp;
+ int notused2;
+ void *notused3;
+{
+ __log_register_args *argp;
+ u_int32_t i;
+ u_int ch;
+ int ret;
+
+ i = 0;
+ ch = 0;
+ notused1 = NULL;
+ notused2 = 0;
+ notused3 = NULL;
+
+ if ((ret = __log_register_read(dbtp->data, &argp)) != 0)
+ return (ret);
+ printf("[%lu][%lu]log_register: rec: %lu txnid %lx prevlsn [%lu][%lu]\n",
+ (u_long)lsnp->file,
+ (u_long)lsnp->offset,
+ (u_long)argp->type,
+ (u_long)argp->txnid->txnid,
+ (u_long)argp->prev_lsn.file,
+ (u_long)argp->prev_lsn.offset);
+ printf("\topcode: %lu\n", (u_long)argp->opcode);
+ printf("\tname: ");
+ for (i = 0; i < argp->name.size; i++) {
+ ch = ((u_int8_t *)argp->name.data)[i];
+ if (isprint(ch) || ch == 0xa)
+ putchar(ch);
+ else
+ printf("%#x ", ch);
+ }
+ printf("\n");
+ printf("\tuid: ");
+ for (i = 0; i < argp->uid.size; i++) {
+ ch = ((u_int8_t *)argp->uid.data)[i];
+ if (isprint(ch) || ch == 0xa)
+ putchar(ch);
+ else
+ printf("%#x ", ch);
+ }
+ printf("\n");
+ printf("\tid: %lu\n", (u_long)argp->id);
+ printf("\tftype: 0x%lx\n", (u_long)argp->ftype);
+ printf("\n");
+ __os_free(argp, 0);
+ return (0);
+}
+
+/*
+ * PUBLIC: int __log_register_read __P((void *, __log_register_args **));
+ */
+int
+__log_register_read(recbuf, argpp)
+ void *recbuf;
+ __log_register_args **argpp;
+{
+ __log_register_args *argp;
+ u_int8_t *bp;
+ int ret;
+
+ ret = __os_malloc(sizeof(__log_register_args) +
+ sizeof(DB_TXN), NULL, &argp);
+ if (ret != 0)
+ return (ret);
+ argp->txnid = (DB_TXN *)&argp[1];
+ bp = recbuf;
+ memcpy(&argp->type, bp, sizeof(argp->type));
+ bp += sizeof(argp->type);
+ memcpy(&argp->txnid->txnid, bp, sizeof(argp->txnid->txnid));
+ bp += sizeof(argp->txnid->txnid);
+ memcpy(&argp->prev_lsn, bp, sizeof(DB_LSN));
+ bp += sizeof(DB_LSN);
+ memcpy(&argp->opcode, bp, sizeof(argp->opcode));
+ bp += sizeof(argp->opcode);
+ memcpy(&argp->name.size, bp, sizeof(u_int32_t));
+ bp += sizeof(u_int32_t);
+ argp->name.data = bp;
+ bp += argp->name.size;
+ memcpy(&argp->uid.size, bp, sizeof(u_int32_t));
+ bp += sizeof(u_int32_t);
+ argp->uid.data = bp;
+ bp += argp->uid.size;
+ memcpy(&argp->id, bp, sizeof(argp->id));
+ bp += sizeof(argp->id);
+ memcpy(&argp->ftype, bp, sizeof(argp->ftype));
+ bp += sizeof(argp->ftype);
+ *argpp = argp;
+ return (0);
+}
+
+/*
+ * PUBLIC: int __log_init_print __P((DB_ENV *));
+ */
+int
+__log_init_print(dbenv)
+ DB_ENV *dbenv;
+{
+ int ret;
+
+ if ((ret = __db_add_recovery(dbenv,
+ __log_register_print, DB_log_register)) != 0)
+ return (ret);
+ return (0);
+}
+
+/*
+ * PUBLIC: int __log_init_recover __P((DB_ENV *));
+ */
+int
+__log_init_recover(dbenv)
+ DB_ENV *dbenv;
+{
+ int ret;
+
+ if ((ret = __db_add_recovery(dbenv,
+ __log_register_recover, DB_log_register)) != 0)
+ return (ret);
+ return (0);
+}
+
diff --git a/usr/src/cmd/sendmail/db/log/log_compare.c b/usr/src/cmd/sendmail/db/log/log_compare.c
new file mode 100644
index 0000000000..8163568131
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/log/log_compare.c
@@ -0,0 +1,42 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997
+ * Sleepycat Software. All rights reserved.
+ */
+/*
+ * Copyright (c) 1998 by Sun Microsystems, Inc.
+ * All rights reserved.
+ */
+
+#include "config.h"
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#ifndef lint
+static const char sccsid[] = "@(#)log_compare.c 10.2 (Sleepycat) 6/21/97";
+static const char sccsi2[] = "%W% (Sun) %G%";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+#endif
+
+#include "db_int.h"
+
+/*
+ * log_compare --
+ * Compare two LSN's.
+ */
+int
+log_compare(lsn0, lsn1)
+ const DB_LSN *lsn0, *lsn1;
+{
+ if (lsn0->file != lsn1->file)
+ return (lsn0->file < lsn1->file ? -1 : 1);
+
+ if (lsn0->offset != lsn1->offset)
+ return (lsn0->offset < lsn1->offset ? -1 : 1);
+
+ return (0);
+}
diff --git a/usr/src/cmd/sendmail/db/log/log_findckp.c b/usr/src/cmd/sendmail/db/log/log_findckp.c
new file mode 100644
index 0000000000..9a260b5441
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/log/log_findckp.c
@@ -0,0 +1,140 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)log_findckp.c 10.17 (Sleepycat) 9/17/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <errno.h>
+#include <string.h>
+#endif
+
+#include "db_int.h"
+#include "shqueue.h"
+#include "log.h"
+#include "txn.h"
+#include "common_ext.h"
+
+/*
+ * __log_findckp --
+ *
+ * Looks for the most recent checkpoint that occurs before the most recent
+ * checkpoint LSN, subject to the constraint that there must be at least two
+ * checkpoints. The reason you need two checkpoints is that you might have
+ * crashed during the most recent one and may not have a copy of all the
+ * open files. This is the point from which recovery can start and the
+ * point up to which archival/truncation can take place. Checkpoints in
+ * the log look like:
+ *
+ * -------------------------------------------------------------------
+ * | ckp A, ckplsn 100 | .... record .... | ckp B, ckplsn 600 | ...
+ * -------------------------------------------------------------------
+ * LSN 500 LSN 1000
+ *
+ * If we read what log returns from using the DB_CKP parameter to logput,
+ * we'll get the record at LSN 1000. The checkpoint LSN there is 600.
+ * Now we have to scan backwards looking for a checkpoint before LSN 600.
+ * We find one at 500. This means that we can truncate the log before
+ * 500 or run recovery beginning at 500.
+ *
+ * Returns 0 if we find a suitable checkpoint or we retrieved the
+ * first record in the log from which to start.
+ * Returns DB_NOTFOUND if there are no log records.
+ * Returns errno on error.
+ *
+ * PUBLIC: int __log_findckp __P((DB_LOG *, DB_LSN *));
+ */
+int
+__log_findckp(lp, lsnp)
+ DB_LOG *lp;
+ DB_LSN *lsnp;
+{
+ DBT data;
+ DB_LSN ckp_lsn, final_ckp, last_ckp, next_lsn;
+ __txn_ckp_args *ckp_args;
+ int ret, verbose;
+
+ verbose = lp->dbenv != NULL && lp->dbenv->db_verbose != 0;
+
+ /*
+ * Need to find the appropriate point from which to begin
+ * recovery.
+ */
+ memset(&data, 0, sizeof(data));
+ if (F_ISSET(lp, DB_AM_THREAD))
+ F_SET(&data, DB_DBT_MALLOC);
+ ZERO_LSN(ckp_lsn);
+ if ((ret = log_get(lp, &last_ckp, &data, DB_CHECKPOINT)) != 0)
+ if (ret == ENOENT)
+ goto get_first;
+ else
+ return (ret);
+
+ final_ckp = last_ckp;
+ next_lsn = last_ckp;
+ do {
+ if (F_ISSET(lp, DB_AM_THREAD))
+ __os_free(data.data, data.size);
+
+ if ((ret = log_get(lp, &next_lsn, &data, DB_SET)) != 0)
+ return (ret);
+ if ((ret = __txn_ckp_read(data.data, &ckp_args)) != 0) {
+ if (F_ISSET(lp, DB_AM_THREAD))
+ __os_free(data.data, data.size);
+ return (ret);
+ }
+ if (IS_ZERO_LSN(ckp_lsn))
+ ckp_lsn = ckp_args->ckp_lsn;
+ if (verbose) {
+ __db_err(lp->dbenv, "Checkpoint at: [%lu][%lu]",
+ (u_long)last_ckp.file, (u_long)last_ckp.offset);
+ __db_err(lp->dbenv, "Checkpoint LSN: [%lu][%lu]",
+ (u_long)ckp_args->ckp_lsn.file,
+ (u_long)ckp_args->ckp_lsn.offset);
+ __db_err(lp->dbenv, "Previous checkpoint: [%lu][%lu]",
+ (u_long)ckp_args->last_ckp.file,
+ (u_long)ckp_args->last_ckp.offset);
+ }
+ last_ckp = next_lsn;
+ next_lsn = ckp_args->last_ckp;
+ __os_free(ckp_args, sizeof(*ckp_args));
+
+ /*
+ * Keep looping until either you 1) run out of checkpoints,
+ * 2) you've found a checkpoint before the most recent
+ * checkpoint's LSN and you have at least 2 checkpoints.
+ */
+ } while (!IS_ZERO_LSN(next_lsn) &&
+ (log_compare(&last_ckp, &ckp_lsn) > 0 ||
+ log_compare(&final_ckp, &last_ckp) == 0));
+
+ if (F_ISSET(lp, DB_AM_THREAD))
+ __os_free(data.data, data.size);
+
+ /*
+ * At this point, either, next_lsn is ZERO or ckp_lsn is the
+ * checkpoint lsn and last_ckp is the LSN of the last checkpoint
+ * before ckp_lsn. If the compare in the loop is still true, then
+ * next_lsn must be 0 and we need to roll forward from the
+ * beginning of the log.
+ */
+ if (log_compare(&last_ckp, &ckp_lsn) > 0 ||
+ log_compare(&final_ckp, &last_ckp) == 0) {
+get_first: if ((ret = log_get(lp, &last_ckp, &data, DB_FIRST)) != 0)
+ return (ret);
+ if (F_ISSET(lp, DB_AM_THREAD))
+ __os_free(data.data, data.size);
+ }
+ *lsnp = last_ckp;
+
+ return (IS_ZERO_LSN(last_ckp) ? DB_NOTFOUND : 0);
+}
diff --git a/usr/src/cmd/sendmail/db/log/log_get.c b/usr/src/cmd/sendmail/db/log/log_get.c
new file mode 100644
index 0000000000..e84474cbf8
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/log/log_get.c
@@ -0,0 +1,329 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)log_get.c 10.38 (Sleepycat) 10/3/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#endif
+
+#include "db_int.h"
+#include "shqueue.h"
+#include "db_page.h"
+#include "log.h"
+#include "hash.h"
+#include "common_ext.h"
+
+/*
+ * log_get --
+ * Get a log record.
+ */
+int
+log_get(dblp, alsn, dbt, flags)
+ DB_LOG *dblp;
+ DB_LSN *alsn;
+ DBT *dbt;
+ u_int32_t flags;
+{
+ int ret;
+
+ LOG_PANIC_CHECK(dblp);
+
+ /* Validate arguments. */
+ if (flags != DB_CHECKPOINT && flags != DB_CURRENT &&
+ flags != DB_FIRST && flags != DB_LAST &&
+ flags != DB_NEXT && flags != DB_PREV && flags != DB_SET)
+ return (__db_ferr(dblp->dbenv, "log_get", 1));
+
+ if (F_ISSET(dblp, DB_AM_THREAD)) {
+ if (flags == DB_NEXT || flags == DB_PREV || flags == DB_CURRENT)
+ return (__db_ferr(dblp->dbenv, "log_get", 1));
+ if (!F_ISSET(dbt, DB_DBT_USERMEM | DB_DBT_MALLOC))
+ return (__db_ferr(dblp->dbenv, "threaded data", 1));
+ }
+
+ LOCK_LOGREGION(dblp);
+
+ /*
+ * If we get one of the log's header records, repeat the operation.
+ * This assumes that applications don't ever request the log header
+ * records by LSN, but that seems reasonable to me.
+ */
+ ret = __log_get(dblp, alsn, dbt, flags, 0);
+ if (ret == 0 && alsn->offset == 0) {
+ switch (flags) {
+ case DB_FIRST:
+ flags = DB_NEXT;
+ break;
+ case DB_LAST:
+ flags = DB_PREV;
+ break;
+ }
+ ret = __log_get(dblp, alsn, dbt, flags, 0);
+ }
+
+ UNLOCK_LOGREGION(dblp);
+
+ return (ret);
+}
+
+/*
+ * __log_get --
+ * Get a log record; internal version.
+ *
+ * PUBLIC: int __log_get __P((DB_LOG *, DB_LSN *, DBT *, u_int32_t, int));
+ */
+int
+__log_get(dblp, alsn, dbt, flags, silent)
+ DB_LOG *dblp;
+ DB_LSN *alsn;
+ DBT *dbt;
+ u_int32_t flags;
+ int silent;
+{
+ DB_LSN nlsn;
+ HDR hdr;
+ LOG *lp;
+ size_t len;
+ ssize_t nr;
+ int cnt, ret;
+ char *np, *tbuf;
+ const char *fail;
+ void *p, *shortp;
+
+ lp = dblp->lp;
+ fail = np = tbuf = NULL;
+
+ nlsn = dblp->c_lsn;
+ switch (flags) {
+ case DB_CHECKPOINT:
+ nlsn = lp->chkpt_lsn;
+ if (IS_ZERO_LSN(nlsn)) {
+ __db_err(dblp->dbenv,
+ "log_get: unable to find checkpoint record: no checkpoint set.");
+ ret = ENOENT;
+ goto err2;
+ }
+ break;
+ case DB_NEXT: /* Next log record. */
+ if (!IS_ZERO_LSN(nlsn)) {
+ /* Increment the cursor by the cursor record size. */
+ nlsn.offset += dblp->c_len;
+ break;
+ }
+ /* FALLTHROUGH */
+ case DB_FIRST: /* Find the first log record. */
+ /* Find the first log file. */
+ if ((ret = __log_find(dblp, 1, &cnt)) != 0)
+ goto err2;
+
+ /*
+ * We may have only entered records in the buffer, and not
+ * yet written a log file. If no log files were found and
+ * there's anything in the buffer, it belongs to file 1.
+ */
+ if (cnt == 0)
+ cnt = 1;
+
+ nlsn.file = cnt;
+ nlsn.offset = 0;
+ break;
+ case DB_CURRENT: /* Current log record. */
+ break;
+ case DB_PREV: /* Previous log record. */
+ if (!IS_ZERO_LSN(nlsn)) {
+ /* If at start-of-file, move to the previous file. */
+ if (nlsn.offset == 0) {
+ if (nlsn.file == 1 ||
+ __log_valid(dblp, nlsn.file - 1, 0) != 0)
+ return (DB_NOTFOUND);
+
+ --nlsn.file;
+ nlsn.offset = dblp->c_off;
+ } else
+ nlsn.offset = dblp->c_off;
+ break;
+ }
+ /* FALLTHROUGH */
+ case DB_LAST: /* Last log record. */
+ nlsn.file = lp->lsn.file;
+ nlsn.offset = lp->lsn.offset - lp->len;
+ break;
+ case DB_SET: /* Set log record. */
+ nlsn = *alsn;
+ break;
+ }
+
+retry:
+ /* Return 1 if the request is past end-of-file. */
+ if (nlsn.file > lp->lsn.file ||
+ (nlsn.file == lp->lsn.file && nlsn.offset >= lp->lsn.offset))
+ return (DB_NOTFOUND);
+
+ /* If we've switched files, discard the current fd. */
+ if (dblp->c_lsn.file != nlsn.file && dblp->c_fd != -1) {
+ (void)__os_close(dblp->c_fd);
+ dblp->c_fd = -1;
+ }
+
+ /* If the entire record is in the in-memory buffer, copy it out. */
+ if (nlsn.file == lp->lsn.file && nlsn.offset >= lp->w_off) {
+ /* Copy the header. */
+ p = lp->buf + (nlsn.offset - lp->w_off);
+ memcpy(&hdr, p, sizeof(HDR));
+
+ /* Copy the record. */
+ len = hdr.len - sizeof(HDR);
+ if ((ret = __db_retcopy(dbt, (u_int8_t *)p + sizeof(HDR),
+ len, &dblp->c_dbt.data, &dblp->c_dbt.ulen, NULL)) != 0)
+ goto err1;
+ goto cksum;
+ }
+
+ /* Acquire a file descriptor. */
+ if (dblp->c_fd == -1) {
+ if ((ret = __log_name(dblp, nlsn.file,
+ &np, &dblp->c_fd, DB_RDONLY | DB_SEQUENTIAL)) != 0) {
+ fail = np;
+ goto err1;
+ }
+ __os_freestr(np);
+ np = NULL;
+ }
+
+ /* Seek to the header offset and read the header. */
+ if ((ret =
+ __os_seek(dblp->c_fd, 0, 0, nlsn.offset, 0, SEEK_SET)) != 0) {
+ fail = "seek";
+ goto err1;
+ }
+ if ((ret = __os_read(dblp->c_fd, &hdr, sizeof(HDR), &nr)) != 0) {
+ fail = "read";
+ goto err1;
+ }
+ if (nr == sizeof(HDR))
+ shortp = NULL;
+ else {
+ /* If read returns EOF, try the next file. */
+ if (nr == 0) {
+ if (flags != DB_NEXT || nlsn.file == lp->lsn.file)
+ goto corrupt;
+
+ /* Move to the next file. */
+ ++nlsn.file;
+ nlsn.offset = 0;
+ goto retry;
+ }
+
+ /*
+ * If read returns a short count the rest of the record has
+ * to be in the in-memory buffer.
+ */
+ if (lp->b_off < sizeof(HDR) - nr)
+ goto corrupt;
+
+ /* Get the rest of the header from the in-memory buffer. */
+ memcpy((u_int8_t *)&hdr + nr, lp->buf, sizeof(HDR) - nr);
+ shortp = lp->buf + (sizeof(HDR) - nr);
+ }
+
+ /*
+ * Check for buffers of 0's, that's what we usually see during
+ * recovery, although it's certainly not something on which we
+ * can depend.
+ */
+ if (hdr.len <= sizeof(HDR))
+ goto corrupt;
+ len = hdr.len - sizeof(HDR);
+
+ /* If we've already moved to the in-memory buffer, fill from there. */
+ if (shortp != NULL) {
+ if (lp->b_off < ((u_int8_t *)shortp - lp->buf) + len)
+ goto corrupt;
+ if ((ret = __db_retcopy(dbt, shortp, len,
+ &dblp->c_dbt.data, &dblp->c_dbt.ulen, NULL)) != 0)
+ goto err1;
+ goto cksum;
+ }
+
+ /*
+ * Allocate temporary memory to hold the record.
+ *
+ * XXX
+ * We're calling malloc(3) with a region locked. This isn't
+ * a good idea.
+ */
+ if ((ret = __os_malloc(len, NULL, &tbuf)) != 0)
+ goto err1;
+
+ /*
+ * Read the record into the buffer. If read returns a short count,
+ * there was an error or the rest of the record is in the in-memory
+ * buffer. Note, the information may be garbage if we're in recovery,
+ * so don't read past the end of the buffer's memory.
+ */
+ if ((ret = __os_read(dblp->c_fd, tbuf, len, &nr)) != 0) {
+ fail = "read";
+ goto err1;
+ }
+ if (len - nr > sizeof(lp->buf))
+ goto corrupt;
+ if (nr != (ssize_t)len) {
+ if (lp->b_off < len - nr)
+ goto corrupt;
+
+ /* Get the rest of the record from the in-memory buffer. */
+ memcpy((u_int8_t *)tbuf + nr, lp->buf, len - nr);
+ }
+
+ /* Copy the record into the user's DBT. */
+ if ((ret = __db_retcopy(dbt, tbuf, len,
+ &dblp->c_dbt.data, &dblp->c_dbt.ulen, NULL)) != 0)
+ goto err1;
+ __os_free(tbuf, 0);
+ tbuf = NULL;
+
+cksum: if (hdr.cksum != __ham_func4(dbt->data, dbt->size)) {
+ if (!silent)
+ __db_err(dblp->dbenv, "log_get: checksum mismatch");
+ goto corrupt;
+ }
+
+ /* Update the cursor and the return lsn. */
+ dblp->c_off = hdr.prev;
+ dblp->c_len = hdr.len;
+ dblp->c_lsn = *alsn = nlsn;
+
+ return (0);
+
+corrupt:/*
+ * This is the catchall -- for some reason we didn't find enough
+ * information or it wasn't reasonable information, and it wasn't
+ * because a system call failed.
+ */
+ ret = EIO;
+ fail = "read";
+
+err1: if (!silent)
+ if (fail == NULL)
+ __db_err(dblp->dbenv, "log_get: %s", strerror(ret));
+ else
+ __db_err(dblp->dbenv,
+ "log_get: %s: %s", fail, strerror(ret));
+err2: if (np != NULL)
+ __os_freestr(np);
+ if (tbuf != NULL)
+ __os_free(tbuf, 0);
+ return (ret);
+}
diff --git a/usr/src/cmd/sendmail/db/log/log_put.c b/usr/src/cmd/sendmail/db/log/log_put.c
new file mode 100644
index 0000000000..8121d0158a
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/log/log_put.c
@@ -0,0 +1,602 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)log_put.c 10.44 (Sleepycat) 11/3/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#endif
+
+#include "db_int.h"
+#include "shqueue.h"
+#include "db_page.h"
+#include "log.h"
+#include "hash.h"
+#include "clib_ext.h"
+#include "common_ext.h"
+
+static int __log_fill __P((DB_LOG *, DB_LSN *, void *, u_int32_t));
+static int __log_flush __P((DB_LOG *, const DB_LSN *));
+static int __log_newfd __P((DB_LOG *));
+static int __log_putr __P((DB_LOG *, DB_LSN *, const DBT *, u_int32_t));
+static int __log_write __P((DB_LOG *, void *, u_int32_t));
+
+/*
+ * log_put --
+ * Write a log record.
+ */
+int
+log_put(dblp, lsn, dbt, flags)
+ DB_LOG *dblp;
+ DB_LSN *lsn;
+ const DBT *dbt;
+ u_int32_t flags;
+{
+ int ret;
+
+ LOG_PANIC_CHECK(dblp);
+
+ /* Validate arguments. */
+ if (flags != 0 && flags != DB_CHECKPOINT &&
+ flags != DB_CURLSN && flags != DB_FLUSH)
+ return (__db_ferr(dblp->dbenv, "log_put", 0));
+
+ LOCK_LOGREGION(dblp);
+ ret = __log_put(dblp, lsn, dbt, flags);
+ UNLOCK_LOGREGION(dblp);
+ return (ret);
+}
+
+/*
+ * __log_put --
+ * Write a log record; internal version.
+ *
+ * PUBLIC: int __log_put __P((DB_LOG *, DB_LSN *, const DBT *, u_int32_t));
+ */
+int
+__log_put(dblp, lsn, dbt, flags)
+ DB_LOG *dblp;
+ DB_LSN *lsn;
+ const DBT *dbt;
+ u_int32_t flags;
+{
+ DBT fid_dbt, t;
+ DB_LSN r_unused;
+ FNAME *fnp;
+ LOG *lp;
+ u_int32_t lastoff;
+ int ret;
+
+ lp = dblp->lp;
+
+ /*
+ * If the application just wants to know where we are, fill in
+ * the information. Currently used by the transaction manager
+ * to avoid writing TXN_begin records.
+ */
+ if (flags == DB_CURLSN) {
+ lsn->file = lp->lsn.file;
+ lsn->offset = lp->lsn.offset;
+ return (0);
+ }
+
+ /* If this information won't fit in the file, swap files. */
+ if (lp->lsn.offset + sizeof(HDR) + dbt->size > lp->persist.lg_max) {
+ if (sizeof(HDR) +
+ sizeof(LOGP) + dbt->size > lp->persist.lg_max) {
+ __db_err(dblp->dbenv,
+ "log_put: record larger than maximum file size");
+ return (EINVAL);
+ }
+
+ /* Flush the log. */
+ if ((ret = __log_flush(dblp, NULL)) != 0)
+ return (ret);
+
+ /*
+ * Save the last known offset from the previous file, we'll
+ * need it to initialize the persistent header information.
+ */
+ lastoff = lp->lsn.offset;
+
+ /* Point the current LSN to the new file. */
+ ++lp->lsn.file;
+ lp->lsn.offset = 0;
+
+ /* Reset the file write offset. */
+ lp->w_off = 0;
+ } else
+ lastoff = 0;
+
+ /* Initialize the LSN information returned to the user. */
+ lsn->file = lp->lsn.file;
+ lsn->offset = lp->lsn.offset;
+
+ /*
+ * Insert persistent information as the first record in every file.
+ * Note that the previous length is wrong for the very first record
+ * of the log, but that's okay, we check for it during retrieval.
+ */
+ if (lp->lsn.offset == 0) {
+ t.data = &lp->persist;
+ t.size = sizeof(LOGP);
+ if ((ret = __log_putr(dblp, lsn,
+ &t, lastoff == 0 ? 0 : lastoff - lp->len)) != 0)
+ return (ret);
+
+ /* Update the LSN information returned to the user. */
+ lsn->file = lp->lsn.file;
+ lsn->offset = lp->lsn.offset;
+ }
+
+ /* Write the application's log record. */
+ if ((ret = __log_putr(dblp, lsn, dbt, lp->lsn.offset - lp->len)) != 0)
+ return (ret);
+
+ /*
+ * On a checkpoint, we:
+ * Put out the checkpoint record (above).
+ * Save the LSN of the checkpoint in the shared region.
+ * Append the set of file name information into the log.
+ */
+ if (flags == DB_CHECKPOINT) {
+ lp->chkpt_lsn = *lsn;
+
+ for (fnp = SH_TAILQ_FIRST(&dblp->lp->fq, __fname);
+ fnp != NULL; fnp = SH_TAILQ_NEXT(fnp, q, __fname)) {
+ if (fnp->ref == 0) /* Entry not in use. */
+ continue;
+ memset(&t, 0, sizeof(t));
+ t.data = R_ADDR(dblp, fnp->name_off);
+ t.size = strlen(t.data) + 1;
+ memset(&fid_dbt, 0, sizeof(fid_dbt));
+ fid_dbt.data = fnp->ufid;
+ fid_dbt.size = DB_FILE_ID_LEN;
+ if ((ret = __log_register_log(dblp, NULL, &r_unused, 0,
+ LOG_CHECKPOINT, &t, &fid_dbt, fnp->id, fnp->s_type))
+ != 0)
+ return (ret);
+ }
+ }
+
+ /*
+ * On a checkpoint or when flush is requested, we:
+ * Flush the current buffer contents to disk.
+ * Sync the log to disk.
+ */
+ if (flags == DB_FLUSH || flags == DB_CHECKPOINT)
+ if ((ret = __log_flush(dblp, NULL)) != 0)
+ return (ret);
+
+ /*
+ * On a checkpoint, we:
+ * Save the time the checkpoint was written.
+ * Reset the bytes written since the last checkpoint.
+ */
+ if (flags == DB_CHECKPOINT) {
+ (void)time(&lp->chkpt);
+ lp->stat.st_wc_bytes = lp->stat.st_wc_mbytes = 0;
+ }
+ return (0);
+}
+
+/*
+ * __log_putr --
+ * Actually put a record into the log.
+ */
+static int
+__log_putr(dblp, lsn, dbt, prev)
+ DB_LOG *dblp;
+ DB_LSN *lsn;
+ const DBT *dbt;
+ u_int32_t prev;
+{
+ HDR hdr;
+ LOG *lp;
+ int ret;
+
+ lp = dblp->lp;
+
+ /*
+ * Initialize the header. If we just switched files, lsn.offset will
+ * be 0, and what we really want is the offset of the previous record
+ * in the previous file. Fortunately, prev holds the value we want.
+ */
+ hdr.prev = prev;
+ hdr.len = sizeof(HDR) + dbt->size;
+ hdr.cksum = __ham_func4(dbt->data, dbt->size);
+
+ if ((ret = __log_fill(dblp, lsn, &hdr, sizeof(HDR))) != 0)
+ return (ret);
+ lp->len = sizeof(HDR);
+ lp->lsn.offset += sizeof(HDR);
+
+ if ((ret = __log_fill(dblp, lsn, dbt->data, dbt->size)) != 0)
+ return (ret);
+ lp->len += dbt->size;
+ lp->lsn.offset += dbt->size;
+ return (0);
+}
+
+/*
+ * log_flush --
+ * Write all records less than or equal to the specified LSN.
+ */
+int
+log_flush(dblp, lsn)
+ DB_LOG *dblp;
+ const DB_LSN *lsn;
+{
+ int ret;
+
+ LOG_PANIC_CHECK(dblp);
+
+ LOCK_LOGREGION(dblp);
+ ret = __log_flush(dblp, lsn);
+ UNLOCK_LOGREGION(dblp);
+ return (ret);
+}
+
+/*
+ * __log_flush --
+ * Write all records less than or equal to the specified LSN; internal
+ * version.
+ */
+static int
+__log_flush(dblp, lsn)
+ DB_LOG *dblp;
+ const DB_LSN *lsn;
+{
+ DB_LSN t_lsn;
+ LOG *lp;
+ int current, ret;
+
+ ret = 0;
+ lp = dblp->lp;
+
+ /*
+ * If no LSN specified, flush the entire log by setting the flush LSN
+ * to the last LSN written in the log. Otherwise, check that the LSN
+ * isn't a non-existent record for the log.
+ */
+ if (lsn == NULL) {
+ t_lsn.file = lp->lsn.file;
+ t_lsn.offset = lp->lsn.offset - lp->len;
+ lsn = &t_lsn;
+ } else
+ if (lsn->file > lp->lsn.file ||
+ (lsn->file == lp->lsn.file &&
+ lsn->offset > lp->lsn.offset - lp->len)) {
+ __db_err(dblp->dbenv,
+ "log_flush: LSN past current end-of-log");
+ return (EINVAL);
+ }
+
+ /*
+ * If the LSN is less than the last-sync'd LSN, we're done. Note,
+ * the last-sync LSN saved in s_lsn is the LSN of the first byte
+ * we absolutely know has been written to disk, so the test is <=.
+ */
+ if (lsn->file < lp->s_lsn.file ||
+ (lsn->file == lp->s_lsn.file && lsn->offset <= lp->s_lsn.offset))
+ return (0);
+
+ /*
+ * We may need to write the current buffer. We have to write the
+ * current buffer if the flush LSN is greater than or equal to the
+ * buffer's starting LSN.
+ */
+ current = 0;
+ if (lp->b_off != 0 && log_compare(lsn, &lp->f_lsn) >= 0) {
+ if ((ret = __log_write(dblp, lp->buf, lp->b_off)) != 0)
+ return (ret);
+
+ lp->b_off = 0;
+ current = 1;
+ }
+
+ /*
+ * It's possible that this thread may never have written to this log
+ * file. Acquire a file descriptor if we don't already have one.
+ */
+ if (dblp->lfname != dblp->lp->lsn.file)
+ if ((ret = __log_newfd(dblp)) != 0)
+ return (ret);
+
+ /* Sync all writes to disk. */
+ if ((ret = __os_fsync(dblp->lfd)) != 0) {
+ __db_panic(dblp->dbenv, ret);
+ return (ret);
+ }
+ ++lp->stat.st_scount;
+
+ /*
+ * Set the last-synced LSN, using the LSN of the current buffer. If
+ * the current buffer was flushed, we know the LSN of the first byte
+ * of the buffer is on disk, otherwise, we only know that the LSN of
+ * the record before the one beginning the current buffer is on disk.
+ *
+ * XXX
+ * Check to make sure that the saved lsn isn't 0 before we go making
+ * this change. If DB_CHECKPOINT was called before we actually wrote
+ * something, you can end up here without ever having written anything
+ * to a log file, and decrementing either s_lsn.file or s_lsn.offset
+ * will cause much sadness later on.
+ */
+ lp->s_lsn = lp->f_lsn;
+ if (!current && lp->s_lsn.file != 0)
+ if (lp->s_lsn.offset == 0) {
+ --lp->s_lsn.file;
+ lp->s_lsn.offset = lp->persist.lg_max;
+ } else
+ --lp->s_lsn.offset;
+
+ return (0);
+}
+
+/*
+ * __log_fill --
+ * Write information into the log.
+ */
+static int
+__log_fill(dblp, lsn, addr, len)
+ DB_LOG *dblp;
+ DB_LSN *lsn;
+ void *addr;
+ u_int32_t len;
+{
+ LOG *lp;
+ u_int32_t nrec;
+ size_t nw, remain;
+ int ret;
+
+ /* Copy out the data. */
+ for (lp = dblp->lp; len > 0;) {
+ /*
+ * If we're beginning a new buffer, note the user LSN to which
+ * the first byte of the buffer belongs. We have to know this
+ * when flushing the buffer so that we know if the in-memory
+ * buffer needs to be flushed.
+ */
+ if (lp->b_off == 0)
+ lp->f_lsn = *lsn;
+
+ /*
+ * If we're on a buffer boundary and the data is big enough,
+ * copy as many records as we can directly from the data.
+ */
+ if (lp->b_off == 0 && len >= sizeof(lp->buf)) {
+ nrec = len / sizeof(lp->buf);
+ if ((ret = __log_write(dblp,
+ addr, nrec * sizeof(lp->buf))) != 0)
+ return (ret);
+ addr = (u_int8_t *)addr + nrec * sizeof(lp->buf);
+ len -= nrec * sizeof(lp->buf);
+ continue;
+ }
+
+ /* Figure out how many bytes we can copy this time. */
+ remain = sizeof(lp->buf) - lp->b_off;
+ nw = remain > len ? len : remain;
+ memcpy(lp->buf + lp->b_off, addr, nw);
+ addr = (u_int8_t *)addr + nw;
+ len -= nw;
+ lp->b_off += nw;
+
+ /* If we fill the buffer, flush it. */
+ if (lp->b_off == sizeof(lp->buf)) {
+ if ((ret =
+ __log_write(dblp, lp->buf, sizeof(lp->buf))) != 0)
+ return (ret);
+ lp->b_off = 0;
+ }
+ }
+ return (0);
+}
+
+/*
+ * __log_write --
+ * Write the log buffer to disk.
+ */
+static int
+__log_write(dblp, addr, len)
+ DB_LOG *dblp;
+ void *addr;
+ u_int32_t len;
+{
+ LOG *lp;
+ ssize_t nw;
+ int ret;
+
+ /*
+ * If we haven't opened the log file yet or the current one
+ * has changed, acquire a new log file.
+ */
+ lp = dblp->lp;
+ if (dblp->lfd == -1 || dblp->lfname != lp->lsn.file)
+ if ((ret = __log_newfd(dblp)) != 0)
+ return (ret);
+
+ /*
+ * Seek to the offset in the file (someone may have written it
+ * since we last did).
+ */
+ if ((ret = __os_seek(dblp->lfd, 0, 0, lp->w_off, 0, SEEK_SET)) != 0 ||
+ (ret = __os_write(dblp->lfd, addr, len, &nw)) != 0) {
+ __db_panic(dblp->dbenv, ret);
+ return (ret);
+ }
+ if (nw != (int32_t)len)
+ return (EIO);
+
+ /* Reset the buffer offset and update the seek offset. */
+ lp->w_off += len;
+
+ /* Update written statistics. */
+ if ((lp->stat.st_w_bytes += len) >= MEGABYTE) {
+ lp->stat.st_w_bytes -= MEGABYTE;
+ ++lp->stat.st_w_mbytes;
+ }
+ if ((lp->stat.st_wc_bytes += len) >= MEGABYTE) {
+ lp->stat.st_wc_bytes -= MEGABYTE;
+ ++lp->stat.st_wc_mbytes;
+ }
+ ++lp->stat.st_wcount;
+
+ return (0);
+}
+
+/*
+ * log_file --
+ * Map a DB_LSN to a file name.
+ */
+int
+log_file(dblp, lsn, namep, len)
+ DB_LOG *dblp;
+ const DB_LSN *lsn;
+ char *namep;
+ size_t len;
+{
+ int ret;
+ char *name;
+
+ LOG_PANIC_CHECK(dblp);
+
+ LOCK_LOGREGION(dblp);
+ ret = __log_name(dblp, lsn->file, &name, NULL, 0);
+ UNLOCK_LOGREGION(dblp);
+ if (ret != 0)
+ return (ret);
+
+ /* Check to make sure there's enough room and copy the name. */
+ if (len < strlen(name) + 1) {
+ *namep = '\0';
+ return (ENOMEM);
+ }
+ (void)strcpy(namep, name);
+ __os_freestr(name);
+
+ return (0);
+}
+
+/*
+ * __log_newfd --
+ * Acquire a file descriptor for the current log file.
+ */
+static int
+__log_newfd(dblp)
+ DB_LOG *dblp;
+{
+ int ret;
+ char *name;
+
+ /* Close any previous file descriptor. */
+ if (dblp->lfd != -1) {
+ (void)__os_close(dblp->lfd);
+ dblp->lfd = -1;
+ }
+
+ /* Get the path of the new file and open it. */
+ dblp->lfname = dblp->lp->lsn.file;
+ if ((ret = __log_name(dblp,
+ dblp->lfname, &name, &dblp->lfd, DB_CREATE | DB_SEQUENTIAL)) != 0)
+ __db_err(dblp->dbenv, "log_put: %s: %s", name, strerror(ret));
+
+ __os_freestr(name);
+ return (ret);
+}
+
+/*
+ * __log_name --
+ * Return the log name for a particular file, and optionally open it.
+ *
+ * PUBLIC: int __log_name __P((DB_LOG *, u_int32_t, char **, int *, u_int32_t));
+ */
+int
+__log_name(dblp, filenumber, namep, fdp, flags)
+ DB_LOG *dblp;
+ u_int32_t filenumber, flags;
+ char **namep;
+ int *fdp;
+{
+ int ret;
+ char *oname;
+ char old[sizeof(LFPREFIX) + 5 + 20], new[sizeof(LFPREFIX) + 10 + 20];
+
+ /*
+ * !!!
+ * The semantics of this routine are bizarre.
+ *
+ * The reason for all of this is that we need a place where we can
+ * intercept requests for log files, and, if appropriate, check for
+ * both the old-style and new-style log file names. The trick is
+ * that all callers of this routine that are opening the log file
+ * read-only want to use an old-style file name if they can't find
+ * a match using a new-style name. The only down-side is that some
+ * callers may check for the old-style when they really don't need
+ * to, but that shouldn't mess up anything, and we only check for
+ * the old-style name when we've already failed to find a new-style
+ * one.
+ *
+ * Create a new-style file name, and if we're not going to open the
+ * file, return regardless.
+ */
+ (void)snprintf(new, sizeof(new), LFNAME, filenumber);
+ if ((ret = __db_appname(dblp->dbenv,
+ DB_APP_LOG, dblp->dir, new, 0, NULL, namep)) != 0 || fdp == NULL)
+ return (ret);
+
+ /* Open the new-style file -- if we succeed, we're done. */
+ if ((ret = __db_open(*namep,
+ flags, flags, dblp->lp->persist.mode, fdp)) == 0)
+ return (0);
+
+ /*
+ * The open failed... if the DB_RDONLY flag isn't set, we're done,
+ * the caller isn't interested in old-style files.
+ */
+ if (!LF_ISSET(DB_RDONLY))
+ return (ret);
+
+ /* Create an old-style file name. */
+ (void)snprintf(old, sizeof(old), LFNAME_V1, filenumber);
+ if ((ret = __db_appname(dblp->dbenv,
+ DB_APP_LOG, dblp->dir, old, 0, NULL, &oname)) != 0)
+ goto err;
+
+ /*
+ * Open the old-style file -- if we succeed, we're done. Free the
+ * space allocated for the new-style name and return the old-style
+ * name to the caller.
+ */
+ if ((ret = __db_open(oname,
+ flags, flags, dblp->lp->persist.mode, fdp)) == 0) {
+ __os_freestr(*namep);
+ *namep = oname;
+ return (0);
+ }
+
+ /*
+ * Couldn't find either style of name -- return the new-style name
+ * for the caller's error message. If it's an old-style name that's
+ * actually missing we're going to confuse the user with the error
+ * message, but that implies that not only were we looking for an
+ * old-style name, but we expected it to exist and we weren't just
+ * looking for any log file. That's not a likely error.
+ */
+err: __os_freestr(oname);
+ return (ret);
+}
diff --git a/usr/src/cmd/sendmail/db/log/log_rec.c b/usr/src/cmd/sendmail/db/log/log_rec.c
new file mode 100644
index 0000000000..447d520d0b
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/log/log_rec.c
@@ -0,0 +1,446 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+/*
+ * Copyright (c) 1995, 1996
+ * The President and Fellows of Harvard University. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)log_rec.c 10.26 (Sleepycat) 10/21/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <errno.h>
+#include <string.h>
+#endif
+
+#include "db_int.h"
+#include "shqueue.h"
+#include "log.h"
+#include "db_dispatch.h"
+#include "common_ext.h"
+
+static int __log_do_open __P((DB_LOG *,
+ u_int8_t *, char *, DBTYPE, u_int32_t));
+static int __log_lid_to_fname __P((DB_LOG *, u_int32_t, FNAME **));
+static int __log_open_file __P((DB_LOG *, __log_register_args *));
+
+/*
+ * PUBLIC: int __log_register_recover
+ * PUBLIC: __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+ */
+int
+__log_register_recover(logp, dbtp, lsnp, redo, info)
+ DB_LOG *logp;
+ DBT *dbtp;
+ DB_LSN *lsnp;
+ int redo;
+ void *info;
+{
+ DB_ENTRY *dbe;
+ __log_register_args *argp;
+ int ret;
+
+#ifdef DEBUG_RECOVER
+ __log_register_print(logp, dbtp, lsnp, redo, info);
+#endif
+ COMPQUIET(info, NULL);
+ COMPQUIET(lsnp, NULL);
+
+ F_SET(logp, DBC_RECOVER);
+
+ if ((ret = __log_register_read(dbtp->data, &argp)) != 0)
+ goto out;
+
+ if ((argp->opcode == LOG_CHECKPOINT && redo == TXN_OPENFILES) ||
+ (argp->opcode == LOG_OPEN &&
+ (redo == TXN_REDO || redo == TXN_OPENFILES ||
+ redo == TXN_FORWARD_ROLL)) ||
+ (argp->opcode == LOG_CLOSE &&
+ (redo == TXN_UNDO || redo == TXN_BACKWARD_ROLL))) {
+ /*
+ * If we are redoing an open or undoing a close, then we need
+ * to open a file.
+ */
+ ret = __log_open_file(logp, argp);
+ if (ret == ENOENT) {
+ if (redo == TXN_OPENFILES)
+ __db_err(logp->dbenv, "warning: %s: %s",
+ argp->name.data, strerror(ENOENT));
+ ret = 0;
+ }
+ } else if (argp->opcode != LOG_CHECKPOINT &&
+ argp->opcode != LOG_CLOSE) {
+ /*
+ * If we are undoing an open, then we need to close the file.
+ * Note that we do *not* close the file if we are redoing a
+ * close, because we do not log the reference counts on log
+ * files and we may have had the file open multiple times,
+ * and therefore, this close should just dec a reference
+ * count. However, since we only do one open during a
+ * checkpoint, this will inadvertently close the file.
+ *
+ * If the file is deleted, then we can just ignore this close.
+ * Otherwise, we should usually have a valid dbp we should
+ * close or whose reference count should be decremented.
+ * However, if we shut down without closing a file, we
+ * may, in fact, not have the file open, and that's OK.
+ */
+ LOCK_LOGTHREAD(logp);
+ if (argp->id < logp->dbentry_cnt) {
+ dbe = &logp->dbentry[argp->id];
+ if (dbe->dbp != NULL && --dbe->refcount == 0) {
+ ret = dbe->dbp->close(dbe->dbp, 0);
+ if (dbe->name != NULL) {
+ __os_freestr(dbe->name);
+ dbe->name = NULL;
+ }
+ (void)__log_rem_logid(logp, argp->id);
+ }
+ }
+ UNLOCK_LOGTHREAD(logp);
+ } else if (argp->opcode == LOG_CHECKPOINT && redo == TXN_UNDO &&
+ (argp->id >= logp->dbentry_cnt ||
+ (!logp->dbentry[argp->id].deleted &&
+ logp->dbentry[argp->id].dbp == NULL))) {
+ /*
+ * It's a checkpoint and we are rolling backward. It
+ * is possible that the system was shut down and thus
+ * ended with a stable checkpoint; this file was never
+ * closed and has therefore not been reopened yet. If
+ * so, we need to try to open it.
+ */
+ ret = __log_open_file(logp, argp);
+ if (ret == ENOENT) {
+ __db_err(logp->dbenv, "warning: %s: %s",
+ argp->name.data, strerror(ENOENT));
+ ret = 0;
+ }
+ }
+
+out: F_CLR(logp, DBC_RECOVER);
+ if (argp != NULL)
+ __os_free(argp, 0);
+ return (ret);
+}
+
+/* Hand coded routines. */
+
+/*
+ * Called during log_register recovery. Make sure that we have an
+ * entry in the dbentry table for this ndx.
+ * Returns 0 on success, non-zero on error.
+ */
+static int
+__log_open_file(lp, argp)
+ DB_LOG *lp;
+ __log_register_args *argp;
+{
+ DB_ENTRY *dbe;
+
+ if (argp->name.size == 0)
+ return(0);
+
+ /*
+ * Because of reference counting, we cannot automatically close files
+ * during recovery, so when we're opening, we have to check that the
+ * name we are opening is what we expect. If it's not, then we close
+ * the old file and open the new one.
+ */
+ LOCK_LOGTHREAD(lp);
+ if (argp->id < lp->dbentry_cnt)
+ dbe = &lp->dbentry[argp->id];
+ else
+ dbe = NULL;
+
+ if (dbe != NULL && (dbe->deleted == 1 || dbe->dbp != NULL) &&
+ dbe->name != NULL && argp->name.data != NULL &&
+ strncmp(argp->name.data, dbe->name, argp->name.size) == 0) {
+
+ dbe->refcount++;
+ UNLOCK_LOGTHREAD(lp);
+ return (0);
+ }
+ UNLOCK_LOGTHREAD(lp);
+
+ if (dbe != NULL && dbe->dbp != NULL) {
+ (void)dbe->dbp->close(dbe->dbp, 0);
+ if (dbe->name != NULL)
+ __os_freestr(dbe->name);
+ dbe->name = NULL;
+ (void)__log_rem_logid(lp, argp->id);
+ }
+
+
+ return (__log_do_open(lp,
+ argp->uid.data, argp->name.data, argp->ftype, argp->id));
+}
+
+/*
+ * __log_do_open --
+ * Open files referenced in the log. This is the part of the open that
+ * is not protected by the thread mutex.
+ */
+
+static int
+__log_do_open(lp, uid, name, ftype, ndx)
+ DB_LOG *lp;
+ u_int8_t *uid;
+ char *name;
+ DBTYPE ftype;
+ u_int32_t ndx;
+{
+ DB *dbp;
+ int ret;
+
+ dbp = NULL;
+ if ((ret = db_open(name, ftype, 0, 0, lp->dbenv, NULL, &dbp)) == 0) {
+ /*
+ * Verify that we are opening the same file that we were
+ * referring to when we wrote this log record.
+ */
+ if (memcmp(uid, dbp->fileid, DB_FILE_ID_LEN) != 0) {
+ (void)dbp->close(dbp, 0);
+ dbp = NULL;
+ ret = ENOENT;
+ }
+ }
+
+ if (ret == 0 || ret == ENOENT)
+ (void)__log_add_logid(lp, dbp, name, ndx);
+
+ return (ret);
+}
+
+/*
+ * __log_add_logid --
+ * Adds a DB entry to the log's DB entry table.
+ *
+ * PUBLIC: int __log_add_logid __P((DB_LOG *, DB *, const char *, u_int32_t));
+ */
+int
+__log_add_logid(logp, dbp, name, ndx)
+ DB_LOG *logp;
+ DB *dbp;
+ const char *name;
+ u_int32_t ndx;
+{
+ u_int32_t i;
+ int ret;
+
+ ret = 0;
+
+ LOCK_LOGTHREAD(logp);
+
+ /*
+ * Check if we need to grow the table. Note, ndx is 0-based (the
+ * index into the DB entry table) an dbentry_cnt is 1-based, the
+ * number of available slots.
+ */
+ if (logp->dbentry_cnt <= ndx) {
+ if ((ret = __os_realloc(&logp->dbentry,
+ (ndx + DB_GROW_SIZE) * sizeof(DB_ENTRY))) != 0)
+ goto err;
+
+ /* Initialize the new entries. */
+ for (i = logp->dbentry_cnt; i < ndx + DB_GROW_SIZE; i++) {
+ logp->dbentry[i].dbp = NULL;
+ logp->dbentry[i].deleted = 0;
+ logp->dbentry[i].name = NULL;
+ }
+
+ logp->dbentry_cnt = i;
+ }
+
+ /* Make space for the name and copy it in. */
+ if (name != NULL) {
+ if ((ret = __os_malloc(strlen(name) + 1,
+ NULL, &logp->dbentry[ndx].name)) != 0)
+ goto err;
+ strcpy(logp->dbentry[ndx].name, name);
+ }
+
+ if (logp->dbentry[ndx].deleted == 0 && logp->dbentry[ndx].dbp == NULL) {
+ logp->dbentry[ndx].dbp = dbp;
+ logp->dbentry[ndx].refcount = 1;
+ logp->dbentry[ndx].deleted = dbp == NULL;
+ } else
+ logp->dbentry[ndx].refcount++;
+
+
+err: UNLOCK_LOGTHREAD(logp);
+ return (ret);
+}
+
+
+/*
+ * __db_fileid_to_db --
+ * Return the DB corresponding to the specified fileid.
+ *
+ * PUBLIC: int __db_fileid_to_db __P((DB_LOG *, DB **, u_int32_t));
+ */
+int
+__db_fileid_to_db(logp, dbpp, ndx)
+ DB_LOG *logp;
+ DB **dbpp;
+ u_int32_t ndx;
+{
+ int ret;
+ char *name;
+ FNAME *fname;
+
+ ret = 0;
+ LOCK_LOGTHREAD(logp);
+
+ /*
+ * Under XA, a process different than the one issuing DB
+ * operations may abort a transaction. In this case,
+ * recovery routines are run by a process that does not
+ * necessarily have the file open. In this case, we must
+ * open the file explicitly.
+ */
+ if (ndx >= logp->dbentry_cnt ||
+ (!logp->dbentry[ndx].deleted && logp->dbentry[ndx].dbp == NULL)) {
+ if (__log_lid_to_fname(logp, ndx, &fname) != 0) {
+ /* Couldn't find entry; this is a fatal error. */
+ ret = EINVAL;
+ goto err;
+ }
+ name = R_ADDR(logp, fname->name_off);
+ /*
+ * __log_do_open is called without protection of the
+ * log thread lock.
+ */
+ UNLOCK_LOGTHREAD(logp);
+ /*
+ * At this point, we are not holding the thread lock, so
+ * exit directly instead of going through the exit code
+ * at the bottom. If the __log_do_open succeeded, then
+ * we don't need to do any of the remaining error checking
+ * at the end of this routine.
+ */
+ if ((ret = __log_do_open(logp,
+ fname->ufid, name, fname->s_type, ndx)) != 0)
+ return (ret);
+ *dbpp = logp->dbentry[ndx].dbp;
+ return (0);
+ }
+
+ /*
+ * Return DB_DELETED if the file has been deleted
+ * (it's not an error).
+ */
+ if (logp->dbentry[ndx].deleted) {
+ ret = DB_DELETED;
+ goto err;
+ }
+
+ /*
+ * Otherwise return 0, but if we don't have a corresponding DB,
+ * it's an error.
+ */
+ if ((*dbpp = logp->dbentry[ndx].dbp) == NULL)
+ ret = ENOENT;
+
+err: UNLOCK_LOGTHREAD(logp);
+ return (ret);
+}
+
+/*
+ * Close files that were opened by the recovery daemon.
+ *
+ * PUBLIC: void __log_close_files __P((DB_LOG *));
+ */
+void
+__log_close_files(logp)
+ DB_LOG *logp;
+{
+ u_int32_t i;
+
+ LOCK_LOGTHREAD(logp);
+ for (i = 0; i < logp->dbentry_cnt; i++)
+ if (logp->dbentry[i].dbp) {
+ logp->dbentry[i].dbp->close(logp->dbentry[i].dbp, 0);
+ logp->dbentry[i].dbp = NULL;
+ logp->dbentry[i].deleted = 0;
+ }
+ F_CLR(logp, DBC_RECOVER);
+ UNLOCK_LOGTHREAD(logp);
+}
+
+/*
+ * PUBLIC: void __log_rem_logid __P((DB_LOG *, u_int32_t));
+ */
+void
+__log_rem_logid(logp, ndx)
+ DB_LOG *logp;
+ u_int32_t ndx;
+{
+ LOCK_LOGTHREAD(logp);
+ if (--logp->dbentry[ndx].refcount == 0) {
+ logp->dbentry[ndx].dbp = NULL;
+ logp->dbentry[ndx].deleted = 0;
+ }
+ UNLOCK_LOGTHREAD(logp);
+}
+
+/*
+ * __log_lid_to_fname --
+ * Traverse the shared-memory region looking for the entry that
+ * matches the passed log fileid. Returns 0 on success; -1 on error.
+ */
+static int
+__log_lid_to_fname(dblp, lid, fnamep)
+ DB_LOG *dblp;
+ u_int32_t lid;
+ FNAME **fnamep;
+{
+ FNAME *fnp;
+
+ for (fnp = SH_TAILQ_FIRST(&dblp->lp->fq, __fname);
+ fnp != NULL; fnp = SH_TAILQ_NEXT(fnp, q, __fname)) {
+ if (fnp->ref == 0) /* Entry not in use. */
+ continue;
+ if (fnp->id == lid) {
+ *fnamep = fnp;
+ return (0);
+ }
+ }
+ return (-1);
+}
diff --git a/usr/src/cmd/sendmail/db/log/log_register.c b/usr/src/cmd/sendmail/db/log/log_register.c
new file mode 100644
index 0000000000..71a32d6504
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/log/log_register.c
@@ -0,0 +1,208 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)log_register.c 10.22 (Sleepycat) 9/27/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <errno.h>
+#include <string.h>
+#endif
+
+#include "db_int.h"
+#include "shqueue.h"
+#include "log.h"
+#include "common_ext.h"
+
+/*
+ * log_register --
+ * Register a file name.
+ */
+int
+log_register(dblp, dbp, name, type, idp)
+ DB_LOG *dblp;
+ DB *dbp;
+ const char *name;
+ DBTYPE type;
+ u_int32_t *idp;
+{
+ DBT fid_dbt, r_name;
+ DB_LSN r_unused;
+ FNAME *fnp, *reuse_fnp;
+ size_t len;
+ u_int32_t maxid;
+ int inserted, ret;
+ char *fullname;
+ void *namep;
+
+ inserted = 0;
+ fullname = NULL;
+ fnp = namep = reuse_fnp = NULL;
+
+ LOG_PANIC_CHECK(dblp);
+
+ /* Check the arguments. */
+ if (type != DB_BTREE && type != DB_HASH && type != DB_RECNO) {
+ __db_err(dblp->dbenv, "log_register: unknown DB file type");
+ return (EINVAL);
+ }
+
+ /* Get the log file id. */
+ if ((ret = __db_appname(dblp->dbenv,
+ DB_APP_DATA, NULL, name, 0, NULL, &fullname)) != 0)
+ return (ret);
+
+ LOCK_LOGREGION(dblp);
+
+ /*
+ * See if we've already got this file in the log, finding the
+ * (maximum+1) in-use file id and some available file id (if we
+ * find an available fid, we'll use it, else we'll have to allocate
+ * one after the maximum that we found).
+ */
+ for (maxid = 0, fnp = SH_TAILQ_FIRST(&dblp->lp->fq, __fname);
+ fnp != NULL; fnp = SH_TAILQ_NEXT(fnp, q, __fname)) {
+ if (fnp->ref == 0) { /* Entry is not in use. */
+ if (reuse_fnp == NULL)
+ reuse_fnp = fnp;
+ continue;
+ }
+ if (!memcmp(dbp->fileid, fnp->ufid, DB_FILE_ID_LEN)) {
+ ++fnp->ref;
+ goto found;
+ }
+ if (maxid <= fnp->id)
+ maxid = fnp->id + 1;
+ }
+
+ /* Fill in fnp structure. */
+
+ if (reuse_fnp != NULL) /* Reuse existing one. */
+ fnp = reuse_fnp;
+ else if ((ret = __db_shalloc(dblp->addr, sizeof(FNAME), 0, &fnp)) != 0)
+ goto err;
+ else /* Allocate a new one. */
+ fnp->id = maxid;
+
+ fnp->ref = 1;
+ fnp->s_type = type;
+ memcpy(fnp->ufid, dbp->fileid, DB_FILE_ID_LEN);
+
+ len = strlen(name) + 1;
+ if ((ret = __db_shalloc(dblp->addr, len, 0, &namep)) != 0)
+ goto err;
+ fnp->name_off = R_OFFSET(dblp, namep);
+ memcpy(namep, name, len);
+
+ /* Only do the insert if we allocated a new fnp. */
+ if (reuse_fnp == NULL)
+ SH_TAILQ_INSERT_HEAD(&dblp->lp->fq, fnp, q, __fname);
+ inserted = 1;
+
+found: /* Log the registry. */
+ if (!F_ISSET(dblp, DBC_RECOVER)) {
+ r_name.data = (void *)name; /* XXX: Yuck! */
+ r_name.size = strlen(name) + 1;
+ memset(&fid_dbt, 0, sizeof(fid_dbt));
+ fid_dbt.data = dbp->fileid;
+ fid_dbt.size = DB_FILE_ID_LEN;
+ if ((ret = __log_register_log(dblp, NULL, &r_unused,
+ 0, LOG_OPEN, &r_name, &fid_dbt, fnp->id, type)) != 0)
+ goto err;
+ if ((ret = __log_add_logid(dblp, dbp, name, fnp->id)) != 0)
+ goto err;
+ }
+
+ if (0) {
+err: /*
+ * XXX
+ * We should grow the region.
+ */
+ if (inserted)
+ SH_TAILQ_REMOVE(&dblp->lp->fq, fnp, q, __fname);
+ if (namep != NULL)
+ __db_shalloc_free(dblp->addr, namep);
+ if (fnp != NULL)
+ __db_shalloc_free(dblp->addr, fnp);
+ }
+
+ if (idp != NULL)
+ *idp = fnp->id;
+ UNLOCK_LOGREGION(dblp);
+
+ if (fullname != NULL)
+ __os_freestr(fullname);
+
+ return (ret);
+}
+
+/*
+ * log_unregister --
+ * Discard a registered file name.
+ */
+int
+log_unregister(dblp, fid)
+ DB_LOG *dblp;
+ u_int32_t fid;
+{
+ DBT fid_dbt, r_name;
+ DB_LSN r_unused;
+ FNAME *fnp;
+ int ret;
+
+ LOG_PANIC_CHECK(dblp);
+
+ ret = 0;
+ LOCK_LOGREGION(dblp);
+
+ /* Find the entry in the log. */
+ for (fnp = SH_TAILQ_FIRST(&dblp->lp->fq, __fname);
+ fnp != NULL; fnp = SH_TAILQ_NEXT(fnp, q, __fname))
+ if (fid == fnp->id)
+ break;
+ if (fnp == NULL) {
+ __db_err(dblp->dbenv, "log_unregister: non-existent file id");
+ ret = EINVAL;
+ goto ret1;
+ }
+
+ /* Unlog the registry. */
+ if (!F_ISSET(dblp, DBC_RECOVER)) {
+ memset(&r_name, 0, sizeof(r_name));
+ r_name.data = R_ADDR(dblp, fnp->name_off);
+ r_name.size = strlen(r_name.data) + 1;
+ memset(&fid_dbt, 0, sizeof(fid_dbt));
+ fid_dbt.data = fnp->ufid;
+ fid_dbt.size = DB_FILE_ID_LEN;
+ if ((ret = __log_register_log(dblp, NULL, &r_unused,
+ 0, LOG_CLOSE, &r_name, &fid_dbt, fid, fnp->s_type)) != 0)
+ goto ret1;
+ }
+
+ /*
+ * If more than 1 reference, just decrement the reference and return.
+ * Otherwise, free the name.
+ */
+ --fnp->ref;
+ if (fnp->ref == 0)
+ __db_shalloc_free(dblp->addr, R_ADDR(dblp, fnp->name_off));
+
+ /*
+ * Remove from the process local table. If this operation is taking
+ * place during recovery, then the logid was never added to the table,
+ * so do not remove it.
+ */
+ if (!F_ISSET(dblp, DBC_RECOVER))
+ __log_rem_logid(dblp, fid);
+
+ret1: UNLOCK_LOGREGION(dblp);
+ return (ret);
+}
diff --git a/usr/src/cmd/sendmail/db/mp/mp_bh.c b/usr/src/cmd/sendmail/db/mp/mp_bh.c
new file mode 100644
index 0000000000..05b7a7bd28
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/mp/mp_bh.c
@@ -0,0 +1,590 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)mp_bh.c 10.45 (Sleepycat) 11/25/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#endif
+
+#include "db_int.h"
+#include "shqueue.h"
+#include "db_shash.h"
+#include "mp.h"
+#include "common_ext.h"
+
+static int __memp_upgrade __P((DB_MPOOL *, DB_MPOOLFILE *, MPOOLFILE *));
+
+/*
+ * __memp_bhwrite --
+ * Write the page associated with a given bucket header.
+ *
+ * PUBLIC: int __memp_bhwrite
+ * PUBLIC: __P((DB_MPOOL *, MPOOLFILE *, BH *, int *, int *));
+ */
+int
+__memp_bhwrite(dbmp, mfp, bhp, restartp, wrotep)
+ DB_MPOOL *dbmp;
+ MPOOLFILE *mfp;
+ BH *bhp;
+ int *restartp, *wrotep;
+{
+ DB_MPOOLFILE *dbmfp;
+ DB_MPREG *mpreg;
+ int incremented, ret;
+
+ if (restartp != NULL)
+ *restartp = 0;
+ if (wrotep != NULL)
+ *wrotep = 0;
+ incremented = 0;
+
+ /*
+ * Walk the process' DB_MPOOLFILE list and find a file descriptor for
+ * the file. We also check that the descriptor is open for writing.
+ * If we find a descriptor on the file that's not open for writing, we
+ * try and upgrade it to make it writeable. If that fails, we're done.
+ */
+ LOCKHANDLE(dbmp, dbmp->mutexp);
+ for (dbmfp = TAILQ_FIRST(&dbmp->dbmfq);
+ dbmfp != NULL; dbmfp = TAILQ_NEXT(dbmfp, q))
+ if (dbmfp->mfp == mfp) {
+ if (F_ISSET(dbmfp, MP_READONLY) &&
+ __memp_upgrade(dbmp, dbmfp, mfp)) {
+ UNLOCKHANDLE(dbmp, dbmp->mutexp);
+ return (0);
+ }
+
+ /*
+ * Increment the reference count -- see the comment in
+ * memp_fclose().
+ */
+ ++dbmfp->ref;
+ incremented = 1;
+ break;
+ }
+ UNLOCKHANDLE(dbmp, dbmp->mutexp);
+ if (dbmfp != NULL)
+ goto found;
+
+ /*
+ * It's not a page from a file we've opened. If the file requires
+ * input/output processing, see if this process has ever registered
+ * information as to how to write this type of file. If not, there's
+ * nothing we can do.
+ */
+ if (mfp->ftype != 0) {
+ LOCKHANDLE(dbmp, dbmp->mutexp);
+ for (mpreg = LIST_FIRST(&dbmp->dbregq);
+ mpreg != NULL; mpreg = LIST_NEXT(mpreg, q))
+ if (mpreg->ftype == mfp->ftype)
+ break;
+ UNLOCKHANDLE(dbmp, dbmp->mutexp);
+ if (mpreg == NULL)
+ return (0);
+ }
+
+ /*
+ * Try and open the file, attaching to the underlying shared area.
+ *
+ * XXX
+ * Don't try to attach to temporary files. There are two problems in
+ * trying to do that. First, if we have different privileges than the
+ * process that "owns" the temporary file, we might create the backing
+ * disk file such that the owning process couldn't read/write its own
+ * buffers, e.g., memp_trickle() running as root creating a file owned
+ * as root, mode 600. Second, if the temporary file has already been
+ * created, we don't have any way of finding out what its real name is,
+ * and, even if we did, it was already unlinked (so that it won't be
+ * left if the process dies horribly). This decision causes a problem,
+ * however: if the temporary file consumes the entire buffer cache,
+ * and the owner doesn't flush the buffers to disk, we could end up
+ * with resource starvation, and the memp_trickle() thread couldn't do
+ * anything about it. That's a pretty unlikely scenario, though.
+ *
+ * XXX
+ * There's no negative cache, so we may repeatedly try and open files
+ * that we have previously tried (and failed) to open.
+ *
+ * Ignore any error, assume it's a permissions problem.
+ */
+ if (F_ISSET(mfp, MP_TEMP))
+ return (0);
+
+ if (__memp_fopen(dbmp, mfp, R_ADDR(dbmp, mfp->path_off),
+ 0, 0, mfp->stat.st_pagesize, 0, NULL, &dbmfp) != 0)
+ return (0);
+
+found: ret = __memp_pgwrite(dbmfp, bhp, restartp, wrotep);
+
+ if (incremented) {
+ LOCKHANDLE(dbmp, dbmp->mutexp);
+ --dbmfp->ref;
+ UNLOCKHANDLE(dbmp, dbmp->mutexp);
+ }
+
+ return (ret);
+}
+
+/*
+ * __memp_pgread --
+ * Read a page from a file.
+ *
+ * PUBLIC: int __memp_pgread __P((DB_MPOOLFILE *, BH *, int));
+ */
+int
+__memp_pgread(dbmfp, bhp, can_create)
+ DB_MPOOLFILE *dbmfp;
+ BH *bhp;
+ int can_create;
+{
+ DB_IO db_io;
+ DB_MPOOL *dbmp;
+ MPOOLFILE *mfp;
+ size_t len, pagesize;
+ ssize_t nr;
+ int created, ret;
+
+ dbmp = dbmfp->dbmp;
+ mfp = dbmfp->mfp;
+ pagesize = mfp->stat.st_pagesize;
+
+ F_SET(bhp, BH_LOCKED | BH_TRASH);
+ LOCKBUFFER(dbmp, bhp);
+ UNLOCKREGION(dbmp);
+
+ /*
+ * Temporary files may not yet have been created. We don't create
+ * them now, we create them when the pages have to be flushed.
+ */
+ nr = 0;
+ if (dbmfp->fd == -1)
+ ret = 0;
+ else {
+ /*
+ * Ignore read errors if we have permission to create the page.
+ * Assume that the page doesn't exist, and that we'll create it
+ * when we write it out.
+ */
+ db_io.fd_io = dbmfp->fd;
+ db_io.fd_lock = dbmp->reginfo.fd;
+ db_io.mutexp =
+ F_ISSET(dbmp, MP_LOCKHANDLE) ? dbmfp->mutexp : NULL;
+ db_io.pagesize = db_io.bytes = pagesize;
+ db_io.pgno = bhp->pgno;
+ db_io.buf = bhp->buf;
+
+ ret = __os_io(&db_io, DB_IO_READ, &nr);
+ }
+
+ created = 0;
+ if (nr < (ssize_t)pagesize)
+ if (can_create)
+ created = 1;
+ else {
+ /* If we had a short read, ret may be 0. */
+ if (ret == 0)
+ ret = EIO;
+ __db_err(dbmp->dbenv,
+ "%s: page %lu doesn't exist, create flag not set",
+ __memp_fn(dbmfp), (u_long)bhp->pgno);
+ goto err;
+ }
+
+ /*
+ * Clear any bytes we didn't read that need to be cleared. If we're
+ * running in diagnostic mode, smash any bytes on the page that are
+ * unknown quantities for the caller.
+ */
+ if (nr != (ssize_t)pagesize) {
+ len = mfp->clear_len == 0 ? pagesize : mfp->clear_len;
+ if (nr < (ssize_t)len)
+ memset(bhp->buf + nr, 0, len - nr);
+#ifdef DIAGNOSTIC
+ if (nr > (ssize_t)len)
+ len = nr;
+ if (len < pagesize)
+ memset(bhp->buf + len, 0xdb, pagesize - len);
+#endif
+ }
+
+ /* Call any pgin function. */
+ ret = mfp->ftype == 0 ? 0 : __memp_pg(dbmfp, bhp, 1);
+
+ /* Unlock the buffer and reacquire the region lock. */
+err: UNLOCKBUFFER(dbmp, bhp);
+ LOCKREGION(dbmp);
+
+ /*
+ * If no errors occurred, the data is now valid, clear the BH_TRASH
+ * flag; regardless, clear the lock bit and let other threads proceed.
+ */
+ F_CLR(bhp, BH_LOCKED);
+ if (ret == 0) {
+ F_CLR(bhp, BH_TRASH);
+
+ /* Update the statistics. */
+ if (created) {
+ ++dbmp->mp->stat.st_page_create;
+ ++mfp->stat.st_page_create;
+ } else {
+ ++dbmp->mp->stat.st_page_in;
+ ++mfp->stat.st_page_in;
+ }
+ }
+
+ return (ret);
+}
+
+/*
+ * __memp_pgwrite --
+ * Write a page to a file.
+ *
+ * PUBLIC: int __memp_pgwrite __P((DB_MPOOLFILE *, BH *, int *, int *));
+ */
+int
+__memp_pgwrite(dbmfp, bhp, restartp, wrotep)
+ DB_MPOOLFILE *dbmfp;
+ BH *bhp;
+ int *restartp, *wrotep;
+{
+ DB_ENV *dbenv;
+ DB_IO db_io;
+ DB_LOG *lg_info;
+ DB_LSN lsn;
+ DB_MPOOL *dbmp;
+ MPOOL *mp;
+ MPOOLFILE *mfp;
+ ssize_t nw;
+ int callpgin, dosync, ret, syncfail;
+ const char *fail;
+
+ dbmp = dbmfp->dbmp;
+ dbenv = dbmp->dbenv;
+ mp = dbmp->mp;
+ mfp = dbmfp->mfp;
+
+ if (restartp != NULL)
+ *restartp = 0;
+ if (wrotep != NULL)
+ *wrotep = 0;
+ callpgin = 0;
+
+ /*
+ * Check the dirty bit -- this buffer may have been written since we
+ * decided to write it.
+ */
+ if (!F_ISSET(bhp, BH_DIRTY)) {
+ if (wrotep != NULL)
+ *wrotep = 1;
+ return (0);
+ }
+
+ LOCKBUFFER(dbmp, bhp);
+
+ /*
+ * If there were two writers, we may have just been waiting while the
+ * other writer completed I/O on this buffer. Check the dirty bit one
+ * more time.
+ */
+ if (!F_ISSET(bhp, BH_DIRTY)) {
+ UNLOCKBUFFER(dbmp, bhp);
+
+ if (wrotep != NULL)
+ *wrotep = 1;
+ return (0);
+ }
+
+ F_SET(bhp, BH_LOCKED);
+ UNLOCKREGION(dbmp);
+
+ if (restartp != NULL)
+ *restartp = 1;
+
+ /* Copy the LSN off the page if we're going to need it. */
+ lg_info = dbenv->lg_info;
+ if (lg_info != NULL || F_ISSET(bhp, BH_WRITE))
+ memcpy(&lsn, bhp->buf + mfp->lsn_off, sizeof(DB_LSN));
+
+ /* Ensure the appropriate log records are on disk. */
+ if (lg_info != NULL && (ret = log_flush(lg_info, &lsn)) != 0)
+ goto err;
+
+ /*
+ * Call any pgout function. We set the callpgin flag so that we flag
+ * that the contents of the buffer will need to be passed through pgin
+ * before they are reused.
+ */
+ if (mfp->ftype == 0)
+ ret = 0;
+ else {
+ callpgin = 1;
+ if ((ret = __memp_pg(dbmfp, bhp, 0)) != 0)
+ goto err;
+ }
+
+ /* Temporary files may not yet have been created. */
+ if (dbmfp->fd == -1) {
+ LOCKHANDLE(dbmp, dbmfp->mutexp);
+ if (dbmfp->fd == -1 && ((ret = __db_appname(dbenv,
+ DB_APP_TMP, NULL, NULL, DB_CREATE | DB_EXCL | DB_TEMPORARY,
+ &dbmfp->fd, NULL)) != 0 || dbmfp->fd == -1)) {
+ UNLOCKHANDLE(dbmp, dbmfp->mutexp);
+ __db_err(dbenv,
+ "unable to create temporary backing file");
+ goto err;
+ }
+ UNLOCKHANDLE(dbmp, dbmfp->mutexp);
+ }
+
+ /* Write the page. */
+ db_io.fd_io = dbmfp->fd;
+ db_io.fd_lock = dbmp->reginfo.fd;
+ db_io.mutexp = F_ISSET(dbmp, MP_LOCKHANDLE) ? dbmfp->mutexp : NULL;
+ db_io.pagesize = db_io.bytes = mfp->stat.st_pagesize;
+ db_io.pgno = bhp->pgno;
+ db_io.buf = bhp->buf;
+ if ((ret = __os_io(&db_io, DB_IO_WRITE, &nw)) != 0) {
+ __db_panic(dbenv, ret);
+ fail = "write";
+ goto syserr;
+ }
+ if (nw != (ssize_t)mfp->stat.st_pagesize) {
+ ret = EIO;
+ fail = "write";
+ goto syserr;
+ }
+
+ if (wrotep != NULL)
+ *wrotep = 1;
+
+ /* Unlock the buffer and reacquire the region lock. */
+ UNLOCKBUFFER(dbmp, bhp);
+ LOCKREGION(dbmp);
+
+ /*
+ * Clean up the flags based on a successful write.
+ *
+ * If we rewrote the page, it will need processing by the pgin
+ * routine before reuse.
+ */
+ if (callpgin)
+ F_SET(bhp, BH_CALLPGIN);
+ F_CLR(bhp, BH_DIRTY | BH_LOCKED);
+
+ /*
+ * If we write a buffer for which a checkpoint is waiting, update
+ * the count of pending buffers (both in the mpool as a whole and
+ * for this file). If the count for this file goes to zero, set a
+ * flag so we flush the writes.
+ */
+ if (F_ISSET(bhp, BH_WRITE)) {
+ F_CLR(bhp, BH_WRITE);
+
+ --mp->lsn_cnt;
+ dosync = --mfp->lsn_cnt == 0 ? 1 : 0;
+ } else
+ dosync = 0;
+
+ /* Update the page clean/dirty statistics. */
+ ++mp->stat.st_page_clean;
+ --mp->stat.st_page_dirty;
+
+ /* Update I/O statistics. */
+ ++mp->stat.st_page_out;
+ ++mfp->stat.st_page_out;
+
+ /*
+ * Do the sync after everything else has been updated, so any incoming
+ * checkpoint doesn't see inconsistent information.
+ *
+ * XXX:
+ * Don't lock the region around the sync, fsync(2) has no atomicity
+ * issues.
+ *
+ * XXX:
+ * We ignore errors from the sync -- it makes no sense to return an
+ * error to the calling process, so set a flag causing the checkpoint
+ * to be retried later. There is a possibility, of course, that a
+ * subsequent checkpoint was started and that we're going to force it
+ * to fail. That should be unlikely, and fixing it would be difficult.
+ */
+ if (dosync) {
+ UNLOCKREGION(dbmp);
+ syncfail = __os_fsync(dbmfp->fd) != 0;
+ LOCKREGION(dbmp);
+ if (syncfail)
+ F_SET(mp, MP_LSN_RETRY);
+ }
+
+ return (0);
+
+syserr: __db_err(dbenv, "%s: %s failed for page %lu",
+ __memp_fn(dbmfp), fail, (u_long)bhp->pgno);
+
+err: /* Unlock the buffer and reacquire the region lock. */
+ UNLOCKBUFFER(dbmp, bhp);
+ LOCKREGION(dbmp);
+
+ /*
+ * Clean up the flags based on a failure.
+ *
+ * The page remains dirty but we remove our lock. If we rewrote the
+ * page, it will need processing by the pgin routine before reuse.
+ */
+ if (callpgin)
+ F_SET(bhp, BH_CALLPGIN);
+ F_CLR(bhp, BH_LOCKED);
+
+ return (ret);
+}
+
+/*
+ * __memp_pg --
+ * Call the pgin/pgout routine.
+ *
+ * PUBLIC: int __memp_pg __P((DB_MPOOLFILE *, BH *, int));
+ */
+int
+__memp_pg(dbmfp, bhp, is_pgin)
+ DB_MPOOLFILE *dbmfp;
+ BH *bhp;
+ int is_pgin;
+{
+ DBT dbt, *dbtp;
+ DB_MPOOL *dbmp;
+ DB_MPREG *mpreg;
+ MPOOLFILE *mfp;
+ int ftype, ret;
+
+ dbmp = dbmfp->dbmp;
+ mfp = dbmfp->mfp;
+
+ LOCKHANDLE(dbmp, dbmp->mutexp);
+
+ ftype = mfp->ftype;
+ for (mpreg = LIST_FIRST(&dbmp->dbregq);
+ mpreg != NULL; mpreg = LIST_NEXT(mpreg, q)) {
+ if (ftype != mpreg->ftype)
+ continue;
+ if (mfp->pgcookie_len == 0)
+ dbtp = NULL;
+ else {
+ dbt.size = mfp->pgcookie_len;
+ dbt.data = R_ADDR(dbmp, mfp->pgcookie_off);
+ dbtp = &dbt;
+ }
+ UNLOCKHANDLE(dbmp, dbmp->mutexp);
+
+ if (is_pgin) {
+ if (mpreg->pgin != NULL && (ret =
+ mpreg->pgin(bhp->pgno, bhp->buf, dbtp)) != 0)
+ goto err;
+ } else
+ if (mpreg->pgout != NULL && (ret =
+ mpreg->pgout(bhp->pgno, bhp->buf, dbtp)) != 0)
+ goto err;
+ break;
+ }
+
+ if (mpreg == NULL)
+ UNLOCKHANDLE(dbmp, dbmp->mutexp);
+
+ return (0);
+
+err: UNLOCKHANDLE(dbmp, dbmp->mutexp);
+ __db_err(dbmp->dbenv, "%s: %s failed for page %lu",
+ __memp_fn(dbmfp), is_pgin ? "pgin" : "pgout", (u_long)bhp->pgno);
+ return (ret);
+}
+
+/*
+ * __memp_bhfree --
+ * Free a bucket header and its referenced data.
+ *
+ * PUBLIC: void __memp_bhfree __P((DB_MPOOL *, MPOOLFILE *, BH *, int));
+ */
+void
+__memp_bhfree(dbmp, mfp, bhp, free_mem)
+ DB_MPOOL *dbmp;
+ MPOOLFILE *mfp;
+ BH *bhp;
+ int free_mem;
+{
+ size_t off;
+
+ /* Delete the buffer header from the hash bucket queue. */
+ off = BUCKET(dbmp->mp, R_OFFSET(dbmp, mfp), bhp->pgno);
+ SH_TAILQ_REMOVE(&dbmp->htab[off], bhp, hq, __bh);
+
+ /* Delete the buffer header from the LRU queue. */
+ SH_TAILQ_REMOVE(&dbmp->mp->bhq, bhp, q, __bh);
+
+ /*
+ * If we're not reusing it immediately, free the buffer header
+ * and data for real.
+ */
+ if (free_mem) {
+ __db_shalloc_free(dbmp->addr, bhp);
+ --dbmp->mp->stat.st_page_clean;
+ }
+}
+
+/*
+ * __memp_upgrade --
+ * Upgrade a file descriptor from readonly to readwrite.
+ */
+static int
+__memp_upgrade(dbmp, dbmfp, mfp)
+ DB_MPOOL *dbmp;
+ DB_MPOOLFILE *dbmfp;
+ MPOOLFILE *mfp;
+{
+ int fd, ret;
+ char *rpath;
+
+ /*
+ * !!!
+ * We expect the handle to already be locked.
+ */
+
+ /* Check to see if we've already upgraded. */
+ if (F_ISSET(dbmfp, MP_UPGRADE))
+ return (0);
+
+ /* Check to see if we've already failed. */
+ if (F_ISSET(dbmfp, MP_UPGRADE_FAIL))
+ return (1);
+
+ /*
+ * Calculate the real name for this file and try to open it read/write.
+ * We know we have a valid pathname for the file because it's the only
+ * way we could have gotten a file descriptor of any kind.
+ */
+ if ((ret = __db_appname(dbmp->dbenv, DB_APP_DATA,
+ NULL, R_ADDR(dbmp, mfp->path_off), 0, NULL, &rpath)) != 0)
+ return (ret);
+ if (__db_open(rpath, 0, 0, 0, &fd) != 0) {
+ F_SET(dbmfp, MP_UPGRADE_FAIL);
+ ret = 1;
+ } else {
+ /* Swap the descriptors and set the upgrade flag. */
+ (void)__os_close(dbmfp->fd);
+ dbmfp->fd = fd;
+ F_SET(dbmfp, MP_UPGRADE);
+ ret = 0;
+ }
+ __os_freestr(rpath);
+ return (ret);
+}
diff --git a/usr/src/cmd/sendmail/db/mp/mp_fget.c b/usr/src/cmd/sendmail/db/mp/mp_fget.c
new file mode 100644
index 0000000000..8eae94322d
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/mp/mp_fget.c
@@ -0,0 +1,351 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)mp_fget.c 10.53 (Sleepycat) 11/16/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <errno.h>
+#include <string.h>
+#endif
+
+#include "db_int.h"
+#include "shqueue.h"
+#include "db_shash.h"
+#include "mp.h"
+#include "common_ext.h"
+
+/*
+ * memp_fget --
+ * Get a page from the file.
+ */
+int
+memp_fget(dbmfp, pgnoaddr, flags, addrp)
+ DB_MPOOLFILE *dbmfp;
+ db_pgno_t *pgnoaddr;
+ u_int32_t flags;
+ void *addrp;
+{
+ BH *bhp;
+ DB_MPOOL *dbmp;
+ MPOOL *mp;
+ MPOOLFILE *mfp;
+ size_t bucket, mf_offset;
+ u_int32_t st_hsearch;
+ int b_incr, first, ret;
+
+ dbmp = dbmfp->dbmp;
+ mp = dbmp->mp;
+ mfp = dbmfp->mfp;
+
+ MP_PANIC_CHECK(dbmp);
+
+ /*
+ * Validate arguments.
+ *
+ * !!!
+ * Don't test for DB_MPOOL_CREATE and DB_MPOOL_NEW flags for readonly
+ * files here, and create non-existent pages in readonly files if the
+ * flags are set, later. The reason is that the hash access method
+ * wants to get empty pages that don't really exist in readonly files.
+ * The only alternative is for hash to write the last "bucket" all the
+ * time, which we don't want to do because one of our big goals in life
+ * is to keep database files small. It's sleazy as hell, but we catch
+ * any attempt to actually write the file in memp_fput().
+ */
+#define OKFLAGS (DB_MPOOL_CREATE | DB_MPOOL_LAST | DB_MPOOL_NEW)
+ if (flags != 0) {
+ if ((ret =
+ __db_fchk(dbmp->dbenv, "memp_fget", flags, OKFLAGS)) != 0)
+ return (ret);
+
+ switch (flags) {
+ case DB_MPOOL_CREATE:
+ case DB_MPOOL_LAST:
+ case DB_MPOOL_NEW:
+ case 0:
+ break;
+ default:
+ return (__db_ferr(dbmp->dbenv, "memp_fget", 1));
+ }
+ }
+
+#ifdef DIAGNOSTIC
+ /*
+ * XXX
+ * We want to switch threads as often as possible. Yield every time
+ * we get a new page to ensure contention.
+ */
+ if (DB_GLOBAL(db_pageyield))
+ __os_yield(1);
+#endif
+
+ /* Initialize remaining local variables. */
+ mf_offset = R_OFFSET(dbmp, mfp);
+ bhp = NULL;
+ st_hsearch = 0;
+ b_incr = ret = 0;
+
+ /* Determine the hash bucket where this page will live. */
+ bucket = BUCKET(mp, mf_offset, *pgnoaddr);
+
+ LOCKREGION(dbmp);
+
+ /*
+ * Check for the last or last + 1 page requests.
+ *
+ * Examine and update the file's last_pgno value. We don't care if
+ * the last_pgno value immediately changes due to another thread --
+ * at this instant in time, the value is correct. We do increment the
+ * current last_pgno value if the thread is asking for a new page,
+ * however, to ensure that two threads creating pages don't get the
+ * same one.
+ */
+ if (LF_ISSET(DB_MPOOL_LAST | DB_MPOOL_NEW)) {
+ if (LF_ISSET(DB_MPOOL_NEW))
+ ++mfp->last_pgno;
+ *pgnoaddr = mfp->last_pgno;
+ bucket = BUCKET(mp, mf_offset, mfp->last_pgno);
+
+ if (LF_ISSET(DB_MPOOL_NEW))
+ goto alloc;
+ }
+
+ /*
+ * If mmap'ing the file and the page is not past the end of the file,
+ * just return a pointer.
+ *
+ * The page may be past the end of the file, so check the page number
+ * argument against the original length of the file. If we previously
+ * returned pages past the original end of the file, last_pgno will
+ * have been updated to match the "new" end of the file, and checking
+ * against it would return pointers past the end of the mmap'd region.
+ *
+ * If another process has opened the file for writing since we mmap'd
+ * it, we will start playing the game by their rules, i.e. everything
+ * goes through the cache. All pages previously returned will be safe,
+ * as long as the correct locking protocol was observed.
+ *
+ * XXX
+ * We don't discard the map because we don't know when all of the
+ * pages will have been discarded from the process' address space.
+ * It would be possible to do so by reference counting the open
+ * pages from the mmap, but it's unclear to me that it's worth it.
+ */
+ if (dbmfp->addr != NULL && F_ISSET(mfp, MP_CAN_MMAP))
+ if (*pgnoaddr > mfp->orig_last_pgno) {
+ /*
+ * !!!
+ * See the comment above about non-existent pages and
+ * the hash access method.
+ */
+ if (!LF_ISSET(DB_MPOOL_CREATE)) {
+ __db_err(dbmp->dbenv,
+ "%s: page %lu doesn't exist",
+ __memp_fn(dbmfp), (u_long)*pgnoaddr);
+ ret = EINVAL;
+ goto err;
+ }
+ } else {
+ *(void **)addrp =
+ R_ADDR(dbmfp, *pgnoaddr * mfp->stat.st_pagesize);
+ ++mp->stat.st_map;
+ ++mfp->stat.st_map;
+ goto done;
+ }
+
+ /* Search the hash chain for the page. */
+ for (bhp = SH_TAILQ_FIRST(&dbmp->htab[bucket], __bh);
+ bhp != NULL; bhp = SH_TAILQ_NEXT(bhp, hq, __bh)) {
+ ++st_hsearch;
+ if (bhp->pgno != *pgnoaddr || bhp->mf_offset != mf_offset)
+ continue;
+
+ /* Increment the reference count. */
+ if (bhp->ref == UINT16_T_MAX) {
+ __db_err(dbmp->dbenv,
+ "%s: page %lu: reference count overflow",
+ __memp_fn(dbmfp), (u_long)bhp->pgno);
+ ret = EINVAL;
+ goto err;
+ }
+
+ /*
+ * Increment the reference count. We may discard the region
+ * lock as we evaluate and/or read the buffer, so we need to
+ * ensure that it doesn't move and that its contents remain
+ * unchanged.
+ */
+ ++bhp->ref;
+ b_incr = 1;
+
+ /*
+ * Any buffer we find might be trouble.
+ *
+ * BH_LOCKED --
+ * I/O is in progress. Because we've incremented the buffer
+ * reference count, we know the buffer can't move. Unlock
+ * the region lock, wait for the I/O to complete, and reacquire
+ * the region.
+ */
+ for (first = 1; F_ISSET(bhp, BH_LOCKED); first = 0) {
+ UNLOCKREGION(dbmp);
+
+ /*
+ * Explicitly yield the processor if it's not the first
+ * pass through this loop -- if we don't, we might end
+ * up running to the end of our CPU quantum as we will
+ * simply be swapping between the two locks.
+ */
+ if (!first)
+ __os_yield(1);
+
+ LOCKBUFFER(dbmp, bhp);
+ /* Wait for I/O to finish... */
+ UNLOCKBUFFER(dbmp, bhp);
+ LOCKREGION(dbmp);
+ }
+
+ /*
+ * BH_TRASH --
+ * The contents of the buffer are garbage. Shouldn't happen,
+ * and this read is likely to fail, but might as well try.
+ */
+ if (F_ISSET(bhp, BH_TRASH))
+ goto reread;
+
+ /*
+ * BH_CALLPGIN --
+ * The buffer was converted so it could be written, and the
+ * contents need to be converted again.
+ */
+ if (F_ISSET(bhp, BH_CALLPGIN)) {
+ if ((ret = __memp_pg(dbmfp, bhp, 1)) != 0)
+ goto err;
+ F_CLR(bhp, BH_CALLPGIN);
+ }
+
+ ++mp->stat.st_cache_hit;
+ ++mfp->stat.st_cache_hit;
+ *(void **)addrp = bhp->buf;
+ goto done;
+ }
+
+alloc: /* Allocate new buffer header and data space. */
+ if ((ret = __memp_alloc(dbmp, sizeof(BH) -
+ sizeof(u_int8_t) + mfp->stat.st_pagesize, NULL, &bhp)) != 0)
+ goto err;
+
+#ifdef DIAGNOSTIC
+ if ((ALIGNTYPE)bhp->buf & (sizeof(size_t) - 1)) {
+ __db_err(dbmp->dbenv,
+ "Internal error: BH data NOT size_t aligned.");
+ ret = EINVAL;
+ goto err;
+ }
+#endif
+ /* Initialize the BH fields. */
+ memset(bhp, 0, sizeof(BH));
+ LOCKINIT(dbmp, &bhp->mutex);
+ bhp->ref = 1;
+ bhp->pgno = *pgnoaddr;
+ bhp->mf_offset = mf_offset;
+
+ /*
+ * Prepend the bucket header to the head of the appropriate MPOOL
+ * bucket hash list. Append the bucket header to the tail of the
+ * MPOOL LRU chain.
+ */
+ SH_TAILQ_INSERT_HEAD(&dbmp->htab[bucket], bhp, hq, __bh);
+ SH_TAILQ_INSERT_TAIL(&mp->bhq, bhp, q);
+
+ /*
+ * If we created the page, zero it out and continue.
+ *
+ * !!!
+ * Note: DB_MPOOL_NEW specifically doesn't call the pgin function.
+ * If DB_MPOOL_CREATE is used, then the application's pgin function
+ * has to be able to handle pages of 0's -- if it uses DB_MPOOL_NEW,
+ * it can detect all of its page creates, and not bother.
+ *
+ * Otherwise, read the page into memory, optionally creating it if
+ * DB_MPOOL_CREATE is set.
+ */
+ if (LF_ISSET(DB_MPOOL_NEW)) {
+ if (mfp->clear_len == 0)
+ memset(bhp->buf, 0, mfp->stat.st_pagesize);
+ else {
+ memset(bhp->buf, 0, mfp->clear_len);
+#ifdef DIAGNOSTIC
+ memset(bhp->buf + mfp->clear_len, 0xdb,
+ mfp->stat.st_pagesize - mfp->clear_len);
+#endif
+ }
+
+ ++mp->stat.st_page_create;
+ ++mfp->stat.st_page_create;
+ } else {
+ /*
+ * It's possible for the read function to fail, which means
+ * that we fail as well. Note, the __memp_pgread() function
+ * discards the region lock, so the buffer must be pinned
+ * down so that it cannot move and its contents are unchanged.
+ */
+reread: if ((ret = __memp_pgread(dbmfp,
+ bhp, LF_ISSET(DB_MPOOL_CREATE))) != 0) {
+ /*
+ * !!!
+ * Discard the buffer unless another thread is waiting
+ * on our I/O to complete. Regardless, the header has
+ * the BH_TRASH flag set.
+ */
+ if (bhp->ref == 1)
+ __memp_bhfree(dbmp, mfp, bhp, 1);
+ goto err;
+ }
+
+ ++mp->stat.st_cache_miss;
+ ++mfp->stat.st_cache_miss;
+ }
+
+ /*
+ * If we're returning a page after our current notion of the last-page,
+ * update our information. Note, there's no way to un-instantiate this
+ * page, it's going to exist whether it's returned to us dirty or not.
+ */
+ if (bhp->pgno > mfp->last_pgno)
+ mfp->last_pgno = bhp->pgno;
+
+ ++mp->stat.st_page_clean;
+ *(void **)addrp = bhp->buf;
+
+done: /* Update the chain search statistics. */
+ if (st_hsearch) {
+ ++mp->stat.st_hash_searches;
+ if (st_hsearch > mp->stat.st_hash_longest)
+ mp->stat.st_hash_longest = st_hsearch;
+ mp->stat.st_hash_examined += st_hsearch;
+ }
+
+ ++dbmfp->pinref;
+
+ UNLOCKREGION(dbmp);
+
+ return (0);
+
+err: /* Discard our reference. */
+ if (b_incr)
+ --bhp->ref;
+ UNLOCKREGION(dbmp);
+
+ *(void **)addrp = NULL;
+ return (ret);
+}
diff --git a/usr/src/cmd/sendmail/db/mp/mp_fopen.c b/usr/src/cmd/sendmail/db/mp/mp_fopen.c
new file mode 100644
index 0000000000..dd02662fd8
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/mp/mp_fopen.c
@@ -0,0 +1,560 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)mp_fopen.c 10.60 (Sleepycat) 1/1/99";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <errno.h>
+#include <string.h>
+#endif
+
+#include "db_int.h"
+#include "shqueue.h"
+#include "db_shash.h"
+#include "mp.h"
+#include "common_ext.h"
+
+static int __memp_mf_close __P((DB_MPOOL *, DB_MPOOLFILE *));
+static int __memp_mf_open __P((DB_MPOOL *,
+ const char *, size_t, db_pgno_t, DB_MPOOL_FINFO *, MPOOLFILE **));
+
+/*
+ * memp_fopen --
+ * Open a backing file for the memory pool.
+ */
+int
+memp_fopen(dbmp, path, flags, mode, pagesize, finfop, retp)
+ DB_MPOOL *dbmp;
+ const char *path;
+ u_int32_t flags;
+ int mode;
+ size_t pagesize;
+ DB_MPOOL_FINFO *finfop;
+ DB_MPOOLFILE **retp;
+{
+ int ret;
+
+ MP_PANIC_CHECK(dbmp);
+
+ /* Validate arguments. */
+ if ((ret = __db_fchk(dbmp->dbenv,
+ "memp_fopen", flags, DB_CREATE | DB_NOMMAP | DB_RDONLY)) != 0)
+ return (ret);
+
+ /* Require a non-zero pagesize. */
+ if (pagesize == 0) {
+ __db_err(dbmp->dbenv, "memp_fopen: pagesize not specified");
+ return (EINVAL);
+ }
+ if (finfop != NULL && finfop->clear_len > pagesize)
+ return (EINVAL);
+
+ return (__memp_fopen(dbmp,
+ NULL, path, flags, mode, pagesize, 1, finfop, retp));
+}
+
+/*
+ * __memp_fopen --
+ * Open a backing file for the memory pool; internal version.
+ *
+ * PUBLIC: int __memp_fopen __P((DB_MPOOL *, MPOOLFILE *, const char *,
+ * PUBLIC: u_int32_t, int, size_t, int, DB_MPOOL_FINFO *, DB_MPOOLFILE **));
+ */
+int
+__memp_fopen(dbmp, mfp, path, flags, mode, pagesize, needlock, finfop, retp)
+ DB_MPOOL *dbmp;
+ MPOOLFILE *mfp;
+ const char *path;
+ u_int32_t flags;
+ int mode, needlock;
+ size_t pagesize;
+ DB_MPOOL_FINFO *finfop;
+ DB_MPOOLFILE **retp;
+{
+ DB_ENV *dbenv;
+ DB_MPOOLFILE *dbmfp;
+ DB_MPOOL_FINFO finfo;
+ db_pgno_t last_pgno;
+ size_t maxmap;
+ u_int32_t mbytes, bytes;
+ int ret;
+ u_int8_t idbuf[DB_FILE_ID_LEN];
+ char *rpath;
+
+ dbenv = dbmp->dbenv;
+ ret = 0;
+ rpath = NULL;
+
+ /*
+ * If mfp is provided, we take the DB_MPOOL_FINFO information from
+ * the mfp. We don't bother initializing everything, because some
+ * of them are expensive to acquire. If no mfp is provided and the
+ * finfop argument is NULL, we default the values.
+ */
+ if (finfop == NULL) {
+ memset(&finfo, 0, sizeof(finfo));
+ if (mfp != NULL) {
+ finfo.ftype = mfp->ftype;
+ finfo.pgcookie = NULL;
+ finfo.fileid = NULL;
+ finfo.lsn_offset = mfp->lsn_off;
+ finfo.clear_len = mfp->clear_len;
+ } else {
+ finfo.ftype = 0;
+ finfo.pgcookie = NULL;
+ finfo.fileid = NULL;
+ finfo.lsn_offset = -1;
+ finfo.clear_len = 0;
+ }
+ finfop = &finfo;
+ }
+
+ /* Allocate and initialize the per-process structure. */
+ if ((ret = __os_calloc(1, sizeof(DB_MPOOLFILE), &dbmfp)) != 0)
+ return (ret);
+ dbmfp->dbmp = dbmp;
+ dbmfp->fd = -1;
+ dbmfp->ref = 1;
+ if (LF_ISSET(DB_RDONLY))
+ F_SET(dbmfp, MP_READONLY);
+
+ if (path == NULL) {
+ if (LF_ISSET(DB_RDONLY)) {
+ __db_err(dbenv,
+ "memp_fopen: temporary files can't be readonly");
+ ret = EINVAL;
+ goto err;
+ }
+ last_pgno = 0;
+ } else {
+ /* Get the real name for this file and open it. */
+ if ((ret = __db_appname(dbenv,
+ DB_APP_DATA, NULL, path, 0, NULL, &rpath)) != 0)
+ goto err;
+ if ((ret = __db_open(rpath,
+ LF_ISSET(DB_CREATE | DB_RDONLY),
+ DB_CREATE | DB_RDONLY, mode, &dbmfp->fd)) != 0) {
+ __db_err(dbenv, "%s: %s", rpath, strerror(ret));
+ goto err;
+ }
+
+ /*
+ * Don't permit files that aren't a multiple of the pagesize,
+ * and find the number of the last page in the file, all the
+ * time being careful not to overflow 32 bits.
+ *
+ * !!!
+ * We can't use off_t's here, or in any code in the mainline
+ * library for that matter. (We have to use them in the os
+ * stubs, of course, as there are system calls that take them
+ * as arguments.) The reason is that some customers build in
+ * environments where an off_t is 32-bits, but still run where
+ * offsets are 64-bits, and they pay us a lot of money.
+ */
+ if ((ret = __os_ioinfo(rpath,
+ dbmfp->fd, &mbytes, &bytes, NULL)) != 0) {
+ __db_err(dbenv, "%s: %s", rpath, strerror(ret));
+ goto err;
+ }
+
+ /* Page sizes have to be a power-of-two, ignore mbytes. */
+ if (bytes % pagesize != 0) {
+ __db_err(dbenv,
+ "%s: file size not a multiple of the pagesize",
+ rpath);
+ ret = EINVAL;
+ goto err;
+ }
+
+ last_pgno = mbytes * (MEGABYTE / pagesize);
+ last_pgno += bytes / pagesize;
+
+ /* Correction: page numbers are zero-based, not 1-based. */
+ if (last_pgno != 0)
+ --last_pgno;
+
+ /*
+ * Get the file id if we weren't given one. Generated file id's
+ * don't use timestamps, otherwise there'd be no chance of any
+ * other process joining the party.
+ */
+ if (finfop->fileid == NULL) {
+ if ((ret = __os_fileid(dbenv, rpath, 0, idbuf)) != 0)
+ goto err;
+ finfop->fileid = idbuf;
+ }
+ }
+
+ /*
+ * If we weren't provided an underlying shared object to join with,
+ * find/allocate the shared file objects. Also allocate space for
+ * for the per-process thread lock.
+ */
+ if (needlock)
+ LOCKREGION(dbmp);
+
+ if (mfp == NULL)
+ ret = __memp_mf_open(dbmp,
+ path, pagesize, last_pgno, finfop, &mfp);
+ else {
+ ++mfp->ref;
+ ret = 0;
+ }
+ if (ret == 0 &&
+ F_ISSET(dbmp, MP_LOCKHANDLE) && (ret =
+ __memp_alloc(dbmp, sizeof(db_mutex_t), NULL, &dbmfp->mutexp)) == 0)
+ LOCKINIT(dbmp, dbmfp->mutexp);
+
+ if (needlock)
+ UNLOCKREGION(dbmp);
+ if (ret != 0)
+ goto err;
+
+ dbmfp->mfp = mfp;
+
+ /*
+ * If a file:
+ * + is read-only
+ * + isn't temporary
+ * + doesn't require any pgin/pgout support
+ * + the DB_NOMMAP flag wasn't set
+ * + and is less than mp_mmapsize bytes in size
+ *
+ * we can mmap it instead of reading/writing buffers. Don't do error
+ * checking based on the mmap call failure. We want to do normal I/O
+ * on the file if the reason we failed was because the file was on an
+ * NFS mounted partition, and we can fail in buffer I/O just as easily
+ * as here.
+ *
+ * XXX
+ * We'd like to test to see if the file is too big to mmap. Since we
+ * don't know what size or type off_t's or size_t's are, or the largest
+ * unsigned integral type is, or what random insanity the local C
+ * compiler will perpetrate, doing the comparison in a portable way is
+ * flatly impossible. Hope that mmap fails if the file is too large.
+ */
+#define DB_MAXMMAPSIZE (10 * 1024 * 1024) /* 10 Mb. */
+ if (F_ISSET(mfp, MP_CAN_MMAP)) {
+ if (!F_ISSET(dbmfp, MP_READONLY))
+ F_CLR(mfp, MP_CAN_MMAP);
+ if (path == NULL)
+ F_CLR(mfp, MP_CAN_MMAP);
+ if (finfop->ftype != 0)
+ F_CLR(mfp, MP_CAN_MMAP);
+ if (LF_ISSET(DB_NOMMAP))
+ F_CLR(mfp, MP_CAN_MMAP);
+ maxmap = dbenv == NULL || dbenv->mp_mmapsize == 0 ?
+ DB_MAXMMAPSIZE : dbenv->mp_mmapsize;
+ if (mbytes > maxmap / MEGABYTE ||
+ (mbytes == maxmap / MEGABYTE && bytes >= maxmap % MEGABYTE))
+ F_CLR(mfp, MP_CAN_MMAP);
+ }
+ dbmfp->addr = NULL;
+ if (F_ISSET(mfp, MP_CAN_MMAP)) {
+ dbmfp->len = (size_t)mbytes * MEGABYTE + bytes;
+ if (__db_mapfile(rpath,
+ dbmfp->fd, dbmfp->len, 1, &dbmfp->addr) != 0) {
+ dbmfp->addr = NULL;
+ F_CLR(mfp, MP_CAN_MMAP);
+ }
+ }
+ if (rpath != NULL)
+ __os_freestr(rpath);
+
+ LOCKHANDLE(dbmp, dbmp->mutexp);
+ TAILQ_INSERT_TAIL(&dbmp->dbmfq, dbmfp, q);
+ UNLOCKHANDLE(dbmp, dbmp->mutexp);
+
+ *retp = dbmfp;
+ return (0);
+
+err: /*
+ * Note that we do not have to free the thread mutex, because we
+ * never get to here after we have successfully allocated it.
+ */
+ if (rpath != NULL)
+ __os_freestr(rpath);
+ if (dbmfp->fd != -1)
+ (void)__os_close(dbmfp->fd);
+ if (dbmfp != NULL)
+ __os_free(dbmfp, sizeof(DB_MPOOLFILE));
+ return (ret);
+}
+
+/*
+ * __memp_mf_open --
+ * Open an MPOOLFILE.
+ */
+static int
+__memp_mf_open(dbmp, path, pagesize, last_pgno, finfop, retp)
+ DB_MPOOL *dbmp;
+ const char *path;
+ size_t pagesize;
+ db_pgno_t last_pgno;
+ DB_MPOOL_FINFO *finfop;
+ MPOOLFILE **retp;
+{
+ MPOOLFILE *mfp;
+ int ret;
+ void *p;
+
+#define ISTEMPORARY (path == NULL)
+
+ /*
+ * Walk the list of MPOOLFILE's, looking for a matching file.
+ * Temporary files can't match previous files.
+ */
+ if (!ISTEMPORARY)
+ for (mfp = SH_TAILQ_FIRST(&dbmp->mp->mpfq, __mpoolfile);
+ mfp != NULL; mfp = SH_TAILQ_NEXT(mfp, q, __mpoolfile)) {
+ if (F_ISSET(mfp, MP_TEMP))
+ continue;
+ if (!memcmp(finfop->fileid,
+ R_ADDR(dbmp, mfp->fileid_off), DB_FILE_ID_LEN)) {
+ if (finfop->clear_len != mfp->clear_len ||
+ finfop->ftype != mfp->ftype ||
+ pagesize != mfp->stat.st_pagesize) {
+ __db_err(dbmp->dbenv,
+ "%s: ftype, clear length or pagesize changed",
+ path);
+ return (EINVAL);
+ }
+
+ /* Found it: increment the reference count. */
+ ++mfp->ref;
+ *retp = mfp;
+ return (0);
+ }
+ }
+
+ /* Allocate a new MPOOLFILE. */
+ if ((ret = __memp_alloc(dbmp, sizeof(MPOOLFILE), NULL, &mfp)) != 0)
+ return (ret);
+ *retp = mfp;
+
+ /* Initialize the structure. */
+ memset(mfp, 0, sizeof(MPOOLFILE));
+ mfp->ref = 1;
+ mfp->ftype = finfop->ftype;
+ mfp->lsn_off = finfop->lsn_offset;
+ mfp->clear_len = finfop->clear_len;
+
+ /*
+ * If the user specifies DB_MPOOL_LAST or DB_MPOOL_NEW on a memp_fget,
+ * we have to know the last page in the file. Figure it out and save
+ * it away.
+ */
+ mfp->stat.st_pagesize = pagesize;
+ mfp->orig_last_pgno = mfp->last_pgno = last_pgno;
+
+ if (ISTEMPORARY)
+ F_SET(mfp, MP_TEMP);
+ else {
+ /* Copy the file path into shared memory. */
+ if ((ret = __memp_alloc(dbmp,
+ strlen(path) + 1, &mfp->path_off, &p)) != 0)
+ goto err;
+ memcpy(p, path, strlen(path) + 1);
+
+ /* Copy the file identification string into shared memory. */
+ if ((ret = __memp_alloc(dbmp,
+ DB_FILE_ID_LEN, &mfp->fileid_off, &p)) != 0)
+ goto err;
+ memcpy(p, finfop->fileid, DB_FILE_ID_LEN);
+
+ F_SET(mfp, MP_CAN_MMAP);
+ }
+
+ /* Copy the page cookie into shared memory. */
+ if (finfop->pgcookie == NULL || finfop->pgcookie->size == 0) {
+ mfp->pgcookie_len = 0;
+ mfp->pgcookie_off = 0;
+ } else {
+ if ((ret = __memp_alloc(dbmp,
+ finfop->pgcookie->size, &mfp->pgcookie_off, &p)) != 0)
+ goto err;
+ memcpy(p, finfop->pgcookie->data, finfop->pgcookie->size);
+ mfp->pgcookie_len = finfop->pgcookie->size;
+ }
+
+ /* Prepend the MPOOLFILE to the list of MPOOLFILE's. */
+ SH_TAILQ_INSERT_HEAD(&dbmp->mp->mpfq, mfp, q, __mpoolfile);
+
+ if (0) {
+err: if (mfp->path_off != 0)
+ __db_shalloc_free(dbmp->addr,
+ R_ADDR(dbmp, mfp->path_off));
+ if (mfp->fileid_off != 0)
+ __db_shalloc_free(dbmp->addr,
+ R_ADDR(dbmp, mfp->fileid_off));
+ if (mfp != NULL)
+ __db_shalloc_free(dbmp->addr, mfp);
+ mfp = NULL;
+ }
+ return (0);
+}
+
+/*
+ * memp_fclose --
+ * Close a backing file for the memory pool.
+ */
+int
+memp_fclose(dbmfp)
+ DB_MPOOLFILE *dbmfp;
+{
+ DB_MPOOL *dbmp;
+ int ret, t_ret;
+
+ dbmp = dbmfp->dbmp;
+ ret = 0;
+
+ MP_PANIC_CHECK(dbmp);
+
+ for (;;) {
+ LOCKHANDLE(dbmp, dbmp->mutexp);
+
+ /*
+ * We have to reference count DB_MPOOLFILE structures as other
+ * threads may be using them. The problem only happens if the
+ * application makes a bad design choice. Here's the path:
+ *
+ * Thread A opens a database.
+ * Thread B uses thread A's DB_MPOOLFILE to write a buffer
+ * in order to free up memory in the mpool cache.
+ * Thread A closes the database while thread B is using the
+ * DB_MPOOLFILE structure.
+ *
+ * By opening all databases before creating the threads, and
+ * closing them after the threads have exited, applications
+ * get better performance and avoid the problem path entirely.
+ *
+ * Regardless, holding the DB_MPOOLFILE to flush a dirty buffer
+ * is a short-term lock, even in worst case, since we better be
+ * the only thread of control using the DB_MPOOLFILE structure
+ * to read pages *into* the cache. Wait until we're the only
+ * reference holder and remove the DB_MPOOLFILE structure from
+ * the list, so nobody else can even find it.
+ */
+ if (dbmfp->ref == 1) {
+ TAILQ_REMOVE(&dbmp->dbmfq, dbmfp, q);
+ break;
+ }
+ UNLOCKHANDLE(dbmp, dbmp->mutexp);
+
+ (void)__os_sleep(1, 0);
+ }
+ UNLOCKHANDLE(dbmp, dbmp->mutexp);
+
+ /* Complain if pinned blocks never returned. */
+ if (dbmfp->pinref != 0)
+ __db_err(dbmp->dbenv, "%s: close: %lu blocks left pinned",
+ __memp_fn(dbmfp), (u_long)dbmfp->pinref);
+
+ /* Close the underlying MPOOLFILE. */
+ (void)__memp_mf_close(dbmp, dbmfp);
+
+ /* Discard any mmap information. */
+ if (dbmfp->addr != NULL &&
+ (ret = __db_unmapfile(dbmfp->addr, dbmfp->len)) != 0)
+ __db_err(dbmp->dbenv,
+ "%s: %s", __memp_fn(dbmfp), strerror(ret));
+
+ /* Close the file; temporary files may not yet have been created. */
+ if (dbmfp->fd != -1 && (t_ret = __os_close(dbmfp->fd)) != 0) {
+ __db_err(dbmp->dbenv,
+ "%s: %s", __memp_fn(dbmfp), strerror(t_ret));
+ if (ret != 0)
+ t_ret = ret;
+ }
+
+ /* Free memory. */
+ if (dbmfp->mutexp != NULL) {
+ LOCKREGION(dbmp);
+ __db_shalloc_free(dbmp->addr, dbmfp->mutexp);
+ UNLOCKREGION(dbmp);
+ }
+
+ /* Discard the DB_MPOOLFILE structure. */
+ __os_free(dbmfp, sizeof(DB_MPOOLFILE));
+
+ return (ret);
+}
+
+/*
+ * __memp_mf_close --
+ * Close down an MPOOLFILE.
+ */
+static int
+__memp_mf_close(dbmp, dbmfp)
+ DB_MPOOL *dbmp;
+ DB_MPOOLFILE *dbmfp;
+{
+ BH *bhp, *nbhp;
+ MPOOL *mp;
+ MPOOLFILE *mfp;
+ size_t mf_offset;
+
+ mp = dbmp->mp;
+ mfp = dbmfp->mfp;
+
+ LOCKREGION(dbmp);
+
+ /* If more than a single reference, simply decrement. */
+ if (mfp->ref > 1) {
+ --mfp->ref;
+ goto ret1;
+ }
+
+ /*
+ * Move any BH's held by the file to the free list. We don't free the
+ * memory itself because we may be discarding the memory pool, and it's
+ * fairly expensive to reintegrate the buffers back into the region for
+ * no purpose.
+ */
+ mf_offset = R_OFFSET(dbmp, mfp);
+ for (bhp = SH_TAILQ_FIRST(&mp->bhq, __bh); bhp != NULL; bhp = nbhp) {
+ nbhp = SH_TAILQ_NEXT(bhp, q, __bh);
+
+#ifdef DEBUG_NO_DIRTY
+ /* Complain if we find any blocks that were left dirty. */
+ if (F_ISSET(bhp, BH_DIRTY))
+ __db_err(dbmp->dbenv,
+ "%s: close: pgno %lu left dirty; ref %lu",
+ __memp_fn(dbmfp),
+ (u_long)bhp->pgno, (u_long)bhp->ref);
+#endif
+
+ if (bhp->mf_offset == mf_offset) {
+ if (F_ISSET(bhp, BH_DIRTY)) {
+ ++mp->stat.st_page_clean;
+ --mp->stat.st_page_dirty;
+ }
+ __memp_bhfree(dbmp, mfp, bhp, 0);
+ SH_TAILQ_INSERT_HEAD(&mp->bhfq, bhp, q, __bh);
+ }
+ }
+
+ /* Delete from the list of MPOOLFILEs. */
+ SH_TAILQ_REMOVE(&mp->mpfq, mfp, q, __mpoolfile);
+
+ /* Free the space. */
+ if (mfp->path_off != 0)
+ __db_shalloc_free(dbmp->addr, R_ADDR(dbmp, mfp->path_off));
+ if (mfp->fileid_off != 0)
+ __db_shalloc_free(dbmp->addr, R_ADDR(dbmp, mfp->fileid_off));
+ if (mfp->pgcookie_off != 0)
+ __db_shalloc_free(dbmp->addr, R_ADDR(dbmp, mfp->pgcookie_off));
+ __db_shalloc_free(dbmp->addr, mfp);
+
+ret1: UNLOCKREGION(dbmp);
+ return (0);
+}
diff --git a/usr/src/cmd/sendmail/db/mp/mp_fput.c b/usr/src/cmd/sendmail/db/mp/mp_fput.c
new file mode 100644
index 0000000000..5bba53b414
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/mp/mp_fput.c
@@ -0,0 +1,151 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)mp_fput.c 10.24 (Sleepycat) 9/27/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <errno.h>
+#endif
+
+#include "db_int.h"
+#include "shqueue.h"
+#include "db_shash.h"
+#include "mp.h"
+#include "common_ext.h"
+
+/*
+ * memp_fput --
+ * Mpool file put function.
+ */
+int
+memp_fput(dbmfp, pgaddr, flags)
+ DB_MPOOLFILE *dbmfp;
+ void *pgaddr;
+ u_int32_t flags;
+{
+ BH *bhp;
+ DB_MPOOL *dbmp;
+ MPOOL *mp;
+ int wrote, ret;
+
+ dbmp = dbmfp->dbmp;
+ mp = dbmp->mp;
+
+ MP_PANIC_CHECK(dbmp);
+
+ /* Validate arguments. */
+ if (flags) {
+ if ((ret = __db_fchk(dbmp->dbenv, "memp_fput", flags,
+ DB_MPOOL_CLEAN | DB_MPOOL_DIRTY | DB_MPOOL_DISCARD)) != 0)
+ return (ret);
+ if ((ret = __db_fcchk(dbmp->dbenv, "memp_fput",
+ flags, DB_MPOOL_CLEAN, DB_MPOOL_DIRTY)) != 0)
+ return (ret);
+
+ if (LF_ISSET(DB_MPOOL_DIRTY) && F_ISSET(dbmfp, MP_READONLY)) {
+ __db_err(dbmp->dbenv,
+ "%s: dirty flag set for readonly file page",
+ __memp_fn(dbmfp));
+ return (EACCES);
+ }
+ }
+
+ LOCKREGION(dbmp);
+
+ /* Decrement the pinned reference count. */
+ if (dbmfp->pinref == 0)
+ __db_err(dbmp->dbenv,
+ "%s: put: more blocks returned than retrieved",
+ __memp_fn(dbmfp));
+ else
+ --dbmfp->pinref;
+
+ /*
+ * If we're mapping the file, there's nothing to do. Because we can
+ * stop mapping the file at any time, we have to check on each buffer
+ * to see if the address we gave the application was part of the map
+ * region.
+ */
+ if (dbmfp->addr != NULL && pgaddr >= dbmfp->addr &&
+ (u_int8_t *)pgaddr <= (u_int8_t *)dbmfp->addr + dbmfp->len) {
+ UNLOCKREGION(dbmp);
+ return (0);
+ }
+
+ /* Convert the page address to a buffer header. */
+ bhp = (BH *)((u_int8_t *)pgaddr - SSZA(BH, buf));
+
+ /* Set/clear the page bits. */
+ if (LF_ISSET(DB_MPOOL_CLEAN) && F_ISSET(bhp, BH_DIRTY)) {
+ ++mp->stat.st_page_clean;
+ --mp->stat.st_page_dirty;
+ F_CLR(bhp, BH_DIRTY);
+ }
+ if (LF_ISSET(DB_MPOOL_DIRTY) && !F_ISSET(bhp, BH_DIRTY)) {
+ --mp->stat.st_page_clean;
+ ++mp->stat.st_page_dirty;
+ F_SET(bhp, BH_DIRTY);
+ }
+ if (LF_ISSET(DB_MPOOL_DISCARD))
+ F_SET(bhp, BH_DISCARD);
+
+ /*
+ * Check for a reference count going to zero. This can happen if the
+ * application returns a page twice.
+ */
+ if (bhp->ref == 0) {
+ __db_err(dbmp->dbenv, "%s: page %lu: unpinned page returned",
+ __memp_fn(dbmfp), (u_long)bhp->pgno);
+ UNLOCKREGION(dbmp);
+ return (EINVAL);
+ }
+
+ /*
+ * If more than one reference to the page, we're done. Ignore the
+ * discard flags (for now) and leave it at its position in the LRU
+ * chain. The rest gets done at last reference close.
+ */
+ if (--bhp->ref > 0) {
+ UNLOCKREGION(dbmp);
+ return (0);
+ }
+
+ /* Move the buffer to the head/tail of the LRU chain. */
+ SH_TAILQ_REMOVE(&mp->bhq, bhp, q, __bh);
+ if (F_ISSET(bhp, BH_DISCARD))
+ SH_TAILQ_INSERT_HEAD(&mp->bhq, bhp, q, __bh);
+ else
+ SH_TAILQ_INSERT_TAIL(&mp->bhq, bhp, q);
+
+ /*
+ * If this buffer is scheduled for writing because of a checkpoint, we
+ * need to write it (if we marked it dirty), or update the checkpoint
+ * counters (if we didn't mark it dirty). If we try to write it and
+ * can't, that's not necessarily an error, but set a flag so that the
+ * next time the memp_sync function runs we try writing it there, as
+ * the checkpoint application better be able to write all of the files.
+ */
+ if (F_ISSET(bhp, BH_WRITE))
+ if (F_ISSET(bhp, BH_DIRTY)) {
+ if (__memp_bhwrite(dbmp,
+ dbmfp->mfp, bhp, NULL, &wrote) != 0 || !wrote)
+ F_SET(mp, MP_LSN_RETRY);
+ } else {
+ F_CLR(bhp, BH_WRITE);
+
+ --dbmfp->mfp->lsn_cnt;
+ --mp->lsn_cnt;
+ }
+
+ UNLOCKREGION(dbmp);
+ return (0);
+}
diff --git a/usr/src/cmd/sendmail/db/mp/mp_fset.c b/usr/src/cmd/sendmail/db/mp/mp_fset.c
new file mode 100644
index 0000000000..1940d3b198
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/mp/mp_fset.c
@@ -0,0 +1,83 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)mp_fset.c 10.16 (Sleepycat) 9/27/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <errno.h>
+#endif
+
+#include "db_int.h"
+#include "shqueue.h"
+#include "db_shash.h"
+#include "mp.h"
+#include "common_ext.h"
+
+/*
+ * memp_fset --
+ * Mpool page set-flag routine.
+ */
+int
+memp_fset(dbmfp, pgaddr, flags)
+ DB_MPOOLFILE *dbmfp;
+ void *pgaddr;
+ u_int32_t flags;
+{
+ BH *bhp;
+ DB_MPOOL *dbmp;
+ MPOOL *mp;
+ int ret;
+
+ dbmp = dbmfp->dbmp;
+ mp = dbmp->mp;
+
+ MP_PANIC_CHECK(dbmp);
+
+ /* Validate arguments. */
+ if (flags == 0)
+ return (__db_ferr(dbmp->dbenv, "memp_fset", 1));
+
+ if ((ret = __db_fchk(dbmp->dbenv, "memp_fset", flags,
+ DB_MPOOL_DIRTY | DB_MPOOL_CLEAN | DB_MPOOL_DISCARD)) != 0)
+ return (ret);
+ if ((ret = __db_fcchk(dbmp->dbenv, "memp_fset",
+ flags, DB_MPOOL_CLEAN, DB_MPOOL_DIRTY)) != 0)
+ return (ret);
+
+ if (LF_ISSET(DB_MPOOL_DIRTY) && F_ISSET(dbmfp, MP_READONLY)) {
+ __db_err(dbmp->dbenv,
+ "%s: dirty flag set for readonly file page",
+ __memp_fn(dbmfp));
+ return (EACCES);
+ }
+
+ /* Convert the page address to a buffer header. */
+ bhp = (BH *)((u_int8_t *)pgaddr - SSZA(BH, buf));
+
+ LOCKREGION(dbmp);
+
+ if (LF_ISSET(DB_MPOOL_CLEAN) && F_ISSET(bhp, BH_DIRTY)) {
+ ++mp->stat.st_page_clean;
+ --mp->stat.st_page_dirty;
+ F_CLR(bhp, BH_DIRTY);
+ }
+ if (LF_ISSET(DB_MPOOL_DIRTY) && !F_ISSET(bhp, BH_DIRTY)) {
+ --mp->stat.st_page_clean;
+ ++mp->stat.st_page_dirty;
+ F_SET(bhp, BH_DIRTY);
+ }
+ if (LF_ISSET(DB_MPOOL_DISCARD))
+ F_SET(bhp, BH_DISCARD);
+
+ UNLOCKREGION(dbmp);
+ return (0);
+}
diff --git a/usr/src/cmd/sendmail/db/mp/mp_open.c b/usr/src/cmd/sendmail/db/mp/mp_open.c
new file mode 100644
index 0000000000..4c90fc438f
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/mp/mp_open.c
@@ -0,0 +1,221 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)mp_open.c 10.27 (Sleepycat) 10/1/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <errno.h>
+#include <string.h>
+#endif
+
+#include "db_int.h"
+#include "shqueue.h"
+#include "db_shash.h"
+#include "mp.h"
+#include "common_ext.h"
+
+/*
+ * memp_open --
+ * Initialize and/or join a memory pool.
+ */
+int
+memp_open(path, flags, mode, dbenv, retp)
+ const char *path;
+ u_int32_t flags;
+ int mode;
+ DB_ENV *dbenv;
+ DB_MPOOL **retp;
+{
+ DB_MPOOL *dbmp;
+ size_t cachesize;
+ int is_private, ret;
+
+ /* Validate arguments. */
+#ifdef HAVE_SPINLOCKS
+#define OKFLAGS (DB_CREATE | DB_MPOOL_PRIVATE | DB_NOMMAP | DB_THREAD)
+#else
+#define OKFLAGS (DB_CREATE | DB_MPOOL_PRIVATE | DB_NOMMAP)
+#endif
+ if ((ret = __db_fchk(dbenv, "memp_open", flags, OKFLAGS)) != 0)
+ return (ret);
+
+ /* Extract fields from DB_ENV structure. */
+ cachesize = dbenv == NULL ? 0 : dbenv->mp_size;
+
+ /* Create and initialize the DB_MPOOL structure. */
+ if ((ret = __os_calloc(1, sizeof(DB_MPOOL), &dbmp)) != 0)
+ return (ret);
+ LIST_INIT(&dbmp->dbregq);
+ TAILQ_INIT(&dbmp->dbmfq);
+
+ dbmp->dbenv = dbenv;
+
+ /* Decide if it's possible for anyone else to access the pool. */
+ is_private =
+ (dbenv == NULL && path == NULL) || LF_ISSET(DB_MPOOL_PRIVATE);
+
+ /*
+ * Map in the region. We do locking regardless, as portions of it are
+ * implemented in common code (if we put the region in a file, that is).
+ */
+ F_SET(dbmp, MP_LOCKREGION);
+ if ((ret = __memp_ropen(dbmp,
+ path, cachesize, mode, is_private, LF_ISSET(DB_CREATE))) != 0)
+ goto err;
+ F_CLR(dbmp, MP_LOCKREGION);
+
+ /*
+ * If there's concurrent access, then we have to lock the region.
+ * If it's threaded, then we have to lock both the handles and the
+ * region, and we need to allocate a mutex for that purpose.
+ */
+ if (!is_private)
+ F_SET(dbmp, MP_LOCKREGION);
+ if (LF_ISSET(DB_THREAD)) {
+ F_SET(dbmp, MP_LOCKHANDLE | MP_LOCKREGION);
+ LOCKREGION(dbmp);
+ ret = __memp_alloc(dbmp,
+ sizeof(db_mutex_t), NULL, &dbmp->mutexp);
+ UNLOCKREGION(dbmp);
+ if (ret != 0) {
+ (void)memp_close(dbmp);
+ goto err;
+ }
+ LOCKINIT(dbmp, dbmp->mutexp);
+ }
+
+ *retp = dbmp;
+ return (0);
+
+err: if (dbmp != NULL)
+ __os_free(dbmp, sizeof(DB_MPOOL));
+ return (ret);
+}
+
+/*
+ * memp_close --
+ * Close a memory pool.
+ */
+int
+memp_close(dbmp)
+ DB_MPOOL *dbmp;
+{
+ DB_MPOOLFILE *dbmfp;
+ DB_MPREG *mpreg;
+ int ret, t_ret;
+
+ ret = 0;
+
+ MP_PANIC_CHECK(dbmp);
+
+ /* Discard DB_MPREGs. */
+ while ((mpreg = LIST_FIRST(&dbmp->dbregq)) != NULL) {
+ LIST_REMOVE(mpreg, q);
+ __os_free(mpreg, sizeof(DB_MPREG));
+ }
+
+ /* Discard DB_MPOOLFILEs. */
+ while ((dbmfp = TAILQ_FIRST(&dbmp->dbmfq)) != NULL)
+ if ((t_ret = memp_fclose(dbmfp)) != 0 && ret == 0)
+ ret = t_ret;
+
+ /* Discard thread mutex. */
+ if (F_ISSET(dbmp, MP_LOCKHANDLE)) {
+ LOCKREGION(dbmp);
+ __db_shalloc_free(dbmp->addr, dbmp->mutexp);
+ UNLOCKREGION(dbmp);
+ }
+
+ /* Close the region. */
+ if ((t_ret = __db_rdetach(&dbmp->reginfo)) != 0 && ret == 0)
+ ret = t_ret;
+
+ if (dbmp->reginfo.path != NULL)
+ __os_freestr(dbmp->reginfo.path);
+ __os_free(dbmp, sizeof(DB_MPOOL));
+
+ return (ret);
+}
+
+/*
+ * __memp_panic --
+ * Panic a memory pool.
+ *
+ * PUBLIC: void __memp_panic __P((DB_ENV *));
+ */
+void
+__memp_panic(dbenv)
+ DB_ENV *dbenv;
+{
+ if (dbenv->mp_info != NULL)
+ dbenv->mp_info->mp->rlayout.panic = 1;
+}
+
+/*
+ * memp_unlink --
+ * Exit a memory pool.
+ */
+int
+memp_unlink(path, force, dbenv)
+ const char *path;
+ int force;
+ DB_ENV *dbenv;
+{
+ REGINFO reginfo;
+ int ret;
+
+ memset(&reginfo, 0, sizeof(reginfo));
+ reginfo.dbenv = dbenv;
+ reginfo.appname = DB_APP_NONE;
+ if (path != NULL && (ret = __os_strdup(path, &reginfo.path)) != 0)
+ return (ret);
+ reginfo.file = DB_DEFAULT_MPOOL_FILE;
+ ret = __db_runlink(&reginfo, force);
+ if (reginfo.path != NULL)
+ __os_freestr(reginfo.path);
+ return (ret);
+}
+
+/*
+ * memp_register --
+ * Register a file type's pgin, pgout routines.
+ */
+int
+memp_register(dbmp, ftype, pgin, pgout)
+ DB_MPOOL *dbmp;
+ int ftype;
+ int (*pgin) __P((db_pgno_t, void *, DBT *));
+ int (*pgout) __P((db_pgno_t, void *, DBT *));
+{
+ DB_MPREG *mpr;
+ int ret;
+
+ MP_PANIC_CHECK(dbmp);
+
+ if ((ret = __os_malloc(sizeof(DB_MPREG), NULL, &mpr)) != 0)
+ return (ret);
+
+ mpr->ftype = ftype;
+ mpr->pgin = pgin;
+ mpr->pgout = pgout;
+
+ /*
+ * Insert at the head. Because we do a linear walk, we'll find
+ * the most recent registry in the case of multiple entries, so
+ * we don't have to check for multiple registries.
+ */
+ LOCKHANDLE(dbmp, dbmp->mutexp);
+ LIST_INSERT_HEAD(&dbmp->dbregq, mpr, q);
+ UNLOCKHANDLE(dbmp, dbmp->mutexp);
+
+ return (0);
+}
diff --git a/usr/src/cmd/sendmail/db/mp/mp_pr.c b/usr/src/cmd/sendmail/db/mp/mp_pr.c
new file mode 100644
index 0000000000..84c782e781
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/mp/mp_pr.c
@@ -0,0 +1,304 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)mp_pr.c 10.30 (Sleepycat) 10/1/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#endif
+
+#include "db_int.h"
+#include "db_page.h"
+#include "shqueue.h"
+#include "db_shash.h"
+#include "mp.h"
+#include "db_auto.h"
+#include "db_ext.h"
+#include "common_ext.h"
+
+static void __memp_pbh __P((DB_MPOOL *, BH *, size_t *, FILE *));
+
+/*
+ * memp_stat --
+ * Display MPOOL statistics.
+ */
+int
+memp_stat(dbmp, gspp, fspp, db_malloc)
+ DB_MPOOL *dbmp;
+ DB_MPOOL_STAT **gspp;
+ DB_MPOOL_FSTAT ***fspp;
+ void *(*db_malloc) __P((size_t));
+{
+ DB_MPOOL_FSTAT **tfsp;
+ MPOOLFILE *mfp;
+ size_t len, nlen;
+ int ret;
+ char *name;
+
+ MP_PANIC_CHECK(dbmp);
+
+ /* Allocate space for the global statistics. */
+ if (gspp != NULL) {
+ *gspp = NULL;
+
+ if ((ret = __os_malloc(sizeof(**gspp), db_malloc, gspp)) != 0)
+ return (ret);
+
+ LOCKREGION(dbmp);
+
+ /* Copy out the global statistics. */
+ **gspp = dbmp->mp->stat;
+ (*gspp)->st_hash_buckets = dbmp->mp->htab_buckets;
+ (*gspp)->st_region_wait =
+ dbmp->mp->rlayout.lock.mutex_set_wait;
+ (*gspp)->st_region_nowait =
+ dbmp->mp->rlayout.lock.mutex_set_nowait;
+ (*gspp)->st_refcnt = dbmp->mp->rlayout.refcnt;
+ (*gspp)->st_regsize = dbmp->mp->rlayout.size;
+
+ UNLOCKREGION(dbmp);
+ }
+
+ if (fspp != NULL) {
+ *fspp = NULL;
+
+ LOCKREGION(dbmp);
+
+ /* Count the MPOOLFILE structures. */
+ for (len = 0,
+ mfp = SH_TAILQ_FIRST(&dbmp->mp->mpfq, __mpoolfile);
+ mfp != NULL;
+ ++len, mfp = SH_TAILQ_NEXT(mfp, q, __mpoolfile))
+ ;
+
+ UNLOCKREGION(dbmp);
+
+ if (len == 0)
+ return (0);
+
+ /* Allocate space for the pointers. */
+ len = (len + 1) * sizeof(DB_MPOOL_FSTAT *);
+ if ((ret = __os_malloc(len, db_malloc, fspp)) != 0)
+ return (ret);
+
+ LOCKREGION(dbmp);
+
+ /* Build each individual entry. */
+ for (tfsp = *fspp,
+ mfp = SH_TAILQ_FIRST(&dbmp->mp->mpfq, __mpoolfile);
+ mfp != NULL;
+ ++tfsp, mfp = SH_TAILQ_NEXT(mfp, q, __mpoolfile)) {
+ name = __memp_fns(dbmp, mfp);
+ nlen = strlen(name);
+ len = sizeof(DB_MPOOL_FSTAT) + nlen + 1;
+ if ((ret = __os_malloc(len, db_malloc, tfsp)) != 0)
+ return (ret);
+ **tfsp = mfp->stat;
+ (*tfsp)->file_name = (char *)
+ (u_int8_t *)*tfsp + sizeof(DB_MPOOL_FSTAT);
+ memcpy((*tfsp)->file_name, name, nlen + 1);
+ }
+ *tfsp = NULL;
+
+ UNLOCKREGION(dbmp);
+ }
+ return (0);
+}
+
+/*
+ * __memp_fn --
+ * On errors we print whatever is available as the file name.
+ *
+ * PUBLIC: char * __memp_fn __P((DB_MPOOLFILE *));
+ */
+char *
+__memp_fn(dbmfp)
+ DB_MPOOLFILE *dbmfp;
+{
+ return (__memp_fns(dbmfp->dbmp, dbmfp->mfp));
+}
+
+/*
+ * __memp_fns --
+ * On errors we print whatever is available as the file name.
+ *
+ * PUBLIC: char * __memp_fns __P((DB_MPOOL *, MPOOLFILE *));
+ *
+ */
+char *
+__memp_fns(dbmp, mfp)
+ DB_MPOOL *dbmp;
+ MPOOLFILE *mfp;
+{
+ if (mfp->path_off == 0)
+ return ((char *)"temporary");
+
+ return ((char *)R_ADDR(dbmp, mfp->path_off));
+}
+
+#define FMAP_ENTRIES 200 /* Files we map. */
+
+#define MPOOL_DUMP_HASH 0x01 /* Debug hash chains. */
+#define MPOOL_DUMP_LRU 0x02 /* Debug LRU chains. */
+#define MPOOL_DUMP_MEM 0x04 /* Debug region memory. */
+#define MPOOL_DUMP_ALL 0x07 /* Debug all. */
+
+
+/*
+ * __memp_dump_region --
+ * Display MPOOL structures.
+ *
+ * PUBLIC: void __memp_dump_region __P((DB_MPOOL *, char *, FILE *));
+ */
+void
+__memp_dump_region(dbmp, area, fp)
+ DB_MPOOL *dbmp;
+ char *area;
+ FILE *fp;
+{
+ BH *bhp;
+ DB_HASHTAB *htabp;
+ DB_MPOOLFILE *dbmfp;
+ MPOOL *mp;
+ MPOOLFILE *mfp;
+ size_t bucket, fmap[FMAP_ENTRIES + 1];
+ u_int32_t flags;
+ int cnt;
+
+ /* Make it easy to call from the debugger. */
+ if (fp == NULL)
+ fp = stderr;
+
+ for (flags = 0; *area != '\0'; ++area)
+ switch (*area) {
+ case 'A':
+ LF_SET(MPOOL_DUMP_ALL);
+ break;
+ case 'h':
+ LF_SET(MPOOL_DUMP_HASH);
+ break;
+ case 'l':
+ LF_SET(MPOOL_DUMP_LRU);
+ break;
+ case 'm':
+ LF_SET(MPOOL_DUMP_MEM);
+ break;
+ }
+
+ LOCKREGION(dbmp);
+
+ mp = dbmp->mp;
+
+ /* Display MPOOL structures. */
+ (void)fprintf(fp, "%s\nPool (region addr 0x%lx, alloc addr 0x%lx)\n",
+ DB_LINE, (u_long)dbmp->reginfo.addr, (u_long)dbmp->addr);
+
+ /* Display the MPOOLFILE structures. */
+ cnt = 0;
+ for (mfp = SH_TAILQ_FIRST(&dbmp->mp->mpfq, __mpoolfile);
+ mfp != NULL; mfp = SH_TAILQ_NEXT(mfp, q, __mpoolfile), ++cnt) {
+ (void)fprintf(fp, "file #%d: %s: refs %lu, type %ld, %s\n",
+ cnt + 1, __memp_fns(dbmp, mfp), (u_long)mfp->ref,
+ (long)mfp->ftype,
+ F_ISSET(mfp, MP_CAN_MMAP) ? "mmap" : "read/write");
+ if (cnt < FMAP_ENTRIES)
+ fmap[cnt] = R_OFFSET(dbmp, mfp);
+ }
+
+ for (dbmfp = TAILQ_FIRST(&dbmp->dbmfq);
+ dbmfp != NULL; dbmfp = TAILQ_NEXT(dbmfp, q), ++cnt) {
+ (void)fprintf(fp, "file #%d: %s: fd: %d: per-process, %s\n",
+ cnt + 1, __memp_fn(dbmfp), dbmfp->fd,
+ F_ISSET(dbmfp, MP_READONLY) ? "readonly" : "read/write");
+ if (cnt < FMAP_ENTRIES)
+ fmap[cnt] = R_OFFSET(dbmp, mfp);
+ }
+ if (cnt < FMAP_ENTRIES)
+ fmap[cnt] = INVALID;
+ else
+ fmap[FMAP_ENTRIES] = INVALID;
+
+ /* Display the hash table list of BH's. */
+ if (LF_ISSET(MPOOL_DUMP_HASH)) {
+ (void)fprintf(fp,
+ "%s\nBH hash table (%lu hash slots)\npageno, file, ref, address\n",
+ DB_LINE, (u_long)mp->htab_buckets);
+ for (htabp = dbmp->htab,
+ bucket = 0; bucket < mp->htab_buckets; ++htabp, ++bucket) {
+ if (SH_TAILQ_FIRST(&dbmp->htab[bucket], __bh) != NULL)
+ (void)fprintf(fp, "%lu:\n", (u_long)bucket);
+ for (bhp = SH_TAILQ_FIRST(&dbmp->htab[bucket], __bh);
+ bhp != NULL; bhp = SH_TAILQ_NEXT(bhp, hq, __bh))
+ __memp_pbh(dbmp, bhp, fmap, fp);
+ }
+ }
+
+ /* Display the LRU list of BH's. */
+ if (LF_ISSET(MPOOL_DUMP_LRU)) {
+ (void)fprintf(fp, "%s\nBH LRU list\n", DB_LINE);
+ (void)fprintf(fp, "pageno, file, ref, address\n");
+ for (bhp = SH_TAILQ_FIRST(&dbmp->mp->bhq, __bh);
+ bhp != NULL; bhp = SH_TAILQ_NEXT(bhp, q, __bh))
+ __memp_pbh(dbmp, bhp, fmap, fp);
+ }
+
+ if (LF_ISSET(MPOOL_DUMP_MEM))
+ __db_shalloc_dump(dbmp->addr, fp);
+
+ UNLOCKREGION(dbmp);
+
+ /* Flush in case we're debugging. */
+ (void)fflush(fp);
+}
+
+/*
+ * __memp_pbh --
+ * Display a BH structure.
+ */
+static void
+__memp_pbh(dbmp, bhp, fmap, fp)
+ DB_MPOOL *dbmp;
+ BH *bhp;
+ size_t *fmap;
+ FILE *fp;
+{
+ static const FN fn[] = {
+ { BH_CALLPGIN, "callpgin" },
+ { BH_DIRTY, "dirty" },
+ { BH_DISCARD, "discard" },
+ { BH_LOCKED, "locked" },
+ { BH_TRASH, "trash" },
+ { BH_WRITE, "write" },
+ { 0 },
+ };
+ int i;
+
+ for (i = 0; i < FMAP_ENTRIES; ++i)
+ if (fmap[i] == INVALID || fmap[i] == bhp->mf_offset)
+ break;
+
+ if (fmap[i] == INVALID)
+ (void)fprintf(fp, " %4lu, %lu, %2lu, %lu",
+ (u_long)bhp->pgno, (u_long)bhp->mf_offset,
+ (u_long)bhp->ref, (u_long)R_OFFSET(dbmp, bhp));
+ else
+ (void)fprintf(fp, " %4lu, #%d, %2lu, %lu",
+ (u_long)bhp->pgno, i + 1,
+ (u_long)bhp->ref, (u_long)R_OFFSET(dbmp, bhp));
+
+ __db_prflags(bhp->flags, fn, fp);
+
+ (void)fprintf(fp, "\n");
+}
diff --git a/usr/src/cmd/sendmail/db/mp/mp_region.c b/usr/src/cmd/sendmail/db/mp/mp_region.c
new file mode 100644
index 0000000000..47e74bbdfe
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/mp/mp_region.c
@@ -0,0 +1,331 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)mp_region.c 10.35 (Sleepycat) 12/11/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <errno.h>
+#include <string.h>
+#endif
+
+#include "db_int.h"
+#include "shqueue.h"
+#include "db_shash.h"
+#include "mp.h"
+#include "common_ext.h"
+
+/*
+ * __memp_reg_alloc --
+ * Allocate some space in the mpool region, with locking.
+ *
+ * PUBLIC: int __memp_reg_alloc __P((DB_MPOOL *, size_t, size_t *, void *));
+ */
+int
+__memp_reg_alloc(dbmp, len, offsetp, retp)
+ DB_MPOOL *dbmp;
+ size_t len, *offsetp;
+ void *retp;
+{
+ int ret;
+
+ LOCKREGION(dbmp);
+ ret = __memp_alloc(dbmp, len, offsetp, retp);
+ UNLOCKREGION(dbmp);
+ return (ret);
+}
+
+/*
+ * __memp_alloc --
+ * Allocate some space in the mpool region.
+ *
+ * PUBLIC: int __memp_alloc __P((DB_MPOOL *, size_t, size_t *, void *));
+ */
+int
+__memp_alloc(dbmp, len, offsetp, retp)
+ DB_MPOOL *dbmp;
+ size_t len, *offsetp;
+ void *retp;
+{
+ BH *bhp, *nbhp;
+ MPOOL *mp;
+ MPOOLFILE *mfp;
+ size_t fsize, total;
+ int nomore, restart, ret, wrote;
+ void *p;
+
+ mp = dbmp->mp;
+
+ nomore = 0;
+alloc: if ((ret = __db_shalloc(dbmp->addr, len, MUTEX_ALIGNMENT, &p)) == 0) {
+ if (offsetp != NULL)
+ *offsetp = R_OFFSET(dbmp, p);
+ *(void **)retp = p;
+ return (0);
+ }
+ if (nomore) {
+ __db_err(dbmp->dbenv,
+ "Unable to allocate %lu bytes from mpool shared region: %s\n",
+ (u_long)len, strerror(ret));
+ return (ret);
+ }
+
+ /* Look for a buffer on the free list that's the right size. */
+ for (bhp =
+ SH_TAILQ_FIRST(&mp->bhfq, __bh); bhp != NULL; bhp = nbhp) {
+ nbhp = SH_TAILQ_NEXT(bhp, q, __bh);
+
+ if (__db_shsizeof(bhp) == len) {
+ SH_TAILQ_REMOVE(&mp->bhfq, bhp, q, __bh);
+ if (offsetp != NULL)
+ *offsetp = R_OFFSET(dbmp, bhp);
+ *(void **)retp = bhp;
+ return (0);
+ }
+ }
+
+ /* Discard from the free list until we've freed enough memory. */
+ total = 0;
+ for (bhp =
+ SH_TAILQ_FIRST(&mp->bhfq, __bh); bhp != NULL; bhp = nbhp) {
+ nbhp = SH_TAILQ_NEXT(bhp, q, __bh);
+
+ SH_TAILQ_REMOVE(&mp->bhfq, bhp, q, __bh);
+ __db_shalloc_free(dbmp->addr, bhp);
+ --mp->stat.st_page_clean;
+
+ /*
+ * Retry as soon as we've freed up sufficient space. If we
+ * will have to coalesce memory to satisfy the request, don't
+ * try until it's likely (possible?) that we'll succeed.
+ */
+ total += fsize = __db_shsizeof(bhp);
+ if (fsize >= len || total >= 3 * len)
+ goto alloc;
+ }
+
+retry: /* Find a buffer we can flush; pure LRU. */
+ restart = total = 0;
+ for (bhp =
+ SH_TAILQ_FIRST(&mp->bhq, __bh); bhp != NULL; bhp = nbhp) {
+ nbhp = SH_TAILQ_NEXT(bhp, q, __bh);
+
+ /* Ignore pinned or locked (I/O in progress) buffers. */
+ if (bhp->ref != 0 || F_ISSET(bhp, BH_LOCKED))
+ continue;
+
+ /* Find the associated MPOOLFILE. */
+ mfp = R_ADDR(dbmp, bhp->mf_offset);
+
+ /*
+ * Write the page if it's dirty.
+ *
+ * If we wrote the page, fall through and free the buffer. We
+ * don't have to rewalk the list to acquire the buffer because
+ * it was never available for any other process to modify it.
+ * If we didn't write the page, but we discarded and reacquired
+ * the region lock, restart the buffer list walk. If we neither
+ * wrote the buffer nor discarded the region lock, continue down
+ * the buffer list.
+ */
+ if (F_ISSET(bhp, BH_DIRTY)) {
+ ++bhp->ref;
+ if ((ret = __memp_bhwrite(dbmp,
+ mfp, bhp, &restart, &wrote)) != 0)
+ return (ret);
+ --bhp->ref;
+
+ /*
+ * It's possible that another process wants this buffer
+ * and incremented the ref count while we were writing
+ * it.
+ */
+ if (bhp->ref != 0)
+ goto retry;
+
+ if (wrote)
+ ++mp->stat.st_rw_evict;
+ else {
+ if (restart)
+ goto retry;
+ continue;
+ }
+ } else
+ ++mp->stat.st_ro_evict;
+
+ /*
+ * Check to see if the buffer is the size we're looking for.
+ * If it is, simply reuse it.
+ */
+ total += fsize = __db_shsizeof(bhp);
+ if (fsize == len) {
+ __memp_bhfree(dbmp, mfp, bhp, 0);
+
+ if (offsetp != NULL)
+ *offsetp = R_OFFSET(dbmp, bhp);
+ *(void **)retp = bhp;
+ return (0);
+ }
+
+ /* Free the buffer. */
+ __memp_bhfree(dbmp, mfp, bhp, 1);
+
+ /*
+ * Retry as soon as we've freed up sufficient space. If we
+ * have to coalesce of memory to satisfy the request, don't
+ * try until it's likely (possible?) that we'll succeed.
+ */
+ if (fsize >= len || total >= 3 * len)
+ goto alloc;
+
+ /* Restart the walk if we discarded the region lock. */
+ if (restart)
+ goto retry;
+ }
+ nomore = 1;
+ goto alloc;
+}
+
+/*
+ * __memp_ropen --
+ * Attach to, and optionally create, the mpool region.
+ *
+ * PUBLIC: int __memp_ropen
+ * PUBLIC: __P((DB_MPOOL *, const char *, size_t, int, int, u_int32_t));
+ */
+int
+__memp_ropen(dbmp, path, cachesize, mode, is_private, flags)
+ DB_MPOOL *dbmp;
+ const char *path;
+ size_t cachesize;
+ int mode, is_private;
+ u_int32_t flags;
+{
+ MPOOL *mp;
+ size_t rlen;
+ int defcache, ret;
+
+ /*
+ * Unlike other DB subsystems, mpool can't simply grow the region
+ * because it returns pointers into the region to its clients. To
+ * "grow" the region, we'd have to allocate a new region and then
+ * store a region number in the structures that reference regional
+ * objects. It's reasonable that we fail regardless, as clients
+ * shouldn't have every page in the region pinned, so the only
+ * "failure" mode should be a performance penalty because we don't
+ * find a page in the cache that we'd like to have found.
+ *
+ * Up the user's cachesize by 25% to account for our overhead.
+ */
+ defcache = 0;
+ if (cachesize < DB_CACHESIZE_MIN)
+ if (cachesize == 0) {
+ defcache = 1;
+ cachesize = DB_CACHESIZE_DEF;
+ } else
+ cachesize = DB_CACHESIZE_MIN;
+ rlen = cachesize + cachesize / 4;
+
+ /*
+ * Map in the region.
+ *
+ * If it's a private mpool, use malloc, it's a lot faster than
+ * instantiating a region.
+ */
+ dbmp->reginfo.dbenv = dbmp->dbenv;
+ dbmp->reginfo.appname = DB_APP_NONE;
+ if (path == NULL)
+ dbmp->reginfo.path = NULL;
+ else
+ if ((ret = __os_strdup(path, &dbmp->reginfo.path)) != 0)
+ return (ret);
+ dbmp->reginfo.file = DB_DEFAULT_MPOOL_FILE;
+ dbmp->reginfo.mode = mode;
+ dbmp->reginfo.size = rlen;
+ dbmp->reginfo.dbflags = flags;
+ dbmp->reginfo.flags = 0;
+ if (defcache)
+ F_SET(&dbmp->reginfo, REGION_SIZEDEF);
+
+ /*
+ * If we're creating a temporary region, don't use any standard
+ * naming.
+ */
+ if (is_private) {
+ dbmp->reginfo.appname = DB_APP_TMP;
+ dbmp->reginfo.file = NULL;
+ F_SET(&dbmp->reginfo, REGION_PRIVATE);
+ }
+
+ if ((ret = __db_rattach(&dbmp->reginfo)) != 0) {
+ if (dbmp->reginfo.path != NULL)
+ __os_freestr(dbmp->reginfo.path);
+ return (ret);
+ }
+
+ /*
+ * The MPOOL structure is first in the region, the rest of the region
+ * is free space.
+ */
+ dbmp->mp = dbmp->reginfo.addr;
+ dbmp->addr = (u_int8_t *)dbmp->mp + sizeof(MPOOL);
+
+ /* Initialize a created region. */
+ if (F_ISSET(&dbmp->reginfo, REGION_CREATED)) {
+ mp = dbmp->mp;
+ SH_TAILQ_INIT(&mp->bhq);
+ SH_TAILQ_INIT(&mp->bhfq);
+ SH_TAILQ_INIT(&mp->mpfq);
+
+ __db_shalloc_init(dbmp->addr, rlen - sizeof(MPOOL));
+
+ /*
+ * Assume we want to keep the hash chains with under 10 pages
+ * on each chain. We don't know the pagesize in advance, and
+ * it may differ for different files. Use a pagesize of 1K for
+ * the calculation -- we walk these chains a lot, they should
+ * be short.
+ */
+ mp->htab_buckets =
+ __db_tablesize((cachesize / (1 * 1024)) / 10);
+
+ /* Allocate hash table space and initialize it. */
+ if ((ret = __db_shalloc(dbmp->addr,
+ mp->htab_buckets * sizeof(DB_HASHTAB),
+ 0, &dbmp->htab)) != 0)
+ goto err;
+ __db_hashinit(dbmp->htab, mp->htab_buckets);
+ mp->htab = R_OFFSET(dbmp, dbmp->htab);
+
+ ZERO_LSN(mp->lsn);
+ mp->lsn_cnt = 0;
+
+ memset(&mp->stat, 0, sizeof(mp->stat));
+ mp->stat.st_cachesize = cachesize;
+
+ mp->flags = 0;
+ }
+
+ /* Get the local hash table address. */
+ dbmp->htab = R_ADDR(dbmp, dbmp->mp->htab);
+
+ UNLOCKREGION(dbmp);
+ return (0);
+
+err: UNLOCKREGION(dbmp);
+ (void)__db_rdetach(&dbmp->reginfo);
+ if (F_ISSET(&dbmp->reginfo, REGION_CREATED))
+ (void)memp_unlink(path, 1, dbmp->dbenv);
+
+ if (dbmp->reginfo.path != NULL)
+ __os_freestr(dbmp->reginfo.path);
+ return (ret);
+}
diff --git a/usr/src/cmd/sendmail/db/mp/mp_sync.c b/usr/src/cmd/sendmail/db/mp/mp_sync.c
new file mode 100644
index 0000000000..b7728618fc
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/mp/mp_sync.c
@@ -0,0 +1,551 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)mp_sync.c 10.31 (Sleepycat) 12/11/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <errno.h>
+#include <stdlib.h>
+#endif
+
+#include "db_int.h"
+#include "shqueue.h"
+#include "db_shash.h"
+#include "mp.h"
+#include "common_ext.h"
+
+static int __bhcmp __P((const void *, const void *));
+static int __memp_fsync __P((DB_MPOOLFILE *));
+
+/*
+ * memp_sync --
+ * Mpool sync function.
+ */
+int
+memp_sync(dbmp, lsnp)
+ DB_MPOOL *dbmp;
+ DB_LSN *lsnp;
+{
+ BH *bhp, **bharray;
+ DB_ENV *dbenv;
+ MPOOL *mp;
+ MPOOLFILE *mfp;
+ int ar_cnt, nalloc, next, maxpin, ret, wrote;
+
+ MP_PANIC_CHECK(dbmp);
+
+ dbenv = dbmp->dbenv;
+ mp = dbmp->mp;
+
+ if (dbenv->lg_info == NULL) {
+ __db_err(dbenv, "memp_sync: requires logging");
+ return (EINVAL);
+ }
+
+ /*
+ * We try and write the buffers in page order: it should reduce seeks
+ * by the underlying filesystem and possibly reduce the actual number
+ * of writes. We don't want to hold the region lock while we write
+ * the buffers, so only hold it lock while we create a list. Get a
+ * good-size block of memory to hold buffer pointers, we don't want
+ * to run out.
+ */
+ LOCKREGION(dbmp);
+ nalloc = mp->stat.st_page_dirty + mp->stat.st_page_dirty / 2 + 10;
+ UNLOCKREGION(dbmp);
+
+ if ((ret = __os_malloc(nalloc * sizeof(BH *), NULL, &bharray)) != 0)
+ return (ret);
+
+ LOCKREGION(dbmp);
+
+ /*
+ * If the application is asking about a previous call to memp_sync(),
+ * and we haven't found any buffers that the application holding the
+ * pin couldn't write, return yes or no based on the current count.
+ * Note, if the application is asking about a LSN *smaller* than one
+ * we've already handled or are currently handling, then we return a
+ * result based on the count for the larger LSN.
+ */
+ if (!F_ISSET(mp, MP_LSN_RETRY) && log_compare(lsnp, &mp->lsn) <= 0) {
+ if (mp->lsn_cnt == 0) {
+ *lsnp = mp->lsn;
+ ret = 0;
+ } else
+ ret = DB_INCOMPLETE;
+ goto done;
+ }
+
+ /* Else, it's a new checkpoint. */
+ F_CLR(mp, MP_LSN_RETRY);
+
+ /*
+ * Save the LSN. We know that it's a new LSN or larger than the one
+ * for which we were already doing a checkpoint. (BTW, I don't expect
+ * to see multiple LSN's from the same or multiple processes, but You
+ * Just Never Know. Responding as if they all called with the largest
+ * of the LSNs specified makes everything work.)
+ *
+ * We don't currently use the LSN we save. We could potentially save
+ * the last-written LSN in each buffer header and use it to determine
+ * what buffers need to be written. The problem with this is that it's
+ * sizeof(LSN) more bytes of buffer header. We currently write all the
+ * dirty buffers instead.
+ *
+ * Walk the list of shared memory segments clearing the count of
+ * buffers waiting to be written.
+ */
+ mp->lsn = *lsnp;
+ mp->lsn_cnt = 0;
+ for (mfp = SH_TAILQ_FIRST(&dbmp->mp->mpfq, __mpoolfile);
+ mfp != NULL; mfp = SH_TAILQ_NEXT(mfp, q, __mpoolfile))
+ mfp->lsn_cnt = 0;
+
+ /*
+ * Walk the list of buffers and mark all dirty buffers to be written
+ * and all pinned buffers to be potentially written (we can't know if
+ * we'll need to write them until the holding process returns them to
+ * the cache). We do this in one pass while holding the region locked
+ * so that processes can't make new buffers dirty, causing us to never
+ * finish. Since the application may have restarted the sync, clear
+ * any BH_WRITE flags that appear to be left over from previous calls.
+ *
+ * We don't want to pin down the entire buffer cache, otherwise we'll
+ * starve threads needing new pages. Don't pin down more than 80% of
+ * the cache.
+ *
+ * Keep a count of the total number of buffers we need to write in
+ * MPOOL->lsn_cnt, and for each file, in MPOOLFILE->lsn_count.
+ */
+ ar_cnt = 0;
+ maxpin = ((mp->stat.st_page_dirty + mp->stat.st_page_clean) * 8) / 10;
+ for (bhp = SH_TAILQ_FIRST(&mp->bhq, __bh);
+ bhp != NULL; bhp = SH_TAILQ_NEXT(bhp, q, __bh))
+ if (F_ISSET(bhp, BH_DIRTY) || bhp->ref != 0) {
+ F_SET(bhp, BH_WRITE);
+
+ ++mp->lsn_cnt;
+
+ mfp = R_ADDR(dbmp, bhp->mf_offset);
+ ++mfp->lsn_cnt;
+
+ /*
+ * If the buffer isn't in use, we should be able to
+ * write it immediately, so increment the reference
+ * count to lock it and its contents down, and then
+ * save a reference to it.
+ *
+ * If we've run out space to store buffer references,
+ * we're screwed. We don't want to realloc the array
+ * while holding a region lock, so we set the flag to
+ * force the checkpoint to be done again, from scratch,
+ * later.
+ *
+ * If we've pinned down too much of the cache stop, and
+ * set a flag to force the checkpoint to be tried again
+ * later.
+ */
+ if (bhp->ref == 0) {
+ ++bhp->ref;
+ bharray[ar_cnt] = bhp;
+ if (++ar_cnt >= nalloc || ar_cnt >= maxpin) {
+ F_SET(mp, MP_LSN_RETRY);
+ break;
+ }
+ }
+ } else
+ if (F_ISSET(bhp, BH_WRITE))
+ F_CLR(bhp, BH_WRITE);
+
+ /* If there no buffers we can write immediately, we're done. */
+ if (ar_cnt == 0) {
+ ret = mp->lsn_cnt ? DB_INCOMPLETE : 0;
+ goto done;
+ }
+
+ UNLOCKREGION(dbmp);
+
+ /* Sort the buffers we're going to write. */
+ qsort(bharray, ar_cnt, sizeof(BH *), __bhcmp);
+
+ LOCKREGION(dbmp);
+
+ /* Walk the array, writing buffers. */
+ for (next = 0; next < ar_cnt; ++next) {
+ /*
+ * It's possible for a thread to have gotten the buffer since
+ * we listed it for writing. If the reference count is still
+ * 1, we're the only ones using the buffer, go ahead and write.
+ * If it's >1, then skip the buffer and assume that it will be
+ * written when it's returned to the cache.
+ */
+ if (bharray[next]->ref > 1) {
+ --bharray[next]->ref;
+ continue;
+ }
+
+ /* Write the buffer. */
+ mfp = R_ADDR(dbmp, bharray[next]->mf_offset);
+ ret = __memp_bhwrite(dbmp, mfp, bharray[next], NULL, &wrote);
+
+ /* Release the buffer. */
+ --bharray[next]->ref;
+
+ /* If there's an error, release the rest of the buffers. */
+ if (ret != 0 || !wrote) {
+ /*
+ * Any process syncing the shared memory buffer pool
+ * had better be able to write to any underlying file.
+ * Be understanding, but firm, on this point.
+ */
+ if (ret == 0) {
+ __db_err(dbenv, "%s: unable to flush page: %lu",
+ __memp_fns(dbmp, mfp),
+ (u_long)bharray[next]->pgno);
+ ret = EPERM;
+ }
+
+ while (++next < ar_cnt)
+ --bharray[next]->ref;
+ goto err;
+ }
+ }
+ ret = mp->lsn_cnt != 0 ||
+ F_ISSET(mp, MP_LSN_RETRY) ? DB_INCOMPLETE : 0;
+
+done:
+ if (0) {
+err: /*
+ * On error, clear:
+ * MPOOL->lsn_cnt (the total sync count)
+ * MPOOLFILE->lsn_cnt (the per-file sync count)
+ * BH_WRITE flag (the scheduled for writing flag)
+ */
+ mp->lsn_cnt = 0;
+ for (mfp = SH_TAILQ_FIRST(&dbmp->mp->mpfq, __mpoolfile);
+ mfp != NULL; mfp = SH_TAILQ_NEXT(mfp, q, __mpoolfile))
+ mfp->lsn_cnt = 0;
+ for (bhp = SH_TAILQ_FIRST(&mp->bhq, __bh);
+ bhp != NULL; bhp = SH_TAILQ_NEXT(bhp, q, __bh))
+ F_CLR(bhp, BH_WRITE);
+ }
+ UNLOCKREGION(dbmp);
+ __os_free(bharray, nalloc * sizeof(BH *));
+ return (ret);
+}
+
+/*
+ * memp_fsync --
+ * Mpool file sync function.
+ */
+int
+memp_fsync(dbmfp)
+ DB_MPOOLFILE *dbmfp;
+{
+ DB_MPOOL *dbmp;
+ int is_tmp;
+
+ dbmp = dbmfp->dbmp;
+
+ MP_PANIC_CHECK(dbmp);
+
+ /*
+ * If this handle doesn't have a file descriptor that's open for
+ * writing, or if the file is a temporary, there's no reason to
+ * proceed further.
+ */
+ if (F_ISSET(dbmfp, MP_READONLY))
+ return (0);
+
+ LOCKREGION(dbmp);
+ is_tmp = F_ISSET(dbmfp->mfp, MP_TEMP);
+ UNLOCKREGION(dbmp);
+ if (is_tmp)
+ return (0);
+
+ return (__memp_fsync(dbmfp));
+}
+
+/*
+ * __mp_xxx_fd --
+ * Return a file descriptor for DB 1.85 compatibility locking.
+ *
+ * PUBLIC: int __mp_xxx_fd __P((DB_MPOOLFILE *, int *));
+ */
+int
+__mp_xxx_fd(dbmfp, fdp)
+ DB_MPOOLFILE *dbmfp;
+ int *fdp;
+{
+ int ret;
+
+ /*
+ * This is a truly spectacular layering violation, intended ONLY to
+ * support compatibility for the DB 1.85 DB->fd call.
+ *
+ * Sync the database file to disk, creating the file as necessary.
+ *
+ * We skip the MP_READONLY and MP_TEMP tests done by memp_fsync(3).
+ * The MP_READONLY test isn't interesting because we will either
+ * already have a file descriptor (we opened the database file for
+ * reading) or we aren't readonly (we created the database which
+ * requires write privileges). The MP_TEMP test isn't interesting
+ * because we want to write to the backing file regardless so that
+ * we get a file descriptor to return.
+ */
+ ret = dbmfp->fd == -1 ? __memp_fsync(dbmfp) : 0;
+
+ return ((*fdp = dbmfp->fd) == -1 ? ENOENT : ret);
+}
+
+/*
+ * __memp_fsync --
+ * Mpool file internal sync function.
+ */
+static int
+__memp_fsync(dbmfp)
+ DB_MPOOLFILE *dbmfp;
+{
+ BH *bhp, **bharray;
+ DB_MPOOL *dbmp;
+ MPOOL *mp;
+ size_t mf_offset;
+ int ar_cnt, incomplete, nalloc, next, ret, wrote;
+
+ ret = 0;
+ dbmp = dbmfp->dbmp;
+ mp = dbmp->mp;
+ mf_offset = R_OFFSET(dbmp, dbmfp->mfp);
+
+ /*
+ * We try and write the buffers in page order: it should reduce seeks
+ * by the underlying filesystem and possibly reduce the actual number
+ * of writes. We don't want to hold the region lock while we write
+ * the buffers, so only hold it lock while we create a list. Get a
+ * good-size block of memory to hold buffer pointers, we don't want
+ * to run out.
+ */
+ LOCKREGION(dbmp);
+ nalloc = mp->stat.st_page_dirty + mp->stat.st_page_dirty / 2 + 10;
+ UNLOCKREGION(dbmp);
+
+ if ((ret = __os_malloc(nalloc * sizeof(BH *), NULL, &bharray)) != 0)
+ return (ret);
+
+ LOCKREGION(dbmp);
+
+ /*
+ * Walk the LRU list of buffer headers, and get a list of buffers to
+ * write for this MPOOLFILE.
+ */
+ ar_cnt = incomplete = 0;
+ for (bhp = SH_TAILQ_FIRST(&mp->bhq, __bh);
+ bhp != NULL; bhp = SH_TAILQ_NEXT(bhp, q, __bh)) {
+ if (!F_ISSET(bhp, BH_DIRTY) || bhp->mf_offset != mf_offset)
+ continue;
+ if (bhp->ref != 0 || F_ISSET(bhp, BH_LOCKED)) {
+ incomplete = 1;
+ continue;
+ }
+
+ ++bhp->ref;
+ bharray[ar_cnt] = bhp;
+
+ /*
+ * If we've run out space to store buffer references, we're
+ * screwed, as we don't want to realloc the array holding a
+ * region lock. Set the incomplete flag -- the only way we
+ * can get here is if the file is active in the buffer cache,
+ * which is the same thing as finding pinned buffers.
+ */
+ if (++ar_cnt >= nalloc) {
+ incomplete = 1;
+ break;
+ }
+ }
+
+ UNLOCKREGION(dbmp);
+
+ /* Sort the buffers we're going to write. */
+ if (ar_cnt != 0)
+ qsort(bharray, ar_cnt, sizeof(BH *), __bhcmp);
+
+ LOCKREGION(dbmp);
+
+ /* Walk the array, writing buffers. */
+ for (next = 0; next < ar_cnt; ++next) {
+ /*
+ * It's possible for a thread to have gotten the buffer since
+ * we listed it for writing. If the reference count is still
+ * 1, we're the only ones using the buffer, go ahead and write.
+ * If it's >1, then skip the buffer.
+ */
+ if (bharray[next]->ref > 1) {
+ incomplete = 1;
+
+ --bharray[next]->ref;
+ continue;
+ }
+
+ /* Write the buffer. */
+ ret = __memp_pgwrite(dbmfp, bharray[next], NULL, &wrote);
+
+ /* Release the buffer. */
+ --bharray[next]->ref;
+
+ /* If there's an error, release the rest of the buffers. */
+ if (ret != 0) {
+ while (++next < ar_cnt)
+ --bharray[next]->ref;
+ goto err;
+ }
+
+ /*
+ * If we didn't write the buffer for some reason, don't return
+ * success.
+ */
+ if (!wrote)
+ incomplete = 1;
+ }
+
+err: UNLOCKREGION(dbmp);
+
+ __os_free(bharray, nalloc * sizeof(BH *));
+
+ /*
+ * Sync the underlying file as the last thing we do, so that the OS
+ * has maximal opportunity to flush buffers before we request it.
+ *
+ * XXX:
+ * Don't lock the region around the sync, fsync(2) has no atomicity
+ * issues.
+ */
+ if (ret == 0)
+ return (incomplete ? DB_INCOMPLETE : __os_fsync(dbmfp->fd));
+ return (ret);
+}
+
+/*
+ * memp_trickle --
+ * Keep a specified percentage of the buffers clean.
+ */
+int
+memp_trickle(dbmp, pct, nwrotep)
+ DB_MPOOL *dbmp;
+ int pct, *nwrotep;
+{
+ BH *bhp;
+ MPOOL *mp;
+ MPOOLFILE *mfp;
+ db_pgno_t pgno;
+ u_long total;
+ int ret, wrote;
+
+ MP_PANIC_CHECK(dbmp);
+
+ mp = dbmp->mp;
+ if (nwrotep != NULL)
+ *nwrotep = 0;
+
+ if (pct < 1 || pct > 100)
+ return (EINVAL);
+
+ LOCKREGION(dbmp);
+
+ /*
+ * If there are sufficient clean buffers, or no buffers or no dirty
+ * buffers, we're done.
+ *
+ * XXX
+ * Using st_page_clean and st_page_dirty is our only choice at the
+ * moment, but it's not as correct as we might like in the presence
+ * of pools with more than one buffer size, as a free 512-byte buffer
+ * isn't the same as a free 8K buffer.
+ */
+loop: total = mp->stat.st_page_clean + mp->stat.st_page_dirty;
+ if (total == 0 || mp->stat.st_page_dirty == 0 ||
+ (mp->stat.st_page_clean * 100) / total >= (u_long)pct) {
+ UNLOCKREGION(dbmp);
+ return (0);
+ }
+
+ /* Loop until we write a buffer. */
+ for (bhp = SH_TAILQ_FIRST(&mp->bhq, __bh);
+ bhp != NULL; bhp = SH_TAILQ_NEXT(bhp, q, __bh)) {
+ if (bhp->ref != 0 ||
+ !F_ISSET(bhp, BH_DIRTY) || F_ISSET(bhp, BH_LOCKED))
+ continue;
+
+ mfp = R_ADDR(dbmp, bhp->mf_offset);
+
+ /*
+ * We can't write to temporary files -- see the comment in
+ * mp_bh.c:__memp_bhwrite().
+ */
+ if (F_ISSET(mfp, MP_TEMP))
+ continue;
+
+ pgno = bhp->pgno;
+ if ((ret = __memp_bhwrite(dbmp, mfp, bhp, NULL, &wrote)) != 0)
+ goto err;
+
+ /*
+ * Any process syncing the shared memory buffer pool had better
+ * be able to write to any underlying file. Be understanding,
+ * but firm, on this point.
+ */
+ if (!wrote) {
+ __db_err(dbmp->dbenv, "%s: unable to flush page: %lu",
+ __memp_fns(dbmp, mfp), (u_long)pgno);
+ ret = EPERM;
+ goto err;
+ }
+
+ ++mp->stat.st_page_trickle;
+ if (nwrotep != NULL)
+ ++*nwrotep;
+ goto loop;
+ }
+
+ /* No more buffers to write. */
+ ret = 0;
+
+err: UNLOCKREGION(dbmp);
+ return (ret);
+}
+
+static int
+__bhcmp(p1, p2)
+ const void *p1, *p2;
+{
+ BH *bhp1, *bhp2;
+
+ bhp1 = *(BH * const *)p1;
+ bhp2 = *(BH * const *)p2;
+
+ /* Sort by file (shared memory pool offset). */
+ if (bhp1->mf_offset < bhp2->mf_offset)
+ return (-1);
+ if (bhp1->mf_offset > bhp2->mf_offset)
+ return (1);
+
+ /*
+ * !!!
+ * Defend against badly written quicksort code calling the comparison
+ * function with two identical pointers (e.g., WATCOM C++ (Power++)).
+ */
+ if (bhp1->pgno < bhp2->pgno)
+ return (-1);
+ if (bhp1->pgno > bhp2->pgno)
+ return (1);
+ return (0);
+}
diff --git a/usr/src/cmd/sendmail/db/mutex/mutex.c b/usr/src/cmd/sendmail/db/mutex/mutex.c
new file mode 100644
index 0000000000..acc6aa07c9
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/mutex/mutex.c
@@ -0,0 +1,314 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)mutex.c 10.52 (Sleepycat) 11/8/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#endif
+
+#include "db_int.h"
+
+#ifdef HAVE_SPINLOCKS
+
+#ifdef HAVE_FUNC_AIX
+#define TSL_INIT(x)
+#define TSL_SET(x) (!_check_lock(x, 0, 1))
+#define TSL_UNSET(x) _clear_lock(x, 0)
+#endif
+
+#ifdef HAVE_ASSEM_MC68020_GCC
+#include "68020.gcc"
+#endif
+
+#if defined(HAVE_FUNC_MSEM)
+/*
+ * !!!
+ * Do not remove the MSEM_IF_NOWAIT flag. The problem is that if a single
+ * process makes two msem_lock() calls in a row, the second one returns an
+ * error. We depend on the fact that we can lock against ourselves in the
+ * locking subsystem, where we set up a mutex so that we can block ourselves.
+ * Tested on OSF1 v4.0.
+ */
+#define TSL_INIT(x) (msem_init(x, MSEM_UNLOCKED) == NULL)
+#define TSL_INIT_ERROR 1
+#define TSL_SET(x) (!msem_lock(x, MSEM_IF_NOWAIT))
+#define TSL_UNSET(x) msem_unlock(x, 0)
+#endif
+
+#ifdef HAVE_FUNC_RELIANT
+#define TSL_INIT(x) initspin(x, 1)
+#define TSL_SET(x) (cspinlock(x) == 0)
+#define TSL_UNSET(x) spinunlock(x)
+#endif
+
+#ifdef HAVE_FUNC_SGI
+#define TSL_INIT(x) (init_lock(x) != 0)
+#define TSL_INIT_ERROR 1
+#define TSL_SET(x) (!acquire_lock(x))
+#define TSL_UNSET(x) release_lock(x)
+#endif
+
+#ifdef HAVE_FUNC_SOLARIS
+/*
+ * Semaphore calls don't work on Solaris 5.5.
+ *
+ * #define TSL_INIT(x) (sema_init(x, 1, USYNC_PROCESS, NULL) != 0)
+ * #define TSL_INIT_ERROR 1
+ * #define TSL_SET(x) (sema_wait(x) == 0)
+ * #define TSL_UNSET(x) sema_post(x)
+ */
+#define TSL_INIT(x)
+#define TSL_SET(x) (_lock_try(x))
+#define TSL_UNSET(x) _lock_clear(x)
+#endif
+
+#ifdef HAVE_FUNC_VMS
+#include <builtins.h>
+#ifdef __ALPHA
+#define TSL_SET(tsl) (!__TESTBITSSI(tsl, 0))
+#else /* __VAX */
+#define TSL_SET(tsl) (!(int)_BBSSI(0, tsl))
+#endif
+#define TSL_UNSET(tsl) (*(tsl) = 0)
+#define TSL_INIT(tsl) TSL_UNSET(tsl)
+#endif
+
+#ifdef HAVE_ASSEM_PARISC_GCC
+#include "parisc.gcc"
+#endif
+
+#ifdef HAVE_ASSEM_SCO_CC
+#include "sco.cc"
+#endif
+
+#ifdef HAVE_ASSEM_SPARC_GCC
+#include "sparc.gcc"
+#endif
+
+#ifdef HAVE_ASSEM_UTS4_CC
+#define TSL_INIT(x)
+#define TSL_SET(x) (!uts_lock(x, 1))
+#define TSL_UNSET(x) (*(x) = 0)
+#endif
+
+#ifdef HAVE_ASSEM_X86_GCC
+#include "x86.gcc"
+#endif
+
+#ifdef WIN16
+/* Win16 spinlocks are simple because we cannot possibly be preempted. */
+#define TSL_INIT(tsl)
+#define TSL_SET(tsl) (*(tsl) = 1)
+#define TSL_UNSET(tsl) (*(tsl) = 0)
+#endif
+
+#if defined(_WIN32)
+/*
+ * XXX
+ * DBDB this needs to be byte-aligned!!
+ */
+#define TSL_INIT(tsl)
+#define TSL_SET(tsl) (!InterlockedExchange((PLONG)tsl, 1))
+#define TSL_UNSET(tsl) (*(tsl) = 0)
+#endif
+
+#endif /* HAVE_SPINLOCKS */
+
+/*
+ * __db_mutex_init --
+ * Initialize a DB mutex structure.
+ *
+ * PUBLIC: int __db_mutex_init __P((db_mutex_t *, u_int32_t));
+ */
+int
+__db_mutex_init(mp, off)
+ db_mutex_t *mp;
+ u_int32_t off;
+{
+#ifdef DIAGNOSTIC
+ if ((ALIGNTYPE)mp & (MUTEX_ALIGNMENT - 1)) {
+ (void)fprintf(stderr,
+ "MUTEX ERROR: mutex NOT %d-byte aligned!\n",
+ MUTEX_ALIGNMENT);
+ abort();
+ }
+#endif
+ memset(mp, 0, sizeof(db_mutex_t));
+
+#ifdef HAVE_SPINLOCKS
+ COMPQUIET(off, 0);
+
+#ifdef TSL_INIT_ERROR
+ if (TSL_INIT(&mp->tsl_resource))
+ return (errno);
+#else
+ TSL_INIT(&mp->tsl_resource);
+#endif
+ mp->spins = __os_spin();
+#else
+ mp->off = off;
+#endif
+ return (0);
+}
+
+#define MS(n) ((n) * 1000) /* Milliseconds to micro-seconds. */
+#define SECOND (MS(1000)) /* A second's worth of micro-seconds. */
+
+/*
+ * __db_mutex_lock
+ * Lock on a mutex, logically blocking if necessary.
+ *
+ * PUBLIC: int __db_mutex_lock __P((db_mutex_t *, int));
+ */
+int
+__db_mutex_lock(mp, fd)
+ db_mutex_t *mp;
+ int fd;
+{
+ u_long usecs;
+#ifdef HAVE_SPINLOCKS
+ int nspins;
+#else
+ struct flock k_lock;
+ pid_t mypid;
+ int locked;
+#endif
+
+ if (!DB_GLOBAL(db_mutexlocks))
+ return (0);
+
+#ifdef HAVE_SPINLOCKS
+ COMPQUIET(fd, 0);
+
+ for (usecs = MS(1);;) {
+ /* Try and acquire the uncontested resource lock for N spins. */
+ for (nspins = mp->spins; nspins > 0; --nspins)
+ if (TSL_SET(&mp->tsl_resource)) {
+#ifdef DIAGNOSTIC
+ if (mp->pid != 0) {
+ (void)fprintf(stderr,
+ "MUTEX ERROR: __db_mutex_lock: lock currently locked\n");
+ abort();
+ }
+ mp->pid = getpid();
+#endif
+ if (usecs == MS(1))
+ ++mp->mutex_set_nowait;
+ else
+ ++mp->mutex_set_wait;
+ return (0);
+ }
+
+ /* Yield the processor; wait 1ms initially, up to 1 second. */
+ __os_yield(usecs);
+ if ((usecs <<= 1) > SECOND)
+ usecs = SECOND;
+ }
+ /* NOTREACHED */
+
+#else /* !HAVE_SPINLOCKS */
+
+ /* Initialize the lock. */
+ k_lock.l_whence = SEEK_SET;
+ k_lock.l_start = mp->off;
+ k_lock.l_len = 1;
+
+ for (locked = 0, mypid = getpid();;) {
+ /*
+ * Wait for the lock to become available; wait 1ms initially,
+ * up to 1 second.
+ */
+ for (usecs = MS(1); mp->pid != 0;) {
+ __os_yield(usecs);
+ if ((usecs <<= 1) > SECOND)
+ usecs = SECOND;
+ }
+
+ /* Acquire an exclusive kernel lock. */
+ k_lock.l_type = F_WRLCK;
+ if (fcntl(fd, F_SETLKW, &k_lock))
+ return (errno);
+
+ /* If the resource tsl is still available, it's ours. */
+ if (mp->pid == 0) {
+ locked = 1;
+ mp->pid = mypid;
+ }
+
+ /* Release the kernel lock. */
+ k_lock.l_type = F_UNLCK;
+ if (fcntl(fd, F_SETLK, &k_lock))
+ return (errno);
+
+ /*
+ * If we got the resource tsl we're done.
+ *
+ * !!!
+ * We can't check to see if the lock is ours, because we may
+ * be trying to block ourselves in the lock manager, and so
+ * the holder of the lock that's preventing us from getting
+ * the lock may be us! (Seriously.)
+ */
+ if (locked)
+ break;
+ }
+ return (0);
+#endif /* !HAVE_SPINLOCKS */
+}
+
+/*
+ * __db_mutex_unlock --
+ * Release a lock.
+ *
+ * PUBLIC: int __db_mutex_unlock __P((db_mutex_t *, int));
+ */
+int
+__db_mutex_unlock(mp, fd)
+ db_mutex_t *mp;
+ int fd;
+{
+ if (!DB_GLOBAL(db_mutexlocks))
+ return (0);
+
+#ifdef DIAGNOSTIC
+ if (mp->pid == 0) {
+ (void)fprintf(stderr,
+ "MUTEX ERROR: __db_mutex_unlock: lock already unlocked\n");
+ abort();
+ }
+#endif
+
+#ifdef HAVE_SPINLOCKS
+ COMPQUIET(fd, 0);
+
+#ifdef DIAGNOSTIC
+ mp->pid = 0;
+#endif
+
+ /* Release the resource tsl. */
+ TSL_UNSET(&mp->tsl_resource);
+#else
+ /*
+ * Release the resource tsl. We don't have to acquire any locks
+ * because processes trying to acquire the lock are checking for
+ * a pid of 0, not a specific value.
+ */
+ mp->pid = 0;
+#endif
+ return (0);
+}
diff --git a/usr/src/cmd/sendmail/db/os/os_abs.c b/usr/src/cmd/sendmail/db/os/os_abs.c
new file mode 100644
index 0000000000..547a6804b4
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/os/os_abs.c
@@ -0,0 +1,31 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)os_abs.c 10.9 (Sleepycat) 7/21/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+#endif
+
+#include "db_int.h"
+
+/*
+ * __os_abspath --
+ * Return if a path is an absolute path.
+ *
+ * PUBLIC: int __os_abspath __P((const char *));
+ */
+int
+__os_abspath(path)
+ const char *path;
+{
+ return (path[0] == '/');
+}
diff --git a/usr/src/cmd/sendmail/db/os/os_alloc.c b/usr/src/cmd/sendmail/db/os/os_alloc.c
new file mode 100644
index 0000000000..0090eb14a7
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/os/os_alloc.c
@@ -0,0 +1,219 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)os_alloc.c 10.10 (Sleepycat) 10/12/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#endif
+
+#include "db_int.h"
+#include "os_jump.h"
+
+/*
+ * !!!
+ * Correct for systems that return NULL when you allocate 0 bytes of memory.
+ * There are several places in DB where we allocate the number of bytes held
+ * by the key/data item, and it can be 0. Correct here so that malloc never
+ * returns a NULL for that reason (which behavior is permitted by ANSI). We
+ * could make these calls macros on non-Alpha architectures (that's where we
+ * saw the problem), but it's probably not worth the autoconf complexity.
+ *
+ * !!!
+ * Correct for systems that don't set errno when malloc and friends fail.
+ *
+ * Out of memory.
+ * We wish to hold the whole sky,
+ * But we never will.
+ */
+
+/*
+ * __os_strdup --
+ * The strdup(3) function for DB.
+ *
+ * PUBLIC: int __os_strdup __P((const char *, void *));
+ */
+int
+__os_strdup(str, storep)
+ const char *str;
+ void *storep;
+{
+ size_t size;
+ int ret;
+ void *p;
+
+ *(void **)storep = NULL;
+
+ size = strlen(str) + 1;
+ if ((ret = __os_malloc(size, NULL, &p)) != 0)
+ return (ret);
+
+ memcpy(p, str, size);
+
+ *(void **)storep = p;
+ return (0);
+}
+
+/*
+ * __os_calloc --
+ * The calloc(3) function for DB.
+ *
+ * PUBLIC: int __os_calloc __P((size_t, size_t, void *));
+ */
+int
+__os_calloc(num, size, storep)
+ size_t num, size;
+ void *storep;
+{
+ void *p;
+ int ret;
+
+ size *= num;
+ if ((ret = __os_malloc(size, NULL, &p)) != 0)
+ return (ret);
+
+ memset(p, 0, size);
+ *(void **)storep = p;
+
+ return (0);
+}
+
+/*
+ * __os_malloc --
+ * The malloc(3) function for DB.
+ *
+ * PUBLIC: int __os_malloc __P((size_t, void *(*)(size_t), void *));
+ */
+int
+__os_malloc(size, db_malloc, storep)
+ size_t size;
+ void *(*db_malloc) __P((size_t)), *storep;
+{
+ void *p;
+
+ *(void **)storep = NULL;
+
+ /* Never allocate 0 bytes -- some C libraries don't like it. */
+ if (size == 0)
+ ++size;
+
+ /* Some C libraries don't correctly set errno when malloc(3) fails. */
+ errno = 0;
+ if (db_malloc != NULL)
+ p = db_malloc(size);
+ else if (__db_jump.j_malloc != NULL)
+ p = __db_jump.j_malloc(size);
+ else
+ p = malloc(size);
+ if (p == NULL) {
+ if (errno == 0)
+ errno = ENOMEM;
+ return (errno);
+ }
+
+#ifdef DIAGNOSTIC
+ memset(p, 0xdb, size);
+#endif
+ *(void **)storep = p;
+
+ return (0);
+}
+
+/*
+ * __os_realloc --
+ * The realloc(3) function for DB.
+ *
+ * PUBLIC: int __os_realloc __P((void *, size_t));
+ */
+int
+__os_realloc(storep, size)
+ void *storep;
+ size_t size;
+{
+ void *p, *ptr;
+
+ ptr = *(void **)storep;
+
+ /* If we haven't yet allocated anything yet, simply call malloc. */
+ if (ptr == NULL)
+ return (__os_malloc(size, NULL, storep));
+
+ /* Never allocate 0 bytes -- some C libraries don't like it. */
+ if (size == 0)
+ ++size;
+
+ /*
+ * Some C libraries don't correctly set errno when realloc(3) fails.
+ *
+ * Don't overwrite the original pointer, there are places in DB we
+ * try to continue after realloc fails.
+ */
+ errno = 0;
+ if (__db_jump.j_realloc != NULL)
+ p = __db_jump.j_realloc(ptr, size);
+ else
+ p = realloc(ptr, size);
+ if (p == NULL) {
+ if (errno == 0)
+ errno = ENOMEM;
+ return (errno);
+ }
+
+ *(void **)storep = p;
+
+ return (0);
+}
+
+/*
+ * __os_free --
+ * The free(3) function for DB.
+ *
+ * PUBLIC: void __os_free __P((void *, size_t));
+ */
+void
+__os_free(ptr, size)
+ void *ptr;
+ size_t size;
+{
+#ifdef DIAGNOSTIC
+ if (size != 0)
+ memset(ptr, 0xdb, size);
+#endif
+
+ if (__db_jump.j_free != NULL)
+ __db_jump.j_free(ptr);
+ else
+ free(ptr);
+}
+
+/*
+ * __os_freestr --
+ * The free(3) function for DB, freeing a string.
+ *
+ * PUBLIC: void __os_freestr __P((void *));
+ */
+void
+__os_freestr(ptr)
+ void *ptr;
+{
+#ifdef DIAGNOSTIC
+ memset(ptr, 0xdb, strlen(ptr) + 1);
+#endif
+
+ if (__db_jump.j_free != NULL)
+ __db_jump.j_free(ptr);
+ else
+ free(ptr);
+}
diff --git a/usr/src/cmd/sendmail/db/os/os_config.c b/usr/src/cmd/sendmail/db/os/os_config.c
new file mode 100644
index 0000000000..71d379a387
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/os/os_config.c
@@ -0,0 +1,153 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)os_config.c 10.30 (Sleepycat) 10/12/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <errno.h>
+#endif
+
+#include "db_int.h"
+#include "os_jump.h"
+
+struct __db_jumptab __db_jump;
+
+DB_GLOBALS __db_global_values = {
+ 1, /* DB_MUTEXLOCKS */
+ 0, /* DB_PAGEYIELD */
+ 0, /* DB_REGION_ANON, DB_REGION_NAME */
+ 0, /* DB_REGION_INIT */
+ 0, /* DB_TSL_SPINS */
+ {NULL, &__db_global_values.db_envq.tqh_first}, /* Environemnt queue */
+ {NULL, &__db_global_values.db_nameq.tqh_first} /* Name queue */
+};
+
+/*
+ * db_jump_set --
+ * Replace functions for the DB package.
+ */
+int
+db_jump_set(func, which)
+ void *func;
+ int which;
+{
+ switch (which) {
+ case DB_FUNC_CLOSE:
+ __db_jump.j_close = (int (*) __P((int)))func;
+ break;
+ case DB_FUNC_DIRFREE:
+ __db_jump.j_dirfree = (void (*) __P((char **, int)))func;
+ break;
+ case DB_FUNC_DIRLIST:
+ __db_jump.j_dirlist =
+ (int (*) __P((const char *, char ***, int *)))func;
+ break;
+ case DB_FUNC_EXISTS:
+ __db_jump.j_exists = (int (*) __P((const char *, int *)))func;
+ break;
+ case DB_FUNC_FREE:
+ __db_jump.j_free = (void (*) __P((void *)))func;
+ break;
+ case DB_FUNC_FSYNC:
+ __db_jump.j_fsync = (int (*) __P((int)))func;
+ break;
+ case DB_FUNC_IOINFO:
+ __db_jump.j_ioinfo = (int (*) __P((const char *,
+ int, u_int32_t *, u_int32_t *, u_int32_t *)))func;
+ break;
+ case DB_FUNC_MALLOC:
+ __db_jump.j_malloc = (void *(*) __P((size_t)))func;
+ break;
+ case DB_FUNC_MAP:
+ __db_jump.j_map = (int (*)
+ __P((char *, int, size_t, int, int, int, void **)))func;
+ break;
+ case DB_FUNC_OPEN:
+ __db_jump.j_open = (int (*) __P((const char *, int, ...)))func;
+ break;
+ case DB_FUNC_READ:
+ __db_jump.j_read =
+ (ssize_t (*) __P((int, void *, size_t)))func;
+ break;
+ case DB_FUNC_REALLOC:
+ __db_jump.j_realloc = (void *(*) __P((void *, size_t)))func;
+ break;
+ case DB_FUNC_RUNLINK:
+ __db_jump.j_runlink = (int (*) __P((char *)))func;
+ break;
+ case DB_FUNC_SEEK:
+ __db_jump.j_seek = (int (*)
+ __P((int, size_t, db_pgno_t, u_int32_t, int, int)))func;
+ break;
+ case DB_FUNC_SLEEP:
+ __db_jump.j_sleep = (int (*) __P((u_long, u_long)))func;
+ break;
+ case DB_FUNC_UNLINK:
+ __db_jump.j_unlink = (int (*) __P((const char *)))func;
+ break;
+ case DB_FUNC_UNMAP:
+ __db_jump.j_unmap = (int (*) __P((void *, size_t)))func;
+ break;
+ case DB_FUNC_WRITE:
+ __db_jump.j_write =
+ (ssize_t (*) __P((int, const void *, size_t)))func;
+ break;
+ case DB_FUNC_YIELD:
+ __db_jump.j_yield = (int (*) __P((void)))func;
+ break;
+ default:
+ return (EINVAL);
+ }
+ return (0);
+}
+
+/*
+ * db_value_set --
+ * Replace values for the DB package.
+ */
+int
+db_value_set(value, which)
+ int value, which;
+{
+ int ret;
+
+ switch (which) {
+ case DB_MUTEXLOCKS:
+ DB_GLOBAL(db_mutexlocks) = value;
+ break;
+ case DB_PAGEYIELD:
+ DB_GLOBAL(db_pageyield) = value;
+ break;
+ case DB_REGION_ANON:
+ if (value != 0 && (ret = __db_mapanon_ok(0)) != 0)
+ return (ret);
+ DB_GLOBAL(db_region_anon) = value;
+ break;
+ case DB_REGION_INIT:
+ DB_GLOBAL(db_region_init) = value;
+ break;
+ case DB_REGION_NAME:
+ if (value != 0 && (ret = __db_mapanon_ok(1)) != 0)
+ return (ret);
+ DB_GLOBAL(db_region_anon) = value;
+ break;
+ case DB_TSL_SPINS:
+ if (value <= 0)
+ return (EINVAL);
+ DB_GLOBAL(db_tsl_spins) = value;
+ break;
+ default:
+ return (EINVAL);
+ }
+ return (0);
+}
diff --git a/usr/src/cmd/sendmail/db/os/os_dir.c b/usr/src/cmd/sendmail/db/os/os_dir.c
new file mode 100644
index 0000000000..f2ee128c1e
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/os/os_dir.c
@@ -0,0 +1,101 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)os_dir.c 10.19 (Sleepycat) 10/12/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#if HAVE_DIRENT_H
+# include <dirent.h>
+# define NAMLEN(dirent) strlen((dirent)->d_name)
+#else
+# define dirent direct
+# define NAMLEN(dirent) (dirent)->d_namlen
+# if HAVE_SYS_NDIR_H
+# include <sys/ndir.h>
+# endif
+# if HAVE_SYS_DIR_H
+# include <sys/dir.h>
+# endif
+# if HAVE_NDIR_H
+# include <ndir.h>
+# endif
+#endif
+
+#include <errno.h>
+#endif
+
+#include "db_int.h"
+#include "os_jump.h"
+
+/*
+ * __os_dirlist --
+ * Return a list of the files in a directory.
+ *
+ * PUBLIC: int __os_dirlist __P((const char *, char ***, int *));
+ */
+int
+__os_dirlist(dir, namesp, cntp)
+ const char *dir;
+ char ***namesp;
+ int *cntp;
+{
+ struct dirent *dp;
+ DIR *dirp;
+ int arraysz, cnt, ret;
+ char **names;
+
+ if (__db_jump.j_dirlist != NULL)
+ return (__db_jump.j_dirlist(dir, namesp, cntp));
+
+ if ((dirp = opendir(dir)) == NULL)
+ return (errno);
+ names = NULL;
+ for (arraysz = cnt = 0; (dp = readdir(dirp)) != NULL; ++cnt) {
+ if (cnt >= arraysz) {
+ arraysz += 100;
+ if ((ret = __os_realloc(&names,
+ arraysz * sizeof(names[0]))) != 0)
+ goto nomem;
+ }
+ if ((ret = __os_strdup(dp->d_name, &names[cnt])) != 0)
+ goto nomem;
+ }
+ (void)closedir(dirp);
+
+ *namesp = names;
+ *cntp = cnt;
+ return (0);
+
+nomem: if (names != NULL)
+ __os_dirfree(names, cnt);
+ return (ret);
+}
+
+/*
+ * __os_dirfree --
+ * Free the list of files.
+ *
+ * PUBLIC: void __os_dirfree __P((char **, int));
+ */
+void
+__os_dirfree(names, cnt)
+ char **names;
+ int cnt;
+{
+ if (__db_jump.j_dirfree != NULL)
+ __db_jump.j_dirfree(names, cnt);
+
+ while (cnt > 0)
+ __os_free(names[--cnt], 0);
+ __os_free(names, 0);
+}
diff --git a/usr/src/cmd/sendmail/db/os/os_fid.c b/usr/src/cmd/sendmail/db/os/os_fid.c
new file mode 100644
index 0000000000..62da590611
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/os/os_fid.c
@@ -0,0 +1,76 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)os_fid.c 10.12 (Sleepycat) 7/21/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <string.h>
+#include <time.h>
+#endif
+
+#include "db_int.h"
+#include "common_ext.h"
+
+/*
+ * __os_fileid --
+ * Return a unique identifier for a file.
+ *
+ * PUBLIC: int __os_fileid __P((DB_ENV *, const char *, int, u_int8_t *));
+ */
+int
+__os_fileid(dbenv, fname, timestamp, fidp)
+ DB_ENV *dbenv;
+ const char *fname;
+ int timestamp;
+ u_int8_t *fidp;
+{
+ struct stat sb;
+ size_t i;
+ time_t now;
+ u_int8_t *p;
+
+ /* Clear the buffer. */
+ memset(fidp, 0, DB_FILE_ID_LEN);
+
+ /* Check for the unthinkable. */
+ if (sizeof(sb.st_ino) +
+ sizeof(sb.st_dev) + sizeof(time_t) > DB_FILE_ID_LEN)
+ return (EINVAL);
+
+ /* On UNIX, use a dev/inode pair. */
+ if (stat(fname, &sb)) {
+ __db_err(dbenv, "%s: %s", fname, strerror(errno));
+ return (errno);
+ }
+
+ /*
+ * Use the inode first and in reverse order, hopefully putting the
+ * distinguishing information early in the string.
+ */
+ for (p = (u_int8_t *)&sb.st_ino +
+ sizeof(sb.st_ino), i = 0; i < sizeof(sb.st_ino); ++i)
+ *fidp++ = *--p;
+ for (p = (u_int8_t *)&sb.st_dev +
+ sizeof(sb.st_dev), i = 0; i < sizeof(sb.st_dev); ++i)
+ *fidp++ = *--p;
+
+ if (timestamp) {
+ (void)time(&now);
+ for (p = (u_int8_t *)&now +
+ sizeof(now), i = 0; i < sizeof(now); ++i)
+ *fidp++ = *--p;
+ }
+ return (0);
+}
diff --git a/usr/src/cmd/sendmail/db/os/os_fsync.c b/usr/src/cmd/sendmail/db/os/os_fsync.c
new file mode 100644
index 0000000000..61a504f84d
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/os/os_fsync.c
@@ -0,0 +1,59 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)os_fsync.c 10.7 (Sleepycat) 10/12/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <errno.h>
+#include <fcntl.h> /* XXX: Required by __hp3000s900 */
+#include <unistd.h>
+#endif
+
+#include "db_int.h"
+#include "os_jump.h"
+
+#ifdef __hp3000s900
+int
+__mpe_fsync(fd)
+ int fd;
+{
+ extern FCONTROL(short, short, void *);
+
+ FCONTROL(_MPE_FILENO(fd), 2, NULL); /* Flush the buffers */
+ FCONTROL(_MPE_FILENO(fd), 6, NULL); /* Write the EOF */
+ return (0);
+}
+#endif
+
+#ifdef __hp3000s900
+#define fsync(fd) __mpe_fsync(fd);
+#endif
+#ifdef _WIN32
+#define fsync(fd) _commit(fd);
+#endif
+
+/*
+ * __os_fsync --
+ * Flush a file descriptor.
+ *
+ * PUBLIC: int __os_fsync __P((int));
+ */
+int
+__os_fsync(fd)
+ int fd;
+{
+ int ret;
+
+ ret = __db_jump.j_fsync != NULL ? __db_jump.j_fsync(fd) : fsync(fd);
+ return (ret == 0 ? 0 : errno);
+}
diff --git a/usr/src/cmd/sendmail/db/os/os_map.c b/usr/src/cmd/sendmail/db/os/os_map.c
new file mode 100644
index 0000000000..5664a2edec
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/os/os_map.c
@@ -0,0 +1,447 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)os_map.c 10.24 (Sleepycat) 10/12/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+#ifdef HAVE_MMAP
+#include <sys/mman.h>
+#endif
+
+#ifdef HAVE_SHMGET
+#include <sys/ipc.h>
+#include <sys/shm.h>
+#endif
+
+#include <errno.h>
+#include <string.h>
+#endif
+
+#include "db_int.h"
+#include "os_jump.h"
+#include "common_ext.h"
+
+#ifdef HAVE_MMAP
+static int __os_map __P((char *, int, size_t, int, int, int, void **));
+#endif
+#ifdef HAVE_SHMGET
+static int __os_shmget __P((REGINFO *));
+#endif
+
+/*
+ * __db_mapanon_ok --
+ * Return if this OS can support anonymous memory regions.
+ *
+ * PUBLIC: int __db_mapanon_ok __P((int));
+ */
+int
+__db_mapanon_ok(need_names)
+ int need_names;
+{
+ int ret;
+
+ ret = EINVAL;
+
+ /*
+ * If we don't have spinlocks, we have to have a file descriptor
+ * for fcntl(2) locking, which implies using mmap(2) to map in a
+ * regular file. Theoretically, we could probably find ways to
+ * get a file descriptor to lock other types of shared regions,
+ * but I don't see any reason to do so.
+ *
+ * If need_names is set, the application wants to share anonymous
+ * memory among multiple processes, so we have to have a way to
+ * name it. This requires shmget(2), on UNIX systems.
+ */
+#ifdef HAVE_SPINLOCKS
+#ifdef HAVE_SHMGET
+ ret = 0;
+#endif
+#ifdef HAVE_MMAP
+#ifdef MAP_ANON
+ if (!need_names)
+ ret = 0;
+#endif
+#ifdef MAP_ANONYMOUS
+ if (!need_names)
+ ret = 0;
+#endif
+#else
+ COMPQUIET(need_names, 0);
+#endif /* HAVE_MMAP */
+#endif /* HAVE_SPINLOCKS */
+
+ return (ret);
+}
+
+/*
+ * __db_mapinit --
+ * Return if shared regions need to be initialized.
+ *
+ * PUBLIC: int __db_mapinit __P((void));
+ */
+int
+__db_mapinit()
+{
+ /*
+ * Historically, some systems required that all of the bytes of the
+ * region be written before it could be mmapped and accessed randomly.
+ * We have the option of setting REGION_INIT_NEEDED at configuration
+ * time if we're running on one of those systems.
+ */
+#ifdef REGION_INIT_NEEDED
+ return (1);
+#else
+ return (0);
+#endif
+}
+
+/*
+ * __db_mapregion --
+ * Attach to a shared memory region.
+ *
+ * PUBLIC: int __db_mapregion __P((char *, REGINFO *));
+ */
+int
+__db_mapregion(path, infop)
+ char *path;
+ REGINFO *infop;
+{
+ int called, ret;
+
+ called = 0;
+ ret = EINVAL;
+
+ /* If the user replaces the map call, call through their interface. */
+ if (__db_jump.j_map != NULL) {
+ F_SET(infop, REGION_HOLDINGSYS);
+ return (__db_jump.j_map(path, infop->fd, infop->size,
+ 1, F_ISSET(infop, REGION_ANONYMOUS), 0, &infop->addr));
+ }
+
+ if (F_ISSET(infop, REGION_ANONYMOUS)) {
+ /*
+ * !!!
+ * If we're creating anonymous regions:
+ *
+ * If it's private, we use mmap(2). The problem with using
+ * shmget(2) is that we may be creating a region of which the
+ * application isn't aware, and if the application crashes
+ * we'll have no way to remove the system resources for the
+ * region.
+ *
+ * If it's not private, we use the shmget(2) interface if it's
+ * available, because it allows us to name anonymous memory.
+ * If shmget(2) isn't available, use the mmap(2) calls.
+ *
+ * In the case of anonymous memory, using mmap(2) means the
+ * memory isn't named and only the single process and its
+ * threads can access the region.
+ */
+#ifdef HAVE_MMAP
+#ifdef MAP_ANON
+#define HAVE_MMAP_ANONYMOUS 1
+#else
+#ifdef MAP_ANONYMOUS
+#define HAVE_MMAP_ANONYMOUS 1
+#endif
+#endif
+#endif
+#ifdef HAVE_MMAP_ANONYMOUS
+ if (!called && F_ISSET(infop, REGION_PRIVATE)) {
+ called = 1;
+ ret = __os_map(path,
+ infop->fd, infop->size, 1, 1, 0, &infop->addr);
+ }
+#endif
+#ifdef HAVE_SHMGET
+ if (!called) {
+ called = 1;
+ ret = __os_shmget(infop);
+ }
+#endif
+#ifdef HAVE_MMAP
+ /*
+ * If we're trying to join an unnamed anonymous region, fail --
+ * that's not possible.
+ */
+ if (!called) {
+ called = 1;
+
+ if (!F_ISSET(infop, REGION_CREATED)) {
+ __db_err(infop->dbenv,
+ "cannot join region in unnamed anonymous memory");
+ return (EINVAL);
+ }
+
+ ret = __os_map(path,
+ infop->fd, infop->size, 1, 1, 0, &infop->addr);
+ }
+#endif
+ } else {
+ /*
+ * !!!
+ * If we're creating normal regions, we use the mmap(2)
+ * interface if it's available because it's POSIX 1003.1
+ * standard and we trust it more than we do shmget(2).
+ */
+#ifdef HAVE_MMAP
+ if (!called) {
+ called = 1;
+
+ /* Mmap(2) regions that aren't anonymous can grow. */
+ F_SET(infop, REGION_CANGROW);
+
+ ret = __os_map(path,
+ infop->fd, infop->size, 1, 0, 0, &infop->addr);
+ }
+#endif
+#ifdef HAVE_SHMGET
+ if (!called) {
+ called = 1;
+ ret = __os_shmget(infop);
+ }
+#endif
+ }
+ return (ret);
+}
+
+/*
+ * __db_unmapregion --
+ * Detach from the shared memory region.
+ *
+ * PUBLIC: int __db_unmapregion __P((REGINFO *));
+ */
+int
+__db_unmapregion(infop)
+ REGINFO *infop;
+{
+ int called, ret;
+
+ called = 0;
+ ret = EINVAL;
+
+ if (__db_jump.j_unmap != NULL)
+ return (__db_jump.j_unmap(infop->addr, infop->size));
+
+#ifdef HAVE_SHMGET
+ if (infop->segid != INVALID_SEGID) {
+ called = 1;
+ ret = shmdt(infop->addr) ? errno : 0;
+ }
+#endif
+#ifdef HAVE_MMAP
+ if (!called) {
+ called = 1;
+ ret = munmap(infop->addr, infop->size) ? errno : 0;
+ }
+#endif
+ return (ret);
+}
+
+/*
+ * __db_unlinkregion --
+ * Remove the shared memory region.
+ *
+ * PUBLIC: int __db_unlinkregion __P((char *, REGINFO *));
+ */
+int
+__db_unlinkregion(name, infop)
+ char *name;
+ REGINFO *infop;
+{
+ int called, ret;
+
+ called = 0;
+ ret = EINVAL;
+
+ if (__db_jump.j_runlink != NULL)
+ return (__db_jump.j_runlink(name));
+
+#ifdef HAVE_SHMGET
+ if (infop->segid != INVALID_SEGID) {
+ called = 1;
+ ret = shmctl(infop->segid, IPC_RMID, NULL) ? errno : 0;
+ }
+#endif
+#ifdef HAVE_MMAP
+ COMPQUIET(infop, NULL);
+ if (!called) {
+ called = 1;
+ ret = 0;
+ }
+#endif
+ return (ret);
+}
+
+/*
+ * __db_mapfile --
+ * Map in a shared memory file.
+ *
+ * PUBLIC: int __db_mapfile __P((char *, int, size_t, int, void **));
+ */
+int
+__db_mapfile(path, fd, len, is_rdonly, addr)
+ char *path;
+ int fd, is_rdonly;
+ size_t len;
+ void **addr;
+{
+ if (__db_jump.j_map != NULL)
+ return (__db_jump.j_map(path, fd, len, 0, 0, is_rdonly, addr));
+
+#ifdef HAVE_MMAP
+ return (__os_map(path, fd, len, 0, 0, is_rdonly, addr));
+#else
+ return (EINVAL);
+#endif
+}
+
+/*
+ * __db_unmapfile --
+ * Unmap the shared memory file.
+ *
+ * PUBLIC: int __db_unmapfile __P((void *, size_t));
+ */
+int
+__db_unmapfile(addr, len)
+ void *addr;
+ size_t len;
+{
+ if (__db_jump.j_unmap != NULL)
+ return (__db_jump.j_unmap(addr, len));
+
+#ifdef HAVE_MMAP
+ return (munmap(addr, len) ? errno : 0);
+#else
+ return (EINVAL);
+#endif
+}
+
+#ifdef HAVE_MMAP
+/*
+ * __os_map --
+ * Call the mmap(2) function.
+ */
+static int
+__os_map(path, fd, len, is_region, is_anonymous, is_rdonly, addr)
+ char *path;
+ int fd, is_region, is_anonymous, is_rdonly;
+ size_t len;
+ void **addr;
+{
+ void *p;
+ int flags, prot;
+
+ COMPQUIET(path, NULL);
+
+ /*
+ * If it's read-only, it's private, and if it's not, it's shared.
+ * Don't bother with an additional parameter.
+ */
+ flags = is_rdonly ? MAP_PRIVATE : MAP_SHARED;
+
+ if (is_region && is_anonymous) {
+ /*
+ * BSD derived systems use MAP_ANON; Digital Unix and HP/UX
+ * use MAP_ANONYMOUS.
+ */
+#ifdef MAP_ANON
+ flags |= MAP_ANON;
+#endif
+#ifdef MAP_ANONYMOUS
+ flags |= MAP_ANONYMOUS;
+#endif
+ fd = -1;
+ }
+#ifdef MAP_FILE
+ if (!is_region || !is_anonymous) {
+ /*
+ * Historically, MAP_FILE was required for mapping regular
+ * files, even though it was the default. Some systems have
+ * it, some don't, some that have it set it to 0.
+ */
+ flags |= MAP_FILE;
+ }
+#endif
+
+ /*
+ * I know of no systems that implement the flag to tell the system
+ * that the region contains semaphores, but it's not an unreasonable
+ * thing to do, and has been part of the design since forever. I
+ * don't think anyone will object, but don't set it for read-only
+ * files, it doesn't make sense.
+ */
+#ifdef MAP_HASSEMAPHORE
+ if (!is_rdonly)
+ flags |= MAP_HASSEMAPHORE;
+#endif
+
+ prot = PROT_READ | (is_rdonly ? 0 : PROT_WRITE);
+
+/*
+ * XXX
+ * Work around a bug in the VMS V7.1 mmap() implementation. To map a file
+ * into memory on VMS it needs to be opened in a certain way, originally.
+ * To get the file opened in that certain way, the VMS mmap() closes the
+ * file and re-opens it. When it does this, it doesn't flush any caches
+ * out to disk before closing. The problem this causes us is that when the
+ * memory cache doesn't get written out, the file isn't big enough to match
+ * the memory chunk and the mmap() call fails. This call to fsync() fixes
+ * the problem. DEC thinks this isn't a bug because of language in XPG5
+ * discussing user responsibility for on-disk and in-memory synchronization.
+ */
+#ifdef VMS
+ if (__os_fsync(fd) == -1)
+ return(errno);
+#endif
+
+ /* MAP_FAILED was not defined in early mmap implementations. */
+#ifndef MAP_FAILED
+#define MAP_FAILED -1
+#endif
+ if ((p =
+ mmap(NULL, len, prot, flags, fd, (off_t)0)) == (void *)MAP_FAILED)
+ return (errno);
+
+ *addr = p;
+ return (0);
+}
+#endif
+
+#ifdef HAVE_SHMGET
+/*
+ * __os_shmget --
+ * Call the shmget(2) family of functions.
+ */
+static int
+__os_shmget(infop)
+ REGINFO *infop;
+{
+ if (F_ISSET(infop, REGION_CREATED) &&
+ (infop->segid = shmget(0, infop->size, IPC_PRIVATE | 0600)) == -1)
+ return (errno);
+
+ if ((infop->addr = shmat(infop->segid, NULL, 0)) == (void *)-1) {
+ /*
+ * If we're trying to join the region and failing, assume
+ * that there was a reboot and the region no longer exists.
+ */
+ if (!F_ISSET(infop, REGION_CREATED))
+ errno = EAGAIN;
+ return (errno);
+ }
+
+ F_SET(infop, REGION_HOLDINGSYS);
+ return (0);
+}
+#endif
diff --git a/usr/src/cmd/sendmail/db/os/os_oflags.c b/usr/src/cmd/sendmail/db/os/os_oflags.c
new file mode 100644
index 0000000000..388c1c6faa
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/os/os_oflags.c
@@ -0,0 +1,94 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)os_oflags.c 10.6 (Sleepycat) 4/19/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <fcntl.h>
+#endif
+
+#include "db_int.h"
+
+/*
+ * __db_oflags --
+ * Convert open(2) flags to DB flags.
+ *
+ * PUBLIC: u_int32_t __db_oflags __P((int));
+ */
+u_int32_t
+__db_oflags(oflags)
+ int oflags;
+{
+ u_int32_t dbflags;
+
+ /*
+ * XXX
+ * Convert POSIX 1003.1 open(2) flags to DB flags. Not an exact
+ * science as most POSIX implementations don't have a flag value
+ * for O_RDONLY, it's simply the lack of a write flag.
+ */
+ dbflags = 0;
+ if (oflags & O_CREAT)
+ dbflags |= DB_CREATE;
+ if (!(oflags & (O_RDWR | O_WRONLY)) || oflags & O_RDONLY)
+ dbflags |= DB_RDONLY;
+ if (oflags & O_TRUNC)
+ dbflags |= DB_TRUNCATE;
+ return (dbflags);
+}
+
+/*
+ * __db_omode --
+ * Convert a permission string to the correct open(2) flags.
+ *
+ * PUBLIC: int __db_omode __P((const char *));
+ */
+int
+__db_omode(perm)
+ const char *perm;
+{
+ int mode;
+
+#ifndef S_IRUSR
+#if defined(_WIN32) || defined(WIN16)
+#define S_IRUSR S_IREAD /* R for owner */
+#define S_IWUSR S_IWRITE /* W for owner */
+#define S_IRGRP 0 /* R for group */
+#define S_IWGRP 0 /* W for group */
+#define S_IROTH 0 /* R for other */
+#define S_IWOTH 0 /* W for other */
+#else
+#define S_IRUSR 0000400 /* R for owner */
+#define S_IWUSR 0000200 /* W for owner */
+#define S_IRGRP 0000040 /* R for group */
+#define S_IWGRP 0000020 /* W for group */
+#define S_IROTH 0000004 /* R for other */
+#define S_IWOTH 0000002 /* W for other */
+#endif /* _WIN32 || WIN16 */
+#endif
+ mode = 0;
+ if (perm[0] == 'r')
+ mode |= S_IRUSR;
+ if (perm[1] == 'w')
+ mode |= S_IWUSR;
+ if (perm[2] == 'r')
+ mode |= S_IRGRP;
+ if (perm[3] == 'w')
+ mode |= S_IWGRP;
+ if (perm[4] == 'r')
+ mode |= S_IROTH;
+ if (perm[5] == 'w')
+ mode |= S_IWOTH;
+ return (mode);
+}
diff --git a/usr/src/cmd/sendmail/db/os/os_open.c b/usr/src/cmd/sendmail/db/os/os_open.c
new file mode 100644
index 0000000000..c54fd7365d
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/os/os_open.c
@@ -0,0 +1,152 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)os_open.c 10.33 (Sleepycat) 10/12/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <unistd.h>
+#endif
+
+#include "db_int.h"
+#include "os_jump.h"
+
+/*
+ * __db_open --
+ * Open a file descriptor.
+ *
+ * PUBLIC: int __db_open __P((const char *, u_int32_t, u_int32_t, int, int *));
+ */
+int
+__db_open(name, arg_flags, ok_flags, mode, fdp)
+ const char *name;
+ u_int32_t arg_flags, ok_flags;
+ int mode, *fdp;
+{
+#if !defined(_WIN32) && defined(HAVE_SIGFILLSET)
+ sigset_t set, oset;
+#endif
+ int flags, ret;
+
+ if (arg_flags & ~ok_flags)
+ return (EINVAL);
+
+ flags = 0;
+
+ /*
+ * DB requires the semantic that two files opened at the same time
+ * with O_CREAT and O_EXCL set will return failure in at least one.
+ */
+ if (arg_flags & DB_CREATE)
+ flags |= O_CREAT;
+
+ if (arg_flags & DB_EXCL)
+ flags |= O_EXCL;
+
+ if (arg_flags & DB_RDONLY)
+ flags |= O_RDONLY;
+ else
+ flags |= O_RDWR;
+
+#if defined(_WIN32) || defined(WIN16)
+#ifdef _MSC_VER
+ if (arg_flags & DB_SEQUENTIAL)
+ flags |= _O_SEQUENTIAL;
+ else
+ flags |= _O_RANDOM;
+
+ if (arg_flags & DB_TEMPORARY)
+ flags |= _O_TEMPORARY;
+#endif
+ flags |= O_BINARY | O_NOINHERIT;
+#endif
+
+ if (arg_flags & DB_TRUNCATE)
+ flags |= O_TRUNC;
+
+#if !defined(_WIN32) && defined(HAVE_SIGFILLSET)
+ /*
+ * We block every signal we can get our hands on so that the temporary
+ * file isn't left around if we're interrupted at the wrong time. Of
+ * course, if we drop core in-between the calls we'll hang forever, but
+ * that's probably okay. ;-)
+ */
+ if (arg_flags & DB_TEMPORARY) {
+ (void)sigfillset(&set);
+ (void)sigprocmask(SIG_BLOCK, &set, &oset);
+ }
+#endif
+
+ /* Open the file. */
+ if ((ret = __os_open(name, flags, mode, fdp)) != 0)
+ return (ret);
+
+#if !defined(_WIN32)
+ /* Delete any temporary file; done for Win32 by _O_TEMPORARY. */
+ if (arg_flags & DB_TEMPORARY) {
+ (void)__os_unlink(name);
+#if defined(HAVE_SIGFILLSET)
+ (void)sigprocmask(SIG_SETMASK, &oset, NULL);
+#endif
+ }
+#endif
+
+#if !defined(_WIN32) && !defined(WIN16) && !defined(VMS)
+ /*
+ * Deny access to any child process.
+ * VMS: does not have fd inheritance.
+ * Win32: done by O_NOINHERIT.
+ */
+ if (fcntl(*fdp, F_SETFD, 1) == -1) {
+ ret = errno;
+
+ (void)__os_close(*fdp);
+ return (ret);
+ }
+#endif
+ return (0);
+}
+
+/*
+ * __os_open --
+ * Open a file.
+ *
+ * PUBLIC: int __os_open __P((const char *, int, int, int *));
+ */
+int
+__os_open(name, flags, mode, fdp)
+ const char *name;
+ int flags, mode, *fdp;
+{
+ *fdp = __db_jump.j_open != NULL ?
+ __db_jump.j_open(name, flags, mode) : open(name, flags, mode);
+ return (*fdp == -1 ? errno : 0);
+}
+
+/*
+ * __os_close --
+ * Close a file descriptor.
+ *
+ * PUBLIC: int __os_close __P((int));
+ */
+int
+__os_close(fd)
+ int fd;
+{
+ int ret;
+
+ ret = __db_jump.j_close != NULL ? __db_jump.j_close(fd) : close(fd);
+ return (ret == 0 ? 0 : errno);
+}
diff --git a/usr/src/cmd/sendmail/db/os/os_rpath.c b/usr/src/cmd/sendmail/db/os/os_rpath.c
new file mode 100644
index 0000000000..4b0cbe1c3c
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/os/os_rpath.c
@@ -0,0 +1,49 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1997
+ * Sleepycat Software. All rights reserved.
+ */
+/*
+ * Copyright (c) 1998 by Sun Microsystems, Inc.
+ * All rights reserved.
+ */
+
+#include "config.h"
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#ifndef lint
+static const char sccsid[] = "@(#)os_rpath.c 10.2 (Sleepycat) 10/24/97";
+static const char sccsi2[] = "%W% (Sun) %G%";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <string.h>
+#endif
+
+#include "db_int.h"
+
+/*
+ * __db_rpath --
+ * Return the last path separator in the path or NULL if none found.
+ *
+ * PUBLIC: char *__db_rpath __P((const char *));
+ */
+char *
+__db_rpath(path)
+ const char *path;
+{
+ const char *s, *last;
+
+ last = NULL;
+ if (PATH_SEPARATOR[1] != '\0') {
+ for (s = path; s[0] != '\0'; ++s)
+ if (strchr(PATH_SEPARATOR, s[0]) != NULL)
+ last = s;
+ } else
+ for (s = path; s[0] != '\0'; ++s)
+ if (s[0] == PATH_SEPARATOR[0])
+ last = s;
+ return ((char *)last);
+}
diff --git a/usr/src/cmd/sendmail/db/os/os_rw.c b/usr/src/cmd/sendmail/db/os/os_rw.c
new file mode 100644
index 0000000000..46eac1bf51
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/os/os_rw.c
@@ -0,0 +1,136 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)os_rw.c 10.11 (Sleepycat) 10/12/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <errno.h>
+#include <unistd.h>
+#endif
+
+#include "db_int.h"
+#include "os_jump.h"
+
+/*
+ * __os_io --
+ * Do an I/O.
+ *
+ * PUBLIC: int __os_io __P((DB_IO *, int, ssize_t *));
+ */
+int
+__os_io(db_iop, op, niop)
+ DB_IO *db_iop;
+ int op;
+ ssize_t *niop;
+{
+ int ret;
+
+#ifdef HAVE_PREAD
+ switch (op) {
+ case DB_IO_READ:
+ if (__db_jump.j_read != NULL)
+ goto slow;
+ *niop = pread(db_iop->fd_io, db_iop->buf,
+ db_iop->bytes, (off_t)db_iop->pgno * db_iop->pagesize);
+ break;
+ case DB_IO_WRITE:
+ if (__db_jump.j_write != NULL)
+ goto slow;
+ *niop = pwrite(db_iop->fd_io, db_iop->buf,
+ db_iop->bytes, (off_t)db_iop->pgno * db_iop->pagesize);
+ break;
+ }
+ if (*niop == db_iop->bytes)
+ return (0);
+slow:
+#endif
+ if (db_iop->mutexp != NULL)
+ (void)__db_mutex_lock(db_iop->mutexp, db_iop->fd_lock);
+
+ if ((ret = __os_seek(db_iop->fd_io,
+ db_iop->pagesize, db_iop->pgno, 0, 0, SEEK_SET)) != 0)
+ goto err;
+ switch (op) {
+ case DB_IO_READ:
+ ret =
+ __os_read(db_iop->fd_io, db_iop->buf, db_iop->bytes, niop);
+ break;
+ case DB_IO_WRITE:
+ ret =
+ __os_write(db_iop->fd_io, db_iop->buf, db_iop->bytes, niop);
+ break;
+ }
+
+err: if (db_iop->mutexp != NULL)
+ (void)__db_mutex_unlock(db_iop->mutexp, db_iop->fd_lock);
+
+ return (ret);
+
+}
+
+/*
+ * __os_read --
+ * Read from a file handle.
+ *
+ * PUBLIC: int __os_read __P((int, void *, size_t, ssize_t *));
+ */
+int
+__os_read(fd, addr, len, nrp)
+ int fd;
+ void *addr;
+ size_t len;
+ ssize_t *nrp;
+{
+ size_t offset;
+ ssize_t nr;
+ u_int8_t *taddr;
+
+ for (taddr = addr,
+ offset = 0; offset < len; taddr += nr, offset += nr) {
+ if ((nr = __db_jump.j_read != NULL ?
+ __db_jump.j_read(fd, taddr, len - offset) :
+ read(fd, taddr, len - offset)) < 0)
+ return (errno);
+ if (nr == 0)
+ break;
+ }
+ *nrp = taddr - (u_int8_t *)addr;
+ return (0);
+}
+
+/*
+ * __os_write --
+ * Write to a file handle.
+ *
+ * PUBLIC: int __os_write __P((int, void *, size_t, ssize_t *));
+ */
+int
+__os_write(fd, addr, len, nwp)
+ int fd;
+ void *addr;
+ size_t len;
+ ssize_t *nwp;
+{
+ size_t offset;
+ ssize_t nw;
+ u_int8_t *taddr;
+
+ for (taddr = addr,
+ offset = 0; offset < len; taddr += nw, offset += nw)
+ if ((nw = __db_jump.j_write != NULL ?
+ __db_jump.j_write(fd, taddr, len - offset) :
+ write(fd, taddr, len - offset)) < 0)
+ return (errno);
+ *nwp = len;
+ return (0);
+}
diff --git a/usr/src/cmd/sendmail/db/os/os_seek.c b/usr/src/cmd/sendmail/db/os/os_seek.c
new file mode 100644
index 0000000000..ae5272bd1c
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/os/os_seek.c
@@ -0,0 +1,52 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)os_seek.c 10.11 (Sleepycat) 10/12/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <errno.h>
+#include <unistd.h>
+#endif
+
+#include "db_int.h"
+#include "os_jump.h"
+
+/*
+ * __os_seek --
+ * Seek to a page/byte offset in the file.
+ *
+ * PUBLIC: int __os_seek __P((int, size_t, db_pgno_t, u_int32_t, int, int));
+ */
+int
+__os_seek(fd, pgsize, pageno, relative, isrewind, whence)
+ int fd;
+ size_t pgsize;
+ db_pgno_t pageno;
+ u_int32_t relative;
+ int isrewind, whence;
+{
+ off_t offset;
+ int ret;
+
+ if (__db_jump.j_seek != NULL)
+ ret = __db_jump.j_seek(fd,
+ pgsize, pageno, relative, isrewind, whence);
+ else {
+ offset = (off_t)pgsize * pageno + relative;
+ if (isrewind)
+ offset = -offset;
+
+ ret = lseek(fd, offset, whence);
+ }
+ return (ret == -1 ? errno : 0);
+}
diff --git a/usr/src/cmd/sendmail/db/os/os_sleep.c b/usr/src/cmd/sendmail/db/os/os_sleep.c
new file mode 100644
index 0000000000..5aa476352e
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/os/os_sleep.c
@@ -0,0 +1,59 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)os_sleep.c 10.12 (Sleepycat) 10/12/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+#ifdef HAVE_SYS_TIME_H
+#include <sys/time.h>
+#endif
+#ifdef HAVE_SYS_SELECT_H
+#include <sys/select.h>
+#endif
+
+#include <errno.h>
+#ifndef HAVE_SYS_TIME_H
+#include <time.h>
+#endif
+#include <unistd.h>
+#endif
+
+#include "db_int.h"
+#include "os_jump.h"
+
+/*
+ * __os_sleep --
+ * Yield the processor for a period of time.
+ *
+ * PUBLIC: int __os_sleep __P((u_long, u_long));
+ */
+int
+__os_sleep(secs, usecs)
+ u_long secs, usecs; /* Seconds and microseconds. */
+{
+ struct timeval t;
+
+ /* Don't require that the values be normalized. */
+ for (; usecs >= 1000000; ++secs, usecs -= 1000000)
+ ;
+
+ if (__db_jump.j_sleep != NULL)
+ return (__db_jump.j_sleep(secs, usecs));
+
+ /*
+ * It's important that we yield the processor here so that other
+ * processes or threads are permitted to run.
+ */
+ t.tv_sec = secs;
+ t.tv_usec = usecs;
+ return (select(0, NULL, NULL, NULL, &t) == -1 ? errno : 0);
+}
diff --git a/usr/src/cmd/sendmail/db/os/os_spin.c b/usr/src/cmd/sendmail/db/os/os_spin.c
new file mode 100644
index 0000000000..e2659b26ae
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/os/os_spin.c
@@ -0,0 +1,107 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)os_spin.c 10.10 (Sleepycat) 10/12/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+#if defined(HAVE_PSTAT_GETDYNAMIC)
+#include <sys/pstat.h>
+#endif
+
+#include <limits.h>
+#include <unistd.h>
+#endif
+
+#include "db_int.h"
+#include "os_jump.h"
+
+#if defined(HAVE_PSTAT_GETDYNAMIC)
+/*
+ * __os_pstat_getdynamic --
+ * HP/UX.
+ */
+static int
+__os_pstat_getdynamic()
+{
+ struct pst_dynamic psd;
+
+ return (pstat_getdynamic(&psd,
+ sizeof(psd), (size_t)1, 0) == -1 ? 1 : psd.psd_proc_cnt);
+}
+#endif
+
+#if defined(HAVE_SYSCONF) && defined(_SC_NPROCESSORS_ONLN)
+/*
+ * __os_sysconf --
+ * Solaris, Linux.
+ */
+static int
+__os_sysconf()
+{
+ int nproc;
+
+ return ((nproc = sysconf(_SC_NPROCESSORS_ONLN)) > 1 ? nproc : 1);
+}
+#endif
+
+/*
+ * __os_spin --
+ * Return the number of default spins before blocking.
+ *
+ * PUBLIC: int __os_spin __P((void));
+ */
+int
+__os_spin()
+{
+ /*
+ * If the application specified a value or we've already figured it
+ * out, return it.
+ *
+ * XXX
+ * We don't want to repeatedly call the underlying function because
+ * it can be expensive (e.g., requiring multiple filesystem accesses
+ * under Debian Linux).
+ */
+ if (DB_GLOBAL(db_tsl_spins) != 0)
+ return (DB_GLOBAL(db_tsl_spins));
+
+ DB_GLOBAL(db_tsl_spins) = 1;
+#if defined(HAVE_PSTAT_GETDYNAMIC)
+ DB_GLOBAL(db_tsl_spins) = __os_pstat_getdynamic();
+#endif
+#if defined(HAVE_SYSCONF) && defined(_SC_NPROCESSORS_ONLN)
+ DB_GLOBAL(db_tsl_spins) = __os_sysconf();
+#endif
+
+ /*
+ * Spin 50 times per processor, we have anecdotal evidence that this
+ * is a reasonable value.
+ */
+ DB_GLOBAL(db_tsl_spins) *= 50;
+
+ return (DB_GLOBAL(db_tsl_spins));
+}
+
+/*
+ * __os_yield --
+ * Yield the processor.
+ *
+ * PUBLIC: void __os_yield __P((u_long));
+ */
+void
+__os_yield(usecs)
+ u_long usecs;
+{
+ if (__db_jump.j_yield != NULL && __db_jump.j_yield() == 0)
+ return;
+ __os_sleep(0, usecs);
+}
diff --git a/usr/src/cmd/sendmail/db/os/os_stat.c b/usr/src/cmd/sendmail/db/os/os_stat.c
new file mode 100644
index 0000000000..65cba82efa
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/os/os_stat.c
@@ -0,0 +1,99 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)os_stat.c 10.18 (Sleepycat) 10/12/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#endif
+
+#include "db_int.h"
+#include "os_jump.h"
+
+/*
+ * __os_exists --
+ * Return if the file exists.
+ *
+ * PUBLIC: int __os_exists __P((const char *, int *));
+ */
+int
+__os_exists(path, isdirp)
+ const char *path;
+ int *isdirp;
+{
+ struct stat sb;
+
+ if (__db_jump.j_exists != NULL)
+ return (__db_jump.j_exists(path, isdirp));
+
+ if (stat(path, &sb) != 0)
+ return (errno);
+
+#if !defined(S_ISDIR) || defined(STAT_MACROS_BROKEN)
+#if defined(_WIN32) || defined(WIN16)
+#define S_ISDIR(m) (_S_IFDIR & (m))
+#else
+#define S_ISDIR(m) (((m) & 0170000) == 0040000)
+#endif
+#endif
+ if (isdirp != NULL)
+ *isdirp = S_ISDIR(sb.st_mode);
+
+ return (0);
+}
+
+/*
+ * __os_ioinfo --
+ * Return file size and I/O size; abstracted to make it easier
+ * to replace.
+ *
+ * PUBLIC: int __os_ioinfo
+ * PUBLIC: __P((const char *, int, u_int32_t *, u_int32_t *, u_int32_t *));
+ */
+int
+__os_ioinfo(path, fd, mbytesp, bytesp, iosizep)
+ const char *path;
+ int fd;
+ u_int32_t *mbytesp, *bytesp, *iosizep;
+{
+ struct stat sb;
+
+ if (__db_jump.j_ioinfo != NULL)
+ return (__db_jump.j_ioinfo(path, fd, mbytesp, bytesp, iosizep));
+
+ if (fstat(fd, &sb) == -1)
+ return (errno);
+
+ /* Return the size of the file. */
+ if (mbytesp != NULL)
+ *mbytesp = sb.st_size / MEGABYTE;
+ if (bytesp != NULL)
+ *bytesp = sb.st_size % MEGABYTE;
+
+ /*
+ * Return the underlying filesystem blocksize, if available.
+ *
+ * XXX
+ * Check for a 0 size -- the HP MPE/iX architecture has st_blksize,
+ * but it's always 0.
+ */
+#ifdef HAVE_ST_BLKSIZE
+ if (iosizep != NULL && (*iosizep = sb.st_blksize) == 0)
+ *iosizep = DB_DEF_IOSIZE;
+#else
+ if (iosizep != NULL)
+ *iosizep = DB_DEF_IOSIZE;
+#endif
+ return (0);
+}
diff --git a/usr/src/cmd/sendmail/db/os/os_tmpdir.c b/usr/src/cmd/sendmail/db/os/os_tmpdir.c
new file mode 100644
index 0000000000..05ef52c1ab
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/os/os_tmpdir.c
@@ -0,0 +1,115 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1998
+ * Sleepycat Software. All rights reserved.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)os_tmpdir.c 10.3 (Sleepycat) 10/13/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <errno.h>
+#include <stdlib.h>
+#endif
+
+#include "db_int.h"
+#include "common_ext.h"
+
+#ifdef macintosh
+#include <TFileSpec.h>
+#endif
+
+/*
+ * __os_tmpdir --
+ * Set the temporary directory path.
+ *
+ * The order of items in the list structure and the order of checks in
+ * the environment are documented.
+ *
+ * PUBLIC: int __os_tmpdir __P((DB_ENV *, u_int32_t));
+ */
+int
+__os_tmpdir(dbenv, flags)
+ DB_ENV *dbenv;
+ u_int32_t flags;
+{
+ /*
+ * !!!
+ * Don't change this to:
+ *
+ * static const char * const list[]
+ *
+ * because it creates a text relocation in position independent code.
+ */
+ static const char * list[] = {
+ "/var/tmp",
+ "/usr/tmp",
+ "/temp", /* Windows. */
+ "/tmp",
+ "C:/temp", /* Windows. */
+ "C:/tmp", /* Windows. */
+ NULL
+ };
+ const char * const *lp, *p;
+
+ /* Use the environment if it's permitted and initialized. */
+ p = NULL;
+#ifdef HAVE_GETEUID
+ if (LF_ISSET(DB_USE_ENVIRON) ||
+ (LF_ISSET(DB_USE_ENVIRON_ROOT) && getuid() == 0))
+#else
+ if (LF_ISSET(DB_USE_ENVIRON))
+#endif
+ {
+ if ((p = getenv("TMPDIR")) != NULL && p[0] == '\0') {
+ __db_err(dbenv, "illegal TMPDIR environment variable");
+ return (EINVAL);
+ }
+ /* Windows */
+ if (p == NULL && (p = getenv("TEMP")) != NULL && p[0] == '\0') {
+ __db_err(dbenv, "illegal TEMP environment variable");
+ return (EINVAL);
+ }
+ /* Windows */
+ if (p == NULL && (p = getenv("TMP")) != NULL && p[0] == '\0') {
+ __db_err(dbenv, "illegal TMP environment variable");
+ return (EINVAL);
+ }
+ /* Macintosh */
+ if (p == NULL &&
+ (p = getenv("TempFolder")) != NULL && p[0] == '\0') {
+ __db_err(dbenv,
+ "illegal TempFolder environment variable");
+ return (EINVAL);
+ }
+ }
+
+#ifdef macintosh
+ /* Get the path to the temporary folder. */
+ if (p == NULL) {
+ FSSpec spec;
+
+ if (!Special2FSSpec(kTemporaryFolderType,
+ kOnSystemDisk, 0, &spec))
+ (void)__os_strdup(FSp2FullPath(&spec), &p);
+ }
+#endif
+
+ /* Step through the list looking for a possibility. */
+ if (p == NULL)
+ for (lp = list; *lp != NULL; ++lp)
+ if (__os_exists(p = *lp, NULL) == 0)
+ break;
+ if (p == NULL)
+ return (0);
+
+ return (__os_strdup(p, &dbenv->db_tmp_dir));
+}
diff --git a/usr/src/cmd/sendmail/db/os/os_unlink.c b/usr/src/cmd/sendmail/db/os/os_unlink.c
new file mode 100644
index 0000000000..aa484de843
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/os/os_unlink.c
@@ -0,0 +1,39 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)os_unlink.c 10.7 (Sleepycat) 10/12/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <errno.h>
+#include <unistd.h>
+#endif
+
+#include "db_int.h"
+#include "os_jump.h"
+
+/*
+ * __os_unlink --
+ * Remove a file.
+ *
+ * PUBLIC: int __os_unlink __P((const char *));
+ */
+int
+__os_unlink(path)
+ const char *path;
+{
+ int ret;
+
+ ret = __db_jump.j_unlink != NULL ?
+ __db_jump.j_unlink(path) : unlink(path);
+ return (ret == -1 ? errno : 0);
+}
diff --git a/usr/src/cmd/sendmail/db/txn/txn.c b/usr/src/cmd/sendmail/db/txn/txn.c
new file mode 100644
index 0000000000..aa0b3652ce
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/txn/txn.c
@@ -0,0 +1,1046 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+/*
+ * Copyright (c) 1995, 1996
+ * The President and Fellows of Harvard University. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Margo Seltzer.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)txn.c 10.66 (Sleepycat) 1/3/99";
+#endif /* not lint */
+
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <errno.h>
+#include <string.h>
+#include <time.h>
+#endif
+
+#include "db_int.h"
+#include "shqueue.h"
+#include "db_page.h"
+#include "db_shash.h"
+#include "txn.h"
+#include "db_dispatch.h"
+#include "lock.h"
+#include "log.h"
+#include "db_am.h"
+#include "common_ext.h"
+
+static int __txn_begin __P((DB_TXN *));
+static int __txn_check_running __P((const DB_TXN *, TXN_DETAIL **));
+static int __txn_end __P((DB_TXN *, int));
+static void __txn_freekids __P((DB_TXN *));
+static int __txn_grow_region __P((DB_TXNMGR *));
+static int __txn_init __P((DB_TXNREGION *));
+static int __txn_undo __P((DB_TXN *));
+static int __txn_validate_region __P((DB_TXNMGR *));
+
+/*
+ * This file contains the top level routines of the transaction library.
+ * It assumes that a lock manager and log manager that conform to the db_log(3)
+ * and db_lock(3) interfaces exist.
+ *
+ * Initialize a transaction region in shared memory.
+ * Return 0 on success, errno on failure.
+ */
+static int
+__txn_init(txn_region)
+ DB_TXNREGION *txn_region;
+{
+ time_t now;
+
+ (void)time(&now);
+
+ /* maxtxns is already initialized. */
+ txn_region->magic = DB_TXNMAGIC;
+ txn_region->version = DB_TXNVERSION;
+ txn_region->last_txnid = TXN_MINIMUM;
+ /*
+ * XXX
+ * If we ever do more types of locking and logging, this changes.
+ */
+ txn_region->logtype = 0;
+ txn_region->locktype = 0;
+ txn_region->time_ckp = now;
+ ZERO_LSN(txn_region->last_ckp);
+ ZERO_LSN(txn_region->pending_ckp);
+ SH_TAILQ_INIT(&txn_region->active_txn);
+ __db_shalloc_init((void *)&txn_region[1],
+ TXN_REGION_SIZE(txn_region->maxtxns) - sizeof(DB_TXNREGION));
+
+ return (0);
+}
+
+int
+txn_open(path, flags, mode, dbenv, mgrpp)
+ const char *path;
+ u_int32_t flags;
+ int mode;
+ DB_ENV *dbenv;
+ DB_TXNMGR **mgrpp;
+{
+ DB_TXNMGR *tmgrp;
+ u_int32_t maxtxns;
+ int ret;
+
+ /* Validate arguments. */
+ if (dbenv == NULL)
+ return (EINVAL);
+#ifdef HAVE_SPINLOCKS
+#define OKFLAGS (DB_CREATE | DB_THREAD | DB_TXN_NOSYNC)
+#else
+#define OKFLAGS (DB_CREATE | DB_TXN_NOSYNC)
+#endif
+ if ((ret = __db_fchk(dbenv, "txn_open", flags, OKFLAGS)) != 0)
+ return (ret);
+
+ maxtxns = dbenv->tx_max != 0 ? dbenv->tx_max : 20;
+
+ /* Now, create the transaction manager structure and set its fields. */
+ if ((ret = __os_calloc(1, sizeof(DB_TXNMGR), &tmgrp)) != 0)
+ return (ret);
+
+ /* Initialize the transaction manager structure. */
+ tmgrp->mutexp = NULL;
+ tmgrp->dbenv = dbenv;
+ tmgrp->recover =
+ dbenv->tx_recover == NULL ? __db_dispatch : dbenv->tx_recover;
+ tmgrp->flags = LF_ISSET(DB_TXN_NOSYNC | DB_THREAD);
+ TAILQ_INIT(&tmgrp->txn_chain);
+
+ /* Join/create the txn region. */
+ tmgrp->reginfo.dbenv = dbenv;
+ tmgrp->reginfo.appname = DB_APP_NONE;
+ if (path == NULL)
+ tmgrp->reginfo.path = NULL;
+ else
+ if ((ret = __os_strdup(path, &tmgrp->reginfo.path)) != 0)
+ goto err;
+ tmgrp->reginfo.file = DEFAULT_TXN_FILE;
+ tmgrp->reginfo.mode = mode;
+ tmgrp->reginfo.size = TXN_REGION_SIZE(maxtxns);
+ tmgrp->reginfo.dbflags = flags;
+ tmgrp->reginfo.addr = NULL;
+ tmgrp->reginfo.fd = -1;
+ tmgrp->reginfo.flags = dbenv->tx_max == 0 ? REGION_SIZEDEF : 0;
+ if ((ret = __db_rattach(&tmgrp->reginfo)) != 0)
+ goto err;
+
+ /* Fill in region-related fields. */
+ tmgrp->region = tmgrp->reginfo.addr;
+ tmgrp->mem = &tmgrp->region[1];
+
+ if (F_ISSET(&tmgrp->reginfo, REGION_CREATED)) {
+ tmgrp->region->maxtxns = maxtxns;
+ if ((ret = __txn_init(tmgrp->region)) != 0)
+ goto err;
+
+ } else if (tmgrp->region->magic != DB_TXNMAGIC) {
+ /* Check if valid region. */
+ __db_err(dbenv, "txn_open: Bad magic number");
+ ret = EINVAL;
+ goto err;
+ }
+
+ if (LF_ISSET(DB_THREAD)) {
+ if ((ret = __db_shalloc(tmgrp->mem, sizeof(db_mutex_t),
+ MUTEX_ALIGNMENT, &tmgrp->mutexp)) == 0)
+ /*
+ * Since we only get here if threading is turned on, we
+ * know that we have spinlocks, so the offset is going
+ * to be ignored. We put 0 here as a valid placeholder.
+ */
+ __db_mutex_init(tmgrp->mutexp, 0);
+ if (ret != 0)
+ goto err;
+ }
+
+ UNLOCK_TXNREGION(tmgrp);
+ *mgrpp = tmgrp;
+ return (0);
+
+err: if (tmgrp->reginfo.addr != NULL) {
+ if (tmgrp->mutexp != NULL)
+ __db_shalloc_free(tmgrp->mem, tmgrp->mutexp);
+
+ UNLOCK_TXNREGION(tmgrp);
+ (void)__db_rdetach(&tmgrp->reginfo);
+ if (F_ISSET(&tmgrp->reginfo, REGION_CREATED))
+ (void)txn_unlink(path, 1, dbenv);
+ }
+
+ if (tmgrp->reginfo.path != NULL)
+ __os_freestr(tmgrp->reginfo.path);
+ __os_free(tmgrp, sizeof(*tmgrp));
+ return (ret);
+}
+
+/*
+ * __txn_panic --
+ * Panic a transaction region.
+ *
+ * PUBLIC: void __txn_panic __P((DB_ENV *));
+ */
+void
+__txn_panic(dbenv)
+ DB_ENV *dbenv;
+{
+ if (dbenv->tx_info != NULL)
+ dbenv->tx_info->region->hdr.panic = 1;
+}
+
+/*
+ * txn_begin --
+ * This is a wrapper to the actual begin process. Normal txn_begin()
+ * allocates a DB_TXN structure for the caller, while txn_xa_begin() does
+ * not. Other than that, both call into the common __txn_begin code().
+ *
+ * Internally, we use TXN_DETAIL structures, but the DB_TXN structure
+ * provides access to the transaction ID and the offset in the transaction
+ * region of the TXN_DETAIL structure.
+ */
+int
+txn_begin(tmgrp, parent, txnpp)
+ DB_TXNMGR *tmgrp;
+ DB_TXN *parent, **txnpp;
+{
+ DB_TXN *txn;
+ int ret;
+
+ TXN_PANIC_CHECK(tmgrp);
+
+ if ((ret = __os_calloc(1, sizeof(DB_TXN), &txn)) != 0)
+ return (ret);
+
+ txn->parent = parent;
+ TAILQ_INIT(&txn->kids);
+ txn->mgrp = tmgrp;
+ txn->flags = TXN_MALLOC;
+ if ((ret = __txn_begin(txn)) != 0) {
+ __os_free(txn, sizeof(DB_TXN));
+ txn = NULL;
+ }
+ if (txn != NULL && parent != NULL)
+ TAILQ_INSERT_HEAD(&parent->kids, txn, klinks);
+ *txnpp = txn;
+ return (ret);
+}
+
+/*
+ * __txn_xa_begin --
+ * XA version of txn_begin.
+ *
+ * PUBLIC: int __txn_xa_begin __P((DB_ENV *, DB_TXN *));
+ */
+int
+__txn_xa_begin(dbenv, txn)
+ DB_ENV *dbenv;
+ DB_TXN *txn;
+{
+ TXN_PANIC_CHECK(dbenv->tx_info);
+
+ memset(txn, 0, sizeof(DB_TXN));
+
+ txn->mgrp = dbenv->tx_info;
+
+ return (__txn_begin(txn));
+}
+
+/*
+ * __txn_begin --
+ * Normal DB version of txn_begin.
+ */
+static int
+__txn_begin(txn)
+ DB_TXN *txn;
+{
+ DB_LSN begin_lsn;
+ DB_TXNMGR *mgr;
+ TXN_DETAIL *td;
+ size_t off;
+ u_int32_t id;
+ int ret;
+
+ /*
+ * We do not have to write begin records (and if we do not, then we
+ * need never write records for read-only transactions). However,
+ * we do need to find the current LSN so that we can store it in the
+ * transaction structure, so we can know where to take checkpoints.
+ */
+ mgr = txn->mgrp;
+ if (mgr->dbenv->lg_info != NULL && (ret =
+ log_put(mgr->dbenv->lg_info, &begin_lsn, NULL, DB_CURLSN)) != 0)
+ goto err2;
+
+ LOCK_TXNREGION(mgr);
+
+ /* Make sure that last_txnid is not going to wrap around. */
+ if (mgr->region->last_txnid == TXN_INVALID) {
+ __db_err(mgr->dbenv, "txn_begin: %s %s",
+ "Transaction ID wrapping.",
+ "Snapshot your database and start a new log.");
+ ret = EINVAL;
+ goto err1;
+ }
+
+ if ((ret = __txn_validate_region(mgr)) != 0)
+ goto err1;
+
+ /* Allocate a new transaction detail structure. */
+ if ((ret = __db_shalloc(mgr->mem, sizeof(TXN_DETAIL), 0, &td)) != 0
+ && ret == ENOMEM && (ret = __txn_grow_region(mgr)) == 0)
+ ret = __db_shalloc(mgr->mem, sizeof(TXN_DETAIL), 0, &td);
+ if (ret != 0)
+ goto err1;
+
+ /* Place transaction on active transaction list. */
+ SH_TAILQ_INSERT_HEAD(&mgr->region->active_txn, td, links, __txn_detail);
+
+ id = ++mgr->region->last_txnid;
+ ++mgr->region->nbegins;
+
+ td->txnid = id;
+ td->begin_lsn = begin_lsn;
+ ZERO_LSN(td->last_lsn);
+ td->last_lock = 0;
+ td->status = TXN_RUNNING;
+ if (txn->parent != NULL)
+ td->parent = txn->parent->off;
+ else
+ td->parent = 0;
+
+ off = (u_int8_t *)td - (u_int8_t *)mgr->region;
+ UNLOCK_TXNREGION(mgr);
+
+ ZERO_LSN(txn->last_lsn);
+ txn->txnid = id;
+ txn->off = off;
+
+ if (F_ISSET(txn, TXN_MALLOC)) {
+ LOCK_TXNTHREAD(mgr);
+ TAILQ_INSERT_TAIL(&mgr->txn_chain, txn, links);
+ UNLOCK_TXNTHREAD(mgr);
+ }
+
+ return (0);
+
+err1: UNLOCK_TXNREGION(mgr);
+
+err2: return (ret);
+}
+/*
+ * txn_commit --
+ * Commit a transaction.
+ */
+int
+txn_commit(txnp)
+ DB_TXN *txnp;
+{
+ DB_LOG *logp;
+ DB_TXNMGR *mgr;
+ int ret;
+
+ mgr = txnp->mgrp;
+
+ TXN_PANIC_CHECK(mgr);
+ if ((ret = __txn_check_running(txnp, NULL)) != 0)
+ return (ret);
+
+ /*
+ * If there are any log records, write a log record and sync
+ * the log, else do no log writes. If the commit is for a child
+ * transaction, we do not need to commit the child synchronously
+ * since if its parent aborts, it will abort too and its parent
+ * (or ultimate ancestor) will write synchronously.
+ */
+ if ((logp = mgr->dbenv->lg_info) != NULL &&
+ !IS_ZERO_LSN(txnp->last_lsn)) {
+ if (txnp->parent == NULL)
+ ret = __txn_regop_log(logp, txnp, &txnp->last_lsn,
+ F_ISSET(mgr, DB_TXN_NOSYNC) ? 0 : DB_FLUSH,
+ TXN_COMMIT);
+ else
+ ret = __txn_child_log(logp, txnp, &txnp->last_lsn, 0,
+ TXN_COMMIT, txnp->parent->txnid);
+ if (ret != 0)
+ return (ret);
+ }
+
+ /*
+ * If this is the senior ancestor (i.e., it has no children), then we
+ * can release all the child transactions since everyone is committing.
+ * Then we can release this transaction. If this is not the ultimate
+ * ancestor, then we can neither free it or its children.
+ */
+ if (txnp->parent == NULL)
+ __txn_freekids(txnp);
+
+ return (__txn_end(txnp, 1));
+}
+
+/*
+ * txn_abort --
+ * Abort a transcation.
+ */
+int
+txn_abort(txnp)
+ DB_TXN *txnp;
+{
+ int ret;
+ DB_TXN *kids;
+
+ TXN_PANIC_CHECK(txnp->mgrp);
+ if ((ret = __txn_check_running(txnp, NULL)) != 0)
+ return (ret);
+
+ for (kids = TAILQ_FIRST(&txnp->kids);
+ kids != NULL;
+ kids = TAILQ_FIRST(&txnp->kids))
+ txn_abort(kids);
+
+ if ((ret = __txn_undo(txnp)) != 0) {
+ __db_err(txnp->mgrp->dbenv,
+ "txn_abort: Log undo failed %s", strerror(ret));
+ return (ret);
+ }
+ return (__txn_end(txnp, 0));
+}
+
+/*
+ * txn_prepare --
+ * Flush the log so a future commit is guaranteed to succeed.
+ */
+int
+txn_prepare(txnp)
+ DB_TXN *txnp;
+{
+ DBT xid;
+ DB_ENV *dbenv;
+ TXN_DETAIL *td;
+ int ret;
+
+ if ((ret = __txn_check_running(txnp, &td)) != 0)
+ return (ret);
+
+ dbenv = txnp->mgrp->dbenv;
+ memset(&xid, 0, sizeof(xid));
+ xid.data = td->xid;
+ /*
+ * We indicate that a transaction is an XA transaction by putting
+ * a valid size in the xid.size fiels. XA requires that the transaction
+ * be either ENDED or SUSPENDED when prepare is called, so we know
+ * that if the xa_status isn't in one of those states, but we are
+ * calling prepare that we are not an XA transaction.
+ */
+ xid.size =
+ td->xa_status != TXN_XA_ENDED && td->xa_status != TXN_XA_SUSPENDED ?
+ 0 : sizeof(td->xid);
+ if (dbenv->lg_info != NULL &&
+ (ret = __txn_xa_regop_log(dbenv->lg_info, txnp, &txnp->last_lsn,
+ F_ISSET(txnp->mgrp, DB_TXN_NOSYNC) ? 0 : DB_FLUSH, TXN_PREPARE,
+ &xid, td->format, td->gtrid, td->bqual, &td->begin_lsn)) != 0) {
+ __db_err(dbenv,
+ "txn_prepare: log_write failed %s\n", strerror(ret));
+ return (ret);
+ }
+
+ LOCK_TXNTHREAD(txnp->mgrp);
+ td->status = TXN_PREPARED;
+ UNLOCK_TXNTHREAD(txnp->mgrp);
+ return (ret);
+}
+
+/*
+ * Return the transaction ID associated with a particular transaction
+ */
+u_int32_t
+txn_id(txnp)
+ DB_TXN *txnp;
+{
+ return (txnp->txnid);
+}
+
+/*
+ * txn_close --
+ * Close the transaction region, does not imply a checkpoint.
+ */
+int
+txn_close(tmgrp)
+ DB_TXNMGR *tmgrp;
+{
+ DB_TXN *txnp;
+ int ret, t_ret;
+
+ TXN_PANIC_CHECK(tmgrp);
+
+ ret = 0;
+
+ /*
+ * This function had better only be called once per process
+ * (i.e., not per thread), so there should be no synchronization
+ * required.
+ */
+ while ((txnp =
+ TAILQ_FIRST(&tmgrp->txn_chain)) != TAILQ_END(&tmgrp->txn_chain))
+ if ((t_ret = txn_abort(txnp)) != 0) {
+ __txn_end(txnp, 0);
+ if (ret == 0)
+ ret = t_ret;
+ }
+
+ if (tmgrp->dbenv->lg_info &&
+ (t_ret = log_flush(tmgrp->dbenv->lg_info, NULL)) != 0 && ret == 0)
+ ret = t_ret;
+
+ if (tmgrp->mutexp != NULL) {
+ LOCK_TXNREGION(tmgrp);
+ __db_shalloc_free(tmgrp->mem, tmgrp->mutexp);
+ UNLOCK_TXNREGION(tmgrp);
+ }
+
+ if ((t_ret = __db_rdetach(&tmgrp->reginfo)) != 0 && ret == 0)
+ ret = t_ret;
+
+ if (tmgrp->reginfo.path != NULL)
+ __os_freestr(tmgrp->reginfo.path);
+ __os_free(tmgrp, sizeof(*tmgrp));
+
+ return (ret);
+}
+
+/*
+ * txn_unlink --
+ * Remove the transaction region.
+ */
+int
+txn_unlink(path, force, dbenv)
+ const char *path;
+ int force;
+ DB_ENV *dbenv;
+{
+ REGINFO reginfo;
+ int ret;
+
+ memset(&reginfo, 0, sizeof(reginfo));
+ reginfo.dbenv = dbenv;
+ reginfo.appname = DB_APP_NONE;
+ if (path != NULL && (ret = __os_strdup(path, &reginfo.path)) != 0)
+ return (ret);
+ reginfo.file = DEFAULT_TXN_FILE;
+ ret = __db_runlink(&reginfo, force);
+ if (reginfo.path != NULL)
+ __os_freestr(reginfo.path);
+ return (ret);
+}
+
+/* Internal routines. */
+
+/*
+ * Return 0 if the txnp is reasonable, otherwise returns EINVAL.
+ */
+static int
+__txn_check_running(txnp, tdp)
+ const DB_TXN *txnp;
+ TXN_DETAIL **tdp;
+{
+ TXN_DETAIL *tp;
+
+ tp = NULL;
+ if (txnp != NULL && txnp->mgrp != NULL && txnp->mgrp->region != NULL) {
+ tp = (TXN_DETAIL *)((u_int8_t *)txnp->mgrp->region + txnp->off);
+ /*
+ * Child transactions could be marked committed which is OK.
+ */
+ if (tp->status != TXN_RUNNING &&
+ tp->status != TXN_PREPARED && tp->status != TXN_COMMITTED)
+ tp = NULL;
+ if (tdp != NULL)
+ *tdp = tp;
+ }
+
+ return (tp == NULL ? EINVAL : 0);
+}
+
+static int
+__txn_end(txnp, is_commit)
+ DB_TXN *txnp;
+ int is_commit;
+{
+ DB_LOCKREQ request;
+ DB_TXNMGR *mgr;
+ TXN_DETAIL *tp;
+ u_int32_t locker;
+ int ret;
+
+ mgr = txnp->mgrp;
+
+ /* Release the locks. */
+ locker = txnp->txnid;
+ request.op = txnp->parent == NULL ||
+ is_commit == 0 ? DB_LOCK_PUT_ALL : DB_LOCK_INHERIT;
+
+ if (mgr->dbenv->lk_info) {
+ ret =
+ lock_tvec(mgr->dbenv->lk_info, txnp, 0, &request, 1, NULL);
+ if (ret != 0 && (ret != DB_LOCK_DEADLOCK || is_commit)) {
+ __db_err(mgr->dbenv, "%s: release locks failed %s",
+ is_commit ? "txn_commit" : "txn_abort",
+ strerror(ret));
+ return (ret);
+ }
+ }
+
+ /* End the transaction. */
+ LOCK_TXNREGION(mgr);
+
+ /*
+ * Child transactions that are committing cannot be released until
+ * the parent commits, since the parent may abort, causing the child
+ * to abort as well.
+ */
+ tp = (TXN_DETAIL *)((u_int8_t *)mgr->region + txnp->off);
+ if (txnp->parent == NULL || !is_commit) {
+ SH_TAILQ_REMOVE(&mgr->region->active_txn,
+ tp, links, __txn_detail);
+
+ __db_shalloc_free(mgr->mem, tp);
+ } else
+ tp->status = is_commit ? TXN_COMMITTED : TXN_ABORTED;
+
+ if (is_commit)
+ mgr->region->ncommits++;
+ else
+ mgr->region->naborts++;
+
+ UNLOCK_TXNREGION(mgr);
+
+ /*
+ * If the transaction aborted, we can remove it from its parent links.
+ * If it committed, then we need to leave it on, since the parent can
+ * still abort.
+ */
+ if (txnp->parent != NULL && !is_commit)
+ TAILQ_REMOVE(&txnp->parent->kids, txnp, klinks);
+
+ /* Free the space. */
+ if (F_ISSET(txnp, TXN_MALLOC) && (txnp->parent == NULL || !is_commit)) {
+ LOCK_TXNTHREAD(mgr);
+ TAILQ_REMOVE(&mgr->txn_chain, txnp, links);
+ UNLOCK_TXNTHREAD(mgr);
+
+ __os_free(txnp, sizeof(*txnp));
+ }
+
+ return (0);
+}
+
+
+/*
+ * __txn_undo --
+ * Undo the transaction with id txnid. Returns 0 on success and
+ * errno on failure.
+ */
+static int
+__txn_undo(txnp)
+ DB_TXN *txnp;
+{
+ DBT rdbt;
+ DB_LOG *logp;
+ DB_LSN key_lsn;
+ DB_TXNMGR *mgr;
+ int ret;
+
+ mgr = txnp->mgrp;
+ logp = mgr->dbenv->lg_info;
+ if (logp == NULL)
+ return (0);
+
+ /*
+ * This is the simplest way to code this, but if the mallocs during
+ * recovery turn out to be a performance issue, we can do the
+ * allocation here and use DB_DBT_USERMEM.
+ */
+ memset(&rdbt, 0, sizeof(rdbt));
+ if (F_ISSET(logp, DB_AM_THREAD))
+ F_SET(&rdbt, DB_DBT_MALLOC);
+
+ key_lsn = txnp->last_lsn; /* structure assignment */
+ for (ret = 0; ret == 0 && !IS_ZERO_LSN(key_lsn);) {
+ /*
+ * The dispatch routine returns the lsn of the record
+ * before the current one in the key_lsn argument.
+ */
+ if ((ret = log_get(logp, &key_lsn, &rdbt, DB_SET)) == 0) {
+ ret =
+ mgr->recover(logp, &rdbt, &key_lsn, TXN_UNDO, NULL);
+ if (F_ISSET(logp, DB_AM_THREAD) && rdbt.data != NULL) {
+ __os_free(rdbt.data, rdbt.size);
+ rdbt.data = NULL;
+ }
+ }
+ if (ret != 0)
+ return (ret);
+ }
+
+ return (ret);
+}
+
+/*
+ * Transaction checkpoint.
+ * If either kbytes or minutes is non-zero, then we only take the checkpoint
+ * more than "minutes" minutes have passed since the last checkpoint or if
+ * more than "kbytes" of log data have been written since the last checkpoint.
+ * When taking a checkpoint, find the oldest active transaction and figure out
+ * its first LSN. This is the lowest LSN we can checkpoint, since any record
+ * written after since that point may be involved in a transaction and may
+ * therefore need to be undone in the case of an abort.
+ */
+int
+txn_checkpoint(mgr, kbytes, minutes)
+ const DB_TXNMGR *mgr;
+ u_int32_t kbytes, minutes;
+{
+ DB_LOG *dblp;
+ DB_LSN ckp_lsn, sync_lsn, last_ckp;
+ TXN_DETAIL *txnp;
+ time_t last_ckp_time, now;
+ u_int32_t kbytes_written;
+ int ret;
+
+ TXN_PANIC_CHECK(mgr);
+
+ /*
+ * Check if we need to run recovery.
+ */
+ ZERO_LSN(ckp_lsn);
+ if (minutes != 0) {
+ (void)time(&now);
+
+ LOCK_TXNREGION(mgr);
+ last_ckp_time = mgr->region->time_ckp;
+ UNLOCK_TXNREGION(mgr);
+
+ if (now - last_ckp_time >= (time_t)(minutes * 60))
+ goto do_ckp;
+ }
+
+ if (kbytes != 0) {
+ dblp = mgr->dbenv->lg_info;
+ LOCK_LOGREGION(dblp);
+ kbytes_written =
+ dblp->lp->stat.st_wc_mbytes * 1024 +
+ dblp->lp->stat.st_wc_bytes / 1024;
+ ckp_lsn = dblp->lp->lsn;
+ UNLOCK_LOGREGION(dblp);
+ if (kbytes_written >= (u_int32_t)kbytes)
+ goto do_ckp;
+ }
+
+ /*
+ * If we checked time and data and didn't go to checkpoint,
+ * we're done.
+ */
+ if (minutes != 0 || kbytes != 0)
+ return (0);
+
+do_ckp:
+ if (IS_ZERO_LSN(ckp_lsn)) {
+ dblp = mgr->dbenv->lg_info;
+ LOCK_LOGREGION(dblp);
+ ckp_lsn = dblp->lp->lsn;
+ UNLOCK_LOGREGION(dblp);
+ }
+
+ /*
+ * We have to find an LSN such that all transactions begun
+ * before that LSN are complete.
+ */
+ LOCK_TXNREGION(mgr);
+
+ if (!IS_ZERO_LSN(mgr->region->pending_ckp))
+ ckp_lsn = mgr->region->pending_ckp;
+ else
+ for (txnp =
+ SH_TAILQ_FIRST(&mgr->region->active_txn, __txn_detail);
+ txnp != NULL;
+ txnp = SH_TAILQ_NEXT(txnp, links, __txn_detail)) {
+
+ /*
+ * Look through the active transactions for the
+ * lowest begin lsn.
+ */
+ if (!IS_ZERO_LSN(txnp->begin_lsn) &&
+ log_compare(&txnp->begin_lsn, &ckp_lsn) < 0)
+ ckp_lsn = txnp->begin_lsn;
+ }
+
+ mgr->region->pending_ckp = ckp_lsn;
+ UNLOCK_TXNREGION(mgr);
+
+ /*
+ * memp_sync may change the lsn you pass it, so don't pass it
+ * the actual ckp_lsn, pass it a temp instead.
+ */
+ sync_lsn = ckp_lsn;
+ if (mgr->dbenv->mp_info != NULL &&
+ (ret = memp_sync(mgr->dbenv->mp_info, &sync_lsn)) != 0) {
+ /*
+ * ret == DB_INCOMPLETE means that there are still buffers to
+ * flush, the checkpoint is not complete. Wait and try again.
+ */
+ if (ret > 0)
+ __db_err(mgr->dbenv,
+ "txn_checkpoint: system failure in memp_sync %s\n",
+ strerror(ret));
+ return (ret);
+ }
+ if (mgr->dbenv->lg_info != NULL) {
+ LOCK_TXNREGION(mgr);
+ last_ckp = mgr->region->last_ckp;
+ ZERO_LSN(mgr->region->pending_ckp);
+ UNLOCK_TXNREGION(mgr);
+
+ if ((ret = __txn_ckp_log(mgr->dbenv->lg_info,
+ NULL, &ckp_lsn, DB_CHECKPOINT, &ckp_lsn, &last_ckp)) != 0) {
+ __db_err(mgr->dbenv,
+ "txn_checkpoint: log failed at LSN [%ld %ld] %s\n",
+ (long)ckp_lsn.file, (long)ckp_lsn.offset,
+ strerror(ret));
+ return (ret);
+ }
+
+ LOCK_TXNREGION(mgr);
+ mgr->region->last_ckp = ckp_lsn;
+ (void)time(&mgr->region->time_ckp);
+ UNLOCK_TXNREGION(mgr);
+ }
+ return (0);
+}
+
+/*
+ * __txn_validate_region --
+ * Called at every interface to verify if the region has changed size,
+ * and if so, to remap the region in and reset the process' pointers.
+ */
+static int
+__txn_validate_region(tp)
+ DB_TXNMGR *tp;
+{
+ int ret;
+
+ if (tp->reginfo.size == tp->region->hdr.size)
+ return (0);
+
+ /* Detach/reattach the region. */
+ if ((ret = __db_rreattach(&tp->reginfo, tp->region->hdr.size)) != 0)
+ return (ret);
+
+ /* Reset region information. */
+ tp->region = tp->reginfo.addr;
+ tp->mem = &tp->region[1];
+
+ return (0);
+}
+
+static int
+__txn_grow_region(tp)
+ DB_TXNMGR *tp;
+{
+ size_t incr, oldsize;
+ u_int32_t mutex_offset, oldmax;
+ u_int8_t *curaddr;
+ int ret;
+
+ oldmax = tp->region->maxtxns;
+ incr = oldmax * sizeof(DB_TXN);
+ mutex_offset = tp->mutexp != NULL ?
+ (u_int8_t *)tp->mutexp - (u_int8_t *)tp->region : 0;
+
+ oldsize = tp->reginfo.size;
+ if ((ret = __db_rgrow(&tp->reginfo, oldsize + incr)) != 0)
+ return (ret);
+ tp->region = tp->reginfo.addr;
+
+ /* Throw the new space on the free list. */
+ curaddr = (u_int8_t *)tp->region + oldsize;
+ tp->mem = &tp->region[1];
+ tp->mutexp = mutex_offset != 0 ?
+ (db_mutex_t *)((u_int8_t *)tp->region + mutex_offset) : NULL;
+
+ *((size_t *)curaddr) = incr - sizeof(size_t);
+ curaddr += sizeof(size_t);
+ __db_shalloc_free(tp->mem, curaddr);
+
+ tp->region->maxtxns = 2 * oldmax;
+
+ return (0);
+}
+
+int
+txn_stat(mgr, statp, db_malloc)
+ DB_TXNMGR *mgr;
+ DB_TXN_STAT **statp;
+ void *(*db_malloc) __P((size_t));
+{
+ DB_TXN_STAT *stats;
+ TXN_DETAIL *txnp;
+ size_t nbytes;
+ u_int32_t nactive, ndx;
+ int ret;
+
+ TXN_PANIC_CHECK(mgr);
+
+ LOCK_TXNREGION(mgr);
+ nactive = mgr->region->nbegins -
+ mgr->region->naborts - mgr->region->ncommits;
+ UNLOCK_TXNREGION(mgr);
+
+ /*
+ * Allocate a bunch of extra active structures to handle any
+ * that have been created since we unlocked the region.
+ */
+ nbytes = sizeof(DB_TXN_STAT) + sizeof(DB_TXN_ACTIVE) * (nactive + 200);
+ if ((ret = __os_malloc(nbytes, db_malloc, &stats)) != 0)
+ return (ret);
+
+ LOCK_TXNREGION(mgr);
+ stats->st_last_txnid = mgr->region->last_txnid;
+ stats->st_last_ckp = mgr->region->last_ckp;
+ stats->st_maxtxns = mgr->region->maxtxns;
+ stats->st_naborts = mgr->region->naborts;
+ stats->st_nbegins = mgr->region->nbegins;
+ stats->st_ncommits = mgr->region->ncommits;
+ stats->st_pending_ckp = mgr->region->pending_ckp;
+ stats->st_time_ckp = mgr->region->time_ckp;
+ stats->st_nactive = stats->st_nbegins -
+ stats->st_naborts - stats->st_ncommits;
+ if (stats->st_nactive > nactive + 200)
+ stats->st_nactive = nactive + 200;
+ stats->st_txnarray = (DB_TXN_ACTIVE *)&stats[1];
+
+ ndx = 0;
+ for (txnp = SH_TAILQ_FIRST(&mgr->region->active_txn, __txn_detail);
+ txnp != NULL;
+ txnp = SH_TAILQ_NEXT(txnp, links, __txn_detail)) {
+ stats->st_txnarray[ndx].txnid = txnp->txnid;
+ stats->st_txnarray[ndx].lsn = txnp->begin_lsn;
+ ndx++;
+
+ if (ndx >= stats->st_nactive)
+ break;
+ }
+
+ stats->st_region_wait = mgr->region->hdr.lock.mutex_set_wait;
+ stats->st_region_nowait = mgr->region->hdr.lock.mutex_set_nowait;
+ stats->st_refcnt = mgr->region->hdr.refcnt;
+ stats->st_regsize = mgr->region->hdr.size;
+
+ UNLOCK_TXNREGION(mgr);
+ *statp = stats;
+ return (0);
+}
+
+static void
+__txn_freekids(txnp)
+ DB_TXN *txnp;
+{
+ DB_TXNMGR *mgr;
+ TXN_DETAIL *tp;
+ DB_TXN *kids;
+
+ mgr = txnp->mgrp;
+
+ for (kids = TAILQ_FIRST(&txnp->kids);
+ kids != NULL;
+ kids = TAILQ_FIRST(&txnp->kids)) {
+ /* Free any children of this transaction. */
+ __txn_freekids(kids);
+
+ /* Free the transaction detail in the region. */
+ LOCK_TXNREGION(mgr);
+ tp = (TXN_DETAIL *)((u_int8_t *)mgr->region + kids->off);
+ SH_TAILQ_REMOVE(&mgr->region->active_txn,
+ tp, links, __txn_detail);
+
+ __db_shalloc_free(mgr->mem, tp);
+ UNLOCK_TXNREGION(mgr);
+
+ /* Now remove from its parent. */
+ TAILQ_REMOVE(&txnp->kids, kids, klinks);
+ if (F_ISSET(txnp, TXN_MALLOC)) {
+ LOCK_TXNTHREAD(mgr);
+ TAILQ_REMOVE(&mgr->txn_chain, kids, links);
+ UNLOCK_TXNTHREAD(mgr);
+ __os_free(kids, sizeof(*kids));
+ }
+ }
+}
+
+/*
+ * __txn_is_ancestor --
+ * Determine if a transaction is an ancestor of another transaction.
+ * This is used during lock promotion when we do not have the per-process
+ * data structures that link parents together. Instead, we'll have to
+ * follow the links in the transaction region.
+ *
+ * PUBLIC: int __txn_is_ancestor __P((DB_TXNMGR *, size_t, size_t));
+ */
+int
+__txn_is_ancestor(mgr, hold_off, req_off)
+ DB_TXNMGR *mgr;
+ size_t hold_off, req_off;
+{
+ TXN_DETAIL *hold_tp, *req_tp;
+
+ hold_tp = (TXN_DETAIL *)((u_int8_t *)mgr->region + hold_off);
+ req_tp = (TXN_DETAIL *)((u_int8_t *)mgr->region + req_off);
+
+ while (req_tp->parent != 0) {
+ req_tp =
+ (TXN_DETAIL *)((u_int8_t *)mgr->region + req_tp->parent);
+ if (req_tp->txnid == hold_tp->txnid)
+ return (1);
+ }
+
+ return (0);
+}
diff --git a/usr/src/cmd/sendmail/db/txn/txn_auto.c b/usr/src/cmd/sendmail/db/txn/txn_auto.c
new file mode 100644
index 0000000000..e6d431f089
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/txn/txn_auto.c
@@ -0,0 +1,621 @@
+/* Do not edit: automatically built by dist/db_gen.sh. */
+#include "config.h"
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <ctype.h>
+#include <errno.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#endif
+
+#include "db_int.h"
+#include "db_page.h"
+#include "db_dispatch.h"
+#include "txn.h"
+#include "db_am.h"
+/*
+ * PUBLIC: int __txn_regop_log
+ * PUBLIC: __P((DB_LOG *, DB_TXN *, DB_LSN *, u_int32_t,
+ * PUBLIC: u_int32_t));
+ */
+int __txn_regop_log(logp, txnid, ret_lsnp, flags,
+ opcode)
+ DB_LOG *logp;
+ DB_TXN *txnid;
+ DB_LSN *ret_lsnp;
+ u_int32_t flags;
+ u_int32_t opcode;
+{
+ DBT logrec;
+ DB_LSN *lsnp, null_lsn;
+ u_int32_t rectype, txn_num;
+ int ret;
+ u_int8_t *bp;
+
+ rectype = DB_txn_regop;
+ txn_num = txnid == NULL ? 0 : txnid->txnid;
+ if (txnid == NULL) {
+ ZERO_LSN(null_lsn);
+ lsnp = &null_lsn;
+ } else
+ lsnp = &txnid->last_lsn;
+ logrec.size = sizeof(rectype) + sizeof(txn_num) + sizeof(DB_LSN)
+ + sizeof(opcode);
+ if ((ret = __os_malloc(logrec.size, NULL, &logrec.data)) != 0)
+ return (ret);
+
+ bp = logrec.data;
+ memcpy(bp, &rectype, sizeof(rectype));
+ bp += sizeof(rectype);
+ memcpy(bp, &txn_num, sizeof(txn_num));
+ bp += sizeof(txn_num);
+ memcpy(bp, lsnp, sizeof(DB_LSN));
+ bp += sizeof(DB_LSN);
+ memcpy(bp, &opcode, sizeof(opcode));
+ bp += sizeof(opcode);
+#ifdef DIAGNOSTIC
+ if ((u_int32_t)(bp - (u_int8_t *)logrec.data) != logrec.size)
+ fprintf(stderr, "Error in log record length");
+#endif
+ ret = log_put(logp, ret_lsnp, (DBT *)&logrec, flags);
+ if (txnid != NULL)
+ txnid->last_lsn = *ret_lsnp;
+ __os_free(logrec.data, 0);
+ return (ret);
+}
+
+/*
+ * PUBLIC: int __txn_regop_print
+ * PUBLIC: __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+ */
+int
+__txn_regop_print(notused1, dbtp, lsnp, notused2, notused3)
+ DB_LOG *notused1;
+ DBT *dbtp;
+ DB_LSN *lsnp;
+ int notused2;
+ void *notused3;
+{
+ __txn_regop_args *argp;
+ u_int32_t i;
+ u_int ch;
+ int ret;
+
+ i = 0;
+ ch = 0;
+ notused1 = NULL;
+ notused2 = 0;
+ notused3 = NULL;
+
+ if ((ret = __txn_regop_read(dbtp->data, &argp)) != 0)
+ return (ret);
+ printf("[%lu][%lu]txn_regop: rec: %lu txnid %lx prevlsn [%lu][%lu]\n",
+ (u_long)lsnp->file,
+ (u_long)lsnp->offset,
+ (u_long)argp->type,
+ (u_long)argp->txnid->txnid,
+ (u_long)argp->prev_lsn.file,
+ (u_long)argp->prev_lsn.offset);
+ printf("\topcode: %lu\n", (u_long)argp->opcode);
+ printf("\n");
+ __os_free(argp, 0);
+ return (0);
+}
+
+/*
+ * PUBLIC: int __txn_regop_read __P((void *, __txn_regop_args **));
+ */
+int
+__txn_regop_read(recbuf, argpp)
+ void *recbuf;
+ __txn_regop_args **argpp;
+{
+ __txn_regop_args *argp;
+ u_int8_t *bp;
+ int ret;
+
+ ret = __os_malloc(sizeof(__txn_regop_args) +
+ sizeof(DB_TXN), NULL, &argp);
+ if (ret != 0)
+ return (ret);
+ argp->txnid = (DB_TXN *)&argp[1];
+ bp = recbuf;
+ memcpy(&argp->type, bp, sizeof(argp->type));
+ bp += sizeof(argp->type);
+ memcpy(&argp->txnid->txnid, bp, sizeof(argp->txnid->txnid));
+ bp += sizeof(argp->txnid->txnid);
+ memcpy(&argp->prev_lsn, bp, sizeof(DB_LSN));
+ bp += sizeof(DB_LSN);
+ memcpy(&argp->opcode, bp, sizeof(argp->opcode));
+ bp += sizeof(argp->opcode);
+ *argpp = argp;
+ return (0);
+}
+
+/*
+ * PUBLIC: int __txn_ckp_log
+ * PUBLIC: __P((DB_LOG *, DB_TXN *, DB_LSN *, u_int32_t,
+ * PUBLIC: DB_LSN *, DB_LSN *));
+ */
+int __txn_ckp_log(logp, txnid, ret_lsnp, flags,
+ ckp_lsn, last_ckp)
+ DB_LOG *logp;
+ DB_TXN *txnid;
+ DB_LSN *ret_lsnp;
+ u_int32_t flags;
+ DB_LSN * ckp_lsn;
+ DB_LSN * last_ckp;
+{
+ DBT logrec;
+ DB_LSN *lsnp, null_lsn;
+ u_int32_t rectype, txn_num;
+ int ret;
+ u_int8_t *bp;
+
+ rectype = DB_txn_ckp;
+ txn_num = txnid == NULL ? 0 : txnid->txnid;
+ if (txnid == NULL) {
+ ZERO_LSN(null_lsn);
+ lsnp = &null_lsn;
+ } else
+ lsnp = &txnid->last_lsn;
+ logrec.size = sizeof(rectype) + sizeof(txn_num) + sizeof(DB_LSN)
+ + sizeof(*ckp_lsn)
+ + sizeof(*last_ckp);
+ if ((ret = __os_malloc(logrec.size, NULL, &logrec.data)) != 0)
+ return (ret);
+
+ bp = logrec.data;
+ memcpy(bp, &rectype, sizeof(rectype));
+ bp += sizeof(rectype);
+ memcpy(bp, &txn_num, sizeof(txn_num));
+ bp += sizeof(txn_num);
+ memcpy(bp, lsnp, sizeof(DB_LSN));
+ bp += sizeof(DB_LSN);
+ if (ckp_lsn != NULL)
+ memcpy(bp, ckp_lsn, sizeof(*ckp_lsn));
+ else
+ memset(bp, 0, sizeof(*ckp_lsn));
+ bp += sizeof(*ckp_lsn);
+ if (last_ckp != NULL)
+ memcpy(bp, last_ckp, sizeof(*last_ckp));
+ else
+ memset(bp, 0, sizeof(*last_ckp));
+ bp += sizeof(*last_ckp);
+#ifdef DIAGNOSTIC
+ if ((u_int32_t)(bp - (u_int8_t *)logrec.data) != logrec.size)
+ fprintf(stderr, "Error in log record length");
+#endif
+ ret = log_put(logp, ret_lsnp, (DBT *)&logrec, flags);
+ if (txnid != NULL)
+ txnid->last_lsn = *ret_lsnp;
+ __os_free(logrec.data, 0);
+ return (ret);
+}
+
+/*
+ * PUBLIC: int __txn_ckp_print
+ * PUBLIC: __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+ */
+int
+__txn_ckp_print(notused1, dbtp, lsnp, notused2, notused3)
+ DB_LOG *notused1;
+ DBT *dbtp;
+ DB_LSN *lsnp;
+ int notused2;
+ void *notused3;
+{
+ __txn_ckp_args *argp;
+ u_int32_t i;
+ u_int ch;
+ int ret;
+
+ i = 0;
+ ch = 0;
+ notused1 = NULL;
+ notused2 = 0;
+ notused3 = NULL;
+
+ if ((ret = __txn_ckp_read(dbtp->data, &argp)) != 0)
+ return (ret);
+ printf("[%lu][%lu]txn_ckp: rec: %lu txnid %lx prevlsn [%lu][%lu]\n",
+ (u_long)lsnp->file,
+ (u_long)lsnp->offset,
+ (u_long)argp->type,
+ (u_long)argp->txnid->txnid,
+ (u_long)argp->prev_lsn.file,
+ (u_long)argp->prev_lsn.offset);
+ printf("\tckp_lsn: [%lu][%lu]\n",
+ (u_long)argp->ckp_lsn.file, (u_long)argp->ckp_lsn.offset);
+ printf("\tlast_ckp: [%lu][%lu]\n",
+ (u_long)argp->last_ckp.file, (u_long)argp->last_ckp.offset);
+ printf("\n");
+ __os_free(argp, 0);
+ return (0);
+}
+
+/*
+ * PUBLIC: int __txn_ckp_read __P((void *, __txn_ckp_args **));
+ */
+int
+__txn_ckp_read(recbuf, argpp)
+ void *recbuf;
+ __txn_ckp_args **argpp;
+{
+ __txn_ckp_args *argp;
+ u_int8_t *bp;
+ int ret;
+
+ ret = __os_malloc(sizeof(__txn_ckp_args) +
+ sizeof(DB_TXN), NULL, &argp);
+ if (ret != 0)
+ return (ret);
+ argp->txnid = (DB_TXN *)&argp[1];
+ bp = recbuf;
+ memcpy(&argp->type, bp, sizeof(argp->type));
+ bp += sizeof(argp->type);
+ memcpy(&argp->txnid->txnid, bp, sizeof(argp->txnid->txnid));
+ bp += sizeof(argp->txnid->txnid);
+ memcpy(&argp->prev_lsn, bp, sizeof(DB_LSN));
+ bp += sizeof(DB_LSN);
+ memcpy(&argp->ckp_lsn, bp, sizeof(argp->ckp_lsn));
+ bp += sizeof(argp->ckp_lsn);
+ memcpy(&argp->last_ckp, bp, sizeof(argp->last_ckp));
+ bp += sizeof(argp->last_ckp);
+ *argpp = argp;
+ return (0);
+}
+
+/*
+ * PUBLIC: int __txn_xa_regop_log
+ * PUBLIC: __P((DB_LOG *, DB_TXN *, DB_LSN *, u_int32_t,
+ * PUBLIC: u_int32_t, const DBT *, int32_t, u_int32_t,
+ * PUBLIC: u_int32_t, DB_LSN *));
+ */
+int __txn_xa_regop_log(logp, txnid, ret_lsnp, flags,
+ opcode, xid, formatID, gtrid, bqual, begin_lsn)
+ DB_LOG *logp;
+ DB_TXN *txnid;
+ DB_LSN *ret_lsnp;
+ u_int32_t flags;
+ u_int32_t opcode;
+ const DBT *xid;
+ int32_t formatID;
+ u_int32_t gtrid;
+ u_int32_t bqual;
+ DB_LSN * begin_lsn;
+{
+ DBT logrec;
+ DB_LSN *lsnp, null_lsn;
+ u_int32_t zero;
+ u_int32_t rectype, txn_num;
+ int ret;
+ u_int8_t *bp;
+
+ rectype = DB_txn_xa_regop;
+ txn_num = txnid == NULL ? 0 : txnid->txnid;
+ if (txnid == NULL) {
+ ZERO_LSN(null_lsn);
+ lsnp = &null_lsn;
+ } else
+ lsnp = &txnid->last_lsn;
+ logrec.size = sizeof(rectype) + sizeof(txn_num) + sizeof(DB_LSN)
+ + sizeof(opcode)
+ + sizeof(u_int32_t) + (xid == NULL ? 0 : xid->size)
+ + sizeof(formatID)
+ + sizeof(gtrid)
+ + sizeof(bqual)
+ + sizeof(*begin_lsn);
+ if ((ret = __os_malloc(logrec.size, NULL, &logrec.data)) != 0)
+ return (ret);
+
+ bp = logrec.data;
+ memcpy(bp, &rectype, sizeof(rectype));
+ bp += sizeof(rectype);
+ memcpy(bp, &txn_num, sizeof(txn_num));
+ bp += sizeof(txn_num);
+ memcpy(bp, lsnp, sizeof(DB_LSN));
+ bp += sizeof(DB_LSN);
+ memcpy(bp, &opcode, sizeof(opcode));
+ bp += sizeof(opcode);
+ if (xid == NULL) {
+ zero = 0;
+ memcpy(bp, &zero, sizeof(u_int32_t));
+ bp += sizeof(u_int32_t);
+ } else {
+ memcpy(bp, &xid->size, sizeof(xid->size));
+ bp += sizeof(xid->size);
+ memcpy(bp, xid->data, xid->size);
+ bp += xid->size;
+ }
+ memcpy(bp, &formatID, sizeof(formatID));
+ bp += sizeof(formatID);
+ memcpy(bp, &gtrid, sizeof(gtrid));
+ bp += sizeof(gtrid);
+ memcpy(bp, &bqual, sizeof(bqual));
+ bp += sizeof(bqual);
+ if (begin_lsn != NULL)
+ memcpy(bp, begin_lsn, sizeof(*begin_lsn));
+ else
+ memset(bp, 0, sizeof(*begin_lsn));
+ bp += sizeof(*begin_lsn);
+#ifdef DIAGNOSTIC
+ if ((u_int32_t)(bp - (u_int8_t *)logrec.data) != logrec.size)
+ fprintf(stderr, "Error in log record length");
+#endif
+ ret = log_put(logp, ret_lsnp, (DBT *)&logrec, flags);
+ if (txnid != NULL)
+ txnid->last_lsn = *ret_lsnp;
+ __os_free(logrec.data, 0);
+ return (ret);
+}
+
+/*
+ * PUBLIC: int __txn_xa_regop_print
+ * PUBLIC: __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+ */
+int
+__txn_xa_regop_print(notused1, dbtp, lsnp, notused2, notused3)
+ DB_LOG *notused1;
+ DBT *dbtp;
+ DB_LSN *lsnp;
+ int notused2;
+ void *notused3;
+{
+ __txn_xa_regop_args *argp;
+ u_int32_t i;
+ u_int ch;
+ int ret;
+
+ i = 0;
+ ch = 0;
+ notused1 = NULL;
+ notused2 = 0;
+ notused3 = NULL;
+
+ if ((ret = __txn_xa_regop_read(dbtp->data, &argp)) != 0)
+ return (ret);
+ printf("[%lu][%lu]txn_xa_regop: rec: %lu txnid %lx prevlsn [%lu][%lu]\n",
+ (u_long)lsnp->file,
+ (u_long)lsnp->offset,
+ (u_long)argp->type,
+ (u_long)argp->txnid->txnid,
+ (u_long)argp->prev_lsn.file,
+ (u_long)argp->prev_lsn.offset);
+ printf("\topcode: %lu\n", (u_long)argp->opcode);
+ printf("\txid: ");
+ for (i = 0; i < argp->xid.size; i++) {
+ ch = ((u_int8_t *)argp->xid.data)[i];
+ if (isprint(ch) || ch == 0xa)
+ putchar(ch);
+ else
+ printf("%#x ", ch);
+ }
+ printf("\n");
+ printf("\tformatID: %ld\n", (long)argp->formatID);
+ printf("\tgtrid: %u\n", argp->gtrid);
+ printf("\tbqual: %u\n", argp->bqual);
+ printf("\tbegin_lsn: [%lu][%lu]\n",
+ (u_long)argp->begin_lsn.file, (u_long)argp->begin_lsn.offset);
+ printf("\n");
+ __os_free(argp, 0);
+ return (0);
+}
+
+/*
+ * PUBLIC: int __txn_xa_regop_read __P((void *, __txn_xa_regop_args **));
+ */
+int
+__txn_xa_regop_read(recbuf, argpp)
+ void *recbuf;
+ __txn_xa_regop_args **argpp;
+{
+ __txn_xa_regop_args *argp;
+ u_int8_t *bp;
+ int ret;
+
+ ret = __os_malloc(sizeof(__txn_xa_regop_args) +
+ sizeof(DB_TXN), NULL, &argp);
+ if (ret != 0)
+ return (ret);
+ argp->txnid = (DB_TXN *)&argp[1];
+ bp = recbuf;
+ memcpy(&argp->type, bp, sizeof(argp->type));
+ bp += sizeof(argp->type);
+ memcpy(&argp->txnid->txnid, bp, sizeof(argp->txnid->txnid));
+ bp += sizeof(argp->txnid->txnid);
+ memcpy(&argp->prev_lsn, bp, sizeof(DB_LSN));
+ bp += sizeof(DB_LSN);
+ memcpy(&argp->opcode, bp, sizeof(argp->opcode));
+ bp += sizeof(argp->opcode);
+ memcpy(&argp->xid.size, bp, sizeof(u_int32_t));
+ bp += sizeof(u_int32_t);
+ argp->xid.data = bp;
+ bp += argp->xid.size;
+ memcpy(&argp->formatID, bp, sizeof(argp->formatID));
+ bp += sizeof(argp->formatID);
+ memcpy(&argp->gtrid, bp, sizeof(argp->gtrid));
+ bp += sizeof(argp->gtrid);
+ memcpy(&argp->bqual, bp, sizeof(argp->bqual));
+ bp += sizeof(argp->bqual);
+ memcpy(&argp->begin_lsn, bp, sizeof(argp->begin_lsn));
+ bp += sizeof(argp->begin_lsn);
+ *argpp = argp;
+ return (0);
+}
+
+/*
+ * PUBLIC: int __txn_child_log
+ * PUBLIC: __P((DB_LOG *, DB_TXN *, DB_LSN *, u_int32_t,
+ * PUBLIC: u_int32_t, u_int32_t));
+ */
+int __txn_child_log(logp, txnid, ret_lsnp, flags,
+ opcode, parent)
+ DB_LOG *logp;
+ DB_TXN *txnid;
+ DB_LSN *ret_lsnp;
+ u_int32_t flags;
+ u_int32_t opcode;
+ u_int32_t parent;
+{
+ DBT logrec;
+ DB_LSN *lsnp, null_lsn;
+ u_int32_t rectype, txn_num;
+ int ret;
+ u_int8_t *bp;
+
+ rectype = DB_txn_child;
+ txn_num = txnid == NULL ? 0 : txnid->txnid;
+ if (txnid == NULL) {
+ ZERO_LSN(null_lsn);
+ lsnp = &null_lsn;
+ } else
+ lsnp = &txnid->last_lsn;
+ logrec.size = sizeof(rectype) + sizeof(txn_num) + sizeof(DB_LSN)
+ + sizeof(opcode)
+ + sizeof(parent);
+ if ((ret = __os_malloc(logrec.size, NULL, &logrec.data)) != 0)
+ return (ret);
+
+ bp = logrec.data;
+ memcpy(bp, &rectype, sizeof(rectype));
+ bp += sizeof(rectype);
+ memcpy(bp, &txn_num, sizeof(txn_num));
+ bp += sizeof(txn_num);
+ memcpy(bp, lsnp, sizeof(DB_LSN));
+ bp += sizeof(DB_LSN);
+ memcpy(bp, &opcode, sizeof(opcode));
+ bp += sizeof(opcode);
+ memcpy(bp, &parent, sizeof(parent));
+ bp += sizeof(parent);
+#ifdef DIAGNOSTIC
+ if ((u_int32_t)(bp - (u_int8_t *)logrec.data) != logrec.size)
+ fprintf(stderr, "Error in log record length");
+#endif
+ ret = log_put(logp, ret_lsnp, (DBT *)&logrec, flags);
+ if (txnid != NULL)
+ txnid->last_lsn = *ret_lsnp;
+ __os_free(logrec.data, 0);
+ return (ret);
+}
+
+/*
+ * PUBLIC: int __txn_child_print
+ * PUBLIC: __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+ */
+int
+__txn_child_print(notused1, dbtp, lsnp, notused2, notused3)
+ DB_LOG *notused1;
+ DBT *dbtp;
+ DB_LSN *lsnp;
+ int notused2;
+ void *notused3;
+{
+ __txn_child_args *argp;
+ u_int32_t i;
+ u_int ch;
+ int ret;
+
+ i = 0;
+ ch = 0;
+ notused1 = NULL;
+ notused2 = 0;
+ notused3 = NULL;
+
+ if ((ret = __txn_child_read(dbtp->data, &argp)) != 0)
+ return (ret);
+ printf("[%lu][%lu]txn_child: rec: %lu txnid %lx prevlsn [%lu][%lu]\n",
+ (u_long)lsnp->file,
+ (u_long)lsnp->offset,
+ (u_long)argp->type,
+ (u_long)argp->txnid->txnid,
+ (u_long)argp->prev_lsn.file,
+ (u_long)argp->prev_lsn.offset);
+ printf("\topcode: %lu\n", (u_long)argp->opcode);
+ printf("\tparent: %lu\n", (u_long)argp->parent);
+ printf("\n");
+ __os_free(argp, 0);
+ return (0);
+}
+
+/*
+ * PUBLIC: int __txn_child_read __P((void *, __txn_child_args **));
+ */
+int
+__txn_child_read(recbuf, argpp)
+ void *recbuf;
+ __txn_child_args **argpp;
+{
+ __txn_child_args *argp;
+ u_int8_t *bp;
+ int ret;
+
+ ret = __os_malloc(sizeof(__txn_child_args) +
+ sizeof(DB_TXN), NULL, &argp);
+ if (ret != 0)
+ return (ret);
+ argp->txnid = (DB_TXN *)&argp[1];
+ bp = recbuf;
+ memcpy(&argp->type, bp, sizeof(argp->type));
+ bp += sizeof(argp->type);
+ memcpy(&argp->txnid->txnid, bp, sizeof(argp->txnid->txnid));
+ bp += sizeof(argp->txnid->txnid);
+ memcpy(&argp->prev_lsn, bp, sizeof(DB_LSN));
+ bp += sizeof(DB_LSN);
+ memcpy(&argp->opcode, bp, sizeof(argp->opcode));
+ bp += sizeof(argp->opcode);
+ memcpy(&argp->parent, bp, sizeof(argp->parent));
+ bp += sizeof(argp->parent);
+ *argpp = argp;
+ return (0);
+}
+
+/*
+ * PUBLIC: int __txn_init_print __P((DB_ENV *));
+ */
+int
+__txn_init_print(dbenv)
+ DB_ENV *dbenv;
+{
+ int ret;
+
+ if ((ret = __db_add_recovery(dbenv,
+ __txn_regop_print, DB_txn_regop)) != 0)
+ return (ret);
+ if ((ret = __db_add_recovery(dbenv,
+ __txn_ckp_print, DB_txn_ckp)) != 0)
+ return (ret);
+ if ((ret = __db_add_recovery(dbenv,
+ __txn_xa_regop_print, DB_txn_xa_regop)) != 0)
+ return (ret);
+ if ((ret = __db_add_recovery(dbenv,
+ __txn_child_print, DB_txn_child)) != 0)
+ return (ret);
+ return (0);
+}
+
+/*
+ * PUBLIC: int __txn_init_recover __P((DB_ENV *));
+ */
+int
+__txn_init_recover(dbenv)
+ DB_ENV *dbenv;
+{
+ int ret;
+
+ if ((ret = __db_add_recovery(dbenv,
+ __txn_regop_recover, DB_txn_regop)) != 0)
+ return (ret);
+ if ((ret = __db_add_recovery(dbenv,
+ __txn_ckp_recover, DB_txn_ckp)) != 0)
+ return (ret);
+ if ((ret = __db_add_recovery(dbenv,
+ __txn_xa_regop_recover, DB_txn_xa_regop)) != 0)
+ return (ret);
+ if ((ret = __db_add_recovery(dbenv,
+ __txn_child_recover, DB_txn_child)) != 0)
+ return (ret);
+ return (0);
+}
+
diff --git a/usr/src/cmd/sendmail/db/txn/txn_rec.c b/usr/src/cmd/sendmail/db/txn/txn_rec.c
new file mode 100644
index 0000000000..f21a0f92c8
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/txn/txn_rec.c
@@ -0,0 +1,296 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+/*
+ * Copyright (c) 1996
+ * The President and Fellows of Harvard University. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)txn_rec.c 10.15 (Sleepycat) 1/3/99";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <errno.h>
+#endif
+
+#include "db_int.h"
+#include "db_page.h"
+#include "shqueue.h"
+#include "txn.h"
+#include "db_am.h"
+#include "log.h"
+#include "common_ext.h"
+
+static int __txn_restore_txn __P((DB_ENV *, DB_LSN *, __txn_xa_regop_args *));
+
+#define IS_XA_TXN(R) (R->xid.size != 0)
+
+/*
+ * PUBLIC: int __txn_regop_recover
+ * PUBLIC: __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+ *
+ * These records are only ever written for commits.
+ */
+int
+__txn_regop_recover(logp, dbtp, lsnp, redo, info)
+ DB_LOG *logp;
+ DBT *dbtp;
+ DB_LSN *lsnp;
+ int redo;
+ void *info;
+{
+ __txn_regop_args *argp;
+ int ret;
+
+#ifdef DEBUG_RECOVER
+ (void)__txn_regop_print(logp, dbtp, lsnp, redo, info);
+#endif
+ COMPQUIET(redo, 0);
+ COMPQUIET(logp, NULL);
+
+ if ((ret = __txn_regop_read(dbtp->data, &argp)) != 0)
+ return (ret);
+
+ if (argp->opcode != TXN_COMMIT)
+ ret = EINVAL;
+ else
+ if (__db_txnlist_find(info, argp->txnid->txnid) == DB_NOTFOUND)
+ ret = __db_txnlist_add(info, argp->txnid->txnid);
+
+ if (ret == 0)
+ *lsnp = argp->prev_lsn;
+ __os_free(argp, 0);
+
+ return (ret);
+}
+
+/*
+ * PUBLIC: int __txn_xa_regop_recover
+ * PUBLIC: __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+ *
+ * These records are only ever written for prepares.
+ */
+int
+__txn_xa_regop_recover(logp, dbtp, lsnp, redo, info)
+ DB_LOG *logp;
+ DBT *dbtp;
+ DB_LSN *lsnp;
+ int redo;
+ void *info;
+{
+ __txn_xa_regop_args *argp;
+ int ret;
+
+#ifdef DEBUG_RECOVER
+ (void)__txn_xa_regop_print(logp, dbtp, lsnp, redo, info);
+#endif
+ COMPQUIET(redo, 0);
+ COMPQUIET(logp, NULL);
+
+ if ((ret = __txn_xa_regop_read(dbtp->data, &argp)) != 0)
+ return (ret);
+
+ if (argp->opcode != TXN_PREPARE)
+ ret = EINVAL;
+ else {
+ /*
+ * Whether we are in XA or not, we need to call
+ * __db_txnlist_find so that we update the maxid.
+ * If this is an XA transaction, then we treat
+ * prepares like commits so that we roll forward to
+ * a point where we can handle commit/abort calls
+ * from the TMS. If this isn't XA, then a prepare
+ * is treated like a No-op; we only care about the
+ * commit.
+ */
+ ret = __db_txnlist_find(info, argp->txnid->txnid);
+ if (IS_XA_TXN(argp) && ret == DB_NOTFOUND) {
+ /*
+ * This is an XA prepared, but not yet committed
+ * transaction. We need to add it to the
+ * transaction list, so that it gets rolled
+ * forward. We also have to add it to the region's
+ * internal state so it can be properly aborted
+ * or recovered.
+ */
+ ret = __db_txnlist_add(info, argp->txnid->txnid);
+ if (ret == 0)
+ ret = __txn_restore_txn(logp->dbenv,
+ lsnp, argp);
+ }
+ }
+
+ if (ret == 0)
+ *lsnp = argp->prev_lsn;
+ __os_free(argp, 0);
+
+ return (ret);
+}
+
+/*
+ * PUBLIC: int __txn_ckp_recover __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+ */
+int
+__txn_ckp_recover(logp, dbtp, lsnp, redo, info)
+ DB_LOG *logp;
+ DBT *dbtp;
+ DB_LSN *lsnp;
+ int redo;
+ void *info;
+{
+ __txn_ckp_args *argp;
+ int ret;
+
+#ifdef DEBUG_RECOVER
+ __txn_ckp_print(logp, dbtp, lsnp, redo, info);
+#endif
+ COMPQUIET(logp, NULL);
+
+ if ((ret = __txn_ckp_read(dbtp->data, &argp)) != 0)
+ return (ret);
+
+ /*
+ * Check for 'restart' checkpoint record. This occurs when the
+ * checkpoint lsn is equal to the lsn of the checkpoint record
+ * and means that we could set the transaction ID back to 1, so
+ * that we don't exhaust the transaction ID name space.
+ */
+ if (argp->ckp_lsn.file == lsnp->file &&
+ argp->ckp_lsn.offset == lsnp->offset)
+ __db_txnlist_gen(info, redo ? -1 : 1);
+
+ *lsnp = argp->last_ckp;
+ __os_free(argp, 0);
+ return (DB_TXN_CKP);
+}
+
+/*
+ * __txn_child_recover
+ * Recover a commit record for a child transaction.
+ *
+ * PUBLIC: int __txn_child_recover
+ * PUBLIC: __P((DB_LOG *, DBT *, DB_LSN *, int, void *));
+ */
+int
+__txn_child_recover(logp, dbtp, lsnp, redo, info)
+ DB_LOG *logp;
+ DBT *dbtp;
+ DB_LSN *lsnp;
+ int redo;
+ void *info;
+{
+ __txn_child_args *argp;
+ int ret;
+
+#ifdef DEBUG_RECOVER
+ (void)__txn_child_print(logp, dbtp, lsnp, redo, info);
+#endif
+ COMPQUIET(redo, 0);
+ COMPQUIET(logp, NULL);
+
+ if ((ret = __txn_child_read(dbtp->data, &argp)) != 0)
+ return (ret);
+
+ /*
+ * We count the child as committed only if its parent committed.
+ * So, if we are not yet in the transaction list, but our parent
+ * is, then we should go ahead and commit.
+ */
+ if (argp->opcode != TXN_COMMIT)
+ ret = EINVAL;
+ else
+ if (__db_txnlist_find(info, argp->parent) == 0 &&
+ __db_txnlist_find(info, argp->txnid->txnid) == DB_NOTFOUND)
+ ret = __db_txnlist_add(info, argp->txnid->txnid);
+
+ if (ret == 0)
+ *lsnp = argp->prev_lsn;
+ __os_free(argp, 0);
+
+ return (ret);
+}
+
+/*
+ * __txn_restore_txn --
+ * Using only during XA recovery. If we find any transactions that are
+ * prepared, but not yet committed, then we need to restore the transaction's
+ * state into the shared region, because the TM is going to issue a txn_abort
+ * or txn_commit and we need to respond correctly.
+ *
+ * lsnp is the LSN of the returned LSN
+ * argp is the perpare record (in an appropriate structure)
+ */
+static int
+__txn_restore_txn(dbenv, lsnp, argp)
+ DB_ENV *dbenv;
+ DB_LSN *lsnp;
+ __txn_xa_regop_args *argp;
+{
+ DB_TXNMGR *mgr;
+ TXN_DETAIL *td;
+ int ret;
+
+ if (argp->xid.size == 0)
+ return(0);
+
+ mgr = dbenv->tx_info;
+ LOCK_TXNREGION(mgr);
+
+ /* Allocate a new transaction detail structure. */
+ if ((ret = __db_shalloc(mgr->mem, sizeof(TXN_DETAIL), 0, &td)) != 0)
+ return (ret);
+
+ /* Place transaction on active transaction list. */
+ SH_TAILQ_INSERT_HEAD(&mgr->region->active_txn, td, links, __txn_detail);
+
+ td->txnid = argp->txnid->txnid;
+ td->begin_lsn = argp->begin_lsn;
+ td->last_lsn = *lsnp;
+ td->last_lock = 0;
+ td->parent = 0;
+ td->status = TXN_PREPARED;
+ td->xa_status = TXN_XA_PREPARED;
+ memcpy(td->xid, argp->xid.data, argp->xid.size);
+ td->bqual = argp->bqual;
+ td->gtrid = argp->gtrid;
+ td->format = argp->formatID;
+
+ UNLOCK_TXNREGION(mgr);
+ return (0);
+}
diff --git a/usr/src/cmd/sendmail/db/xa/xa.c b/usr/src/cmd/sendmail/db/xa/xa.c
new file mode 100644
index 0000000000..f8d96f5d37
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/xa/xa.c
@@ -0,0 +1,681 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1998
+ * Sleepycat Software. All rights reserved.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/* XXX Remove the global transaction and hang it off the environment. */
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)xa.c 10.4 (Sleepycat) 10/11/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#endif
+
+#include "db_int.h"
+#include "db_page.h"
+#include "shqueue.h"
+#include "log.h"
+#include "txn.h"
+#include "db_auto.h"
+#include "db_ext.h"
+#include "db_dispatch.h"
+
+static int __db_xa_close __P((char *, int, long));
+static int __db_xa_commit __P((XID *, int, long));
+static int __db_xa_complete __P((int *, int *, int, long));
+static int __db_xa_end __P((XID *, int, long));
+static int __db_xa_forget __P((XID *, int, long));
+static int __db_xa_open __P((char *, int, long));
+static int __db_xa_prepare __P((XID *, int, long));
+static int __db_xa_recover __P((XID *, long, int, long));
+static int __db_xa_rollback __P((XID *, int, long));
+static int __db_xa_start __P((XID *, int, long));
+static void __xa_txn_end __P((DB_ENV *));
+static void __xa_txn_init __P((DB_ENV *, TXN_DETAIL *, size_t));
+
+/*
+ * Possible flag values:
+ * Dynamic registration 0 => no dynamic registration
+ * TMREGISTER => dynamic registration
+ * Asynchronous operation 0 => no support for asynchrony
+ * TMUSEASYNC => async support
+ * Migration support 0 => migration of transactions across
+ * threads is possible
+ * TMNOMIGRATE => no migration across threads
+ */
+const struct xa_switch_t db_xa_switch = {
+ "Berkeley DB", /* name[RMNAMESZ] */
+ TMNOMIGRATE, /* flags */
+ 0, /* version */
+ __db_xa_open, /* xa_open_entry */
+ __db_xa_close, /* xa_close_entry */
+ __db_xa_start, /* xa_start_entry */
+ __db_xa_end, /* xa_end_entry */
+ __db_xa_rollback, /* xa_rollback_entry */
+ __db_xa_prepare, /* xa_prepare_entry */
+ __db_xa_commit, /* xa_commit_entry */
+ __db_xa_recover, /* xa_recover_entry */
+ __db_xa_forget, /* xa_forget_entry */
+ __db_xa_complete /* xa_complete_entry */
+};
+
+/*
+ * __db_xa_open --
+ * The open call in the XA protocol. The rmid field is an id number
+ * that the TM assigned us and will pass us on every xa call. We need to
+ * map that rmid number into a dbenv structure that we create during
+ * initialization. Since this id number is thread specific, we do not
+ * need to store it in shared memory. The file xa_map.c implements all
+ * such xa->db mappings.
+ * The xa_info field is instance specific information. We require
+ * that the value of DB_HOME be passed in xa_info. Since xa_info is the
+ * only thing that we get to pass to db_appinit, any config information
+ * will have to be done via a config file instead of via the db_appinit
+ * call.
+ */
+static int
+__db_xa_open(xa_info, rmid, flags)
+ char *xa_info;
+ int rmid;
+ long flags;
+{
+ DB_ENV *env;
+
+ if (LF_ISSET(TMASYNC))
+ return (XAER_ASYNC);
+ if (flags != TMNOFLAGS)
+ return (XAER_INVAL);
+
+ /* Verify if we already have this environment open. */
+ if (__db_rmid_to_env(rmid, &env, 0) == 0)
+ return (XA_OK);
+
+ /*
+ * Since we cannot tell whether the environment is OK or not,
+ * we can't actually do the db_appinit in xa_open. Instead,
+ * we save the mapping between the rmid and the xa_info. If
+ * we next get a call to __xa_recover, we do the db_appinit
+ * with DB_RECOVER set. If we get any other call, then we
+ * do the db_appinit.
+ */
+ return (__db_map_rmid_name(rmid, xa_info));
+}
+
+/*
+ * __db_xa_close --
+ * The close call of the XA protocol. The only trickiness here
+ * is that if there are any active transactions, we must fail. It is
+ * *not* an error to call close on an environment that has already been
+ * closed (I am interpreting that to mean it's OK to call close on an
+ * environment that has never been opened).
+ */
+static int
+__db_xa_close(xa_info, rmid, flags)
+ char *xa_info;
+ int rmid;
+ long flags;
+{
+ DB_ENV *env;
+ int ret, t_ret;
+
+ COMPQUIET(xa_info, NULL);
+
+ if (LF_ISSET(TMASYNC))
+ return (XAER_ASYNC);
+ if (flags != TMNOFLAGS)
+ return (XAER_INVAL);
+
+ /* If the environment is closed, then we're done. */
+ if (__db_rmid_to_env(rmid, &env, 0) != 0)
+ return (XA_OK);
+
+ /* Check if there are any pending transactions. */
+ if (env->xa_txn != NULL && env->xa_txn->txnid != TXN_INVALID)
+ return (XAER_PROTO);
+
+ /* Now, destroy the mapping and close the environment. */
+ ret = __db_unmap_rmid(rmid);
+ if ((t_ret = db_appexit(env)) != 0 && ret == 0)
+ ret = t_ret;
+
+ __os_free(env, sizeof(DB_ENV));
+
+ return (ret == 0 ? XA_OK : XAER_RMERR);
+}
+
+/*
+ * __db_xa_start --
+ * Begin a transaction for the current resource manager.
+ */
+static int
+__db_xa_start(xid, rmid, flags)
+ XID *xid;
+ int rmid;
+ long flags;
+{
+ DB_ENV *env;
+ TXN_DETAIL *td;
+ size_t off;
+ int is_known;
+
+#define OK_FLAGS (TMJOIN | TMRESUME | TMNOWAIT | TMASYNC | TMNOFLAGS)
+ if (LF_ISSET(~OK_FLAGS))
+ return (XAER_INVAL);
+
+ if (LF_ISSET(TMJOIN) && LF_ISSET(TMRESUME))
+ return (XAER_INVAL);
+
+ if (LF_ISSET(TMASYNC))
+ return (XAER_ASYNC);
+
+ if (__db_rmid_to_env(rmid, &env, 1) != 0)
+ return (XAER_PROTO);
+
+ is_known = __db_xid_to_txn(env, xid, &off) == 0;
+
+ if (is_known && !LF_ISSET(TMRESUME) && !LF_ISSET(TMJOIN))
+ return (XAER_DUPID);
+
+ if (!is_known && LF_ISSET(TMRESUME | TMJOIN))
+ return (XAER_NOTA);
+
+ /*
+ * This can't block, so we can ignore TMNOWAIT.
+ *
+ * Other error conditions: RMERR, RMFAIL, OUTSIDE, PROTO, RB*
+ */
+ if (is_known) {
+ td = (TXN_DETAIL *)((u_int8_t *)env->tx_info->region + off);
+ if (td->xa_status == TXN_XA_SUSPENDED && !LF_ISSET(TMRESUME))
+ return (XAER_PROTO);
+ if (td->xa_status == TXN_XA_DEADLOCKED)
+ return (XA_RBDEADLOCK);
+ if (td->xa_status == TXN_XA_ABORTED)
+ return (XA_RBOTHER);
+
+ /* Now, fill in the global transaction structure. */
+ __xa_txn_init(env, td, off);
+ td->xa_status = TXN_XA_STARTED;
+ } else {
+ if (__txn_xa_begin(env, env->xa_txn) != 0)
+ return (XAER_RMERR);
+ (void)__db_map_xid(env, xid, env->xa_txn->off);
+ td = (TXN_DETAIL *)
+ ((u_int8_t *)env->tx_info->region + env->xa_txn->off);
+ td->xa_status = TXN_XA_STARTED;
+ }
+ return (XA_OK);
+}
+
+/*
+ * __db_xa_end --
+ * Disassociate the current transaction from the current process.
+ */
+static int
+__db_xa_end(xid, rmid, flags)
+ XID *xid;
+ int rmid;
+ long flags;
+{
+ DB_ENV *env;
+ DB_TXN *txn;
+ TXN_DETAIL *td;
+ size_t off;
+
+ if (flags != TMNOFLAGS && !LF_ISSET(TMSUSPEND | TMSUCCESS | TMFAIL))
+ return (XAER_INVAL);
+
+ if (__db_rmid_to_env(rmid, &env, 0) != 0)
+ return (XAER_PROTO);
+
+ if (__db_xid_to_txn(env, xid, &off) != 0)
+ return (XAER_NOTA);
+
+ txn = env->xa_txn;
+ if (off != txn->off)
+ return (XAER_PROTO);
+
+ td = (TXN_DETAIL *)((u_int8_t *)env->tx_info->region + off);
+ if (td->xa_status == TXN_XA_DEADLOCKED)
+ return (XA_RBDEADLOCK);
+
+ if (td->status == TXN_ABORTED)
+ return (XA_RBOTHER);
+
+ if (td->xa_status != TXN_XA_STARTED)
+ return (XAER_PROTO);
+
+ /* Update the shared memory last_lsn field */
+ td->last_lsn = txn->last_lsn;
+
+ /*
+ * If we ever support XA migration, we cannot keep SUSPEND/END
+ * status in the shared region; it would have to be process local.
+ */
+ if (LF_ISSET(TMSUSPEND))
+ td->xa_status = TXN_XA_SUSPENDED;
+ else
+ td->xa_status = TXN_XA_ENDED;
+
+ txn->txnid = TXN_INVALID;
+ return (XA_OK);
+}
+
+/*
+ * __db_xa_prepare --
+ * Sync the log to disk so we can guarantee recoverability.
+ */
+static int
+__db_xa_prepare(xid, rmid, flags)
+ XID *xid;
+ int rmid;
+ long flags;
+{
+ DB_ENV *env;
+ TXN_DETAIL *td;
+ size_t off;
+
+ if (LF_ISSET(TMASYNC))
+ return (XAER_ASYNC);
+ if (flags != TMNOFLAGS)
+ return (XAER_INVAL);
+
+ /*
+ * We need to know if we've ever called prepare on this.
+ * As part of the prepare, we set the xa_status field to
+ * reflect that fact that prepare has been called, and if
+ * it's ever called again, it's an error.
+ */
+ if (__db_rmid_to_env(rmid, &env, 1) != 0)
+ return (XAER_PROTO);
+
+ if (__db_xid_to_txn(env, xid, &off) != 0)
+ return (XAER_NOTA);
+
+ td = (TXN_DETAIL *)((u_int8_t *)env->tx_info->region + off);
+
+ if (td->xa_status == TXN_XA_DEADLOCKED)
+ return (XA_RBDEADLOCK);
+
+ if (td->xa_status != TXN_XA_ENDED && td->xa_status != TXN_XA_SUSPENDED)
+ return (XAER_PROTO);
+
+ /* Now, fill in the global transaction structure. */
+ __xa_txn_init(env, td, off);
+
+ if (txn_prepare(env->xa_txn) != 0)
+ return (XAER_RMERR);
+
+ td->xa_status = TXN_XA_PREPARED;
+
+ /* No fatal value that would require an XAER_RMFAIL. */
+ __xa_txn_end(env);
+ return (XA_OK);
+}
+
+/*
+ * __db_xa_commit --
+ * Commit the transaction
+ */
+static int
+__db_xa_commit(xid, rmid, flags)
+ XID *xid;
+ int rmid;
+ long flags;
+{
+ DB_ENV *env;
+ TXN_DETAIL *td;
+ size_t off;
+
+ if (LF_ISSET(TMASYNC))
+ return (XAER_ASYNC);
+#undef OK_FLAGS
+#define OK_FLAGS (TMNOFLAGS | TMNOWAIT | TMONEPHASE)
+ if (LF_ISSET(~OK_FLAGS))
+ return (XAER_INVAL);
+
+ /*
+ * We need to know if we've ever called prepare on this.
+ * We can verify this by examining the xa_status field.
+ */
+ if (__db_rmid_to_env(rmid, &env, 1) != 0)
+ return (XAER_PROTO);
+
+ if (__db_xid_to_txn(env, xid, &off) != 0)
+ return (XAER_NOTA);
+
+ td = (TXN_DETAIL *)((u_int8_t *)env->tx_info->region + off);
+
+ if (td->xa_status == TXN_XA_DEADLOCKED)
+ return (XA_RBDEADLOCK);
+
+ if (td->xa_status == TXN_XA_ABORTED)
+ return (XA_RBOTHER);
+
+ if (LF_ISSET(TMONEPHASE) &&
+ td->xa_status != TXN_XA_ENDED && td->xa_status != TXN_XA_SUSPENDED)
+ return (XAER_PROTO);
+
+ if (!LF_ISSET(TMONEPHASE) && td->xa_status != TXN_XA_PREPARED)
+ return (XAER_PROTO);
+
+ /* Now, fill in the global transaction structure. */
+ __xa_txn_init(env, td, off);
+
+ if (txn_commit(env->xa_txn) != 0)
+ return (XAER_RMERR);
+
+ /* No fatal value that would require an XAER_RMFAIL. */
+ __xa_txn_end(env);
+ return (XA_OK);
+}
+
+/*
+ * __db_xa_recover --
+ * Returns a list of prepared and heuristically completed transactions.
+ *
+ * The return value is the number of xids placed into the xid array (less
+ * than or equal to the count parameter). The flags are going to indicate
+ * whether we are starting a scan or continuing one.
+ */
+static int
+__db_xa_recover(xids, count, rmid, flags)
+ XID *xids;
+ long count, flags;
+ int rmid;
+{
+ __txn_xa_regop_args *argp;
+ DBT data;
+ DB_ENV *env;
+ DB_LOG *log;
+ XID *xidp;
+ char *dbhome;
+ int err, ret;
+ u_int32_t rectype, txnid;
+
+ ret = 0;
+ xidp = xids;
+
+
+ /*
+ * If we are starting a scan, then we need to open the environment
+ * and run recovery. This recovery puts us in a state where we can
+ * either commit or abort any transactions that were prepared but not
+ * yet committed. Once we've done that, we need to figure out where
+ * to begin checking for such transactions. If we are not starting
+ * a scan, then the environment had better have already been recovered
+ * and we'll start from * wherever the log cursor is. Since XA apps
+ * cannot be threaded, we don't have to worry about someone else
+ * having moved it.
+ */
+ if (LF_ISSET(TMSTARTRSCAN)) {
+ /* If the environment is open, we have a problem. */
+ if (__db_rmid_to_env(rmid, &env, 0) == XA_OK)
+ return (XAER_PROTO);
+
+ if ((ret = __os_calloc(1, sizeof(DB_ENV), &env)) != 0)
+ return (XAER_RMERR);
+
+ if (__db_rmid_to_name(rmid, &dbhome) != 0)
+ goto err1;
+
+#undef XA_FLAGS
+#define XA_FLAGS DB_RECOVER | \
+ DB_CREATE | DB_INIT_LOCK | DB_INIT_LOG | DB_INIT_MPOOL | DB_INIT_TXN
+ if ((ret = db_appinit(dbhome, NULL, env, XA_FLAGS)) != 0)
+ goto err1;
+
+ if (__db_map_rmid(rmid, env) != 0)
+ goto err2;
+
+ /* Now figure out from where to begin scan. */
+ log = env->lg_info;
+ if ((err = __log_findckp(log, &log->xa_first)) == DB_NOTFOUND) {
+ /*
+ * If there were no log files, then we have no
+ * transactions to return, so we simply return 0.
+ */
+ return (0);
+ }
+ if ((err = __db_txnlist_init(&log->xa_info)) != 0)
+ goto err3;
+ } else {
+ /* We had better already know about this rmid. */
+ if (__db_rmid_to_env(rmid, &env, 0) != 0)
+ return (XAER_PROTO);
+ /*
+ * If we are not starting a scan, the log cursor had
+ * better be set.
+ */
+ log = env->lg_info;
+ if (IS_ZERO_LSN(log->xa_lsn))
+ return (XAER_PROTO);
+ }
+
+ /*
+ * At this point log->xa_first contains the point in the log
+ * to which we need to roll back. If we are starting a scan,
+ * we'll start at the last record; if we're continuing a scan,
+ * we'll have to start at log->xa_lsn.
+ */
+
+ memset(&data, 0, sizeof(data));
+ for (err = log_get(log, &log->xa_lsn, &data,
+ LF_ISSET(TMSTARTRSCAN) ? DB_LAST : DB_SET);
+ err == 0 && log_compare(&log->xa_lsn, &log->xa_first) > 0;
+ err = log_get(log, &log->xa_lsn, &data, DB_PREV)) {
+ memcpy(&rectype, data.data, sizeof(rectype));
+
+ /*
+ * The only record type we care about is an DB_txn_xa_regop.
+ * If it's a commit, we have to add it to a txnlist. If it's
+ * a prepare, and we don't have a commit, then we return it.
+ * We are redoing some of what's in the xa_regop_recovery
+ * code, but we have to do it here so we can get at the xid
+ * in the record.
+ */
+ if (rectype != DB_txn_xa_regop && rectype != DB_txn_regop)
+ continue;
+
+ memcpy(&txnid, (u_int8_t *)data.data + sizeof(rectype),
+ sizeof(txnid));
+ err = __db_txnlist_find(log->xa_info, txnid);
+ switch (rectype) {
+ case DB_txn_regop:
+ if (err == DB_NOTFOUND)
+ __db_txnlist_add(log->xa_info, txnid);
+ err = 0;
+ break;
+ case DB_txn_xa_regop:
+ /*
+ * This transaction is commited, so we needn't read
+ * the record and do anything.
+ */
+ if (err == 0)
+ break;
+ if ((err =
+ __txn_xa_regop_read(data.data, &argp)) != 0) {
+ ret = XAER_RMERR;
+ goto out;
+ }
+
+ xidp->formatID = argp->formatID;
+ xidp->gtrid_length = argp->gtrid;
+ xidp->bqual_length = argp->bqual;
+ memcpy(xidp->data, argp->xid.data, argp->xid.size);
+ ret++;
+ xidp++;
+ __os_free(argp, sizeof(*argp));
+ if (ret == count)
+ goto done;
+ break;
+ }
+ }
+
+ if (err != 0 && err != DB_NOTFOUND)
+ goto out;
+
+done: if (LF_ISSET(TMENDRSCAN)) {
+ ZERO_LSN(log->xa_lsn);
+ ZERO_LSN(log->xa_first);
+
+out: __db_txnlist_end(log->xa_info);
+ log->xa_info = NULL;
+ }
+ return (ret);
+
+err3: (void)__db_unmap_rmid(rmid);
+err2: (void)db_appexit(env);
+err1: __os_free(env, sizeof(DB_ENV));
+ return (XAER_RMERR);
+}
+
+/*
+ * __db_xa_rollback
+ * Abort an XA transaction.
+ */
+static int
+__db_xa_rollback(xid, rmid, flags)
+ XID *xid;
+ int rmid;
+ long flags;
+{
+ DB_ENV *env;
+ TXN_DETAIL *td;
+ size_t off;
+
+ if (LF_ISSET(TMASYNC))
+ return (XAER_ASYNC);
+ if (flags != TMNOFLAGS)
+ return (XAER_INVAL);
+
+ if (__db_rmid_to_env(rmid, &env, 1) != 0)
+ return (XAER_PROTO);
+
+ if (__db_xid_to_txn(env, xid, &off) != 0)
+ return (XAER_NOTA);
+
+ td = (TXN_DETAIL *)((u_int8_t *)env->tx_info->region + off);
+
+ if (td->xa_status == TXN_XA_DEADLOCKED)
+ return (XA_RBDEADLOCK);
+
+ if (td->xa_status == TXN_XA_ABORTED)
+ return (XA_RBOTHER);
+
+ if (LF_ISSET(TMONEPHASE) &&
+ td->xa_status != TXN_XA_ENDED && td->xa_status != TXN_XA_SUSPENDED)
+ return (XAER_PROTO);
+
+ /* Now, fill in the global transaction structure. */
+ __xa_txn_init(env, td, off);
+ if (txn_abort(env->xa_txn) != 0)
+ return (XAER_RMERR);
+
+ /* No fatal value that would require an XAER_RMFAIL. */
+ __xa_txn_end(env);
+ return (XA_OK);
+}
+
+/*
+ * __db_xa_forget --
+ * Forget about an XID for a transaction that was heuristically
+ * completed. Since we do not heuristically complete anything, I
+ * don't think we have to do anything here, but we should make sure
+ * that we reclaim the slots in the txnid table.
+ */
+static int
+__db_xa_forget(xid, rmid, flags)
+ XID *xid;
+ int rmid;
+ long flags;
+{
+ DB_ENV *env;
+ size_t off;
+
+ if (LF_ISSET(TMASYNC))
+ return (XAER_ASYNC);
+ if (flags != TMNOFLAGS)
+ return (XAER_INVAL);
+
+ if (__db_rmid_to_env(rmid, &env, 1) != 0)
+ return (XAER_PROTO);
+
+ /*
+ * If mapping is gone, then we're done.
+ */
+ if (__db_xid_to_txn(env, xid, &off) != 0)
+ return (XA_OK);
+
+ __db_unmap_xid(env, xid, off);
+
+ /* No fatal value that would require an XAER_RMFAIL. */
+ return (XA_OK);
+}
+
+/*
+ * __db_xa_complete --
+ * Used to wait for asynchronous operations to complete. Since we're
+ * not doing asynch, this is an invalid operation.
+ */
+static int
+__db_xa_complete(handle, retval, rmid, flags)
+ int *handle, *retval, rmid;
+ long flags;
+{
+ COMPQUIET(handle, NULL);
+ COMPQUIET(retval, NULL);
+ COMPQUIET(rmid, 0);
+ COMPQUIET(flags, 0);
+
+ return (XAER_INVAL);
+}
+
+/*
+ * __xa_txn_init --
+ * Fill in the fields of the local transaction structure given
+ * the detail transaction structure.
+ */
+static void
+__xa_txn_init(env, td, off)
+ DB_ENV *env;
+ TXN_DETAIL *td;
+ size_t off;
+{
+ DB_TXN *txn;
+
+ txn = env->xa_txn;
+ txn->mgrp = env->tx_info;
+ txn->parent = NULL;
+ txn->last_lsn = td->last_lsn;
+ txn->txnid = td->txnid;
+ txn->off = off;
+ txn->flags = 0;
+}
+
+/*
+ * __xa_txn_end --
+ * Invalidate a transaction structure that was generated by xa_txn_init.
+ */
+static void
+__xa_txn_end(env)
+ DB_ENV *env;
+{
+ DB_TXN *txn;
+
+ txn = env->xa_txn;
+ if (txn != NULL)
+ txn->txnid = TXN_INVALID;
+}
+
diff --git a/usr/src/cmd/sendmail/db/xa/xa_db.c b/usr/src/cmd/sendmail/db/xa/xa_db.c
new file mode 100644
index 0000000000..59b56e78a1
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/xa/xa_db.c
@@ -0,0 +1,308 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1998
+ * Sleepycat Software. All rights reserved.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)xa_db.c 10.6 (Sleepycat) 12/19/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#endif
+
+#include "db_int.h"
+#include "db_page.h"
+#include "xa.h"
+#include "xa_ext.h"
+#include "db_am.h"
+#include "db_ext.h"
+#include "common_ext.h"
+
+static int __xa_c_close __P((DBC *));
+static int __xa_c_del __P((DBC *, u_int32_t));
+static int __xa_c_get __P((DBC *, DBT *, DBT *, u_int32_t));
+static int __xa_c_put __P((DBC *, DBT *, DBT *, u_int32_t));
+static int __xa_close __P((DB *, u_int32_t));
+static int __xa_cursor __P((DB *, DB_TXN *, DBC **, u_int32_t));
+static int __xa_del __P((DB *, DB_TXN *, DBT *, u_int32_t));
+static int __xa_fd __P((DB *, int *));
+static int __xa_get __P((DB *, DB_TXN *, DBT *, DBT *, u_int32_t));
+static int __xa_put __P((DB *, DB_TXN *, DBT *, DBT *, u_int32_t));
+static int __xa_stat __P((DB *, void *, void *(*)(size_t), u_int32_t));
+static int __xa_sync __P((DB *, u_int32_t));
+
+int
+db_xa_open(fname, type, flags, mode, dbinfo, dbpp)
+ const char *fname;
+ DBTYPE type;
+ u_int32_t flags;
+ int mode;
+ DB_INFO *dbinfo;
+ DB **dbpp;
+{
+ DB *dbp, *real_dbp;
+ DB_ENV *dbenv;
+ struct __rmname *rp;
+ int ret;
+
+ /*
+ * First try to open up the underlying DB.
+ *
+ * !!!
+ * The dbenv argument is taken from the global list of environments.
+ * When the transaction manager called xa_start() (__db_xa_start()),
+ * the "current" DB environment was moved to the start of the list.
+ * However, if we were called in a tpsvrinit function (which is
+ * entirely plausible), then it's possible that xa_open was called
+ * (which simply recorded the name of the environment to open) and
+ * this is the next call into DB. In that case, we still have to
+ * open the environment.
+ *
+ * The way that we know that xa_open and nothing else was called
+ * is because the nameq is not NULL.
+ */
+ if ((rp = TAILQ_FIRST(&DB_GLOBAL(db_nameq))) != NULL &&
+ (ret = __db_rmid_to_env(rp->rmid, &dbenv, 1)) != 0)
+ return (ret);
+
+ dbenv = TAILQ_FIRST(&DB_GLOBAL(db_envq));
+ if ((ret = db_open(fname,
+ type, flags, mode, dbenv, dbinfo, &real_dbp)) != 0)
+ return (ret);
+
+ /*
+ * Allocate our own DB handle, and copy the exported fields and
+ * function pointers into it. The internal pointer references
+ * the real underlying DB handle.
+ */
+ if ((ret = __os_calloc(1, sizeof(DB), &dbp)) != 0) {
+ (void)real_dbp->close(real_dbp, 0);
+ return (ret);
+ }
+ dbp->type = real_dbp->type;
+ dbp->byteswapped = real_dbp->byteswapped;
+ dbp->dbenv = dbenv;
+ dbp->internal = real_dbp;
+ TAILQ_INIT(&dbp->active_queue);
+ TAILQ_INIT(&dbp->free_queue);
+ dbp->close = __xa_close;
+ dbp->cursor = __xa_cursor;
+ dbp->del = __xa_del;
+ dbp->fd = __xa_fd;
+ dbp->get = __xa_get;
+ dbp->join = real_dbp->join;
+ dbp->put = __xa_put;
+ dbp->stat = __xa_stat;
+ dbp->sync = __xa_sync;
+
+ *dbpp = dbp;
+ return (0);
+}
+
+static int
+__xa_close(dbp, flags)
+ DB *dbp;
+ u_int32_t flags;
+{
+ DB *real_dbp;
+ DBC *dbc;
+ int ret;
+
+ /* Close any associated cursors. */
+ while ((dbc = TAILQ_FIRST(&dbp->active_queue)) != NULL)
+ (void)dbc->c_close(dbc);
+
+ /* Close the DB handle. */
+ real_dbp = (DB *)dbp->internal;
+ ret = real_dbp->close(real_dbp, flags);
+
+ __os_free(dbp, sizeof(DB));
+ return (ret);
+}
+
+static int
+__xa_cursor(dbp, txn, dbcp, flags)
+ DB *dbp;
+ DB_TXN *txn;
+ DBC **dbcp;
+ u_int32_t flags;
+{
+ DB *real_dbp;
+ DBC *real_dbc, *dbc;
+ int ret;
+
+ real_dbp = (DB *)dbp->internal;
+ txn = dbp->dbenv->xa_txn;
+
+ if ((ret = real_dbp->cursor(real_dbp, txn, &real_dbc, flags)) != 0)
+ return (ret);
+
+ /*
+ * Allocate our own DBC handle, and copy the exported fields and
+ * function pointers into it. The internal pointer references
+ * the real underlying DBC handle.
+ */
+ if ((ret = __os_calloc(1, sizeof(DBC), &dbc)) != 0) {
+ (void)real_dbc->c_close(real_dbc);
+ return (ret);
+ }
+ dbc->dbp = dbp;
+ dbc->c_close = __xa_c_close;
+ dbc->c_del = __xa_c_del;
+ dbc->c_get = __xa_c_get;
+ dbc->c_put = __xa_c_put;
+ dbc->internal = real_dbc;
+ TAILQ_INSERT_TAIL(&dbp->active_queue, dbc, links);
+
+ *dbcp = dbc;
+ return (0);
+}
+
+static int
+__xa_fd(dbp, fdp)
+ DB *dbp;
+ int *fdp;
+{
+ DB *real_dbp;
+
+ COMPQUIET(fdp, NULL);
+
+ real_dbp = (DB *)dbp->internal;
+ return (__db_eopnotsup(real_dbp->dbenv));
+}
+
+static int
+__xa_del(dbp, txn, key, flags)
+ DB *dbp;
+ DB_TXN *txn;
+ DBT *key;
+ u_int32_t flags;
+{
+ DB *real_dbp;
+
+ real_dbp = (DB *)dbp->internal;
+ txn = dbp->dbenv->xa_txn;
+
+ return (real_dbp->del(real_dbp, txn, key, flags));
+}
+
+static int
+__xa_get(dbp, txn, key, data, flags)
+ DB *dbp;
+ DB_TXN *txn;
+ DBT *key;
+ DBT *data;
+ u_int32_t flags;
+{
+ DB *real_dbp;
+
+ real_dbp = (DB *)dbp->internal;
+ txn = dbp->dbenv->xa_txn;
+
+ return (real_dbp->get(real_dbp, txn, key, data, flags));
+}
+
+static int
+__xa_put(dbp, txn, key, data, flags)
+ DB *dbp;
+ DB_TXN *txn;
+ DBT *key;
+ DBT *data;
+ u_int32_t flags;
+{
+ DB *real_dbp;
+
+ real_dbp = (DB *)dbp->internal;
+ txn = dbp->dbenv->xa_txn;
+
+ return (real_dbp->put(real_dbp, txn, key, data, flags));
+}
+
+static int
+__xa_stat(dbp, spp, db_malloc, flags)
+ DB *dbp;
+ void *spp;
+ void *(*db_malloc) __P((size_t));
+ u_int32_t flags;
+{
+ DB *real_dbp;
+
+ real_dbp = (DB *)dbp->internal;
+ return (real_dbp->stat(real_dbp, spp, db_malloc, flags));
+}
+
+static int
+__xa_sync(dbp, flags)
+ DB *dbp;
+ u_int32_t flags;
+{
+ DB *real_dbp;
+
+ real_dbp = (DB *)dbp->internal;
+ return (real_dbp->sync(real_dbp, flags));
+}
+
+static int
+__xa_c_close(dbc)
+ DBC *dbc;
+{
+ DBC *real_dbc;
+ int ret;
+
+ real_dbc = (DBC *)dbc->internal;
+
+ ret = real_dbc->c_close(real_dbc);
+
+ TAILQ_REMOVE(&dbc->dbp->active_queue, dbc, links);
+ __os_free(dbc, sizeof(DBC));
+
+ return (ret);
+}
+
+static int
+__xa_c_del(dbc, flags)
+ DBC *dbc;
+ u_int32_t flags;
+{
+ DBC *real_dbc;
+
+ real_dbc = (DBC *)dbc->internal;
+ return (real_dbc->c_del(real_dbc, flags));
+}
+
+static int
+__xa_c_get(dbc, key, data, flags)
+ DBC *dbc;
+ DBT *key;
+ DBT *data;
+ u_int32_t flags;
+{
+ DBC *real_dbc;
+
+ real_dbc = (DBC *)dbc->internal;
+ return (real_dbc->c_get(real_dbc, key, data, flags));
+}
+
+static int
+__xa_c_put(dbc, key, data, flags)
+ DBC *dbc;
+ DBT *key;
+ DBT *data;
+ u_int32_t flags;
+{
+ DBC *real_dbc;
+
+ real_dbc = (DBC *)dbc->internal;
+ return (real_dbc->c_put(real_dbc, key, data, flags));
+}
diff --git a/usr/src/cmd/sendmail/db/xa/xa_map.c b/usr/src/cmd/sendmail/db/xa/xa_map.c
new file mode 100644
index 0000000000..5186fed5f6
--- /dev/null
+++ b/usr/src/cmd/sendmail/db/xa/xa_map.c
@@ -0,0 +1,307 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1996, 1997, 1998
+ * Sleepycat Software. All rights reserved.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)xa_map.c 10.4 (Sleepycat) 10/20/98";
+#endif /* not lint */
+
+#ifndef NO_SYSTEM_INCLUDES
+#include <sys/types.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#endif
+
+#include "db_int.h"
+#include "shqueue.h"
+#include "txn.h"
+
+/*
+ * This file contains all the mapping information that we need to support
+ * the DB/XA interface.
+ */
+
+/*
+ * __db_rmid_to_env
+ * Return the environment associated with a given XA rmid.
+ *
+ * PUBLIC: int __db_rmid_to_env __P((int rmid, DB_ENV **envp, int open_ok));
+ */
+int
+__db_rmid_to_env(rmid, envp, open_ok)
+ int rmid;
+ DB_ENV **envp;
+ int open_ok;
+{
+ DB_ENV *env;
+ char *dbhome;
+
+ env = TAILQ_FIRST(&DB_GLOBAL(db_envq));
+ if (env != NULL && env->xa_rmid == rmid) {
+ *envp = env;
+ return (0);
+ }
+
+ /*
+ * When we map an rmid, move that environment to be the first one in
+ * the list of environments, so we pass the correct environment from
+ * the upcoming db_xa_open() call into db_open().
+ */
+ for (; env != NULL; env = TAILQ_NEXT(env, links))
+ if (env->xa_rmid == rmid) {
+ TAILQ_REMOVE(&DB_GLOBAL(db_envq), env, links);
+ TAILQ_INSERT_HEAD(&DB_GLOBAL(db_envq), env, links);
+ *envp = env;
+ return (0);
+ }
+
+ /*
+ * We have not found the rmid on the environment list. If we
+ * are allowed to do an open, search for the rmid on the name
+ * list and, if we find it, then open it.
+ */
+ if (!open_ok)
+ return (1);
+
+ if (__db_rmid_to_name(rmid, &dbhome) != 0)
+ return (1);
+#undef XA_FLAGS
+#define XA_FLAGS \
+ DB_CREATE | DB_INIT_LOCK | DB_INIT_LOG | DB_INIT_MPOOL | DB_INIT_TXN
+
+ if (__os_calloc(1, sizeof(DB_ENV), &env) != 0)
+ return (1);
+
+ if (db_appinit(dbhome, NULL, env, XA_FLAGS) != 0)
+ goto err;
+
+ if (__db_map_rmid(rmid, env) != 0)
+ goto err1;
+
+ __db_unmap_rmid_name(rmid);
+
+ *envp = env;
+ return (0);
+
+err1: (void)db_appexit(env);
+err: __os_free(env, sizeof(DB_ENV));
+ return (1);
+}
+
+/*
+ * __db_xid_to_txn
+ * Return the txn that corresponds to this XID.
+ *
+ * PUBLIC: int __db_xid_to_txn __P((DB_ENV *, XID *, size_t *));
+ */
+int
+__db_xid_to_txn(dbenv, xid, offp)
+ DB_ENV *dbenv;
+ XID *xid;
+ size_t *offp;
+{
+ DB_TXNREGION *tmr;
+ struct __txn_detail *td;
+
+ /*
+ * Search the internal active transaction table to find the
+ * matching xid. If this is a performance hit, then we
+ * can create a hash table, but I doubt it's worth it.
+ */
+ tmr = dbenv->tx_info->region;
+
+ LOCK_TXNREGION(dbenv->tx_info);
+ for (td = SH_TAILQ_FIRST(&tmr->active_txn, __txn_detail);
+ td != NULL;
+ td = SH_TAILQ_NEXT(td, links, __txn_detail))
+ if (memcmp(xid->data, td->xid, XIDDATASIZE) == 0)
+ break;
+ UNLOCK_TXNREGION(dbenv->tx_info);
+
+ if (td == NULL)
+ return (EINVAL);
+
+ *offp = (u_int8_t *)td - (u_int8_t *)tmr;
+ return (0);
+}
+
+/*
+ * __db_map_rmid
+ * Create a mapping between the specified rmid and environment.
+ *
+ * PUBLIC: int __db_map_rmid __P((int, DB_ENV *));
+ */
+int
+__db_map_rmid(rmid, env)
+ int rmid;
+ DB_ENV *env;
+{
+ if (__os_calloc(1, sizeof(DB_TXN), &env->xa_txn) != 0)
+ return (XAER_RMERR);
+ env->xa_txn->txnid = TXN_INVALID;
+ env->xa_rmid = rmid;
+ TAILQ_INSERT_HEAD(&DB_GLOBAL(db_envq), env, links);
+ return (XA_OK);
+}
+
+/*
+ * __db_unmap_rmid
+ * Destroy the mapping for the given rmid.
+ *
+ * PUBLIC: int __db_unmap_rmid __P((int));
+ */
+int
+__db_unmap_rmid(rmid)
+ int rmid;
+{
+ DB_ENV *e;
+
+ for (e = TAILQ_FIRST(&DB_GLOBAL(db_envq));
+ e->xa_rmid != rmid;
+ e = TAILQ_NEXT(e, links));
+
+ if (e == NULL)
+ return (EINVAL);
+
+ TAILQ_REMOVE(&DB_GLOBAL(db_envq), e, links);
+ if (e->xa_txn != NULL)
+ __os_free(e->xa_txn, sizeof(DB_TXN));
+ return (0);
+}
+
+/*
+ * __db_map_xid
+ * Create a mapping between this XID and the transaction at
+ * "off" in the shared region.
+ *
+ * PUBLIC: int __db_map_xid __P((DB_ENV *, XID *, size_t));
+ */
+int
+__db_map_xid(env, xid, off)
+ DB_ENV *env;
+ XID *xid;
+ size_t off;
+{
+ DB_TXNMGR *tm;
+ TXN_DETAIL *td;
+
+ tm = env->tx_info;
+ td = (TXN_DETAIL *)((u_int8_t *)tm->region + off);
+
+ LOCK_TXNREGION(tm);
+ memcpy(td->xid, xid->data, XIDDATASIZE);
+ UNLOCK_TXNREGION(tm);
+
+ return (0);
+}
+
+/*
+ * __db_unmap_xid
+ * Destroy the mapping for the specified XID.
+ *
+ * PUBLIC: void __db_unmap_xid __P((DB_ENV *, XID *, size_t));
+ */
+
+void
+__db_unmap_xid(env, xid, off)
+ DB_ENV *env;
+ XID *xid;
+ size_t off;
+{
+ TXN_DETAIL *td;
+
+ COMPQUIET(xid, NULL);
+
+ td = (TXN_DETAIL *)((u_int8_t *)env->tx_info->region + off);
+ memset(td->xid, 0, sizeof(td->xid));
+}
+
+/*
+ * __db_map_rmid_name --
+ * Create a mapping from an rmid to a name (the xa_info argument).
+ * We use this during create and then at some later point when we are
+ * trying to map an rmid, we might indicate that it's OK to do an open
+ * in which case, we'll get the xa_info parameter from here and then
+ * free it up.
+ *
+ * PUBLIC: int __db_map_rmid_name __P((int, char *));
+ */
+
+int
+__db_map_rmid_name(rmid, dbhome)
+ int rmid;
+ char *dbhome;
+{
+ struct __rmname *entry;
+ int ret;
+
+ if ((ret = __os_malloc(sizeof(struct __rmname), NULL, &entry)) != 0)
+ return (ret);
+
+ if ((ret = __os_strdup(dbhome, &entry->dbhome)) != 0) {
+ __os_free(entry, sizeof(struct __rmname));
+ return (ret);
+ }
+
+ entry->rmid = rmid;
+
+ TAILQ_INSERT_HEAD(&DB_GLOBAL(db_nameq), entry, links);
+ return (0);
+}
+
+/*
+ * __db_rmid_to_name --
+ * Given an rmid, return the name of the home directory for that
+ * rmid.
+ *
+ * PUBLIC: int __db_rmid_to_name __P((int, char **));
+ */
+int
+__db_rmid_to_name(rmid, dbhomep)
+ int rmid;
+ char **dbhomep;
+{
+ struct __rmname *np;
+
+ for (np = TAILQ_FIRST(&DB_GLOBAL(db_nameq)); np != NULL;
+ np = TAILQ_NEXT(np, links)) {
+ if (np->rmid == rmid) {
+ *dbhomep = np->dbhome;
+ return (0);
+ }
+ }
+ return (1);
+}
+
+/*
+ * __db_unmap_rmid_name --
+ * Given an rmid, remove its entry from the name list.
+ *
+ * PUBLIC: void __db_unmap_rmid_name __P((int));
+ */
+void
+__db_unmap_rmid_name(rmid)
+ int rmid;
+{
+ struct __rmname *np, *next;
+
+ for (np = TAILQ_FIRST(&DB_GLOBAL(db_nameq)); np != NULL; np = next) {
+ next = TAILQ_NEXT(np, links);
+ if (np->rmid == rmid) {
+ TAILQ_REMOVE(&DB_GLOBAL(db_nameq), np, links);
+ __os_freestr(np->dbhome);
+ __os_free(np, sizeof(struct __rmname));
+ return;
+ }
+ }
+ return;
+}
diff --git a/usr/src/cmd/sendmail/include/libmilter/mfapi.h b/usr/src/cmd/sendmail/include/libmilter/mfapi.h
new file mode 100644
index 0000000000..536873b0d1
--- /dev/null
+++ b/usr/src/cmd/sendmail/include/libmilter/mfapi.h
@@ -0,0 +1,497 @@
+/*
+ * Copyright (c) 1999-2004 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ *
+ * $Id: mfapi.h,v 8.60 2004/08/20 21:24:14 ca Exp $
+ */
+
+/*
+ * MFAPI.H -- Global definitions for mail filter library and mail filters.
+ */
+
+#ifndef _LIBMILTER_MFAPI_H
+#define _LIBMILTER_MFAPI_H
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef SMFI_VERSION
+#define SMFI_VERSION 2 /* version number */
+#endif /* ! SMFI_VERSION */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <libmilter/mfdef.h>
+
+#define LIBMILTER_API extern
+
+#ifndef _SOCK_ADDR
+#define _SOCK_ADDR struct sockaddr
+#endif /* ! _SOCK_ADDR */
+
+/*
+ * libmilter functions return one of the following to indicate
+ * success/failure:
+ */
+
+#define MI_SUCCESS 0
+#define MI_FAILURE (-1)
+
+/* "forward" declarations */
+typedef struct smfi_str SMFICTX;
+typedef struct smfi_str *SMFICTX_PTR;
+
+typedef struct smfiDesc smfiDesc_str;
+typedef struct smfiDesc *smfiDesc_ptr;
+
+/*
+ * Type which callbacks should return to indicate message status.
+ * This may take on one of the SMFIS_* values listed below.
+ */
+
+typedef int sfsistat;
+
+#if defined(__linux__) && defined(__GNUC__) && defined(__cplusplus) && \
+ __GNUC_MINOR__ >= 8
+#define SM__P(X) __PMT(X)
+#else /* __linux__ && __GNUC__ && __cplusplus && _GNUC_MINOR__ >= 8 */
+#define SM__P(X) __P(X)
+#endif /* __linux__ && __GNUC__ && __cplusplus && _GNUC_MINOR__ >= 8 */
+
+/* Some platforms don't define __P -- do it for them here: */
+#ifndef __P
+#ifdef __STDC__
+#define __P(X) X
+#else /* __STDC__ */
+#define __P(X) ()
+#endif /* __STDC__ */
+#endif /* __P */
+
+#if SM_CONF_STDBOOL_H
+#include <stdbool.h>
+#else /* SM_CONF_STDBOOL_H */
+#ifndef __cplusplus
+#ifndef bool
+#ifndef __bool_true_false_are_defined
+typedef int bool;
+#define __bool_true_false_are_defined 1
+#endif /* ! __bool_true_false_are_defined */
+#endif /* bool */
+#endif /* ! __cplusplus */
+#endif /* SM_CONF_STDBOOL_H */
+
+/*
+ * structure describing one milter
+ */
+
+struct smfiDesc
+{
+ char *xxfi_name; /* filter name */
+ int xxfi_version; /* version code -- do not change */
+ unsigned long xxfi_flags; /* flags */
+
+ /* connection info filter */
+ sfsistat (*xxfi_connect) SM__P((SMFICTX *, char *,
+ _SOCK_ADDR *));
+
+ /* SMTP HELO command filter */
+ sfsistat (*xxfi_helo) SM__P((SMFICTX *, char *));
+
+ /* envelope sender filter */
+ sfsistat (*xxfi_envfrom) SM__P((SMFICTX *, char **));
+
+ /* envelope recipient filter */
+ sfsistat (*xxfi_envrcpt) SM__P((SMFICTX *, char **));
+
+ /* header filter */
+ sfsistat (*xxfi_header) SM__P((SMFICTX *, char *, char *));
+
+ /* end of header */
+ sfsistat (*xxfi_eoh) SM__P((SMFICTX *));
+
+ /* body block */
+ sfsistat (*xxfi_body) SM__P((SMFICTX *, unsigned char *,
+ size_t));
+
+ /* end of message */
+ sfsistat (*xxfi_eom) SM__P((SMFICTX *));
+
+ /* message aborted */
+ sfsistat (*xxfi_abort) SM__P((SMFICTX *));
+
+ /* connection cleanup */
+ sfsistat (*xxfi_close) SM__P((SMFICTX *));
+
+#if SMFI_VERSION > 2
+ /* any unrecognized or unimplemented command filter */
+ sfsistat (*xxfi_unknown) SM__P((SMFICTX *, char *));
+#endif /* SMFI_VERSION > 2 */
+
+#if SMFI_VERSION > 3
+ /* any unrecognized or unimplemented command filter */
+ sfsistat (*xxfi_data) SM__P((SMFICTX *));
+#endif /* SMFI_VERSION > 3 */
+};
+
+LIBMILTER_API int smfi_opensocket __P((bool));
+LIBMILTER_API int smfi_register __P((struct smfiDesc));
+LIBMILTER_API int smfi_main __P((void));
+LIBMILTER_API int smfi_setbacklog __P((int));
+LIBMILTER_API int smfi_setdbg __P((int));
+LIBMILTER_API int smfi_settimeout __P((int));
+LIBMILTER_API int smfi_setconn __P((char *));
+LIBMILTER_API int smfi_stop __P((void));
+#if _FFR_MAXDATASIZE
+LIBMILTER_API size_t smfi_setmaxdatasize __P((size_t));
+#endif /* _FFR_MAXDATASIZE */
+
+/*
+ * What the filter might do -- values to be ORed together for
+ * smfiDesc.xxfi_flags.
+ */
+
+#define SMFIF_NONE 0x00000000L /* no flags */
+#define SMFIF_ADDHDRS 0x00000001L /* filter may add headers */
+#define SMFIF_CHGBODY 0x00000002L /* filter may replace body */
+#define SMFIF_MODBODY SMFIF_CHGBODY /* backwards compatible */
+#define SMFIF_ADDRCPT 0x00000004L /* filter may add recipients */
+#define SMFIF_DELRCPT 0x00000008L /* filter may delete recipients */
+#define SMFIF_CHGHDRS 0x00000010L /* filter may change/delete headers */
+#define SMFIF_QUARANTINE 0x00000020L /* filter may quarantine envelope */
+
+/*
+ * Continue processing message/connection.
+ */
+
+#define SMFIS_CONTINUE 0
+
+/*
+ * Reject the message/connection.
+ * No further routines will be called for this message
+ * (or connection, if returned from a connection-oriented routine).
+ */
+
+#define SMFIS_REJECT 1
+
+/*
+ * Accept the message,
+ * but silently discard the message.
+ * No further routines will be called for this message.
+ * This is only meaningful from message-oriented routines.
+ */
+
+#define SMFIS_DISCARD 2
+
+/*
+ * Accept the message/connection.
+ * No further routines will be called for this message
+ * (or connection, if returned from a connection-oriented routine;
+ * in this case, it causes all messages on this connection
+ * to be accepted without filtering).
+ */
+
+#define SMFIS_ACCEPT 3
+
+/*
+ * Return a temporary failure, i.e.,
+ * the corresponding SMTP command will return a 4xx status code.
+ * In some cases this may prevent further routines from
+ * being called on this message or connection,
+ * although in other cases (e.g., when processing an envelope
+ * recipient) processing of the message will continue.
+ */
+
+#define SMFIS_TEMPFAIL 4
+
+#if 0
+/*
+ * Filter Routine Details
+ */
+
+/* connection info filter */
+extern sfsistat xxfi_connect __P((SMFICTX *, char *, _SOCK_ADDR *));
+
+/*
+ * xxfi_connect(ctx, hostname, hostaddr) Invoked on each connection
+ *
+ * char *hostname; Host domain name, as determined by a reverse lookup
+ * on the host address.
+ * _SOCK_ADDR *hostaddr; Host address, as determined by a getpeername
+ * call on the SMTP socket.
+ */
+
+/* SMTP HELO command filter */
+extern sfsistat xxfi_helo __P((SMFICTX *, char *));
+
+/*
+ * xxfi_helo(ctx, helohost) Invoked on SMTP HELO/EHLO command
+ *
+ * char *helohost; Value passed to HELO/EHLO command, which should be
+ * the domain name of the sending host (but is, in practice,
+ * anything the sending host wants to send).
+ */
+
+/* envelope sender filter */
+extern sfsistat xxfi_envfrom __P((SMFICTX *, char **));
+
+/*
+ * xxfi_envfrom(ctx, argv) Invoked on envelope from
+ *
+ * char **argv; Null-terminated SMTP command arguments;
+ * argv[0] is guaranteed to be the sender address.
+ * Later arguments are the ESMTP arguments.
+ */
+
+/* envelope recipient filter */
+extern sfsistat xxfi_envrcpt __P((SMFICTX *, char **));
+
+/*
+ * xxfi_envrcpt(ctx, argv) Invoked on each envelope recipient
+ *
+ * char **argv; Null-terminated SMTP command arguments;
+ * argv[0] is guaranteed to be the recipient address.
+ * Later arguments are the ESMTP arguments.
+ */
+
+/* unknown command filter */
+
+extern sfsistat *xxfi_unknown __P((SMFICTX *, char *));
+
+/*
+ * xxfi_unknown(ctx, arg) Invoked when SMTP command is not recognized or not
+ * implemented.
+ * char *arg; Null-terminated SMTP command
+ */
+
+/* header filter */
+extern sfsistat xxfi_header __P((SMFICTX *, char *, char *));
+
+/*
+ * xxfi_header(ctx, headerf, headerv) Invoked on each message header. The
+ * content of the header may have folded white space (that is, multiple
+ * lines with following white space) included.
+ *
+ * char *headerf; Header field name
+ * char *headerv; Header field value
+ */
+
+/* end of header */
+extern sfsistat xxfi_eoh __P((SMFICTX *));
+
+/*
+ * xxfi_eoh(ctx) Invoked at end of header
+ */
+
+/* body block */
+extern sfsistat xxfi_body __P((SMFICTX *, unsigned char *, size_t));
+
+/*
+ * xxfi_body(ctx, bodyp, bodylen) Invoked for each body chunk. There may
+ * be multiple body chunks passed to the filter. End-of-lines are
+ * represented as received from SMTP (normally Carriage-Return/Line-Feed).
+ *
+ * unsigned char *bodyp; Pointer to body data
+ * size_t bodylen; Length of body data
+ */
+
+/* end of message */
+extern sfsistat xxfi_eom __P((SMFICTX *));
+
+/*
+ * xxfi_eom(ctx) Invoked at end of message. This routine can perform
+ * special operations such as modifying the message header, body, or
+ * envelope.
+ */
+
+/* message aborted */
+extern sfsistat xxfi_abort __P((SMFICTX *));
+
+/*
+ * xxfi_abort(ctx) Invoked if message is aborted outside of the control of
+ * the filter, for example, if the SMTP sender issues an RSET command. If
+ * xxfi_abort is called, xxfi_eom will not be called and vice versa.
+ */
+
+/* connection cleanup */
+extern sfsistat xxfi_close __P((SMFICTX *));
+
+/*
+ * xxfi_close(ctx) Invoked at end of the connection. This is called on
+ * close even if the previous mail transaction was aborted.
+ */
+#endif /* 0 */
+
+/*
+ * Additional information is passed in to the vendor filter routines using
+ * symbols. Symbols correspond closely to sendmail macros. The symbols
+ * defined depend on the context. The value of a symbol is accessed using:
+ */
+
+/* Return the value of a symbol. */
+LIBMILTER_API char *smfi_getsymval __P((SMFICTX *, char *));
+
+/*
+ * Return the value of a symbol.
+ *
+ * SMFICTX *ctx; Opaque context structure
+ * char *symname; The name of the symbol to access.
+ */
+
+/*
+ * Vendor filter routines that want to pass additional information back to
+ * the MTA for use in SMTP replies may call smfi_setreply before returning.
+ */
+
+LIBMILTER_API int smfi_setreply __P((SMFICTX *, char *, char *, char *));
+
+/*
+ * Alternatively, smfi_setmlreply can be called if a multi-line SMTP reply
+ * is needed.
+ */
+
+LIBMILTER_API int smfi_setmlreply __P((SMFICTX *, const char *, const char *,
+ ...));
+
+/*
+ * Set the specific reply code to be used in response to the active
+ * command. If not specified, a generic reply code is used.
+ *
+ * SMFICTX *ctx; Opaque context structure
+ * char *rcode; The three-digit (RFC 821) SMTP reply code to be
+ * returned, e.g., ``551''.
+ * char *xcode; The extended (RFC 2034) reply code, e.g., ``5.7.6''.
+ * char *message; The text part of the SMTP reply.
+ */
+
+/*
+ * The xxfi_eom routine is called at the end of a message (essentially,
+ * after the final DATA dot). This routine can call some special routines
+ * to modify the envelope, header, or body of the message before the
+ * message is enqueued. These routines must not be called from any vendor
+ * routine other than xxfi_eom.
+ */
+
+LIBMILTER_API int smfi_addheader __P((SMFICTX *, char *, char *));
+
+/*
+ * Add a header to the message. It is not checked for standards
+ * compliance; the mail filter must ensure that no protocols are violated
+ * as a result of adding this header.
+ *
+ * SMFICTX *ctx; Opaque context structure
+ * char *headerf; Header field name
+ * char *headerv; Header field value
+ */
+
+LIBMILTER_API int smfi_chgheader __P((SMFICTX *, char *, int, char *));
+
+/*
+ * Change/delete a header in the message. It is not checked for standards
+ * compliance; the mail filter must ensure that no protocols are violated
+ * as a result of adding this header.
+ *
+ * SMFICTX *ctx; Opaque context structure
+ * char *headerf; Header field name
+ * int index; The Nth occurence of header field name
+ * char *headerv; New header field value (empty for delete header)
+ */
+
+LIBMILTER_API int smfi_insheader __P((SMFICTX *, int, char *, char *));
+
+/*
+ * Insert a header into the message. It is not checked for standards
+ * compliance; the mail filter must ensure that no protocols are violated
+ * as a result of adding this header.
+ *
+ * SMFICTX *ctx; Opaque context structure
+ * int idx; index into the header list where the insertion should happen
+ * char *headerh; Header field name
+ * char *headerv; Header field value
+ */
+
+LIBMILTER_API int smfi_addrcpt __P((SMFICTX *, char *));
+
+/*
+ * Add a recipient to the envelope
+ *
+ * SMFICTX *ctx; Opaque context structure
+ * char *rcpt; Recipient to be added
+ */
+
+LIBMILTER_API int smfi_delrcpt __P((SMFICTX *, char *));
+
+/*
+ * Send a "no-op" up to the MTA to tell it we're still alive, so long
+ * milter-side operations don't time out.
+ *
+ * SMFICTX *ctx; Opaque context structure
+ */
+
+LIBMILTER_API int smfi_progress __P((SMFICTX *));
+
+/*
+ * Delete a recipient from the envelope
+ *
+ * SMFICTX *ctx; Opaque context structure
+ * char *rcpt; Envelope recipient to be deleted. This should be in
+ * exactly the form passed to xxfi_envrcpt or the address may
+ * not be deleted.
+ */
+
+LIBMILTER_API int smfi_replacebody __P((SMFICTX *, unsigned char *, int));
+
+/*
+ * Replace the body of the message. This routine may be called multiple
+ * times if the body is longer than convenient to send in one call. End of
+ * line should be represented as Carriage-Return/Line Feed.
+ *
+ * char *bodyp; Pointer to block of body information to insert
+ * int bodylen; Length of data pointed at by bodyp
+ */
+
+/*
+ * If the message is aborted (for example, if the SMTP sender sends the
+ * envelope but then does a QUIT or RSET before the data is sent),
+ * xxfi_abort is called. This can be used to reset state.
+ */
+
+/*
+ * Quarantine an envelope
+ *
+ * SMFICTX *ctx; Opaque context structure
+ * char *reason: explanation
+ */
+
+LIBMILTER_API int smfi_quarantine __P((SMFICTX *ctx, char *reason));
+
+/*
+ * Connection-private data (specific to an SMTP connection) can be
+ * allocated using the smfi_setpriv routine; routines can access private
+ * data using smfi_getpriv.
+ */
+
+LIBMILTER_API int smfi_setpriv __P((SMFICTX *, void *));
+
+/*
+ * Set the private data pointer
+ *
+ * SMFICTX *ctx; Opaque context structure
+ * void *privatedata; Pointer to private data area
+ */
+
+LIBMILTER_API void *smfi_getpriv __P((SMFICTX *));
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* !_LIBMILTER_MFAPI_H */
diff --git a/usr/src/cmd/sendmail/include/libmilter/mfdef.h b/usr/src/cmd/sendmail/include/libmilter/mfdef.h
new file mode 100644
index 0000000000..2648f72286
--- /dev/null
+++ b/usr/src/cmd/sendmail/include/libmilter/mfdef.h
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 1999-2004 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ *
+ * $Id: mfdef.h,v 8.21 2004/07/07 21:41:31 ca Exp $
+ */
+
+/*
+ * mfdef.h -- Global definitions for mail filter and MTA.
+ */
+
+#ifndef _LIBMILTER_MFDEF_H
+#define _LIBMILTER_MFDEF_H
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Shared protocol constants */
+#define MILTER_LEN_BYTES 4 /* length of 32 bit integer in bytes */
+#define MILTER_OPTLEN (MILTER_LEN_BYTES * 3) /* length of options */
+#define MILTER_CHUNK_SIZE 65535 /* body chunk size */
+#define MILTER_MAX_DATA_SIZE 65535 /* default milter command data limit */
+
+/* These apply to SMFIF_* flags */
+#define SMFI_V1_ACTS 0x0000000FL /* The actions of V1 filter */
+#define SMFI_V2_ACTS 0x0000003FL /* The actions of V2 filter */
+#define SMFI_CURR_ACTS SMFI_V2_ACTS /* The current version */
+
+/* address families */
+#define SMFIA_UNKNOWN 'U' /* unknown */
+#define SMFIA_UNIX 'L' /* unix/local */
+#define SMFIA_INET '4' /* inet */
+#define SMFIA_INET6 '6' /* inet6 */
+
+/* commands: don't use anything smaller than ' ' */
+#define SMFIC_ABORT 'A' /* Abort */
+#define SMFIC_BODY 'B' /* Body chunk */
+#define SMFIC_CONNECT 'C' /* Connection information */
+#define SMFIC_MACRO 'D' /* Define macro */
+#define SMFIC_BODYEOB 'E' /* final body chunk (End) */
+#define SMFIC_HELO 'H' /* HELO/EHLO */
+#define SMFIC_HEADER 'L' /* Header */
+#define SMFIC_MAIL 'M' /* MAIL from */
+#define SMFIC_EOH 'N' /* EOH */
+#define SMFIC_OPTNEG 'O' /* Option negotiation */
+#define SMFIC_QUIT 'Q' /* QUIT */
+#define SMFIC_RCPT 'R' /* RCPT to */
+#if SMFI_VERSION > 3
+#define SMFIC_DATA 'T' /* DATA */
+#endif /* SMFI_VERSION > 3 */
+#if SMFI_VERSION > 2
+#define SMFIC_UNKNOWN 'U' /* Any unknown command */
+#endif /* SMFI_VERSION > 2 */
+
+/* actions (replies) */
+#define SMFIR_ADDRCPT '+' /* add recipient */
+#define SMFIR_DELRCPT '-' /* remove recipient */
+#define SMFIR_ACCEPT 'a' /* accept */
+#define SMFIR_REPLBODY 'b' /* replace body (chunk) */
+#define SMFIR_CONTINUE 'c' /* continue */
+#define SMFIR_DISCARD 'd' /* discard */
+#define SMFIR_CHGHEADER 'm' /* change header */
+#define SMFIR_PROGRESS 'p' /* progress */
+#define SMFIR_REJECT 'r' /* reject */
+#define SMFIR_TEMPFAIL 't' /* tempfail */
+#define SMFIR_SHUTDOWN '4' /* 421: shutdown (internal to MTA) */
+#define SMFIR_ADDHEADER 'h' /* add header */
+#define SMFIR_INSHEADER 'i' /* insert header */
+#define SMFIR_REPLYCODE 'y' /* reply code etc */
+#define SMFIR_QUARANTINE 'q' /* quarantine */
+
+/* What the MTA can send/filter wants in protocol */
+#define SMFIP_NOCONNECT 0x00000001L /* MTA should not send connect info */
+#define SMFIP_NOHELO 0x00000002L /* MTA should not send HELO info */
+#define SMFIP_NOMAIL 0x00000004L /* MTA should not send MAIL info */
+#define SMFIP_NORCPT 0x00000008L /* MTA should not send RCPT info */
+#define SMFIP_NOBODY 0x00000010L /* MTA should not send body */
+#define SMFIP_NOHDRS 0x00000020L /* MTA should not send headers */
+#define SMFIP_NOEOH 0x00000040L /* MTA should not send EOH */
+#if _FFR_MILTER_NOHDR_RESP
+#define SMFIP_NOHREPL 0x00000080L /* No reply for headers */
+#endif /* _FFR_MILTER_NOHDR_RESP */
+
+#define SMFI_V1_PROT 0x0000003FL /* The protocol of V1 filter */
+#define SMFI_V2_PROT 0x0000007FL /* The protocol of V2 filter */
+#if _FFR_MILTER_NOHDR_RESP
+#define SMFI_CURR_PROT 0x000000FFL /* The current version */
+#else /* _FFR_MILTER_NOHDR_RESP */
+#define SMFI_CURR_PROT SMFI_V2_PROT /* The current version */
+#endif /* _FFR_MILTER_NOHDR_RESP */
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* !_LIBMILTER_MFDEF_H */
diff --git a/usr/src/cmd/sendmail/include/libmilter/milter.h b/usr/src/cmd/sendmail/include/libmilter/milter.h
new file mode 100644
index 0000000000..05b5486c42
--- /dev/null
+++ b/usr/src/cmd/sendmail/include/libmilter/milter.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 1999-2003 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ *
+ * $Id: milter.h,v 8.39 2003/12/02 00:21:42 msk Exp $
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+** MILTER.H -- Global definitions for mail filter.
+*/
+
+#ifndef _LIBMILTER_MILTER_H
+# define _LIBMILTER_MILTER_H 1
+
+#include "sendmail.h"
+#include "libmilter/mfapi.h"
+
+/* socket and thread portability */
+# include <pthread.h>
+typedef pthread_t sthread_t;
+typedef int socket_t;
+
+# define MAX_MACROS_ENTRIES 5 /* max size of macro pointer array */
+
+/*
+** context for milter
+** implementation hint:
+** macros are stored in mac_buf[] as sequence of:
+** macro_name \0 macro_value
+** (just as read from the MTA)
+** mac_ptr is a list of pointers into mac_buf to the beginning of each
+** entry, i.e., macro_name, macro_value, ...
+*/
+
+struct smfi_str
+{
+ sthread_t ctx_id; /* thread id */
+ socket_t ctx_sd; /* socket descriptor */
+ int ctx_dbg; /* debug level */
+ time_t ctx_timeout; /* timeout */
+ int ctx_state; /* state */
+ smfiDesc_ptr ctx_smfi; /* filter description */
+ unsigned long ctx_pflags; /* protocol flags */
+ char **ctx_mac_ptr[MAX_MACROS_ENTRIES];
+ char *ctx_mac_buf[MAX_MACROS_ENTRIES];
+ char *ctx_reply; /* reply code */
+ void *ctx_privdata; /* private data */
+};
+
+#endif /* ! _LIBMILTER_MILTER_H */
diff --git a/usr/src/cmd/sendmail/include/libsmdb/smdb.h b/usr/src/cmd/sendmail/include/libsmdb/smdb.h
new file mode 100644
index 0000000000..8260ff1811
--- /dev/null
+++ b/usr/src/cmd/sendmail/include/libsmdb/smdb.h
@@ -0,0 +1,362 @@
+/*
+ * Copyright (c) 1999-2002 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ * $Id: smdb.h,v 8.40.2.1 2002/10/05 17:04:51 ca Exp $
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#ifndef _SMDB_H_
+# define _SMDB_H_
+
+# include <sys/types.h>
+# include <sys/stat.h>
+# include <sm/gen.h>
+# include <sm/errstring.h>
+
+# ifdef NDBM
+# include <ndbm.h>
+# endif /* NDBM */
+
+# ifdef NEWDB
+# include "sm/bdb.h"
+# endif /* NEWDB */
+
+/*
+** Some size constants
+*/
+
+#define SMDB_MAX_USER_NAME_LEN 1024
+
+/*
+** This file defines the abstraction for database lookups. It is pretty
+** much a copy of the db2 interface with the exception that every function
+** returns 0 on success and non-zero on failure. The non-zero return code
+** is meaningful.
+**
+** I'm going to put the function comments in this file since the interface
+** MUST be the same for all inheritors of this interface.
+*/
+
+typedef struct database_struct SMDB_DATABASE;
+typedef struct cursor_struct SMDB_CURSOR;
+typedef struct entry_struct SMDB_DBENT;
+
+/*
+** DB_CLOSE_FUNC -- close the database
+**
+** Parameters:
+** db -- The database to close.
+**
+** Returns:
+** 0 - Success, otherwise errno.
+**
+*/
+
+typedef int (*db_close_func) __P((SMDB_DATABASE *db));
+
+/*
+** DB_DEL_FUNC -- removes a key and data pair from the database
+**
+** Parameters:
+** db -- The database to close.
+** key -- The key to remove.
+** flags -- delete options. There are currently no defined
+** flags for delete.
+**
+** Returns:
+** 0 - Success, otherwise errno.
+**
+*/
+
+typedef int (*db_del_func) __P((SMDB_DATABASE *db,
+ SMDB_DBENT *key, unsigned int flags));
+
+/*
+** DB_FD_FUNC -- Returns a pointer to a file used for the database.
+**
+** Parameters:
+** db -- The database to close.
+** fd -- A pointer to store the returned fd in.
+**
+** Returns:
+** 0 - Success, otherwise errno.
+**
+*/
+
+typedef int (*db_fd_func) __P((SMDB_DATABASE *db, int* fd));
+
+/*
+** DB_GET_FUNC -- Gets the data associated with a key.
+**
+** Parameters:
+** db -- The database to close.
+** key -- The key to access.
+** data -- A place to store the returned data.
+** flags -- get options. There are currently no defined
+** flags for get.
+**
+** Returns:
+** 0 - Success, otherwise errno.
+**
+*/
+
+typedef int (*db_get_func) __P((SMDB_DATABASE *db,
+ SMDB_DBENT *key,
+ SMDB_DBENT *data, unsigned int flags));
+
+/*
+** DB_PUT_FUNC -- Sets some data according to the key.
+**
+** Parameters:
+** db -- The database to close.
+** key -- The key to use.
+** data -- The data to store.
+** flags -- put options:
+** SMDBF_NO_OVERWRITE - Return an error if key alread
+** exists.
+** SMDBF_ALLOW_DUP - Allow duplicates in btree maps.
+**
+** Returns:
+** 0 - Success, otherwise errno.
+**
+*/
+
+typedef int (*db_put_func) __P((SMDB_DATABASE *db,
+ SMDB_DBENT *key,
+ SMDB_DBENT *data, unsigned int flags));
+
+/*
+** DB_SYNC_FUNC -- Flush any cached information to disk.
+**
+** Parameters:
+** db -- The database to sync.
+** flags -- sync options:
+**
+** Returns:
+** 0 - Success, otherwise errno.
+**
+*/
+
+typedef int (*db_sync_func) __P((SMDB_DATABASE *db, unsigned int flags));
+
+/*
+** DB_SET_OWNER_FUNC -- Set the owner and group of the database files.
+**
+** Parameters:
+** db -- The database to set.
+** uid -- The UID for the new owner (-1 for no change)
+** gid -- The GID for the new owner (-1 for no change)
+**
+** Returns:
+** 0 - Success, otherwise errno.
+**
+*/
+
+typedef int (*db_set_owner_func) __P((SMDB_DATABASE *db, uid_t uid, gid_t gid));
+
+/*
+** DB_CURSOR -- Obtain a cursor for sequential access
+**
+** Parameters:
+** db -- The database to use.
+** cursor -- The address of a cursor pointer.
+** flags -- sync options:
+**
+** Returns:
+** 0 - Success, otherwise errno.
+**
+*/
+
+typedef int (*db_cursor_func) __P((SMDB_DATABASE *db,
+ SMDB_CURSOR **cursor, unsigned int flags));
+
+typedef int (*db_lockfd_func) __P((SMDB_DATABASE *db));
+
+struct database_struct
+{
+ db_close_func smdb_close;
+ db_del_func smdb_del;
+ db_fd_func smdb_fd;
+ db_get_func smdb_get;
+ db_put_func smdb_put;
+ db_sync_func smdb_sync;
+ db_set_owner_func smdb_set_owner;
+ db_cursor_func smdb_cursor;
+ db_lockfd_func smdb_lockfd;
+ void *smdb_impl;
+};
+/*
+** DB_CURSOR_CLOSE -- Close a cursor
+**
+** Parameters:
+** cursor -- The cursor to close.
+**
+** Returns:
+** 0 - Success, otherwise errno.
+**
+*/
+
+typedef int (*db_cursor_close_func) __P((SMDB_CURSOR *cursor));
+
+/*
+** DB_CURSOR_DEL -- Delete the key/value pair of this cursor
+**
+** Parameters:
+** cursor -- The cursor.
+** flags -- flags
+**
+** Returns:
+** 0 - Success, otherwise errno.
+**
+*/
+
+typedef int (*db_cursor_del_func) __P((SMDB_CURSOR *cursor,
+ unsigned int flags));
+
+/*
+** DB_CURSOR_GET -- Get the key/value of this cursor.
+**
+** Parameters:
+** cursor -- The cursor.
+** key -- The current key.
+** value -- The current value
+** flags -- flags
+**
+** Returns:
+** 0 - Success, otherwise errno.
+** SMDBE_LAST_ENTRY - This is a success condition that
+** gets returned when the end of the
+** database is hit.
+**
+*/
+
+typedef int (*db_cursor_get_func) __P((SMDB_CURSOR *cursor,
+ SMDB_DBENT *key,
+ SMDB_DBENT *data,
+ unsigned int flags));
+
+/*
+** Flags for DB_CURSOR_GET
+*/
+
+#define SMDB_CURSOR_GET_FIRST 0
+#define SMDB_CURSOR_GET_LAST 1
+#define SMDB_CURSOR_GET_NEXT 2
+#define SMDB_CURSOR_GET_RANGE 3
+
+/*
+** DB_CURSOR_PUT -- Put the key/value at this cursor.
+**
+** Parameters:
+** cursor -- The cursor.
+** key -- The current key.
+** value -- The current value
+** flags -- flags
+**
+** Returns:
+** 0 - Success, otherwise errno.
+**
+*/
+
+typedef int (*db_cursor_put_func) __P((SMDB_CURSOR *cursor,
+ SMDB_DBENT *key,
+ SMDB_DBENT *data,
+ unsigned int flags));
+
+
+
+struct cursor_struct
+{
+ db_cursor_close_func smdbc_close;
+ db_cursor_del_func smdbc_del;
+ db_cursor_get_func smdbc_get;
+ db_cursor_put_func smdbc_put;
+ void *smdbc_impl;
+};
+
+
+struct database_params_struct
+{
+ unsigned int smdbp_num_elements;
+ unsigned int smdbp_cache_size;
+ bool smdbp_allow_dup;
+};
+
+typedef struct database_params_struct SMDB_DBPARAMS;
+
+struct database_user_struct
+{
+ uid_t smdbu_id;
+ gid_t smdbu_group_id;
+ char smdbu_name[SMDB_MAX_USER_NAME_LEN];
+};
+
+typedef struct database_user_struct SMDB_USER_INFO;
+
+struct entry_struct
+{
+ void *data;
+ size_t size;
+};
+
+typedef char *SMDB_DBTYPE;
+typedef unsigned int SMDB_FLAG;
+
+/*
+** These are types of databases.
+*/
+
+# define SMDB_TYPE_DEFAULT NULL
+# define SMDB_TYPE_DEFAULT_LEN 0
+# define SMDB_TYPE_HASH "hash"
+# define SMDB_TYPE_HASH_LEN 5
+# define SMDB_TYPE_BTREE "btree"
+# define SMDB_TYPE_BTREE_LEN 6
+# define SMDB_TYPE_NDBM "dbm"
+# define SMDB_TYPE_NDBM_LEN 4
+
+/*
+** These are flags
+*/
+
+/* Flags for put */
+# define SMDBF_NO_OVERWRITE 0x00000001
+# define SMDBF_ALLOW_DUP 0x00000002
+
+
+extern SMDB_DATABASE *smdb_malloc_database __P((void));
+extern void smdb_free_database __P((SMDB_DATABASE *));
+extern int smdb_open_database __P((SMDB_DATABASE **, char *, int,
+ int, long, SMDB_DBTYPE,
+ SMDB_USER_INFO *,
+ SMDB_DBPARAMS *));
+# ifdef NEWDB
+extern int smdb_db_open __P((SMDB_DATABASE **, char *, int, int,
+ long, SMDB_DBTYPE, SMDB_USER_INFO *,
+ SMDB_DBPARAMS *));
+# endif /* NEWDB */
+# ifdef NDBM
+extern int smdb_ndbm_open __P((SMDB_DATABASE **, char *, int, int,
+ long, SMDB_DBTYPE,
+ SMDB_USER_INFO *,
+ SMDB_DBPARAMS *));
+# endif /* NDBM */
+extern int smdb_add_extension __P((char *, int, char *, char *));
+extern int smdb_setup_file __P((char *, char *, int, long,
+ SMDB_USER_INFO *, struct stat *));
+extern int smdb_lock_file __P((int *, char *, int, long, char *));
+extern int smdb_unlock_file __P((int));
+extern int smdb_filechanged __P((char *, char *, int,
+ struct stat *));
+extern void smdb_print_available_types __P((void));
+extern char *smdb_db_definition __P((SMDB_DBTYPE));
+extern int smdb_lock_map __P((SMDB_DATABASE *, int));
+extern int smdb_unlock_map __P((SMDB_DATABASE *));
+#endif /* ! _SMDB_H_ */
diff --git a/usr/src/cmd/sendmail/include/sendmail/mailstats.h b/usr/src/cmd/sendmail/include/sendmail/mailstats.h
new file mode 100644
index 0000000000..c8cc5515c4
--- /dev/null
+++ b/usr/src/cmd/sendmail/include/sendmail/mailstats.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 1998, 1999 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ *
+ * $Id: mailstats.h,v 8.19 2002/06/27 22:47:22 gshapiro Exp $
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#define STAT_VERSION 4
+#define STAT_MAGIC 0x1B1DE
+
+/*
+** Statistics structure.
+*/
+
+struct statistics
+{
+ int stat_magic; /* magic number */
+ int stat_version; /* stat file version */
+ time_t stat_itime; /* file initialization time */
+ short stat_size; /* size of this structure */
+ long stat_cf; /* # from connections */
+ long stat_ct; /* # to connections */
+ long stat_cr; /* # rejected connections */
+ long stat_nf[MAXMAILERS]; /* # msgs from each mailer */
+ long stat_bf[MAXMAILERS]; /* kbytes from each mailer */
+ long stat_nt[MAXMAILERS]; /* # msgs to each mailer */
+ long stat_bt[MAXMAILERS]; /* kbytes to each mailer */
+ long stat_nr[MAXMAILERS]; /* # rejects by each mailer */
+ long stat_nd[MAXMAILERS]; /* # discards by each mailer */
+ long stat_nq[MAXMAILERS]; /* # quarantines by each mailer */
+};
diff --git a/usr/src/cmd/sendmail/include/sendmail/pathnames.h b/usr/src/cmd/sendmail/include/sendmail/pathnames.h
new file mode 100644
index 0000000000..5ca3b51bd4
--- /dev/null
+++ b/usr/src/cmd/sendmail/include/sendmail/pathnames.h
@@ -0,0 +1,64 @@
+/*-
+ * Copyright (c) 1998-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ *
+ * $Id: pathnames.h,v 8.35 2001/03/23 22:09:44 ca Exp $
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#ifndef SM_PATHNAMES_H
+# define SM_PATHNAMES_H
+
+
+# ifndef _PATH_SENDMAILCF
+# if defined(USE_VENDOR_CF_PATH) && defined(_PATH_VENDOR_CF)
+# define _PATH_SENDMAILCF _PATH_VENDOR_CF
+# else /* defined(USE_VENDOR_CF_PATH) && defined(_PATH_VENDOR_CF) */
+# define _PATH_SENDMAILCF "/etc/mail/sendmail.cf"
+# endif /* defined(USE_VENDOR_CF_PATH) && defined(_PATH_VENDOR_CF) */
+# endif /* ! _PATH_SENDMAILCF */
+
+# ifndef _PATH_SENDMAILPID
+# ifdef BSD4_4
+# define _PATH_SENDMAILPID "/var/run/sendmail.pid"
+# else /* BSD4_4 */
+# define _PATH_SENDMAILPID "/etc/mail/sendmail.pid"
+# endif /* BSD4_4 */
+# endif /* ! _PATH_SENDMAILPID */
+
+# ifndef _PATH_SENDMAIL
+# define _PATH_SENDMAIL "/usr/lib/sendmail"
+# endif /* ! _PATH_SENDMAIL */
+
+# ifndef _PATH_MAILDIR
+# define _PATH_MAILDIR "/var/spool/mail"
+# endif /* ! _PATH_MAILDIR */
+
+# ifndef _PATH_LOCTMP
+# define _PATH_LOCTMP "/tmp/local.XXXXXX"
+# endif /* ! _PATH_LOCTMP */
+
+# ifndef _PATH_HOSTS
+# define _PATH_HOSTS "/etc/hosts"
+# endif /* ! _PATH_HOSTS */
+
+
+
+# ifndef _DIR_SENDMAILCF
+# define _DIR_SENDMAILCF "/etc/mail/"
+# endif /* ! _DIR_SENDMAILCF */
+
+# define SM_GET_RIGHT_CF 0 /* get "right" .cf */
+# define SM_GET_SENDMAIL_CF 1 /* always use sendmail.cf */
+# define SM_GET_SUBMIT_CF 2 /* always use submit.cf */
+
+extern char *getcfname __P((int, int, int, char *));
+#endif /* ! SM_PATHNAMES_H */
diff --git a/usr/src/cmd/sendmail/include/sendmail/sendmail.h b/usr/src/cmd/sendmail/include/sendmail/sendmail.h
new file mode 100644
index 0000000000..7da11057e8
--- /dev/null
+++ b/usr/src/cmd/sendmail/include/sendmail/sendmail.h
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 1998-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ *
+ * $Id: sendmail.h,v 8.68 2002/07/01 22:18:53 gshapiro Exp $
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+** SENDMAIL.H -- Global definitions for sendmail.
+*/
+
+#include <stdio.h>
+#include <sm/bitops.h>
+#include <sm/io.h>
+#include <sm/string.h>
+#include "conf.h"
+
+/**********************************************************************
+** Table sizes, etc....
+** There shouldn't be much need to change these....
+**********************************************************************/
+#ifndef MAXMAILERS
+# define MAXMAILERS 25 /* maximum mailers known to system */
+#endif /* ! MAXMAILERS */
+
+/*
+** Flags passed to safefile/safedirpath.
+*/
+
+#define SFF_ANYFILE 0L /* no special restrictions */
+#define SFF_MUSTOWN 0x00000001L /* user must own this file */
+#define SFF_NOSLINK 0x00000002L /* file cannot be a symbolic link */
+#define SFF_ROOTOK 0x00000004L /* ok for root to own this file */
+#define SFF_RUNASREALUID 0x00000008L /* if no ctladdr, run as real uid */
+#define SFF_NOPATHCHECK 0x00000010L /* don't bother checking dir path */
+#define SFF_SETUIDOK 0x00000020L /* set-user-ID files are ok */
+#define SFF_CREAT 0x00000040L /* ok to create file if necessary */
+#define SFF_REGONLY 0x00000080L /* regular files only */
+#define SFF_SAFEDIRPATH 0x00000100L /* no writable directories allowed */
+#define SFF_NOHLINK 0x00000200L /* file cannot have hard links */
+#define SFF_NOWLINK 0x00000400L /* links only in non-writable dirs */
+#define SFF_NOGWFILES 0x00000800L /* disallow world writable files */
+#define SFF_NOWWFILES 0x00001000L /* disallow group writable files */
+#define SFF_OPENASROOT 0x00002000L /* open as root instead of real user */
+#define SFF_NOLOCK 0x00004000L /* don't lock the file */
+#define SFF_NOGRFILES 0x00008000L /* disallow g readable files */
+#define SFF_NOWRFILES 0x00010000L /* disallow o readable files */
+#define SFF_NOTEXCL 0x00020000L /* creates don't need to be exclusive */
+#define SFF_EXECOK 0x00040000L /* executable files are ok (E_SM_ISEXEC) */
+#define SFF_NBLOCK 0x00080000L /* use a non-blocking lock */
+#define SFF_NORFILES (SFF_NOGRFILES|SFF_NOWRFILES)
+
+/* pseudo-flags */
+#define SFF_NOLINK (SFF_NOHLINK|SFF_NOSLINK)
+
+/* functions */
+extern int safefile __P((char *, UID_T, GID_T, char *, long, int, struct stat *));
+extern int safedirpath __P((char *, UID_T, GID_T, char *, long, int, int));
+extern int safeopen __P((char *, int, int, long));
+extern SM_FILE_T*safefopen __P((char *, int, int, long));
+extern int dfopen __P((char *, int, int, long));
+extern bool filechanged __P((char *, int, struct stat *));
+
+/*
+** DontBlameSendmail options
+**
+** Hopefully nobody uses these.
+*/
+
+#define DBS_SAFE 0
+#define DBS_ASSUMESAFECHOWN 1
+#define DBS_GROUPWRITABLEDIRPATHSAFE 2
+#define DBS_GROUPWRITABLEFORWARDFILESAFE 3
+#define DBS_GROUPWRITABLEINCLUDEFILESAFE 4
+#define DBS_GROUPWRITABLEALIASFILE 5
+#define DBS_WORLDWRITABLEALIASFILE 6
+#define DBS_FORWARDFILEINUNSAFEDIRPATH 7
+#define DBS_MAPINUNSAFEDIRPATH 8
+#define DBS_LINKEDALIASFILEINWRITABLEDIR 9
+#define DBS_LINKEDCLASSFILEINWRITABLEDIR 10
+#define DBS_LINKEDFORWARDFILEINWRITABLEDIR 11
+#define DBS_LINKEDINCLUDEFILEINWRITABLEDIR 12
+#define DBS_LINKEDMAPINWRITABLEDIR 13
+#define DBS_LINKEDSERVICESWITCHFILEINWRITABLEDIR 14
+#define DBS_FILEDELIVERYTOHARDLINK 15
+#define DBS_FILEDELIVERYTOSYMLINK 16
+#define DBS_WRITEMAPTOHARDLINK 17
+#define DBS_WRITEMAPTOSYMLINK 18
+#define DBS_WRITESTATSTOHARDLINK 19
+#define DBS_WRITESTATSTOSYMLINK 20
+#define DBS_FORWARDFILEINGROUPWRITABLEDIRPATH 21
+#define DBS_INCLUDEFILEINGROUPWRITABLEDIRPATH 22
+#define DBS_CLASSFILEINUNSAFEDIRPATH 23
+#define DBS_ERRORHEADERINUNSAFEDIRPATH 24
+#define DBS_HELPFILEINUNSAFEDIRPATH 25
+#define DBS_FORWARDFILEINUNSAFEDIRPATHSAFE 26
+#define DBS_INCLUDEFILEINUNSAFEDIRPATHSAFE 27
+#define DBS_RUNPROGRAMINUNSAFEDIRPATH 28
+#define DBS_RUNWRITABLEPROGRAM 29
+#define DBS_INCLUDEFILEINUNSAFEDIRPATH 30
+#define DBS_NONROOTSAFEADDR 31
+#define DBS_TRUSTSTICKYBIT 32
+#define DBS_DONTWARNFORWARDFILEINUNSAFEDIRPATH 33
+#define DBS_INSUFFICIENTENTROPY 34
+#define DBS_GROUPREADABLESASLDBFILE 35
+#define DBS_GROUPWRITABLESASLDBFILE 36
+#define DBS_GROUPWRITABLEFORWARDFILE 37
+#define DBS_GROUPWRITABLEINCLUDEFILE 38
+#define DBS_WORLDWRITABLEFORWARDFILE 39
+#define DBS_WORLDWRITABLEINCLUDEFILE 40
+#define DBS_GROUPREADABLEKEYFILE 41
+#if _FFR_GROUPREADABLEAUTHINFOFILE
+# define DBS_GROUPREADABLEAUTHINFOFILE 42
+#endif /* _FFR_GROUPREADABLEAUTHINFOFILE */
+
+/* struct defining such things */
+struct dbsval
+{
+ char *dbs_name; /* name of DontBlameSendmail flag */
+ unsigned char dbs_flag; /* numeric level */
+};
+
+/* Flags for submitmode */
+#define SUBMIT_UNKNOWN 0x0000 /* unknown agent type */
+#define SUBMIT_MTA 0x0001 /* act like a message transfer agent */
+#define SUBMIT_MSA 0x0002 /* act like a message submission agent */
+
diff --git a/usr/src/cmd/sendmail/include/sm/assert.h b/usr/src/cmd/sendmail/include/sm/assert.h
new file mode 100644
index 0000000000..d96920e7fd
--- /dev/null
+++ b/usr/src/cmd/sendmail/include/sm/assert.h
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ * $Id: assert.h,v 1.10 2001/06/07 20:04:53 ca Exp $
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+** libsm abnormal program termination and assertion checking
+** See libsm/assert.html for documentation.
+*/
+
+#ifndef SM_ASSERT_H
+# define SM_ASSERT_H
+
+# include <sm/gen.h>
+# include <sm/debug.h>
+
+/*
+** abnormal program termination
+*/
+
+typedef void (*SM_ABORT_HANDLER_T) __P((const char *, int, const char *));
+
+extern SM_DEAD(void
+sm_abort_at __P((
+ const char *,
+ int,
+ const char *)));
+
+extern void
+sm_abort_sethandler __P((
+ SM_ABORT_HANDLER_T));
+
+extern SM_DEAD(void PRINTFLIKE(1, 2)
+sm_abort __P((
+ char *,
+ ...)));
+
+/*
+** assertion checking
+*/
+
+# ifndef SM_CHECK_ALL
+# define SM_CHECK_ALL 1
+# endif /* ! SM_CHECK_ALL */
+
+# ifndef SM_CHECK_REQUIRE
+# define SM_CHECK_REQUIRE SM_CHECK_ALL
+# endif /* ! SM_CHECK_REQUIRE */
+
+# ifndef SM_CHECK_ENSURE
+# define SM_CHECK_ENSURE SM_CHECK_ALL
+# endif /* ! SM_CHECK_ENSURE */
+
+# ifndef SM_CHECK_ASSERT
+# define SM_CHECK_ASSERT SM_CHECK_ALL
+# endif /* ! SM_CHECK_ASSERT */
+
+# if SM_CHECK_REQUIRE
+# if defined(__STDC__) || defined(__cplusplus)
+# define SM_REQUIRE(cond) \
+ ((void) ((cond) || (sm_abort_at(__FILE__, __LINE__, \
+ "SM_REQUIRE(" #cond ") failed"), 0)))
+# else /* defined(__STDC__) || defined(__cplusplus) */
+# define SM_REQUIRE(cond) \
+ ((void) ((cond) || (sm_abort_at(__FILE__, __LINE__, \
+ "SM_REQUIRE(cond) failed"), 0)))
+# endif /* defined(__STDC__) || defined(__cplusplus) */
+# else /* SM_CHECK_REQUIRE */
+# define SM_REQUIRE(cond) ((void) 0)
+# endif /* SM_CHECK_REQUIRE */
+
+# define SM_REQUIRE_ISA(obj, magic) \
+ SM_REQUIRE((obj) != NULL && (obj)->sm_magic == (magic))
+
+# if SM_CHECK_ENSURE
+# if defined(__STDC__) || defined(__cplusplus)
+# define SM_ENSURE(cond) \
+ ((void) ((cond) || (sm_abort_at(__FILE__, __LINE__, \
+ "SM_ENSURE(" #cond ") failed"), 0)))
+# else /* defined(__STDC__) || defined(__cplusplus) */
+# define SM_ENSURE(cond) \
+ ((void) ((cond) || (sm_abort_at(__FILE__, __LINE__, \
+ "SM_ENSURE(cond) failed"), 0)))
+# endif /* defined(__STDC__) || defined(__cplusplus) */
+# else /* SM_CHECK_ENSURE */
+# define SM_ENSURE(cond) ((void) 0)
+# endif /* SM_CHECK_ENSURE */
+
+# if SM_CHECK_ASSERT
+# if defined(__STDC__) || defined(__cplusplus)
+# define SM_ASSERT(cond) \
+ ((void) ((cond) || (sm_abort_at(__FILE__, __LINE__, \
+ "SM_ASSERT(" #cond ") failed"), 0)))
+# else /* defined(__STDC__) || defined(__cplusplus) */
+# define SM_ASSERT(cond) \
+ ((void) ((cond) || (sm_abort_at(__FILE__, __LINE__, \
+ "SM_ASSERT(cond) failed"), 0)))
+# endif /* defined(__STDC__) || defined(__cplusplus) */
+# else /* SM_CHECK_ASSERT */
+# define SM_ASSERT(cond) ((void) 0)
+# endif /* SM_CHECK_ASSERT */
+
+extern SM_DEBUG_T SmExpensiveRequire;
+extern SM_DEBUG_T SmExpensiveEnsure;
+extern SM_DEBUG_T SmExpensiveAssert;
+
+#endif /* ! SM_ASSERT_H */
diff --git a/usr/src/cmd/sendmail/include/sm/bdb.h b/usr/src/cmd/sendmail/include/sm/bdb.h
new file mode 100644
index 0000000000..0b9d0d0007
--- /dev/null
+++ b/usr/src/cmd/sendmail/include/sm/bdb.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2002, 2003 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ *
+ * $Id: bdb.h,v 1.1.2.4 2003/03/06 16:27:38 ca Exp $
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#ifndef SM_BDB_H
+#define SM_BDB_H
+
+#if NEWDB
+# include <db.h>
+# ifndef DB_VERSION_MAJOR
+# define DB_VERSION_MAJOR 1
+# endif /* ! DB_VERSION_MAJOR */
+
+# if DB_VERSION_MAJOR >= 4 && DB_VERSION_MINOR >= 1
+
+# define DBTXN NULL ,
+
+/*
+** Always turn on DB_FCNTL_LOCKING for DB 4.1.x since its
+** "workaround" for accepting an empty (locked) file depends on
+** this flag. Notice: this requires 4.1.24 + patch (which should be
+** part of 4.1.25).
+*/
+
+# define SM_DB_FLAG_ADD(flag) (flag) |= DB_FCNTL_LOCKING
+
+# else /* DB_VERSION_MAJOR >= 4 && DB_VERSION_MINOR >= 1 */
+
+# define DBTXN
+# if !HASFLOCK && defined(DB_FCNTL_LOCKING)
+# define SM_DB_FLAG_ADD(flag) (flag) |= DB_FCNTL_LOCKING
+# else /* !HASFLOCK && defined(DB_FCNTL_LOCKING) */
+# define SM_DB_FLAG_ADD(flag) ((void) 0)
+# endif /* !HASFLOCK && defined(DB_FCNTL_LOCKING) */
+
+# endif /* DB_VERSION_MAJOR >= 4 && DB_VERSION_MINOR >= 1 */
+#endif /* NEWDB */
+
+#endif /* ! SM_BDB_H */
diff --git a/usr/src/cmd/sendmail/include/sm/bitops.h b/usr/src/cmd/sendmail/include/sm/bitops.h
new file mode 100644
index 0000000000..bb68faed12
--- /dev/null
+++ b/usr/src/cmd/sendmail/include/sm/bitops.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 1998-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ *
+ * $Id: bitops.h,v 1.2 2001/09/22 22:05:42 ca Exp $
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#ifndef SM_BITOPS_H
+# define SM_BITOPS_H
+
+/*
+** Data structure for bit maps.
+**
+** Each bit in this map can be referenced by an ascii character.
+** This is 256 possible bits, or 32 8-bit bytes.
+*/
+
+# define BITMAPBITS 256 /* number of bits in a bit map */
+# define BYTEBITS 8 /* number of bits in a byte */
+# define BITMAPBYTES (BITMAPBITS / BYTEBITS) /* number of bytes in bit map */
+# define BITMAPMAX ((BITMAPBYTES / sizeof (int)) - 1)
+
+/* internal macros */
+
+/* make sure this index never leaves the allowed range: 0 to BITMAPMAX */
+# define _BITWORD(bit) (((unsigned char)(bit) / (BYTEBITS * sizeof (int))) & BITMAPMAX)
+# define _BITBIT(bit) ((unsigned int)1 << ((unsigned char)(bit) % (BYTEBITS * sizeof (int))))
+
+typedef unsigned int BITMAP256[BITMAPBYTES / sizeof (int)];
+
+/* properly case and truncate bit */
+# define bitidx(bit) ((unsigned int) (bit) & 0xff)
+
+/* test bit number N */
+# define bitnset(bit, map) ((map)[_BITWORD(bit)] & _BITBIT(bit))
+
+/* set bit number N */
+# define setbitn(bit, map) (map)[_BITWORD(bit)] |= _BITBIT(bit)
+
+/* clear bit number N */
+# define clrbitn(bit, map) (map)[_BITWORD(bit)] &= ~_BITBIT(bit)
+
+/* clear an entire bit map */
+# define clrbitmap(map) memset((char *) map, '\0', BITMAPBYTES)
+
+/* bit hacking */
+# define bitset(bit, word) (((word) & (bit)) != 0)
+
+#endif /* ! SM_BITOPS_H */
diff --git a/usr/src/cmd/sendmail/include/sm/cdefs.h b/usr/src/cmd/sendmail/include/sm/cdefs.h
new file mode 100644
index 0000000000..73c6f64e6d
--- /dev/null
+++ b/usr/src/cmd/sendmail/include/sm/cdefs.h
@@ -0,0 +1,149 @@
+/*
+ * Copyright (c) 2000-2002 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ * $Id: cdefs.h,v 1.15.2.1 2003/12/05 22:44:17 ca Exp $
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+** libsm C language portability macros
+** See libsm/cdefs.html for documentation.
+*/
+
+#ifndef SM_CDEFS_H
+# define SM_CDEFS_H
+
+# include <sm/config.h>
+
+/*
+** BSD and Linux have <sys/cdefs.h> which defines a set of C language
+** portability macros that are a defacto standard in the open source
+** community.
+*/
+
+# if SM_CONF_SYS_CDEFS_H
+# include <sys/cdefs.h>
+# endif /* SM_CONF_SYS_CDEFS_H */
+
+/*
+** Define the standard C language portability macros
+** for platforms that lack <sys/cdefs.h>.
+*/
+
+# if !SM_CONF_SYS_CDEFS_H
+# if defined(__cplusplus)
+# define __BEGIN_DECLS extern "C" {
+# define __END_DECLS };
+# else /* defined(__cplusplus) */
+# define __BEGIN_DECLS
+# define __END_DECLS
+# endif /* defined(__cplusplus) */
+# if defined(__STDC__) || defined(__cplusplus)
+# ifndef __P
+# define __P(protos) protos
+# endif /* __P */
+# define __CONCAT(x,y) x ## y
+# define __STRING(x) #x
+# else /* defined(__STDC__) || defined(__cplusplus) */
+# define __P(protos) ()
+# define __CONCAT(x,y) x/**/y
+# define __STRING(x) "x"
+# define const
+# define signed
+# define volatile
+# endif /* defined(__STDC__) || defined(__cplusplus) */
+# endif /* !SM_CONF_SYS_CDEFS_H */
+
+/*
+** Define SM_DEAD, a macro used to declare functions that do not return
+** to their caller.
+*/
+
+# ifndef SM_DEAD
+# if __GNUC__ >= 2
+# if __GNUC__ == 2 && __GNUC_MINOR__ < 5
+# define SM_DEAD(proto) volatile proto
+# define SM_DEAD_D volatile
+# else /* __GNUC__ == 2 && __GNUC_MINOR__ < 5 */
+# define SM_DEAD(proto) proto __attribute__((__noreturn__))
+# define SM_DEAD_D
+# endif /* __GNUC__ == 2 && __GNUC_MINOR__ < 5 */
+# else /* __GNUC__ >= 2 */
+# define SM_DEAD(proto) proto
+# define SM_DEAD_D
+# endif /* __GNUC__ >= 2 */
+# endif /* SM_DEAD */
+
+/*
+** Define SM_UNUSED, a macro used to declare variables that may be unused.
+*/
+
+# ifndef SM_UNUSED
+# if __GNUC__ >= 2
+# if __GNUC__ == 2 && __GNUC_MINOR__ < 7
+# define SM_UNUSED(decl) decl
+# else /* __GNUC__ == 2 && __GNUC_MINOR__ < 7 */
+# define SM_UNUSED(decl) decl __attribute__((__unused__))
+# endif /* __GNUC__ == 2 && __GNUC_MINOR__ < 7 */
+# else /* __GNUC__ >= 2 */
+# define SM_UNUSED(decl) decl
+# endif /* __GNUC__ >= 2 */
+# endif /* SM_UNUSED */
+
+/*
+** The SM_NONVOLATILE macro is used to declare variables that are not
+** volatile, but which must be declared volatile when compiling with
+** gcc -O -Wall in order to suppress bogus warning messages.
+**
+** Variables that actually are volatile should be declared volatile
+** using the "volatile" keyword. If a variable actually is volatile,
+** then SM_NONVOLATILE should not be used.
+**
+** To compile sendmail with gcc and see all non-bogus warnings,
+** you should use
+** gcc -O -Wall -DSM_OMIT_BOGUS_WARNINGS ...
+** Do not use -DSM_OMIT_BOGUS_WARNINGS when compiling the production
+** version of sendmail, because there is a performance hit.
+*/
+
+# ifdef SM_OMIT_BOGUS_WARNINGS
+# define SM_NONVOLATILE volatile
+# else /* SM_OMIT_BOGUS_WARNINGS */
+# define SM_NONVOLATILE
+# endif /* SM_OMIT_BOGUS_WARNINGS */
+
+/*
+** Turn on format string argument checking.
+*/
+
+# ifndef SM_CONF_FORMAT_TEST
+# if __GNUC__ == 2 && __GNUC_MINOR__ >= 7
+# define SM_CONF_FORMAT_TEST 1
+# else /* __GNUC__ == 2 && __GNUC_MINOR__ >= 7 */
+# define SM_CONF_FORMAT_TEST 0
+# endif /* __GNUC__ == 2 && __GNUC_MINOR__ >= 7 */
+# endif /* SM_CONF_FORMAT_TEST */
+
+# ifndef PRINTFLIKE
+# if SM_CONF_FORMAT_TEST
+# define PRINTFLIKE(x,y) __attribute__ ((__format__ (__printf__, x, y)))
+# else /* SM_CONF_FORMAT_TEST */
+# define PRINTFLIKE(x,y)
+# endif /* SM_CONF_FORMAT_TEST */
+# endif /* ! PRINTFLIKE */
+
+# ifndef SCANFLIKE
+# if SM_CONF_FORMAT_TEST
+# define SCANFLIKE(x,y) __attribute__ ((__format__ (__scanf__, x, y)))
+# else /* SM_CONF_FORMAT_TEST */
+# define SCANFLIKE(x,y)
+# endif /* SM_CONF_FORMAT_TEST */
+# endif /* ! SCANFLIKE */
+
+#endif /* ! SM_CDEFS_H */
diff --git a/usr/src/cmd/sendmail/include/sm/cf.h b/usr/src/cmd/sendmail/include/sm/cf.h
new file mode 100644
index 0000000000..3d4c032d01
--- /dev/null
+++ b/usr/src/cmd/sendmail/include/sm/cf.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ * $Id: cf.h,v 1.2 2001/03/08 03:23:07 ca Exp $
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#ifndef SM_CF_H
+# define SM_CF_H
+
+#include <sm/gen.h>
+
+typedef struct
+{
+ char *opt_name;
+ char *opt_val;
+} SM_CF_OPT_T;
+
+extern int
+sm_cf_getopt __P((
+ char *path,
+ int optc,
+ SM_CF_OPT_T *optv));
+
+#endif /* ! SM_CF_H */
diff --git a/usr/src/cmd/sendmail/include/sm/clock.h b/usr/src/cmd/sendmail/include/sm/clock.h
new file mode 100644
index 0000000000..5996e216bf
--- /dev/null
+++ b/usr/src/cmd/sendmail/include/sm/clock.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 1998-2001, 2004 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ * $Id: clock.h,v 1.12 2004/08/03 19:57:21 ca Exp $
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+** CLOCK.H -- for co-ordinating timed events
+*/
+
+#ifndef _SM_CLOCK_H
+# define _SM_CLOCK_H 1
+
+# include <sm/signal.h>
+# if SM_CONF_SETITIMER
+# include <sys/time.h>
+# endif /* SM_CONF_SETITIMER */
+
+/*
+** STRUCT SM_EVENT -- event queue.
+**
+** Maintained in sorted order.
+**
+** We store the pid of the process that set this event to insure
+** that when we fork we will not take events intended for the parent.
+*/
+
+struct sm_event
+{
+# if SM_CONF_SETITIMER
+ struct timeval ev_time; /* time of the call (microseconds) */
+# else /* SM_CONF_SETITIMER */
+ time_t ev_time; /* time of the call (seconds) */
+# endif /* SM_CONF_SETITIMER */
+ void (*ev_func)__P((int));
+ /* function to call */
+ int ev_arg; /* argument to ev_func */
+ pid_t ev_pid; /* pid that set this event */
+ struct sm_event *ev_link; /* link to next item */
+};
+
+typedef struct sm_event SM_EVENT;
+
+/* functions */
+extern void sm_clrevent __P((SM_EVENT *));
+extern void sm_clear_events __P((void));
+extern SM_EVENT *sm_seteventm __P((int, void(*)__P((int)), int));
+extern SM_EVENT *sm_sigsafe_seteventm __P((int, void(*)__P((int)), int));
+extern SIGFUNC_DECL sm_tick __P((int));
+
+/*
+** SM_SETEVENT -- set an event to happen at a specific time in seconds.
+**
+** Translates the seconds into millseconds and calls sm_seteventm()
+** to get a specific event to happen in the future at a specific time.
+**
+** Parameters:
+** t -- intvl until next event occurs (seconds).
+** f -- function to call on event.
+** a -- argument to func on event.
+**
+** Returns:
+** result of sm_seteventm().
+**
+** Side Effects:
+** Any that sm_seteventm() have.
+*/
+
+#define sm_setevent(t, f, a) sm_seteventm((int)((t) * 1000), (f), (a))
+#define sm_sigsafe_setevent(t, f, a) sm_sigsafe_seteventm((int)((t) * 1000), (f), (a))
+
+#endif /* _SM_CLOCK_H */
diff --git a/usr/src/cmd/sendmail/include/sm/conf.h b/usr/src/cmd/sendmail/include/sm/conf.h
new file mode 100644
index 0000000000..976dbc1bcc
--- /dev/null
+++ b/usr/src/cmd/sendmail/include/sm/conf.h
@@ -0,0 +1,2967 @@
+/*
+ * Copyright (c) 1998-2005 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ *
+ * $Id: conf.h,v 1.120 2005/03/22 22:07:53 ca Exp $
+ */
+
+/*
+ * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+** CONF.H -- All user-configurable parameters for sendmail
+**
+** Send updates to sendmail@Sendmail.ORG so they will be
+** included in the next release.
+*/
+
+#ifndef SM_CONF_H
+# define SM_CONF_H 1
+
+
+# include <sm/config.h>
+# include <sm/varargs.h>
+
+/*
+** General "standard C" defines.
+**
+** These may be undone later, to cope with systems that claim to
+** be Standard C but aren't. Gcc is the biggest offender -- it
+** doesn't realize that the library is part of the language.
+**
+** Life would be much easier if we could get rid of this sort
+** of bozo problems.
+*/
+
+# ifdef __STDC__
+# define HASSETVBUF 1 /* we have setvbuf(3) in libc */
+# endif /* __STDC__ */
+
+/*
+** Assume you have standard calls; can be #undefed below if necessary.
+*/
+
+# ifndef HASLSTAT
+# define HASLSTAT 1 /* has lstat(2) call */
+# endif /* ! HASLSTAT */
+
+# ifndef HASNICE
+# define HASNICE 1 /* has nice(2) call */
+# endif /* ! HASNICE */
+
+# ifndef HASRRESVPORT
+# define HASRRESVPORT 1 /* has rrsevport(3) call */
+# endif /* ! HASRRESVPORT */
+
+/**********************************************************************
+** "Hard" compilation options.
+** #define these if they are available; comment them out otherwise.
+** These cannot be overridden from the Makefile, and should really not
+** be turned off unless absolutely necessary.
+**********************************************************************/
+
+#define LOG 1 /* enable logging -- don't turn off */
+
+/**********************************************************************
+** Operating system configuration.
+**
+** Unless you are porting to a new OS, you shouldn't have to
+** change these.
+**********************************************************************/
+
+/*
+** HP-UX -- tested for 8.07, 9.00, and 9.01.
+**
+** If V4FS is defined, compile for HP-UX 10.0.
+** 11.x support from Richard Allen <ra@hp.is>.
+*/
+
+# ifdef __hpux
+ /* common definitions for HP-UX 9.x and 10.x */
+# undef m_flags /* conflict between Berkeley DB 1.85 db.h & sys/sysmacros.h on HP 300 */
+# define SYSTEM5 1 /* include all the System V defines */
+# define HASINITGROUPS 1 /* has initgroups(3) call */
+# define HASFCHMOD 1 /* has fchmod(2) syscall */
+# define USESETEUID 1 /* has usable seteuid(2) call */
+# define HASSETRESGID 1 /* use setresgid(2) to set saved gid */
+# define BOGUS_O_EXCL 1 /* exclusive open follows symlinks */
+# define seteuid(e) setresuid(-1, e, -1)
+# define IP_SRCROUTE 1 /* can check IP source routing */
+# define LA_TYPE LA_HPUX
+# define SPT_TYPE SPT_PSTAT
+# define SFS_TYPE SFS_VFS /* use <sys/vfs.h> statfs() implementation */
+# define GIDSET_T gid_t
+# define LDA_USE_LOCKF 1
+# ifndef HASGETUSERSHELL
+# define HASGETUSERSHELL 0 /* getusershell(3) causes core dumps */
+# endif /* ! HASGETUSERSHELL */
+# ifdef HPUX10
+# define _PATH_SENDMAIL "/usr/sbin/sendmail"
+# ifndef SMRSH_CMDDIR
+# define SMRSH_CMDDIR "/var/adm/sm.bin"
+# endif /* ! SMRSH_CMDDIR */
+# endif /* HPUX10 */
+# ifdef HPUX11
+# define HASSETREUID 1 /* setreuid(2) works on HP-UX 11.x */
+# define HASFCHOWN 1 /* has fchown(2) */
+# ifndef BROKEN_RES_SEARCH
+# define BROKEN_RES_SEARCH 1 /* res_search(unknown) returns h_errno=0 */
+# endif /* ! BROKEN_RES_SEARCH */
+# ifndef SMRSH_CMDDIR
+# define SMRSH_CMDDIR "/var/adm/sm.bin"
+# endif /* ! SMRSH_CMDDIR */
+# define _PATH_SENDMAIL "/usr/sbin/sendmail"
+# else /* HPUX11 */
+# ifndef NOT_SENDMAIL
+# define syslog hard_syslog
+# endif /* ! NOT_SENDMAIL */
+# endif /* HPUX11 */
+# define SAFENFSPATHCONF 1 /* pathconf(2) pessimizes on NFS filesystems */
+
+# ifdef V4FS
+ /* HP-UX 10.x */
+# define _PATH_UNIX "/stand/vmunix"
+# ifndef _PATH_VENDOR_CF
+# define _PATH_VENDOR_CF "/etc/mail/sendmail.cf"
+# endif /* ! _PATH_VENDOR_CF */
+# ifndef _PATH_SENDMAILPID
+# define _PATH_SENDMAILPID "/etc/mail/sendmail.pid"
+# endif /* ! _PATH_SENDMAILPID */
+# ifndef IDENTPROTO
+# define IDENTPROTO 1 /* TCP/IP implementation fixed in 10.0 */
+# endif /* ! IDENTPROTO */
+# include <sys/mpctl.h> /* for mpctl() in get_num_procs_online() */
+# else /* V4FS */
+ /* HP-UX 9.x */
+# define _PATH_UNIX "/hp-ux"
+# ifndef _PATH_VENDOR_CF
+# define _PATH_VENDOR_CF "/usr/lib/sendmail.cf"
+# endif /* ! _PATH_VENDOR_CF */
+# ifndef IDENTPROTO
+# define IDENTPROTO 0 /* TCP/IP implementation is broken */
+# endif /* ! IDENTPROTO */
+# ifdef __STDC__
+extern void hard_syslog(int, char *, ...);
+# else /* __STDC__ */
+extern void hard_syslog();
+# endif /* __STDC__ */
+# define FDSET_CAST (int *) /* cast for fd_set parameters to select */
+# endif /* V4FS */
+
+# endif /* __hpux */
+
+/*
+** IBM AIX 5.x
+*/
+
+# ifdef _AIX5
+# define _AIX4 40300
+# define SOCKADDR_LEN_T socklen_t /* e.g., arg#3 to accept, getsockname */
+# define SOCKOPT_LEN_T socklen_t /* arg#5 to getsockopt */
+# if _AIX5 >= 50200
+# define HASUNSETENV 1 /* has unsetenv(3) call */
+# endif /* _AIX5 >= 50200 */
+# endif /* _AIX5 */
+
+/*
+** IBM AIX 4.x
+*/
+
+# ifdef _AIX4
+# define _AIX3 1 /* pull in AIX3 stuff */
+# define BSD4_4_SOCKADDR /* has sa_len */
+# define USESETEUID 1 /* seteuid(2) works */
+# define TZ_TYPE TZ_NAME /* use tzname[] vector */
+# ifndef SOCKOPT_LEN_T
+# define SOCKOPT_LEN_T size_t /* arg#5 to getsockopt */
+# endif /* SOCKOPT_LEN_T */
+# if _AIX4 >= 40200
+# define HASSETREUID 1 /* setreuid(2) works as of AIX 4.2 */
+# ifndef SOCKADDR_LEN_T
+# define SOCKADDR_LEN_T size_t /* e.g., arg#3 to accept, getsockname */
+# endif /* SOCKADDR_LEN_T */
+# endif /* _AIX4 >= 40200 */
+# if defined(_ILS_MACROS) /* IBM versions aren't side-effect clean */
+# undef isascii
+# define isascii(c) !(c & ~0177)
+# undef isdigit
+# define isdigit(__a) (_IS(__a,_ISDIGIT))
+# undef isspace
+# define isspace(__a) (_IS(__a,_ISSPACE))
+# endif /* defined(_ILS_MACROS) */
+# endif /* _AIX4 */
+
+
+/*
+** IBM AIX 3.x -- actually tested for 3.2.3
+*/
+
+# ifdef _AIX3
+# include <paths.h>
+# include <sys/machine.h> /* to get byte order */
+# include <sys/select.h>
+# define HASFCHOWN 1 /* has fchown(2) */
+# define HASINITGROUPS 1 /* has initgroups(3) call */
+# define HASUNAME 1 /* use System V uname(2) system call */
+# define HASGETUSERSHELL 0 /* does not have getusershell(3) call */
+# define HASFCHMOD 1 /* has fchmod(2) syscall */
+# define IP_SRCROUTE 0 /* Something is broken with getsockopt() */
+# define GIDSET_T gid_t
+# define SFS_TYPE SFS_STATFS /* use <sys/statfs.h> statfs() impl */
+# define SPT_PADCHAR '\0' /* pad process title with nulls */
+# ifndef LA_TYPE
+# define LA_TYPE LA_INT
+# endif /* LA_TYPE */
+# define FSHIFT 16
+# define LA_AVENRUN "avenrun"
+# if !defined(_AIX4) || _AIX4 < 40300
+# ifndef __BIT_TYPES_DEFINED__
+# define SM_INT32 int
+# endif /* __BIT_TYPES_DEFINED__ */
+# endif /* !defined(_AIX4) || _AIX4 < 40300 */
+# if !defined(_AIX4) || _AIX4 < 40200
+# define SM_CONF_SYSLOG 0
+# endif /* !defined(_AIX4) || _AIX4 < 40200 */
+# endif /* _AIX3 */
+
+
+/*
+** IBM AIX 2.2.1 -- actually tested for osupdate level 2706+1773
+**
+** From Mark Whetzel <markw@wg.waii.com>.
+*/
+
+# ifdef AIX /* AIX/RT compiler pre-defines this */
+# include <paths.h>
+# include <sys/time.h> /* AIX/RT resource.h does NOT include this */
+# define HASINITGROUPS 1 /* has initgroups(3) call */
+# define HASUNAME 1 /* use System V uname(2) system call */
+# define HASGETUSERSHELL 0 /* does not have getusershell(3) call */
+# define HASFCHMOD 0 /* does not have fchmod(2) syscall */
+# define HASSETREUID 1 /* use setreuid(2) -lbsd system call */
+# define HASSETVBUF 1 /* use setvbuf(2) system call */
+# define HASSETRLIMIT 0 /* does not have setrlimit call */
+# define HASFLOCK 0 /* does not have flock call - use fcntl */
+# define HASULIMIT 1 /* use ulimit instead of setrlimit call */
+# define SM_CONF_GETOPT 0 /* Do we need theirs or ours */
+# define SYS5SETPGRP 1 /* don't have setpgid on AIX/RT */
+# define IP_SRCROUTE 0 /* Something is broken with getsockopt() */
+# define BSD4_3 1 /* NOT bsd 4.4 or posix signals */
+# define GIDSET_T int
+# define SFS_TYPE SFS_STATFS /* use <sys/statfs.h> statfs() impl */
+# define SPT_PADCHAR '\0' /* pad process title with nulls */
+# define LA_TYPE LA_SUBR /* use our ported loadavgd daemon */
+# define TZ_TYPE TZ_TZNAME /* use tzname[] vector */
+# define ARBPTR_T int *
+# define void int
+typedef int pid_t;
+/* RTisms for BSD compatibility, specified in the Makefile
+ define BSD 1
+ define BSD_INCLUDES 1
+ define BSD_REMAP_SIGNAL_TO_SIGVEC
+ RTisms needed above */
+/* make this sendmail in a completely different place */
+# ifndef _PATH_VENDOR_CF
+# define _PATH_VENDOR_CF "/usr/local/newmail/sendmail.cf"
+# endif /* ! _PATH_VENDOR_CF */
+# ifndef _PATH_SENDMAILPID
+# define _PATH_SENDMAILPID "/usr/local/newmail/sendmail.pid"
+# endif /* ! _PATH_SENDMAILPID */
+# endif /* AIX */
+
+# if defined(_AIX)
+# define LDA_USE_LOCKF 1
+# define LDA_USE_SETEUID 1
+# endif /* defined(_AIX) */
+
+/*
+** Silicon Graphics IRIX
+**
+** Compiles on 4.0.1.
+**
+** Use IRIX64 instead of IRIX for 64-bit IRIX (6.0).
+** Use IRIX5 instead of IRIX for IRIX 5.x.
+**
+** IRIX64 changes from Mark R. Levinson <ml@cvdev.rochester.edu>.
+** IRIX5 changes from Kari E. Hurtta <Kari.Hurtta@fmi.fi>.
+*/
+
+# ifdef IRIX
+# define SYSTEM5 1 /* this is a System-V derived system */
+# define HASSETREUID 1 /* has setreuid(2) call */
+# define HASINITGROUPS 1 /* has initgroups(3) call */
+# define HASFCHMOD 1 /* has fchmod(2) syscall */
+# define HASGETUSERSHELL 0 /* does not have getusershell(3) call */
+# define IP_SRCROUTE 1 /* can check IP source routing */
+# define setpgid BSDsetpgrp
+# define GIDSET_T gid_t
+# define SFS_TYPE SFS_4ARGS /* four argument statfs() call */
+# define SFS_BAVAIL f_bfree /* alternate field name */
+# define SYSLOG_BUFSIZE 512
+# if defined(_SC_NPROC_ONLN) && !defined(_SC_NPROCESSORS_ONLN)
+ /* _SC_NPROC_ONLN is 'mpadmin -u', total # of unrestricted processors */
+# define _SC_NPROCESSORS_ONLN _SC_NPROC_ONLN
+# endif /* if defined(_SC_NPROC_ONLN) && !defined(_SC_NPROCESSORS_ONLN) */
+# ifdef IRIX6
+# define STAT64 1
+# define QUAD_T unsigned long long
+# define LA_TYPE LA_IRIX6 /* figure out at run time */
+# define SAFENFSPATHCONF 0 /* pathconf(2) lies on NFS filesystems */
+# else /* IRIX6 */
+# define LA_TYPE LA_INT
+
+# ifdef IRIX64
+# define STAT64 1
+# define QUAD_T unsigned long long
+# define NAMELISTMASK 0x7fffffffffffffff /* mask for nlist() values */
+# else /* IRIX64 */
+# define STAT64 0
+# define NAMELISTMASK 0x7fffffff /* mask for nlist() values */
+# endif /* IRIX64 */
+# endif /* IRIX6 */
+# if defined(IRIX64) || defined(IRIX5) || defined(IRIX6)
+# include <sys/cdefs.h>
+# include <paths.h>
+# define ARGV_T char *const *
+# define HASFCHOWN 1 /* has fchown(2) */
+# define HASSETRLIMIT 1 /* has setrlimit(2) syscall */
+# define HASGETDTABLESIZE 1 /* has getdtablesize(2) syscall */
+# define HASSTRERROR 1 /* has strerror(3) */
+# else /* defined(IRIX64) || defined(IRIX5) || defined(IRIX6) */
+# define ARGV_T const char **
+# define WAITUNION 1 /* use "union wait" as wait argument type */
+# endif /* defined(IRIX64) || defined(IRIX5) || defined(IRIX6) */
+# endif /* IRIX */
+
+
+/*
+** SunOS and Solaris
+**
+** Tested on SunOS 4.1.x (a.k.a. Solaris 1.1.x) and
+** Solaris 2.4 (a.k.a. SunOS 5.4).
+*/
+
+# if defined(sun) && !defined(BSD)
+
+# include <sys/time.h>
+# define HASINITGROUPS 1 /* has initgroups(3) call */
+# define HASUNAME 1 /* use System V uname(2) system call */
+# define HASFCHMOD 1 /* has fchmod(2) syscall */
+# define IP_SRCROUTE 1 /* can check IP source routing */
+# define SAFENFSPATHCONF 1 /* pathconf(2) pessimizes on NFS filesystems */
+# ifndef HASFCHOWN
+# define HASFCHOWN 1 /* fchown(2) */
+# endif /* ! HASFCHOWN */
+
+# ifdef __svr4__
+# define LDA_USE_LOCKF 1
+# define LDA_USE_SETEUID 1
+# define _PATH_MAILDIR "/var/mail"
+# endif /* __svr4__ */
+
+# ifdef SOLARIS_2_3
+# define SOLARIS 20300 /* for back compat only -- use -DSOLARIS=20300 */
+# endif /* SOLARIS_2_3 */
+
+# if defined(NOT_SENDMAIL) && !defined(SOLARIS) && defined(sun) && (defined(__svr4__) || defined(__SVR4))
+# define SOLARIS 1 /* unknown Solaris version */
+# endif /* defined(NOT_SENDMAIL) && !defined(SOLARIS) && defined(sun) && (defined(__svr4__) || defined(__SVR4)) */
+
+# ifdef SOLARIS
+ /* Solaris 2.x (a.k.a. SunOS 5.x) */
+# ifndef __svr4__
+# define __svr4__ /* use all System V Release 4 defines below */
+# endif /* ! __svr4__ */
+# define GIDSET_T gid_t
+# define USE_SA_SIGACTION 1 /* use sa_sigaction field */
+# define BROKEN_PTHREAD_SLEEP 1 /* sleep after pthread_create() fails */
+# define HASSTRERROR 1 /* has strerror(3) */
+# ifndef _PATH_UNIX
+# define _PATH_UNIX "/dev/ksyms"
+# endif /* ! _PATH_UNIX */
+# ifndef _PATH_VENDOR_CF
+# define _PATH_VENDOR_CF "/etc/mail/sendmail.cf"
+# endif /* ! _PATH_VENDOR_CF */
+# ifndef _PATH_SENDMAILPID
+# define _PATH_SENDMAILPID "/etc/mail/sendmail.pid"
+# endif /* ! _PATH_SENDMAILPID */
+# ifndef _PATH_HOSTS
+# define _PATH_HOSTS "/etc/inet/hosts"
+# endif /* ! _PATH_HOSTS */
+# ifndef SYSLOG_BUFSIZE
+# define SYSLOG_BUFSIZE 1024 /* allow full size syslog buffer */
+# endif /* ! SYSLOG_BUFSIZE */
+# ifndef TZ_TYPE
+# define TZ_TYPE TZ_TZNAME
+# endif /* ! TZ_TYPE */
+# if SOLARIS >= 20300 || (SOLARIS < 10000 && SOLARIS >= 203)
+# define USESETEUID 1 /* seteuid works as of 2.3 */
+# define LDA_CONTENTLENGTH 1 /* Needs the Content-Length header */
+# endif /* SOLARIS >= 20300 || (SOLARIS < 10000 && SOLARIS >= 203) */
+# if SOLARIS >= 20500 || (SOLARIS < 10000 && SOLARIS >= 205)
+# define HASSETREUID 1 /* setreuid works as of 2.5 */
+# define HASSETREGID 1 /* use setregid(2) to set saved gid */
+# if SOLARIS < 207 || (SOLARIS > 10000 && SOLARIS < 20700)
+# ifndef LA_TYPE
+# define LA_TYPE LA_KSTAT /* use kstat(3k) -- may work in < 2.5 */
+# endif /* ! LA_TYPE */
+# ifndef RANDOMSHIFT /* random() doesn't work well (sometimes) */
+# define RANDOMSHIFT 8
+# endif /* ! RANDOMSHIFT */
+# endif /* SOLARIS < 207 || (SOLARIS > 10000 && SOLARIS < 20700) */
+# else /* SOLARIS >= 20500 || (SOLARIS < 10000 && SOLARIS >= 205) */
+# ifndef HASRANDOM
+# define HASRANDOM 0 /* doesn't have random(3) */
+# endif /* ! HASRANDOM */
+# endif /* SOLARIS >= 20500 || (SOLARIS < 10000 && SOLARIS >= 205) */
+# if (SOLARIS > 10000 && SOLARIS < 20600) || SOLARIS < 206
+# define SM_INT32 int /* 32bit integer */
+# endif /* (SOLARIS > 10000 && SOLARIS < 20600) || SOLARIS < 206 */
+# if SOLARIS >= 20700 || (SOLARIS < 10000 && SOLARIS >= 207)
+# ifndef LA_TYPE
+# include <sys/loadavg.h>
+# if SOLARIS >= 20900 || (SOLARIS < 10000 && SOLARIS >= 209)
+# include <sys/pset.h>
+# define LA_TYPE LA_PSET /* pset_getloadavg(3c) appears in 2.9 */
+# else /* SOLARIS >= 20900 || (SOLARIS < 10000 && SOLARIS >= 209) */
+# define LA_TYPE LA_SUBR /* getloadavg(3c) appears in 2.7 */
+# endif /* SOLARIS >= 20900 || (SOLARIS < 10000 && SOLARIS >= 209) */
+# endif /* ! LA_TYPE */
+# define HASGETUSERSHELL 1 /* getusershell(3c) bug fixed in 2.7 */
+# endif /* SOLARIS >= 20700 || (SOLARIS < 10000 && SOLARIS >= 207) */
+# if SOLARIS >= 20800 || (SOLARIS < 10000 && SOLARIS >= 208)
+# undef _PATH_SENDMAILPID /* tmpfs /var/run added in 2.8 */
+# define _PATH_SENDMAILPID "/var/run/sendmail.pid"
+# ifndef SMRSH_CMDDIR
+# define SMRSH_CMDDIR "/var/adm/sm.bin"
+# endif /* ! SMRSH_CMDDIR */
+# define SL_FUDGE 34 /* fudge offset for SyslogPrefixLen */
+# define HASLDAPGETALIASBYNAME 1 /* added in S8 */
+# endif /* SOLARIS >= 20800 || (SOLARIS < 10000 && SOLARIS >= 208) */
+# if SOLARIS >= 20900 || (SOLARIS < 10000 && SOLARIS >= 209)
+# define HASURANDOMDEV 1 /* /dev/[u]random added in S9 */
+# define HASCLOSEFROM 1 /* closefrom(3c) added in S9 */
+# define HASFDWALK 1 /* fdwalk(3c) added in S9 */
+# endif /* SOLARIS >= 20900 || (SOLARIS < 10000 && SOLARIS >= 209) */
+# if SOLARIS >= 21000 || (SOLARIS < 10000 && SOLARIS >= 210)
+# define HASUNSETENV 1 /* unsetenv() added in S10 */
+# endif /* SOLARIS >= 21000 || (SOLARIS < 10000 && SOLARIS >= 210) */
+# ifndef HASGETUSERSHELL
+# define HASGETUSERSHELL 0 /* getusershell(3) causes core dumps pre-2.7 */
+# endif /* ! HASGETUSERSHELL */
+
+# else /* SOLARIS */
+ /* SunOS 4.0.3 or 4.1.x */
+# define HASGETUSERSHELL 1 /* DOES have getusershell(3) call in libc */
+# define HASSETREUID 1 /* has setreuid(2) call */
+# ifndef HASFLOCK
+# define HASFLOCK 1 /* has flock(2) call */
+# endif /* ! HASFLOCK */
+# define SFS_TYPE SFS_VFS /* use <sys/vfs.h> statfs() implementation */
+# define TZ_TYPE TZ_TM_ZONE /* use tm->tm_zone */
+# include <memory.h>
+# include <vfork.h>
+# ifdef __GNUC__
+# define strtoul strtol /* gcc library bogosity */
+# endif /* __GNUC__ */
+# define memmove(d, s, l) (bcopy((s), (d), (l)))
+# define atexit(f) on_exit((f), 0) /* ugly hack for SunOS */
+# define SM_INT32 int /* 32bit integer */
+# define SM_ALIGN_SIZE (sizeof(long))
+# define GIDSET_T int
+# define SM_CONF_SYSLOG 0
+
+# ifdef SUNOS403
+ /* special tweaking for SunOS 4.0.3 */
+# include <malloc.h>
+# define BSD4_3 1 /* 4.3 BSD-based */
+# define NEEDSTRSTR 1 /* need emulation of strstr(3) routine */
+# define WAITUNION 1 /* use "union wait" as wait argument type */
+# undef WIFEXITED
+# undef WEXITSTATUS
+# undef HASUNAME
+# define setpgid setpgrp
+# define MODE_T int
+typedef int pid_t;
+extern char *getenv();
+
+# else /* SUNOS403 */
+ /* 4.1.x specifics */
+# define HASSETSID 1 /* has POSIX setsid(2) call */
+# define HASSETVBUF 1 /* we have setvbuf(3) in libc */
+
+# endif /* SUNOS403 */
+# endif /* SOLARIS */
+
+# ifndef LA_TYPE
+# define LA_TYPE LA_INT
+# endif /* ! LA_TYPE */
+
+# endif /* defined(sun) && !defined(BSD) */
+
+/*
+** DG/UX
+**
+** Tested on 5.4.2 and 5.4.3. Use DGUX_5_4_2 to get the
+** older support.
+** 5.4.3 changes from Mark T. Robinson <mtr@ornl.gov>.
+*/
+
+# ifdef DGUX_5_4_2
+# define DGUX 1
+# endif /* DGUX_5_4_2 */
+
+# ifdef DGUX
+# define SYSTEM5 1
+# define LA_TYPE LA_DGUX
+# define HASSETREUID 1 /* has setreuid(2) call */
+# define HASUNAME 1 /* use System V uname(2) system call */
+# define HASSETSID 1 /* has POSIX setsid(2) call */
+# define HASINITGROUPS 1 /* has initgroups(3) call */
+# define IP_SRCROUTE 0 /* does not have <netinet/ip_var.h> */
+# define HASGETUSERSHELL 0 /* does not have getusershell(3) */
+# ifndef IDENTPROTO
+# define IDENTPROTO 0 /* TCP/IP implementation is broken */
+# endif /* ! IDENTPROTO */
+# define SPT_TYPE SPT_NONE /* don't use setproctitle */
+# define SFS_TYPE SFS_4ARGS /* four argument statfs() call */
+# define LDA_USE_LOCKF 1
+
+/* these include files must be included early on DG/UX */
+# include <netinet/in.h>
+# include <arpa/inet.h>
+
+/* compiler doesn't understand const? */
+# define const
+
+# ifdef DGUX_5_4_2
+# define inet_addr dgux_inet_addr
+extern long dgux_inet_addr();
+# endif /* DGUX_5_4_2 */
+# endif /* DGUX */
+
+
+/*
+** Digital Ultrix 4.2 - 4.5
+**
+** Apparently, fcntl locking is broken on 4.2A, in that locks are
+** not dropped when the process exits. This causes major problems,
+** so flock is the only alternative.
+*/
+
+# ifdef ultrix
+# define HASSETREUID 1 /* has setreuid(2) call */
+# define HASUNSETENV 1 /* has unsetenv(3) call */
+# define HASINITGROUPS 1 /* has initgroups(3) call */
+# define HASUNAME 1 /* use System V uname(2) system call */
+# define HASFCHMOD 1 /* has fchmod(2) syscall */
+# define HASFCHOWN 1 /* has fchown(2) syscall */
+# ifndef HASFLOCK
+# define HASFLOCK 1 /* has flock(2) call */
+# endif /* ! HASFLOCK */
+# define HASGETUSERSHELL 0 /* does not have getusershell(3) call */
+# ifndef BROKEN_RES_SEARCH
+# define BROKEN_RES_SEARCH 1 /* res_search(unknown) returns h_errno=0 */
+# endif /* ! BROKEN_RES_SEARCH */
+# if !defined(NEEDLOCAL_HOSTNAME_LENGTH) && NAMED_BIND && __RES >= 19931104 && __RES < 19950621
+# define NEEDLOCAL_HOSTNAME_LENGTH 1 /* see sendmail/README */
+# endif /* !defined(NEEDLOCAL_HOSTNAME_LENGTH) && NAMED_BIND && __RES >= 19931104 && __RES < 19950621 */
+# ifdef vax
+# define LA_TYPE LA_FLOAT
+# else /* vax */
+# define LA_TYPE LA_INT
+# define LA_AVENRUN "avenrun"
+# endif /* vax */
+# define SFS_TYPE SFS_MOUNT /* use <sys/mount.h> statfs() impl */
+# ifndef IDENTPROTO
+# define IDENTPROTO 0 /* pre-4.4 TCP/IP implementation is broken */
+# endif /* ! IDENTPROTO */
+# define SYSLOG_BUFSIZE 256
+# define SM_CONF_SYSLOG 0
+# endif /* ultrix */
+
+
+/*
+** OSF/1 for KSR.
+**
+** Contributed by Todd C. Miller <Todd.Miller@cs.colorado.edu>
+*/
+
+# ifdef __ksr__
+# define __osf__ 1 /* get OSF/1 defines below */
+# ifndef TZ_TYPE
+# define TZ_TYPE TZ_TZNAME /* use tzname[] vector */
+# endif /* ! TZ_TYPE */
+# endif /* __ksr__ */
+
+
+/*
+** OSF/1 for Intel Paragon.
+**
+** Contributed by Jeff A. Earickson <jeff@ssd.intel.com>
+** of Intel Scalable Systems Divison.
+*/
+
+# ifdef __PARAGON__
+# define __osf__ 1 /* get OSF/1 defines below */
+# ifndef TZ_TYPE
+# define TZ_TYPE TZ_TZNAME /* use tzname[] vector */
+# endif /* ! TZ_TYPE */
+# define GIDSET_T gid_t
+# define MAXNAMLEN NAME_MAX
+# endif /* __PARAGON__ */
+
+
+/*
+** Tru64 UNIX, formerly known as Digital UNIX, formerly known as DEC OSF/1
+**
+** Tested for 3.2 and 4.0.
+*/
+
+# ifdef __osf__
+# define HASUNAME 1 /* has uname(2) call */
+# define HASUNSETENV 1 /* has unsetenv(3) call */
+# define USESETEUID 1 /* has usable seteuid(2) call */
+# define HASINITGROUPS 1 /* has initgroups(3) call */
+# define HASFCHMOD 1 /* has fchmod(2) syscall */
+# define HASFCHOWN 1 /* has fchown(2) syscall */
+# define HASSETLOGIN 1 /* has setlogin(2) */
+# define IP_SRCROUTE 1 /* can check IP source routing */
+# define HAS_ST_GEN 1 /* has st_gen field in stat struct */
+# define GIDSET_T gid_t
+# define SM_INT32 int /* 32bit integer */
+# ifndef HASFLOCK
+# include <standards.h>
+# if _XOPEN_SOURCE+0 >= 400
+# define HASFLOCK 0 /* 5.0 and later has bad flock(2) call */
+# else /* _XOPEN_SOURCE+0 >= 400 */
+# define HASFLOCK 1 /* has flock(2) call */
+# endif /* _XOPEN_SOURCE+0 >= 400 */
+# endif /* ! HASFLOCK */
+# define LA_TYPE LA_ALPHAOSF
+# define SFS_TYPE SFS_STATVFS /* use <sys/statvfs.h> statfs() impl */
+# ifndef _PATH_VENDOR_CF
+# define _PATH_VENDOR_CF "/var/adm/sendmail/sendmail.cf"
+# endif /* ! _PATH_VENDOR_CF */
+# ifndef _PATH_SENDMAILPID
+# define _PATH_SENDMAILPID "/var/run/sendmail.pid"
+# endif /* ! _PATH_SENDMAILPID */
+# if _FFR_DIGUNIX_SAFECHOWN
+/*
+** Testing on a Digital UNIX 4.0a system showed this to be the correct
+** setting but given the security consequences, more testing and
+** verification is needed. Unfortunately, the man page offers no
+** assistance.
+*/
+# define IS_SAFE_CHOWN >= 0
+# endif /* _FFR_DIGUNIX_SAFECHOWN */
+# endif /* __osf__ */
+
+
+/*
+** NeXTstep
+*/
+
+# ifdef NeXT
+# define HASINITGROUPS 1 /* has initgroups(3) call */
+# define NEEDPUTENV 2 /* need putenv(3) call; no setenv(3) call */
+# ifndef HASFLOCK
+# define HASFLOCK 1 /* has flock(2) call */
+# endif /* ! HASFLOCK */
+# define UID_T int /* compiler gripes on uid_t */
+# define GID_T int /* ditto for gid_t */
+# define MODE_T int /* and mode_t */
+# define setpgid setpgrp
+# ifndef NOT_SENDMAIL
+# define sleep sleepX
+# endif /* ! NOT_SENDMAIL */
+# ifndef LA_TYPE
+# define LA_TYPE LA_MACH
+# endif /* ! LA_TYPE */
+# define SFS_TYPE SFS_VFS /* use <sys/vfs.h> statfs() implementation */
+# ifdef _POSIX_SOURCE
+extern struct passwd *getpwent();
+# else /* _POSIX_SOURCE */
+# define SM_CONF_GETOPT 0 /* need a replacement for getopt(3) */
+# define WAITUNION 1 /* use "union wait" as wait argument type */
+typedef int pid_t;
+# undef WEXITSTATUS
+# undef WIFEXITED
+# undef WIFSTOPPED
+# undef WTERMSIG
+# endif /* _POSIX_SOURCE */
+# ifndef _PATH_VENDOR_CF
+# define _PATH_VENDOR_CF "/etc/sendmail/sendmail.cf"
+# endif /* ! _PATH_VENDOR_CF */
+# ifndef _PATH_SENDMAILPID
+# define _PATH_SENDMAILPID "/etc/sendmail/sendmail.pid"
+# endif /* ! _PATH_SENDMAILPID */
+# define SM_INT32 int /* 32bit integer */
+
+# ifdef TCPWRAPPERS
+# ifndef HASUNSETENV
+# define HASUNSETENV 1
+# endif /* ! HASUNSETENV */
+# undef NEEDPUTENV
+# endif /* TCPWRAPPERS */
+# ifndef __APPLE__
+# include <libc.h>
+# ifndef S_IRUSR
+# define S_IRUSR S_IREAD
+# endif /* ! S_IRUSR */
+# ifndef S_IWUSR
+# define S_IWUSR S_IWRITE
+# endif /* ! S_IWUSR */
+# define _PATH_MAILDIR "/usr/spool/mail"
+# endif /* ! __APPLE__ */
+# ifndef isascii
+# define isascii(c) ((unsigned)(c) <= 0177)
+# endif /* ! isascii */
+# endif /* NeXT */
+
+/*
+** Apple Darwin
+** Contributed by Wilfredo Sanchez <wsanchez@mit.edu>
+*/
+
+# if defined(DARWIN)
+# define HASFCHMOD 1 /* has fchmod(2) */
+# define HASFCHOWN 1 /* has fchown(2) */
+# define HASFLOCK 1 /* has flock(2) */
+# define HASUNAME 1 /* has uname(2) */
+# define HASUNSETENV 1 /* has unsetenv(3) */
+# define HASSETSID 1 /* has POSIX setsid(2) call */
+# define HASINITGROUPS 1 /* has initgroups(3) */
+# define HASSETVBUF 1 /* has setvbuf (3) */
+# define HASSETREUID 0 /* setreuid(2) unusable */
+# define HASSETEUID 1 /* has seteuid(2) */
+# define USESETEUID 1 /* has seteuid(2) */
+# define HASSETEGID 1 /* has setegid(2) */
+# define HASSETREGID 1 /* has setregid(2) */
+# define HASSETRESGID 0 /* no setresgid(2) */
+# define HASLSTAT 1 /* has lstat(2) */
+# define HASSETRLIMIT 1 /* has setrlimit(2) */
+# define HASWAITPID 1 /* has waitpid(2) */
+# define HASGETDTABLESIZE 1 /* has getdtablesize(2) */
+# define HAS_ST_GEN 1 /* has st_gen field in struct stat */
+# define HASURANDOMDEV 1 /* has urandom(4) */
+# define HASSTRERROR 1 /* has strerror(3) */
+# define HASGETUSERSHELL 1 /* had getusershell(3) */
+# define GIDSET_T gid_t /* getgroups(2) takes gid_t */
+# define LA_TYPE LA_SUBR /* use getloadavg(3) */
+# define SFS_TYPE SFS_MOUNT /* use <sys/mount.h> statfs() impl */
+# define SPT_TYPE SPT_PSSTRINGS /* use magic PS_STRINGS pointer for setproctitle */
+# define ERRLIST_PREDEFINED /* don't declare sys_errlist */
+# define BSD4_4_SOCKADDR /* struct sockaddr has sa_len */
+# define SAFENFSPATHCONF 0 /* unverified: pathconf(2) doesn't work on NFS */
+# define HAS_IN_H 1
+# define NETLINK 1 /* supports AF_LINK */
+# ifndef NOT_SENDMAIL
+# define sleep sleepX
+extern unsigned int sleepX __P((unsigned int seconds));
+# endif /* ! NOT_SENDMAIL */
+# endif /* defined(DARWIN) */
+
+
+/*
+** 4.4 BSD
+**
+** See also BSD defines.
+*/
+
+# if defined(BSD4_4) && !defined(__bsdi__) && !defined(__GNU__)
+# include <paths.h>
+# define HASUNSETENV 1 /* has unsetenv(3) call */
+# define USESETEUID 1 /* has usable seteuid(2) call */
+# define HASFCHMOD 1 /* has fchmod(2) syscall */
+# define HASFCHOWN 1 /* has fchown(2) syscall */
+# define HASSTRERROR 1 /* has strerror(3) */
+# define HAS_ST_GEN 1 /* has st_gen field in stat struct */
+# include <sys/cdefs.h>
+# define ERRLIST_PREDEFINED /* don't declare sys_errlist */
+# define BSD4_4_SOCKADDR /* has sa_len */
+# define NEED_PRINTF_PERCENTQ 1 /* doesn't have %lld */
+# define NETLINK 1 /* supports AF_LINK */
+# ifndef LA_TYPE
+# define LA_TYPE LA_SUBR
+# endif /* ! LA_TYPE */
+# define SFS_TYPE SFS_MOUNT /* use <sys/mount.h> statfs() impl */
+# define SPT_TYPE SPT_PSSTRINGS /* use PS_STRINGS pointer */
+# endif /* defined(BSD4_4) && !defined(__bsdi__) && !defined(__GNU__) */
+
+
+/*
+** BSD/OS (was BSD/386) (all versions)
+** From Tony Sanders, BSDI
+*/
+
+# ifdef __bsdi__
+# include <paths.h>
+# define HASUNSETENV 1 /* has the unsetenv(3) call */
+# define HASSETREUID 0 /* BSD-OS has broken setreuid(2) emulation */
+# define HASSETSID 1 /* has POSIX setsid(2) call */
+# define USESETEUID 1 /* has usable seteuid(2) call */
+# define HASFCHMOD 1 /* has fchmod(2) syscall */
+# define HASSETLOGIN 1 /* has setlogin(2) */
+# define HASUNAME 1 /* has uname(2) syscall */
+# define HASSTRERROR 1 /* has strerror(3) */
+# define HAS_ST_GEN 1 /* has st_gen field in stat struct */
+# include <sys/cdefs.h>
+# define ERRLIST_PREDEFINED /* don't declare sys_errlist */
+# define BSD4_4_SOCKADDR /* has sa_len */
+# define NETLINK 1 /* supports AF_LINK */
+# define SFS_TYPE SFS_MOUNT /* use <sys/mount.h> statfs() impl */
+# ifndef LA_TYPE
+# define LA_TYPE LA_SUBR
+# endif /* ! LA_TYPE */
+# define GIDSET_T gid_t
+# define QUAD_T quad_t
+# if defined(_BSDI_VERSION) && _BSDI_VERSION >= 199312
+ /* version 1.1 or later */
+# undef SPT_TYPE
+# define SPT_TYPE SPT_BUILTIN /* setproctitle is in libc */
+# else /* defined(_BSDI_VERSION) && _BSDI_VERSION >= 199312 */
+ /* version 1.0 or earlier */
+# define SPT_PADCHAR '\0' /* pad process title with nulls */
+# endif /* defined(_BSDI_VERSION) && _BSDI_VERSION >= 199312 */
+# if defined(_BSDI_VERSION) && _BSDI_VERSION >= 199701 /* on 3.x */
+# define HASSETUSERCONTEXT 1 /* has setusercontext */
+# endif /* defined(_BSDI_VERSION) && _BSDI_VERSION >= 199701 */
+# if defined(_BSDI_VERSION) && _BSDI_VERSION <= 199701 /* 3.1 and earlier */
+# define MODE_T int /* va_arg() can't handle less than int */
+# endif /* defined(_BSDI_VERSION) && _BSDI_VERSION <= 199701 */
+# if defined(_BSDI_VERSION) && _BSDI_VERSION >= 199910 /* on 4.x */
+# define HASURANDOMDEV 1 /* has /dev/urandom(4) */
+# endif /* defined(_BSDI_VERSION) && _BSDI_VERSION >= 199910 */
+# endif /* __bsdi__ */
+
+
+/*
+** QNX 4.2x
+** Contributed by Glen McCready <glen@qnx.com>.
+**
+** Should work with all versions of QNX.
+*/
+
+# if defined(__QNX__)
+# include <unix.h>
+# include <sys/select.h>
+# undef NGROUPS_MAX
+# define HASSETSID 1 /* has POSIX setsid(2) call */
+# define USESETEUID 1 /* has usable seteuid(2) call */
+# define HASFCHMOD 1 /* has fchmod(2) syscall */
+# define HASGETDTABLESIZE 1 /* has getdtablesize(2) call */
+# define HASSETREUID 1 /* has setreuid(2) call */
+# define HASSTRERROR 1 /* has strerror(3) */
+# define HASFLOCK 0
+# undef HASINITGROUPS /* has initgroups(3) call */
+# define SM_CONF_GETOPT 0 /* need a replacement for getopt(3) */
+# define IP_SRCROUTE 1 /* can check IP source routing */
+# define TZ_TYPE TZ_TMNAME /* use tmname variable */
+# define GIDSET_T gid_t
+# define LA_TYPE LA_ZERO
+# define SFS_TYPE SFS_NONE
+# define SPT_TYPE SPT_REUSEARGV
+# define SPT_PADCHAR '\0' /* pad process title with nulls */
+# define HASGETUSERSHELL 0
+# define E_PSEUDOBASE 512
+# define _FILE_H_INCLUDED
+# endif /* defined(__QNX__) */
+
+
+/*
+** DragonFly BSD/ FreeBSD / NetBSD / OpenBSD (all architectures, all versions)
+**
+** 4.3BSD clone, closer to 4.4BSD for FreeBSD 1.x and NetBSD 0.9x
+** 4.4BSD-Lite based for FreeBSD 2.x and NetBSD 1.x
+**
+** See also BSD defines.
+*/
+
+# if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
+# include <paths.h>
+# define HASUNSETENV 1 /* has unsetenv(3) call */
+# define HASSETSID 1 /* has POSIX setsid(2) call */
+# define USESETEUID 1 /* has usable seteuid(2) call */
+# define HASFCHMOD 1 /* has fchmod(2) syscall */
+# define HASFCHOWN 1 /* has fchown(2) syscall */
+# define HASUNAME 1 /* has uname(2) syscall */
+# define HASSTRERROR 1 /* has strerror(3) */
+# define HAS_ST_GEN 1 /* has st_gen field in stat struct */
+# define NEED_PRINTF_PERCENTQ 1 /* doesn't have %lld */
+# include <sys/cdefs.h>
+# define ERRLIST_PREDEFINED /* don't declare sys_errlist */
+# define BSD4_4_SOCKADDR /* has sa_len */
+# define NETLINK 1 /* supports AF_LINK */
+# define SAFENFSPATHCONF 1 /* pathconf(2) pessimizes on NFS filesystems */
+# define GIDSET_T gid_t
+# define QUAD_T unsigned long long
+# ifndef LA_TYPE
+# define LA_TYPE LA_SUBR
+# endif /* ! LA_TYPE */
+# if defined(__NetBSD__) && defined(__NetBSD_Version__) && __NetBSD_Version__ >= 200040000
+# undef SFS_TYPE
+# define SFS_TYPE SFS_STATVFS
+# else
+# define SFS_TYPE SFS_MOUNT /* use <sys/mount.h> statfs() impl */
+# endif
+# if defined(__NetBSD__) && (NetBSD > 199307 || NetBSD0_9 > 1)
+# undef SPT_TYPE
+# define SPT_TYPE SPT_BUILTIN /* setproctitle is in libc */
+# endif /* defined(__NetBSD__) && (NetBSD > 199307 || NetBSD0_9 > 1) */
+# if defined(__NetBSD__) && ((__NetBSD_Version__ > 102070000) || (NetBSD1_2 > 8) || defined(NetBSD1_4) || defined(NetBSD1_3))
+# define HASURANDOMDEV 1 /* has /dev/urandom(4) */
+# endif /* defined(__NetBSD__) && ((__NetBSD_Version__ > 102070000) || (NetBSD1_2 > 8) || defined(NetBSD1_4) || defined(NetBSD1_3)) */
+# if defined(__NetBSD__) && defined(__NetBSD_Version__) && __NetBSD_Version__ >= 104170000
+# define HASSETUSERCONTEXT 1 /* BSDI-style login classes */
+# endif
+# if defined(__NetBSD__) && defined(__NetBSD_Version__) && __NetBSD_Version__ >= 200060000
+# define HASCLOSEFROM 1 /* closefrom(3) added in 2.0F */
+# endif
+# if defined(__NetBSD__)
+# define USESYSCTL 1 /* use sysctl(3) for getting ncpus */
+# include <sys/param.h>
+# include <sys/sysctl.h>
+# endif
+# if defined(__DragonFly__)
+# define HASSETLOGIN 1 /* has setlogin(2) */
+# define HASSRANDOMDEV 1 /* has srandomdev(3) */
+# define HASURANDOMDEV 1 /* has /dev/urandom(4) */
+# undef SPT_TYPE
+# include <libutil.h>
+# define SPT_TYPE SPT_BUILTIN
+# define HASSETUSERCONTEXT 1 /* BSDI-style login classes */
+# ifndef SMRSH_CMDDIR
+# define SMRSH_CMDDIR "/usr/libexec/sm.bin"
+# endif /* ! SMRSH_CMDDIR */
+# ifndef SMRSH_PATH
+# define SMRSH_PATH "/bin:/usr/bin"
+# endif /* ! SMRSH_PATH */
+# define USESYSCTL 1 /* use sysctl(3) for getting ncpus */
+# include <sys/sysctl.h>
+# endif /* defined(__DragonFly__) */
+# if defined(__FreeBSD__)
+# define HASSETLOGIN 1 /* has setlogin(2) */
+# if __FreeBSD_version >= 227001
+# define HASSRANDOMDEV 1 /* has srandomdev(3) */
+# define HASURANDOMDEV 1 /* has /dev/urandom(4) */
+# endif /* __FreeBSD_version >= 227001 */
+# undef SPT_TYPE
+# if __FreeBSD__ >= 2
+# include <osreldate.h>
+# if __FreeBSD_version >= 199512 /* 2.2-current when it appeared */
+# include <libutil.h>
+# define SPT_TYPE SPT_BUILTIN
+# endif /* __FreeBSD_version >= 199512 */
+# if __FreeBSD_version >= 222000 /* 2.2.2-release and later */
+# define HASSETUSERCONTEXT 1 /* BSDI-style login classes */
+# endif /* __FreeBSD_version >= 222000 */
+# if __FreeBSD_version >= 330000 /* 3.3.0-release and later */
+# ifndef SMRSH_CMDDIR
+# define SMRSH_CMDDIR "/usr/libexec/sm.bin"
+# endif /* ! SMRSH_CMDDIR */
+# ifndef SMRSH_PATH
+# define SMRSH_PATH "/bin:/usr/bin"
+# endif /* ! SMRSH_PATH */
+# endif /* __FreeBSD_version >= 330000 */
+# define USESYSCTL 1 /* use sysctl(3) for getting ncpus */
+# include <sys/sysctl.h>
+# endif /* __FreeBSD__ >= 2 */
+# ifndef SPT_TYPE
+# define SPT_TYPE SPT_REUSEARGV
+# define SPT_PADCHAR '\0' /* pad process title with nulls */
+# endif /* ! SPT_TYPE */
+# endif /* defined(__FreeBSD__) */
+# if defined(__OpenBSD__)
+# undef SPT_TYPE
+# define SPT_TYPE SPT_BUILTIN /* setproctitle is in libc */
+# define HASSETLOGIN 1 /* has setlogin(2) */
+# if OpenBSD < 200305
+# define HASSETREUID 0 /* setreuid(2) broken in OpenBSD < 3.3 */
+# endif /* OpenBSD < 200305 */
+# define HASSETEGID 1 /* use setegid(2) to set saved gid */
+# define HASURANDOMDEV 1 /* has /dev/urandom(4) */
+# if OpenBSD >= 200006
+# define HASSRANDOMDEV 1 /* has srandomdev(3) */
+# endif /* OpenBSD >= 200006 */
+# if OpenBSD >= 200012
+# define HASSETUSERCONTEXT 1 /* BSDI-style login classes */
+# endif /* OpenBSD >= 200012 */
+# if OpenBSD >= 200405
+# define HASCLOSEFROM 1 /* closefrom(3) added in 3.5 */
+# endif /* OpenBSD >= 200405 */
+# endif /* defined(__OpenBSD__) */
+# endif /* defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) */
+
+
+/*
+** Mach386
+**
+** For mt Xinu's Mach386 system.
+*/
+
+# if defined(MACH) && defined(i386) && !defined(__GNU__)
+# define MACH386 1
+# define HASUNSETENV 1 /* has unsetenv(3) call */
+# define HASINITGROUPS 1 /* has initgroups(3) call */
+# ifndef HASFLOCK
+# define HASFLOCK 1 /* has flock(2) call */
+# endif /* ! HASFLOCK */
+# define SM_CONF_GETOPT 0 /* need a replacement for getopt(3) */
+# define NEEDSTRTOL 1 /* need the strtol() function */
+# define setpgid setpgrp
+# ifndef LA_TYPE
+# define LA_TYPE LA_FLOAT
+# endif /* ! LA_TYPE */
+# define SFS_TYPE SFS_VFS /* use <sys/vfs.h> statfs() implementation */
+# undef HASSETVBUF /* don't actually have setvbuf(3) */
+# undef WEXITSTATUS
+# undef WIFEXITED
+# ifndef _PATH_VENDOR_CF
+# define _PATH_VENDOR_CF "/usr/lib/sendmail.cf"
+# endif /* ! _PATH_VENDOR_CF */
+# ifndef _PATH_SENDMAILPID
+# define _PATH_SENDMAILPID "/etc/sendmail.pid"
+# endif /* ! _PATH_SENDMAILPID */
+# endif /* defined(MACH) && defined(i386) && !defined(__GNU__) */
+
+
+
+/*
+** GNU OS (hurd)
+** Largely BSD & posix compatible.
+** Port contributed by Miles Bader <miles@gnu.ai.mit.edu>.
+** Updated by Mark Kettenis <kettenis@wins.uva.nl>.
+*/
+
+# if defined(__GNU__) && !defined(NeXT)
+# include <paths.h>
+# define HASFCHMOD 1 /* has fchmod(2) call */
+# define HASFCHOWN 1 /* has fchown(2) call */
+# define HASUNAME 1 /* has uname(2) call */
+# define HASUNSETENV 1 /* has unsetenv(3) call */
+# define HAS_ST_GEN 1 /* has st_gen field in stat struct */
+# define HASSTRERROR 1 /* has strerror(3) */
+# define GIDSET_T gid_t
+# define SOCKADDR_LEN_T socklen_t
+# define SOCKOPT_LEN_T socklen_t
+# if (__GLIBC__ == 2 && __GLIBC_MINOR__ > 1) || __GLIBC__ > 2
+# define LA_TYPE LA_SUBR
+# else /* (__GLIBC__ == 2 && __GLIBC_MINOR__ > 1) || __GLIBC__ > 2 */
+# define LA_TYPE LA_MACH
+ /* GNU uses mach[34], which renames some rpcs from mach2.x. */
+# define host_self mach_host_self
+# endif /* (__GLIBC__ == 2 && __GLIBC_MINOR__ > 1) || __GLIBC__ > 2 */
+# define SFS_TYPE SFS_STATFS
+# define SPT_TYPE SPT_CHANGEARGV
+# define ERRLIST_PREDEFINED 1 /* don't declare sys_errlist */
+# define BSD4_4_SOCKADDR 1 /* has sa_len */
+# define SIOCGIFCONF_IS_BROKEN 1 /* SIOCGFCONF doesn't work */
+# define HAS_IN_H 1 /* GNU has netinet/in.h. */
+/* GNU has no MAXPATHLEN; ideally the code should be changed to not use it. */
+# define MAXPATHLEN 2048
+# endif /* defined(__GNU__) && !defined(NeXT) */
+
+/*
+** 4.3 BSD -- this is for very old systems
+**
+** Should work for mt Xinu MORE/BSD and Mips UMIPS-BSD 2.1.
+**
+** You'll also have to install a new resolver library.
+** I don't guarantee that support for this environment is complete.
+*/
+
+# if defined(oldBSD43) || defined(MORE_BSD) || defined(umipsbsd)
+# define NEEDVPRINTF 1 /* need a replacement for vprintf(3) */
+# define SM_CONF_GETOPT 0 /* need a replacement for getopt(3) */
+# define ARBPTR_T char *
+# define setpgid setpgrp
+# ifndef LA_TYPE
+# define LA_TYPE LA_FLOAT
+# endif /* ! LA_TYPE */
+# ifndef _PATH_VENDOR_CF
+# define _PATH_VENDOR_CF "/usr/lib/sendmail.cf"
+# endif /* ! _PATH_VENDOR_CF */
+# ifndef IDENTPROTO
+# define IDENTPROTO 0 /* TCP/IP implementation is broken */
+# endif /* ! IDENTPROTO */
+# undef WEXITSTATUS
+# undef WIFEXITED
+typedef short pid_t;
+# endif /* defined(oldBSD43) || defined(MORE_BSD) || defined(umipsbsd) */
+
+
+/*
+** SCO Unix
+**
+** This includes three parts:
+**
+** The first is for SCO OpenServer 5.
+** (Contributed by Keith Reynolds <keithr@sco.COM>).
+**
+** SCO OpenServer 5 has a compiler version number macro,
+** which we can use to figure out what version we're on.
+** This may have to change in future releases.
+**
+** The second is for SCO UNIX 3.2v4.2/Open Desktop 3.0.
+** (Contributed by Philippe Brand <phb@colombo.telesys-innov.fr>).
+**
+** The third is for SCO UNIX 3.2v4.0/Open Desktop 2.0 and earlier.
+*/
+
+/* SCO OpenServer 5 */
+# if _SCO_DS >= 1
+# include <paths.h>
+# define SIOCGIFNUM_IS_BROKEN 1 /* SIOCGIFNUM returns bogus value */
+# define HASFCHMOD 1 /* has fchmod(2) call */
+# define HASFCHOWN 1 /* has fchown(2) call */
+# define HASSETRLIMIT 1 /* has setrlimit(2) call */
+# define USESETEUID 1 /* has seteuid(2) call */
+# define HASINITGROUPS 1 /* has initgroups(3) call */
+# define HASGETDTABLESIZE 1 /* has getdtablesize(2) call */
+# define RLIMIT_NEEDS_SYS_TIME_H 1
+# define LDA_USE_LOCKF 1
+# ifndef LA_TYPE
+# define LA_TYPE LA_DEVSHORT
+# endif /* ! LA_TYPE */
+# define _PATH_AVENRUN "/dev/table/avenrun"
+# ifndef _SCO_unix_4_2
+# define _SCO_unix_4_2
+# else /* ! _SCO_unix_4_2 */
+# define SOCKADDR_LEN_T size_t /* e.g., arg#3 to accept, getsockname */
+# define SOCKOPT_LEN_T size_t /* arg#5 to getsockopt */
+# endif /* ! _SCO_unix_4_2 */
+# endif /* _SCO_DS >= 1 */
+
+/* SCO UNIX 3.2v4.2/Open Desktop 3.0 */
+# ifdef _SCO_unix_4_2
+# define _SCO_unix_
+# define HASSETREUID 1 /* has setreuid(2) call */
+# endif /* _SCO_unix_4_2 */
+
+/* SCO UNIX 3.2v4.0 Open Desktop 2.0 and earlier */
+# ifdef _SCO_unix_
+# include <sys/stream.h> /* needed for IP_SRCROUTE */
+# define SYSTEM5 1 /* include all the System V defines */
+# define HASGETUSERSHELL 0 /* does not have getusershell(3) call */
+# define NOFTRUNCATE 0 /* has (simulated) ftruncate call */
+# ifndef USE_SIGLONGJMP
+# define USE_SIGLONGJMP 1 /* sigsetjmp needed for signal handling */
+# endif /* ! USE_SIGLONGJMP */
+# define MAXPATHLEN PATHSIZE
+# define SFS_TYPE SFS_4ARGS /* use <sys/statfs.h> 4-arg impl */
+# define SFS_BAVAIL f_bfree /* alternate field name */
+# define SPT_TYPE SPT_SCO /* write kernel u. area */
+# define TZ_TYPE TZ_TM_NAME /* use tm->tm_name */
+# define UID_T uid_t
+# define GID_T gid_t
+# define GIDSET_T gid_t
+# define _PATH_UNIX "/unix"
+# ifndef _PATH_VENDOR_CF
+# define _PATH_VENDOR_CF "/usr/lib/sendmail.cf"
+# endif /* ! _PATH_VENDOR_CF */
+# ifndef _PATH_SENDMAILPID
+# define _PATH_SENDMAILPID "/etc/sendmail.pid"
+# endif /* ! _PATH_SENDMAILPID */
+
+/* stuff fixed in later releases */
+# ifndef _SCO_unix_4_2
+# define SYS5SIGNALS 1 /* SysV signal semantics -- reset on each sig */
+# endif /* ! _SCO_unix_4_2 */
+
+# ifndef _SCO_DS
+# define ftruncate chsize /* use chsize(2) to emulate ftruncate */
+# define NEEDFSYNC 1 /* needs the fsync(2) call stub */
+# define NETUNIX 0 /* no unix domain socket support */
+# define LA_TYPE LA_SHORT
+# endif /* ! _SCO_DS */
+
+# endif /* _SCO_unix_ */
+
+/*
+** ISC (SunSoft) Unix.
+**
+** Contributed by J.J. Bailey <jjb@jagware.bcc.com>
+*/
+
+# ifdef ISC_UNIX
+# include <net/errno.h>
+# include <sys/stream.h> /* needed for IP_SRCROUTE */
+# include <sys/bsdtypes.h>
+# define SYSTEM5 1 /* include all the System V defines */
+# define SYS5SIGNALS 1 /* SysV signal semantics -- reset on each sig */
+# define HASGETUSERSHELL 0 /* does not have getusershell(3) call */
+# define HASSETREUID 1 /* has setreuid(2) call */
+# define NEEDFSYNC 1 /* needs the fsync(2) call stub */
+# define NETUNIX 0 /* no unix domain socket support */
+# define MAXPATHLEN 1024
+# define LA_TYPE LA_SHORT
+# define SFS_TYPE SFS_STATFS /* use <sys/statfs.h> statfs() impl */
+# define SFS_BAVAIL f_bfree /* alternate field name */
+# define _PATH_UNIX "/unix"
+# ifndef _PATH_VENDOR_CF
+# define _PATH_VENDOR_CF "/usr/lib/sendmail.cf"
+# endif /* ! _PATH_VENDOR_CF */
+# ifndef _PATH_SENDMAILPID
+# define _PATH_SENDMAILPID "/etc/sendmail.pid"
+# endif /* ! _PATH_SENDMAILPID */
+# endif /* ISC_UNIX */
+
+
+/*
+** Altos System V (5.3.1)
+** Contributed by Tim Rice <tim@trr.metro.net>.
+*/
+
+# ifdef ALTOS_SYSTEM_V
+# include <sys/stream.h>
+# include <limits.h>
+# define SYSTEM5 1 /* include all the System V defines */
+# define SYS5SIGNALS 1 /* SysV signal semantics -- reset on each sig */
+# define HASGETUSERSHELL 0 /* does not have getusershell(3) call */
+# define WAITUNION 1 /* use "union wait" as wait argument type */
+# define NEEDFSYNC 1 /* no fsync(2) in system library */
+# define NEEDSTRSTR 1 /* need emulation of the strstr(3) call */
+# define NOFTRUNCATE 1 /* do not have ftruncate(2) */
+# define MAXPATHLEN PATH_MAX
+# define LA_TYPE LA_SHORT
+# define SFS_TYPE SFS_STATFS /* use <sys/statfs.h> statfs() impl */
+# define SFS_BAVAIL f_bfree /* alternate field name */
+# define TZ_TYPE TZ_TZNAME /* use tzname[] vector */
+# define NETUNIX 0 /* no unix domain socket support */
+# undef WIFEXITED
+# undef WEXITSTATUS
+# define strtoul strtol /* gcc library bogosity */
+
+typedef unsigned short uid_t;
+typedef unsigned short gid_t;
+typedef short pid_t;
+typedef unsigned long mode_t;
+
+/* some stuff that should have been in the include files */
+extern char *malloc();
+extern struct passwd *getpwent();
+extern struct passwd *getpwnam();
+extern struct passwd *getpwuid();
+extern char *getenv();
+extern struct group *getgrgid();
+extern struct group *getgrnam();
+
+# endif /* ALTOS_SYSTEM_V */
+
+
+/*
+** ConvexOS 11.0 and later
+**
+** "Todd C. Miller" <millert@mroe.cs.colorado.edu> claims this
+** works on 9.1 as well.
+**
+** ConvexOS 11.5 and later, should work on 11.0 as defined.
+** For pre-ConvexOOS 11.0, define SM_CONF_GETOPT=0, undef IDENTPROTO
+**
+** Eric Schnoebelen (eric@cirr.com) For CONVEX Computer Corp.
+** (now the CONVEX Technologies Center of Hewlett Packard)
+*/
+
+# ifdef _CONVEX_SOURCE
+# define HASGETDTABLESIZE 1 /* has getdtablesize(2) */
+# define HASINITGROUPS 1 /* has initgroups(3) */
+# define HASUNAME 1 /* use System V uname(2) system call */
+# define HASSETSID 1 /* has POSIX setsid(2) call */
+# define HASUNSETENV 1 /* has unsetenv(3) */
+# define HASFLOCK 1 /* has flock(2) */
+# define HASSETRLIMIT 1 /* has setrlimit(2) */
+# define HASSETREUID 1 /* has setreuid(2) */
+# define BROKEN_RES_SEARCH 1 /* res_search(unknown) returns h_error=0 */
+# define NEEDPUTENV 1 /* needs putenv (written in terms of setenv) */
+# define SM_CONF_GETOPT 1 /* need a replacement for getopt(3) */
+# define IP_SRCROUTE 0 /* Something is broken with getsockopt() */
+# define LA_TYPE LA_FLOAT
+# define SFS_TYPE SFS_VFS /* use <sys/vfs.h> statfs() implementation */
+# ifndef _PATH_VENDOR_CF
+# define _PATH_VENDOR_CF "/usr/lib/sendmail.cf"
+# endif /* ! _PATH_VENDOR_CF */
+# ifndef S_IREAD
+# define S_IREAD _S_IREAD
+# define S_IWRITE _S_IWRITE
+# define S_IEXEC _S_IEXEC
+# define S_IFMT _S_IFMT
+# define S_IFCHR _S_IFCHR
+# define S_IFBLK _S_IFBLK
+# endif /* ! S_IREAD */
+# ifndef TZ_TYPE
+# define TZ_TYPE TZ_TIMEZONE
+# endif /* ! TZ_TYPE */
+# ifndef IDENTPROTO
+# define IDENTPROTO 1
+# endif /* ! IDENTPROTO */
+# ifndef SHARE_V1
+# define SHARE_V1 1 /* version 1 of the fair share scheduler */
+# endif /* ! SHARE_V1 */
+# if !defined(__GNUC__ )
+# define UID_T int /* GNUC gets it right, ConvexC botches */
+# define GID_T int /* GNUC gets it right, ConvexC botches */
+# endif /* !defined(__GNUC__ ) */
+# if SECUREWARE
+# define FORK fork /* SecureWare wants the real fork! */
+# else /* SECUREWARE */
+# define FORK vfork /* the rest of the OS versions don't care */
+# endif /* SECUREWARE */
+# endif /* _CONVEX_SOURCE */
+
+
+/*
+** RISC/os 4.52
+**
+** Gives a ton of warning messages, but otherwise compiles.
+*/
+
+# ifdef RISCOS
+
+# define HASUNSETENV 1 /* has unsetenv(3) call */
+# ifndef HASFLOCK
+# define HASFLOCK 1 /* has flock(2) call */
+# endif /* ! HASFLOCK */
+# define WAITUNION 1 /* use "union wait" as wait argument type */
+# define SM_CONF_GETOPT 0 /* need a replacement for getopt(3) */
+# define NEEDPUTENV 1 /* need putenv(3) call */
+# define NEEDSTRSTR 1 /* need emulation of the strstr(3) call */
+# define SFS_TYPE SFS_VFS /* use <sys/vfs.h> statfs() implementation */
+# define LA_TYPE LA_INT
+# define LA_AVENRUN "avenrun"
+# define _PATH_UNIX "/unix"
+# undef WIFEXITED
+
+# define setpgid setpgrp
+
+typedef int pid_t;
+# define SIGFUNC_DEFINED
+# define SIGFUNC_RETURN (0)
+# define SIGFUNC_DECL int
+typedef int (*sigfunc_t)();
+extern char *getenv();
+extern void *malloc();
+
+/* added for RISC/os 4.01...which is dumber than 4.50 */
+# ifdef RISCOS_4_0
+# ifndef ARBPTR_T
+# define ARBPTR_T char *
+# endif /* ! ARBPTR_T */
+# undef HASFLOCK
+# define HASFLOCK 0
+# endif /* RISCOS_4_0 */
+
+# include <sys/time.h>
+
+# endif /* RISCOS */
+
+
+/*
+** Linux 0.99pl10 and above...
+**
+** Thanks to, in reverse order of contact:
+**
+** John Kennedy <warlock@csuchico.edu>
+** Andrew Pam <avatar@aus.xanadu.com>
+** Florian La Roche <rzsfl@rz.uni-sb.de>
+** Karl London <karl@borg.demon.co.uk>
+**
+** NOTE: Override HASFLOCK as you will but, as of 1.99.6, mixed-style
+** file locking is no longer allowed. In particular, make sure
+** your DBM library and sendmail are both using either flock(2)
+** *or* fcntl(2) file locking, but not both.
+*/
+
+# ifdef __linux__
+# include <linux/version.h>
+# if !defined(KERNEL_VERSION) /* not defined in 2.0.x kernel series */
+# define KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))
+# endif /* !defined(KERNEL_VERSION) */
+# define BSD 1 /* include BSD defines */
+# define HASSETREGID 1 /* use setregid(2) to set saved gid */
+# ifndef REQUIRES_DIR_FSYNC
+# define REQUIRES_DIR_FSYNC 1 /* requires fsync() on directory */
+# endif /* REQUIRES_DIR_FSYNC */
+# ifndef USESETEUID
+# define USESETEUID 0 /* has it due to POSIX, but doesn't work */
+# endif /* USESETEUID */
+# define SM_CONF_GETOPT 0 /* need a replacement for getopt(3) */
+# define HASUNAME 1 /* use System V uname(2) system call */
+# define HASUNSETENV 1 /* has unsetenv(3) call */
+# define ERRLIST_PREDEFINED /* don't declare sys_errlist */
+# define GIDSET_T gid_t /* from <linux/types.h> */
+# ifndef HASGETUSERSHELL
+# define HASGETUSERSHELL 0 /* getusershell(3) broken in Slackware 2.0 */
+# endif /* HASGETUSERSHELL */
+# ifndef IP_SRCROUTE
+# define IP_SRCROUTE 0 /* linux <= 1.2.8 doesn't support IP_OPTIONS */
+# endif /* ! IP_SRCROUTE */
+# ifndef HAS_IN_H
+# define HAS_IN_H 1 /* use netinet/in.h */
+# endif /* ! HAS_IN_H */
+# ifndef USE_SIGLONGJMP
+# define USE_SIGLONGJMP 1 /* sigsetjmp needed for signal handling */
+# endif /* ! USE_SIGLONGJMP */
+# ifndef HASFLOCK
+# if LINUX_VERSION_CODE < 66399
+# define HASFLOCK 0 /* flock(2) is broken after 0.99.13 */
+# else /* LINUX_VERSION_CODE < 66399 */
+# if (LINUX_VERSION_CODE < KERNEL_VERSION(2,4,0))
+# define HASFLOCK 1 /* flock(2) fixed after 1.3.95 */
+# else /* (LINUX_VERSION_CODE < KERNEL_VERSION(2,4,0)) */
+# define HASFLOCK 0 /* flock(2) is broken (again) after 2.4.0 */
+# endif /* (LINUX_VERSION_CODE < KERNEL_VERSION(2,4,0)) */
+# endif /* LINUX_VERSION_CODE < 66399 */
+# endif /* ! HASFLOCK */
+# ifndef LA_TYPE
+# define LA_TYPE LA_PROCSTR
+# endif /* ! LA_TYPE */
+# define SFS_TYPE SFS_VFS /* use <sys/vfs.h> statfs() impl */
+# define SPT_PADCHAR '\0' /* pad process title with nulls */
+# if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,0,0))
+# ifndef HASURANDOMDEV
+# define HASURANDOMDEV 1 /* 2.0 (at least) has linux/drivers/char/random.c */
+# endif /* ! HASURANDOMDEV */
+# endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(2,0,0)) */
+# if defined(__GLIBC__) && defined(__GLIBC_MINOR__)
+# define HASSTRERROR 1 /* has strerror(3) */
+# endif /* defined(__GLIBC__) && defined(__GLIBC_MINOR__) */
+# ifndef TZ_TYPE
+# define TZ_TYPE TZ_NONE /* no standard for Linux */
+# endif /* ! TZ_TYPE */
+# if (__GLIBC__ >= 2)
+# include <paths.h>
+# endif /* (__GLIBC__ >= 2) */
+# ifndef _PATH_SENDMAILPID
+# define _PATH_SENDMAILPID "/var/run/sendmail.pid"
+# endif /* ! _PATH_SENDMAILPID */
+# include <sys/sysmacros.h>
+# undef atol /* wounded in <stdlib.h> */
+# if NETINET6
+ /*
+ ** Linux doesn't have a good way to tell userland what interfaces are
+ ** IPv6-capable. Therefore, the BIND resolver can not determine if there
+ ** are IPv6 interfaces to honor AI_ADDRCONFIG. Unfortunately, it assumes
+ ** that none are present. (Excuse the macro name ADDRCONFIG_IS_BROKEN.)
+ */
+# define ADDRCONFIG_IS_BROKEN 1
+
+ /*
+ ** Indirectly included from glibc's <feature.h>. IPv6 support is native
+ ** in 2.1 and later, but the APIs appear before the functions.
+ */
+# if defined(__GLIBC__) && defined(__GLIBC_MINOR__)
+# define GLIBC_VERSION ((__GLIBC__ << 8) + __GLIBC_MINOR__)
+# if (GLIBC_VERSION >= 0x201)
+# undef IPPROTO_ICMPV6 /* linux #defines, glibc enums */
+# else /* (GLIBC_VERSION >= 0x201) */
+# include <linux/in6.h> /* IPv6 support */
+# endif /* (GLIBC_VERSION >= 0x201) */
+# if (GLIBC_VERSION >= 0x201 && !defined(NEEDSGETIPNODE))
+ /* Have APIs in <netdb.h>, but no support in glibc */
+# define NEEDSGETIPNODE 1
+# endif /* (GLIBC_VERSION >= 0x201 && !defined(NEEDSGETIPNODE)) */
+# undef GLIBC_VERSION
+# endif /* defined(__GLIBC__) && defined(__GLIBC_MINOR__) */
+# endif /* NETINET6 */
+# ifndef HASFCHOWN
+# define HASFCHOWN 1 /* fchown(2) */
+# endif /* ! HASFCHOWN */
+# if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,0,36)) && !defined(HASFCHMOD)
+# define HASFCHMOD 1 /* fchmod(2) */
+# endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(2,0,36)) && !defined(HASFCHMOD) */
+# endif /* __linux__ */
+
+
+/*
+** DELL SVR4 Issue 2.2, and others
+** From Kimmo Suominen <kim@grendel.lut.fi>
+**
+** It's on #ifdef DELL_SVR4 because Solaris also gets __svr4__
+** defined, and the definitions conflict.
+**
+** Peter Wemm <peter@perth.DIALix.oz.au> claims that the setreuid
+** trick works on DELL 2.2 (SVR4.0/386 version 4.0) and ESIX 4.0.3A
+** (SVR4.0/386 version 3.0).
+*/
+
+# ifdef DELL_SVR4
+ /* no changes necessary */
+ /* see general __svr4__ defines below */
+# endif /* DELL_SVR4 */
+
+
+/*
+** Apple A/UX 3.0
+*/
+
+# ifdef _AUX_SOURCE
+# include <sys/sysmacros.h>
+# define BSD /* has BSD routines */
+# define HASSETRLIMIT 0 /* ... but not setrlimit(2) */
+# define BROKEN_RES_SEARCH 1 /* res_search(unknown) returns h_errno=0 */
+# define BOGUS_O_EXCL 1 /* exclusive open follows symlinks */
+# define HASUNAME 1 /* use System V uname(2) system call */
+# define HASFCHMOD 1 /* has fchmod(2) syscall */
+# define HASINITGROUPS 1 /* has initgroups(3) call */
+# define HASSETVBUF 1 /* has setvbuf(3) in libc */
+# define HASSTRERROR 1 /* has strerror(3) */
+# define SIGFUNC_DEFINED /* sigfunc_t already defined */
+# define SIGFUNC_RETURN /* POSIX-mode */
+# define SIGFUNC_DECL void /* POSIX-mode */
+# define ERRLIST_PREDEFINED 1
+# ifndef IDENTPROTO
+# define IDENTPROTO 0 /* TCP/IP implementation is broken */
+# endif /* ! IDENTPROTO */
+# ifndef LA_TYPE
+# define LA_TYPE LA_INT
+# define FSHIFT 16
+# endif /* ! LA_TYPE */
+# define LA_AVENRUN "avenrun"
+# define SFS_TYPE SFS_VFS /* use <sys/vfs.h> statfs() implementation */
+# define TZ_TYPE TZ_TZNAME
+# ifndef _PATH_UNIX
+# define _PATH_UNIX "/unix" /* should be in <paths.h> */
+# endif /* ! _PATH_UNIX */
+# ifndef _PATH_VENDOR_CF
+# define _PATH_VENDOR_CF "/usr/lib/sendmail.cf"
+# endif /* ! _PATH_VENDOR_CF */
+# undef WIFEXITED
+# undef WEXITSTATUS
+# endif /* _AUX_SOURCE */
+
+
+/*
+** Encore UMAX V
+**
+** Not extensively tested.
+*/
+
+# ifdef UMAXV
+# define HASUNAME 1 /* use System V uname(2) system call */
+# define HASSETVBUF 1 /* we have setvbuf(3) in libc */
+# define HASINITGROUPS 1 /* has initgroups(3) call */
+# define HASGETUSERSHELL 0 /* does not have getusershell(3) call */
+# define SYS5SIGNALS 1 /* SysV signal semantics -- reset on each sig */
+# define SYS5SETPGRP 1 /* use System V setpgrp(2) syscall */
+# define SFS_TYPE SFS_4ARGS /* four argument statfs() call */
+# define MAXPATHLEN PATH_MAX
+extern struct passwd *getpwent(), *getpwnam(), *getpwuid();
+extern struct group *getgrent(), *getgrnam(), *getgrgid();
+# undef WIFEXITED
+# undef WEXITSTATUS
+# endif /* UMAXV */
+
+
+/*
+** Stardent Titan 3000 running TitanOS 4.2.
+**
+** Must be compiled in "cc -43" mode.
+**
+** From Kate Hedstrom <kate@ahab.rutgers.edu>.
+**
+** Note the tweaking below after the BSD defines are set.
+*/
+
+# ifdef titan
+# define setpgid setpgrp
+typedef int pid_t;
+# undef WIFEXITED
+# undef WEXITSTATUS
+# endif /* titan */
+
+
+/*
+** Sequent DYNIX 3.2.0
+**
+** From Jim Davis <jdavis@cs.arizona.edu>.
+*/
+
+# ifdef sequent
+
+# define BSD 1
+# define HASUNSETENV 1
+# define BSD4_3 1 /* to get signal() in conf.c */
+# define WAITUNION 1
+# define LA_TYPE LA_FLOAT
+# ifdef _POSIX_VERSION
+# undef _POSIX_VERSION /* set in <unistd.h> */
+# endif /* _POSIX_VERSION */
+# undef HASSETVBUF /* don't actually have setvbuf(3) */
+# define setpgid setpgrp
+
+/* Have to redefine WIFEXITED to take an int, to work with waitfor() */
+# undef WIFEXITED
+# define WIFEXITED(s) (((union wait*)&(s))->w_stopval != WSTOPPED && \
+ ((union wait*)&(s))->w_termsig == 0)
+# define WEXITSTATUS(s) (((union wait*)&(s))->w_retcode)
+typedef int pid_t;
+# define isgraph(c) (isprint(c) && (c != ' '))
+
+# ifndef IDENTPROTO
+# define IDENTPROTO 0 /* TCP/IP implementation is broken */
+# endif /* ! IDENTPROTO */
+
+# ifndef _PATH_UNIX
+# define _PATH_UNIX "/dynix"
+# endif /* ! _PATH_UNIX */
+# ifndef _PATH_VENDOR_CF
+# define _PATH_VENDOR_CF "/usr/lib/sendmail.cf"
+# endif /* ! _PATH_VENDOR_CF */
+# endif /* sequent */
+
+
+/*
+** Sequent DYNIX/ptx v2.0 (and higher)
+**
+** For DYNIX/ptx v1.x, undefine HASSETREUID.
+**
+** From Tim Wright <timw@sequent.com>.
+** Update from Jack Woolley <jwoolley@sctcorp.com>, 26 Dec 1995,
+** for DYNIX/ptx 4.0.2.
+*/
+
+# ifdef _SEQUENT_
+# include <sys/stream.h>
+# define SYSTEM5 1 /* include all the System V defines */
+# define HASSETSID 1 /* has POSIX setsid(2) call */
+# define HASINITGROUPS 1 /* has initgroups(3) call */
+# define HASSETREUID 1 /* has setreuid(2) call */
+# define HASGETUSERSHELL 0 /* does not have getusershell(3) call */
+# define GIDSET_T gid_t
+# define LA_TYPE LA_INT
+# define SFS_TYPE SFS_STATFS /* use <sys/statfs.h> statfs() impl */
+# define SPT_TYPE SPT_NONE /* don't use setproctitle */
+# ifndef IDENTPROTO
+# define IDENTPROTO 0 /* TCP/IP implementation is broken */
+# endif /* ! IDENTPROTO */
+# ifndef _PATH_VENDOR_CF
+# define _PATH_VENDOR_CF "/usr/lib/sendmail.cf"
+# endif /* ! _PATH_VENDOR_CF */
+# ifndef _PATH_SENDMAILPID
+# define _PATH_SENDMAILPID "/etc/sendmail.pid"
+# endif /* ! _PATH_SENDMAILPID */
+# endif /* _SEQUENT_ */
+
+/*
+** Cray UNICOS, UNICOS/mk, and UNICOS/mp
+**
+** UNICOS:
+** Ported by David L. Kensiski, Sterling Sofware <kensiski@nas.nasa.gov>
+** Update Brian Ginsbach <ginsbach@cray.com>
+** UNICOS/mk (Cray T3E):
+** Contributed by Manu Mahonen <mailadm@csc.fi>
+** of Center for Scientific Computing.
+** Update Brian Ginsbach <ginsbach@cray.com>
+** UNICOS/mp:
+** From Aaron Davis <awd@cray.com> & Brian Ginsbach <ginsbach@cray.com>
+*/
+
+# if defined(_CRAY) || defined(UNICOS) || defined(_UNICOSMP)
+# define SYSTEM5 1 /* include all the System V defines */
+# define HASFCHMOD 1 /* has fchmod(2) syscall */
+# define HASFCHOWN 1 /* has fchown(2) */
+# define HASUNSETENV 1 /* has unsetenv(3) call */
+# define HASINITGROUPS 1 /* has initgroups(3) call */
+# define HASSETREUID 1 /* has setreuid(2) call */
+# define USESETEUID 1 /* has usable seteuid(2) call */
+# define HASGETDTABLESIZE 1 /* has getdtablesize(2) syscall */
+# define HASSTRERROR 1 /* has strerror(3) */
+# define GIDSET_T gid_t
+# define SFS_TYPE SFS_4ARGS /* four argument statfs() call */
+# define SFS_BAVAIL f_bfree /* alternate field name */
+# define SAFENFSPATHCONF 1 /* pathconf(2) pessimizes on NFS filesystems */
+# ifdef UNICOS
+# define SYS5SIGNALS 1 /* SysV signal semantics -- reset on each sig */
+# define LA_TYPE LA_ZERO
+# define _PATH_MAILDIR "/usr/spool/mail"
+# define GET_IPOPT_DST(dst) *(struct in_addr *)&(dst)
+# ifndef MAXPATHLEN
+# define MAXPATHLEN PATHSIZE
+# endif /* ! MAXPATHLEN */
+# ifndef _PATH_UNIX
+# ifdef UNICOSMK
+# define _PATH_UNIX "/unicosmk.ar"
+# else
+# define _PATH_UNIX "/unicos"
+# endif /* UNICOSMK */
+# endif /* ! _PATH_UNIX */
+# ifndef _PATH_VENDOR_CF
+# define _PATH_VENDOR_CF "/usr/lib/sendmail.cf"
+# endif /* ! _PATH_VENDOR_CF */
+# endif /* UNICOS */
+# ifdef _UNICOSMP
+# if defined(_SC_NPROC_ONLN) && !defined(_SC_NPROCESSORS_ONLN)
+ /* _SC_NPROC_ONLN is 'mpadmin -u', total # of unrestricted processors */
+# define _SC_NPROCESSORS_ONLN _SC_NPROC_ONLN
+# endif /* if defined(_SC_NPROC_ONLN) && !defined(_SC_NPROCESSORS_ONLN) */
+# define HASGETUSERSHELL 0 /* does not have getusershell(3) call */
+# define HASSETRLIMIT 1 /* has setrlimit(2) syscall */
+# define LA_TYPE LA_IRIX6 /* figure out at run time */
+# include <sys/cdefs.h>
+# include <paths.h>
+# define ARGV_T char *const *
+# endif /* _UNICOSMP */
+# endif /* _CRAY */
+
+/*
+** Apollo DomainOS
+**
+** From Todd Martin <tmartint@tus.ssi1.com> & Don Lewis <gdonl@gv.ssi1.com>
+**
+** 15 Jan 1994; updated 2 Aug 1995
+**
+*/
+
+# ifdef apollo
+# define HASSETREUID 1 /* has setreuid(2) call */
+# define HASINITGROUPS 1 /* has initgroups(2) call */
+# define IP_SRCROUTE 0 /* does not have <netinet/ip_var.h> */
+# define SPT_TYPE SPT_NONE /* don't use setproctitle */
+# define LA_TYPE LA_SUBR /* use getloadavg.c */
+# define SFS_TYPE SFS_4ARGS /* four argument statfs() call */
+# define SFS_BAVAIL f_bfree /* alternate field name */
+# define TZ_TYPE TZ_TZNAME
+# ifndef _PATH_VENDOR_CF
+# define _PATH_VENDOR_CF "/usr/lib/sendmail.cf"
+# endif /* ! _PATH_VENDOR_CF */
+# ifndef _PATH_SENDMAILPID
+# define _PATH_SENDMAILPID "/etc/sendmail.pid"
+# endif /* ! _PATH_SENDMAILPID */
+# undef S_IFSOCK /* S_IFSOCK and S_IFIFO are the same */
+# undef S_IFIFO
+# define S_IFIFO 0010000
+# ifndef IDENTPROTO
+# define IDENTPROTO 0 /* TCP/IP implementation is broken */
+# endif /* ! IDENTPROTO */
+# define RLIMIT_NEEDS_SYS_TIME_H 1
+# if defined(NGROUPS_MAX) && !NGROUPS_MAX
+# undef NGROUPS_MAX
+# endif /* defined(NGROUPS_MAX) && !NGROUPS_MAX */
+# endif /* apollo */
+
+/*
+** MPE-iX
+**
+** Requires MPE 6.0 or greater. See sendmail/README for more info.
+**
+** From Mark Bixby <mark_bixby@hp.com> or <mark@bixby.org>.
+*/
+
+# ifdef MPE
+
+# include <sys/sysmacros.h>
+# include <fcntl.h>
+
+/* Sendmail stuff */
+# define HASFCHOWN 0 /* lacks fchown() */
+# define HASGETUSERSHELL 0 /* lacks getusershell() */
+# ifdef HASNICE
+# undef HASNICE
+# endif /* HASNICE */
+# define HASNICE 0 /* lacks nice() */
+# define HASRANDOM 0 /* lacks random() */
+# ifdef HASRRESVPORT
+# undef HASRRESVPORT
+# endif /* HASRRESVPORT */
+# define HASRRESVPORT 0 /* lacks rresvport() */
+# define IP_SRCROUTE 0 /* lacks IP source routing fields */
+# ifdef MATCHGECOS
+# undef MATCHGECOS
+# endif /* MATCHGECOS */
+# define MATCHGECOS 0 /* lacks an initialized GECOS field */
+# define NEEDFSYNC 1 /* use sendmail's fsync() */
+# define NEEDLINK 1 /* use sendmail's link() */
+# define NOFTRUNCATE 1 /* lacks ftruncate() */
+# define SFS_TYPE SFS_NONE /* can't determine disk space */
+# define SM_CONF_SYSLOG 0 /* use sendmail decl of syslog() */
+# define USE_DOUBLE_FORK 0 /* don't fork an intermediate zombie */
+# define USE_ENVIRON 1 /* use environ instead of envp */
+
+/* Missing header stuff */
+# define AF_UNSPEC 0
+# define AF_MAX AF_INET
+# define IFF_LOOPBACK 0x8
+# define IN_LOOPBACKNET 127
+# define MAXNAMLEN NAME_MAX
+# define S_IEXEC S_IXUSR
+# define S_IREAD S_IRUSR
+# define S_IWRITE S_IWUSR
+
+/* Present header stuff that needs to be missing */
+# undef NGROUPS_MAX
+
+/* Shadow functions */
+# define bind sendmail_mpe_bind
+# define _exit sendmail_mpe__exit
+# define exit sendmail_mpe_exit
+# define fcntl sendmail_mpe_fcntl
+# define getegid sendmail_mpe_getegid
+# define geteuid sendmail_mpe_geteuid
+# define getpwnam sendmail_mpe_getpwnam
+# define getpwuid sendmail_mpe_getpwuid
+# define setgid sendmail_mpe_setgid
+# define setuid sendmail_mpe_setuid
+extern int sendmail_mpe_fcntl __P((int, int, ...));
+extern struct passwd * sendmail_mpe_getpwnam __P((const char *));
+extern struct passwd * sendmail_mpe_getpwuid __P((uid_t));
+# endif /* MPE */
+
+/*
+** System V Rel 5.x (a.k.a Unixware7 w/o BSD-Compatibility Libs ie. native)
+**
+** Contributed by Paul Gampe <paulg@apnic.net>
+*/
+
+# ifdef __svr5__
+# include <sys/mkdev.h>
+# define __svr4__
+# define SYS5SIGNALS 1
+# define HASFCHOWN 1 /* has fchown(2) call */
+# define HASSETSID 1 /* has POSIX setsid(2) call */
+# define HASSETREUID 1
+# define HASWAITPID 1
+# define HASGETDTABLESIZE 1
+# define GIDSET_T gid_t
+# define SOCKADDR_LEN_T size_t
+# define SOCKOPT_LEN_T size_t
+# ifndef _PATH_UNIX
+# define _PATH_UNIX "/stand/unix"
+# endif /* ! _PATH_UNIX */
+# define SPT_PADCHAR '\0' /* pad process title with nulls */
+# ifndef SYSLOG_BUFSIZE
+# define SYSLOG_BUFSIZE 1024 /* unsure */
+# endif /* ! SYSLOG_BUFSIZE */
+# ifndef _PATH_VENDOR_CF
+# define _PATH_VENDOR_CF "/etc/sendmail.cf"
+# endif /* ! _PATH_VENDOR_CF */
+# ifndef _PATH_SENDMAILPID
+# define _PATH_SENDMAILPID "/etc/sendmail.pid"
+# endif /* ! _PATH_SENDMAILPID */
+# undef offsetof /* avoid stddefs.h, sys/sysmacros.h conflict */
+#if !defined(SM_SET_H_ERRNO) && defined(_REENTRANT)
+# define SM_SET_H_ERRNO(err) set_h_errno((err))
+#endif /* ! SM_SET_H_ERRNO && _REENTRANT */
+# endif /* __svr5__ */
+
+/* ###################################################################### */
+
+/*
+** UnixWare 2.x
+*/
+
+# ifdef UNIXWARE2
+# define UNIXWARE 1
+# undef offsetof /* avoid stddefs.h, sys/sysmacros.h conflict */
+# endif /* UNIXWARE2 */
+
+
+/*
+** UnixWare 1.1.2.
+**
+** Updated by Petr Lampa <lampa@fee.vutbr.cz>.
+** From Evan Champion <evanc@spatial.synapse.org>.
+*/
+
+# ifdef UNIXWARE
+# include <sys/mkdev.h>
+# define SYSTEM5 1
+# define HASGETUSERSHELL 0 /* does not have getusershell(3) call */
+# define HASSETREUID 1
+# define HASSETSID 1 /* has POSIX setsid(2) call */
+# define HASINITGROUPS 1
+# define GIDSET_T gid_t
+# define SLEEP_T unsigned
+# define SFS_TYPE SFS_STATVFS
+# define LA_TYPE LA_ZERO
+# undef WIFEXITED
+# undef WEXITSTATUS
+# ifndef _PATH_UNIX
+# define _PATH_UNIX "/unix"
+# endif /* ! _PATH_UNIX */
+# ifndef _PATH_VENDOR_CF
+# define _PATH_VENDOR_CF "/usr/ucblib/sendmail.cf"
+# endif /* ! _PATH_VENDOR_CF */
+# ifndef _PATH_SENDMAILPID
+# define _PATH_SENDMAILPID "/usr/ucblib/sendmail.pid"
+# endif /* ! _PATH_SENDMAILPID */
+# define SYSLOG_BUFSIZE 128
+# endif /* UNIXWARE */
+
+
+/*
+** Intergraph CLIX 3.1
+**
+** From Paul Southworth <pauls@locust.cic.net>
+*/
+
+# ifdef CLIX
+# define SYSTEM5 1 /* looks like System V */
+# ifndef HASGETUSERSHELL
+# define HASGETUSERSHELL 0 /* does not have getusershell(3) call */
+# endif /* ! HASGETUSERSHELL */
+# define DEV_BSIZE 512 /* device block size not defined */
+# define GIDSET_T gid_t
+# undef LOG /* syslog not available */
+# define NEEDFSYNC 1 /* no fsync in system library */
+# define GETSHORT _getshort
+# endif /* CLIX */
+
+
+/*
+** NCR MP-RAS 2.x (SysVr4) with Wollongong TCP/IP
+**
+** From Kevin Darcy <kevin@tech.mis.cfc.com>.
+*/
+
+# ifdef NCR_MP_RAS2
+# include <sys/sockio.h>
+# define __svr4__
+# define IP_SRCROUTE 0 /* Something is broken with getsockopt() */
+# define SYSLOG_BUFSIZE 1024
+# define SPT_TYPE SPT_NONE
+# endif /* NCR_MP_RAS2 */
+
+
+/*
+** NCR MP-RAS 3.x (SysVr4) with STREAMware TCP/IP
+**
+** From Tom Moore <Tom.Moore@DaytonOH.NCR.COM>
+*/
+
+# ifdef NCR_MP_RAS3
+# define __svr4__
+# define HASFCHOWN 1 /* has fchown(2) call */
+# define LDA_USE_LOCKF 1
+# define SIOCGIFNUM_IS_BROKEN 1 /* SIOCGIFNUM has non-std interface */
+# define SO_REUSEADDR_IS_BROKEN 1 /* doesn't work if accept() fails */
+# define SYSLOG_BUFSIZE 1024
+# define SPT_TYPE SPT_NONE
+# define _PATH_MAILDIR "/var/mail"
+# ifndef _XOPEN_SOURCE
+# define _XOPEN_SOURCE
+# define _XOPEN_SOURCE_EXTENDED 1
+# include <sys/resource.h>
+# undef _XOPEN_SOURCE
+# undef _XOPEN_SOURCE_EXTENDED
+# endif /* ! _XOPEN_SOURCE */
+# endif /* NCR_MP_RAS3 */
+
+
+/*
+** Tandem NonStop-UX SVR4
+**
+** From Rick McCarty <mccarty@mpd.tandem.com>.
+*/
+
+# ifdef NonStop_UX_BXX
+# define __svr4__
+# endif /* NonStop_UX_BXX */
+
+
+/*
+** Hitachi 3050R/3050RX and 3500 Workstations running HI-UX/WE2.
+**
+** Tested for 1.04, 1.03
+** From Akihiro Hashimoto ("Hash") <hash@dominic.ipc.chiba-u.ac.jp>.
+**
+** Tested for 4.02, 6.10 and 7.10
+** From Motonori NAKAMURA <motonori@media.kyoto-u.ac.jp>.
+*/
+
+# if !defined(__hpux) && (defined(_H3050R) || defined(_HIUX_SOURCE))
+# define SYSTEM5 1 /* include all the System V defines */
+# define HASINITGROUPS 1 /* has initgroups(3) call */
+# define HASFCHMOD 1 /* has fchmod(2) syscall */
+# define setreuid(r, e) setresuid(r, e, -1)
+# define LA_TYPE LA_FLOAT
+# define SPT_TYPE SPT_PSTAT
+# define SFS_TYPE SFS_VFS /* use <sys/vfs.h> statfs() implementation */
+# ifndef HASSETVBUF
+# define HASSETVBUF /* HI-UX has no setlinebuf */
+# endif /* ! HASSETVBUF */
+# ifndef GIDSET_T
+# define GIDSET_T gid_t
+# endif /* ! GIDSET_T */
+# ifndef _PATH_UNIX
+# define _PATH_UNIX "/HI-UX"
+# endif /* ! _PATH_UNIX */
+# ifndef _PATH_VENDOR_CF
+# define _PATH_VENDOR_CF "/usr/lib/sendmail.cf"
+# endif /* ! _PATH_VENDOR_CF */
+# ifndef IDENTPROTO
+# define IDENTPROTO 0 /* TCP/IP implementation is broken */
+# endif /* ! IDENTPROTO */
+# ifndef HASGETUSERSHELL
+# define HASGETUSERSHELL 0 /* getusershell(3) causes core dumps */
+# endif /* ! HASGETUSERSHELL */
+# define FDSET_CAST (int *) /* cast for fd_set parameters to select */
+
+/*
+** avoid m_flags conflict between Berkeley DB 1.85 db.h & sys/sysmacros.h
+** on HIUX 3050
+*/
+# undef m_flags
+
+# define SM_CONF_SYSLOG 0
+
+# endif /* !defined(__hpux) && (defined(_H3050R) || defined(_HIUX_SOURCE)) */
+
+
+/*
+** Amdahl UTS System V 2.1.5 (SVr3-based)
+**
+** From: Janet Jackson <janet@dialix.oz.au>.
+*/
+
+# ifdef _UTS
+# include <sys/sysmacros.h>
+# undef HASLSTAT /* has symlinks, but they cause problems */
+# define NEEDFSYNC 1 /* system fsync(2) fails on non-EFS filesys */
+# define SYS5SIGNALS 1 /* System V signal semantics */
+# define SYS5SETPGRP 1 /* use System V setpgrp(2) syscall */
+# define HASUNAME 1 /* use System V uname(2) system call */
+# define HASINITGROUPS 1 /* has initgroups(3) function */
+# define HASSETVBUF 1 /* has setvbuf(3) function */
+# ifndef HASGETUSERSHELL
+# define HASGETUSERSHELL 0 /* does not have getusershell(3) function */
+# endif /* ! HASGETUSERSHELL */
+# define GIDSET_T gid_t /* type of 2nd arg to getgroups(2) isn't int */
+# define LA_TYPE LA_ZERO /* doesn't have load average */
+# define SFS_TYPE SFS_4ARGS /* use 4-arg statfs() */
+# define SFS_BAVAIL f_bfree /* alternate field name */
+# define _PATH_UNIX "/unix"
+# ifndef _PATH_VENDOR_CF
+# define _PATH_VENDOR_CF "/usr/lib/sendmail.cf"
+# endif /* ! _PATH_VENDOR_CF */
+# endif /* _UTS */
+
+/*
+** Cray Computer Corporation's CSOS
+**
+** From Scott Bolte <scott@craycos.com>.
+*/
+
+# ifdef _CRAYCOM
+# define SYSTEM5 1 /* include all the System V defines */
+# define SYS5SIGNALS 1 /* SysV signal semantics -- reset on each sig */
+# define NEEDFSYNC 1 /* no fsync in system library */
+# define MAXPATHLEN PATHSIZE
+# define LA_TYPE LA_ZERO
+# define SFS_TYPE SFS_4ARGS /* four argument statfs() call */
+# define SFS_BAVAIL f_bfree /* alternate field name */
+# define _POSIX_CHOWN_RESTRICTED -1
+extern struct group *getgrent(), *getgrnam(), *getgrgid();
+# endif /* _CRAYCOM */
+
+
+/*
+** Sony NEWS-OS 4.2.1R and 6.0.3
+**
+** From Motonori NAKAMURA <motonori@cs.ritsumei.ac.jp>.
+*/
+
+# ifdef sony_news
+# ifndef __svr4
+ /* NEWS-OS 4.2.1R */
+# ifndef BSD
+# define BSD /* has BSD routines */
+# endif /* ! BSD */
+# define HASUNSETENV 1 /* has unsetenv(2) call */
+# undef HASSETVBUF /* don't actually have setvbuf(3) */
+# define WAITUNION 1 /* use "union wait" as wait argument type */
+# define LA_TYPE LA_INT
+# define SFS_TYPE SFS_VFS /* use <sys/vfs.h> statfs() implementation */
+# ifndef HASFLOCK
+# define HASFLOCK 1 /* has flock(2) call */
+# endif /* ! HASFLOCK */
+# define setpgid setpgrp
+# undef WIFEXITED
+# undef WEXITSTATUS
+# define MODE_T int /* system include files have no mode_t */
+typedef int pid_t;
+typedef int (*sigfunc_t)();
+# define SIGFUNC_DEFINED
+# define SIGFUNC_RETURN (0)
+# define SIGFUNC_DECL int
+
+# else /* ! __svr4 */
+ /* NEWS-OS 6.0.3 with /bin/cc */
+# ifndef __svr4__
+# define __svr4__ /* use all System V Release 4 defines below */
+# endif /* ! __svr4__ */
+# define HASSETSID 1 /* has POSIX setsid(2) call */
+# define HASGETUSERSHELL 1 /* DOES have getusershell(3) call in libc */
+# define LA_TYPE LA_READKSYM /* use MIOC_READKSYM ioctl */
+# ifndef SPT_TYPE
+# define SPT_TYPE SPT_SYSMIPS /* use sysmips() (OS 6.0.2 or later) */
+# endif /* ! SPT_TYPE */
+# define GIDSET_T gid_t
+# undef WIFEXITED
+# undef WEXITSTATUS
+# ifndef SYSLOG_BUFSIZE
+# define SYSLOG_BUFSIZE 256
+# endif /* ! SYSLOG_BUFSIZE */
+# define _PATH_UNIX "/stand/unix"
+# ifndef _PATH_VENDOR_CF
+# define _PATH_VENDOR_CF "/etc/mail/sendmail.cf"
+# endif /* ! _PATH_VENDOR_CF */
+# ifndef _PATH_SENDMAILPID
+# define _PATH_SENDMAILPID "/etc/mail/sendmail.pid"
+# endif /* ! _PATH_SENDMAILPID */
+
+# endif /* ! __svr4 */
+# endif /* sony_news */
+
+
+/*
+** Omron LUNA/UNIOS-B 3.0, LUNA2/Mach and LUNA88K Mach
+**
+** From Motonori NAKAMURA <motonori@cs.ritsumei.ac.jp>.
+*/
+
+# ifdef luna
+# ifndef IDENTPROTO
+# define IDENTPROTO 0 /* TCP/IP implementation is broken */
+# endif /* ! IDENTPROTO */
+# define HASUNSETENV 1 /* has unsetenv(2) call */
+# define NEEDPUTENV 1 /* need putenv(3) call */
+# define SM_CONF_GETOPT 0 /* need a replacement for getopt(3) */
+# define NEEDSTRSTR 1 /* need emulation of the strstr(3) call */
+# define WAITUNION 1 /* use "union wait" as wait argument type */
+# ifdef uniosb
+# include <sys/time.h>
+# define NEEDVPRINTF 1 /* need a replacement for vprintf(3) */
+# define LA_TYPE LA_INT
+# define TZ_TYPE TZ_TM_ZONE /* use tm->tm_zone */
+# endif /* uniosb */
+# ifdef luna2
+# define LA_TYPE LA_SUBR
+# define TZ_TYPE TZ_TM_ZONE /* use tm->tm_zone */
+# endif /* luna2 */
+# ifdef luna88k
+# define LA_TYPE LA_INT
+# endif /* luna88k */
+# define SFS_TYPE SFS_VFS /* use <sys/vfs.h> statfs() implementation */
+# define setpgid setpgrp
+# undef WIFEXITED
+# undef WEXITSTATUS
+typedef int pid_t;
+typedef int (*sigfunc_t)();
+# define SIGFUNC_DEFINED
+# define SIGFUNC_RETURN (0)
+# define SIGFUNC_DECL int
+extern char *getenv();
+# ifndef _PATH_VENDOR_CF
+# define _PATH_VENDOR_CF "/usr/lib/sendmail.cf"
+# endif /* ! _PATH_VENDOR_CF */
+# endif /* luna */
+
+
+/*
+** NEC EWS-UX/V 4.2 (with /usr/ucb/cc)
+**
+** From Motonori NAKAMURA <motonori@cs.ritsumei.ac.jp>.
+*/
+
+# if defined(nec_ews_svr4) || defined(_nec_ews_svr4)
+# ifndef __svr4__
+# define __svr4__ /* use all System V Release 4 defines below */
+# endif /* ! __svr4__ */
+# define SYS5SIGNALS 1 /* SysV signal semantics -- reset on each sig */
+# define HASSETSID 1 /* has POSIX setsid(2) call */
+# define LA_TYPE LA_READKSYM /* use MIOC_READSYM ioctl */
+# define SFS_TYPE SFS_USTAT /* use System V ustat(2) syscall */
+# define GIDSET_T gid_t
+# undef WIFEXITED
+# undef WEXITSTATUS
+# define NAMELISTMASK 0x7fffffff /* mask for nlist() values */
+# ifndef _PATH_VENDOR_CF
+# define _PATH_VENDOR_CF "/usr/ucblib/sendmail.cf"
+# endif /* ! _PATH_VENDOR_CF */
+# ifndef _PATH_SENDMAILPID
+# define _PATH_SENDMAILPID "/usr/ucblib/sendmail.pid"
+# endif /* ! _PATH_SENDMAILPID */
+# ifndef SYSLOG_BUFSIZE
+# define SYSLOG_BUFSIZE 1024 /* allow full size syslog buffer */
+# endif /* ! SYSLOG_BUFSIZE */
+# endif /* defined(nec_ews_svr4) || defined(_nec_ews_svr4) */
+
+
+/*
+** Fujitsu/ICL UXP/DS (For the DS/90 Series)
+**
+** From Diego R. Lopez <drlopez@cica.es>.
+** Additional changes from Fumio Moriya and Toshiaki Nomura of the
+** Fujitsu Fresoftware group <dsfrsoft@oai6.yk.fujitsu.co.jp>.
+*/
+
+# ifdef __uxp__
+# include <arpa/nameser.h>
+# include <sys/sysmacros.h>
+# include <sys/mkdev.h>
+# define __svr4__
+# define HASGETUSERSHELL 0
+# define HASFLOCK 0
+# define _PATH_UNIX "/stand/unix"
+# ifndef _PATH_VENDOR_CF
+# define _PATH_VENDOR_CF "/usr/ucblib/sendmail.cf"
+# endif /* ! _PATH_VENDOR_CF */
+# ifndef _PATH_SENDMAILPID
+# define _PATH_SENDMAILPID "/usr/ucblib/sendmail.pid"
+# endif /* ! _PATH_SENDMAILPID */
+# endif /* __uxp__ */
+
+/*
+** Pyramid DC/OSx
+**
+** From Earle Ake <akee@wpdiss1.wpafb.af.mil>.
+*/
+
+# ifdef DCOSx
+# define GIDSET_T gid_t
+# ifndef IDENTPROTO
+# define IDENTPROTO 0 /* TCP/IP implementation is broken */
+# endif /* ! IDENTPROTO */
+# endif /* DCOSx */
+
+/*
+** Concurrent Computer Corporation Maxion
+**
+** From Donald R. Laster Jr. <laster@access.digex.net>.
+*/
+
+# ifdef __MAXION__
+
+# include <sys/stream.h>
+# define __svr4__ 1 /* SVR4.2MP */
+# define HASSETREUID 1 /* have setreuid(2) */
+# define HASLSTAT 1 /* have lstat(2) */
+# define HASSETRLIMIT 1 /* have setrlimit(2) */
+# define HASGETDTABLESIZE 1 /* have getdtablesize(2) */
+# define HASGETUSERSHELL 1 /* have getusershell(3) */
+# define NOFTRUNCATE 1 /* do not have ftruncate(2) */
+# define SLEEP_T unsigned
+# define SFS_TYPE SFS_STATVFS
+# define SFS_BAVAIL f_bavail
+# ifndef SYSLOG_BUFSIZE
+# define SYSLOG_BUFSIZE 256 /* Use 256 bytes */
+# endif /* ! SYSLOG_BUFSIZE */
+
+# undef WUNTRACED
+# undef WIFEXITED
+# undef WIFSIGNALED
+# undef WIFSTOPPED
+# undef WEXITSTATUS
+# undef WTERMSIG
+# undef WSTOPSIG
+
+# endif /* __MAXION__ */
+
+/*
+** Harris Nighthawk PowerUX (nh6000 box)
+**
+** Contributed by Bob Miorelli, Pratt & Whitney <miorelli@pweh.com>
+*/
+
+# ifdef _PowerUX
+# ifndef __svr4__
+# define __svr4__
+# endif /* ! __svr4__ */
+# ifndef _PATH_VENDOR_CF
+# define _PATH_VENDOR_CF "/etc/mail/sendmail.cf"
+# endif /* ! _PATH_VENDOR_CF */
+# ifndef _PATH_SENDMAILPID
+# define _PATH_SENDMAILPID "/etc/mail/sendmail.pid"
+# endif /* ! _PATH_SENDMAILPID */
+# define SYSLOG_BUFSIZE 1024
+# define LA_TYPE LA_ZERO
+typedef struct msgb mblk_t;
+# undef offsetof /* avoid stddefs.h and sys/sysmacros.h conflict */
+# endif /* _PowerUX */
+
+/*
+** Siemens Nixdorf Informationssysteme AG SINIX
+**
+** Contributed by Gerald Rinske of Siemens Business Services VAS.
+*/
+# ifdef sinix
+# define HASRANDOM 0 /* has random(3) */
+# define SYSLOG_BUFSIZE 1024
+# define SM_INT32 int /* 32bit integer */
+# endif /* sinix */
+
+
+/*
+** Motorola 922, MC88110, UNIX SYSTEM V/88 Release 4.0 Version 4.3
+**
+** Contributed by Sergey Rusanov <rsm@utfoms.udmnet.ru>
+*/
+
+# ifdef MOTO
+# define HASFCHMOD 1
+# define HASSETRLIMIT 0
+# define HASSETSID 1 /* has POSIX setsid(2) call */
+# define HASSETREUID 1
+# define HASULIMIT 1
+# define HASWAITPID 1
+# define HASGETDTABLESIZE 1
+# define HASGETUSERSHELL 1
+# define IP_SRCROUTE 0
+# define IDENTPROTO 0
+# define RES_DNSRCH_VARIABLE _res_dnsrch
+# define _PATH_UNIX "/unix"
+# define _PATH_VENDOR_CF "/etc/sendmail.cf"
+# define _PATH_SENDMAILPID "/var/run/sendmail.pid"
+# endif /* MOTO */
+
+/*
+** Interix
+** Contributed by Nedelcho Stanev <nedelcho.stanev@atlanticsky.com>
+**
+** Used for Interix support.
+*/
+
+# if defined(__INTERIX)
+# define HASURANDOMDEV 1
+# define HASGETUSERSHELL 0
+# define HASSTRERROR 1
+# define HASUNSETENV 1
+# define HASFCHOWN 1
+# undef HAVE_SYS_ERRLIST
+# define sys_errlist __sys_errlist
+# define sys_nerr __sys_nerr
+# include <sys/mkdev.h>
+# ifndef major
+# define major(dev) ((int)(((dev) >> 8) & 0xff))
+# endif /* ! major */
+# ifndef minor
+# define minor(dev) ((int)((dev) & 0xff))
+# endif /* ! minor */
+# endif /* defined(__INTERIX) */
+
+
+/**********************************************************************
+** End of Per-Operating System defines
+**********************************************************************/
+/**********************************************************************
+** More general defines
+**********************************************************************/
+
+/* general BSD defines */
+# ifdef BSD
+# define HASGETDTABLESIZE 1 /* has getdtablesize(2) call */
+# ifndef HASSETREUID
+# define HASSETREUID 1 /* has setreuid(2) call */
+# endif /* ! HASSETREUID */
+# define HASINITGROUPS 1 /* has initgroups(3) call */
+# ifndef IP_SRCROUTE
+# define IP_SRCROUTE 1 /* can check IP source routing */
+# endif /* ! IP_SRCROUTE */
+# ifndef HASSETRLIMIT
+# define HASSETRLIMIT 1 /* has setrlimit(2) call */
+# endif /* ! HASSETRLIMIT */
+# ifndef HASFLOCK
+# define HASFLOCK 1 /* has flock(2) call */
+# endif /* ! HASFLOCK */
+# ifndef TZ_TYPE
+# define TZ_TYPE TZ_TM_ZONE /* use tm->tm_zone variable */
+# endif /* ! TZ_TYPE */
+# endif /* BSD */
+
+/* general System V Release 4 defines */
+# ifdef __svr4__
+# define SYSTEM5 1
+# define USESETEUID 1 /* has usable seteuid(2) call */
+# define HASINITGROUPS 1 /* has initgroups(3) call */
+# define BSD_COMP 1 /* get BSD ioctl calls */
+# ifndef HASSETRLIMIT
+# define HASSETRLIMIT 1 /* has setrlimit(2) call */
+# endif /* ! HASSETRLIMIT */
+# ifndef HASGETUSERSHELL
+# define HASGETUSERSHELL 0 /* does not have getusershell(3) call */
+# endif /* ! HASGETUSERSHELL */
+# ifndef HASFCHMOD
+# define HASFCHMOD 1 /* most (all?) SVr4s seem to have fchmod(2) */
+# endif /* ! HASFCHMOD */
+
+# ifndef _PATH_UNIX
+# define _PATH_UNIX "/unix"
+# endif /* ! _PATH_UNIX */
+# ifndef _PATH_VENDOR_CF
+# define _PATH_VENDOR_CF "/usr/ucblib/sendmail.cf"
+# endif /* ! _PATH_VENDOR_CF */
+# ifndef _PATH_SENDMAILPID
+# define _PATH_SENDMAILPID "/usr/ucblib/sendmail.pid"
+# endif /* ! _PATH_SENDMAILPID */
+# ifndef SYSLOG_BUFSIZE
+# define SYSLOG_BUFSIZE 128
+# endif /* ! SYSLOG_BUFSIZE */
+# ifndef SFS_TYPE
+# define SFS_TYPE SFS_STATVFS
+# endif /* ! SFS_TYPE */
+
+# ifndef USE_SIGLONGJMP
+# define USE_SIGLONGJMP 1 /* sigsetjmp needed for signal handling */
+# endif /* ! USE_SIGLONGJMP */
+# endif /* __svr4__ */
+
+# ifdef __SVR4
+# define LDA_USE_LOCKF 1
+# define LDA_USE_SETEUID 1
+# define _PATH_MAILDIR "/var/mail"
+# endif /* __SVR4 */
+
+/* general System V defines */
+# ifdef SYSTEM5
+# include <sys/sysmacros.h>
+# define HASUNAME 1 /* use System V uname(2) system call */
+# define SYS5SETPGRP 1 /* use System V setpgrp(2) syscall */
+# define HASSETVBUF 1 /* we have setvbuf(3) in libc */
+# ifndef HASULIMIT
+# define HASULIMIT 1 /* has the ulimit(2) syscall */
+# endif /* ! HASULIMIT */
+# ifndef LA_TYPE
+# ifdef MIOC_READKSYM
+# define LA_TYPE LA_READKSYM /* use MIOC_READKSYM ioctl */
+# else /* MIOC_READKSYM */
+# define LA_TYPE LA_INT /* assume integer load average */
+# endif /* MIOC_READKSYM */
+# endif /* ! LA_TYPE */
+# ifndef SFS_TYPE
+# define SFS_TYPE SFS_USTAT /* use System V ustat(2) syscall */
+# endif /* ! SFS_TYPE */
+# ifndef TZ_TYPE
+# define TZ_TYPE TZ_TZNAME /* use tzname[] vector */
+# endif /* ! TZ_TYPE */
+# endif /* SYSTEM5 */
+
+/* general POSIX defines */
+# ifdef _POSIX_VERSION
+# define HASSETSID 1 /* has POSIX setsid(2) call */
+# define HASWAITPID 1 /* has POSIX waitpid(2) call */
+# if _POSIX_VERSION >= 199500 && !defined(USESETEUID)
+# define USESETEUID 1 /* has usable seteuid(2) call */
+# endif /* _POSIX_VERSION >= 199500 && !defined(USESETEUID) */
+# endif /* _POSIX_VERSION */
+/*
+** Tweaking for systems that (for example) claim to be BSD or POSIX
+** but don't have all the standard BSD or POSIX routines (boo hiss).
+*/
+
+# ifdef titan
+# undef HASINITGROUPS /* doesn't have initgroups(3) call */
+# endif /* titan */
+
+# ifdef _CRAYCOM
+# undef HASSETSID /* despite POSIX claim, doesn't have setsid */
+# endif /* _CRAYCOM */
+
+# ifdef MOTO
+# undef USESETEUID
+# endif /* MOTO */
+
+/*
+** Due to a "feature" in some operating systems such as Ultrix 4.3 and
+** HPUX 8.0, if you receive a "No route to host" message (ICMP message
+** ICMP_UNREACH_HOST) on _any_ connection, all connections to that host
+** are closed. Some firewalls return this error if you try to connect
+** to the IDENT port (113), so you can't receive email from these hosts
+** on these systems. The firewall really should use a more specific
+** message such as ICMP_UNREACH_PROTOCOL or _PORT or _FILTER_PROHIB. If
+** not explicitly set to zero above, default it on.
+*/
+
+# ifndef IDENTPROTO
+# define IDENTPROTO 1 /* use IDENT proto (RFC 1413) */
+# endif /* ! IDENTPROTO */
+
+# ifndef IP_SRCROUTE
+# define IP_SRCROUTE 1 /* Detect IP source routing */
+# endif /* ! IP_SRCROUTE */
+
+# ifndef HASGETUSERSHELL
+# define HASGETUSERSHELL 1 /* libc has getusershell(3) call */
+# endif /* ! HASGETUSERSHELL */
+
+# ifndef NETUNIX
+# define NETUNIX 1 /* include unix domain support */
+# endif /* ! NETUNIX */
+
+# ifndef HASRANDOM
+# define HASRANDOM 1 /* has random(3) support */
+# endif /* ! HASRANDOM */
+
+# ifndef HASFLOCK
+# define HASFLOCK 0 /* assume no flock(2) support */
+# endif /* ! HASFLOCK */
+
+# ifndef HASSETREUID
+# define HASSETREUID 0 /* assume no setreuid(2) call */
+# endif /* ! HASSETREUID */
+
+# ifndef HASFCHMOD
+# define HASFCHMOD 0 /* assume no fchmod(2) syscall */
+# endif /* ! HASFCHMOD */
+
+# ifndef USESETEUID
+# define USESETEUID 0 /* assume no seteuid(2) call or no saved ids */
+# endif /* ! USESETEUID */
+
+# ifndef HASSETRLIMIT
+# define HASSETRLIMIT 0 /* assume no setrlimit(2) support */
+# endif /* ! HASSETRLIMIT */
+
+# ifndef HASULIMIT
+# define HASULIMIT 0 /* assume no ulimit(2) support */
+# endif /* ! HASULIMIT */
+
+# ifndef SECUREWARE
+# define SECUREWARE 0 /* assume no SecureWare C2 auditing hooks */
+# endif /* ! SECUREWARE */
+
+# ifndef USE_DOUBLE_FORK
+# define USE_DOUBLE_FORK 1 /* avoid intermediate zombies */
+# endif /* ! USE_DOUBLE_FORK */
+
+# ifndef USE_ENVIRON
+# define USE_ENVIRON 0 /* use main() envp instead of extern environ */
+# endif /* ! USE_ENVIRON */
+
+# ifndef USE_SIGLONGJMP
+# define USE_SIGLONGJMP 0 /* assume setjmp handles signals properly */
+# endif /* ! USE_SIGLONGJMP */
+
+# ifndef FDSET_CAST
+# define FDSET_CAST /* (empty) cast for fd_set arg to select */
+# endif /* ! FDSET_CAST */
+
+/*
+** Pick a mailer setuid method for changing the current uid
+*/
+
+# define USE_SETEUID 0
+# define USE_SETREUID 1
+# define USE_SETUID 2
+
+# if USESETEUID
+# define MAILER_SETUID_METHOD USE_SETEUID
+# else /* USESETEUID */
+# if HASSETREUID
+# define MAILER_SETUID_METHOD USE_SETREUID
+# else /* HASSETREUID */
+# define MAILER_SETUID_METHOD USE_SETUID
+# endif /* HASSETREUID */
+# endif /* USESETEUID */
+
+/*
+** If no type for argument two of getgroups call is defined, assume
+** it's an integer -- unfortunately, there seem to be several choices
+** here.
+*/
+
+# ifndef GIDSET_T
+# define GIDSET_T int
+# endif /* ! GIDSET_T */
+
+# ifndef UID_T
+# define UID_T uid_t
+# endif /* ! UID_T */
+
+# ifndef GID_T
+# define GID_T gid_t
+# endif /* ! GID_T */
+
+# ifndef MODE_T
+# define MODE_T mode_t
+# endif /* ! MODE_T */
+
+# ifndef ARGV_T
+# define ARGV_T char **
+# endif /* ! ARGV_T */
+
+# ifndef SOCKADDR_LEN_T
+# define SOCKADDR_LEN_T int
+# endif /* ! SOCKADDR_LEN_T */
+
+# ifndef SOCKOPT_LEN_T
+# define SOCKOPT_LEN_T int
+# endif /* ! SOCKOPT_LEN_T */
+
+# ifndef QUAD_T
+# define QUAD_T unsigned long
+# endif /* ! QUAD_T */
+/**********************************************************************
+** Remaining definitions should never have to be changed. They are
+** primarily to provide back compatibility for older systems -- for
+** example, it includes some POSIX compatibility definitions
+**********************************************************************/
+
+/* System 5 compatibility */
+# ifndef S_ISREG
+# define S_ISREG(foo) ((foo & S_IFMT) == S_IFREG)
+# endif /* ! S_ISREG */
+# ifndef S_ISDIR
+# define S_ISDIR(foo) ((foo & S_IFMT) == S_IFDIR)
+# endif /* ! S_ISDIR */
+# if !defined(S_ISLNK) && defined(S_IFLNK)
+# define S_ISLNK(foo) ((foo & S_IFMT) == S_IFLNK)
+# endif /* !defined(S_ISLNK) && defined(S_IFLNK) */
+# if !defined(S_ISFIFO)
+# if defined(S_IFIFO)
+# define S_ISFIFO(foo) ((foo & S_IFMT) == S_IFIFO)
+# else /* defined(S_IFIFO) */
+# define S_ISFIFO(foo) false
+# endif /* defined(S_IFIFO) */
+# endif /* !defined(S_ISFIFO) */
+# ifndef S_IRUSR
+# define S_IRUSR 0400
+# endif /* ! S_IRUSR */
+# ifndef S_IWUSR
+# define S_IWUSR 0200
+# endif /* ! S_IWUSR */
+# ifndef S_IRGRP
+# define S_IRGRP 0040
+# endif /* ! S_IRGRP */
+# ifndef S_IWGRP
+# define S_IWGRP 0020
+# endif /* ! S_IWGRP */
+# ifndef S_IROTH
+# define S_IROTH 0004
+# endif /* ! S_IROTH */
+# ifndef S_IWOTH
+# define S_IWOTH 0002
+# endif /* ! S_IWOTH */
+
+/* close-on-exec flag */
+# ifndef FD_CLOEXEC
+# define FD_CLOEXEC 1
+# endif /* ! FD_CLOEXEC */
+
+/*
+** Older systems don't have this error code -- it should be in
+** /usr/include/sysexits.h.
+*/
+
+# ifndef EX_CONFIG
+# define EX_CONFIG 78 /* configuration error */
+# endif /* ! EX_CONFIG */
+
+/* pseudo-codes */
+# define EX_QUIT 22 /* drop out of server immediately */
+# define EX_RESTART 23 /* restart sendmail daemon */
+# define EX_SHUTDOWN 24 /* shutdown sendmail daemon */
+
+#ifndef EX_NOTFOUND
+# define EX_NOTFOUND EX_NOHOST
+#endif /* ! EX_NOTFOUND */
+
+/* pseudo-code used for mci_setstat */
+# define EX_NOTSTICKY (-5) /* don't save persistent status */
+
+
+/*
+** An "impossible" file mode to indicate that the file does not exist.
+*/
+
+# define ST_MODE_NOFILE 0171147 /* unlikely to occur */
+
+
+/* type of arbitrary pointer */
+# ifndef ARBPTR_T
+# define ARBPTR_T void *
+# endif /* ! ARBPTR_T */
+
+# ifndef __P
+# include "sm/cdefs.h"
+# endif /* ! __P */
+
+# if HESIOD && !defined(NAMED_BIND)
+# define NAMED_BIND 1 /* not one without the other */
+# endif /* HESIOD && !defined(NAMED_BIND) */
+
+# if NAMED_BIND && !defined( __ksr__ ) && !defined( h_errno )
+extern int h_errno;
+# endif /* NAMED_BIND && !defined( __ksr__ ) && !defined( h_errno ) */
+
+# if NEEDPUTENV
+extern int putenv __P((char *));
+# endif /* NEEDPUTENV */
+
+#if !HASUNSETENV
+extern void unsetenv __P((char *));
+#endif /* !HASUNSETENV */
+
+# ifdef LDAPMAP
+# include <sys/time.h>
+# include <lber.h>
+# include <ldap.h>
+
+/* Some LDAP constants */
+# define LDAPMAP_FALSE 0
+# define LDAPMAP_TRUE 1
+
+/*
+** ldap_init(3) is broken in Umich 3.x and OpenLDAP 1.0/1.1.
+** Use the lack of LDAP_OPT_SIZELIMIT to detect old API implementations
+** and assume (falsely) that all old API implementations are broken.
+** (OpenLDAP 1.2 and later have a working ldap_init(), add -DUSE_LDAP_INIT)
+*/
+
+# if defined(LDAP_OPT_SIZELIMIT) && !defined(USE_LDAP_INIT)
+# define USE_LDAP_INIT 1
+# endif /* defined(LDAP_OPT_SIZELIMIT) && !defined(USE_LDAP_INIT) */
+
+/*
+** LDAP_OPT_SIZELIMIT is not defined under Umich 3.x nor OpenLDAP 1.x,
+** hence ldap_set_option() must not exist.
+*/
+
+# if defined(LDAP_OPT_SIZELIMIT) && !defined(USE_LDAP_SET_OPTION)
+# define USE_LDAP_SET_OPTION 1
+# endif /* defined(LDAP_OPT_SIZELIMIT) && !defined(USE_LDAP_SET_OPTION) */
+
+# endif /* LDAPMAP */
+
+# if HASUNAME
+# include <sys/utsname.h>
+# ifdef newstr
+# undef newstr
+# endif /* newstr */
+# else /* HASUNAME */
+# define NODE_LENGTH 32
+struct utsname
+{
+ char nodename[NODE_LENGTH + 1];
+};
+# endif /* HASUNAME */
+
+# if !defined(MAXHOSTNAMELEN) && !defined(_SCO_unix_) && !defined(NonStop_UX_BXX) && !defined(ALTOS_SYSTEM_V)
+# define MAXHOSTNAMELEN 256
+# endif /* !defined(MAXHOSTNAMELEN) && !defined(_SCO_unix_) && !defined(NonStop_UX_BXX) && !defined(ALTOS_SYSTEM_V) */
+
+# if !defined(SIGCHLD) && defined(SIGCLD)
+# define SIGCHLD SIGCLD
+# endif /* !defined(SIGCHLD) && defined(SIGCLD) */
+
+# ifndef STDIN_FILENO
+# define STDIN_FILENO 0
+# endif /* ! STDIN_FILENO */
+
+# ifndef STDOUT_FILENO
+# define STDOUT_FILENO 1
+# endif /* ! STDOUT_FILENO */
+
+# ifndef STDERR_FILENO
+# define STDERR_FILENO 2
+# endif /* ! STDERR_FILENO */
+
+# ifndef LOCK_SH
+# define LOCK_SH 0x01 /* shared lock */
+# define LOCK_EX 0x02 /* exclusive lock */
+# define LOCK_NB 0x04 /* non-blocking lock */
+# define LOCK_UN 0x08 /* unlock */
+# endif /* ! LOCK_SH */
+
+# ifndef S_IXOTH
+# define S_IXOTH (S_IEXEC >> 6)
+# endif /* ! S_IXOTH */
+
+# ifndef S_IXGRP
+# define S_IXGRP (S_IEXEC >> 3)
+# endif /* ! S_IXGRP */
+
+# ifndef S_IXUSR
+# define S_IXUSR (S_IEXEC)
+# endif /* ! S_IXUSR */
+
+#ifndef O_ACCMODE
+# define O_ACCMODE (O_RDONLY|O_WRONLY|O_RDWR)
+#endif /* ! O_ACCMODE */
+
+# ifndef SEEK_SET
+# define SEEK_SET 0
+# define SEEK_CUR 1
+# define SEEK_END 2
+# endif /* ! SEEK_SET */
+
+# ifndef SIG_ERR
+# define SIG_ERR ((void (*)()) -1)
+# endif /* ! SIG_ERR */
+
+# ifndef WEXITSTATUS
+# define WEXITSTATUS(st) (((st) >> 8) & 0377)
+# endif /* ! WEXITSTATUS */
+# ifndef WIFEXITED
+# define WIFEXITED(st) (((st) & 0377) == 0)
+# endif /* ! WIFEXITED */
+# ifndef WIFSTOPPED
+# define WIFSTOPPED(st) (((st) & 0100) == 0)
+# endif /* ! WIFSTOPPED */
+# ifndef WCOREDUMP
+# define WCOREDUMP(st) (((st) & 0200) != 0)
+# endif /* ! WCOREDUMP */
+# ifndef WTERMSIG
+# define WTERMSIG(st) (((st) & 0177))
+# endif /* ! WTERMSIG */
+
+# ifndef SIGFUNC_DEFINED
+typedef void (*sigfunc_t) __P((int));
+# endif /* ! SIGFUNC_DEFINED */
+# ifndef SIGFUNC_RETURN
+# define SIGFUNC_RETURN
+# endif /* ! SIGFUNC_RETURN */
+# ifndef SIGFUNC_DECL
+# define SIGFUNC_DECL void
+# endif /* ! SIGFUNC_DECL */
+
+/* size of syslog buffer */
+# ifndef SYSLOG_BUFSIZE
+# define SYSLOG_BUFSIZE 1024
+# endif /* ! SYSLOG_BUFSIZE */
+
+/* for FD_SET() */
+#ifndef FD_SETSIZE
+# define FD_SETSIZE 256
+#endif /* ! FD_SETSIZE */
+
+/*
+** Size of prescan buffer.
+** Despite comments in the _sendmail_ book, this probably should
+** not be changed; there are some hard-to-define dependencies.
+*/
+
+# define PSBUFSIZE (MAXNAME + MAXATOM) /* size of prescan buffer */
+
+/* fork routine -- set above using #ifdef _osname_ or in Makefile */
+# ifndef FORK
+# define FORK fork /* function to call to fork mailer */
+# endif /* ! FORK */
+
+/* setting h_errno */
+# ifndef SM_SET_H_ERRNO
+# define SM_SET_H_ERRNO(err) h_errno = (err)
+# endif /* SM_SET_H_ERRNO */
+
+# ifndef SM_CONF_GETOPT
+# define SM_CONF_GETOPT 1
+# endif /* ! SM_CONF_GETOPT */
+
+/* random routine -- set above using #ifdef _osname_ or in Makefile */
+# if HASRANDOM
+# define get_random() random()
+# else /* HASRANDOM */
+# define get_random() ((long) rand())
+# ifndef RANDOMSHIFT
+# define RANDOMSHIFT 8
+# endif /* ! RANDOMSHIFT */
+# endif /* HASRANDOM */
+
+/*
+** Default to using scanf in readcf.
+*/
+
+# ifndef SCANF
+# define SCANF 1
+# endif /* ! SCANF */
+
+/* XXX 32 bit type */
+# ifndef SM_INT32
+# define SM_INT32 int32_t
+# endif /* ! SM_INT32 */
+
+/*
+** SVr4 and similar systems use different routines for setjmp/longjmp
+** with signal support
+*/
+
+# if USE_SIGLONGJMP
+# ifdef jmp_buf
+# undef jmp_buf
+# endif /* jmp_buf */
+# define jmp_buf sigjmp_buf
+# ifdef setjmp
+# undef setjmp
+# endif /* setjmp */
+# define setjmp(env) sigsetjmp(env, 1)
+# ifdef longjmp
+# undef longjmp
+# endif /* longjmp */
+# define longjmp(env, val) siglongjmp(env, val)
+# endif /* USE_SIGLONGJMP */
+
+# if !defined(NGROUPS_MAX) && defined(NGROUPS)
+# define NGROUPS_MAX NGROUPS /* POSIX naming convention */
+# endif /* !defined(NGROUPS_MAX) && defined(NGROUPS) */
+
+/*
+** Some snprintf() implementations are rumored not to NUL terminate.
+*/
+# if SNPRINTF_IS_BROKEN
+# ifdef snprintf
+# undef snprintf
+# endif /* snprintf */
+# define snprintf sm_snprintf
+# ifdef vsnprintf
+# undef vsnprintf
+# endif /* vsnprintf */
+# define vsnprintf sm_vsnprintf
+# endif /* SNPRINTF_IS_BROKEN */
+
+/*
+** If we don't have a system syslog, simulate it.
+*/
+
+# if !LOG
+# define LOG_EMERG 0 /* system is unusable */
+# define LOG_ALERT 1 /* action must be taken immediately */
+# define LOG_CRIT 2 /* critical conditions */
+# define LOG_ERR 3 /* error conditions */
+# define LOG_WARNING 4 /* warning conditions */
+# define LOG_NOTICE 5 /* normal but significant condition */
+# define LOG_INFO 6 /* informational */
+# define LOG_DEBUG 7 /* debug-level messages */
+# endif /* !LOG */
+
+# ifndef SM_CONF_SYSLOG
+# define SM_CONF_SYSLOG 1 /* syslog.h has prototype for syslog() */
+# endif /* SM_CONF_SYSLOG */
+
+# if !SM_CONF_SYSLOG
+# ifdef __STDC__
+extern void syslog(int, const char *, ...);
+# else /* __STDC__ */
+extern void syslog();
+# endif /* __STDC__ */
+# endif /* !SM_CONF_SYSLOG */
+
+/* portable(?) definition for alignment */
+# ifndef SM_ALIGN_SIZE
+struct sm_align
+{
+ char al_c;
+ union
+ {
+ long al_l;
+ void *al_p;
+ double al_d;
+ void (*al_f) __P((void));
+ } al_u;
+};
+# define SM_ALIGN_SIZE offsetof(struct sm_align, al_u)
+# endif /* ! SM_ALIGN_SIZE */
+# define SM_ALIGN_BITS (SM_ALIGN_SIZE - 1)
+
+#endif /* ! SM_CONF_H */
diff --git a/usr/src/cmd/sendmail/include/sm/config.h b/usr/src/cmd/sendmail/include/sm/config.h
new file mode 100644
index 0000000000..b66cc96216
--- /dev/null
+++ b/usr/src/cmd/sendmail/include/sm/config.h
@@ -0,0 +1,189 @@
+/*
+ * Copyright (c) 2000-2003 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ * $Id: config.h,v 1.47 2004/10/26 21:41:07 gshapiro Exp $
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+** libsm configuration macros.
+** The values of these macros are platform dependent.
+** The default values are given here.
+** If the default is incorrect, then the correct value can be specified
+** in the m4 configuration file in devtools/OS.
+*/
+
+#ifndef SM_CONFIG_H
+# define SM_CONFIG_H
+
+# include "sm_os.h"
+
+/*
+** SM_CONF_STDBOOL_H is 1 if <stdbool.h> exists
+*/
+
+# ifndef SM_CONF_STDBOOL_H
+# if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
+# define SM_CONF_STDBOOL_H 1
+# else /* defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L */
+# define SM_CONF_STDBOOL_H 0
+# endif /* defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L */
+# endif /* ! SM_CONF_STDBOOL_H */
+
+/*
+** Configuration macros that specify how __P is defined.
+*/
+
+# ifndef SM_CONF_SYS_CDEFS_H
+# define SM_CONF_SYS_CDEFS_H 0
+# endif /* ! SM_CONF_SYS_CDEFS_H */
+
+/*
+** SM_CONF_STDDEF_H is 1 if <stddef.h> exists
+*/
+
+# ifndef SM_CONF_STDDEF_H
+# define SM_CONF_STDDEF_H 1
+# endif /* ! SM_CONF_STDDEF_H */
+
+/*
+** Configuration macro that specifies whether strlcpy/strlcat are available.
+** Note: this is the default so that the libsm version (optimized) will
+** be used by default (sm_strlcpy/sm_strlcat).
+*/
+
+# ifndef SM_CONF_STRL
+# define SM_CONF_STRL 0
+# endif /* ! SM_CONF_STRL */
+
+/*
+** Configuration macro indicating that setitimer is available
+*/
+
+# ifndef SM_CONF_SETITIMER
+# define SM_CONF_SETITIMER 1
+# endif /* ! SM_CONF_SETITIMER */
+
+/*
+** Does <sys/types.h> define uid_t and gid_t?
+*/
+
+# ifndef SM_CONF_UID_GID
+# define SM_CONF_UID_GID 1
+# endif /* ! SM_CONF_UID_GID */
+
+/*
+** Does <sys/types.h> define ssize_t?
+*/
+# ifndef SM_CONF_SSIZE_T
+# define SM_CONF_SSIZE_T 1
+# endif /* ! SM_CONF_SSIZE_T */
+
+/*
+** Does the C compiler support long long?
+*/
+
+# ifndef SM_CONF_LONGLONG
+# if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
+# define SM_CONF_LONGLONG 1
+# else /* defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L */
+# if defined(__GNUC__)
+# define SM_CONF_LONGLONG 1
+# else /* defined(__GNUC__) */
+# define SM_CONF_LONGLONG 0
+# endif /* defined(__GNUC__) */
+# endif /* defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L */
+# endif /* ! SM_CONF_LONGLONG */
+
+/*
+** Does <sys/types.h> define quad_t and u_quad_t?
+** We only care if long long is not available.
+*/
+
+# ifndef SM_CONF_QUAD_T
+# define SM_CONF_QUAD_T 0
+# endif /* ! SM_CONF_QUAD_T */
+
+/*
+** Configuration macro indicating that shared memory is available
+*/
+
+# ifndef SM_CONF_SHM
+# define SM_CONF_SHM 0
+# endif /* ! SM_CONF_SHM */
+
+/*
+** Does <setjmp.h> define sigsetjmp?
+*/
+
+# ifndef SM_CONF_SIGSETJMP
+# define SM_CONF_SIGSETJMP 1
+# endif /* ! SM_CONF_SIGSETJMP */
+
+/*
+** Does <sysexits.h> exist, and define the EX_* macros with values
+** that differ from the default BSD values in <sm/sysexits.h>?
+*/
+
+# ifndef SM_CONF_SYSEXITS_H
+# define SM_CONF_SYSEXITS_H 0
+# endif /* ! SM_CONF_SYSEXITS_H */
+
+/* has memchr() prototype? (if not: needs memory.h) */
+# ifndef SM_CONF_MEMCHR
+# define SM_CONF_MEMCHR 1
+# endif /* ! SM_CONF_MEMCHR */
+
+/* try LLONG tests in libsm/t-types.c? */
+# ifndef SM_CONF_TEST_LLONG
+# define SM_CONF_TEST_LLONG 1
+# endif /* !SM_CONF_TEST_LLONG */
+
+/* LDAP Checks */
+# if LDAPMAP
+# include <lber.h>
+# include <ldap.h>
+
+/* Does the LDAP library have ldap_memfree()? */
+# ifndef SM_CONF_LDAP_MEMFREE
+
+/*
+** The new LDAP C API (draft-ietf-ldapext-ldap-c-api-04.txt) includes
+** ldap_memfree() in the API. That draft states to use LDAP_API_VERSION
+** of 2004 to identify the API.
+*/
+
+# if USING_NETSCAPE_LDAP || LDAP_API_VERSION >= 2004
+# define SM_CONF_LDAP_MEMFREE 1
+# else /* USING_NETSCAPE_LDAP || LDAP_API_VERSION >= 2004 */
+# define SM_CONF_LDAP_MEMFREE 0
+# endif /* USING_NETSCAPE_LDAP || LDAP_API_VERSION >= 2004 */
+# endif /* ! SM_CONF_LDAP_MEMFREE */
+
+/* Does the LDAP library have ldap_initialize()? */
+# ifndef SM_CONF_LDAP_INITIALIZE
+
+/*
+** Check for ldap_initialize() support for support for LDAP URI's with
+** non-ldap:// schemes.
+*/
+
+/* OpenLDAP does it with LDAP_OPT_URI */
+# ifdef LDAP_OPT_URI
+# define SM_CONF_LDAP_INITIALIZE 1
+# endif /* LDAP_OPT_URI */
+# endif /* !SM_CONF_LDAP_INITIALIZE */
+# endif /* LDAPMAP */
+
+/* don't use strcpy() */
+# ifndef DO_NOT_USE_STRCPY
+# define DO_NOT_USE_STRCPY 1
+# endif /* ! DO_NOT_USE_STRCPY */
+
+#endif /* ! SM_CONFIG_H */
diff --git a/usr/src/cmd/sendmail/include/sm/debug.h b/usr/src/cmd/sendmail/include/sm/debug.h
new file mode 100644
index 0000000000..863a289741
--- /dev/null
+++ b/usr/src/cmd/sendmail/include/sm/debug.h
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 2000, 2001, 2003 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ * $Id: debug.h,v 1.16 2003/01/10 00:26:06 ca Exp $
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+** libsm debugging and tracing
+** See libsm/debug.html for documentation.
+*/
+
+#ifndef SM_DEBUG_H
+# define SM_DEBUG_H
+
+# include <sm/gen.h>
+# include <sm/io.h>
+
+/*
+** abstractions for printing trace messages
+*/
+
+extern SM_FILE_T *
+sm_debug_file __P((void));
+
+extern void
+sm_debug_setfile __P(( SM_FILE_T *));
+
+extern void PRINTFLIKE(1, 2)
+sm_dprintf __P((char *_fmt, ...));
+
+extern void
+sm_dflush __P((void));
+
+extern void
+sm_debug_close __P((void));
+
+/*
+** abstractions for setting and testing debug activation levels
+*/
+
+extern void
+sm_debug_addsettings_x __P((const char *));
+
+extern void
+sm_debug_addsetting_x __P((const char *, int));
+
+# define SM_DEBUG_UNKNOWN ((SM_ATOMIC_UINT_T)(-1))
+
+extern const char SmDebugMagic[];
+
+typedef struct sm_debug SM_DEBUG_T;
+struct sm_debug
+{
+ const char *sm_magic; /* points to SmDebugMagic */
+
+ /*
+ ** debug_level is the activation level of this debug
+ ** object. Level 0 means no debug activity.
+ ** It is initialized to SM_DEBUG_UNKNOWN, which indicates
+ ** that the true value is unknown. If debug_level ==
+ ** SM_DEBUG_UNKNOWN, then the access functions will look up
+ ** its true value in the internal table of debug settings.
+ */
+
+ SM_ATOMIC_UINT_T debug_level;
+
+ /*
+ ** debug_name is the name used to reference this SM_DEBUG
+ ** structure via the sendmail -d option.
+ */
+
+ char *debug_name;
+
+ /*
+ ** debug_desc is a literal character string of the form
+ ** "@(#)$Debug: <name> - <short description> $"
+ */
+
+ char *debug_desc;
+
+ /*
+ ** We keep a linked list of initialized SM_DEBUG structures
+ ** so that when sm_debug_addsetting is called, we can reset
+ ** them all back to the uninitialized state.
+ */
+
+ SM_DEBUG_T *debug_next;
+};
+
+# ifndef SM_DEBUG_CHECK
+# define SM_DEBUG_CHECK 1
+# endif /* ! SM_DEBUG_CHECK */
+
+# if SM_DEBUG_CHECK
+/*
+** This macro is cleverly designed so that if the debug object is below
+** the specified level, then the only overhead is a single comparison
+** (except for the first time this macro is invoked).
+*/
+
+# define sm_debug_active(debug, level) \
+ ((debug)->debug_level >= (level) && \
+ ((debug)->debug_level != SM_DEBUG_UNKNOWN || \
+ sm_debug_loadactive(debug, level)))
+
+# define sm_debug_level(debug) \
+ ((debug)->debug_level == SM_DEBUG_UNKNOWN \
+ ? sm_debug_loadlevel(debug) : (debug)->debug_level)
+
+# define sm_debug_unknown(debug) ((debug)->debug_level == SM_DEBUG_UNKNOWN)
+# else /* SM_DEBUG_CHECK */
+# define sm_debug_active(debug, level) 0
+# define sm_debug_level(debug) 0
+# define sm_debug_unknown(debug) 0
+# endif /* SM_DEBUG_CHECK */
+
+extern bool
+sm_debug_loadactive __P((SM_DEBUG_T *, int));
+
+extern int
+sm_debug_loadlevel __P((SM_DEBUG_T *));
+
+# define SM_DEBUG_INITIALIZER(name, desc) { \
+ SmDebugMagic, \
+ SM_DEBUG_UNKNOWN, \
+ name, \
+ desc, \
+ NULL}
+
+#endif /* ! SM_DEBUG_H */
diff --git a/usr/src/cmd/sendmail/include/sm/errstring.h b/usr/src/cmd/sendmail/include/sm/errstring.h
new file mode 100644
index 0000000000..bfb2b2f096
--- /dev/null
+++ b/usr/src/cmd/sendmail/include/sm/errstring.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 1998-2001, 2003 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ * $Id: errstring.h,v 1.9 2003/12/10 03:19:06 gshapiro Exp $
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+** Error codes.
+*/
+
+#ifndef SM_ERRSTRING_H
+# define SM_ERRSTRING_H
+
+#include <errno.h>
+#if NEEDINTERRNO
+extern int errno;
+#endif /* NEEDINTERRNO */
+
+/*
+** These are used in a few cases where we need some special
+** error codes, but where the system doesn't provide something
+** reasonable. They are printed in sm_errstring.
+*/
+
+#ifndef E_PSEUDOBASE
+# define E_PSEUDOBASE 256
+#endif /* ! E_PSEUDOBASE */
+
+#define E_SM_OPENTIMEOUT (E_PSEUDOBASE + 0) /* Timeout on file open */
+#define E_SM_NOSLINK (E_PSEUDOBASE + 1) /* Symbolic links not allowed */
+#define E_SM_NOHLINK (E_PSEUDOBASE + 2) /* Hard links not allowed */
+#define E_SM_REGONLY (E_PSEUDOBASE + 3) /* Regular files only */
+#define E_SM_ISEXEC (E_PSEUDOBASE + 4) /* Executable files not allowed */
+#define E_SM_WWDIR (E_PSEUDOBASE + 5) /* World writable directory */
+#define E_SM_GWDIR (E_PSEUDOBASE + 6) /* Group writable directory */
+#define E_SM_FILECHANGE (E_PSEUDOBASE + 7) /* File changed after open */
+#define E_SM_WWFILE (E_PSEUDOBASE + 8) /* World writable file */
+#define E_SM_GWFILE (E_PSEUDOBASE + 9) /* Group writable file */
+#define E_SM_GRFILE (E_PSEUDOBASE + 10) /* g readable file */
+#define E_SM_WRFILE (E_PSEUDOBASE + 11) /* o readable file */
+#define E_DNSBASE (E_PSEUDOBASE + 20) /* base for DNS h_errno */
+#define E_SMDBBASE (E_PSEUDOBASE + 40) /* base for libsmdb errors */
+#define E_LDAPBASE (E_PSEUDOBASE + 70) /* base for LDAP errors */
+#define E_LDAPURLBASE (E_PSEUDOBASE + 200) /* base for LDAP URL errors */
+
+
+/* libsmdb */
+#define SMDBE_OK 0
+#define SMDBE_MALLOC (E_SMDBBASE + 1)
+#define SMDBE_GDBM_IS_BAD (E_SMDBBASE + 2)
+#define SMDBE_UNSUPPORTED (E_SMDBBASE + 3)
+#define SMDBE_DUPLICATE (E_SMDBBASE + 4)
+#define SMDBE_BAD_OPEN (E_SMDBBASE + 5)
+#define SMDBE_NOT_FOUND (E_SMDBBASE + 6)
+#define SMDBE_UNKNOWN_DB_TYPE (E_SMDBBASE + 7)
+#define SMDBE_UNSUPPORTED_DB_TYPE (E_SMDBBASE + 8)
+#define SMDBE_INCOMPLETE (E_SMDBBASE + 9)
+#define SMDBE_KEY_EMPTY (E_SMDBBASE + 10)
+#define SMDBE_KEY_EXIST (E_SMDBBASE + 11)
+#define SMDBE_LOCK_DEADLOCK (E_SMDBBASE + 12)
+#define SMDBE_LOCK_NOT_GRANTED (E_SMDBBASE + 13)
+#define SMDBE_LOCK_NOT_HELD (E_SMDBBASE + 14)
+#define SMDBE_RUN_RECOVERY (E_SMDBBASE + 15)
+#define SMDBE_IO_ERROR (E_SMDBBASE + 16)
+#define SMDBE_READ_ONLY (E_SMDBBASE + 17)
+#define SMDBE_DB_NAME_TOO_LONG (E_SMDBBASE + 18)
+#define SMDBE_INVALID_PARAMETER (E_SMDBBASE + 19)
+#define SMDBE_ONLY_SUPPORTS_ONE_CURSOR (E_SMDBBASE + 20)
+#define SMDBE_NOT_A_VALID_CURSOR (E_SMDBBASE + 21)
+#define SMDBE_LAST_ENTRY (E_SMDBBASE + 22)
+#define SMDBE_OLD_VERSION (E_SMDBBASE + 23)
+#define SMDBE_VERSION_MISMATCH (E_SMDBBASE + 24)
+
+extern const char *sm_errstring __P((int _errno));
+
+
+#endif /* SM_ERRSTRING_H */
diff --git a/usr/src/cmd/sendmail/include/sm/exc.h b/usr/src/cmd/sendmail/include/sm/exc.h
new file mode 100644
index 0000000000..eea62b1188
--- /dev/null
+++ b/usr/src/cmd/sendmail/include/sm/exc.h
@@ -0,0 +1,188 @@
+/*
+ * Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ * $Id: exc.h,v 1.23 2001/06/07 20:04:53 ca Exp $
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+** libsm exception handling
+** See libsm/exc.html for documentation.
+*/
+
+#ifndef SM_EXC_H
+# define SM_EXC_H
+
+#include <sm/setjmp.h>
+#include <sm/io.h>
+#include <sm/gen.h>
+#include <sm/assert.h>
+
+typedef struct sm_exc SM_EXC_T;
+typedef struct sm_exc_type SM_EXC_TYPE_T;
+typedef union sm_val SM_VAL_T;
+
+/*
+** Exception types
+*/
+
+extern const char SmExcTypeMagic[];
+
+struct sm_exc_type
+{
+ const char *sm_magic;
+ const char *etype_category;
+ const char *etype_argformat;
+ void (*etype_print) __P((SM_EXC_T *, SM_FILE_T *));
+ const char *etype_printcontext;
+};
+
+extern const SM_EXC_TYPE_T SmEtypeOs;
+extern const SM_EXC_TYPE_T SmEtypeErr;
+
+extern void
+sm_etype_printf __P((
+ SM_EXC_T *_exc,
+ SM_FILE_T *_stream));
+
+/*
+** Exception objects
+*/
+
+extern const char SmExcMagic[];
+
+union sm_val
+{
+ int v_int;
+ long v_long;
+ char *v_str;
+ SM_EXC_T *v_exc;
+};
+
+struct sm_exc
+{
+ const char *sm_magic;
+ size_t exc_refcount;
+ const SM_EXC_TYPE_T *exc_type;
+ SM_VAL_T *exc_argv;
+};
+
+# define SM_EXC_INITIALIZER(type, argv) \
+ { \
+ SmExcMagic, \
+ 0, \
+ type, \
+ argv, \
+ }
+
+extern SM_EXC_T *
+sm_exc_new_x __P((
+ const SM_EXC_TYPE_T *_type,
+ ...));
+
+extern SM_EXC_T *
+sm_exc_addref __P((
+ SM_EXC_T *_exc));
+
+extern void
+sm_exc_free __P((
+ SM_EXC_T *_exc));
+
+extern bool
+sm_exc_match __P((
+ SM_EXC_T *_exc,
+ const char *_pattern));
+
+extern void
+sm_exc_write __P((
+ SM_EXC_T *_exc,
+ SM_FILE_T *_stream));
+
+extern void
+sm_exc_print __P((
+ SM_EXC_T *_exc,
+ SM_FILE_T *_stream));
+
+extern SM_DEAD(void
+sm_exc_raise_x __P((
+ SM_EXC_T *_exc)));
+
+extern SM_DEAD(void
+sm_exc_raisenew_x __P((
+ const SM_EXC_TYPE_T *_type,
+ ...)));
+
+/*
+** Exception handling
+*/
+
+typedef void (*SM_EXC_DEFAULT_HANDLER_T) __P((SM_EXC_T *));
+
+extern void
+sm_exc_newthread __P((
+ SM_EXC_DEFAULT_HANDLER_T _handle));
+
+typedef struct sm_exc_handler SM_EXC_HANDLER_T;
+struct sm_exc_handler
+{
+ SM_EXC_T *eh_value;
+ SM_JMPBUF_T eh_context;
+ SM_EXC_HANDLER_T *eh_parent;
+ int eh_state;
+};
+
+/* values for eh_state */
+enum
+{
+ SM_EH_PUSHED = 2,
+ SM_EH_POPPED = 0,
+ SM_EH_HANDLED = 1
+};
+
+extern SM_EXC_HANDLER_T *SmExcHandler;
+
+# define SM_TRY { SM_EXC_HANDLER_T _h; \
+ do { \
+ _h.eh_value = NULL; \
+ _h.eh_parent = SmExcHandler; \
+ _h.eh_state = SM_EH_PUSHED; \
+ SmExcHandler = &_h; \
+ if (sm_setjmp_nosig(_h.eh_context) == 0) {
+
+# define SM_FINALLY SM_ASSERT(SmExcHandler == &_h); \
+ } \
+ if (sm_setjmp_nosig(_h.eh_context) == 0) {
+
+# define SM_EXCEPT(e,pat) } \
+ if (_h.eh_state == SM_EH_HANDLED) \
+ break; \
+ if (_h.eh_state == SM_EH_PUSHED) { \
+ SM_ASSERT(SmExcHandler == &_h); \
+ SmExcHandler = _h.eh_parent; \
+ } \
+ _h.eh_state = sm_exc_match(_h.eh_value,pat) \
+ ? SM_EH_HANDLED : SM_EH_POPPED; \
+ if (_h.eh_state == SM_EH_HANDLED) { \
+ SM_UNUSED(SM_EXC_T *e) = _h.eh_value;
+
+# define SM_END_TRY } \
+ } while (0); \
+ if (_h.eh_state == SM_EH_PUSHED) { \
+ SM_ASSERT(SmExcHandler == &_h); \
+ SmExcHandler = _h.eh_parent; \
+ if (_h.eh_value != NULL) \
+ sm_exc_raise_x(_h.eh_value); \
+ } else if (_h.eh_state == SM_EH_POPPED) { \
+ if (_h.eh_value != NULL) \
+ sm_exc_raise_x(_h.eh_value); \
+ } else \
+ sm_exc_free(_h.eh_value); \
+ }
+
+#endif /* SM_EXC_H */
diff --git a/usr/src/cmd/sendmail/include/sm/fdset.h b/usr/src/cmd/sendmail/include/sm/fdset.h
new file mode 100644
index 0000000000..86b2839a02
--- /dev/null
+++ b/usr/src/cmd/sendmail/include/sm/fdset.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2001, 2002 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ * $Id: fdset.h,v 1.3.10.2 2002/12/10 04:02:25 ca Exp $
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#ifndef SM_FDSET_H
+# define SM_FDSET_H
+
+/*
+** Note: SM_FD_OK_SELECT(fd) requires that ValidSocket(fd) has been checked
+** before.
+*/
+
+# define SM_FD_SET(fd, pfdset) FD_SET(fd, pfdset)
+# define SM_FD_ISSET(fd, pfdset) FD_ISSET(fd, pfdset)
+# define SM_FD_SETSIZE FD_SETSIZE
+# define SM_FD_OK_SELECT(fd) (FD_SETSIZE <= 0 || (fd) < FD_SETSIZE)
+
+#endif /* SM_FDSET_H */
diff --git a/usr/src/cmd/sendmail/include/sm/gen.h b/usr/src/cmd/sendmail/include/sm/gen.h
new file mode 100644
index 0000000000..19875fea3d
--- /dev/null
+++ b/usr/src/cmd/sendmail/include/sm/gen.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2000-2002 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ * $Id: gen.h,v 1.23 2003/11/04 18:51:54 ca Exp $
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+** libsm general definitions
+** See libsm/gen.html for documentation.
+*/
+
+#ifndef SM_GEN_H
+# define SM_GEN_H
+
+# include <sm/config.h>
+# include <sm/cdefs.h>
+# include <sm/types.h>
+
+/*
+** Define SM_RCSID and SM_IDSTR,
+** macros used to embed RCS and SCCS identification strings in object files.
+*/
+
+# ifdef lint
+# define SM_RCSID(str)
+# define SM_IDSTR(id,str)
+# else /* lint */
+# define SM_RCSID(str) SM_UNUSED(static const char RcsId[]) = str;
+# define SM_IDSTR(id,str) SM_UNUSED(static const char id[]) = str;
+# endif /* lint */
+
+/*
+** Define NULL and offsetof (from the C89 standard)
+*/
+
+# if SM_CONF_STDDEF_H
+# include <stddef.h>
+# else /* SM_CONF_STDDEF_H */
+# ifndef NULL
+# define NULL 0
+# endif /* ! NULL */
+# define offsetof(type, member) ((size_t)(&((type *)0)->member))
+# endif /* SM_CONF_STDDEF_H */
+
+/*
+** Define bool, true, false (from the C99 standard)
+*/
+
+# if SM_CONF_STDBOOL_H
+# include <stdbool.h>
+# else /* SM_CONF_STDBOOL_H */
+# ifndef __cplusplus
+ typedef int bool;
+# define false 0
+# define true 1
+# define __bool_true_false_are_defined 1
+# endif /* ! __cplusplus */
+# endif /* SM_CONF_STDBOOL_H */
+
+/*
+** Define SM_MAX and SM_MIN
+*/
+
+# define SM_MAX(a, b) ((a) > (b) ? (a) : (b))
+# define SM_MIN(a, b) ((a) < (b) ? (a) : (b))
+
+/* Define SM_SUCCESS and SM_FAILURE */
+# define SM_SUCCESS 0
+# define SM_FAILURE (-1)
+
+/* XXX This needs to be fixed when we start to use threads: */
+typedef int SM_ATOMIC_INT_T;
+typedef unsigned int SM_ATOMIC_UINT_T;
+
+#endif /* SM_GEN_H */
diff --git a/usr/src/cmd/sendmail/include/sm/heap.h b/usr/src/cmd/sendmail/include/sm/heap.h
new file mode 100644
index 0000000000..0b0f8504d6
--- /dev/null
+++ b/usr/src/cmd/sendmail/include/sm/heap.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ * $Id: heap.h,v 1.22 2001/09/04 22:41:55 ca Exp $
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+** Sendmail debugging memory allocation package.
+** See libsm/heap.html for documentation.
+*/
+
+#ifndef SM_HEAP_H
+# define SM_HEAP_H
+
+# include <sm/io.h>
+# include <stdlib.h>
+# include <sm/debug.h>
+# include <sm/exc.h>
+
+/* change default to 0 for production? */
+# ifndef SM_HEAP_CHECK
+# define SM_HEAP_CHECK 1
+# endif /* ! SM_HEAP_CHECK */
+
+# if SM_HEAP_CHECK
+# define sm_malloc_x(sz) sm_malloc_tagged_x(sz, __FILE__, __LINE__, SmHeapGroup)
+# define sm_malloc(size) sm_malloc_tagged(size, __FILE__, __LINE__, SmHeapGroup)
+# define sm_free(ptr) sm_free_tagged(ptr, __FILE__, __LINE__)
+
+extern void *sm_malloc_tagged __P((size_t, char *, int, int));
+extern void *sm_malloc_tagged_x __P((size_t, char *, int, int));
+extern void sm_free_tagged __P((void *, char *, int));
+extern void *sm_realloc_x __P((void *, size_t));
+extern bool sm_heap_register __P((void *, size_t, char *, int, int));
+extern void sm_heap_checkptr_tagged __P((void *, char *, int));
+extern void sm_heap_report __P((SM_FILE_T *, int));
+
+# else /* SM_HEAP_CHECK */
+# define sm_malloc_tagged(size, file, line, grp) sm_malloc(size)
+# define sm_malloc_tagged_x(size, file, line, grp) sm_malloc_x(size)
+# define sm_free_tagged(ptr, file, line) sm_free(ptr)
+# define sm_heap_register(ptr, size, file, line, grp) (true)
+# define sm_heap_checkptr_tagged(ptr, tag, num) ((void)0)
+# define sm_heap_report(file, verbose) ((void)0)
+
+extern void *sm_malloc __P((size_t));
+extern void *sm_malloc_x __P((size_t));
+extern void *sm_realloc_x __P((void *, size_t));
+extern void sm_free __P((void *));
+# endif /* SM_HEAP_CHECK */
+
+extern void *sm_realloc __P((void *, size_t));
+
+# define sm_heap_checkptr(ptr) sm_heap_checkptr_tagged(ptr, __FILE__, __LINE__)
+
+#if 0
+/*
+** sm_f[mc]alloc are plug in replacements for malloc and calloc
+** which can be used in a context requiring a function pointer,
+** and which are compatible with sm_free. Warning: sm_heap_report
+** cannot report where storage leaked by sm_f[mc]alloc was allocated.
+*/
+
+/* XXX unused right now */
+
+extern void *
+sm_fmalloc __P((
+ size_t));
+
+extern void *
+sm_fcalloc __P((
+ size_t,
+ size_t));
+#endif /* 0 */
+
+/*
+** Allocate 'permanent' storage that can be freed but may still be
+** allocated when the process exits. sm_heap_report will not complain
+** about a storage leak originating from a call to sm_pmalloc.
+*/
+
+# define sm_pmalloc(size) sm_malloc_tagged(size, __FILE__, __LINE__, 0)
+# define sm_pmalloc_x(size) sm_malloc_tagged_x(size, __FILE__, __LINE__, 0)
+
+# define sm_heap_group() SmHeapGroup
+# define sm_heap_setgroup(g) (SmHeapGroup = (g))
+# define sm_heap_newgroup() (SmHeapGroup = ++SmHeapMaxGroup)
+
+extern int SmHeapGroup;
+extern int SmHeapMaxGroup;
+
+extern SM_DEBUG_T SmHeapTrace;
+extern SM_DEBUG_T SmHeapCheck;
+extern SM_EXC_T SmHeapOutOfMemory;
+
+#endif /* ! SM_HEAP_H */
diff --git a/usr/src/cmd/sendmail/include/sm/io.h b/usr/src/cmd/sendmail/include/sm/io.h
new file mode 100644
index 0000000000..93808b5f9c
--- /dev/null
+++ b/usr/src/cmd/sendmail/include/sm/io.h
@@ -0,0 +1,391 @@
+/*
+ * Copyright (c) 2000-2002, 2004 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1990
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ * $Id: io.h,v 1.24 2004/03/03 19:14:49 ca Exp $
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*-
+ * @(#)stdio.h 5.17 (Berkeley) 6/3/91
+ */
+
+#ifndef SM_IO_H
+#define SM_IO_H
+
+#include <stdio.h>
+#include <sm/gen.h>
+#include <sm/varargs.h>
+
+/* mode for sm io (exposed) */
+#define SM_IO_RDWR 1 /* read-write */
+#define SM_IO_RDONLY 2 /* read-only */
+#define SM_IO_WRONLY 3 /* write-only */
+#define SM_IO_APPEND 4 /* write-only from eof */
+#define SM_IO_APPENDRW 5 /* read-write from eof */
+#define SM_IO_RDWRTR 6 /* read-write with truncation indicated */
+
+# define SM_IO_BINARY 0x0 /* binary mode: not used in Unix */
+#define SM_IS_BINARY(mode) (((mode) & SM_IO_BINARY) != 0)
+#define SM_IO_MODE(mode) ((mode) & 0x0f)
+
+#define SM_IO_RDWR_B (SM_IO_RDWR|SM_IO_BINARY)
+#define SM_IO_RDONLY_B (SM_IO_RDONLY|SM_IO_BINARY)
+#define SM_IO_WRONLY_B (SM_IO_WRONLY|SM_IO_BINARY)
+#define SM_IO_APPEND_B (SM_IO_APPEND|SM_IO_BINARY)
+#define SM_IO_APPENDRW_B (SM_IO_APPENDRW|SM_IO_BINARY)
+#define SM_IO_RDWRTR_B (SM_IO_RDWRTR|SM_IO_BINARY)
+
+/* for sm_io_fseek, et al api's (exposed) */
+#define SM_IO_SEEK_SET 0
+#define SM_IO_SEEK_CUR 1
+#define SM_IO_SEEK_END 2
+
+/* flags for info what's with different types (exposed) */
+#define SM_IO_WHAT_MODE 1
+#define SM_IO_WHAT_VECTORS 2
+#define SM_IO_WHAT_FD 3
+#define SM_IO_WHAT_TYPE 4
+#define SM_IO_WHAT_ISTYPE 5
+#define SM_IO_IS_READABLE 6
+#define SM_IO_WHAT_TIMEOUT 7
+#define SM_IO_WHAT_SIZE 8
+
+/* info flags (exposed) */
+#define SM_IO_FTYPE_CREATE 1
+#define SM_IO_FTYPE_MODIFY 2
+#define SM_IO_FTYPE_DELETE 3
+
+#define SM_IO_SL_PRIO 1
+
+#define SM_IO_OPEN_MAX 20
+
+/* for internal buffers */
+struct smbuf
+{
+ unsigned char *smb_base;
+ int smb_size;
+};
+
+/*
+** sm I/O state variables (internal only).
+**
+** The following always hold:
+**
+** if (flags&(SMLBF|SMWR)) == (SMLBF|SMWR),
+** lbfsize is -bf.size, else lbfsize is 0
+** if flags&SMRD, w is 0
+** if flags&SMWR, r is 0
+**
+** This ensures that the getc and putc macros (or inline functions) never
+** try to write or read from a file that is in `read' or `write' mode.
+** (Moreover, they can, and do, automatically switch from read mode to
+** write mode, and back, on "r+" and "w+" files.)
+**
+** lbfsize is used only to make the inline line-buffered output stream
+** code as compact as possible.
+**
+** ub, up, and ur are used when ungetc() pushes back more characters
+** than fit in the current bf, or when ungetc() pushes back a character
+** that does not match the previous one in bf. When this happens,
+** ub.base becomes non-nil (i.e., a stream has ungetc() data iff
+** ub.base!=NULL) and up and ur save the current values of p and r.
+*/
+
+typedef struct sm_file SM_FILE_T;
+
+struct sm_file
+{
+ const char *sm_magic; /* This SM_FILE_T is free when NULL */
+ unsigned char *f_p; /* current position in (some) buffer */
+ int f_r; /* read space left for getc() */
+ int f_w; /* write space left for putc() */
+ long f_flags; /* flags, below */
+ short f_file; /* fileno, if Unix fd, else -1 */
+ struct smbuf f_bf; /* the buffer (>= 1 byte, if !NULL) */
+ int f_lbfsize; /* 0 or -bf.size, for inline putc */
+
+ /* These can be used for any purpose by a file type implementation: */
+ void *f_cookie;
+ int f_ival;
+
+ /* operations */
+ int (*f_close) __P((SM_FILE_T *));
+ ssize_t (*f_read) __P((SM_FILE_T *, char *, size_t));
+ off_t (*f_seek) __P((SM_FILE_T *, off_t, int));
+ ssize_t (*f_write) __P((SM_FILE_T *, const char *, size_t));
+ int (*f_open) __P((SM_FILE_T *, const void *, int,
+ const void *));
+ int (*f_setinfo) __P((SM_FILE_T *, int , void *));
+ int (*f_getinfo) __P((SM_FILE_T *, int , void *));
+ int f_timeout;
+ int f_timeoutstate; /* either blocking or non-blocking */
+ char *f_type; /* for by-type lookups */
+ struct sm_file *f_flushfp; /* flush this before reading parent */
+ struct sm_file *f_modefp; /* sync mode with this fp */
+
+ /* separate buffer for long sequences of ungetc() */
+ struct smbuf f_ub; /* ungetc buffer */
+ unsigned char *f_up; /* saved f_p when f_p is doing ungetc */
+ int f_ur; /* saved f_r when f_r is counting ungetc */
+
+ /* tricks to meet minimum requirements even when malloc() fails */
+ unsigned char f_ubuf[3]; /* guarantee an ungetc() buffer */
+ unsigned char f_nbuf[1]; /* guarantee a getc() buffer */
+
+ /* Unix stdio files get aligned to block boundaries on fseek() */
+ int f_blksize; /* stat.st_blksize (may be != bf.size) */
+ off_t f_lseekoff; /* current lseek offset */
+ int f_dup_cnt; /* count file dup'd */
+};
+
+__BEGIN_DECLS
+extern SM_FILE_T SmIoF[];
+extern const char SmFileMagic[];
+extern SM_FILE_T SmFtStdio_def;
+extern SM_FILE_T SmFtStdiofd_def;
+extern SM_FILE_T SmFtString_def;
+extern SM_FILE_T SmFtSyslog_def;
+extern SM_FILE_T SmFtRealStdio_def;
+
+#define SMIOIN_FILENO 0
+#define SMIOOUT_FILENO 1
+#define SMIOERR_FILENO 2
+#define SMIOSTDIN_FILENO 3
+#define SMIOSTDOUT_FILENO 4
+#define SMIOSTDERR_FILENO 5
+
+/* Common predefined and already (usually) open files (exposed) */
+#define smioin (&SmIoF[SMIOIN_FILENO])
+#define smioout (&SmIoF[SMIOOUT_FILENO])
+#define smioerr (&SmIoF[SMIOERR_FILENO])
+#define smiostdin (&SmIoF[SMIOSTDIN_FILENO])
+#define smiostdout (&SmIoF[SMIOSTDOUT_FILENO])
+#define smiostderr (&SmIoF[SMIOSTDERR_FILENO])
+
+#define SmFtStdio (&SmFtStdio_def)
+#define SmFtStdiofd (&SmFtStdiofd_def)
+#define SmFtString (&SmFtString_def)
+#define SmFtSyslog (&SmFtSyslog_def)
+#define SmFtRealStdio (&SmFtRealStdio_def)
+
+#ifdef __STDC__
+# define SM_IO_SET_TYPE(f, name, open, close, read, write, seek, get, set, timeout) \
+ (f) = {SmFileMagic, (unsigned char *) 0, 0, 0, 0L, -1, {0}, 0, (void *) 0,\
+ 0, (close), (read), (seek), (write), (open), (set), (get), (timeout),\
+ 0, (name)}
+# define SM_IO_INIT_TYPE(f, name, open, close, read, write, seek, get, set, timeout)
+
+#else /* __STDC__ */
+# define SM_IO_SET_TYPE(f, name, open, close, read, write, seek, get, set, timeout) (f)
+# define SM_IO_INIT_TYPE(f, name, open, close, read, write, seek, get, set, timeout) \
+ (f).sm_magic = SmFileMagic; \
+ (f).f_p = (unsigned char *) 0; \
+ (f).f_r = 0; \
+ (f).f_w = 0; \
+ (f).f_flags = 0L; \
+ (f).f_file = 0; \
+ (f).f_bf.smb_base = (unsigned char *) 0; \
+ (f).f_bf.smb_size = 0; \
+ (f).f_lbfsize = 0; \
+ (f).f_cookie = (void *) 0; \
+ (f).f_ival = 0; \
+ (f).f_close = (close); \
+ (f).f_read = (read); \
+ (f).f_seek = (seek); \
+ (f).f_write = (write); \
+ (f).f_open = (open); \
+ (f).f_setinfo = (set); \
+ (f).f_getinfo = (get); \
+ (f).f_timeout = (timeout); \
+ (f).f_timeoutstate = 0; \
+ (f).f_type = (name);
+
+#endif /* __STDC__ */
+
+__END_DECLS
+
+/* Internal flags */
+#define SMFBF 0x000001 /* XXXX fully buffered */
+#define SMLBF 0x000002 /* line buffered */
+#define SMNBF 0x000004 /* unbuffered */
+#define SMNOW 0x000008 /* Flush each write; take read now */
+#define SMRD 0x000010 /* OK to read */
+#define SMWR 0x000020 /* OK to write */
+ /* RD and WR are never simultaneously asserted */
+#define SMRW 0x000040 /* open for reading & writing */
+#define SMFEOF 0x000080 /* found EOF */
+#define SMERR 0x000100 /* found error */
+#define SMMBF 0x000200 /* buf is from malloc */
+#define SMAPP 0x000400 /* fdopen()ed in append mode */
+#define SMSTR 0x000800 /* this is an snprintf string */
+#define SMOPT 0x001000 /* do fseek() optimisation */
+#define SMNPT 0x002000 /* do not do fseek() optimisation */
+#define SMOFF 0x004000 /* set iff offset is in fact correct */
+#define SMALC 0x010000 /* allocate string space dynamically */
+
+#define SMMODEMASK 0x0070 /* read/write mode */
+
+/* defines for timeout constants */
+#define SM_TIME_IMMEDIATE (0)
+#define SM_TIME_FOREVER (-1)
+#define SM_TIME_DEFAULT (-2)
+
+/* timeout state for blocking */
+#define SM_TIME_BLOCK (0) /* XXX just bool? */
+#define SM_TIME_NONBLOCK (1)
+
+/* Exposed buffering type flags */
+#define SM_IO_FBF 0 /* setvbuf should set fully buffered */
+#define SM_IO_LBF 1 /* setvbuf should set line buffered */
+#define SM_IO_NBF 2 /* setvbuf should set unbuffered */
+
+/* setvbuf buffered, but through at lower file type layers */
+#define SM_IO_NOW 3
+
+/*
+** size of buffer used by setbuf.
+** If underlying filesystem blocksize is discoverable that is used instead
+*/
+
+#define SM_IO_BUFSIZ 4096
+
+#define SM_IO_EOF (-1)
+
+/* Functions defined in ANSI C standard. */
+__BEGIN_DECLS
+SM_FILE_T *sm_io_autoflush __P((SM_FILE_T *, SM_FILE_T *));
+void sm_io_automode __P((SM_FILE_T *, SM_FILE_T *));
+void sm_io_clearerr __P((SM_FILE_T *));
+int sm_io_close __P((SM_FILE_T *, int SM_NONVOLATILE));
+SM_FILE_T *sm_io_dup __P((SM_FILE_T *));
+int sm_io_eof __P((SM_FILE_T *));
+int sm_io_error __P((SM_FILE_T *));
+char *sm_io_fgets __P((SM_FILE_T *, int, char *, int));
+int sm_io_flush __P((SM_FILE_T *, int SM_NONVOLATILE));
+
+int PRINTFLIKE(3, 4)
+sm_io_fprintf __P((SM_FILE_T *, int, const char *, ...));
+
+int sm_io_fputs __P((SM_FILE_T *, int, const char *));
+
+int SCANFLIKE(3, 4)
+sm_io_fscanf __P((SM_FILE_T *, int, const char *, ...));
+
+int sm_io_getc __P((SM_FILE_T *, int));
+int sm_io_getinfo __P((SM_FILE_T *, int, void *));
+SM_FILE_T *sm_io_open __P((const SM_FILE_T *, int SM_NONVOLATILE, const void *,
+ int, const void *));
+int sm_io_purge __P((SM_FILE_T *));
+int sm_io_putc __P((SM_FILE_T *, int, int));
+size_t sm_io_read __P((SM_FILE_T *, int, void *, size_t));
+SM_FILE_T *sm_io_reopen __P((const SM_FILE_T *, int SM_NONVOLATILE,
+ const void *, int, const void *, SM_FILE_T *));
+void sm_io_rewind __P((SM_FILE_T *, int));
+int sm_io_seek __P((SM_FILE_T *, int SM_NONVOLATILE, long SM_NONVOLATILE,
+ int SM_NONVOLATILE));
+int sm_io_setinfo __P((SM_FILE_T *, int, void *));
+int sm_io_setvbuf __P((SM_FILE_T *, int, char *, int, size_t));
+
+int SCANFLIKE(2, 3)
+sm_io_sscanf __P((const char *, char const *, ...));
+
+long sm_io_tell __P((SM_FILE_T *, int SM_NONVOLATILE));
+int sm_io_ungetc __P((SM_FILE_T *, int, int));
+int sm_io_vfprintf __P((SM_FILE_T *, int, const char *, va_list));
+size_t sm_io_write __P((SM_FILE_T *, int, const void *, size_t));
+
+void sm_strio_init __P((SM_FILE_T *, char *, size_t));
+
+extern SM_FILE_T *
+sm_io_fopen __P((
+ char *_pathname,
+ int _flags,
+ ...));
+
+extern SM_FILE_T *
+sm_io_stdioopen __P((
+ FILE *_stream,
+ char *_mode));
+
+extern int
+sm_vasprintf __P((
+ char **_str,
+ const char *_fmt,
+ va_list _ap));
+
+extern int
+sm_vsnprintf __P((
+ char *,
+ size_t,
+ const char *,
+ va_list));
+
+extern void
+sm_perror __P((
+ const char *));
+
+__END_DECLS
+
+/*
+** Functions internal to the implementation.
+*/
+
+__BEGIN_DECLS
+int sm_rget __P((SM_FILE_T *, int));
+int sm_vfscanf __P((SM_FILE_T *, int SM_NONVOLATILE, const char *,
+ va_list SM_NONVOLATILE));
+int sm_wbuf __P((SM_FILE_T *, int, int));
+__END_DECLS
+
+/*
+** The macros are here so that we can
+** define function versions in the library.
+*/
+
+#define sm_getc(f, t) \
+ (--(f)->f_r < 0 ? \
+ sm_rget(f, t) : \
+ (int)(*(f)->f_p++))
+
+/*
+** This has been tuned to generate reasonable code on the vax using pcc.
+** (It also generates reasonable x86 code using gcc.)
+*/
+
+#define sm_putc(f, t, c) \
+ (--(f)->f_w < 0 ? \
+ (f)->f_w >= (f)->f_lbfsize ? \
+ (*(f)->f_p = (c)), *(f)->f_p != '\n' ? \
+ (int)*(f)->f_p++ : \
+ sm_wbuf(f, t, '\n') : \
+ sm_wbuf(f, t, (int)(c)) : \
+ (*(f)->f_p = (c), (int)*(f)->f_p++))
+
+#define sm_eof(p) (((p)->f_flags & SMFEOF) != 0)
+#define sm_error(p) (((p)->f_flags & SMERR) != 0)
+#define sm_clearerr(p) ((void)((p)->f_flags &= ~(SMERR|SMFEOF)))
+
+#define sm_io_eof(p) sm_eof(p)
+#define sm_io_error(p) sm_error(p)
+
+#define sm_io_clearerr(p) sm_clearerr(p)
+
+#ifndef lint
+# ifndef _POSIX_SOURCE
+# define sm_io_getc(fp, t) sm_getc(fp, t)
+# define sm_io_putc(fp, t, x) sm_putc(fp, t, x)
+# endif /* _POSIX_SOURCE */
+#endif /* lint */
+
+#endif /* SM_IO_H */
diff --git a/usr/src/cmd/sendmail/include/sm/ldap.h b/usr/src/cmd/sendmail/include/sm/ldap.h
new file mode 100644
index 0000000000..7b9e6f9584
--- /dev/null
+++ b/usr/src/cmd/sendmail/include/sm/ldap.h
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 2001-2002 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ * $Id: ldap.h,v 1.27 2003/12/20 09:23:47 gshapiro Exp $
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#ifndef SM_LDAP_H
+# define SM_LDAP_H
+
+# include <sm/conf.h>
+# include <sm/rpool.h>
+
+/*
+** NOTE: These should be changed from LDAPMAP_* to SM_LDAP_*
+** in the next major release (8.13) of sendmail.
+*/
+
+# ifndef LDAPMAP_MAX_ATTR
+# define LDAPMAP_MAX_ATTR 64
+# endif /* ! LDAPMAP_MAX_ATTR */
+# ifndef LDAPMAP_MAX_FILTER
+# define LDAPMAP_MAX_FILTER 1024
+# endif /* ! LDAPMAP_MAX_FILTER */
+# ifndef LDAPMAP_MAX_PASSWD
+# define LDAPMAP_MAX_PASSWD 256
+# endif /* ! LDAPMAP_MAX_PASSWD */
+
+# if LDAPMAP
+
+/* Attribute types */
+# define SM_LDAP_ATTR_NONE (-1)
+# define SM_LDAP_ATTR_OBJCLASS 0
+# define SM_LDAP_ATTR_NORMAL 1
+# define SM_LDAP_ATTR_DN 2
+# define SM_LDAP_ATTR_FILTER 3
+# define SM_LDAP_ATTR_URL 4
+
+/* sm_ldap_results() flags */
+# define SM_LDAP_SINGLEMATCH 0x0001
+# define SM_LDAP_MATCHONLY 0x0002
+# define SM_LDAP_USE_ALLATTR 0x0004
+
+struct sm_ldap_struct
+{
+ /* needed for ldap_open or ldap_init */
+ char *ldap_uri;
+ char *ldap_host;
+ int ldap_port;
+ int ldap_version;
+ pid_t ldap_pid;
+
+ /* options set in ld struct before ldap_bind_s */
+ int ldap_deref;
+ time_t ldap_timelimit;
+ int ldap_sizelimit;
+ int ldap_options;
+
+ /* args for ldap_bind_s */
+ LDAP *ldap_ld;
+ char *ldap_binddn;
+ char *ldap_secret;
+ int ldap_method;
+
+ /* args for ldap_search */
+ char *ldap_base;
+ int ldap_scope;
+ char *ldap_filter;
+ char *ldap_attr[LDAPMAP_MAX_ATTR + 1];
+ int ldap_attr_type[LDAPMAP_MAX_ATTR + 1];
+ char *ldap_attr_needobjclass[LDAPMAP_MAX_ATTR + 1];
+ bool ldap_attrsonly;
+
+ /* args for ldap_result */
+ struct timeval ldap_timeout;
+ LDAPMessage *ldap_res;
+
+ /* ldapmap_lookup options */
+ char ldap_attrsep;
+
+ /* Linked list of maps sharing the same LDAP binding */
+ void *ldap_next;
+};
+
+typedef struct sm_ldap_struct SM_LDAP_STRUCT;
+
+struct sm_ldap_recurse_entry
+{
+ char *lr_search;
+ int lr_type;
+ LDAPURLDesc *lr_ludp;
+ char **lr_attrs;
+ bool lr_done;
+};
+
+struct sm_ldap_recurse_list
+{
+ int lr_size;
+ int lr_cnt;
+ struct sm_ldap_recurse_entry **lr_data;
+};
+
+typedef struct sm_ldap_recurse_entry SM_LDAP_RECURSE_ENTRY;
+typedef struct sm_ldap_recurse_list SM_LDAP_RECURSE_LIST;
+
+/* functions */
+extern void sm_ldap_clear __P((SM_LDAP_STRUCT *));
+extern bool sm_ldap_start __P((char *, SM_LDAP_STRUCT *));
+extern int sm_ldap_search __P((SM_LDAP_STRUCT *, char *));
+extern int sm_ldap_results __P((SM_LDAP_STRUCT *, int, int, int,
+ SM_RPOOL_T *, char **, int *, int *,
+ SM_LDAP_RECURSE_LIST *));
+extern void sm_ldap_setopts __P((LDAP *, SM_LDAP_STRUCT *));
+extern int sm_ldap_geterrno __P((LDAP *));
+extern void sm_ldap_close __P((SM_LDAP_STRUCT *));
+
+/* Portability defines */
+# if !SM_CONF_LDAP_MEMFREE
+# define ldap_memfree(x) ((void) 0)
+# endif /* !SM_CONF_LDAP_MEMFREE */
+
+# endif /* LDAPMAP */
+#endif /* ! SM_LDAP_H */
diff --git a/usr/src/cmd/sendmail/include/sm/limits.h b/usr/src/cmd/sendmail/include/sm/limits.h
new file mode 100644
index 0000000000..02d170bcb2
--- /dev/null
+++ b/usr/src/cmd/sendmail/include/sm/limits.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ * $Id: limits.h,v 1.6 2001/03/08 03:23:08 ca Exp $
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+** <sm/limits.h>
+** This header file is a portability wrapper for <limits.h>.
+** It includes <limits.h>, then it ensures that the following macros
+** from the C 1999 standard for <limits.h> are defined:
+** LLONG_MIN, LLONG_MAX
+** ULLONG_MAX
+*/
+
+#ifndef SM_LIMITS_H
+# define SM_LIMITS_H
+
+# include <limits.h>
+# include <sm/types.h>
+# include <sys/param.h>
+
+/*
+** The following assumes two's complement binary arithmetic.
+*/
+
+# ifndef LLONG_MIN
+# define LLONG_MIN ((LONGLONG_T)(~(ULLONG_MAX >> 1)))
+# endif /* ! LLONG_MIN */
+# ifndef LLONG_MAX
+# define LLONG_MAX ((LONGLONG_T)(ULLONG_MAX >> 1))
+# endif /* ! LLONG_MAX */
+# ifndef ULLONG_MAX
+# define ULLONG_MAX ((ULONGLONG_T)(-1))
+# endif /* ! ULLONG_MAX */
+
+/*
+** PATH_MAX is defined by the POSIX standard. All modern systems
+** provide it. Older systems define MAXPATHLEN in <sys/param.h> instead.
+*/
+
+# ifndef PATH_MAX
+# ifdef MAXPATHLEN
+# define PATH_MAX MAXPATHLEN
+# else /* MAXPATHLEN */
+# define PATH_MAX 2048
+# endif /* MAXPATHLEN */
+# endif /* ! PATH_MAX */
+
+#endif /* ! SM_LIMITS_H */
diff --git a/usr/src/cmd/sendmail/include/sm/mbdb.h b/usr/src/cmd/sendmail/include/sm/mbdb.h
new file mode 100644
index 0000000000..3bd125c1c7
--- /dev/null
+++ b/usr/src/cmd/sendmail/include/sm/mbdb.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2001-2002 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ * $Id: mbdb.h,v 1.6 2002/05/24 20:50:14 gshapiro Exp $
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#ifndef SM_MBDB_H
+# define SM_MBDB_H
+
+#include <pwd.h>
+#include <sm/types.h>
+#include <sm/limits.h>
+
+/*
+** This is an abstract interface for looking up local mail recipients.
+*/
+
+#define MBDB_MAXNAME 256
+#define SM_NO_UID ((uid_t)(-1))
+#define SM_NO_GID ((gid_t)(-1))
+
+typedef struct
+{
+ uid_t mbdb_uid;
+ gid_t mbdb_gid;
+ char mbdb_name[MBDB_MAXNAME];
+ char mbdb_fullname[MBDB_MAXNAME];
+ char mbdb_homedir[PATH_MAX];
+ char mbdb_shell[PATH_MAX];
+} SM_MBDB_T;
+
+extern int sm_mbdb_initialize __P((char *));
+extern void sm_mbdb_terminate __P((void));
+extern int sm_mbdb_lookup __P((char *, SM_MBDB_T *));
+extern void sm_mbdb_frompw __P((SM_MBDB_T *, struct passwd *));
+extern void sm_pwfullname __P((char *, char *, char *, size_t));
+
+#endif /* ! SM_MBDB_H */
diff --git a/usr/src/cmd/sendmail/include/sm/os/sm_os_sunos.h b/usr/src/cmd/sendmail/include/sm/os/sm_os_sunos.h
new file mode 100644
index 0000000000..c5968bbaa0
--- /dev/null
+++ b/usr/src/cmd/sendmail/include/sm/os/sm_os_sunos.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ * $Id: sm_os_sunos.h,v 1.14 2001/08/14 18:09:42 ca Exp $
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+** platform definitions for SunOS 4.0.3, SunOS 4.1.x and Solaris 2.x
+*/
+
+#define SM_OS_NAME "sunos"
+
+#ifdef SOLARIS
+/*
+** Solaris 2.x (aka SunOS 5.x)
+** M4 config file is devtools/OS/SunOS.5.x, which defines the SOLARIS macro.
+*/
+
+# define SM_CONF_LONGLONG 1
+# ifndef SM_CONF_SHM
+# define SM_CONF_SHM 1
+# endif /* SM_CONF_SHM */
+# ifndef SM_CONF_SEM
+# define SM_CONF_SEM 2
+# endif /* SM_CONF_SEM */
+# ifndef SM_CONF_MSG
+# define SM_CONF_MSG 1
+# endif /* SM_CONF_MSG */
+
+#else /* SOLARIS */
+
+/*
+** SunOS 4.0.3 or 4.1.x
+*/
+
+# define SM_CONF_SSIZE_T 0
+# ifndef SM_CONF_BROKEN_SIZE_T
+# define SM_CONF_BROKEN_SIZE_T 1 /* size_t is signed? */
+# endif /* SM_CONF_BROKEN_SIZE_T */
+
+# ifndef SM_CONF_BROKEN_STRTOD
+# define SM_CONF_BROKEN_STRTOD 1
+# endif /* ! SM_CONF_BROKEN_STRTOD */
+
+/* has memchr() prototype? (if not: needs memory.h) */
+# ifndef SM_CONF_MEMCHR
+# define SM_CONF_MEMCHR 0
+# endif /* ! SM_CONF_MEMCHR */
+
+# ifdef SUNOS403
+
+/*
+** SunOS 4.0.3
+** M4 config file is devtools/OS/SunOS4.0, which defines the SUNOS403 macro.
+*/
+
+# else /* SUNOS403 */
+
+/*
+** SunOS 4.1.x
+** M4 config file is devtools/OS/SunOS, which defines no macros.
+*/
+
+# endif /* SUNOS403 */
+#endif /* SOLARIS */
diff --git a/usr/src/cmd/sendmail/include/sm/path.h b/usr/src/cmd/sendmail/include/sm/path.h
new file mode 100644
index 0000000000..9627068a8b
--- /dev/null
+++ b/usr/src/cmd/sendmail/include/sm/path.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ * $Id: path.h,v 1.6 2001/04/03 01:53:00 gshapiro Exp $
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+** Portable names for standard filesystem paths
+** and macros for directories.
+*/
+
+#ifndef SM_PATH_H
+# define SM_PATH_H
+
+# include <sm/gen.h>
+
+# define SM_PATH_DEVNULL "/dev/null"
+# define SM_IS_DIR_DELIM(c) ((c) == '/')
+# define SM_FIRST_DIR_DELIM(s) strchr(s, '/')
+# define SM_LAST_DIR_DELIM(s) strrchr(s, '/')
+
+/* Warning: this must be accessible as array */
+# define SM_IS_DIR_START(s) ((s)[0] == '/')
+
+# define sm_path_isdevnull(path) (strcmp(path, "/dev/null") == 0)
+
+#endif /* ! SM_PATH_H */
diff --git a/usr/src/cmd/sendmail/include/sm/rpool.h b/usr/src/cmd/sendmail/include/sm/rpool.h
new file mode 100644
index 0000000000..18a34fa815
--- /dev/null
+++ b/usr/src/cmd/sendmail/include/sm/rpool.h
@@ -0,0 +1,191 @@
+/*
+ * Copyright (c) 2000-2001, 2003 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ * $Id: rpool.h,v 1.16 2003/09/05 23:07:49 ca Exp $
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+** libsm resource pools
+** See libsm/rpool.html for documentation.
+*/
+
+#ifndef SM_RPOOL_H
+# define SM_RPOOL_H
+
+# include <sm/gen.h>
+# include <sm/heap.h>
+# include <sm/string.h>
+
+/*
+** Each memory pool object consists of an SM_POOLLINK_T,
+** followed by a platform specific amount of padding,
+** followed by 'poolsize' bytes of pool data,
+** where 'poolsize' is the value of rpool->sm_poolsize at the time
+** the pool is allocated.
+*/
+
+typedef struct sm_poollink SM_POOLLINK_T;
+struct sm_poollink
+{
+ SM_POOLLINK_T *sm_pnext;
+};
+
+typedef void (*SM_RPOOL_RFREE_T) __P((void *_rcontext));
+
+typedef SM_RPOOL_RFREE_T *SM_RPOOL_ATTACH_T;
+
+typedef struct sm_resource SM_RESOURCE_T;
+struct sm_resource
+{
+ /*
+ ** Function for freeing this resource. It may be NULL,
+ ** meaning that this resource has already been freed.
+ */
+
+ SM_RPOOL_RFREE_T sm_rfree;
+ void *sm_rcontext; /* resource data */
+};
+
+# define SM_RLIST_MAX 511
+
+typedef struct sm_rlist SM_RLIST_T;
+struct sm_rlist
+{
+ SM_RESOURCE_T sm_rvec[SM_RLIST_MAX];
+ SM_RLIST_T *sm_rnext;
+};
+
+typedef struct
+{
+ /* Points to SmRpoolMagic, or is NULL if rpool is freed. */
+ const char *sm_magic;
+
+ /*
+ ** If this rpool object has no parent, then sm_parentlink
+ ** is NULL. Otherwise, we set *sm_parentlink = NULL
+ ** when this rpool is freed, so that it isn't freed a
+ ** second time when the parent is freed.
+ */
+
+ SM_RPOOL_RFREE_T *sm_parentlink;
+
+ /*
+ ** Memory pools
+ */
+
+ /* Size of the next pool to be allocated, not including the header. */
+ size_t sm_poolsize;
+
+ /*
+ ** If an sm_rpool_malloc_x request is too big to fit
+ ** in the current pool, and the request size > bigobjectsize,
+ ** then the object will be given its own malloc'ed block.
+ ** sm_bigobjectsize <= sm_poolsize. The maximum wasted space
+ ** at the end of a pool is maxpooledobjectsize - 1.
+ */
+
+ size_t sm_bigobjectsize;
+
+ /* Points to next free byte in the current pool. */
+ char *sm_poolptr;
+
+ /*
+ ** Number of bytes available in the current pool.
+ ** Initially 0. Set to 0 by sm_rpool_free.
+ */
+
+ size_t sm_poolavail;
+
+ /* Linked list of memory pools. Initially NULL. */
+ SM_POOLLINK_T *sm_pools;
+
+ /*
+ ** Resource lists
+ */
+
+ SM_RESOURCE_T *sm_rptr; /* Points to next free resource slot. */
+
+ /*
+ ** Number of available resource slots in current list.
+ ** Initially 0. Set to 0 by sm_rpool_free.
+ */
+
+ size_t sm_ravail;
+
+ /* Linked list of resource lists. Initially NULL. */
+ SM_RLIST_T *sm_rlists;
+
+#if _FFR_PERF_RPOOL
+ int sm_nbigblocks;
+ int sm_npools;
+#endif /* _FFR_PERF_RPOOL */
+
+} SM_RPOOL_T;
+
+extern SM_RPOOL_T *
+sm_rpool_new_x __P((
+ SM_RPOOL_T *_parent));
+
+extern void
+sm_rpool_free __P((
+ SM_RPOOL_T *_rpool));
+
+# if SM_HEAP_CHECK
+extern void *
+sm_rpool_malloc_tagged_x __P((
+ SM_RPOOL_T *_rpool,
+ size_t _size,
+ char *_file,
+ int _line,
+ int _group));
+# define sm_rpool_malloc_x(rpool, size) \
+ sm_rpool_malloc_tagged_x(rpool, size, __FILE__, __LINE__, SmHeapGroup)
+extern void *
+sm_rpool_malloc_tagged __P((
+ SM_RPOOL_T *_rpool,
+ size_t _size,
+ char *_file,
+ int _line,
+ int _group));
+# define sm_rpool_malloc(rpool, size) \
+ sm_rpool_malloc_tagged(rpool, size, __FILE__, __LINE__, SmHeapGroup)
+# else /* SM_HEAP_CHECK */
+extern void *
+sm_rpool_malloc_x __P((
+ SM_RPOOL_T *_rpool,
+ size_t _size));
+extern void *
+sm_rpool_malloc __P((
+ SM_RPOOL_T *_rpool,
+ size_t _size));
+# endif /* SM_HEAP_CHECK */
+
+#if DO_NOT_USE_STRCPY
+extern char *sm_rpool_strdup_x __P((SM_RPOOL_T *rpool, const char *s));
+#else /* DO_NOT_USE_STRCPY */
+# define sm_rpool_strdup_x(rpool, str) \
+ strcpy(sm_rpool_malloc_x(rpool, strlen(str) + 1), str)
+#endif /* DO_NOT_USE_STRCPY */
+
+extern SM_RPOOL_ATTACH_T
+sm_rpool_attach_x __P((
+ SM_RPOOL_T *_rpool,
+ SM_RPOOL_RFREE_T _rfree,
+ void *_rcontext));
+
+# define sm_rpool_detach(a) ((void)(*(a) = NULL))
+
+extern void
+sm_rpool_setsizes __P((
+ SM_RPOOL_T *_rpool,
+ size_t _poolsize,
+ size_t _bigobjectsize));
+
+#endif /* ! SM_RPOOL_H */
diff --git a/usr/src/cmd/sendmail/include/sm/sem.h b/usr/src/cmd/sendmail/include/sm/sem.h
new file mode 100644
index 0000000000..5bdc9392af
--- /dev/null
+++ b/usr/src/cmd/sendmail/include/sm/sem.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2000-2001, 2005 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ * $Id: sem.h,v 1.9 2005/02/17 22:08:58 ca Exp $
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#ifndef SM_SEM_H
+# define SM_SEM_H 1
+
+#include <sm/gen.h>
+
+/* key for semaphores */
+# define SM_SEM_KEY (41L)
+# define SM_SEM_NO_ID (-1)
+# define SM_NO_SEM(id) ((id) < 0)
+
+# if SM_CONF_SEM > 0
+# include <sys/types.h>
+# include <sys/ipc.h>
+# include <sys/sem.h>
+
+# if SM_CONF_SEM == 2
+union semun
+{
+ int val;
+ struct semid_ds *buf;
+ ushort *array;
+};
+# endif /* SM_CONF_SEM == 2 */
+
+# ifndef SEM_A
+# define SEM_A 0200
+# endif /* SEM_A */
+# ifndef SEM_R
+# define SEM_R 0400
+# endif /* SEM_R */
+
+# define SM_NSEM 1
+
+extern int sm_sem_start __P((key_t, int, int, bool));
+extern int sm_sem_stop __P((int));
+extern int sm_sem_acq __P((int, int, int));
+extern int sm_sem_rel __P((int, int, int));
+extern int sm_sem_get __P((int, int));
+
+# else /* SM_CONF_SEM > 0 */
+# define sm_sem_start(key, nsem, semflg, owner) 0
+# define sm_sem_stop(semid) 0
+# define sm_sem_acq(semid, semnum, timeout) 0
+# define sm_sem_rel(semid, semnum, timeout) 0
+# define sm_sem_get(semid, semnum) 0
+# endif /* SM_CONF_SEM > 0 */
+
+#endif /* ! SM_SEM_H */
diff --git a/usr/src/cmd/sendmail/include/sm/setjmp.h b/usr/src/cmd/sendmail/include/sm/setjmp.h
new file mode 100644
index 0000000000..a76370eefb
--- /dev/null
+++ b/usr/src/cmd/sendmail/include/sm/setjmp.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ * $Id: setjmp.h,v 1.3 2001/03/08 03:23:08 ca Exp $
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#ifndef SM_SETJMP_H
+# define SM_SETJMP_H
+
+# include <sm/config.h>
+# include <setjmp.h>
+
+/*
+** sm_setjmp_sig is a setjmp that saves the signal mask.
+** sm_setjmp_nosig is a setjmp that does *not* save the signal mask.
+** SM_JMPBUF_T is used with both of the above macros.
+**
+** On most systems, these can be implemented using sigsetjmp.
+** Some old BSD systems do not have sigsetjmp, but they do have
+** setjmp and _setjmp, which are just as good.
+*/
+
+# if SM_CONF_SIGSETJMP
+
+typedef sigjmp_buf SM_JMPBUF_T;
+# define sm_setjmp_sig(buf) sigsetjmp(buf, 1)
+# define sm_setjmp_nosig(buf) sigsetjmp(buf, 0)
+# define sm_longjmp_sig(buf, val) siglongjmp(buf, val)
+# define sm_longjmp_nosig(buf, val) siglongjmp(buf, val)
+
+# else /* SM_CONF_SIGSETJMP */
+
+typedef jmp_buf SM_JMPBUF_T;
+# define sm_setjmp_sig(buf) setjmp(buf)
+# define sm_longjmp_sig(buf, val) longjmp(buf, val)
+# define sm_setjmp_nosig(buf) _setjmp(buf)
+# define sm_longjmp_nosig(buf, val) _longjmp(buf, val)
+
+# endif /* SM_CONF_SIGSETJMP */
+
+#endif /* ! SM_SETJMP_H */
diff --git a/usr/src/cmd/sendmail/include/sm/shm.h b/usr/src/cmd/sendmail/include/sm/shm.h
new file mode 100644
index 0000000000..2355eff64b
--- /dev/null
+++ b/usr/src/cmd/sendmail/include/sm/shm.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2000-2003, 2005 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ * $Id: shm.h,v 1.11 2005/01/13 22:57:04 ca Exp $
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#ifndef SM_SHM_H
+# define SM_SHM_H
+
+# if SM_CONF_SHM
+# include <sys/types.h>
+# include <sys/ipc.h>
+# include <sys/shm.h>
+
+/* # include "def.h" */
+
+/* key for shared memory */
+# define SM_SHM_KEY ((key_t) 42)
+
+/* return value for failed shmget() */
+# define SM_SHM_NULL ((void *) -1)
+# define SM_SHM_NO_ID (-2)
+
+extern void *sm_shmstart __P((key_t, int , int , int *, bool));
+extern int sm_shmstop __P((void *, int, bool));
+extern int sm_shmsetowner __P((int, uid_t, gid_t, mode_t));
+
+
+/* for those braindead systems... (e.g., SunOS 4) */
+# ifndef SHM_R
+# define SHM_R 0400
+# endif /* SHM_R */
+# ifndef SHM_W
+# define SHM_W 0200
+# endif /* SHM_W */
+
+# endif /* SM_CONF_SHM */
+#endif /* ! SM_SHM_H */
diff --git a/usr/src/cmd/sendmail/include/sm/signal.h b/usr/src/cmd/sendmail/include/sm/signal.h
new file mode 100644
index 0000000000..cdfb7bfe96
--- /dev/null
+++ b/usr/src/cmd/sendmail/include/sm/signal.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 1998-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ * $Id: signal.h,v 1.16 2001/07/20 19:48:21 gshapiro Exp $
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+** SIGNAL.H -- libsm (and sendmail) signal facilities
+** Extracted from sendmail/conf.h and focusing
+** on signal configuration.
+*/
+
+#ifndef SM_SIGNAL_H
+#define SM_SIGNAL_H 1
+
+#include <sys/types.h>
+#include <limits.h>
+#include <signal.h>
+#include <sm/cdefs.h>
+#include <sm/conf.h>
+
+/*
+** Critical signal sections
+*/
+
+#define PEND_SIGHUP 0x0001
+#define PEND_SIGINT 0x0002
+#define PEND_SIGTERM 0x0004
+#define PEND_SIGUSR1 0x0008
+
+#define ENTER_CRITICAL() InCriticalSection++
+
+#define LEAVE_CRITICAL() \
+do \
+{ \
+ if (InCriticalSection > 0) \
+ InCriticalSection--; \
+} while (0)
+
+#define CHECK_CRITICAL(sig) \
+do \
+{ \
+ if (InCriticalSection > 0 && (sig) != 0) \
+ { \
+ pend_signal((sig)); \
+ return SIGFUNC_RETURN; \
+ } \
+} while (0)
+
+/* variables */
+extern unsigned int volatile InCriticalSection; /* >0 if in critical section */
+extern int volatile PendingSignal; /* pending signal to resend */
+
+/* functions */
+extern void pend_signal __P((int));
+
+/* reset signal in case System V semantics */
+#ifdef SYS5SIGNALS
+# define FIX_SYSV_SIGNAL(sig, handler) \
+{ \
+ if ((sig) != 0) \
+ (void) sm_signal((sig), (handler)); \
+}
+#else /* SYS5SIGNALS */
+# define FIX_SYSV_SIGNAL(sig, handler) { /* EMPTY */ }
+#endif /* SYS5SIGNALS */
+
+extern void sm_allsignals __P((bool));
+extern int sm_blocksignal __P((int));
+extern int sm_releasesignal __P((int));
+extern sigfunc_t sm_signal __P((int, sigfunc_t));
+extern SIGFUNC_DECL sm_signal_noop __P((int));
+#endif /* SM_SIGNAL_H */
diff --git a/usr/src/cmd/sendmail/include/sm/string.h b/usr/src/cmd/sendmail/include/sm/string.h
new file mode 100644
index 0000000000..81ace73ae4
--- /dev/null
+++ b/usr/src/cmd/sendmail/include/sm/string.h
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2000-2001, 2003 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ * $Id: string.h,v 1.38 2003/10/10 17:56:57 ca Exp $
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+** libsm string manipulation
+*/
+
+#ifndef SM_STRING_H
+# define SM_STRING_H
+
+# include <sm/gen.h>
+# include <sm/varargs.h>
+# include <string.h> /* strlc{py,at}, strerror */
+
+/* return number of bytes left in a buffer */
+#define SPACELEFT(buf, ptr) (sizeof buf - ((ptr) - buf))
+
+extern int PRINTFLIKE(3, 4)
+sm_snprintf __P((char *, size_t, const char *, ...));
+
+extern bool
+sm_match __P((const char *_str, const char *_pattern));
+
+extern char *
+sm_strdup __P((char *));
+
+extern char *
+sm_strndup_x __P((const char *_str, size_t _len));
+
+#if DO_NOT_USE_STRCPY
+/* for "normal" data (free'd before end of process) */
+extern char *
+sm_strdup_x __P((const char *_str));
+
+/* for data that is supposed to be persistent. */
+extern char *
+sm_pstrdup_x __P((const char *_str));
+
+extern char *
+sm_strdup_tagged_x __P((const char *str, char *file, int line, int group));
+
+#else /* DO_NOT_USE_STRCPY */
+
+/* for "normal" data (free'd before end of process) */
+# define sm_strdup_x(str) strcpy(sm_malloc_x(strlen(str) + 1), str)
+
+/* for data that is supposed to be persistent. */
+# define sm_pstrdup_x(str) strcpy(sm_pmalloc_x(strlen(str) + 1), str)
+
+# define sm_strdup_tagged_x(str, file, line, group) \
+ strcpy(sm_malloc_tagged_x(strlen(str) + 1, file, line, group), str)
+#endif /* DO_NOT_USE_STRCPY */
+
+extern char *
+sm_stringf_x __P((const char *_fmt, ...));
+
+extern char *
+sm_vstringf_x __P((const char *_fmt, va_list _ap));
+
+extern size_t
+sm_strlcpy __P((char *_dst, const char *_src, ssize_t _len));
+
+extern size_t
+sm_strlcat __P((char *_dst, const char *_src, ssize_t _len));
+
+extern size_t
+sm_strlcat2 __P((char *, const char *, const char *, ssize_t));
+
+extern size_t
+#ifdef __STDC__
+sm_strlcpyn(char *dst, ssize_t len, int n, ...);
+#else /* __STDC__ */
+sm_strlcpyn __P((char *,
+ ssize_t,
+ int,
+ va_dcl));
+#endif /* __STDC__ */
+
+# if !HASSTRERROR
+extern char *
+strerror __P((int _errno));
+# endif /* !HASSTRERROR */
+
+extern int
+sm_strrevcmp __P((const char *, const char *));
+
+extern int
+sm_strrevcasecmp __P((const char *, const char *));
+
+extern int
+sm_strcasecmp __P((const char *, const char *));
+
+extern int
+sm_strncasecmp __P((const char *, const char *, size_t));
+
+extern LONGLONG_T
+sm_strtoll __P((const char *, char**, int));
+
+extern ULONGLONG_T
+sm_strtoull __P((const char *, char**, int));
+
+extern void
+stripquotes __P((char *));
+
+#endif /* SM_STRING_H */
diff --git a/usr/src/cmd/sendmail/include/sm/sysexits.h b/usr/src/cmd/sendmail/include/sm/sysexits.h
new file mode 100644
index 0000000000..441120363a
--- /dev/null
+++ b/usr/src/cmd/sendmail/include/sm/sysexits.h
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1987, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ * $Id: sysexits.h,v 1.5 2001/03/10 17:30:01 ca Exp $
+ * @(#)sysexits.h 8.1 (Berkeley) 6/2/93
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#ifndef SM_SYSEXITS_H
+# define SM_SYSEXITS_H
+
+# include <sm/gen.h>
+
+/*
+** SYSEXITS.H -- Exit status codes for system programs.
+**
+** This include file attempts to categorize possible error
+** exit statuses for system programs, notably delivermail
+** and the Berkeley network.
+**
+** Error numbers begin at EX__BASE to reduce the possibility of
+** clashing with other exit statuses that random programs may
+** already return. The meaning of the codes is approximately
+** as follows:
+**
+** EX_USAGE -- The command was used incorrectly, e.g., with
+** the wrong number of arguments, a bad flag, a bad
+** syntax in a parameter, or whatever.
+** EX_DATAERR -- The input data was incorrect in some way.
+** This should only be used for user's data & not
+** system files.
+** EX_NOINPUT -- An input file (not a system file) did not
+** exist or was not readable. This could also include
+** errors like "No message" to a mailer (if it cared
+** to catch it).
+** EX_NOUSER -- The user specified did not exist. This might
+** be used for mail addresses or remote logins.
+** EX_NOHOST -- The host specified did not exist. This is used
+** in mail addresses or network requests.
+** EX_UNAVAILABLE -- A service is unavailable. This can occur
+** if a support program or file does not exist. This
+** can also be used as a catchall message when something
+** you wanted to do doesn't work, but you don't know
+** why.
+** EX_SOFTWARE -- An internal software error has been detected.
+** This should be limited to non-operating system related
+** errors as possible.
+** EX_OSERR -- An operating system error has been detected.
+** This is intended to be used for such things as "cannot
+** fork", "cannot create pipe", or the like. It includes
+** things like getuid returning a user that does not
+** exist in the passwd file.
+** EX_OSFILE -- Some system file (e.g., /etc/passwd, /etc/utmp,
+** etc.) does not exist, cannot be opened, or has some
+** sort of error (e.g., syntax error).
+** EX_CANTCREAT -- A (user specified) output file cannot be
+** created.
+** EX_IOERR -- An error occurred while doing I/O on some file.
+** EX_TEMPFAIL -- temporary failure, indicating something that
+** is not really an error. In sendmail, this means
+** that a mailer (e.g.) could not create a connection,
+** and the request should be reattempted later.
+** EX_PROTOCOL -- the remote system returned something that
+** was "not possible" during a protocol exchange.
+** EX_NOPERM -- You did not have sufficient permission to
+** perform the operation. This is not intended for
+** file system problems, which should use NOINPUT or
+** CANTCREAT, but rather for higher level permissions.
+*/
+
+# if SM_CONF_SYSEXITS_H
+# include <sysexits.h>
+# else /* SM_CONF_SYSEXITS_H */
+
+# define EX_OK 0 /* successful termination */
+
+# define EX__BASE 64 /* base value for error messages */
+
+# define EX_USAGE 64 /* command line usage error */
+# define EX_DATAERR 65 /* data format error */
+# define EX_NOINPUT 66 /* cannot open input */
+# define EX_NOUSER 67 /* addressee unknown */
+# define EX_NOHOST 68 /* host name unknown */
+# define EX_UNAVAILABLE 69 /* service unavailable */
+# define EX_SOFTWARE 70 /* internal software error */
+# define EX_OSERR 71 /* system error (e.g., can't fork) */
+# define EX_OSFILE 72 /* critical OS file missing */
+# define EX_CANTCREAT 73 /* can't create (user) output file */
+# define EX_IOERR 74 /* input/output error */
+# define EX_TEMPFAIL 75 /* temp failure; user is invited to retry */
+# define EX_PROTOCOL 76 /* remote error in protocol */
+# define EX_NOPERM 77 /* permission denied */
+# define EX_CONFIG 78 /* configuration error */
+
+# define EX__MAX 78 /* maximum listed value */
+
+# endif /* SM_CONF_SYSEXITS_H */
+
+extern char *sm_strexit __P((int));
+extern char *sm_sysexitmsg __P((int));
+extern char *sm_sysexmsg __P((int));
+
+#endif /* ! SM_SYSEXITS_H */
diff --git a/usr/src/cmd/sendmail/include/sm/test.h b/usr/src/cmd/sendmail/include/sm/test.h
new file mode 100644
index 0000000000..13ea9b9246
--- /dev/null
+++ b/usr/src/cmd/sendmail/include/sm/test.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ * $Id: test.h,v 1.6 2001/04/03 01:53:01 gshapiro Exp $
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+** Abstractions for writing a libsm test program.
+*/
+
+#ifndef SM_TEST_H
+# define SM_TEST_H
+
+# include <sm/gen.h>
+
+# if defined(__STDC__) || defined(__cplusplus)
+# define SM_TEST(cond) sm_test(cond, #cond, __FILE__, __LINE__)
+# else /* defined(__STDC__) || defined(__cplusplus) */
+# define SM_TEST(cond) sm_test(cond, "cond", __FILE__, __LINE__)
+# endif /* defined(__STDC__) || defined(__cplusplus) */
+
+extern int SmTestIndex;
+extern int SmTestNumErrors;
+
+extern void
+sm_test_begin __P((
+ int _argc,
+ char **_argv,
+ char *_testname));
+
+extern bool
+sm_test __P((
+ bool _success,
+ char *_expr,
+ char *_filename,
+ int _lineno));
+
+extern int
+sm_test_end __P((void));
+
+#endif /* ! SM_TEST_H */
diff --git a/usr/src/cmd/sendmail/include/sm/types.h b/usr/src/cmd/sendmail/include/sm/types.h
new file mode 100644
index 0000000000..07ba0dc373
--- /dev/null
+++ b/usr/src/cmd/sendmail/include/sm/types.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ * $Id: types.h,v 1.13 2001/04/03 01:53:01 gshapiro Exp $
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+** This header file defines standard integral types.
+** - It includes <sys/types.h>, and fixes portability problems that
+** exist on older Unix platforms.
+** - It defines LONGLONG_T and ULONGLONG_T, which are portable locutions
+** for 'long long' and 'unsigned long long'.
+*/
+
+#ifndef SM_TYPES_H
+# define SM_TYPES_H
+
+# include <sm/config.h>
+
+/*
+** On BSD 4.2 systems, <sys/types.h> was not idempotent.
+** This problem is circumvented by replacing all occurrences
+** of <sys/types.h> with <sm/types.h>, which is idempotent.
+*/
+
+# include <sys/types.h>
+
+/*
+** On some old Unix platforms, some of the standard types are missing.
+** We fix that here.
+*/
+
+# if !SM_CONF_UID_GID
+# define uid_t int
+# define gid_t int
+# endif /* !SM_CONF_UID_GID */
+
+# if !SM_CONF_SSIZE_T
+# define ssize_t int
+# endif /* !SM_CONF_SSIZE_T */
+
+/*
+** Define LONGLONG_T and ULONGLONG_T, which are portable locutions
+** for 'long long' and 'unsigned long long' from the C 1999 standard.
+*/
+
+# if SM_CONF_LONGLONG
+ typedef long long LONGLONG_T;
+ typedef unsigned long long ULONGLONG_T;
+# else /* SM_CONF_LONGLONG */
+# if SM_CONF_QUAD_T
+ typedef quad_t LONGLONG_T;
+ typedef u_quad_t ULONGLONG_T;
+# else /* SM_CONF_QUAD_T */
+ typedef long LONGLONG_T;
+ typedef unsigned long ULONGLONG_T;
+# endif /* SM_CONF_QUAD_T */
+# endif /* SM_CONF_LONGLONG */
+
+#endif /* ! SM_TYPES_H */
diff --git a/usr/src/cmd/sendmail/include/sm/varargs.h b/usr/src/cmd/sendmail/include/sm/varargs.h
new file mode 100644
index 0000000000..6263718c22
--- /dev/null
+++ b/usr/src/cmd/sendmail/include/sm/varargs.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ * $Id: varargs.h,v 1.7.2.1 2002/07/29 21:43:20 gshapiro Exp $
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+** libsm variable argument lists
+*/
+
+#ifndef SM_VARARGS_H
+# define SM_VARARGS_H
+
+# if defined(__STDC__) || defined(__cplusplus)
+# define SM_VA_STD 1
+# include <stdarg.h>
+# define SM_VA_START(ap, f) va_start(ap, f)
+# else /* defined(__STDC__) || defined(__cplusplus) */
+# define SM_VA_STD 0
+# include <varargs.h>
+# define SM_VA_START(ap, f) va_start(ap)
+# endif /* defined(__STDC__) || defined(__cplusplus) */
+
+# if defined(va_copy)
+# define SM_VA_COPY(dst, src) va_copy((dst), (src))
+# elif defined(__va_copy)
+# define SM_VA_COPY(dst, src) __va_copy((dst), (src))
+# else
+# define SM_VA_COPY(dst, src) memcpy(&(dst), &(src), sizeof((dst)))
+# endif
+
+/*
+** The following macros are useless, but are provided for symmetry.
+*/
+
+# define SM_VA_LOCAL_DECL va_list ap;
+# define SM_VA_ARG(ap, type) va_arg(ap, type)
+# define SM_VA_END(ap) va_end(ap)
+
+#endif /* ! SM_VARARGS_H */
diff --git a/usr/src/cmd/sendmail/include/sm/xtrap.h b/usr/src/cmd/sendmail/include/sm/xtrap.h
new file mode 100644
index 0000000000..78f4316d39
--- /dev/null
+++ b/usr/src/cmd/sendmail/include/sm/xtrap.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ * $Id: xtrap.h,v 1.7 2001/04/03 01:53:01 gshapiro Exp $
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+** scaffolding for testing exception handler code
+*/
+
+#ifndef SM_XTRAP_H
+# define SM_XTRAP_H
+
+# include <sm/debug.h>
+# include <sm/exc.h>
+
+extern SM_ATOMIC_UINT_T SmXtrapCount;
+extern SM_DEBUG_T SmXtrapDebug;
+extern SM_DEBUG_T SmXtrapReport;
+
+# if SM_DEBUG_CHECK
+# define sm_xtrap_check() (++SmXtrapCount == sm_debug_level(&SmXtrapDebug))
+# else /* SM_DEBUG_CHECK */
+# define sm_xtrap_check() (0)
+# endif /* SM_DEBUG_CHECK */
+
+# define sm_xtrap_raise_x(exc) \
+ if (sm_xtrap_check()) \
+ { \
+ sm_exc_raise_x(exc); \
+ } else
+
+#endif /* ! SM_XTRAP_H */
diff --git a/usr/src/cmd/sendmail/lib/Makefile b/usr/src/cmd/sendmail/lib/Makefile
new file mode 100644
index 0000000000..3335510bf3
--- /dev/null
+++ b/usr/src/cmd/sendmail/lib/Makefile
@@ -0,0 +1,78 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License, Version 1.0 only
+# (the "License"). You may not use this file except in compliance
+# with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+#
+# Copyright 2004 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# ident "%Z%%M% %I% %E% SMI"
+#
+
+include ../../Makefile.cmd
+
+SRCS= aliases helpfile local-host-names trusted-users
+
+MANIFEST= smtp-sendmail.xml
+ROOTMANIFESTDIR= $(ROOTSVCNETWORK)
+ROOTMETHOD= $(ROOTLIBSVCMETHOD)/smtp-sendmail
+
+ROOTETCMAIL = $(ROOTETC)/mail
+ROOTETCMAILF = $(ROOTETCMAIL)/aliases $(ROOTETCMAIL)/helpfile \
+ $(ROOTETCMAIL)/local-host-names \
+ $(ROOTETCMAIL)/trusted-users
+ROOTETCSYMLINKS = $(ROOTETC)/aliases
+ROOTETCMAILSYMLINKS = $(ROOTETCMAIL)/sendmail.hf
+
+# conditional assignments
+$(ROOTETCMAILF) := FILEMODE = 644
+$(ROOTETCMAILF) := OWNER = root
+
+$(ROOTSVCNETWORK)/smtp-sendmail.xml := OWNER = root
+$(ROOTSVCNETWORK)/smtp-sendmail.xml := GROUP = sys
+$(ROOTSVCNETWORK)/smtp-sendmail.xml := FILEMODE = 0444
+
+$(ROOT)/lib/svc/method/smtp-sendmail := OWNER = root
+$(ROOT)/lib/svc/method/smtp-sendmail := GROUP = bin
+$(ROOT)/lib/svc/method/smtp-sendmail := FILEMODE = 0555
+
+.KEEP_STATE:
+
+all: $(SRCS)
+
+install: all $(ROOTETCMAILF) $(ROOTETCSYMLINKS) \
+ $(ROOTETCMAILSYMLINKS) $(ROOTMANIFEST) $(ROOTMETHOD)
+
+check: $(CHKMANIFEST)
+
+clean:
+
+clobber:
+
+lint:
+
+$(ROOTETCMAIL)/%: %
+ $(INS.file)
+
+$(ROOTETCSYMLINKS):
+ $(RM) $@; $(SYMLINK) mail/aliases $@
+
+$(ROOTETCMAILSYMLINKS):
+ $(RM) $@; $(SYMLINK) helpfile $@
diff --git a/usr/src/cmd/sendmail/lib/aliases b/usr/src/cmd/sendmail/lib/aliases
new file mode 100644
index 0000000000..fc9f912da7
--- /dev/null
+++ b/usr/src/cmd/sendmail/lib/aliases
@@ -0,0 +1,77 @@
+#
+# Copyright 2005 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License, Version 1.0 only
+# (the "License"). You may not use this file except in compliance
+# with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+#
+#ident "%Z%%M% %I% %E% SMI"
+
+##
+# Aliases can have any mix of upper and lower case on the left-hand side,
+# but the right-hand side should be proper case (usually lower)
+#
+# >>>>>>>>>> The program "newaliases" must be run after
+# >> NOTE >> this file is updated for any changes to
+# >>>>>>>>>> show through to sendmail.
+##
+
+# The following alias is required by the mail protocol, RFC 2821
+# Set it to the address of a HUMAN who deals with this system's mail problems.
+postmaster: root
+
+# Alias for mailer daemon; returned messages from our MAILER-DAEMON
+# should be routed to our local Postmaster.
+MAILER-DAEMON: postmaster
+
+# General redirections for pseudo accounts.
+bin: root
+daemon: root
+system: root
+toor: root
+uucp: root
+
+# Well-known aliases.
+manager: root
+dumper: root
+operator: root
+
+# trap decode to catch security attacks
+decode: root
+
+# Aliases to handle mail to programs or files, eg news or vacation
+nobody: /dev/null
+
+# Sample aliases:
+
+# Alias for distribution list, members specified here:
+#staff:wnj,mosher,sam,ecc,mckusick,sklower,olson,rwh@ernie
+
+# Alias for distribution list, members specified elsewhere:
+#keyboards: :include:/usr/jfarrell/keyboards.list
+
+# Alias for a person, so they can receive mail by several names:
+#epa:eric
+
+#######################
+# Local aliases below #
+#######################
+
diff --git a/usr/src/cmd/sendmail/lib/helpfile b/usr/src/cmd/sendmail/lib/helpfile
new file mode 100644
index 0000000000..6d97582bab
--- /dev/null
+++ b/usr/src/cmd/sendmail/lib/helpfile
@@ -0,0 +1,128 @@
+#vers 2
+cpyr
+cpyr Copyright (c) 1998-2000, 2002 Sendmail, Inc. and its suppliers.
+cpyr All rights reserved.
+cpyr Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+cpyr Copyright (c) 1988, 1993
+cpyr The Regents of the University of California. All rights reserved.
+cpyr Copyright 1994 - 2002 Sun Microsystems, Inc. All rights reserved.
+cpyr Use is subject to license terms.
+cpyr
+cpyr
+cpyr $$Id: helpfile,v 8.40 2002/03/19 00:23:28 gshapiro Exp $$
+cpyr ident "%Z%%M% %I% %E% SMI"
+cpyr
+smtp This is sendmail version $v
+smtp Topics:
+smtp HELO EHLO MAIL RCPT DATA
+smtp RSET NOOP QUIT HELP VRFY
+smtp EXPN VERB ETRN DSN
+smtp For more info use "HELP <topic>".
+smtp To report bugs in the implementation contact Sun Microsystems
+smtp Technical Support.
+smtp For local information send email to Postmaster at your site.
+help HELP [ <topic> ]
+help The HELP command gives help info.
+helo HELO <hostname>
+helo Introduce yourself.
+ehlo EHLO <hostname>
+ehlo Introduce yourself, and request extended SMTP mode.
+ehlo Possible replies include:
+ehlo SEND Send as mail [RFC821]
+ehlo SOML Send as mail or terminal [RFC821]
+ehlo SAML Send as mail and terminal [RFC821]
+ehlo EXPN Expand the mailing list [RFC821]
+ehlo HELP Supply helpful information [RFC821]
+ehlo TURN Turn the operation around [RFC821]
+ehlo 8BITMIME Use 8-bit data [RFC1652]
+ehlo SIZE Message size declaration [RFC1870]
+ehlo VERB Verbose [Allman]
+ehlo CHUNKING Chunking [RFC1830]
+ehlo BINARYMIME Binary MIME [RFC1830]
+ehlo PIPELINING Command Pipelining [RFC1854]
+ehlo DSN Delivery Status Notification [RFC1891]
+ehlo ETRN Remote Message Queue Starting [RFC1985]
+ehlo ENHANCEDSTATUSCODES Enhanced status codes [RFC2034]
+ehlo DELIVERBY Deliver By [RFC2852]
+mail MAIL FROM: <sender> [ <parameters> ]
+mail Specifies the sender. Parameters are ESMTP extensions.
+mail See "HELP DSN" for details.
+rcpt RCPT TO: <recipient> [ <parameters> ]
+rcpt Specifies the recipient. Can be used any number of times.
+rcpt Parameters are ESMTP extensions. See "HELP DSN" for details.
+data DATA
+data Following text is collected as the message.
+data End with a single dot.
+rset RSET
+rset Resets the system.
+quit QUIT
+quit Exit sendmail (SMTP).
+verb VERB
+verb Go into verbose mode. This sends 0xy responses that are
+verb not RFC821 standard (but should be) They are recognized
+verb by humans and other sendmail implementations.
+vrfy VRFY <recipient>
+vrfy Verify an address. If you want to see what it aliases
+vrfy to, use EXPN instead.
+expn EXPN <recipient>
+expn Expand an address. If the address indicates a mailing
+expn list, return the contents of that list.
+noop NOOP
+noop Do nothing.
+send SEND FROM: <sender>
+send replaces the MAIL command, and can be used to send
+send directly to a users terminal. Not supported in this
+send implementation.
+soml SOML FROM: <sender>
+soml Send or mail. If the user is logged in, send directly,
+soml otherwise mail. Not supported in this implementation.
+saml SAML FROM: <sender>
+saml Send and mail. Send directly to the user's terminal,
+saml and also mail a letter. Not supported in this
+saml implementation.
+turn TURN
+turn Reverses the direction of the connection. Not currently
+turn implemented.
+etrn ETRN [ <hostname> | @<domain> | #<queuename> ]
+etrn Run the queue for the specified <hostname>, or
+etrn all hosts within a given <domain>, or a specially-named
+etrn <queuename> (implementation-specific).
+dsn MAIL FROM: <sender> [ RET={ FULL | HDRS} ] [ ENVID=<envid> ]
+dsn RCPT TO: <recipient> [ NOTIFY={NEVER,SUCCESS,FAILURE,DELAY} ]
+dsn [ ORCPT=<recipient> ]
+dsn SMTP Delivery Status Notifications.
+dsn Descriptions:
+dsn RET Return either the full message or only headers.
+dsn ENVID Sender's "envelope identifier" for tracking.
+dsn NOTIFY When to send a DSN. Multiple options are OK, comma-
+dsn delimited. NEVER must appear by itself.
+dsn ORCPT Original recipient.
+-bt Help for test mode:
+-bt ? :this help message.
+-bt .Dmvalue :define macro `m' to `value'.
+-bt .Ccvalue :add `value' to class `c'.
+-bt =Sruleset :dump the contents of the indicated ruleset.
+-bt =M :display the known mailers.
+-bt -ddebug-spec :equivalent to the command-line -d debug flag.
+-bt $$m :print the value of macro $$m.
+-bt $$=c :print the contents of class $$=c.
+-bt /mx host :returns the MX records for `host'.
+-bt /parse address :parse address, returning the value of crackaddr, and
+-bt the parsed address.
+-bt /try mailer addr :rewrite address into the form it will have when
+-bt presented to the indicated mailer.
+-bt /tryflags flags :set flags used by parsing. The flags can be `H' for
+-bt Header or `E' for Envelope, and `S' for Sender or `R'
+-bt for Recipient. These can be combined, `HR' sets
+-bt flags for header recipients.
+-bt /canon hostname :try to canonify hostname.
+-bt /map mapname key :look up `key' in the indicated `mapname'.
+-bt /quit :quit address test mode.
+-bt rules addr :run the indicated address through the named rules.
+-bt Rules can be a comma separated list of rules.
+control Help for smcontrol:
+control help This message.
+control restart Restart sendmail.
+control shutdown Shutdown sendmail.
+control status Show sendmail status.
+control memdump Dump allocated memory list (for debugging only).
diff --git a/usr/src/cmd/sendmail/lib/local-host-names b/usr/src/cmd/sendmail/lib/local-host-names
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/usr/src/cmd/sendmail/lib/local-host-names
diff --git a/usr/src/cmd/sendmail/lib/smtp-sendmail b/usr/src/cmd/sendmail/lib/smtp-sendmail
new file mode 100644
index 0000000000..346851075a
--- /dev/null
+++ b/usr/src/cmd/sendmail/lib/smtp-sendmail
@@ -0,0 +1,151 @@
+#!/sbin/sh
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License, Version 1.0 only
+# (the "License"). You may not use this file except in compliance
+# with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+#
+# Copyright 2005 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# ident "%Z%%M% %I% %E% SMI"
+
+. /lib/svc/share/smf_include.sh
+
+ERRMSG1='WARNING: /var/mail is NFS-mounted without setting actimeo=0,'
+ERRMSG2='this can cause mailbox locking and access problems.'
+SERVER_PID_FILE="/var/run/sendmail.pid"
+CLIENT_PID_FILE="/var/spool/clientmqueue/sm-client.pid"
+DEFAULT_FILE="/etc/default/sendmail"
+ALIASES_FILE="/etc/mail/aliases"
+
+check_queue_interval_syntax()
+{
+ default="15m"
+ if [ $# -lt 1 ]; then
+ answer=$default
+ return
+ fi
+ if echo $1 | egrep '^([0-9]*[1-9][0-9]*[smhdw])+$' >/dev/null 2>&1; then
+ answer=$1
+ else
+ answer=$default
+ fi
+}
+
+check_and_kill()
+{
+ PID=`head -1 $1`
+ kill -0 $PID > /dev/null 2>&1
+ [ $? -eq 0 ] && kill $PID
+}
+
+case "$1" in
+'refresh')
+ [ -f $SERVER_PID_FILE ] && kill -1 `head -1 $SERVER_PID_FILE`
+ [ -f $CLIENT_PID_FILE ] && kill -1 `head -1 $CLIENT_PID_FILE`
+ ;;
+
+'start')
+ if [ ! -f /usr/lib/sendmail -o ! -f /etc/mail/sendmail.cf ]; then
+ exit $SMF_EXIT_ERR_CONFIG
+ fi
+ if [ ! -d /var/spool/mqueue ]; then
+ /usr/bin/mkdir -m 0750 /var/spool/mqueue
+ /usr/bin/chown root:bin /var/spool/mqueue
+ fi
+ if [ ! -f $ALIASES_FILE.db ] && [ ! -f $ALIASES_FILE.dir ] \
+ && [ ! -f $ALIASES_FILE.pag ]; then
+ /usr/sbin/newaliases
+ fi
+ MODE="-bd"
+ [ -f $DEFAULT_FILE ] && . $DEFAULT_FILE
+ #
+ # * MODE should be "-bd" or null (MODE= or MODE="") or
+ # left alone. Anything else and you're on your own.
+ # * QUEUEOPTION should be "p" or null (as above).
+ # * [CLIENT]QUEUEINTERVAL should be set to some legal value;
+ # sanity checks are done below.
+ # * [CLIENT]OPTIONS are catch-alls; set with care.
+ #
+ if [ -n "$QUEUEOPTION" -a "$QUEUEOPTION" != "p" ]; then
+ QUEUEOPTION=""
+ fi
+ if [ -z "$QUEUEOPTION" -o -n "$QUEUEINTERVAL" ]; then
+ check_queue_interval_syntax $QUEUEINTERVAL
+ QUEUEINTERVAL=$answer
+ fi
+ check_queue_interval_syntax $CLIENTQUEUEINTERVAL
+ CLIENTQUEUEINTERVAL=$answer
+ /usr/lib/sendmail $MODE -q$QUEUEOPTION$QUEUEINTERVAL $OPTIONS &
+ /usr/lib/sendmail -Ac -q$CLIENTQUEUEINTERVAL $CLIENTOPTIONS &
+ #
+ # ETRN_HOSTS should be of the form
+ # "s1:c1.1,c1.2 s2:c2.1 s3:c3.1,c3.2,c3.3"
+ # i.e., white-space separated groups of server:client where
+ # client can be one or more comma-separated names; N.B. that
+ # the :client part is optional; see etrn(1M) for details.
+ # server is the name of the server to prod; a mail queue run
+ # is requested for each client name. This is comparable to
+ # running "/usr/lib/sendmail -qRclient" on the host server.
+ #
+ # See RFC 1985 for more information.
+ #
+ for i in $ETRN_HOSTS; do
+ SERVER=`echo $i | /usr/bin/sed -e 's/:.*$//'`
+ CLIENTS=`echo $i | /usr/bin/sed -n -e 's/,/ /g' \
+ -e '/:/s/^.*://p'`
+ /usr/sbin/etrn -b $SERVER $CLIENTS >/dev/null 2>&1 &
+ done
+
+ if /usr/bin/nawk 'BEGIN{s = 1}
+ $2 == "/var/mail" && $3 == "nfs" && $4 !~ /actimeo=0/ &&
+ $4 !~ /noac/{s = 0} END{exit s}' /etc/mnttab; then
+
+ /usr/bin/logger -p mail.crit "$ERRMSG1"
+ /usr/bin/logger -p mail.crit "$ERRMSG2"
+ fi
+ ;;
+
+'stop')
+ [ -f $SERVER_PID_FILE ] && check_and_kill $SERVER_PID_FILE
+ if [ -f $CLIENT_PID_FILE ]; then
+ check_and_kill $CLIENT_PID_FILE
+ rm -f $CLIENT_PID_FILE
+ fi
+ # Need to kill the entire service contract to kill all sendmail related
+ # processes
+ smf_kill_contract $2 TERM 1 30
+ ret=$?
+ [ $ret -eq 1 ] && exit 1
+
+ # Since sendmail spawns user processes out of .forward files, it is
+ # possible that some of these are not responding to TERM. If the
+ # contract did not empty after TERM, move on to KILL.
+ if [ $ret -eq 2 ] ; then
+ smf_kill_contract $2 KILL 1
+ fi
+ ;;
+
+*)
+ echo "Usage: $0 { start | stop | refresh }"
+ exit 1
+ ;;
+esac
+exit 0
diff --git a/usr/src/cmd/sendmail/lib/smtp-sendmail.xml b/usr/src/cmd/sendmail/lib/smtp-sendmail.xml
new file mode 100644
index 0000000000..0f400a9936
--- /dev/null
+++ b/usr/src/cmd/sendmail/lib/smtp-sendmail.xml
@@ -0,0 +1,177 @@
+<?xml version="1.0"?>
+<!DOCTYPE service_bundle SYSTEM "/usr/share/lib/xml/dtd/service_bundle.dtd.1">
+<!--
+ Copyright 2005 Sun Microsystems, Inc. All rights reserved.
+ Use is subject to license terms.
+
+ CDDL HEADER START
+
+ The contents of this file are subject to the terms of the
+ Common Development and Distribution License, Version 1.0 only
+ (the "License"). You may not use this file except in compliance
+ with the License.
+
+ You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ or http://www.opensolaris.org/os/licensing.
+ See the License for the specific language governing permissions
+ and limitations under the License.
+
+ When distributing Covered Code, include this CDDL HEADER in each
+ file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ If applicable, add the following below this CDDL HEADER, with the
+ fields enclosed by brackets "[]" replaced with your own identifying
+ information: Portions Copyright [yyyy] [name of copyright owner]
+
+ CDDL HEADER END
+
+ ident "%Z%%M% %I% %E% SMI"
+
+ NOTE: This service manifest is not editable; its contents will
+ be overwritten by package or patch operations, including
+ operating system upgrade. Make customizations in a different
+ file.
+-->
+
+<service_bundle type='manifest' name='SUNWsndmr:sendmail'>
+
+<service
+ name='network/smtp'
+ type='service'
+ version='1'>
+
+ <single_instance />
+
+ <dependency
+ name='fs-local'
+ grouping='require_all'
+ restart_on='none'
+ type='service'>
+ <service_fmri value='svc:/system/filesystem/local' />
+ </dependency>
+
+ <dependency
+ name='network-service'
+ grouping='require_all'
+ restart_on='none'
+ type='service'>
+ <service_fmri value='svc:/network/service' />
+ </dependency>
+
+ <dependency
+ name='name-services'
+ grouping='require_all'
+ restart_on='refresh'
+ type='service'>
+ <service_fmri value='svc:/milestone/name-services' />
+ </dependency>
+
+ <dependency
+ name='identity'
+ grouping='optional_all'
+ restart_on='refresh'
+ type='service'>
+ <service_fmri value='svc:/system/identity:domain' />
+ </dependency>
+
+ <dependency
+ name='system-log'
+ grouping='optional_all'
+ restart_on='none'
+ type='service'>
+ <service_fmri value='svc:/system/system-log' />
+ </dependency>
+
+ <instance name='sendmail' enabled='false'>
+
+ <dependency
+ name='config-file'
+ grouping='require_all'
+ restart_on='refresh'
+ type='path'>
+ <service_fmri
+ value='file://localhost/etc/mail/sendmail.cf' />
+ </dependency>
+
+ <dependency
+ name='nsswitch'
+ grouping='require_all'
+ restart_on='refresh'
+ type='path'>
+ <service_fmri
+ value='file://localhost/etc/nsswitch.conf' />
+ </dependency>
+
+ <!--
+ If autofs is enabled, wait for it to get users' home
+ directories.
+ -->
+ <dependency
+ name='autofs'
+ grouping='optional_all'
+ restart_on='none'
+ type='service'>
+ <service_fmri value='svc:/system/filesystem/autofs' />
+ </dependency>
+
+ <dependent
+ name='smtp-sendmail_multi-user'
+ grouping='optional_all'
+ restart_on='none'>
+ <service_fmri
+ value='svc:/milestone/multi-user' />
+ </dependent>
+
+ <!--
+ Sendmail is hard-coded to sleep for 60 seconds if it cannot
+ determine the FQHN, so the timeout for start must be longer
+ than that. For details, see
+ http://www.sendmail.org/vendor/sun/differences.html#3.2
+ -->
+
+ <exec_method
+ type='method'
+ name='start'
+ exec='/lib/svc/method/smtp-sendmail start'
+ timeout_seconds='120' />
+
+ <exec_method
+ type='method'
+ name='stop'
+ exec='/lib/svc/method/smtp-sendmail stop %{restarter/contract}'
+ timeout_seconds='60' />
+
+ <exec_method
+ type='method'
+ name='refresh'
+ exec='/lib/svc/method/smtp-sendmail refresh'
+ timeout_seconds='60' />
+
+ <property_group name='startd' type='framework'>
+ <propval name='ignore_error' type='astring'
+ value='core,signal' />
+ </property_group>
+
+ <property_group name='general' type='framework'>
+ <propval name='action_authorization' type='astring'
+ value='solaris.smf.manage.sendmail' />
+ </property_group>
+
+ <template>
+ <common_name>
+ <loctext xml:lang='C'>
+ sendmail SMTP mail transfer agent
+ </loctext>
+ </common_name>
+ <documentation>
+ <manpage title='sendmail' section='1M'
+ manpath='/usr/share/man' />
+ </documentation>
+ </template>
+
+ </instance>
+
+ <stability value='Unstable' />
+
+</service>
+
+</service_bundle>
diff --git a/usr/src/cmd/sendmail/lib/trusted-users b/usr/src/cmd/sendmail/lib/trusted-users
new file mode 100644
index 0000000000..d8649da39d
--- /dev/null
+++ b/usr/src/cmd/sendmail/lib/trusted-users
@@ -0,0 +1 @@
+root
diff --git a/usr/src/cmd/sendmail/libmilter/Makefile b/usr/src/cmd/sendmail/libmilter/Makefile
new file mode 100644
index 0000000000..cf41698dbe
--- /dev/null
+++ b/usr/src/cmd/sendmail/libmilter/Makefile
@@ -0,0 +1,60 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License, Version 1.0 only
+# (the "License"). You may not use this file except in compliance
+# with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+#
+# Copyright 2004 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# ident "%Z%%M% %I% %E% SMI"
+#
+
+include $(SRC)/lib/Makefile.lib
+
+HDRS = mfapi.h mfdef.h
+HDRDIR = ../include/libmilter
+ROOTHDRDIR = $(ROOT)/usr/include/libmilter
+
+SUBDIRS = $(MACH)
+
+all := TARGET = all
+clean := TARGET = clean
+clobber := TARGET = clobber
+install := TARGET = install
+lint := TARGET = lint
+
+.KEEP_STATE:
+
+all clean clobber lint: $(SUBDIRS)
+install: $(SUBDIRS) $(ROOTHDRDIR)/README
+
+install_h: $(ROOTHDRS)
+
+check: $(CHECKHDRS)
+
+$(ROOTHDRDIR)/README%: README%
+ $(INS.file)
+
+$(SUBDIRS): FRC
+ @cd $@; pwd; $(MAKE) $(TARGET)
+
+FRC:
+
+include $(SRC)/lib/Makefile.targ
diff --git a/usr/src/cmd/sendmail/libmilter/Makefile.com b/usr/src/cmd/sendmail/libmilter/Makefile.com
new file mode 100644
index 0000000000..c4ac692bfc
--- /dev/null
+++ b/usr/src/cmd/sendmail/libmilter/Makefile.com
@@ -0,0 +1,66 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License, Version 1.0 only
+# (the "License"). You may not use this file except in compliance
+# with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+#
+# Copyright 2005 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# ident "%Z%%M% %I% %E% SMI"
+#
+
+LIBRARY= libmilter.a
+VERS= .1
+LOCOBJS= main.o engine.o listener.o handler.o comm.o smfi.o signal.o \
+ sm_gethost.o
+REMOBJS= errstring.o strl.o
+OBJECTS= $(LOCOBJS) $(REMOBJS)
+SENDMAIL= $(SRC)/cmd/sendmail
+
+include $(SENDMAIL)/Makefile.cmd
+include $(SRC)/lib/Makefile.lib
+
+REMDIR= $(SENDMAIL)/libsm
+SRCDIR= $(SENDMAIL)/libmilter
+
+SRCS= $(LOCOBJS:%.o=$(SRCDIR)/%.c) $(REMOBJS:%.o=$(REMDIR)/%.c)
+
+INCPATH= -I$(SENDMAIL)/src -I$(SENDMAIL)/include
+ENVDEF= $(RLS_DEF) -DMILTER -DNETINET6 -DNOT_SENDMAIL \
+ -Dsm_snprintf=snprintf -D_REENTRANT -D_FFR_MILTER_ROOT_UNSAFE
+CPPFLAGS += $(INCPATH) $(ENVDEF)
+
+LIBS= $(DYNLIB) $(LINTLIB)
+LDLIBS += -lc -lsocket -lnsl
+$(LINTLIB) := SRCS= $(SRCDIR)/$(LINTSRC)
+
+.KEEP_STATE:
+
+all: $(LIBS)
+
+install: all .WAIT $(ROOTLIBS) $(ROOTLINKS) $(ROOTLINT)
+
+lint: lintcheck
+
+include $(SRC)/lib/Makefile.targ
+
+pics/%.o: $(REMDIR)/%.c
+ $(COMPILE.c) -o $@ $<
+ $(POST_PROCESS_O)
diff --git a/usr/src/cmd/sendmail/libmilter/README b/usr/src/cmd/sendmail/libmilter/README
new file mode 100644
index 0000000000..4079c4f9ef
--- /dev/null
+++ b/usr/src/cmd/sendmail/libmilter/README
@@ -0,0 +1,420 @@
+#
+# Copyright 2004 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# ident "%Z%%M% %I% %E% SMI"
+#
+
+The sendmail Mail Filter API (Milter) is designed to allow third-party
+programs access to mail messages as they are being processed in order to
+filter meta-information and content.
+
+This README file describes the steps needed to compile and run a filter,
+through reference to a sample filter which is attached at the end of this
+file.
+
++----------------+
+| SECURITY HINTS |
++----------------+
+
+Note: we strongly recommend not to run any milter as root. Libmilter
+does not need root access to communicate with sendmail. It is a
+good security practice to run a program only with root privileges
+if really necessary. A milter should probably check first whether
+it runs as root and refuse to start in that case. libmilter will
+not unlink a socket when running as root.
+
++-------------------+
+| BUILDING A FILTER |
++-------------------+
+
+The following command presumes that the sample code from the end of this
+README is saved to a file named 'sample.c'.
+
+ cc -D_REENTRANT -o sample sample.c -lmilter
+
+Filters must be thread-safe!
+
+Note that since filters use threads, it may be necessary to alter per
+process limits in your filter. For example, you might look at using
+setrlimit() to increase the number of open file descriptors if your filter
+is going to be busy.
+
+
++----------------------------------------+
+| SPECIFYING FILTERS IN SENDMAIL CONFIGS |
++----------------------------------------+
+
+Filters are specified with a key letter ``X'' (for ``eXternal'').
+
+For example:
+
+ Xfilter1, S=local:/var/run/f1.sock, F=R
+ Xfilter2, S=inet6:999@localhost, F=T, T=C:10m;S:1s;R:1s;E:5m
+ Xfilter3, S=inet:3333@localhost
+
+specifies three filters. Filters can be specified in your .mc file using
+the following:
+
+ INPUT_MAIL_FILTER(`filter1', `S=local:/var/run/f1.sock, F=R')
+ INPUT_MAIL_FILTER(`filter2', `S=inet6:999@localhost, F=T, T=C:10m;S:1s;R:1s;E:5m')
+ INPUT_MAIL_FILTER(`filter3', `S=inet:3333@localhost')
+
+The first attaches to a Unix-domain socket in the /var/run directory; the
+second uses an IPv6 socket on port 999 of localhost, and the third uses an
+IPv4 socket on port 3333 of localhost. The current flags (F=) are:
+
+ R Reject connection if filter unavailable
+ T Temporary fail connection if filter unavailable
+
+If neither F=R nor F=T is specified, the message is passed through sendmail
+in case of filter errors as if the failing filters were not present.
+
+Finally, you can override the default timeouts used by sendmail when
+talking to the filters using the T= equate. There are four fields inside
+of the T= equate:
+
+Letter Meaning
+ C Timeout for connecting to a filter (if 0, use system timeout)
+ S Timeout for sending information from the MTA to a filter
+ R Timeout for reading reply from the filter
+ E Overall timeout between sending end-of-message to filter
+ and waiting for the final acknowledgment
+
+Note the separator between each is a ';' as a ',' already separates equates
+and therefore can't separate timeouts. The default values (if not set in
+the config) are:
+
+T=C:5m;S:10s;R:10s;E:5m
+
+where 's' is seconds and 'm' is minutes.
+
+Which filters are invoked and their sequencing is handled by the
+InputMailFilters option. Note: if InputMailFilters is not defined no filters
+will be used.
+
+ O InputMailFilters=filter1, filter2, filter3
+
+This is is set automatically according to the order of the
+INPUT_MAIL_FILTER commands in your .mc file. Alternatively, you can
+reset its value by setting confINPUT_MAIL_FILTERS in your .mc file.
+This options causes the three filters to be called in the same order
+they were specified. It allows for possible future filtering on output
+(although this is not intended for this release).
+
+Also note that a filter can be defined without adding it to the input
+filter list by using MAIL_FILTER() instead of INPUT_MAIL_FILTER() in your
+.mc file.
+
+To test sendmail with the sample filter, the following might be added (in
+the appropriate locations) to your .mc file:
+
+ INPUT_MAIL_FILTER(`sample', `S=local:/var/run/f1.sock')
+
+
++------------------+
+| TESTING A FILTER |
++------------------+
+
+Once you have compiled a filter, modified your .mc file and restarted
+the sendmail process, you will want to test that the filter performs as
+intended.
+
+The sample filter takes one argument -p, which indicates the local port
+on which to create a listening socket for the filter. Maintaining
+consistency with the suggested options for sendmail.cf, this would be the
+UNIX domain socket located in /var/run/f1.sock.
+
+ % ./sample -p local:/var/run/f1.sock
+
+If the sample filter returns immediately to a command line, there was either
+an error with your command or a problem creating the specified socket.
+Further logging can be captured through the syslogd daemon. Using the
+'netstat -a' command can ensure that your filter process is listening on
+the appropriate local socket.
+
+Email messages must be injected via SMTP to be filtered. There are two
+simple means of doing this; either using the 'sendmail -bs' command, or
+by telnetting to port 25 of the machine configured for milter. Once
+connected via one of these options, the session can be continued through
+the use of standard SMTP commands.
+
+% sendmail -bs
+220 test.sendmail.com ESMTP Sendmail 8.11.0/8.11.0; Tue, 10 Nov 1970 13:05:23 -0500 (EST)
+HELO localhost
+250 test.sendmail.com Hello testy@localhost, pleased to meet you
+MAIL From:<testy>
+250 2.1.0 <testy>... Sender ok
+RCPT To:<root>
+250 2.1.5 <root>... Recipient ok
+DATA
+354 Enter mail, end with "." on a line by itself
+From: testy@test.sendmail.com
+To: root@test.sendmail.com
+Subject: testing sample filter
+
+Sample body
+.
+250 2.0.0 dB73Zxi25236 Message accepted for delivery
+QUIT
+221 2.0.0 test.sendmail.com closing connection
+
+In the above example, the lines beginning with numbers are output by the
+mail server, and those without are your input. If everything is working
+properly, you will find a file in /tmp by the name of msg.XXXXXXXX (where
+the Xs represent any combination of letters and numbers). This file should
+contain the message body and headers from the test email entered above.
+
+If the sample filter did not log your test email, there are a number of
+methods to narrow down the source of the problem. Check your system
+logs written by syslogd and see if there are any pertinent lines. You
+may need to reconfigure syslogd to capture all relevant data. Additionally,
+the logging level of sendmail can be raised with the LogLevel option.
+See the sendmail(8) manual page for more information.
+
+
++--------------------------+
+| SOURCE FOR SAMPLE FILTER |
++--------------------------+
+
+/* A trivial filter that logs all email to a file. */
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+#include "libmilter/mfapi.h"
+
+#ifndef true
+typedef int bool;
+# define false 0
+# define true 1
+#endif /* ! true */
+
+struct mlfiPriv
+{
+ char *mlfi_fname;
+ FILE *mlfi_fp;
+};
+
+#define MLFIPRIV ((struct mlfiPriv *) smfi_getpriv(ctx))
+
+extern sfsistat mlfi_cleanup(SMFICTX *, bool);
+
+sfsistat
+mlfi_envfrom(ctx, envfrom)
+ SMFICTX *ctx;
+ char **envfrom;
+{
+ struct mlfiPriv *priv;
+ int fd = -1;
+
+ /* allocate some private memory */
+ priv = malloc(sizeof *priv);
+ if (priv == NULL)
+ {
+ /* can't accept this message right now */
+ return SMFIS_TEMPFAIL;
+ }
+ memset(priv, '\0', sizeof *priv);
+
+ /* open a file to store this message */
+ priv->mlfi_fname = strdup("/tmp/msg.XXXXXXXX");
+ if (priv->mlfi_fname == NULL)
+ {
+ free(priv);
+ return SMFIS_TEMPFAIL;
+ }
+ if ((fd = mkstemp(priv->mlfi_fname)) < 0 ||
+ (priv->mlfi_fp = fdopen(fd, "w+")) == NULL)
+ {
+ if (fd >= 0)
+ (void) close(fd);
+ free(priv->mlfi_fname);
+ free(priv);
+ return SMFIS_TEMPFAIL;
+ }
+
+ /* save the private data */
+ smfi_setpriv(ctx, priv);
+
+ /* continue processing */
+ return SMFIS_CONTINUE;
+}
+
+sfsistat
+mlfi_header(ctx, headerf, headerv)
+ SMFICTX *ctx;
+ char *headerf;
+ char *headerv;
+{
+ /* write the header to the log file */
+ fprintf(MLFIPRIV->mlfi_fp, "%s: %s\r\n", headerf, headerv);
+
+ /* continue processing */
+ return SMFIS_CONTINUE;
+}
+
+sfsistat
+mlfi_eoh(ctx)
+ SMFICTX *ctx;
+{
+ /* output the blank line between the header and the body */
+ fprintf(MLFIPRIV->mlfi_fp, "\r\n");
+
+ /* continue processing */
+ return SMFIS_CONTINUE;
+}
+
+sfsistat
+mlfi_body(ctx, bodyp, bodylen)
+ SMFICTX *ctx;
+ u_char *bodyp;
+ size_t bodylen;
+{
+ /* output body block to log file */
+ if (fwrite(bodyp, bodylen, 1, MLFIPRIV->mlfi_fp) == 0)
+ {
+ /* write failed */
+ (void) mlfi_cleanup(ctx, false);
+ return SMFIS_TEMPFAIL;
+ }
+
+ /* continue processing */
+ return SMFIS_CONTINUE;
+}
+
+sfsistat
+mlfi_eom(ctx)
+ SMFICTX *ctx;
+{
+ return mlfi_cleanup(ctx, true);
+}
+
+sfsistat
+mlfi_close(ctx)
+ SMFICTX *ctx;
+{
+ return SMFIS_ACCEPT;
+}
+
+sfsistat
+mlfi_abort(ctx)
+ SMFICTX *ctx;
+{
+ return mlfi_cleanup(ctx, false);
+}
+
+sfsistat
+mlfi_cleanup(ctx, ok)
+ SMFICTX *ctx;
+ bool ok;
+{
+ sfsistat rstat = SMFIS_CONTINUE;
+ struct mlfiPriv *priv = MLFIPRIV;
+ char *p;
+ char host[512];
+ char hbuf[1024];
+
+ if (priv == NULL)
+ return rstat;
+
+ /* close the archive file */
+ if (priv->mlfi_fp != NULL && fclose(priv->mlfi_fp) == EOF)
+ {
+ /* failed; we have to wait until later */
+ rstat = SMFIS_TEMPFAIL;
+ (void) unlink(priv->mlfi_fname);
+ }
+ else if (ok)
+ {
+ /* add a header to the message announcing our presence */
+ if (gethostname(host, sizeof host) < 0)
+ snprintf(host, sizeof host, "localhost");
+ p = strrchr(priv->mlfi_fname, '/');
+ if (p == NULL)
+ p = priv->mlfi_fname;
+ else
+ p++;
+ snprintf(hbuf, sizeof hbuf, "%s@%s", p, host);
+ smfi_addheader(ctx, "X-Archived", hbuf);
+ }
+ else
+ {
+ /* message was aborted -- delete the archive file */
+ (void) unlink(priv->mlfi_fname);
+ }
+
+ /* release private memory */
+ free(priv->mlfi_fname);
+ free(priv);
+ smfi_setpriv(ctx, NULL);
+
+ /* return status */
+ return rstat;
+}
+
+struct smfiDesc smfilter =
+{
+ "SampleFilter", /* filter name */
+ SMFI_VERSION, /* version code -- do not change */
+ SMFIF_ADDHDRS, /* flags */
+ NULL, /* connection info filter */
+ NULL, /* SMTP HELO command filter */
+ mlfi_envfrom, /* envelope sender filter */
+ NULL, /* envelope recipient filter */
+ mlfi_header, /* header filter */
+ mlfi_eoh, /* end of header */
+ mlfi_body, /* body block filter */
+ mlfi_eom, /* end of message */
+ mlfi_abort, /* message aborted */
+ mlfi_close /* connection cleanup */
+};
+
+
+int
+main(argc, argv)
+ int argc;
+ char *argv[];
+{
+ bool setconn = false;
+ int c;
+ const char *args = "p:";
+
+ /* Process command line options */
+ while ((c = getopt(argc, argv, args)) != -1)
+ {
+ switch (c)
+ {
+ case 'p':
+ if (optarg == NULL || *optarg == '\0')
+ {
+ (void) fprintf(stderr, "Illegal conn: %s\n",
+ optarg);
+ exit(EX_USAGE);
+ }
+ (void) smfi_setconn(optarg);
+ setconn = true;
+ break;
+
+ }
+ }
+ if (!setconn)
+ {
+ fprintf(stderr, "%s: Missing required -p argument\n", argv[0]);
+ exit(EX_USAGE);
+ }
+ if (smfi_register(smfilter) == MI_FAILURE)
+ {
+ fprintf(stderr, "smfi_register failed\n");
+ exit(EX_UNAVAILABLE);
+ }
+ return smfi_main();
+}
+
+/* eof */
+
+$Revision: 8.35.2.2 $, Last updated $Date: 2003/05/26 04:10:06 $
diff --git a/usr/src/cmd/sendmail/libmilter/comm.c b/usr/src/cmd/sendmail/libmilter/comm.c
new file mode 100644
index 0000000000..fd64922d37
--- /dev/null
+++ b/usr/src/cmd/sendmail/libmilter/comm.c
@@ -0,0 +1,362 @@
+/*
+ * Copyright (c) 1999-2004 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: comm.c,v 8.66 2004/08/20 20:38:35 ca Exp $")
+
+#include "libmilter.h"
+#include <sm/errstring.h>
+#include <sys/uio.h>
+
+static ssize_t retry_writev __P((socket_t, struct iovec *, int, struct timeval *));
+static size_t Maxdatasize = MILTER_MAX_DATA_SIZE;
+
+#if _FFR_MAXDATASIZE
+/*
+** SMFI_SETMAXDATASIZE -- set limit for milter data read/write.
+**
+** Parameters:
+** sz -- new limit.
+**
+** Returns:
+** old limit
+*/
+
+size_t
+smfi_setmaxdatasize(sz)
+ size_t sz;
+{
+ size_t old;
+
+ old = Maxdatasize;
+ Maxdatasize = sz;
+ return old;
+}
+#endif /* _FFR_MAXDATASIZE */
+
+/*
+** MI_RD_CMD -- read a command
+**
+** Parameters:
+** sd -- socket descriptor
+** timeout -- maximum time to wait
+** cmd -- single character command read from sd
+** rlen -- pointer to length of result
+** name -- name of milter
+**
+** Returns:
+** buffer with rest of command
+** (malloc()ed here, should be free()d)
+** hack: encode error in cmd
+*/
+
+char *
+mi_rd_cmd(sd, timeout, cmd, rlen, name)
+ socket_t sd;
+ struct timeval *timeout;
+ char *cmd;
+ size_t *rlen;
+ char *name;
+{
+ ssize_t len;
+ mi_int32 expl;
+ ssize_t i;
+ FD_RD_VAR(rds, excs);
+ int ret;
+ int save_errno;
+ char *buf;
+ char data[MILTER_LEN_BYTES + 1];
+
+ *cmd = '\0';
+ *rlen = 0;
+
+ i = 0;
+ for (;;)
+ {
+ FD_RD_INIT(sd, rds, excs);
+ ret = FD_RD_READY(sd, rds, excs, timeout);
+ if (ret == 0)
+ break;
+ else if (ret < 0)
+ {
+ if (errno == EINTR)
+ continue;
+ break;
+ }
+ if (FD_IS_RD_EXC(sd, rds, excs))
+ {
+ *cmd = SMFIC_SELECT;
+ return NULL;
+ }
+
+ len = MI_SOCK_READ(sd, data + i, sizeof data - i);
+ if (MI_SOCK_READ_FAIL(len))
+ {
+ smi_log(SMI_LOG_ERR,
+ "%s, mi_rd_cmd: read returned %d: %s",
+ name, (int) len, sm_errstring(errno));
+ *cmd = SMFIC_RECVERR;
+ return NULL;
+ }
+ if (len == 0)
+ {
+ *cmd = SMFIC_EOF;
+ return NULL;
+ }
+ if (len >= (ssize_t) sizeof data - i)
+ break;
+ i += len;
+ }
+ if (ret == 0)
+ {
+ *cmd = SMFIC_TIMEOUT;
+ return NULL;
+ }
+ else if (ret < 0)
+ {
+ smi_log(SMI_LOG_ERR,
+ "%s: mi_rd_cmd: select returned %d: %s",
+ name, ret, sm_errstring(errno));
+ *cmd = SMFIC_RECVERR;
+ return NULL;
+ }
+
+ *cmd = data[MILTER_LEN_BYTES];
+ data[MILTER_LEN_BYTES] = '\0';
+ (void) memcpy((void *) &expl, (void *) &(data[0]), MILTER_LEN_BYTES);
+ expl = ntohl(expl) - 1;
+ if (expl <= 0)
+ return NULL;
+ if (expl > Maxdatasize)
+ {
+ *cmd = SMFIC_TOOBIG;
+ return NULL;
+ }
+#if _FFR_ADD_NULL
+ buf = malloc(expl + 1);
+#else /* _FFR_ADD_NULL */
+ buf = malloc(expl);
+#endif /* _FFR_ADD_NULL */
+ if (buf == NULL)
+ {
+ *cmd = SMFIC_MALLOC;
+ return NULL;
+ }
+
+ i = 0;
+ for (;;)
+ {
+ FD_RD_INIT(sd, rds, excs);
+ ret = FD_RD_READY(sd, rds, excs, timeout);
+ if (ret == 0)
+ break;
+ else if (ret < 0)
+ {
+ if (errno == EINTR)
+ continue;
+ break;
+ }
+ if (FD_IS_RD_EXC(sd, rds, excs))
+ {
+ *cmd = SMFIC_SELECT;
+ free(buf);
+ return NULL;
+ }
+ len = MI_SOCK_READ(sd, buf + i, expl - i);
+ if (MI_SOCK_READ_FAIL(len))
+ {
+ smi_log(SMI_LOG_ERR,
+ "%s: mi_rd_cmd: read returned %d: %s",
+ name, (int) len, sm_errstring(errno));
+ ret = -1;
+ break;
+ }
+ if (len == 0)
+ {
+ *cmd = SMFIC_EOF;
+ free(buf);
+ return NULL;
+ }
+ if (len > expl - i)
+ {
+ *cmd = SMFIC_RECVERR;
+ free(buf);
+ return NULL;
+ }
+ if (len >= expl - i)
+ {
+ *rlen = expl;
+#if _FFR_ADD_NULL
+ /* makes life simpler for common string routines */
+ buf[expl] = '\0';
+#endif /* _FFR_ADD_NULL */
+ return buf;
+ }
+ i += len;
+ }
+
+ save_errno = errno;
+ free(buf);
+
+ /* select returned 0 (timeout) or < 0 (error) */
+ if (ret == 0)
+ {
+ *cmd = SMFIC_TIMEOUT;
+ return NULL;
+ }
+ if (ret < 0)
+ {
+ smi_log(SMI_LOG_ERR,
+ "%s: mi_rd_cmd: select returned %d: %s",
+ name, ret, sm_errstring(save_errno));
+ *cmd = SMFIC_RECVERR;
+ return NULL;
+ }
+ *cmd = SMFIC_UNKNERR;
+ return NULL;
+}
+
+/*
+** RETRY_WRITEV -- Keep calling the writev() system call
+** until all the data is written out or an error occurs.
+**
+** Parameters:
+** fd -- socket descriptor
+** iov -- io vector
+** iovcnt -- number of elements in io vector
+** must NOT exceed UIO_MAXIOV.
+** timeout -- maximum time to wait
+**
+** Returns:
+** success: number of bytes written
+** otherwise: MI_FAILURE
+*/
+
+static ssize_t
+retry_writev(fd, iov, iovcnt, timeout)
+ socket_t fd;
+ struct iovec *iov;
+ int iovcnt;
+ struct timeval *timeout;
+{
+ int i;
+ ssize_t n, written;
+ FD_WR_VAR(wrs);
+
+ written = 0;
+ for (;;)
+ {
+ while (iovcnt > 0 && iov[0].iov_len == 0)
+ {
+ iov++;
+ iovcnt--;
+ }
+ if (iovcnt <= 0)
+ return written;
+
+ /*
+ ** We don't care much about the timeout here,
+ ** it's very long anyway; correct solution would be
+ ** to take the time before the loop and reduce the
+ ** timeout after each invocation.
+ ** FD_SETSIZE is checked when socket is created.
+ */
+
+ FD_WR_INIT(fd, wrs);
+ i = FD_WR_READY(fd, wrs, timeout);
+ if (i == 0)
+ return MI_FAILURE;
+ if (i < 0)
+ {
+ if (errno == EINTR || errno == EAGAIN)
+ continue;
+ return MI_FAILURE;
+ }
+ n = writev(fd, iov, iovcnt);
+ if (n == -1)
+ {
+ if (errno == EINTR || errno == EAGAIN)
+ continue;
+ return MI_FAILURE;
+ }
+
+ written += n;
+ for (i = 0; i < iovcnt; i++)
+ {
+ if (iov[i].iov_len > (unsigned int) n)
+ {
+ iov[i].iov_base = (char *)iov[i].iov_base + n;
+ iov[i].iov_len -= (unsigned int) n;
+ break;
+ }
+ n -= (int) iov[i].iov_len;
+ iov[i].iov_len = 0;
+ }
+ if (i == iovcnt)
+ return written;
+ }
+}
+
+/*
+** MI_WR_CMD -- write a cmd to sd
+**
+** Parameters:
+** sd -- socket descriptor
+** timeout -- maximum time to wait
+** cmd -- single character command to write
+** buf -- buffer with further data
+** len -- length of buffer (without cmd!)
+**
+** Returns:
+** MI_SUCCESS/MI_FAILURE
+*/
+
+int
+mi_wr_cmd(sd, timeout, cmd, buf, len)
+ socket_t sd;
+ struct timeval *timeout;
+ int cmd;
+ char *buf;
+ size_t len;
+{
+ size_t sl, i;
+ ssize_t l;
+ mi_int32 nl;
+ int iovcnt;
+ struct iovec iov[2];
+ char data[MILTER_LEN_BYTES + 1];
+
+ if (len > Maxdatasize || (len > 0 && buf == NULL))
+ return MI_FAILURE;
+
+ nl = htonl(len + 1); /* add 1 for the cmd char */
+ (void) memcpy(data, (void *) &nl, MILTER_LEN_BYTES);
+ data[MILTER_LEN_BYTES] = (char) cmd;
+ i = 0;
+ sl = MILTER_LEN_BYTES + 1;
+
+ /* set up the vector for the size / command */
+ iov[0].iov_base = (void *) data;
+ iov[0].iov_len = sl;
+ iovcnt = 1;
+ if (len >= 0 && buf != NULL)
+ {
+ iov[1].iov_base = (void *) buf;
+ iov[1].iov_len = len;
+ iovcnt = 2;
+ }
+
+ l = retry_writev(sd, iov, iovcnt, timeout);
+ if (l == MI_FAILURE)
+ return MI_FAILURE;
+ return MI_SUCCESS;
+}
diff --git a/usr/src/cmd/sendmail/libmilter/engine.c b/usr/src/cmd/sendmail/libmilter/engine.c
new file mode 100644
index 0000000000..19f8218bc9
--- /dev/null
+++ b/usr/src/cmd/sendmail/libmilter/engine.c
@@ -0,0 +1,1252 @@
+/*
+ * Copyright (c) 1999-2003 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: engine.c,v 8.120 2004/10/20 21:09:00 ca Exp $")
+
+#include "libmilter.h"
+
+#if NETINET || NETINET6
+# include <arpa/inet.h>
+#endif /* NETINET || NETINET6 */
+
+/* generic argument for functions in the command table */
+struct arg_struct
+{
+ size_t a_len; /* length of buffer */
+ char *a_buf; /* argument string */
+ int a_idx; /* index for macro array */
+ SMFICTX_PTR a_ctx; /* context */
+};
+
+typedef struct arg_struct genarg;
+
+/* structure for commands received from MTA */
+struct cmdfct_t
+{
+ char cm_cmd; /* command */
+ int cm_argt; /* type of arguments expected */
+ int cm_next; /* next state */
+ int cm_todo; /* what to do next */
+ int cm_macros; /* index for macros */
+ int (*cm_fct) __P((genarg *)); /* function to execute */
+};
+
+typedef struct cmdfct_t cmdfct;
+
+/* possible values for cm_argt */
+#define CM_ARG0 0 /* no args */
+#define CM_ARG1 1 /* one arg (string) */
+#define CM_ARG2 2 /* two args (strings) */
+#define CM_ARGA 4 /* one string and _SOCK_ADDR */
+#define CM_ARGO 5 /* two integers */
+#define CM_ARGV 8 /* \0 separated list of args, NULL-terminated */
+#define CM_ARGN 9 /* \0 separated list of args (strings) */
+
+/* possible values for cm_todo */
+#define CT_CONT 0x0000 /* continue reading commands */
+#define CT_IGNO 0x0001 /* continue even when error */
+
+/* not needed right now, done via return code instead */
+#define CT_KEEP 0x0004 /* keep buffer (contains symbols) */
+#define CT_END 0x0008 /* start replying */
+
+/* index in macro array: macros only for these commands */
+#define CI_NONE (-1)
+#define CI_CONN 0
+#define CI_HELO 1
+#define CI_MAIL 2
+#define CI_RCPT 3
+#define CI_EOM 4
+#if CI_EOM >= MAX_MACROS_ENTRIES
+ERROR: do not compile with CI_EOM >= MAX_MACROS_ENTRIES
+#endif
+
+/* function prototypes */
+static int st_abortfct __P((genarg *));
+static int st_macros __P((genarg *));
+static int st_optionneg __P((genarg *));
+static int st_bodychunk __P((genarg *));
+static int st_connectinfo __P((genarg *));
+static int st_bodyend __P((genarg *));
+static int st_helo __P((genarg *));
+static int st_header __P((genarg *));
+static int st_sender __P((genarg *));
+static int st_rcpt __P((genarg *));
+#if SMFI_VERSION > 2
+static int st_unknown __P((genarg *));
+#endif /* SMFI_VERSION > 2 */
+#if SMFI_VERSION > 3
+static int st_data __P((genarg *));
+#endif /* SMFI_VERSION > 3 */
+static int st_eoh __P((genarg *));
+static int st_quit __P((genarg *));
+static int sendreply __P((sfsistat, socket_t, struct timeval *, SMFICTX_PTR));
+static void fix_stm __P((SMFICTX_PTR));
+static bool trans_ok __P((int, int));
+static char **dec_argv __P((char *, size_t));
+static int dec_arg2 __P((char *, size_t, char **, char **));
+
+/* states */
+#define ST_NONE (-1)
+#define ST_INIT 0 /* initial state */
+#define ST_OPTS 1 /* option negotiation */
+#define ST_CONN 2 /* connection info */
+#define ST_HELO 3 /* helo */
+#define ST_MAIL 4 /* mail from */
+#define ST_RCPT 5 /* rcpt to */
+#define ST_DATA 6 /* data */
+#define ST_HDRS 7 /* headers */
+#define ST_EOHS 8 /* end of headers */
+#define ST_BODY 9 /* body */
+#define ST_ENDM 10 /* end of message */
+#define ST_QUIT 11 /* quit */
+#define ST_ABRT 12 /* abort */
+#define ST_UNKN 13 /* unknown SMTP command */
+#define ST_LAST ST_UNKN /* last valid state */
+#define ST_SKIP 15 /* not a state but required for the state table */
+
+/* in a mail transaction? must be before eom according to spec. */
+#define ST_IN_MAIL(st) ((st) >= ST_MAIL && (st) < ST_ENDM)
+
+/*
+** set of next states
+** each state (ST_*) corresponds to bit in an int value (1 << state)
+** each state has a set of allowed transitions ('or' of bits of states)
+** so a state transition is valid if the mask of the next state
+** is set in the NX_* value
+** this function is coded in trans_ok(), see below.
+*/
+
+#define MI_MASK(x) (0x0001 << (x)) /* generate a bit "mask" for a state */
+#define NX_INIT (MI_MASK(ST_OPTS))
+#define NX_OPTS (MI_MASK(ST_CONN) | MI_MASK(ST_UNKN))
+#define NX_CONN (MI_MASK(ST_HELO) | MI_MASK(ST_MAIL) | MI_MASK(ST_UNKN))
+#define NX_HELO (MI_MASK(ST_HELO) | MI_MASK(ST_MAIL) | MI_MASK(ST_UNKN))
+#define NX_MAIL (MI_MASK(ST_RCPT) | MI_MASK(ST_ABRT) | MI_MASK(ST_UNKN))
+#define NX_RCPT (MI_MASK(ST_HDRS) | MI_MASK(ST_EOHS) | MI_MASK(ST_DATA) | \
+ MI_MASK(ST_BODY) | MI_MASK(ST_ENDM) | \
+ MI_MASK(ST_RCPT) | MI_MASK(ST_ABRT) | MI_MASK(ST_UNKN))
+#define NX_DATA (MI_MASK(ST_EOHS) | MI_MASK(ST_HDRS) | MI_MASK(ST_ABRT))
+#define NX_HDRS (MI_MASK(ST_EOHS) | MI_MASK(ST_HDRS) | MI_MASK(ST_ABRT))
+#define NX_EOHS (MI_MASK(ST_BODY) | MI_MASK(ST_ENDM) | MI_MASK(ST_ABRT))
+#define NX_BODY (MI_MASK(ST_ENDM) | MI_MASK(ST_BODY) | MI_MASK(ST_ABRT))
+#define NX_ENDM (MI_MASK(ST_QUIT) | MI_MASK(ST_MAIL) | MI_MASK(ST_UNKN))
+#define NX_QUIT 0
+#define NX_ABRT 0
+#define NX_UNKN (MI_MASK(ST_HELO) | MI_MASK(ST_MAIL) | \
+ MI_MASK(ST_RCPT) | MI_MASK(ST_ABRT) | \
+ MI_MASK(ST_DATA) | \
+ MI_MASK(ST_BODY) | MI_MASK(ST_UNKN) | \
+ MI_MASK(ST_ABRT) | MI_MASK(ST_QUIT))
+#define NX_SKIP MI_MASK(ST_SKIP)
+
+static int next_states[] =
+{
+ NX_INIT,
+ NX_OPTS,
+ NX_CONN,
+ NX_HELO,
+ NX_MAIL,
+ NX_RCPT,
+ NX_DATA,
+ NX_HDRS,
+ NX_EOHS,
+ NX_BODY,
+ NX_ENDM,
+ NX_QUIT,
+ NX_ABRT,
+ NX_UNKN
+};
+
+/* commands received by milter */
+static cmdfct cmds[] =
+{
+{SMFIC_ABORT, CM_ARG0, ST_ABRT, CT_CONT, CI_NONE, st_abortfct },
+{SMFIC_MACRO, CM_ARGV, ST_NONE, CT_KEEP, CI_NONE, st_macros },
+{SMFIC_BODY, CM_ARG1, ST_BODY, CT_CONT, CI_NONE, st_bodychunk },
+{SMFIC_CONNECT, CM_ARG2, ST_CONN, CT_CONT, CI_CONN, st_connectinfo },
+{SMFIC_BODYEOB, CM_ARG1, ST_ENDM, CT_CONT, CI_EOM, st_bodyend },
+{SMFIC_HELO, CM_ARG1, ST_HELO, CT_CONT, CI_HELO, st_helo },
+{SMFIC_HEADER, CM_ARG2, ST_HDRS, CT_CONT, CI_NONE, st_header },
+{SMFIC_MAIL, CM_ARGV, ST_MAIL, CT_CONT, CI_MAIL, st_sender },
+{SMFIC_OPTNEG, CM_ARGO, ST_OPTS, CT_CONT, CI_NONE, st_optionneg },
+{SMFIC_EOH, CM_ARG0, ST_EOHS, CT_CONT, CI_NONE, st_eoh },
+{SMFIC_QUIT, CM_ARG0, ST_QUIT, CT_END, CI_NONE, st_quit },
+#if SMFI_VERSION > 3
+{SMFIC_DATA, CM_ARG0, ST_DATA, CT_CONT, CI_NONE, st_data },
+#endif /* SMFI_VERSION > 3 */
+{SMFIC_RCPT, CM_ARGV, ST_RCPT, CT_IGNO, CI_RCPT, st_rcpt }
+#if SMFI_VERSION > 2
+,{SMFIC_UNKNOWN,CM_ARG1, ST_UNKN, CT_IGNO, CI_NONE, st_unknown }
+#endif /* SMFI_VERSION > 2 */
+};
+
+/* additional (internal) reply codes */
+#define _SMFIS_KEEP 20
+#define _SMFIS_ABORT 21
+#define _SMFIS_OPTIONS 22
+#define _SMFIS_NOREPLY 23
+#define _SMFIS_FAIL (-1)
+#define _SMFIS_NONE (-2)
+
+/*
+** MI_ENGINE -- receive commands and process them
+**
+** Parameters:
+** ctx -- context structure
+**
+** Returns:
+** MI_FAILURE/MI_SUCCESS
+*/
+int
+mi_engine(ctx)
+ SMFICTX_PTR ctx;
+{
+ size_t len;
+ int i;
+ socket_t sd;
+ int ret = MI_SUCCESS;
+ int ncmds = sizeof(cmds) / sizeof(cmdfct);
+ int curstate = ST_INIT;
+ int newstate;
+ bool call_abort;
+ sfsistat r;
+ char cmd;
+ char *buf = NULL;
+ genarg arg;
+ struct timeval timeout;
+ int (*f) __P((genarg *));
+ sfsistat (*fi_abort) __P((SMFICTX *));
+ sfsistat (*fi_close) __P((SMFICTX *));
+
+ arg.a_ctx = ctx;
+ sd = ctx->ctx_sd;
+ fi_abort = ctx->ctx_smfi->xxfi_abort;
+ mi_clr_macros(ctx, 0);
+ fix_stm(ctx);
+ r = _SMFIS_NONE;
+ do
+ {
+ /* call abort only if in a mail transaction */
+ call_abort = ST_IN_MAIL(curstate);
+ timeout.tv_sec = ctx->ctx_timeout;
+ timeout.tv_usec = 0;
+ if (mi_stop() == MILTER_ABRT)
+ {
+ if (ctx->ctx_dbg > 3)
+ sm_dprintf("[%d] milter_abort\n",
+ (int) ctx->ctx_id);
+ ret = MI_FAILURE;
+ break;
+ }
+
+ /*
+ ** Notice: buf is allocated by mi_rd_cmd() and it will
+ ** usually be free()d after it has been used in f().
+ ** However, if the function returns _SMFIS_KEEP then buf
+ ** contains macros and will not be free()d.
+ ** Hence r must be set to _SMFIS_NONE if a new buf is
+ ** allocated to avoid problem with housekeeping, esp.
+ ** if the code "break"s out of the loop.
+ */
+
+ r = _SMFIS_NONE;
+ if ((buf = mi_rd_cmd(sd, &timeout, &cmd, &len,
+ ctx->ctx_smfi->xxfi_name)) == NULL &&
+ cmd < SMFIC_VALIDCMD)
+ {
+ if (ctx->ctx_dbg > 5)
+ sm_dprintf("[%d] mi_engine: mi_rd_cmd error (%x)\n",
+ (int) ctx->ctx_id, (int) cmd);
+
+ /*
+ ** eof is currently treated as failure ->
+ ** abort() instead of close(), otherwise use:
+ ** if (cmd != SMFIC_EOF)
+ */
+
+ ret = MI_FAILURE;
+ break;
+ }
+ if (ctx->ctx_dbg > 4)
+ sm_dprintf("[%d] got cmd '%c' len %d\n",
+ (int) ctx->ctx_id, cmd, (int) len);
+ for (i = 0; i < ncmds; i++)
+ {
+ if (cmd == cmds[i].cm_cmd)
+ break;
+ }
+ if (i >= ncmds)
+ {
+ /* unknown command */
+ if (ctx->ctx_dbg > 1)
+ sm_dprintf("[%d] cmd '%c' unknown\n",
+ (int) ctx->ctx_id, cmd);
+ ret = MI_FAILURE;
+ break;
+ }
+ if ((f = cmds[i].cm_fct) == NULL)
+ {
+ /* stop for now */
+ if (ctx->ctx_dbg > 1)
+ sm_dprintf("[%d] cmd '%c' not impl\n",
+ (int) ctx->ctx_id, cmd);
+ ret = MI_FAILURE;
+ break;
+ }
+
+ /* is new state ok? */
+ newstate = cmds[i].cm_next;
+ if (ctx->ctx_dbg > 5)
+ sm_dprintf("[%d] cur %x new %x nextmask %x\n",
+ (int) ctx->ctx_id,
+ curstate, newstate, next_states[curstate]);
+
+ if (newstate != ST_NONE && !trans_ok(curstate, newstate))
+ {
+ if (ctx->ctx_dbg > 1)
+ sm_dprintf("[%d] abort: cur %d (%x) new %d (%x) next %x\n",
+ (int) ctx->ctx_id,
+ curstate, MI_MASK(curstate),
+ newstate, MI_MASK(newstate),
+ next_states[curstate]);
+
+ /* call abort only if in a mail transaction */
+ if (fi_abort != NULL && call_abort)
+ (void) (*fi_abort)(ctx);
+
+ /*
+ ** try to reach the new state from HELO
+ ** if it can't be reached, ignore the command.
+ */
+
+ curstate = ST_HELO;
+ if (!trans_ok(curstate, newstate))
+ {
+ if (buf != NULL)
+ {
+ free(buf);
+ buf = NULL;
+ }
+ continue;
+ }
+ }
+ arg.a_len = len;
+ arg.a_buf = buf;
+ if (newstate != ST_NONE)
+ {
+ curstate = newstate;
+ ctx->ctx_state = curstate;
+ }
+ arg.a_idx = cmds[i].cm_macros;
+ call_abort = ST_IN_MAIL(curstate);
+
+ /* call function to deal with command */
+ r = (*f)(&arg);
+ if (r != _SMFIS_KEEP && buf != NULL)
+ {
+ free(buf);
+ buf = NULL;
+ }
+ if (sendreply(r, sd, &timeout, ctx) != MI_SUCCESS)
+ {
+ ret = MI_FAILURE;
+ break;
+ }
+
+ if (r == SMFIS_ACCEPT)
+ {
+ /* accept mail, no further actions taken */
+ curstate = ST_HELO;
+ }
+ else if (r == SMFIS_REJECT || r == SMFIS_DISCARD ||
+ r == SMFIS_TEMPFAIL)
+ {
+ /*
+ ** further actions depend on current state
+ ** if the IGNO bit is set: "ignore" the error,
+ ** i.e., stay in the current state
+ */
+ if (!bitset(CT_IGNO, cmds[i].cm_todo))
+ curstate = ST_HELO;
+ }
+ else if (r == _SMFIS_ABORT)
+ {
+ if (ctx->ctx_dbg > 5)
+ sm_dprintf("[%d] function returned abort\n",
+ (int) ctx->ctx_id);
+ ret = MI_FAILURE;
+ break;
+ }
+ } while (!bitset(CT_END, cmds[i].cm_todo));
+
+ if (ret != MI_SUCCESS)
+ {
+ /* call abort only if in a mail transaction */
+ if (fi_abort != NULL && call_abort)
+ (void) (*fi_abort)(ctx);
+ }
+
+ /* close must always be called */
+ if ((fi_close = ctx->ctx_smfi->xxfi_close) != NULL)
+ (void) (*fi_close)(ctx);
+ if (r != _SMFIS_KEEP && buf != NULL)
+ free(buf);
+ mi_clr_macros(ctx, 0);
+ return ret;
+}
+/*
+** SENDREPLY -- send a reply to the MTA
+**
+** Parameters:
+** r -- reply code
+** sd -- socket descriptor
+** timeout_ptr -- (ptr to) timeout to use for sending
+** ctx -- context structure
+**
+** Returns:
+** MI_SUCCESS/MI_FAILURE
+*/
+
+static int
+sendreply(r, sd, timeout_ptr, ctx)
+ sfsistat r;
+ socket_t sd;
+ struct timeval *timeout_ptr;
+ SMFICTX_PTR ctx;
+{
+ int ret = MI_SUCCESS;
+
+ switch (r)
+ {
+ case SMFIS_CONTINUE:
+ ret = mi_wr_cmd(sd, timeout_ptr, SMFIR_CONTINUE, NULL, 0);
+ break;
+ case SMFIS_TEMPFAIL:
+ case SMFIS_REJECT:
+ if (ctx->ctx_reply != NULL &&
+ ((r == SMFIS_TEMPFAIL && *ctx->ctx_reply == '4') ||
+ (r == SMFIS_REJECT && *ctx->ctx_reply == '5')))
+ {
+ ret = mi_wr_cmd(sd, timeout_ptr, SMFIR_REPLYCODE,
+ ctx->ctx_reply,
+ strlen(ctx->ctx_reply) + 1);
+ free(ctx->ctx_reply);
+ ctx->ctx_reply = NULL;
+ }
+ else
+ {
+ ret = mi_wr_cmd(sd, timeout_ptr, r == SMFIS_REJECT ?
+ SMFIR_REJECT : SMFIR_TEMPFAIL, NULL, 0);
+ }
+ break;
+ case SMFIS_DISCARD:
+ ret = mi_wr_cmd(sd, timeout_ptr, SMFIR_DISCARD, NULL, 0);
+ break;
+ case SMFIS_ACCEPT:
+ ret = mi_wr_cmd(sd, timeout_ptr, SMFIR_ACCEPT, NULL, 0);
+ break;
+ case _SMFIS_OPTIONS:
+ {
+ char buf[MILTER_OPTLEN];
+ mi_int32 v;
+
+ v = htonl(ctx->ctx_smfi->xxfi_version);
+ (void) memcpy(&(buf[0]), (void *) &v, MILTER_LEN_BYTES);
+ v = htonl(ctx->ctx_smfi->xxfi_flags);
+ (void) memcpy(&(buf[MILTER_LEN_BYTES]), (void *) &v,
+ MILTER_LEN_BYTES);
+ v = htonl(ctx->ctx_pflags);
+ (void) memcpy(&(buf[MILTER_LEN_BYTES * 2]), (void *) &v,
+ MILTER_LEN_BYTES);
+ ret = mi_wr_cmd(sd, timeout_ptr, SMFIC_OPTNEG, buf,
+ MILTER_OPTLEN);
+ }
+ break;
+ default: /* don't send a reply */
+ break;
+ }
+ return ret;
+}
+
+/*
+** CLR_MACROS -- clear set of macros starting from a given index
+**
+** Parameters:
+** ctx -- context structure
+** m -- index from which to clear all macros
+**
+** Returns:
+** None.
+*/
+void
+mi_clr_macros(ctx, m)
+ SMFICTX_PTR ctx;
+ int m;
+{
+ int i;
+
+ for (i = m; i < MAX_MACROS_ENTRIES; i++)
+ {
+ if (ctx->ctx_mac_ptr[i] != NULL)
+ {
+ free(ctx->ctx_mac_ptr[i]);
+ ctx->ctx_mac_ptr[i] = NULL;
+ }
+ if (ctx->ctx_mac_buf[i] != NULL)
+ {
+ free(ctx->ctx_mac_buf[i]);
+ ctx->ctx_mac_buf[i] = NULL;
+ }
+ }
+}
+/*
+** ST_OPTIONNEG -- negotiate options
+**
+** Parameters:
+** g -- generic argument structure
+**
+** Returns:
+** abort/send options/continue
+*/
+
+static int
+st_optionneg(g)
+ genarg *g;
+{
+ mi_int32 i, v;
+
+ if (g == NULL || g->a_ctx->ctx_smfi == NULL)
+ return SMFIS_CONTINUE;
+ mi_clr_macros(g->a_ctx, g->a_idx + 1);
+
+ /* check for minimum length */
+ if (g->a_len < MILTER_OPTLEN)
+ {
+ smi_log(SMI_LOG_ERR,
+ "%s: st_optionneg[%d]: len too short %d < %d",
+ g->a_ctx->ctx_smfi->xxfi_name,
+ (int) g->a_ctx->ctx_id, (int) g->a_len,
+ MILTER_OPTLEN);
+ return _SMFIS_ABORT;
+ }
+
+ (void) memcpy((void *) &i, (void *) &(g->a_buf[0]),
+ MILTER_LEN_BYTES);
+ v = ntohl(i);
+ if (v < g->a_ctx->ctx_smfi->xxfi_version)
+ {
+ /* hard failure for now! */
+ smi_log(SMI_LOG_ERR,
+ "%s: st_optionneg[%d]: version mismatch MTA: %d < milter: %d",
+ g->a_ctx->ctx_smfi->xxfi_name,
+ (int) g->a_ctx->ctx_id, (int) v,
+ g->a_ctx->ctx_smfi->xxfi_version);
+ return _SMFIS_ABORT;
+ }
+
+ (void) memcpy((void *) &i, (void *) &(g->a_buf[MILTER_LEN_BYTES]),
+ MILTER_LEN_BYTES);
+ v = ntohl(i);
+
+ /* no flags? set to default value for V1 actions */
+ if (v == 0)
+ v = SMFI_V1_ACTS;
+ i = g->a_ctx->ctx_smfi->xxfi_flags;
+ if ((v & i) != i)
+ {
+ smi_log(SMI_LOG_ERR,
+ "%s: st_optionneg[%d]: 0x%x does not fulfill action requirements 0x%x",
+ g->a_ctx->ctx_smfi->xxfi_name,
+ (int) g->a_ctx->ctx_id, v, i);
+ return _SMFIS_ABORT;
+ }
+
+ (void) memcpy((void *) &i, (void *) &(g->a_buf[MILTER_LEN_BYTES * 2]),
+ MILTER_LEN_BYTES);
+ v = ntohl(i);
+
+ /* no flags? set to default value for V1 protocol */
+ if (v == 0)
+ v = SMFI_V1_PROT;
+ i = g->a_ctx->ctx_pflags;
+ if ((v & i) != i)
+ {
+ smi_log(SMI_LOG_ERR,
+ "%s: st_optionneg[%d]: 0x%x does not fulfill protocol requirements 0x%x",
+ g->a_ctx->ctx_smfi->xxfi_name,
+ (int) g->a_ctx->ctx_id, v, i);
+ return _SMFIS_ABORT;
+ }
+
+ return _SMFIS_OPTIONS;
+}
+/*
+** ST_CONNECTINFO -- receive connection information
+**
+** Parameters:
+** g -- generic argument structure
+**
+** Returns:
+** continue or filter-specified value
+*/
+
+static int
+st_connectinfo(g)
+ genarg *g;
+{
+ size_t l;
+ size_t i;
+ char *s, family;
+ unsigned short port = 0;
+ _SOCK_ADDR sockaddr;
+ sfsistat (*fi_connect) __P((SMFICTX *, char *, _SOCK_ADDR *));
+
+ if (g == NULL)
+ return _SMFIS_ABORT;
+ mi_clr_macros(g->a_ctx, g->a_idx + 1);
+ if (g->a_ctx->ctx_smfi == NULL ||
+ (fi_connect = g->a_ctx->ctx_smfi->xxfi_connect) == NULL)
+ return SMFIS_CONTINUE;
+
+ s = g->a_buf;
+ i = 0;
+ l = g->a_len;
+ while (s[i] != '\0' && i <= l)
+ ++i;
+ if (i + 1 >= l)
+ return _SMFIS_ABORT;
+
+ /* Move past trailing \0 in host string */
+ i++;
+ family = s[i++];
+ (void) memset(&sockaddr, '\0', sizeof sockaddr);
+ if (family != SMFIA_UNKNOWN)
+ {
+ if (i + sizeof port >= l)
+ {
+ smi_log(SMI_LOG_ERR,
+ "%s: connect[%d]: wrong len %d >= %d",
+ g->a_ctx->ctx_smfi->xxfi_name,
+ (int) g->a_ctx->ctx_id, (int) i, (int) l);
+ return _SMFIS_ABORT;
+ }
+ (void) memcpy((void *) &port, (void *) (s + i),
+ sizeof port);
+ i += sizeof port;
+
+ /* make sure string is terminated */
+ if (s[l - 1] != '\0')
+ return _SMFIS_ABORT;
+# if NETINET
+ if (family == SMFIA_INET)
+ {
+ if (inet_aton(s + i, (struct in_addr *) &sockaddr.sin.sin_addr)
+ != 1)
+ {
+ smi_log(SMI_LOG_ERR,
+ "%s: connect[%d]: inet_aton failed",
+ g->a_ctx->ctx_smfi->xxfi_name,
+ (int) g->a_ctx->ctx_id);
+ return _SMFIS_ABORT;
+ }
+ sockaddr.sa.sa_family = AF_INET;
+ if (port > 0)
+ sockaddr.sin.sin_port = port;
+ }
+ else
+# endif /* NETINET */
+# if NETINET6
+ if (family == SMFIA_INET6)
+ {
+ if (mi_inet_pton(AF_INET6, s + i,
+ &sockaddr.sin6.sin6_addr) != 1)
+ {
+ smi_log(SMI_LOG_ERR,
+ "%s: connect[%d]: mi_inet_pton failed",
+ g->a_ctx->ctx_smfi->xxfi_name,
+ (int) g->a_ctx->ctx_id);
+ return _SMFIS_ABORT;
+ }
+ sockaddr.sa.sa_family = AF_INET6;
+ if (port > 0)
+ sockaddr.sin6.sin6_port = port;
+ }
+ else
+# endif /* NETINET6 */
+# if NETUNIX
+ if (family == SMFIA_UNIX)
+ {
+ if (sm_strlcpy(sockaddr.sunix.sun_path, s + i,
+ sizeof sockaddr.sunix.sun_path) >=
+ sizeof sockaddr.sunix.sun_path)
+ {
+ smi_log(SMI_LOG_ERR,
+ "%s: connect[%d]: path too long",
+ g->a_ctx->ctx_smfi->xxfi_name,
+ (int) g->a_ctx->ctx_id);
+ return _SMFIS_ABORT;
+ }
+ sockaddr.sunix.sun_family = AF_UNIX;
+ }
+ else
+# endif /* NETUNIX */
+ {
+ smi_log(SMI_LOG_ERR,
+ "%s: connect[%d]: unknown family %d",
+ g->a_ctx->ctx_smfi->xxfi_name,
+ (int) g->a_ctx->ctx_id, family);
+ return _SMFIS_ABORT;
+ }
+ }
+ return (*fi_connect)(g->a_ctx, g->a_buf,
+ family != SMFIA_UNKNOWN ? &sockaddr : NULL);
+}
+
+/*
+** ST_EOH -- end of headers
+**
+** Parameters:
+** g -- generic argument structure
+**
+** Returns:
+** continue or filter-specified value
+*/
+
+static int
+st_eoh(g)
+ genarg *g;
+{
+ sfsistat (*fi_eoh) __P((SMFICTX *));
+
+ if (g == NULL)
+ return _SMFIS_ABORT;
+ if (g->a_ctx->ctx_smfi != NULL &&
+ (fi_eoh = g->a_ctx->ctx_smfi->xxfi_eoh) != NULL)
+ return (*fi_eoh)(g->a_ctx);
+ return SMFIS_CONTINUE;
+}
+
+#if SMFI_VERSION > 3
+/*
+** ST_DATA -- DATA command
+**
+** Parameters:
+** g -- generic argument structure
+**
+** Returns:
+** continue or filter-specified value
+*/
+
+static int
+st_data(g)
+ genarg *g;
+{
+ sfsistat (*fi_data) __P((SMFICTX *));
+
+ if (g == NULL)
+ return _SMFIS_ABORT;
+ if (g->a_ctx->ctx_smfi != NULL &&
+ (fi_data = g->a_ctx->ctx_smfi->xxfi_data) != NULL)
+ return (*fi_data)(g->a_ctx);
+ return SMFIS_CONTINUE;
+}
+#endif /* SMFI_VERSION > 3 */
+
+/*
+** ST_HELO -- helo/ehlo command
+**
+** Parameters:
+** g -- generic argument structure
+**
+** Returns:
+** continue or filter-specified value
+*/
+static int
+st_helo(g)
+ genarg *g;
+{
+ sfsistat (*fi_helo) __P((SMFICTX *, char *));
+
+ if (g == NULL)
+ return _SMFIS_ABORT;
+ mi_clr_macros(g->a_ctx, g->a_idx + 1);
+ if (g->a_ctx->ctx_smfi != NULL &&
+ (fi_helo = g->a_ctx->ctx_smfi->xxfi_helo) != NULL)
+ {
+ /* paranoia: check for terminating '\0' */
+ if (g->a_len == 0 || g->a_buf[g->a_len - 1] != '\0')
+ return MI_FAILURE;
+ return (*fi_helo)(g->a_ctx, g->a_buf);
+ }
+ return SMFIS_CONTINUE;
+}
+/*
+** ST_HEADER -- header line
+**
+** Parameters:
+** g -- generic argument structure
+**
+** Returns:
+** continue or filter-specified value
+*/
+
+static int
+st_header(g)
+ genarg *g;
+{
+ char *hf, *hv;
+ sfsistat (*fi_header) __P((SMFICTX *, char *, char *));
+
+ if (g == NULL)
+ return _SMFIS_ABORT;
+ if (g->a_ctx->ctx_smfi == NULL ||
+ (fi_header = g->a_ctx->ctx_smfi->xxfi_header) == NULL)
+ return SMFIS_CONTINUE;
+ if (dec_arg2(g->a_buf, g->a_len, &hf, &hv) == MI_SUCCESS)
+ return (*fi_header)(g->a_ctx, hf, hv);
+ else
+ return _SMFIS_ABORT;
+}
+
+#define ARGV_FCT(lf, rf, idx) \
+ char **argv; \
+ sfsistat (*lf) __P((SMFICTX *, char **)); \
+ int r; \
+ \
+ if (g == NULL) \
+ return _SMFIS_ABORT; \
+ mi_clr_macros(g->a_ctx, g->a_idx + 1); \
+ if (g->a_ctx->ctx_smfi == NULL || \
+ (lf = g->a_ctx->ctx_smfi->rf) == NULL) \
+ return SMFIS_CONTINUE; \
+ if ((argv = dec_argv(g->a_buf, g->a_len)) == NULL) \
+ return _SMFIS_ABORT; \
+ r = (*lf)(g->a_ctx, argv); \
+ free(argv); \
+ return r;
+
+/*
+** ST_SENDER -- MAIL FROM command
+**
+** Parameters:
+** g -- generic argument structure
+**
+** Returns:
+** continue or filter-specified value
+*/
+
+static int
+st_sender(g)
+ genarg *g;
+{
+ ARGV_FCT(fi_envfrom, xxfi_envfrom, CI_MAIL)
+}
+/*
+** ST_RCPT -- RCPT TO command
+**
+** Parameters:
+** g -- generic argument structure
+**
+** Returns:
+** continue or filter-specified value
+*/
+
+static int
+st_rcpt(g)
+ genarg *g;
+{
+ ARGV_FCT(fi_envrcpt, xxfi_envrcpt, CI_RCPT)
+}
+
+#if SMFI_VERSION > 2
+/*
+** ST_UNKNOWN -- unrecognized or unimplemented command
+**
+** Parameters:
+** g -- generic argument structure
+**
+** Returns:
+** continue or filter-specified value
+*/
+
+static int
+st_unknown(g)
+ genarg *g;
+{
+ sfsistat (*fi_unknown) __P((SMFICTX *, char *));
+
+ if (g == NULL)
+ return _SMFIS_ABORT;
+ mi_clr_macros(g->a_ctx, g->a_idx + 1);
+ if (g->a_ctx->ctx_smfi != NULL &&
+ (fi_unknown = g->a_ctx->ctx_smfi->xxfi_unknown) != NULL)
+ return (*fi_unknown)(g->a_ctx, g->a_buf);
+ return SMFIS_CONTINUE;
+}
+#endif /* SMFI_VERSION > 2 */
+
+/*
+** ST_MACROS -- deal with macros received from the MTA
+**
+** Parameters:
+** g -- generic argument structure
+**
+** Returns:
+** continue/keep
+**
+** Side effects:
+** set pointer in macro array to current values.
+*/
+
+static int
+st_macros(g)
+ genarg *g;
+{
+ int i;
+ char **argv;
+
+ if (g == NULL || g->a_len < 1)
+ return _SMFIS_FAIL;
+ if ((argv = dec_argv(g->a_buf + 1, g->a_len - 1)) == NULL)
+ return _SMFIS_FAIL;
+ switch (g->a_buf[0])
+ {
+ case SMFIC_CONNECT:
+ i = CI_CONN;
+ break;
+ case SMFIC_HELO:
+ i = CI_HELO;
+ break;
+ case SMFIC_MAIL:
+ i = CI_MAIL;
+ break;
+ case SMFIC_RCPT:
+ i = CI_RCPT;
+ break;
+ case SMFIC_BODYEOB:
+ i = CI_EOM;
+ break;
+ default:
+ free(argv);
+ return _SMFIS_FAIL;
+ }
+ if (g->a_ctx->ctx_mac_ptr[i] != NULL)
+ free(g->a_ctx->ctx_mac_ptr[i]);
+ if (g->a_ctx->ctx_mac_buf[i] != NULL)
+ free(g->a_ctx->ctx_mac_buf[i]);
+ g->a_ctx->ctx_mac_ptr[i] = argv;
+ g->a_ctx->ctx_mac_buf[i] = g->a_buf;
+ return _SMFIS_KEEP;
+}
+/*
+** ST_QUIT -- quit command
+**
+** Parameters:
+** g -- generic argument structure
+**
+** Returns:
+** noreply
+*/
+
+/* ARGSUSED */
+static int
+st_quit(g)
+ genarg *g;
+{
+ return _SMFIS_NOREPLY;
+}
+/*
+** ST_BODYCHUNK -- deal with a piece of the mail body
+**
+** Parameters:
+** g -- generic argument structure
+**
+** Returns:
+** continue or filter-specified value
+*/
+
+static int
+st_bodychunk(g)
+ genarg *g;
+{
+ sfsistat (*fi_body) __P((SMFICTX *, unsigned char *, size_t));
+
+ if (g == NULL)
+ return _SMFIS_ABORT;
+ if (g->a_ctx->ctx_smfi != NULL &&
+ (fi_body = g->a_ctx->ctx_smfi->xxfi_body) != NULL)
+ return (*fi_body)(g->a_ctx, (unsigned char *)g->a_buf,
+ g->a_len);
+ return SMFIS_CONTINUE;
+}
+/*
+** ST_BODYEND -- deal with the last piece of the mail body
+**
+** Parameters:
+** g -- generic argument structure
+**
+** Returns:
+** continue or filter-specified value
+**
+** Side effects:
+** sends a reply for the body part (if non-empty).
+*/
+
+static int
+st_bodyend(g)
+ genarg *g;
+{
+ sfsistat r;
+ sfsistat (*fi_body) __P((SMFICTX *, unsigned char *, size_t));
+ sfsistat (*fi_eom) __P((SMFICTX *));
+
+ if (g == NULL)
+ return _SMFIS_ABORT;
+ r = SMFIS_CONTINUE;
+ if (g->a_ctx->ctx_smfi != NULL)
+ {
+ if ((fi_body = g->a_ctx->ctx_smfi->xxfi_body) != NULL &&
+ g->a_len > 0)
+ {
+ socket_t sd;
+ struct timeval timeout;
+
+ timeout.tv_sec = g->a_ctx->ctx_timeout;
+ timeout.tv_usec = 0;
+ sd = g->a_ctx->ctx_sd;
+ r = (*fi_body)(g->a_ctx, (unsigned char *)g->a_buf,
+ g->a_len);
+ if (r != SMFIS_CONTINUE &&
+ sendreply(r, sd, &timeout, g->a_ctx) != MI_SUCCESS)
+ return _SMFIS_ABORT;
+ }
+ }
+ if (r == SMFIS_CONTINUE &&
+ (fi_eom = g->a_ctx->ctx_smfi->xxfi_eom) != NULL)
+ return (*fi_eom)(g->a_ctx);
+ return r;
+}
+/*
+** ST_ABORTFCT -- deal with aborts
+**
+** Parameters:
+** g -- generic argument structure
+**
+** Returns:
+** abort or filter-specified value
+*/
+
+static int
+st_abortfct(g)
+ genarg *g;
+{
+ sfsistat (*fi_abort) __P((SMFICTX *));
+
+ if (g == NULL)
+ return _SMFIS_ABORT;
+ if (g != NULL && g->a_ctx->ctx_smfi != NULL &&
+ (fi_abort = g->a_ctx->ctx_smfi->xxfi_abort) != NULL)
+ (void) (*fi_abort)(g->a_ctx);
+ return _SMFIS_NOREPLY;
+}
+/*
+** TRANS_OK -- is the state transition ok?
+**
+** Parameters:
+** old -- old state
+** new -- new state
+**
+** Returns:
+** state transition ok
+*/
+
+static bool
+trans_ok(old, new)
+ int old, new;
+{
+ int s, n;
+
+ s = old;
+ do
+ {
+ /* is this state transition allowed? */
+ if ((MI_MASK(new) & next_states[s]) != 0)
+ return true;
+
+ /*
+ ** no: try next state;
+ ** this works since the relevant states are ordered
+ ** strict sequentially
+ */
+
+ n = s + 1;
+
+ /*
+ ** can we actually "skip" this state?
+ ** see fix_stm() which sets this bit for those
+ ** states which the filter program is not interested in
+ */
+
+ if (bitset(NX_SKIP, next_states[n]))
+ s = n;
+ else
+ return false;
+ } while (s <= ST_LAST);
+ return false;
+}
+/*
+** FIX_STM -- add "skip" bits to the state transition table
+**
+** Parameters:
+** ctx -- context structure
+**
+** Returns:
+** None.
+**
+** Side effects:
+** may change state transition table.
+*/
+
+static void
+fix_stm(ctx)
+ SMFICTX_PTR ctx;
+{
+ unsigned long fl;
+
+ if (ctx == NULL || ctx->ctx_smfi == NULL)
+ return;
+ fl = ctx->ctx_pflags;
+ if (bitset(SMFIP_NOCONNECT, fl))
+ next_states[ST_CONN] |= NX_SKIP;
+ if (bitset(SMFIP_NOHELO, fl))
+ next_states[ST_HELO] |= NX_SKIP;
+ if (bitset(SMFIP_NOMAIL, fl))
+ next_states[ST_MAIL] |= NX_SKIP;
+ if (bitset(SMFIP_NORCPT, fl))
+ next_states[ST_RCPT] |= NX_SKIP;
+ if (bitset(SMFIP_NOHDRS, fl))
+ next_states[ST_HDRS] |= NX_SKIP;
+ if (bitset(SMFIP_NOEOH, fl))
+ next_states[ST_EOHS] |= NX_SKIP;
+ if (bitset(SMFIP_NOBODY, fl))
+ next_states[ST_BODY] |= NX_SKIP;
+}
+/*
+** DEC_ARGV -- split a buffer into a list of strings, NULL terminated
+**
+** Parameters:
+** buf -- buffer with several strings
+** len -- length of buffer
+**
+** Returns:
+** array of pointers to the individual strings
+*/
+
+static char **
+dec_argv(buf, len)
+ char *buf;
+ size_t len;
+{
+ char **s;
+ size_t i;
+ int elem, nelem;
+
+ nelem = 0;
+ for (i = 0; i < len; i++)
+ {
+ if (buf[i] == '\0')
+ ++nelem;
+ }
+ if (nelem == 0)
+ return NULL;
+
+ /* last entry is only for the name */
+ s = (char **)malloc((nelem + 1) * (sizeof *s));
+ if (s == NULL)
+ return NULL;
+ s[0] = buf;
+ for (i = 0, elem = 0; i < len && elem < nelem; i++)
+ {
+ if (buf[i] == '\0')
+ {
+ ++elem;
+ if (i + 1 >= len)
+ s[elem] = NULL;
+ else
+ s[elem] = &(buf[i + 1]);
+ }
+ }
+
+ /* overwrite last entry (already done above, just paranoia) */
+ s[elem] = NULL;
+ return s;
+}
+/*
+** DEC_ARG2 -- split a buffer into two strings
+**
+** Parameters:
+** buf -- buffer with two strings
+** len -- length of buffer
+** s1,s2 -- pointer to result strings
+**
+** Returns:
+** MI_FAILURE/MI_SUCCESS
+*/
+
+static int
+dec_arg2(buf, len, s1, s2)
+ char *buf;
+ size_t len;
+ char **s1;
+ char **s2;
+{
+ size_t i;
+
+ /* paranoia: check for terminating '\0' */
+ if (len == 0 || buf[len - 1] != '\0')
+ return MI_FAILURE;
+ *s1 = buf;
+ for (i = 1; i < len && buf[i] != '\0'; i++)
+ continue;
+ if (i >= len - 1)
+ return MI_FAILURE;
+ *s2 = buf + i + 1;
+ return MI_SUCCESS;
+}
+/*
+** SENDOK -- is it ok for the filter to send stuff to the MTA?
+**
+** Parameters:
+** ctx -- context structure
+** flag -- flag to check
+**
+** Returns:
+** sending allowed (in current state)
+*/
+
+bool
+mi_sendok(ctx, flag)
+ SMFICTX_PTR ctx;
+ int flag;
+{
+ if (ctx == NULL || ctx->ctx_smfi == NULL)
+ return false;
+
+ /* did the milter request this operation? */
+ if (flag != 0 && !bitset(flag, ctx->ctx_smfi->xxfi_flags))
+ return false;
+
+ /* are we in the correct state? It must be "End of Message". */
+ return ctx->ctx_state == ST_ENDM;
+}
diff --git a/usr/src/cmd/sendmail/libmilter/handler.c b/usr/src/cmd/sendmail/libmilter/handler.c
new file mode 100644
index 0000000000..4823b378b2
--- /dev/null
+++ b/usr/src/cmd/sendmail/libmilter/handler.c
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 1999-2003 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: handler.c,v 8.30.2.4 2003/01/23 22:28:36 ca Exp $")
+
+#include "libmilter.h"
+
+
+/*
+** HANDLE_SESSION -- Handle a connected session in its own context
+**
+** Parameters:
+** ctx -- context structure
+**
+** Returns:
+** MI_SUCCESS/MI_FAILURE
+*/
+
+int
+mi_handle_session(ctx)
+ SMFICTX_PTR ctx;
+{
+ int ret;
+
+ if (ctx == NULL)
+ return MI_FAILURE;
+ ctx->ctx_id = (sthread_t) sthread_get_id();
+
+ /*
+ ** Detach so resources are free when the thread returns.
+ ** If we ever "wait" for threads, this call must be removed.
+ */
+
+ if (pthread_detach(ctx->ctx_id) != 0)
+ ret = MI_FAILURE;
+ else
+ ret = mi_engine(ctx);
+ if (ValidSocket(ctx->ctx_sd))
+ {
+ (void) closesocket(ctx->ctx_sd);
+ ctx->ctx_sd = INVALID_SOCKET;
+ }
+ if (ctx->ctx_reply != NULL)
+ {
+ free(ctx->ctx_reply);
+ ctx->ctx_reply = NULL;
+ }
+ if (ctx->ctx_privdata != NULL)
+ {
+ smi_log(SMI_LOG_WARN,
+ "%s: private data not NULL",
+ ctx->ctx_smfi->xxfi_name);
+ }
+ mi_clr_macros(ctx, 0);
+ free(ctx);
+ ctx = NULL;
+ return ret;
+}
diff --git a/usr/src/cmd/sendmail/libmilter/i386/Makefile b/usr/src/cmd/sendmail/libmilter/i386/Makefile
new file mode 100644
index 0000000000..e86ae6a9f7
--- /dev/null
+++ b/usr/src/cmd/sendmail/libmilter/i386/Makefile
@@ -0,0 +1,29 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License, Version 1.0 only
+# (the "License"). You may not use this file except in compliance
+# with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+# Copyright 2004 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# ident "%Z%%M% %I% %E% SMI"
+
+include ../Makefile.com
+
+install: all $(ROOTLIBS) $(ROOTLINKS) $(ROOTLINT)
diff --git a/usr/src/cmd/sendmail/libmilter/libmilter.h b/usr/src/cmd/sendmail/libmilter/libmilter.h
new file mode 100644
index 0000000000..da8a72ba5d
--- /dev/null
+++ b/usr/src/cmd/sendmail/libmilter/libmilter.h
@@ -0,0 +1,189 @@
+/*
+ * Copyright (c) 1999-2003 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+/*
+** LIBMILTER.H -- include file for mail filter library functions
+*/
+
+#ifndef _LIBMILTER_H
+# define _LIBMILTER_H 1
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+
+#ifdef _DEFINE
+# define EXTERN
+# define INIT(x) = x
+SM_IDSTR(MilterlId, "@(#)$Id: libmilter.h,v 8.50 2003/12/11 18:14:34 ca Exp $")
+#else /* _DEFINE */
+# define EXTERN extern
+# define INIT(x)
+#endif /* _DEFINE */
+
+
+#define NOT_SENDMAIL 1
+#define _SOCK_ADDR union bigsockaddr
+#include "sendmail.h"
+
+#include "libmilter/milter.h"
+
+# define ValidSocket(sd) ((sd) >= 0)
+# define INVALID_SOCKET (-1)
+# define closesocket close
+# define MI_SOCK_READ(s, b, l) read(s, b, l)
+# define MI_SOCK_READ_FAIL(x) ((x) < 0)
+# define MI_SOCK_WRITE(s, b, l) write(s, b, l)
+
+# define thread_create(ptid,wr,arg) pthread_create(ptid, NULL, wr, arg)
+# define sthread_get_id() pthread_self()
+
+typedef pthread_mutex_t smutex_t;
+# define smutex_init(mp) (pthread_mutex_init(mp, NULL) == 0)
+# define smutex_destroy(mp) (pthread_mutex_destroy(mp) == 0)
+# define smutex_lock(mp) (pthread_mutex_lock(mp) == 0)
+# define smutex_unlock(mp) (pthread_mutex_unlock(mp) == 0)
+# define smutex_trylock(mp) (pthread_mutex_trylock(mp) == 0)
+
+#if SM_CONF_POLL
+
+# include <poll.h>
+# define MI_POLLSELECT "poll"
+
+# define MI_POLL_RD_FLAGS (POLLIN | POLLPRI)
+# define MI_POLL_WR_FLAGS (POLLOUT)
+# define MI_MS(timeout) (((timeout)->tv_sec * 1000) + (timeout)->tv_usec)
+
+# define FD_RD_VAR(rds, excs) struct pollfd rds
+# define FD_WR_VAR(wrs) struct pollfd wrs
+
+# define FD_RD_INIT(sd, rds, excs) \
+ (rds).fd = (sd); \
+ (rds).events = MI_POLL_RD_FLAGS; \
+ (rds).revents = 0
+
+# define FD_WR_INIT(sd, wrs) \
+ (wrs).fd = (sd); \
+ (wrs).events = MI_POLL_WR_FLAGS; \
+ (wrs).revents = 0
+
+# define FD_IS_RD_EXC(sd, rds, excs) \
+ (((rds).revents & (POLLERR | POLLHUP | POLLNVAL)) != 0)
+
+# define FD_IS_WR_RDY(sd, wrs) \
+ (((wrs).revents & MI_POLL_WR_FLAGS) != 0)
+
+# define FD_IS_RD_RDY(sd, rds, excs) \
+ (((rds).revents & MI_POLL_RD_FLAGS) != 0)
+
+# define FD_WR_READY(sd, excs, timeout) \
+ poll(&(wrs), 1, MI_MS(timeout))
+
+# define FD_RD_READY(sd, rds, excs, timeout) \
+ poll(&(rds), 1, MI_MS(timeout))
+
+#else /* SM_CONF_POLL */
+
+# include <sm/fdset.h>
+# define MI_POLLSELECT "select"
+
+# define FD_RD_VAR(rds, excs) fd_set rds, excs
+# define FD_WR_VAR(wrs) fd_set wrs
+
+# define FD_RD_INIT(sd, rds, excs) \
+ FD_ZERO(&(rds)); \
+ FD_SET((unsigned int) (sd), &(rds)); \
+ FD_ZERO(&(excs)); \
+ FD_SET((unsigned int) (sd), &(excs))
+
+# define FD_WR_INIT(sd, wrs) \
+ FD_ZERO(&(wrs)); \
+ FD_SET((unsigned int) (sd), &(wrs)); \
+
+# define FD_IS_RD_EXC(sd, rds, excs) FD_ISSET(sd, &(excs))
+# define FD_IS_WR_RDY(sd, wrs) FD_ISSET((sd), &(wrs))
+# define FD_IS_RD_RDY(sd, rds, excs) FD_ISSET((sd), &(rds))
+
+# define FD_WR_READY(sd, wrs, timeout) \
+ select((sd) + 1, NULL, &(wrs), NULL, (timeout))
+# define FD_RD_READY(sd, rds, excs, timeout) \
+ select((sd) + 1, &(rds), NULL, &(excs), (timeout))
+
+#endif /* SM_CONF_POLL */
+
+#include <sys/time.h>
+
+/* version info */
+#define MILTER_PRODUCT_NAME "libmilter"
+#define MILTER_VERSION 100
+
+/* some defaults */
+#define MI_TIMEOUT 7210 /* default timeout for read/write */
+#define MI_CHK_TIME 5 /* checking whether to terminate */
+
+#ifndef MI_SOMAXCONN
+# if SOMAXCONN > 20
+# define MI_SOMAXCONN SOMAXCONN
+# else /* SOMAXCONN */
+# define MI_SOMAXCONN 20
+# endif /* SOMAXCONN */
+#endif /* ! MI_SOMAXCONN */
+
+/* maximum number of repeated failures in mi_listener() */
+#define MAX_FAILS_M 16 /* malloc() */
+#define MAX_FAILS_T 16 /* thread creation */
+#define MAX_FAILS_A 16 /* accept() */
+#define MAX_FAILS_S 16 /* select() */
+
+/* internal "commands", i.e., error codes */
+#define SMFIC_TIMEOUT ((char) 1) /* timeout */
+#define SMFIC_SELECT ((char) 2) /* select error */
+#define SMFIC_MALLOC ((char) 3) /* malloc error */
+#define SMFIC_RECVERR ((char) 4) /* recv() error */
+#define SMFIC_EOF ((char) 5) /* eof */
+#define SMFIC_UNKNERR ((char) 6) /* unknown error */
+#define SMFIC_TOOBIG ((char) 7) /* body chunk too big */
+#define SMFIC_VALIDCMD ' ' /* first valid command */
+
+/* hack */
+#define smi_log syslog
+#define sm_dprintf (void) printf
+#define milter_ret int
+#define SMI_LOG_ERR LOG_ERR
+#define SMI_LOG_FATAL LOG_ERR
+#define SMI_LOG_WARN LOG_WARNING
+#define SMI_LOG_INFO LOG_INFO
+#define SMI_LOG_DEBUG LOG_DEBUG
+
+/* stop? */
+#define MILTER_CONT 0
+#define MILTER_STOP 1
+#define MILTER_ABRT 2
+
+/* functions */
+extern int mi_handle_session __P((SMFICTX_PTR));
+extern int mi_engine __P((SMFICTX_PTR));
+extern int mi_listener __P((char *, int, smfiDesc_ptr, time_t, int));
+extern void mi_clr_macros __P((SMFICTX_PTR, int));
+extern int mi_stop __P((void));
+extern int mi_control_startup __P((char *));
+extern void mi_stop_milters __P((int));
+extern void mi_clean_signals __P((void));
+extern struct hostent *mi_gethostbyname __P((char *, int));
+extern int mi_inet_pton __P((int, const char *, void *));
+extern void mi_closener __P((void));
+extern int mi_opensocket __P((char *, int, int, bool, smfiDesc_ptr));
+
+/* communication functions */
+extern char *mi_rd_cmd __P((socket_t, struct timeval *, char *, size_t *, char *));
+extern int mi_wr_cmd __P((socket_t, struct timeval *, int, char *, size_t));
+extern bool mi_sendok __P((SMFICTX_PTR, int));
+
+
+#endif /* ! _LIBMILTER_H */
diff --git a/usr/src/cmd/sendmail/libmilter/listener.c b/usr/src/cmd/sendmail/libmilter/listener.c
new file mode 100644
index 0000000000..1a30004ac2
--- /dev/null
+++ b/usr/src/cmd/sendmail/libmilter/listener.c
@@ -0,0 +1,955 @@
+/*
+ * Copyright (c) 1999-2004 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: listener.c,v 8.111 2004/09/20 21:11:15 msk Exp $")
+
+/*
+** listener.c -- threaded network listener
+*/
+
+#include "libmilter.h"
+#include <sm/errstring.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+
+# if NETINET || NETINET6
+# include <arpa/inet.h>
+# endif /* NETINET || NETINET6 */
+
+static smutex_t L_Mutex;
+static int L_family;
+static SOCKADDR_LEN_T L_socksize;
+static socket_t listenfd = INVALID_SOCKET;
+
+static socket_t mi_milteropen __P((char *, int, bool, char *));
+static void *mi_thread_handle_wrapper __P((void *));
+
+/*
+** MI_OPENSOCKET -- create the socket where this filter and the MTA will meet
+**
+** Parameters:
+** conn -- connection description
+** backlog -- listen backlog
+** dbg -- debug level
+** rmsocket -- if true, try to unlink() the socket first
+** (UNIX domain sockets only)
+** smfi -- filter structure to use
+**
+** Return value:
+** MI_SUCCESS/MI_FAILURE
+*/
+
+int
+mi_opensocket(conn, backlog, dbg, rmsocket, smfi)
+ char *conn;
+ int backlog;
+ int dbg;
+ bool rmsocket;
+ smfiDesc_ptr smfi;
+{
+ if (smfi == NULL || conn == NULL)
+ return MI_FAILURE;
+
+ if (ValidSocket(listenfd))
+ return MI_SUCCESS;
+
+ if (dbg > 0)
+ {
+ smi_log(SMI_LOG_DEBUG,
+ "%s: Opening listen socket on conn %s",
+ smfi->xxfi_name, conn);
+ }
+ (void) smutex_init(&L_Mutex);
+ (void) smutex_lock(&L_Mutex);
+ listenfd = mi_milteropen(conn, backlog, rmsocket, smfi->xxfi_name);
+ if (!ValidSocket(listenfd))
+ {
+ smi_log(SMI_LOG_FATAL,
+ "%s: Unable to create listening socket on conn %s",
+ smfi->xxfi_name, conn);
+ (void) smutex_unlock(&L_Mutex);
+ return MI_FAILURE;
+ }
+#if !SM_CONF_POLL
+ if (!SM_FD_OK_SELECT(listenfd))
+ {
+ smi_log(SMI_LOG_ERR, "%s: fd %d is larger than FD_SETSIZE %d",
+ smfi->xxfi_name, listenfd, FD_SETSIZE);
+ (void) smutex_unlock(&L_Mutex);
+ return MI_FAILURE;
+ }
+#endif /* !SM_CONF_POLL */
+ (void) smutex_unlock(&L_Mutex);
+ return MI_SUCCESS;
+}
+
+/*
+** MI_MILTEROPEN -- setup socket to listen on
+**
+** Parameters:
+** conn -- connection description
+** backlog -- listen backlog
+** rmsocket -- if true, try to unlink() the socket first
+** (UNIX domain sockets only)
+** name -- name for logging
+**
+** Returns:
+** socket upon success, error code otherwise.
+**
+** Side effect:
+** sets sockpath if UNIX socket.
+*/
+
+#if NETUNIX
+static char *sockpath = NULL;
+#endif /* NETUNIX */
+
+static socket_t
+mi_milteropen(conn, backlog, rmsocket, name)
+ char *conn;
+ int backlog;
+ bool rmsocket;
+ char *name;
+{
+ socket_t sock;
+ int sockopt = 1;
+ int fdflags;
+ size_t len = 0;
+ char *p;
+ char *colon;
+ char *at;
+ SOCKADDR addr;
+
+ if (conn == NULL || conn[0] == '\0')
+ {
+ smi_log(SMI_LOG_ERR, "%s: empty or missing socket information",
+ name);
+ return INVALID_SOCKET;
+ }
+ (void) memset(&addr, '\0', sizeof addr);
+
+ /* protocol:filename or protocol:port@host */
+ p = conn;
+ colon = strchr(p, ':');
+ if (colon != NULL)
+ {
+ *colon = '\0';
+
+ if (*p == '\0')
+ {
+#if NETUNIX
+ /* default to AF_UNIX */
+ addr.sa.sa_family = AF_UNIX;
+ L_socksize = sizeof (struct sockaddr_un);
+#else /* NETUNIX */
+# if NETINET
+ /* default to AF_INET */
+ addr.sa.sa_family = AF_INET;
+ L_socksize = sizeof addr.sin;
+# else /* NETINET */
+# if NETINET6
+ /* default to AF_INET6 */
+ addr.sa.sa_family = AF_INET6;
+ L_socksize = sizeof addr.sin6;
+# else /* NETINET6 */
+ /* no protocols available */
+ smi_log(SMI_LOG_ERR,
+ "%s: no valid socket protocols available",
+ name);
+ return INVALID_SOCKET;
+# endif /* NETINET6 */
+# endif /* NETINET */
+#endif /* NETUNIX */
+ }
+#if NETUNIX
+ else if (strcasecmp(p, "unix") == 0 ||
+ strcasecmp(p, "local") == 0)
+ {
+ addr.sa.sa_family = AF_UNIX;
+ L_socksize = sizeof (struct sockaddr_un);
+ }
+#endif /* NETUNIX */
+#if NETINET
+ else if (strcasecmp(p, "inet") == 0)
+ {
+ addr.sa.sa_family = AF_INET;
+ L_socksize = sizeof addr.sin;
+ }
+#endif /* NETINET */
+#if NETINET6
+ else if (strcasecmp(p, "inet6") == 0)
+ {
+ addr.sa.sa_family = AF_INET6;
+ L_socksize = sizeof addr.sin6;
+ }
+#endif /* NETINET6 */
+ else
+ {
+ smi_log(SMI_LOG_ERR, "%s: unknown socket type %s",
+ name, p);
+ return INVALID_SOCKET;
+ }
+ *colon++ = ':';
+ }
+ else
+ {
+ colon = p;
+#if NETUNIX
+ /* default to AF_UNIX */
+ addr.sa.sa_family = AF_UNIX;
+ L_socksize = sizeof (struct sockaddr_un);
+#else /* NETUNIX */
+# if NETINET
+ /* default to AF_INET */
+ addr.sa.sa_family = AF_INET;
+ L_socksize = sizeof addr.sin;
+# else /* NETINET */
+# if NETINET6
+ /* default to AF_INET6 */
+ addr.sa.sa_family = AF_INET6;
+ L_socksize = sizeof addr.sin6;
+# else /* NETINET6 */
+ smi_log(SMI_LOG_ERR, "%s: unknown socket type %s",
+ name, p);
+ return INVALID_SOCKET;
+# endif /* NETINET6 */
+# endif /* NETINET */
+#endif /* NETUNIX */
+ }
+
+#if NETUNIX
+ if (addr.sa.sa_family == AF_UNIX)
+ {
+# if 0
+ long sff = SFF_SAFEDIRPATH|SFF_OPENASROOT|SFF_NOLINK|SFF_CREAT|SFF_MUSTOWN;
+# endif /* 0 */
+
+ at = colon;
+ len = strlen(colon) + 1;
+ if (len >= sizeof addr.sunix.sun_path)
+ {
+ errno = EINVAL;
+ smi_log(SMI_LOG_ERR, "%s: UNIX socket name %s too long",
+ name, colon);
+ return INVALID_SOCKET;
+ }
+ (void) sm_strlcpy(addr.sunix.sun_path, colon,
+ sizeof addr.sunix.sun_path);
+# if 0
+ errno = safefile(colon, RunAsUid, RunAsGid, RunAsUserName, sff,
+ S_IRUSR|S_IWUSR, NULL);
+
+ /* if not safe, don't create */
+ if (errno != 0)
+ {
+ smi_log(SMI_LOG_ERR,
+ "%s: UNIX socket name %s unsafe",
+ name, colon);
+ return INVALID_SOCKET;
+ }
+# endif /* 0 */
+ }
+#endif /* NETUNIX */
+
+#if NETINET || NETINET6
+ if (
+# if NETINET
+ addr.sa.sa_family == AF_INET
+# endif /* NETINET */
+# if NETINET && NETINET6
+ ||
+# endif /* NETINET && NETINET6 */
+# if NETINET6
+ addr.sa.sa_family == AF_INET6
+# endif /* NETINET6 */
+ )
+ {
+ unsigned short port;
+
+ /* Parse port@host */
+ at = strchr(colon, '@');
+ if (at == NULL)
+ {
+ switch (addr.sa.sa_family)
+ {
+# if NETINET
+ case AF_INET:
+ addr.sin.sin_addr.s_addr = INADDR_ANY;
+ break;
+# endif /* NETINET */
+
+# if NETINET6
+ case AF_INET6:
+ addr.sin6.sin6_addr = in6addr_any;
+ break;
+# endif /* NETINET6 */
+ }
+ }
+ else
+ *at = '\0';
+
+ if (isascii(*colon) && isdigit(*colon))
+ port = htons((unsigned short) atoi(colon));
+ else
+ {
+# ifdef NO_GETSERVBYNAME
+ smi_log(SMI_LOG_ERR, "%s: invalid port number %s",
+ name, colon);
+ return INVALID_SOCKET;
+# else /* NO_GETSERVBYNAME */
+ register struct servent *sp;
+
+ sp = getservbyname(colon, "tcp");
+ if (sp == NULL)
+ {
+ smi_log(SMI_LOG_ERR,
+ "%s: unknown port name %s",
+ name, colon);
+ return INVALID_SOCKET;
+ }
+ port = sp->s_port;
+# endif /* NO_GETSERVBYNAME */
+ }
+ if (at != NULL)
+ {
+ *at++ = '@';
+ if (*at == '[')
+ {
+ char *end;
+
+ end = strchr(at, ']');
+ if (end != NULL)
+ {
+ bool found = false;
+# if NETINET
+ unsigned long hid = INADDR_NONE;
+# endif /* NETINET */
+# if NETINET6
+ struct sockaddr_in6 hid6;
+# endif /* NETINET6 */
+
+ *end = '\0';
+# if NETINET
+ if (addr.sa.sa_family == AF_INET &&
+ (hid = inet_addr(&at[1])) != INADDR_NONE)
+ {
+ addr.sin.sin_addr.s_addr = hid;
+ addr.sin.sin_port = port;
+ found = true;
+ }
+# endif /* NETINET */
+# if NETINET6
+ (void) memset(&hid6, '\0', sizeof hid6);
+ if (addr.sa.sa_family == AF_INET6 &&
+ mi_inet_pton(AF_INET6, &at[1],
+ &hid6.sin6_addr) == 1)
+ {
+ addr.sin6.sin6_addr = hid6.sin6_addr;
+ addr.sin6.sin6_port = port;
+ found = true;
+ }
+# endif /* NETINET6 */
+ *end = ']';
+ if (!found)
+ {
+ smi_log(SMI_LOG_ERR,
+ "%s: Invalid numeric domain spec \"%s\"",
+ name, at);
+ return INVALID_SOCKET;
+ }
+ }
+ else
+ {
+ smi_log(SMI_LOG_ERR,
+ "%s: Invalid numeric domain spec \"%s\"",
+ name, at);
+ return INVALID_SOCKET;
+ }
+ }
+ else
+ {
+ struct hostent *hp = NULL;
+
+ hp = mi_gethostbyname(at, addr.sa.sa_family);
+ if (hp == NULL)
+ {
+ smi_log(SMI_LOG_ERR,
+ "%s: Unknown host name %s",
+ name, at);
+ return INVALID_SOCKET;
+ }
+ addr.sa.sa_family = hp->h_addrtype;
+ switch (hp->h_addrtype)
+ {
+# if NETINET
+ case AF_INET:
+ (void) memmove(&addr.sin.sin_addr,
+ hp->h_addr,
+ INADDRSZ);
+ addr.sin.sin_port = port;
+ break;
+# endif /* NETINET */
+
+# if NETINET6
+ case AF_INET6:
+ (void) memmove(&addr.sin6.sin6_addr,
+ hp->h_addr,
+ IN6ADDRSZ);
+ addr.sin6.sin6_port = port;
+ break;
+# endif /* NETINET6 */
+
+ default:
+ smi_log(SMI_LOG_ERR,
+ "%s: Unknown protocol for %s (%d)",
+ name, at, hp->h_addrtype);
+ return INVALID_SOCKET;
+ }
+# if NETINET6
+ freehostent(hp);
+# endif /* NETINET6 */
+ }
+ }
+ else
+ {
+ switch (addr.sa.sa_family)
+ {
+# if NETINET
+ case AF_INET:
+ addr.sin.sin_port = port;
+ break;
+# endif /* NETINET */
+# if NETINET6
+ case AF_INET6:
+ addr.sin6.sin6_port = port;
+ break;
+# endif /* NETINET6 */
+ }
+ }
+ }
+#endif /* NETINET || NETINET6 */
+
+ sock = socket(addr.sa.sa_family, SOCK_STREAM, 0);
+ if (!ValidSocket(sock))
+ {
+ smi_log(SMI_LOG_ERR,
+ "%s: Unable to create new socket: %s",
+ name, sm_errstring(errno));
+ return INVALID_SOCKET;
+ }
+
+ if ((fdflags = fcntl(sock, F_GETFD, 0)) == -1 ||
+ fcntl(sock, F_SETFD, fdflags | FD_CLOEXEC) == -1)
+ {
+ smi_log(SMI_LOG_ERR,
+ "%s: Unable to set close-on-exec: %s", name,
+ sm_errstring(errno));
+ (void) closesocket(sock);
+ return INVALID_SOCKET;
+ }
+
+ if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *) &sockopt,
+ sizeof(sockopt)) == -1)
+ {
+ smi_log(SMI_LOG_ERR,
+ "%s: Unable to setsockopt: %s", name,
+ sm_errstring(errno));
+ (void) closesocket(sock);
+ return INVALID_SOCKET;
+ }
+
+#if NETUNIX
+ if (addr.sa.sa_family == AF_UNIX && rmsocket)
+ {
+ struct stat s;
+
+ if (stat(colon, &s) != 0)
+ {
+ if (errno != ENOENT)
+ {
+ smi_log(SMI_LOG_ERR,
+ "%s: Unable to stat() %s: %s",
+ name, colon, sm_errstring(errno));
+ (void) closesocket(sock);
+ return INVALID_SOCKET;
+ }
+ }
+ else if (!S_ISSOCK(s.st_mode))
+ {
+ smi_log(SMI_LOG_ERR,
+ "%s: %s is not a UNIX domain socket",
+ name, colon);
+ (void) closesocket(sock);
+ return INVALID_SOCKET;
+ }
+ else if (unlink(colon) != 0)
+ {
+ smi_log(SMI_LOG_ERR,
+ "%s: Unable to remove %s: %s",
+ name, colon, sm_errstring(errno));
+ (void) closesocket(sock);
+ return INVALID_SOCKET;
+ }
+ }
+#endif /* NETUNIX */
+
+ if (bind(sock, &addr.sa, L_socksize) < 0)
+ {
+ smi_log(SMI_LOG_ERR,
+ "%s: Unable to bind to port %s: %s",
+ name, conn, sm_errstring(errno));
+ (void) closesocket(sock);
+ return INVALID_SOCKET;
+ }
+
+ if (listen(sock, backlog) < 0)
+ {
+ smi_log(SMI_LOG_ERR,
+ "%s: listen call failed: %s", name,
+ sm_errstring(errno));
+ (void) closesocket(sock);
+ return INVALID_SOCKET;
+ }
+
+#if NETUNIX
+ if (addr.sa.sa_family == AF_UNIX && len > 0)
+ {
+ /*
+ ** Set global variable sockpath so the UNIX socket can be
+ ** unlink()ed at exit.
+ */
+
+ sockpath = (char *) malloc(len);
+ if (sockpath != NULL)
+ (void) sm_strlcpy(sockpath, colon, len);
+ else
+ {
+ smi_log(SMI_LOG_ERR,
+ "%s: can't malloc(%d) for sockpath: %s",
+ name, (int) len, sm_errstring(errno));
+ (void) closesocket(sock);
+ return INVALID_SOCKET;
+ }
+ }
+#endif /* NETUNIX */
+ L_family = addr.sa.sa_family;
+ return sock;
+}
+/*
+** MI_THREAD_HANDLE_WRAPPER -- small wrapper to handle session
+**
+** Parameters:
+** arg -- argument to pass to mi_handle_session()
+**
+** Returns:
+** results from mi_handle_session()
+*/
+
+static void *
+mi_thread_handle_wrapper(arg)
+ void *arg;
+{
+ return (void *) mi_handle_session(arg);
+}
+
+/*
+** MI_CLOSENER -- close listen socket
+**
+** NOTE: It is assumed that this function is called from a
+** function that has a mutex lock (currently mi_stop_milters()).
+**
+** Parameters:
+** none.
+**
+** Returns:
+** none.
+*/
+
+void
+mi_closener()
+{
+ (void) smutex_lock(&L_Mutex);
+ if (ValidSocket(listenfd))
+ {
+#if NETUNIX
+ bool removable;
+ struct stat sockinfo;
+ struct stat fileinfo;
+
+ removable = sockpath != NULL &&
+ geteuid() != 0 &&
+ fstat(listenfd, &sockinfo) == 0 &&
+ (S_ISFIFO(sockinfo.st_mode)
+# ifdef S_ISSOCK
+ || S_ISSOCK(sockinfo.st_mode)
+# endif /* S_ISSOCK */
+ );
+#endif /* NETUNIX */
+
+ (void) closesocket(listenfd);
+ listenfd = INVALID_SOCKET;
+
+#if NETUNIX
+ /* XXX sleep() some time before doing this? */
+ if (sockpath != NULL)
+ {
+ if (removable &&
+ stat(sockpath, &fileinfo) == 0 &&
+ ((fileinfo.st_dev == sockinfo.st_dev &&
+ fileinfo.st_ino == sockinfo.st_ino)
+# ifdef S_ISSOCK
+ || S_ISSOCK(fileinfo.st_mode)
+# endif /* S_ISSOCK */
+ )
+ &&
+ (S_ISFIFO(fileinfo.st_mode)
+# ifdef S_ISSOCK
+ || S_ISSOCK(fileinfo.st_mode)
+# endif /* S_ISSOCK */
+ ))
+ (void) unlink(sockpath);
+ free(sockpath);
+ sockpath = NULL;
+ }
+#endif /* NETUNIX */
+ }
+ (void) smutex_unlock(&L_Mutex);
+}
+
+/*
+** MI_LISTENER -- Generic listener harness
+**
+** Open up listen port
+** Wait for connections
+**
+** Parameters:
+** conn -- connection description
+** dbg -- debug level
+** smfi -- filter structure to use
+** timeout -- timeout for reads/writes
+** backlog -- listen queue backlog size
+**
+** Returns:
+** MI_SUCCESS -- Exited normally
+** (session finished or we were told to exit)
+** MI_FAILURE -- Network initialization failed.
+*/
+
+#if BROKEN_PTHREAD_SLEEP
+
+/*
+** Solaris 2.6, perhaps others, gets an internal threads library panic
+** when sleep() is used:
+**
+** thread_create() failed, returned 11 (EINVAL)
+** co_enable, thr_create() returned error = 24
+** libthread panic: co_enable failed (PID: 17793 LWP 1)
+** stacktrace:
+** ef526b10
+** ef52646c
+** ef534cbc
+** 156a4
+** 14644
+** 1413c
+** 135e0
+** 0
+*/
+
+# define MI_SLEEP(s) \
+{ \
+ int rs = 0; \
+ struct timeval st; \
+ \
+ st.tv_sec = (s); \
+ st.tv_usec = 0; \
+ if (st.tv_sec > 0) \
+ { \
+ for (;;) \
+ { \
+ rs = select(0, NULL, NULL, NULL, &st); \
+ if (rs < 0 && errno == EINTR) \
+ continue; \
+ if (rs != 0) \
+ { \
+ smi_log(SMI_LOG_ERR, \
+ "MI_SLEEP(): select() returned non-zero result %d, errno = %d", \
+ rs, errno); \
+ } \
+ break; \
+ } \
+ } \
+}
+#else /* BROKEN_PTHREAD_SLEEP */
+# define MI_SLEEP(s) sleep((s))
+#endif /* BROKEN_PTHREAD_SLEEP */
+
+int
+mi_listener(conn, dbg, smfi, timeout, backlog)
+ char *conn;
+ int dbg;
+ smfiDesc_ptr smfi;
+ time_t timeout;
+ int backlog;
+{
+ socket_t connfd = INVALID_SOCKET;
+#if _FFR_DUP_FD
+ socket_t dupfd = INVALID_SOCKET;
+#endif /* _FFR_DUP_FD */
+ int sockopt = 1;
+ int r, mistop;
+ int ret = MI_SUCCESS;
+ int mcnt = 0; /* error count for malloc() failures */
+ int tcnt = 0; /* error count for thread_create() failures */
+ int acnt = 0; /* error count for accept() failures */
+ int scnt = 0; /* error count for select() failures */
+ int save_errno = 0;
+ sthread_t thread_id;
+ _SOCK_ADDR cliaddr;
+ SOCKADDR_LEN_T clilen;
+ SMFICTX_PTR ctx;
+ FD_RD_VAR(rds, excs);
+ struct timeval chktime;
+
+ if (mi_opensocket(conn, backlog, dbg, false, smfi) == MI_FAILURE)
+ return MI_FAILURE;
+
+ clilen = L_socksize;
+ while ((mistop = mi_stop()) == MILTER_CONT)
+ {
+ (void) smutex_lock(&L_Mutex);
+ if (!ValidSocket(listenfd))
+ {
+ ret = MI_FAILURE;
+ smi_log(SMI_LOG_ERR,
+ "%s: listenfd=%d corrupted, terminating, errno=%d",
+ smfi->xxfi_name, listenfd, errno);
+ (void) smutex_unlock(&L_Mutex);
+ break;
+ }
+
+ /* select on interface ports */
+ FD_RD_INIT(listenfd, rds, excs);
+ chktime.tv_sec = MI_CHK_TIME;
+ chktime.tv_usec = 0;
+ r = FD_RD_READY(listenfd, rds, excs, &chktime);
+ if (r == 0) /* timeout */
+ {
+ (void) smutex_unlock(&L_Mutex);
+ continue; /* just check mi_stop() */
+ }
+ if (r < 0)
+ {
+ save_errno = errno;
+ (void) smutex_unlock(&L_Mutex);
+ if (save_errno == EINTR)
+ continue;
+ scnt++;
+ smi_log(SMI_LOG_ERR,
+ "%s: select() failed (%s), %s",
+ smfi->xxfi_name, sm_errstring(save_errno),
+ scnt >= MAX_FAILS_S ? "abort" : "try again");
+ MI_SLEEP(scnt);
+ if (scnt >= MAX_FAILS_S)
+ {
+ ret = MI_FAILURE;
+ break;
+ }
+ continue;
+ }
+ if (!FD_IS_RD_RDY(listenfd, rds, excs))
+ {
+ /* some error: just stop for now... */
+ ret = MI_FAILURE;
+ (void) smutex_unlock(&L_Mutex);
+ smi_log(SMI_LOG_ERR,
+ "%s: %s() returned exception for socket, abort",
+ smfi->xxfi_name, MI_POLLSELECT);
+ break;
+ }
+ scnt = 0; /* reset error counter for select() */
+
+ (void) memset(&cliaddr, '\0', sizeof cliaddr);
+ connfd = accept(listenfd, (struct sockaddr *) &cliaddr,
+ &clilen);
+ save_errno = errno;
+ (void) smutex_unlock(&L_Mutex);
+
+ /*
+ ** If remote side closes before
+ ** accept() finishes, sockaddr
+ ** might not be fully filled in.
+ */
+
+ if (ValidSocket(connfd) &&
+ (clilen == 0 ||
+# ifdef BSD4_4_SOCKADDR
+ cliaddr.sa.sa_len == 0 ||
+# endif /* BSD4_4_SOCKADDR */
+ cliaddr.sa.sa_family != L_family))
+ {
+ (void) closesocket(connfd);
+ connfd = INVALID_SOCKET;
+ save_errno = EINVAL;
+ }
+
+#if !SM_CONF_POLL
+ /* check if acceptable for select() */
+ if (ValidSocket(connfd) && !SM_FD_OK_SELECT(connfd))
+ {
+ (void) closesocket(connfd);
+ connfd = INVALID_SOCKET;
+ save_errno = ERANGE;
+ }
+#endif /* !SM_CONF_POLL */
+
+ if (!ValidSocket(connfd))
+ {
+ if (save_errno == EINTR
+#ifdef EAGAIN
+ || save_errno == EAGAIN
+#endif /* EAGAIN */
+#ifdef ECONNABORTED
+ || save_errno == ECONNABORTED
+#endif /* ECONNABORTED */
+#ifdef EMFILE
+ || save_errno == EMFILE
+#endif /* EMFILE */
+#ifdef ENFILE
+ || save_errno == ENFILE
+#endif /* ENFILE */
+#ifdef ENOBUFS
+ || save_errno == ENOBUFS
+#endif /* ENOBUFS */
+#ifdef ENOMEM
+ || save_errno == ENOMEM
+#endif /* ENOMEM */
+#ifdef ENOSR
+ || save_errno == ENOSR
+#endif /* ENOSR */
+#ifdef EWOULDBLOCK
+ || save_errno == EWOULDBLOCK
+#endif /* EWOULDBLOCK */
+ )
+ continue;
+ acnt++;
+ smi_log(SMI_LOG_ERR,
+ "%s: accept() returned invalid socket (%s), %s",
+ smfi->xxfi_name, sm_errstring(save_errno),
+ acnt >= MAX_FAILS_A ? "abort" : "try again");
+ MI_SLEEP(acnt);
+ if (acnt >= MAX_FAILS_A)
+ {
+ ret = MI_FAILURE;
+ break;
+ }
+ continue;
+ }
+ acnt = 0; /* reset error counter for accept() */
+#if _FFR_DUP_FD
+ dupfd = fcntl(connfd, F_DUPFD, 256);
+ if (ValidSocket(dupfd)
+# if !SM_CONF_POLL
+ && SM_FD_OK_SELECT(dupfd)
+# endif /* !SM_CONF_POLL */
+ )
+ {
+ close(connfd);
+ connfd = dupfd;
+ dupfd = INVALID_SOCKET;
+ }
+#endif /* _FFR_DUP_FD */
+
+ if (setsockopt(connfd, SOL_SOCKET, SO_KEEPALIVE,
+ (void *) &sockopt, sizeof sockopt) < 0)
+ {
+ smi_log(SMI_LOG_WARN, "%s: setsockopt() failed (%s)",
+ smfi->xxfi_name, sm_errstring(errno));
+ /* XXX: continue? */
+ }
+ if ((ctx = (SMFICTX_PTR) malloc(sizeof *ctx)) == NULL)
+ {
+ (void) closesocket(connfd);
+ mcnt++;
+ smi_log(SMI_LOG_ERR, "%s: malloc(ctx) failed (%s), %s",
+ smfi->xxfi_name, sm_errstring(save_errno),
+ mcnt >= MAX_FAILS_M ? "abort" : "try again");
+ MI_SLEEP(mcnt);
+ if (mcnt >= MAX_FAILS_M)
+ {
+ ret = MI_FAILURE;
+ break;
+ }
+ continue;
+ }
+ mcnt = 0; /* reset error counter for malloc() */
+ (void) memset(ctx, '\0', sizeof *ctx);
+ ctx->ctx_sd = connfd;
+ ctx->ctx_dbg = dbg;
+ ctx->ctx_timeout = timeout;
+ ctx->ctx_smfi = smfi;
+#if 0
+ if (smfi->xxfi_eoh == NULL)
+ if (smfi->xxfi_eom == NULL)
+ if (smfi->xxfi_abort == NULL)
+ if (smfi->xxfi_close == NULL)
+#endif /* 0 */
+ if (smfi->xxfi_connect == NULL)
+ ctx->ctx_pflags |= SMFIP_NOCONNECT;
+ if (smfi->xxfi_helo == NULL)
+ ctx->ctx_pflags |= SMFIP_NOHELO;
+ if (smfi->xxfi_envfrom == NULL)
+ ctx->ctx_pflags |= SMFIP_NOMAIL;
+ if (smfi->xxfi_envrcpt == NULL)
+ ctx->ctx_pflags |= SMFIP_NORCPT;
+ if (smfi->xxfi_header == NULL)
+ ctx->ctx_pflags |= SMFIP_NOHDRS;
+ if (smfi->xxfi_eoh == NULL)
+ ctx->ctx_pflags |= SMFIP_NOEOH;
+ if (smfi->xxfi_body == NULL)
+ ctx->ctx_pflags |= SMFIP_NOBODY;
+
+ if ((r = thread_create(&thread_id,
+ mi_thread_handle_wrapper,
+ (void *) ctx)) != 0)
+ {
+ tcnt++;
+ smi_log(SMI_LOG_ERR,
+ "%s: thread_create() failed: %d, %s",
+ smfi->xxfi_name, r,
+ tcnt >= MAX_FAILS_T ? "abort" : "try again");
+ MI_SLEEP(tcnt);
+ (void) closesocket(connfd);
+ free(ctx);
+ if (tcnt >= MAX_FAILS_T)
+ {
+ ret = MI_FAILURE;
+ break;
+ }
+ continue;
+ }
+ tcnt = 0;
+ }
+ if (ret != MI_SUCCESS)
+ mi_stop_milters(MILTER_ABRT);
+ else
+ {
+ if (mistop != MILTER_CONT)
+ smi_log(SMI_LOG_INFO, "%s: mi_stop=%d",
+ smfi->xxfi_name, mistop);
+ mi_closener();
+ }
+ (void) smutex_destroy(&L_Mutex);
+ return ret;
+}
diff --git a/usr/src/cmd/sendmail/libmilter/llib-lmilter b/usr/src/cmd/sendmail/libmilter/llib-lmilter
new file mode 100644
index 0000000000..0d56627c3e
--- /dev/null
+++ b/usr/src/cmd/sendmail/libmilter/llib-lmilter
@@ -0,0 +1,32 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+/*
+ * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/* LINTLIBRARY */
+/* PROTOLIB1 */
+
+#include "libmilter.h"
diff --git a/usr/src/cmd/sendmail/libmilter/main.c b/usr/src/cmd/sendmail/libmilter/main.c
new file mode 100644
index 0000000000..4115951fee
--- /dev/null
+++ b/usr/src/cmd/sendmail/libmilter/main.c
@@ -0,0 +1,245 @@
+/*
+ * Copyright (c) 1999-2003 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: main.c,v 8.79 2003/10/20 22:25:09 ca Exp $")
+
+#define _DEFINE 1
+#include "libmilter.h"
+#include <fcntl.h>
+#include <sys/stat.h>
+
+
+static smfiDesc_ptr smfi = NULL;
+
+/*
+** SMFI_REGISTER -- register a filter description
+**
+** Parameters:
+** smfilter -- description of filter to register
+**
+** Returns:
+** MI_SUCCESS/MI_FAILURE
+*/
+
+int
+smfi_register(smfilter)
+ smfiDesc_str smfilter;
+{
+ size_t len;
+
+ if (smfi == NULL)
+ {
+ smfi = (smfiDesc_ptr) malloc(sizeof *smfi);
+ if (smfi == NULL)
+ return MI_FAILURE;
+ }
+ (void) memcpy(smfi, &smfilter, sizeof *smfi);
+ if (smfilter.xxfi_name == NULL)
+ smfilter.xxfi_name = "Unknown";
+
+ len = strlen(smfilter.xxfi_name) + 1;
+ smfi->xxfi_name = (char *) malloc(len);
+ if (smfi->xxfi_name == NULL)
+ return MI_FAILURE;
+ (void) sm_strlcpy(smfi->xxfi_name, smfilter.xxfi_name, len);
+
+ /* compare milter version with hard coded version */
+ if (smfi->xxfi_version != SMFI_VERSION)
+ {
+ /* hard failure for now! */
+ smi_log(SMI_LOG_ERR,
+ "%s: smfi_register: version mismatch application: %d != milter: %d",
+ smfi->xxfi_name, smfi->xxfi_version,
+ (int) SMFI_VERSION);
+
+ /* XXX how about smfi? */
+ free(smfi->xxfi_name);
+ return MI_FAILURE;
+ }
+
+ return MI_SUCCESS;
+}
+
+/*
+** SMFI_STOP -- stop milter
+**
+** Parameters:
+** none.
+**
+** Returns:
+** success.
+*/
+
+int
+smfi_stop()
+{
+ mi_stop_milters(MILTER_STOP);
+ return MI_SUCCESS;
+}
+
+/*
+** Default values for some variables.
+** Most of these can be changed with the functions below.
+*/
+
+static int dbg = 0;
+static char *conn = NULL;
+static int timeout = MI_TIMEOUT;
+static int backlog = MI_SOMAXCONN;
+
+/*
+** SMFI_OPENSOCKET -- try the socket setup to make sure we'll be
+** able to start up
+**
+** Parameters:
+** rmsocket -- if true, instructs libmilter to attempt
+** to remove the socket before creating it;
+** only applies for "local:" or "unix:" sockets
+**
+** Return:
+** MI_SUCCESS/MI_FAILURE
+*/
+
+int
+smfi_opensocket(rmsocket)
+ bool rmsocket;
+{
+ if (smfi == NULL || conn == NULL)
+ return MI_FAILURE;
+
+ return mi_opensocket(conn, backlog, dbg, rmsocket, smfi);
+}
+
+/*
+** SMFI_SETDBG -- set debug level.
+**
+** Parameters:
+** odbg -- new debug level.
+**
+** Returns:
+** MI_SUCCESS
+*/
+
+int
+smfi_setdbg(odbg)
+ int odbg;
+{
+ dbg = odbg;
+ return MI_SUCCESS;
+}
+
+/*
+** SMFI_SETTIMEOUT -- set timeout (for read/write).
+**
+** Parameters:
+** otimeout -- new timeout.
+**
+** Returns:
+** MI_SUCCESS
+*/
+
+int
+smfi_settimeout(otimeout)
+ int otimeout;
+{
+ timeout = otimeout;
+ return MI_SUCCESS;
+}
+
+/*
+** SMFI_SETCONN -- set connection information (socket description)
+**
+** Parameters:
+** oconn -- new connection information.
+**
+** Returns:
+** MI_SUCCESS/MI_FAILURE
+*/
+
+int
+smfi_setconn(oconn)
+ char *oconn;
+{
+ size_t l;
+
+ if (oconn == NULL || *oconn == '\0')
+ return MI_FAILURE;
+ l = strlen(oconn) + 1;
+ if ((conn = (char *) malloc(l)) == NULL)
+ return MI_FAILURE;
+ if (sm_strlcpy(conn, oconn, l) >= l)
+ return MI_FAILURE;
+ return MI_SUCCESS;
+}
+
+/*
+** SMFI_SETBACKLOG -- set backlog
+**
+** Parameters:
+** obacklog -- new backlog.
+**
+** Returns:
+** MI_SUCCESS/MI_FAILURE
+*/
+
+int
+smfi_setbacklog(obacklog)
+ int obacklog;
+{
+ if (obacklog <= 0)
+ return MI_FAILURE;
+ backlog = obacklog;
+ return MI_SUCCESS;
+}
+
+
+/*
+** SMFI_MAIN -- setup milter connnection and start listener.
+**
+** Parameters:
+** none.
+**
+** Returns:
+** MI_SUCCESS/MI_FAILURE
+*/
+
+int
+smfi_main()
+{
+ int r;
+
+ (void) signal(SIGPIPE, SIG_IGN);
+ if (conn == NULL)
+ {
+ smi_log(SMI_LOG_FATAL, "%s: missing connection information",
+ smfi->xxfi_name);
+ return MI_FAILURE;
+ }
+
+ (void) atexit(mi_clean_signals);
+ if (mi_control_startup(smfi->xxfi_name) != MI_SUCCESS)
+ {
+ smi_log(SMI_LOG_FATAL,
+ "%s: Couldn't start signal thread",
+ smfi->xxfi_name);
+ return MI_FAILURE;
+ }
+ r = MI_SUCCESS;
+
+ /* Startup the listener */
+ if (mi_listener(conn, dbg, smfi, timeout, backlog) != MI_SUCCESS)
+ r = MI_FAILURE;
+
+ return r;
+}
+
diff --git a/usr/src/cmd/sendmail/libmilter/signal.c b/usr/src/cmd/sendmail/libmilter/signal.c
new file mode 100644
index 0000000000..e3744cdc21
--- /dev/null
+++ b/usr/src/cmd/sendmail/libmilter/signal.c
@@ -0,0 +1,223 @@
+/*
+ * Copyright (c) 1999-2004 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: signal.c,v 8.42 2004/08/20 21:10:30 ca Exp $")
+
+#include "libmilter.h"
+
+/*
+** thread to handle signals
+*/
+
+static smutex_t M_Mutex;
+
+static int MilterStop = MILTER_CONT;
+
+static void *mi_signal_thread __P((void *));
+static int mi_spawn_signal_thread __P((char *));
+
+/*
+** MI_STOP -- return value of MilterStop
+**
+** Parameters:
+** none.
+**
+** Returns:
+** value of MilterStop
+*/
+
+int
+mi_stop()
+{
+ return MilterStop;
+}
+/*
+** MI_STOP_MILTERS -- set value of MilterStop
+**
+** Parameters:
+** v -- new value for MilterStop.
+**
+** Returns:
+** none.
+*/
+
+void
+mi_stop_milters(v)
+ int v;
+{
+ (void) smutex_lock(&M_Mutex);
+ if (MilterStop < v)
+ MilterStop = v;
+
+ /* close listen socket */
+ mi_closener();
+ (void) smutex_unlock(&M_Mutex);
+}
+/*
+** MI_CLEAN_SIGNALS -- clean up signal handler thread
+**
+** Parameters:
+** none.
+**
+** Returns:
+** none.
+*/
+
+void
+mi_clean_signals()
+{
+ (void) smutex_destroy(&M_Mutex);
+}
+/*
+** MI_SIGNAL_THREAD -- thread to deal with signals
+**
+** Parameters:
+** name -- name of milter
+**
+** Returns:
+** NULL
+*/
+
+static void *
+mi_signal_thread(name)
+ void *name;
+{
+ int sig, errs;
+ sigset_t set;
+
+ (void) sigemptyset(&set);
+ (void) sigaddset(&set, SIGHUP);
+ (void) sigaddset(&set, SIGTERM);
+
+ /* Handle Ctrl-C gracefully for debugging */
+ (void) sigaddset(&set, SIGINT);
+ errs = 0;
+
+ for (;;)
+ {
+ sig = 0;
+#if defined(SOLARIS) || defined(__svr5__)
+ if ((sig = sigwait(&set)) < 0)
+#else /* defined(SOLARIS) || defined(__svr5__) */
+ if (sigwait(&set, &sig) != 0)
+#endif /* defined(SOLARIS) || defined(__svr5__) */
+ {
+ /* this can happen on OSF/1 (at least) */
+ if (errno == EINTR)
+ continue;
+ smi_log(SMI_LOG_ERR,
+ "%s: sigwait returned error: %d",
+ (char *)name, errno);
+ if (++errs > MAX_FAILS_T)
+ {
+ mi_stop_milters(MILTER_ABRT);
+ return NULL;
+ }
+ continue;
+ }
+ errs = 0;
+
+ switch (sig)
+ {
+ case SIGHUP:
+ case SIGTERM:
+ mi_stop_milters(MILTER_STOP);
+ return NULL;
+ case SIGINT:
+ mi_stop_milters(MILTER_ABRT);
+ return NULL;
+ default:
+ smi_log(SMI_LOG_ERR,
+ "%s: sigwait returned unmasked signal: %d",
+ (char *)name, sig);
+ break;
+ }
+ }
+ /* NOTREACHED */
+}
+/*
+** MI_SPAWN_SIGNAL_THREAD -- spawn thread to handle signals
+**
+** Parameters:
+** name -- name of milter
+**
+** Returns:
+** MI_SUCCESS/MI_FAILURE
+*/
+
+static int
+mi_spawn_signal_thread(name)
+ char *name;
+{
+ sthread_t tid;
+ int r;
+ sigset_t set;
+
+ /* Mask HUP and KILL signals */
+ (void) sigemptyset(&set);
+ (void) sigaddset(&set, SIGHUP);
+ (void) sigaddset(&set, SIGTERM);
+ (void) sigaddset(&set, SIGINT);
+
+ if (pthread_sigmask(SIG_BLOCK, &set, NULL) != 0)
+ {
+ smi_log(SMI_LOG_ERR,
+ "%s: Couldn't mask HUP and KILL signals", name);
+ return MI_FAILURE;
+ }
+ r = thread_create(&tid, mi_signal_thread, (void *)name);
+ if (r != 0)
+ {
+ smi_log(SMI_LOG_ERR,
+ "%s: Couldn't start signal thread: %d",
+ name, r);
+ return MI_FAILURE;
+ }
+ return MI_SUCCESS;
+}
+/*
+** MI_CONTROL_STARTUP -- startup for thread to handle signals
+**
+** Parameters:
+** name -- name of milter
+**
+** Returns:
+** MI_SUCCESS/MI_FAILURE
+*/
+
+int
+mi_control_startup(name)
+ char *name;
+{
+
+ if (!smutex_init(&M_Mutex))
+ {
+ smi_log(SMI_LOG_ERR,
+ "%s: Couldn't initialize control pipe mutex", name);
+ return MI_FAILURE;
+ }
+
+ /*
+ ** spawn_signal_thread must happen before other threads are spawned
+ ** off so that it can mask the right signals and other threads
+ ** will inherit that mask.
+ */
+ if (mi_spawn_signal_thread(name) == MI_FAILURE)
+ {
+ smi_log(SMI_LOG_ERR,
+ "%s: Couldn't spawn signal thread", name);
+ (void) smutex_destroy(&M_Mutex);
+ return MI_FAILURE;
+ }
+ return MI_SUCCESS;
+}
diff --git a/usr/src/cmd/sendmail/libmilter/sm_gethost.c b/usr/src/cmd/sendmail/libmilter/sm_gethost.c
new file mode 100644
index 0000000000..504260ce50
--- /dev/null
+++ b/usr/src/cmd/sendmail/libmilter/sm_gethost.c
@@ -0,0 +1,150 @@
+/*
+ * Copyright (c) 1999-2001, 2004 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: sm_gethost.c,v 8.27 2004/08/20 21:12:37 ca Exp $")
+
+#include <sendmail.h>
+#if NETINET || NETINET6
+# include <arpa/inet.h>
+#endif /* NETINET || NETINET6 */
+#include "libmilter.h"
+
+/*
+** MI_GETHOSTBY{NAME,ADDR} -- compatibility routines for gethostbyXXX
+**
+** Some operating systems have wierd problems with the gethostbyXXX
+** routines. For example, Solaris versions at least through 2.3
+** don't properly deliver a canonical h_name field. This tries to
+** work around these problems.
+**
+** Support IPv6 as well as IPv4.
+*/
+
+#if NETINET6 && NEEDSGETIPNODE
+
+static struct hostent *getipnodebyname __P((char *, int, int, int *));
+
+# ifndef AI_ADDRCONFIG
+# define AI_ADDRCONFIG 0 /* dummy */
+# endif /* ! AI_ADDRCONFIG */
+# ifndef AI_ALL
+# define AI_ALL 0 /* dummy */
+# endif /* ! AI_ALL */
+# ifndef AI_DEFAULT
+# define AI_DEFAULT 0 /* dummy */
+# endif /* ! AI_DEFAULT */
+
+static struct hostent *
+getipnodebyname(name, family, flags, err)
+ char *name;
+ int family;
+ int flags;
+ int *err;
+{
+ bool resv6 = true;
+ struct hostent *h;
+
+ if (family == AF_INET6)
+ {
+ /* From RFC2133, section 6.1 */
+ resv6 = bitset(RES_USE_INET6, _res.options);
+ _res.options |= RES_USE_INET6;
+ }
+ SM_SET_H_ERRNO(0);
+ h = gethostbyname(name);
+ if (family == AF_INET6 && !resv6)
+ _res.options &= ~RES_USE_INET6;
+ *err = h_errno;
+ return h;
+}
+
+void
+freehostent(h)
+ struct hostent *h;
+{
+ /*
+ ** Stub routine -- if they don't have getipnodeby*(),
+ ** they probably don't have the free routine either.
+ */
+
+ return;
+}
+#endif /* NEEDSGETIPNODE && NETINET6 */
+
+struct hostent *
+mi_gethostbyname(name, family)
+ char *name;
+ int family;
+{
+ struct hostent *h = NULL;
+#if (SOLARIS > 10000 && SOLARIS < 20400) || (defined(SOLARIS) && SOLARIS < 204) || (defined(sony_news) && defined(__svr4))
+# if SOLARIS == 20300 || SOLARIS == 203
+ static struct hostent hp;
+ static char buf[1000];
+ extern struct hostent *_switch_gethostbyname_r();
+
+ h = _switch_gethostbyname_r(name, &hp, buf, sizeof(buf), &h_errno);
+# else /* SOLARIS == 20300 || SOLARIS == 203 */
+ extern struct hostent *__switch_gethostbyname();
+
+ h = __switch_gethostbyname(name);
+# endif /* SOLARIS == 20300 || SOLARIS == 203 */
+#else /* (SOLARIS > 10000 && SOLARIS < 20400) || (defined(SOLARIS) && SOLARIS < 204) || (defined(sony_news) && defined(__svr4)) */
+# if NETINET6
+ int flags = AI_DEFAULT|AI_ALL;
+ int err;
+# endif /* NETINET6 */
+
+# if NETINET6
+# if ADDRCONFIG_IS_BROKEN
+ flags &= ~AI_ADDRCONFIG;
+# endif /* ADDRCONFIG_IS_BROKEN */
+ h = getipnodebyname(name, family, flags, &err);
+ SM_SET_H_ERRNO(err);
+# else /* NETINET6 */
+ h = gethostbyname(name);
+# endif /* NETINET6 */
+
+#endif /* (SOLARIS > 10000 && SOLARIS < 20400) || (defined(SOLARIS) && SOLARIS < 204) || (defined(sony_news) && defined(__svr4)) */
+ return h;
+}
+
+#if NETINET6
+/*
+** MI_INET_PTON -- convert printed form to network address.
+**
+** Wrapper for inet_pton() which handles IPv6: labels.
+**
+** Parameters:
+** family -- address family
+** src -- string
+** dst -- destination address structure
+**
+** Returns:
+** 1 if the address was valid
+** 0 if the address wasn't parseable
+** -1 if error
+*/
+
+int
+mi_inet_pton(family, src, dst)
+ int family;
+ const char *src;
+ void *dst;
+{
+ if (family == AF_INET6 &&
+ strncasecmp(src, "IPv6:", 5) == 0)
+ src += 5;
+ return inet_pton(family, src, dst);
+}
+#endif /* NETINET6 */
diff --git a/usr/src/cmd/sendmail/libmilter/smfi.c b/usr/src/cmd/sendmail/libmilter/smfi.c
new file mode 100644
index 0000000000..a04811b370
--- /dev/null
+++ b/usr/src/cmd/sendmail/libmilter/smfi.c
@@ -0,0 +1,650 @@
+/*
+ * Copyright (c) 1999-2004 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: smfi.c,v 8.73 2004/09/20 21:26:57 ca Exp $")
+#include <sm/varargs.h>
+#include "libmilter.h"
+
+static int smfi_header __P((SMFICTX *, int, int, char *, char *));
+static int myisenhsc __P((const char *, int));
+
+/* for smfi_set{ml}reply, let's be generous. 256/16 should be sufficient */
+#define MAXREPLYLEN 980 /* max. length of a reply string */
+#define MAXREPLIES 32 /* max. number of reply strings */
+
+/*
+** SMFI_HEADER -- send a header to the MTA
+**
+** Parameters:
+** ctx -- Opaque context structure
+** cmd -- Header modification command
+** hdridx -- Header index
+** headerf -- Header field name
+** headerv -- Header field value
+**
+**
+** Returns:
+** MI_SUCCESS/MI_FAILURE
+*/
+
+static int
+smfi_header(ctx, cmd, hdridx, headerf, headerv)
+ SMFICTX *ctx;
+ int cmd;
+ int hdridx;
+ char *headerf;
+ char *headerv;
+{
+ size_t len, l1, l2, offset;
+ int r;
+ mi_int32 v;
+ char *buf;
+ struct timeval timeout;
+
+ if (headerf == NULL || *headerf == '\0' || headerv == NULL)
+ return MI_FAILURE;
+ timeout.tv_sec = ctx->ctx_timeout;
+ timeout.tv_usec = 0;
+ l1 = strlen(headerf) + 1;
+ l2 = strlen(headerv) + 1;
+ len = l1 + l2;
+ if (hdridx >= 0)
+ len += MILTER_LEN_BYTES;
+ buf = malloc(len);
+ if (buf == NULL)
+ return MI_FAILURE;
+ offset = 0;
+ if (hdridx >= 0)
+ {
+ v = htonl(hdridx);
+ (void) memcpy(&(buf[0]), (void *) &v, MILTER_LEN_BYTES);
+ offset += MILTER_LEN_BYTES;
+ }
+ (void) memcpy(buf + offset, headerf, l1);
+ (void) memcpy(buf + offset + l1, headerv, l2);
+ r = mi_wr_cmd(ctx->ctx_sd, &timeout, cmd, buf, len);
+ free(buf);
+ return r;
+}
+
+/*
+** SMFI_ADDHEADER -- send a new header to the MTA
+**
+** Parameters:
+** ctx -- Opaque context structure
+** headerf -- Header field name
+** headerv -- Header field value
+**
+** Returns:
+** MI_SUCCESS/MI_FAILURE
+*/
+
+int
+smfi_addheader(ctx, headerf, headerv)
+ SMFICTX *ctx;
+ char *headerf;
+ char *headerv;
+{
+ if (!mi_sendok(ctx, SMFIF_ADDHDRS))
+ return MI_FAILURE;
+
+ return smfi_header(ctx, SMFIR_ADDHEADER, -1, headerf, headerv);
+}
+
+/*
+** SMFI_INSHEADER -- send a new header to the MTA (to be inserted)
+**
+** Parameters:
+** ctx -- Opaque context structure
+** hdridx -- index into header list where insertion should occur
+** headerf -- Header field name
+** headerv -- Header field value
+**
+** Returns:
+** MI_SUCCESS/MI_FAILURE
+*/
+
+int
+smfi_insheader(ctx, hdridx, headerf, headerv)
+ SMFICTX *ctx;
+ int hdridx;
+ char *headerf;
+ char *headerv;
+{
+ if (!mi_sendok(ctx, SMFIF_ADDHDRS) || hdridx < 0)
+ return MI_FAILURE;
+
+ return smfi_header(ctx, SMFIR_INSHEADER, hdridx, headerf, headerv);
+}
+
+/*
+** SMFI_CHGHEADER -- send a changed header to the MTA
+**
+** Parameters:
+** ctx -- Opaque context structure
+** headerf -- Header field name
+** hdridx -- Header index value
+** headerv -- Header field value
+**
+** Returns:
+** MI_SUCCESS/MI_FAILURE
+*/
+
+int
+smfi_chgheader(ctx, headerf, hdridx, headerv)
+ SMFICTX *ctx;
+ char *headerf;
+ mi_int32 hdridx;
+ char *headerv;
+{
+ if (!mi_sendok(ctx, SMFIF_CHGHDRS) || hdridx < 0)
+ return MI_FAILURE;
+ if (headerv == NULL)
+ headerv = "";
+
+ return smfi_header(ctx, SMFIR_CHGHEADER, hdridx, headerf, headerv);
+}
+
+/*
+** SMFI_ADDRCPT -- send an additional recipient to the MTA
+**
+** Parameters:
+** ctx -- Opaque context structure
+** rcpt -- recipient address
+**
+** Returns:
+** MI_SUCCESS/MI_FAILURE
+*/
+
+int
+smfi_addrcpt(ctx, rcpt)
+ SMFICTX *ctx;
+ char *rcpt;
+{
+ size_t len;
+ struct timeval timeout;
+
+ if (rcpt == NULL || *rcpt == '\0')
+ return MI_FAILURE;
+ if (!mi_sendok(ctx, SMFIF_ADDRCPT))
+ return MI_FAILURE;
+ timeout.tv_sec = ctx->ctx_timeout;
+ timeout.tv_usec = 0;
+ len = strlen(rcpt) + 1;
+ return mi_wr_cmd(ctx->ctx_sd, &timeout, SMFIR_ADDRCPT, rcpt, len);
+}
+
+/*
+** SMFI_DELRCPT -- send a recipient to be removed to the MTA
+**
+** Parameters:
+** ctx -- Opaque context structure
+** rcpt -- recipient address
+**
+** Returns:
+** MI_SUCCESS/MI_FAILURE
+*/
+
+int
+smfi_delrcpt(ctx, rcpt)
+ SMFICTX *ctx;
+ char *rcpt;
+{
+ size_t len;
+ struct timeval timeout;
+
+ if (rcpt == NULL || *rcpt == '\0')
+ return MI_FAILURE;
+ if (!mi_sendok(ctx, SMFIF_DELRCPT))
+ return MI_FAILURE;
+ timeout.tv_sec = ctx->ctx_timeout;
+ timeout.tv_usec = 0;
+ len = strlen(rcpt) + 1;
+ return mi_wr_cmd(ctx->ctx_sd, &timeout, SMFIR_DELRCPT, rcpt, len);
+}
+
+/*
+** SMFI_REPLACEBODY -- send a body chunk to the MTA
+**
+** Parameters:
+** ctx -- Opaque context structure
+** bodyp -- body chunk
+** bodylen -- length of body chunk
+**
+** Returns:
+** MI_SUCCESS/MI_FAILURE
+*/
+
+int
+smfi_replacebody(ctx, bodyp, bodylen)
+ SMFICTX *ctx;
+ unsigned char *bodyp;
+ int bodylen;
+{
+ int len, off, r;
+ struct timeval timeout;
+
+ if (bodylen < 0 ||
+ (bodyp == NULL && bodylen > 0))
+ return MI_FAILURE;
+ if (!mi_sendok(ctx, SMFIF_CHGBODY))
+ return MI_FAILURE;
+ timeout.tv_sec = ctx->ctx_timeout;
+ timeout.tv_usec = 0;
+
+ /* split body chunk if necessary */
+ off = 0;
+ while (bodylen > 0)
+ {
+ len = (bodylen >= MILTER_CHUNK_SIZE) ? MILTER_CHUNK_SIZE :
+ bodylen;
+ if ((r = mi_wr_cmd(ctx->ctx_sd, &timeout, SMFIR_REPLBODY,
+ (char *) (bodyp + off), len)) != MI_SUCCESS)
+ return r;
+ off += len;
+ bodylen -= len;
+ }
+ return MI_SUCCESS;
+}
+
+/*
+** SMFI_QUARANTINE -- quarantine an envelope
+**
+** Parameters:
+** ctx -- Opaque context structure
+** reason -- why?
+**
+** Returns:
+** MI_SUCCESS/MI_FAILURE
+*/
+
+int
+smfi_quarantine(ctx, reason)
+ SMFICTX *ctx;
+ char *reason;
+{
+ size_t len;
+ int r;
+ char *buf;
+ struct timeval timeout;
+
+ if (reason == NULL || *reason == '\0')
+ return MI_FAILURE;
+ if (!mi_sendok(ctx, SMFIF_QUARANTINE))
+ return MI_FAILURE;
+ timeout.tv_sec = ctx->ctx_timeout;
+ timeout.tv_usec = 0;
+ len = strlen(reason) + 1;
+ buf = malloc(len);
+ if (buf == NULL)
+ return MI_FAILURE;
+ (void) memcpy(buf, reason, len);
+ r = mi_wr_cmd(ctx->ctx_sd, &timeout, SMFIR_QUARANTINE, buf, len);
+ free(buf);
+ return r;
+}
+
+/*
+** MYISENHSC -- check whether a string contains an enhanced status code
+**
+** Parameters:
+** s -- string with possible enhanced status code.
+** delim -- delim for enhanced status code.
+**
+** Returns:
+** 0 -- no enhanced status code.
+** >4 -- length of enhanced status code.
+**
+** Side Effects:
+** none.
+*/
+
+static int
+myisenhsc(s, delim)
+ const char *s;
+ int delim;
+{
+ int l, h;
+
+ if (s == NULL)
+ return 0;
+ if (!((*s == '2' || *s == '4' || *s == '5') && s[1] == '.'))
+ return 0;
+ h = 0;
+ l = 2;
+ while (h < 3 && isascii(s[l + h]) && isdigit(s[l + h]))
+ ++h;
+ if (h == 0 || s[l + h] != '.')
+ return 0;
+ l += h + 1;
+ h = 0;
+ while (h < 3 && isascii(s[l + h]) && isdigit(s[l + h]))
+ ++h;
+ if (h == 0 || s[l + h] != delim)
+ return 0;
+ return l + h;
+}
+
+/*
+** SMFI_SETREPLY -- set the reply code for the next reply to the MTA
+**
+** Parameters:
+** ctx -- Opaque context structure
+** rcode -- The three-digit (RFC 821) SMTP reply code.
+** xcode -- The extended (RFC 2034) reply code.
+** message -- The text part of the SMTP reply.
+**
+** Returns:
+** MI_SUCCESS/MI_FAILURE
+*/
+
+int
+smfi_setreply(ctx, rcode, xcode, message)
+ SMFICTX *ctx;
+ char *rcode;
+ char *xcode;
+ char *message;
+{
+ size_t len;
+ char *buf;
+
+ if (rcode == NULL || ctx == NULL)
+ return MI_FAILURE;
+
+ /* ### <sp> \0 */
+ len = strlen(rcode) + 2;
+ if (len != 5)
+ return MI_FAILURE;
+ if ((rcode[0] != '4' && rcode[0] != '5') ||
+ !isascii(rcode[1]) || !isdigit(rcode[1]) ||
+ !isascii(rcode[2]) || !isdigit(rcode[2]))
+ return MI_FAILURE;
+ if (xcode != NULL)
+ {
+ if (!myisenhsc(xcode, '\0'))
+ return MI_FAILURE;
+ len += strlen(xcode) + 1;
+ }
+ if (message != NULL)
+ {
+ size_t ml;
+
+ /* XXX check also for unprintable chars? */
+ if (strpbrk(message, "\r\n") != NULL)
+ return MI_FAILURE;
+ ml = strlen(message);
+ if (ml > MAXREPLYLEN)
+ return MI_FAILURE;
+ len += ml + 1;
+ }
+ buf = malloc(len);
+ if (buf == NULL)
+ return MI_FAILURE; /* oops */
+ (void) sm_strlcpy(buf, rcode, len);
+ (void) sm_strlcat(buf, " ", len);
+ if (xcode != NULL)
+ (void) sm_strlcat(buf, xcode, len);
+ if (message != NULL)
+ {
+ if (xcode != NULL)
+ (void) sm_strlcat(buf, " ", len);
+ (void) sm_strlcat(buf, message, len);
+ }
+ if (ctx->ctx_reply != NULL)
+ free(ctx->ctx_reply);
+ ctx->ctx_reply = buf;
+ return MI_SUCCESS;
+}
+
+/*
+** SMFI_SETMLREPLY -- set multiline reply code for the next reply to the MTA
+**
+** Parameters:
+** ctx -- Opaque context structure
+** rcode -- The three-digit (RFC 821) SMTP reply code.
+** xcode -- The extended (RFC 2034) reply code.
+** txt, ... -- The text part of the SMTP reply,
+** MUST be terminated with NULL.
+**
+** Returns:
+** MI_SUCCESS/MI_FAILURE
+*/
+
+int
+#if SM_VA_STD
+smfi_setmlreply(SMFICTX *ctx, const char *rcode, const char *xcode, ...)
+#else /* SM_VA_STD */
+smfi_setmlreply(ctx, rcode, xcode, va_alist)
+ SMFICTX *ctx;
+ const char *rcode;
+ const char *xcode;
+ va_dcl
+#endif /* SM_VA_STD */
+{
+ size_t len;
+ size_t rlen;
+ int args;
+ char *buf, *txt;
+ const char *xc;
+ char repl[16];
+ SM_VA_LOCAL_DECL
+
+ if (rcode == NULL || ctx == NULL)
+ return MI_FAILURE;
+
+ /* ### <sp> */
+ len = strlen(rcode) + 1;
+ if (len != 4)
+ return MI_FAILURE;
+ if ((rcode[0] != '4' && rcode[0] != '5') ||
+ !isascii(rcode[1]) || !isdigit(rcode[1]) ||
+ !isascii(rcode[2]) || !isdigit(rcode[2]))
+ return MI_FAILURE;
+ if (xcode != NULL)
+ {
+ if (!myisenhsc(xcode, '\0'))
+ return MI_FAILURE;
+ xc = xcode;
+ }
+ else
+ {
+ if (rcode[0] == '4')
+ xc = "4.0.0";
+ else
+ xc = "5.0.0";
+ }
+
+ /* add trailing space */
+ len += strlen(xc) + 1;
+ rlen = len;
+ args = 0;
+ SM_VA_START(ap, xcode);
+ while ((txt = SM_VA_ARG(ap, char *)) != NULL)
+ {
+ size_t tl;
+
+ tl = strlen(txt);
+ if (tl > MAXREPLYLEN)
+ break;
+
+ /* this text, reply codes, \r\n */
+ len += tl + 2 + rlen;
+ if (++args > MAXREPLIES)
+ break;
+
+ /* XXX check also for unprintable chars? */
+ if (strpbrk(txt, "\r\n") != NULL)
+ break;
+ }
+ SM_VA_END(ap);
+ if (txt != NULL)
+ return MI_FAILURE;
+
+ /* trailing '\0' */
+ ++len;
+ buf = malloc(len);
+ if (buf == NULL)
+ return MI_FAILURE; /* oops */
+ (void) sm_strlcpyn(buf, len, 3, rcode, args == 1 ? " " : "-", xc);
+ (void) sm_strlcpyn(repl, sizeof repl, 4, rcode, args == 1 ? " " : "-",
+ xc, " ");
+ SM_VA_START(ap, xcode);
+ txt = SM_VA_ARG(ap, char *);
+ if (txt != NULL)
+ {
+ (void) sm_strlcat2(buf, " ", txt, len);
+ while ((txt = SM_VA_ARG(ap, char *)) != NULL)
+ {
+ if (--args <= 1)
+ repl[3] = ' ';
+ (void) sm_strlcat2(buf, "\r\n", repl, len);
+ (void) sm_strlcat(buf, txt, len);
+ }
+ }
+ if (ctx->ctx_reply != NULL)
+ free(ctx->ctx_reply);
+ ctx->ctx_reply = buf;
+ SM_VA_END(ap);
+ return MI_SUCCESS;
+}
+
+/*
+** SMFI_SETPRIV -- set private data
+**
+** Parameters:
+** ctx -- Opaque context structure
+** privatedata -- pointer to private data
+**
+** Returns:
+** MI_SUCCESS/MI_FAILURE
+*/
+
+int
+smfi_setpriv(ctx, privatedata)
+ SMFICTX *ctx;
+ void *privatedata;
+{
+ if (ctx == NULL)
+ return MI_FAILURE;
+ ctx->ctx_privdata = privatedata;
+ return MI_SUCCESS;
+}
+
+/*
+** SMFI_GETPRIV -- get private data
+**
+** Parameters:
+** ctx -- Opaque context structure
+**
+** Returns:
+** pointer to private data
+*/
+
+void *
+smfi_getpriv(ctx)
+ SMFICTX *ctx;
+{
+ if (ctx == NULL)
+ return NULL;
+ return ctx->ctx_privdata;
+}
+
+/*
+** SMFI_GETSYMVAL -- get the value of a macro
+**
+** See explanation in mfapi.h about layout of the structures.
+**
+** Parameters:
+** ctx -- Opaque context structure
+** symname -- name of macro
+**
+** Returns:
+** value of macro (NULL in case of failure)
+*/
+
+char *
+smfi_getsymval(ctx, symname)
+ SMFICTX *ctx;
+ char *symname;
+{
+ int i;
+ char **s;
+ char one[2];
+ char braces[4];
+
+ if (ctx == NULL || symname == NULL || *symname == '\0')
+ return NULL;
+
+ if (strlen(symname) == 3 && symname[0] == '{' && symname[2] == '}')
+ {
+ one[0] = symname[1];
+ one[1] = '\0';
+ }
+ else
+ one[0] = '\0';
+ if (strlen(symname) == 1)
+ {
+ braces[0] = '{';
+ braces[1] = *symname;
+ braces[2] = '}';
+ braces[3] = '\0';
+ }
+ else
+ braces[0] = '\0';
+
+ /* search backwards through the macro array */
+ for (i = MAX_MACROS_ENTRIES - 1 ; i >= 0; --i)
+ {
+ if ((s = ctx->ctx_mac_ptr[i]) == NULL ||
+ ctx->ctx_mac_buf[i] == NULL)
+ continue;
+ while (s != NULL && *s != NULL)
+ {
+ if (strcmp(*s, symname) == 0)
+ return *++s;
+ if (one[0] != '\0' && strcmp(*s, one) == 0)
+ return *++s;
+ if (braces[0] != '\0' && strcmp(*s, braces) == 0)
+ return *++s;
+ ++s; /* skip over macro value */
+ ++s; /* points to next macro name */
+ }
+ }
+ return NULL;
+}
+
+/*
+** SMFI_PROGRESS -- send "progress" message to the MTA to prevent premature
+** timeouts during long milter-side operations
+**
+** Parameters:
+** ctx -- Opaque context structure
+**
+** Return value:
+** MI_SUCCESS/MI_FAILURE
+*/
+
+int
+smfi_progress(ctx)
+ SMFICTX *ctx;
+{
+ struct timeval timeout;
+
+ if (ctx == NULL)
+ return MI_FAILURE;
+
+ timeout.tv_sec = ctx->ctx_timeout;
+ timeout.tv_usec = 0;
+
+ return mi_wr_cmd(ctx->ctx_sd, &timeout, SMFIR_PROGRESS, NULL, 0);
+}
diff --git a/usr/src/cmd/sendmail/libmilter/sparc/Makefile b/usr/src/cmd/sendmail/libmilter/sparc/Makefile
new file mode 100644
index 0000000000..e86ae6a9f7
--- /dev/null
+++ b/usr/src/cmd/sendmail/libmilter/sparc/Makefile
@@ -0,0 +1,29 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License, Version 1.0 only
+# (the "License"). You may not use this file except in compliance
+# with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+# Copyright 2004 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# ident "%Z%%M% %I% %E% SMI"
+
+include ../Makefile.com
+
+install: all $(ROOTLIBS) $(ROOTLINKS) $(ROOTLINT)
diff --git a/usr/src/cmd/sendmail/libsm/Makefile b/usr/src/cmd/sendmail/libsm/Makefile
new file mode 100644
index 0000000000..5feb0f11c1
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/Makefile
@@ -0,0 +1,83 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License, Version 1.0 only
+# (the "License"). You may not use this file except in compliance
+# with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+#
+# Copyright 2005 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# ident "%Z%%M% %I% %E% SMI"
+#
+# cmd/sendmail/libsm/Makefile
+#
+
+include ../../Makefile.cmd
+include ../Makefile.cmd
+
+INCPATH= -I. -I../src -I../include
+CPPFLAGS= $(INCPATH) $(RLS_DEF) $(DBMDEF) $(CPPFLAGS.master)
+
+ARFLAGS= cq
+
+OBJS= assert.o cf.o clock.o clrerr.o config.o debug.o errstring.o exc.o \
+ fclose.o feof.o ferror.o fflush.o fget.o findfp.o flags.o fopen.o \
+ fpos.o fprintf.o fpurge.o fput.o fread.o fscanf.o fseek.o fvwrite.o \
+ fwalk.o fwrite.o get.o heap.o ldap.o makebuf.o match.o mbdb.o niprop.o \
+ path.o put.o refill.o rewind.o rpool.o setvbuf.o sem.o shm.o signal.o \
+ smstdio.o snprintf.o sscanf.o stdio.o strcasecmp.o strdup.o strerror.o \
+ strexit.o string.o stringf.o strio.o strl.o strrevcmp.o strto.o test.o \
+ ungetc.o vasprintf.o vfprintf.o vfscanf.o vprintf.o vsnprintf.o \
+ wbuf.o wsetup.o xtrap.o
+
+SRCS= $(OBJS:%.o=%.c)
+
+TESTS= t-event t-exc t-rpool t-string t-smstdio t-match t-strio t-heap \
+ t-fopen t-strl t-strrevcmp t-path t-float t-scanf t-sem t-shm
+
+libsm= libsm.a
+
+.KEEP_STATE:
+all: $(libsm)
+
+.PARALLEL: $(OBJS)
+
+$(libsm): $(OBJS)
+ $(RM) $@
+ $(AR) $(ARFLAGS) $@ $(OBJS)
+
+clean:
+ $(RM) $(OBJS) $(libsm) $(TESTS) foo t-smstdio.1
+
+depend obj:
+
+install: all
+
+LDLIBS += -lldap
+
+lint: lint_SRCS
+
+test: $(TESTS)
+
+t-%: t-%.c
+ $(LINK.c) $< -o $@ $(libsm) $(LDLIBS)
+ $(POST_PROCESS)
+ ./$@
+
+include ../../Makefile.targ
diff --git a/usr/src/cmd/sendmail/libsm/assert.c b/usr/src/cmd/sendmail/libsm/assert.c
new file mode 100644
index 0000000000..dfced1fcb7
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/assert.c
@@ -0,0 +1,189 @@
+/*
+ * Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: assert.c,v 1.25.2.1 2003/12/05 22:44:17 ca Exp $")
+
+/*
+** Abnormal program termination and assertion checking.
+** For documentation, see assert.html.
+*/
+
+#include <signal.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <sm/assert.h>
+#include <sm/exc.h>
+#include <sm/io.h>
+#include <sm/varargs.h>
+
+/*
+** Debug categories that are used to guard expensive assertion checks.
+*/
+
+SM_DEBUG_T SmExpensiveAssert = SM_DEBUG_INITIALIZER("sm_check_assert",
+ "@(#)$Debug: sm_check_assert - check assertions $");
+
+SM_DEBUG_T SmExpensiveRequire = SM_DEBUG_INITIALIZER("sm_check_require",
+ "@(#)$Debug: sm_check_require - check function preconditions $");
+
+SM_DEBUG_T SmExpensiveEnsure = SM_DEBUG_INITIALIZER("sm_check_ensure",
+ "@(#)$Debug: sm_check_ensure - check function postconditions $");
+
+/*
+** Debug category: send self SIGSTOP on fatal error,
+** so that you can run a debugger on the stopped process.
+*/
+
+SM_DEBUG_T SmAbortStop = SM_DEBUG_INITIALIZER("sm_abort_stop",
+ "@(#)$Debug: sm_abort_stop - stop process on fatal error $");
+
+/*
+** SM_ABORT_DEFAULTHANDLER -- Default procedure for abnormal program
+** termination.
+**
+** The goal is to display an error message without disturbing the
+** process state too much, then dump core.
+**
+** Parameters:
+** filename -- filename (can be NULL).
+** lineno -- line number.
+** msg -- message.
+**
+** Returns:
+** doesn't return.
+*/
+
+static void
+sm_abort_defaulthandler __P((
+ const char *filename,
+ int lineno,
+ const char *msg));
+
+static void
+sm_abort_defaulthandler(filename, lineno, msg)
+ const char *filename;
+ int lineno;
+ const char *msg;
+{
+ if (filename != NULL)
+ sm_io_fprintf(smioerr, SM_TIME_DEFAULT, "%s:%d: %s\n", filename,
+ lineno, msg);
+ else
+ sm_io_fprintf(smioerr, SM_TIME_DEFAULT, "%s\n", msg);
+ sm_io_flush(smioerr, SM_TIME_DEFAULT);
+#ifdef SIGSTOP
+ if (sm_debug_active(&SmAbortStop, 1))
+ kill(getpid(), SIGSTOP);
+#endif /* SIGSTOP */
+ abort();
+}
+
+/*
+** This is the action to be taken to cause abnormal program termination.
+*/
+
+static SM_ABORT_HANDLER_T SmAbortHandler = sm_abort_defaulthandler;
+
+/*
+** SM_ABORT_SETHANDLER -- Set handler for SM_ABORT()
+**
+** This allows you to set a handler function for causing abnormal
+** program termination; it is called when a logic bug is detected.
+**
+** Parameters:
+** f -- handler.
+**
+** Returns:
+** none.
+*/
+
+void
+sm_abort_sethandler(f)
+ SM_ABORT_HANDLER_T f;
+{
+ if (f == NULL)
+ SmAbortHandler = sm_abort_defaulthandler;
+ else
+ SmAbortHandler = f;
+}
+
+/*
+** SM_ABORT -- Call it when you have detected a logic bug.
+**
+** Parameters:
+** fmt -- format string.
+** ... -- arguments.
+**
+** Returns:
+** doesn't.
+*/
+
+void SM_DEAD_D
+#if SM_VA_STD
+sm_abort(char *fmt, ...)
+#else /* SM_VA_STD */
+sm_abort(fmt, va_alist)
+ char *fmt;
+ va_dcl
+#endif /* SM_VA_STD */
+{
+ char msg[128];
+ SM_VA_LOCAL_DECL
+
+ SM_VA_START(ap, fmt);
+ sm_vsnprintf(msg, sizeof msg, fmt, ap);
+ SM_VA_END(ap);
+ sm_abort_at(NULL, 0, msg);
+}
+
+/*
+** SM_ABORT_AT -- Initiate abnormal program termination.
+**
+** This is the low level function that is called to initiate abnormal
+** program termination. It prints an error message and terminates the
+** program. It is called by sm_abort and by the assertion macros.
+** If filename != NULL then filename and lineno specify the line of source
+** code at which the bug was detected.
+**
+** Parameters:
+** filename -- filename (can be NULL).
+** lineno -- line number.
+** msg -- message.
+**
+** Returns:
+** doesn't.
+*/
+
+void SM_DEAD_D
+sm_abort_at(filename, lineno, msg)
+ const char *filename;
+ int lineno;
+ const char *msg;
+{
+ SM_TRY
+ (*SmAbortHandler)(filename, lineno, msg);
+ SM_EXCEPT(exc, "*")
+ sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ "exception raised by abort handler:\n");
+ sm_exc_print(exc, smioerr);
+ sm_io_flush(smioerr, SM_TIME_DEFAULT);
+ SM_END_TRY
+
+ /*
+ ** SmAbortHandler isn't supposed to return.
+ ** Since it has, let's make sure that the program is terminated.
+ */
+
+ abort();
+}
diff --git a/usr/src/cmd/sendmail/libsm/cf.c b/usr/src/cmd/sendmail/libsm/cf.c
new file mode 100644
index 0000000000..c1bf90d814
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/cf.c
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: cf.c,v 1.4 2001/02/01 02:40:21 dmoen Exp $")
+
+#include <ctype.h>
+#include <errno.h>
+
+#include <sm/cf.h>
+#include <sm/io.h>
+#include <sm/string.h>
+#include <sm/heap.h>
+
+/*
+** SM_CF_GETOPT -- look up option values in the sendmail.cf file
+**
+** Open the sendmail.cf file and parse all of the 'O' directives.
+** Each time one of the options named in the option vector optv
+** is found, store a malloced copy of the option value in optv.
+**
+** Parameters:
+** path -- pathname of sendmail.cf file
+** optc -- size of option vector
+** optv -- pointer to option vector
+**
+** Results:
+** 0 on success, or an errno value on failure.
+** An exception is raised on malloc failure.
+*/
+
+int
+sm_cf_getopt(path, optc, optv)
+ char *path;
+ int optc;
+ SM_CF_OPT_T *optv;
+{
+ SM_FILE_T *cfp;
+ char buf[2048];
+ char *p;
+ char *id;
+ char *idend;
+ char *val;
+ int i;
+
+ cfp = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, path, SM_IO_RDONLY, NULL);
+ if (cfp == NULL)
+ return errno;
+
+ while (sm_io_fgets(cfp, SM_TIME_DEFAULT, buf, sizeof(buf)) != NULL)
+ {
+ p = strchr(buf, '\n');
+ if (p != NULL)
+ *p = '\0';
+
+ if (buf[0] != 'O' || buf[1] != ' ')
+ continue;
+
+ id = &buf[2];
+ val = strchr(id, '=');
+ if (val == NULL)
+ val = idend = id + strlen(id);
+ else
+ {
+ idend = val;
+ ++val;
+ while (*val == ' ')
+ ++val;
+ while (idend > id && idend[-1] == ' ')
+ --idend;
+ *idend = '\0';
+ }
+
+ for (i = 0; i < optc; ++i)
+ {
+ if (sm_strcasecmp(optv[i].opt_name, id) == 0)
+ {
+ optv[i].opt_val = sm_strdup_x(val);
+ break;
+ }
+ }
+ }
+ if (sm_io_error(cfp))
+ {
+ int save_errno = errno;
+
+ (void) sm_io_close(cfp, SM_TIME_DEFAULT);
+ errno = save_errno;
+ return errno;
+ }
+ (void) sm_io_close(cfp, SM_TIME_DEFAULT);
+ return 0;
+}
diff --git a/usr/src/cmd/sendmail/libsm/clock.c b/usr/src/cmd/sendmail/libsm/clock.c
new file mode 100644
index 0000000000..0eeb94a196
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/clock.c
@@ -0,0 +1,642 @@
+/*
+ * Copyright (c) 1998-2004 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: clock.c,v 1.46 2004/08/03 19:57:22 ca Exp $")
+#include <unistd.h>
+#include <time.h>
+#include <errno.h>
+#if SM_CONF_SETITIMER
+# include <sys/time.h>
+#endif /* SM_CONF_SETITIMER */
+#include <sm/heap.h>
+#include <sm/debug.h>
+#include <sm/bitops.h>
+#include <sm/clock.h>
+#include "local.h"
+#if _FFR_SLEEP_USE_SELECT > 0
+# include <sys/types.h>
+#endif /* _FFR_SLEEP_USE_SELECT > 0 */
+#if defined(_FFR_MAX_SLEEP_TIME) && _FFR_MAX_SLEEP_TIME > 2
+# include <syslog.h>
+#endif /* defined(_FFR_MAX_SLEEP_TIME) && _FFR_MAX_SLEEP_TIME > 2 */
+
+#ifndef sigmask
+# define sigmask(s) (1 << ((s) - 1))
+#endif /* ! sigmask */
+
+
+/*
+** SM_SETEVENTM -- set an event to happen at a specific time in milliseconds.
+**
+** Events are stored in a sorted list for fast processing.
+** An event only applies to the process that set it.
+** Source is #ifdef'd to work with older OS's that don't have setitimer()
+** (that is, don't have a timer granularity less than 1 second).
+**
+** Parameters:
+** intvl -- interval until next event occurs (milliseconds).
+** func -- function to call on event.
+** arg -- argument to func on event.
+**
+** Returns:
+** On success returns the SM_EVENT entry created.
+** On failure returns NULL.
+**
+** Side Effects:
+** none.
+*/
+
+static SM_EVENT *volatile SmEventQueue; /* head of event queue */
+static SM_EVENT *volatile SmFreeEventList; /* list of free events */
+
+SM_EVENT *
+sm_seteventm(intvl, func, arg)
+ int intvl;
+ void (*func)__P((int));
+ int arg;
+{
+ ENTER_CRITICAL();
+ if (SmFreeEventList == NULL)
+ {
+ SmFreeEventList = (SM_EVENT *) sm_pmalloc_x(sizeof *SmFreeEventList);
+ SmFreeEventList->ev_link = NULL;
+ }
+ LEAVE_CRITICAL();
+
+ return sm_sigsafe_seteventm(intvl, func, arg);
+}
+
+/*
+** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+** DOING.
+*/
+
+SM_EVENT *
+sm_sigsafe_seteventm(intvl, func, arg)
+ int intvl;
+ void (*func)__P((int));
+ int arg;
+{
+ register SM_EVENT **evp;
+ register SM_EVENT *ev;
+#if SM_CONF_SETITIMER
+ auto struct timeval now, nowi, ival;
+ auto struct itimerval itime;
+#else /* SM_CONF_SETITIMER */
+ auto time_t now, nowi;
+#endif /* SM_CONF_SETITIMER */
+ int wasblocked;
+
+ /* negative times are not allowed */
+ if (intvl <= 0)
+ return NULL;
+
+ wasblocked = sm_blocksignal(SIGALRM);
+#if SM_CONF_SETITIMER
+ ival.tv_sec = intvl / 1000;
+ ival.tv_usec = (intvl - ival.tv_sec * 1000) * 10;
+ (void) gettimeofday(&now, NULL);
+ nowi = now;
+ timeradd(&now, &ival, &nowi);
+#else /* SM_CONF_SETITIMER */
+ now = time(NULL);
+ nowi = now + (time_t)(intvl / 1000);
+#endif /* SM_CONF_SETITIMER */
+
+ /* search event queue for correct position */
+ for (evp = (SM_EVENT **) (&SmEventQueue);
+ (ev = *evp) != NULL;
+ evp = &ev->ev_link)
+ {
+#if SM_CONF_SETITIMER
+ if (timercmp(&(ev->ev_time), &nowi, >=))
+#else /* SM_CONF_SETITIMER */
+ if (ev->ev_time >= nowi)
+#endif /* SM_CONF_SETITIMER */
+ break;
+ }
+
+ ENTER_CRITICAL();
+ if (SmFreeEventList == NULL)
+ {
+ /*
+ ** This shouldn't happen. If called from sm_seteventm(),
+ ** we have just malloced a SmFreeEventList entry. If
+ ** called from a signal handler, it should have been
+ ** from an existing event which sm_tick() just added to
+ ** SmFreeEventList.
+ */
+
+ LEAVE_CRITICAL();
+ if (wasblocked == 0)
+ (void) sm_releasesignal(SIGALRM);
+ return NULL;
+ }
+ else
+ {
+ ev = SmFreeEventList;
+ SmFreeEventList = ev->ev_link;
+ }
+ LEAVE_CRITICAL();
+
+ /* insert new event */
+ ev->ev_time = nowi;
+ ev->ev_func = func;
+ ev->ev_arg = arg;
+ ev->ev_pid = getpid();
+ ENTER_CRITICAL();
+ ev->ev_link = *evp;
+ *evp = ev;
+ LEAVE_CRITICAL();
+
+ (void) sm_signal(SIGALRM, sm_tick);
+# if SM_CONF_SETITIMER
+ timersub(&SmEventQueue->ev_time, &now, &itime.it_value);
+ itime.it_interval.tv_sec = 0;
+ itime.it_interval.tv_usec = 0;
+ if (itime.it_value.tv_sec < 0)
+ itime.it_value.tv_sec = 0;
+ if (itime.it_value.tv_sec == 0 && itime.it_value.tv_usec == 0)
+ itime.it_value.tv_usec = 1000;
+ (void) setitimer(ITIMER_REAL, &itime, NULL);
+# else /* SM_CONF_SETITIMER */
+ intvl = SmEventQueue->ev_time - now;
+ (void) alarm((unsigned) (intvl < 1 ? 1 : intvl));
+# endif /* SM_CONF_SETITIMER */
+ if (wasblocked == 0)
+ (void) sm_releasesignal(SIGALRM);
+ return ev;
+}
+/*
+** SM_CLREVENT -- remove an event from the event queue.
+**
+** Parameters:
+** ev -- pointer to event to remove.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** arranges for event ev to not happen.
+*/
+
+void
+sm_clrevent(ev)
+ register SM_EVENT *ev;
+{
+ register SM_EVENT **evp;
+ int wasblocked;
+# if SM_CONF_SETITIMER
+ struct itimerval clr;
+# endif /* SM_CONF_SETITIMER */
+
+ if (ev == NULL)
+ return;
+
+ /* find the parent event */
+ wasblocked = sm_blocksignal(SIGALRM);
+ for (evp = (SM_EVENT **) (&SmEventQueue);
+ *evp != NULL;
+ evp = &(*evp)->ev_link)
+ {
+ if (*evp == ev)
+ break;
+ }
+
+ /* now remove it */
+ if (*evp != NULL)
+ {
+ ENTER_CRITICAL();
+ *evp = ev->ev_link;
+ ev->ev_link = SmFreeEventList;
+ SmFreeEventList = ev;
+ LEAVE_CRITICAL();
+ }
+
+ /* restore clocks and pick up anything spare */
+ if (wasblocked == 0)
+ (void) sm_releasesignal(SIGALRM);
+ if (SmEventQueue != NULL)
+ (void) kill(getpid(), SIGALRM);
+ else
+ {
+ /* nothing left in event queue, no need for an alarm */
+# if SM_CONF_SETITIMER
+ clr.it_interval.tv_sec = 0;
+ clr.it_interval.tv_usec = 0;
+ clr.it_value.tv_sec = 0;
+ clr.it_value.tv_usec = 0;
+ (void) setitimer(ITIMER_REAL, &clr, NULL);
+# else /* SM_CONF_SETITIMER */
+ (void) alarm(0);
+# endif /* SM_CONF_SETITIMER */
+ }
+}
+/*
+** SM_CLEAR_EVENTS -- remove all events from the event queue.
+**
+** Parameters:
+** none.
+**
+** Returns:
+** none.
+*/
+
+void
+sm_clear_events()
+{
+ register SM_EVENT *ev;
+#if SM_CONF_SETITIMER
+ struct itimerval clr;
+#endif /* SM_CONF_SETITIMER */
+ int wasblocked;
+
+ /* nothing will be left in event queue, no need for an alarm */
+#if SM_CONF_SETITIMER
+ clr.it_interval.tv_sec = 0;
+ clr.it_interval.tv_usec = 0;
+ clr.it_value.tv_sec = 0;
+ clr.it_value.tv_usec = 0;
+ (void) setitimer(ITIMER_REAL, &clr, NULL);
+#else /* SM_CONF_SETITIMER */
+ (void) alarm(0);
+#endif /* SM_CONF_SETITIMER */
+
+ if (SmEventQueue == NULL)
+ return;
+
+ wasblocked = sm_blocksignal(SIGALRM);
+
+ /* find the end of the EventQueue */
+ for (ev = SmEventQueue; ev->ev_link != NULL; ev = ev->ev_link)
+ continue;
+
+ ENTER_CRITICAL();
+ ev->ev_link = SmFreeEventList;
+ SmFreeEventList = SmEventQueue;
+ SmEventQueue = NULL;
+ LEAVE_CRITICAL();
+
+ /* restore clocks and pick up anything spare */
+ if (wasblocked == 0)
+ (void) sm_releasesignal(SIGALRM);
+}
+/*
+** SM_TICK -- take a clock tick
+**
+** Called by the alarm clock. This routine runs events as needed.
+** Always called as a signal handler, so we assume that SIGALRM
+** has been blocked.
+**
+** Parameters:
+** One that is ignored; for compatibility with signal handlers.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** calls the next function in EventQueue.
+**
+** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+** DOING.
+*/
+
+/* ARGSUSED */
+SIGFUNC_DECL
+sm_tick(sig)
+ int sig;
+{
+ register SM_EVENT *ev;
+ pid_t mypid;
+ int save_errno = errno;
+#if SM_CONF_SETITIMER
+ struct itimerval clr;
+ struct timeval now;
+#else /* SM_CONF_SETITIMER */
+ register time_t now;
+#endif /* SM_CONF_SETITIMER */
+
+#if SM_CONF_SETITIMER
+ clr.it_interval.tv_sec = 0;
+ clr.it_interval.tv_usec = 0;
+ clr.it_value.tv_sec = 0;
+ clr.it_value.tv_usec = 0;
+ (void) setitimer(ITIMER_REAL, &clr, NULL);
+ gettimeofday(&now, NULL);
+#else /* SM_CONF_SETITIMER */
+ (void) alarm(0);
+ now = time(NULL);
+#endif /* SM_CONF_SETITIMER */
+
+ FIX_SYSV_SIGNAL(sig, sm_tick);
+ errno = save_errno;
+ CHECK_CRITICAL(sig);
+
+ mypid = getpid();
+ while (PendingSignal != 0)
+ {
+ int sigbit = 0;
+ int sig = 0;
+
+ if (bitset(PEND_SIGHUP, PendingSignal))
+ {
+ sigbit = PEND_SIGHUP;
+ sig = SIGHUP;
+ }
+ else if (bitset(PEND_SIGINT, PendingSignal))
+ {
+ sigbit = PEND_SIGINT;
+ sig = SIGINT;
+ }
+ else if (bitset(PEND_SIGTERM, PendingSignal))
+ {
+ sigbit = PEND_SIGTERM;
+ sig = SIGTERM;
+ }
+ else if (bitset(PEND_SIGUSR1, PendingSignal))
+ {
+ sigbit = PEND_SIGUSR1;
+ sig = SIGUSR1;
+ }
+ else
+ {
+ /* If we get here, we are in trouble */
+ abort();
+ }
+ PendingSignal &= ~sigbit;
+ kill(mypid, sig);
+ }
+
+#if SM_CONF_SETITIMER
+ gettimeofday(&now, NULL);
+#else /* SM_CONF_SETITIMER */
+ now = time(NULL);
+#endif /* SM_CONF_SETITIMER */
+ while ((ev = SmEventQueue) != NULL &&
+ (ev->ev_pid != mypid ||
+#if SM_CONF_SETITIMER
+ timercmp(&ev->ev_time, &now, <=)
+#else /* SM_CONF_SETITIMER */
+ ev->ev_time <= now
+#endif /* SM_CONF_SETITIMER */
+ ))
+ {
+ void (*f)__P((int));
+ int arg;
+ pid_t pid;
+
+ /* process the event on the top of the queue */
+ ev = SmEventQueue;
+ SmEventQueue = SmEventQueue->ev_link;
+
+ /* we must be careful in here because ev_func may not return */
+ f = ev->ev_func;
+ arg = ev->ev_arg;
+ pid = ev->ev_pid;
+ ENTER_CRITICAL();
+ ev->ev_link = SmFreeEventList;
+ SmFreeEventList = ev;
+ LEAVE_CRITICAL();
+ if (pid != getpid())
+ continue;
+ if (SmEventQueue != NULL)
+ {
+#if SM_CONF_SETITIMER
+ if (timercmp(&SmEventQueue->ev_time, &now, >))
+ {
+ timersub(&SmEventQueue->ev_time, &now,
+ &clr.it_value);
+ clr.it_interval.tv_sec = 0;
+ clr.it_interval.tv_usec = 0;
+ if (clr.it_value.tv_sec < 0)
+ clr.it_value.tv_sec = 0;
+ if (clr.it_value.tv_sec == 0 &&
+ clr.it_value.tv_usec == 0)
+ clr.it_value.tv_usec = 1000;
+ (void) setitimer(ITIMER_REAL, &clr, NULL);
+ }
+ else
+ {
+ clr.it_interval.tv_sec = 0;
+ clr.it_interval.tv_usec = 0;
+ clr.it_value.tv_sec = 3;
+ clr.it_value.tv_usec = 0;
+ (void) setitimer(ITIMER_REAL, &clr, NULL);
+ }
+#else /* SM_CONF_SETITIMER */
+ if (SmEventQueue->ev_time > now)
+ (void) alarm((unsigned) (SmEventQueue->ev_time
+ - now));
+ else
+ (void) alarm(3);
+#endif /* SM_CONF_SETITIMER */
+ }
+
+ /* call ev_func */
+ errno = save_errno;
+ (*f)(arg);
+#if SM_CONF_SETITIMER
+ clr.it_interval.tv_sec = 0;
+ clr.it_interval.tv_usec = 0;
+ clr.it_value.tv_sec = 0;
+ clr.it_value.tv_usec = 0;
+ (void) setitimer(ITIMER_REAL, &clr, NULL);
+ gettimeofday(&now, NULL);
+#else /* SM_CONF_SETITIMER */
+ (void) alarm(0);
+ now = time(NULL);
+#endif /* SM_CONF_SETITIMER */
+ }
+ if (SmEventQueue != NULL)
+ {
+#if SM_CONF_SETITIMER
+ timersub(&SmEventQueue->ev_time, &now, &clr.it_value);
+ clr.it_interval.tv_sec = 0;
+ clr.it_interval.tv_usec = 0;
+ if (clr.it_value.tv_sec < 0)
+ clr.it_value.tv_sec = 0;
+ if (clr.it_value.tv_sec == 0 && clr.it_value.tv_usec == 0)
+ clr.it_value.tv_usec = 1000;
+ (void) setitimer(ITIMER_REAL, &clr, NULL);
+#else /* SM_CONF_SETITIMER */
+ (void) alarm((unsigned) (SmEventQueue->ev_time - now));
+#endif /* SM_CONF_SETITIMER */
+ }
+ errno = save_errno;
+ return SIGFUNC_RETURN;
+}
+/*
+** SLEEP -- a version of sleep that works with this stuff
+**
+** Because Unix sleep uses the alarm facility, I must reimplement
+** it here.
+**
+** Parameters:
+** intvl -- time to sleep.
+**
+** Returns:
+** zero.
+**
+** Side Effects:
+** waits for intvl time. However, other events can
+** be run during that interval.
+*/
+
+
+# if !HAVE_NANOSLEEP
+static void sm_endsleep __P((int));
+static bool volatile SmSleepDone;
+# endif /* !HAVE_NANOSLEEP */
+
+#ifndef SLEEP_T
+# define SLEEP_T unsigned int
+#endif /* ! SLEEP_T */
+
+SLEEP_T
+sleep(intvl)
+ unsigned int intvl;
+{
+#if HAVE_NANOSLEEP
+ struct timespec rqtp;
+
+ if (intvl == 0)
+ return (SLEEP_T) 0;
+ rqtp.tv_sec = intvl;
+ rqtp.tv_nsec = 0;
+ nanosleep(&rqtp, NULL);
+ return (SLEEP_T) 0;
+#else /* HAVE_NANOSLEEP */
+ int was_held;
+ SM_EVENT *ev;
+#if _FFR_SLEEP_USE_SELECT > 0
+ int r;
+# if _FFR_SLEEP_USE_SELECT > 0
+ struct timeval sm_io_to;
+# endif /* _FFR_SLEEP_USE_SELECT > 0 */
+#endif /* _FFR_SLEEP_USE_SELECT > 0 */
+#if SM_CONF_SETITIMER
+ struct timeval now, begin, diff;
+# if _FFR_SLEEP_USE_SELECT > 0
+ struct timeval slpv;
+# endif /* _FFR_SLEEP_USE_SELECT > 0 */
+#else /* SM_CONF_SETITIMER */
+ time_t begin, now;
+#endif /* SM_CONF_SETITIMER */
+
+ if (intvl == 0)
+ return (SLEEP_T) 0;
+#if defined(_FFR_MAX_SLEEP_TIME) && _FFR_MAX_SLEEP_TIME > 2
+ if (intvl > _FFR_MAX_SLEEP_TIME)
+ {
+ syslog(LOG_ERR, "sleep: interval=%u exceeds max value %d",
+ intvl, _FFR_MAX_SLEEP_TIME);
+# if 0
+ SM_ASSERT(intvl < (unsigned int) INT_MAX);
+# endif /* 0 */
+ intvl = _FFR_MAX_SLEEP_TIME;
+ }
+#endif /* defined(_FFR_MAX_SLEEP_TIME) && _FFR_MAX_SLEEP_TIME > 2 */
+ SmSleepDone = false;
+
+#if SM_CONF_SETITIMER
+# if _FFR_SLEEP_USE_SELECT > 0
+ slpv.tv_sec = intvl;
+ slpv.tv_usec = 0;
+# endif /* _FFR_SLEEP_USE_SELECT > 0 */
+ (void) gettimeofday(&now, NULL);
+ begin = now;
+#else /* SM_CONF_SETITIMER */
+ now = begin = time(NULL);
+#endif /* SM_CONF_SETITIMER */
+
+ ev = sm_setevent((time_t) intvl, sm_endsleep, 0);
+ if (ev == NULL)
+ {
+ /* COMPLAIN */
+#if 0
+ syslog(LOG_ERR, "sleep: sm_setevent(%u) failed", intvl);
+#endif /* 0 */
+ SmSleepDone = true;
+ }
+ was_held = sm_releasesignal(SIGALRM);
+
+ while (!SmSleepDone)
+ {
+#if SM_CONF_SETITIMER
+ (void) gettimeofday(&now, NULL);
+ timersub(&now, &begin, &diff);
+ if (diff.tv_sec < 0 ||
+ (diff.tv_sec == 0 && diff.tv_usec == 0))
+ break;
+# if _FFR_SLEEP_USE_SELECT > 0
+ timersub(&slpv, &diff, &sm_io_to);
+# endif /* _FFR_SLEEP_USE_SELECT > 0 */
+#else /* SM_CONF_SETITIMER */
+ now = time(NULL);
+
+ /*
+ ** Check whether time expired before signal is released.
+ ** Due to the granularity of time() add 1 to be on the
+ ** safe side.
+ */
+
+ if (!(begin + (time_t) intvl + 1 > now))
+ break;
+# if _FFR_SLEEP_USE_SELECT > 0
+ sm_io_to.tv_sec = intvl - (now - begin);
+ if (sm_io_to.tv_sec <= 0)
+ sm_io_to.tv_sec = 1;
+ sm_io_to.tv_usec = 0;
+# endif /* _FFR_SLEEP_USE_SELECT > 0 */
+#endif /* SM_CONF_SETITIMER */
+#if _FFR_SLEEP_USE_SELECT > 0
+ if (intvl <= _FFR_SLEEP_USE_SELECT)
+ {
+ r = select(0, NULL, NULL, NULL, &sm_io_to);
+ if (r == 0)
+ break;
+ }
+ else
+#endif /* _FFR_SLEEP_USE_SELECT > 0 */
+ (void) pause();
+ }
+
+ /* if out of the loop without the event being triggered remove it */
+ if (!SmSleepDone)
+ sm_clrevent(ev);
+ if (was_held > 0)
+ (void) sm_blocksignal(SIGALRM);
+ return (SLEEP_T) 0;
+#endif /* HAVE_NANOSLEEP */
+}
+
+#if !HAVE_NANOSLEEP
+static void
+sm_endsleep(ignore)
+ int ignore;
+{
+ /*
+ ** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+ ** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+ ** DOING.
+ */
+
+ SmSleepDone = true;
+}
+#endif /* !HAVE_NANOSLEEP */
+
diff --git a/usr/src/cmd/sendmail/libsm/clrerr.c b/usr/src/cmd/sendmail/libsm/clrerr.c
new file mode 100644
index 0000000000..185cd415ed
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/clrerr.c
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: clrerr.c,v 1.11 2001/01/28 00:29:34 ca Exp $")
+#include <sm/io.h>
+#include <sm/assert.h>
+#include "local.h"
+
+/*
+** SM_IO_CLEARERR -- public function to clear a file pointer's error status
+**
+** Parameters:
+** fp -- the file pointer
+**
+** Returns:
+** nothing.
+*/
+#undef sm_io_clearerr
+
+void
+sm_io_clearerr(fp)
+ SM_FILE_T *fp;
+{
+ SM_REQUIRE_ISA(fp, SmFileMagic);
+
+ sm_clearerr(fp);
+}
diff --git a/usr/src/cmd/sendmail/libsm/config.c b/usr/src/cmd/sendmail/libsm/config.c
new file mode 100644
index 0000000000..b6ce966b3b
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/config.c
@@ -0,0 +1,253 @@
+/*
+ * Copyright (c) 2000-2003 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: config.c,v 1.30 2003/12/10 03:19:07 gshapiro Exp $")
+
+#include <stdlib.h>
+#include <sm/heap.h>
+#include <sm/string.h>
+#include <sm/conf.h>
+
+/*
+** PUTENV -- emulation of putenv() in terms of setenv()
+**
+** Not needed on Posix-compliant systems.
+** This doesn't have full Posix semantics, but it's good enough
+** for sendmail.
+**
+** Parameter:
+** env -- the environment to put.
+**
+** Returns:
+** 0 on success, < 0 on failure.
+*/
+
+#if NEEDPUTENV
+
+# if NEEDPUTENV == 2 /* no setenv(3) call available */
+
+int
+putenv(str)
+ char *str;
+{
+ char **current;
+ int matchlen, envlen = 0;
+ char *tmp;
+ char **newenv;
+ static bool first = true;
+ extern char **environ;
+
+ /*
+ ** find out how much of str to match when searching
+ ** for a string to replace.
+ */
+
+ if ((tmp = strchr(str, '=')) == NULL || tmp == str)
+ matchlen = strlen(str);
+ else
+ matchlen = (int) (tmp - str);
+ ++matchlen;
+
+ /*
+ ** Search for an existing string in the environment and find the
+ ** length of environ. If found, replace and exit.
+ */
+
+ for (current = environ; *current != NULL; current++)
+ {
+ ++envlen;
+
+ if (strncmp(str, *current, matchlen) == 0)
+ {
+ /* found it, now insert the new version */
+ *current = (char *) str;
+ return 0;
+ }
+ }
+
+ /*
+ ** There wasn't already a slot so add space for a new slot.
+ ** If this is our first time through, use malloc(), else realloc().
+ */
+
+ if (first)
+ {
+ newenv = (char **) sm_malloc(sizeof(char *) * (envlen + 2));
+ if (newenv == NULL)
+ return -1;
+
+ first = false;
+ (void) memcpy(newenv, environ, sizeof(char *) * envlen);
+ }
+ else
+ {
+ newenv = (char **) sm_realloc((char *) environ,
+ sizeof(char *) * (envlen + 2));
+ if (newenv == NULL)
+ return -1;
+ }
+
+ /* actually add in the new entry */
+ environ = newenv;
+ environ[envlen] = (char *) str;
+ environ[envlen + 1] = NULL;
+
+ return 0;
+}
+
+# else /* NEEDPUTENV == 2 */
+
+int
+putenv(env)
+ char *env;
+{
+ char *p;
+ int l;
+ char nbuf[100];
+
+ p = strchr(env, '=');
+ if (p == NULL)
+ return 0;
+ l = p - env;
+ if (l > sizeof nbuf - 1)
+ l = sizeof nbuf - 1;
+ memmove(nbuf, env, l);
+ nbuf[l] = '\0';
+ return setenv(nbuf, ++p, 1);
+}
+
+# endif /* NEEDPUTENV == 2 */
+#endif /* NEEDPUTENV */
+/*
+** UNSETENV -- remove a variable from the environment
+**
+** Not needed on newer systems.
+**
+** Parameters:
+** name -- the string name of the environment variable to be
+** deleted from the current environment.
+**
+** Returns:
+** none.
+**
+** Globals:
+** environ -- a pointer to the current environment.
+**
+** Side Effects:
+** Modifies environ.
+*/
+
+#if !HASUNSETENV
+
+void
+unsetenv(name)
+ char *name;
+{
+ extern char **environ;
+ register char **pp;
+ int len = strlen(name);
+
+ for (pp = environ; *pp != NULL; pp++)
+ {
+ if (strncmp(name, *pp, len) == 0 &&
+ ((*pp)[len] == '=' || (*pp)[len] == '\0'))
+ break;
+ }
+
+ for (; *pp != NULL; pp++)
+ *pp = pp[1];
+}
+
+#endif /* !HASUNSETENV */
+
+char *SmCompileOptions[] =
+{
+#if SM_CONF_BROKEN_STRTOD
+ "SM_CONF_BROKEN_STRTOD",
+#endif /* SM_CONF_BROKEN_STRTOD */
+#if SM_CONF_GETOPT
+ "SM_CONF_GETOPT",
+#endif /* SM_CONF_GETOPT */
+#if SM_CONF_LDAP_INITIALIZE
+ "SM_CONF_LDAP_INITIALIZE",
+#endif /* SM_CONF_LDAP_INITIALIZE */
+#if SM_CONF_LDAP_MEMFREE
+ "SM_CONF_LDAP_MEMFREE",
+#endif /* SM_CONF_LDAP_MEMFREE */
+#if SM_CONF_LONGLONG
+ "SM_CONF_LONGLONG",
+#endif /* SM_CONF_LONGLONG */
+#if SM_CONF_MEMCHR
+ "SM_CONF_MEMCHR",
+#endif /* SM_CONF_MEMCHR */
+#if SM_CONF_MSG
+ "SM_CONF_MSG",
+#endif /* SM_CONF_MSG */
+#if SM_CONF_QUAD_T
+ "SM_CONF_QUAD_T",
+#endif /* SM_CONF_QUAD_T */
+#if SM_CONF_SEM
+ "SM_CONF_SEM",
+#endif /* SM_CONF_SEM */
+#if SM_CONF_SETITIMER
+ "SM_CONF_SETITIMER",
+#endif /* SM_CONF_SETITIMER */
+#if SM_CONF_SIGSETJMP
+ "SM_CONF_SIGSETJMP",
+#endif /* SM_CONF_SIGSETJMP */
+#if SM_CONF_SHM
+ "SM_CONF_SHM",
+#endif /* SM_CONF_SHM */
+#if SM_CONF_SHM_DELAY
+ "SM_CONF_SHM_DELAY",
+#endif /* SM_CONF_SHM_DELAY */
+#if SM_CONF_SSIZE_T
+ "SM_CONF_SSIZE_T",
+#endif /* SM_CONF_SSIZE_T */
+#if SM_CONF_STDBOOL_H
+ "SM_CONF_STDBOOL_H",
+#endif /* SM_CONF_STDBOOL_H */
+#if SM_CONF_STDDEF_H
+ "SM_CONF_STDDEF_H",
+#endif /* SM_CONF_STDDEF_H */
+
+#if 0
+/* XXX this is always enabled (for now) */
+#if SM_CONF_STRL
+ "SM_CONF_STRL",
+#endif /* SM_CONF_STRL */
+#endif /* 0 */
+
+#if SM_CONF_SYS_CDEFS_H
+ "SM_CONF_SYS_CDEFS_H",
+#endif /* SM_CONF_SYS_CDEFS_H */
+#if SM_CONF_SYSEXITS_H
+ "SM_CONF_SYSEXITS_H",
+#endif /* SM_CONF_SYSEXITS_H */
+#if SM_CONF_UID_GID
+ "SM_CONF_UID_GID",
+#endif /* SM_CONF_UID_GID */
+#if DO_NOT_USE_STRCPY
+ "DO_NOT_USE_STRCPY",
+#endif /* DO_NOT_USE_STRCPY */
+#if SM_HEAP_CHECK
+ "SM_HEAP_CHECK",
+#endif /* SM_HEAP_CHECK */
+#if defined(SM_OS_NAME) && defined(__STDC__)
+ "SM_OS=sm_os_" SM_OS_NAME,
+#endif /* defined(SM_OS_NAME) && defined(__STDC__) */
+#if SM_VA_STD
+ "SM_VA_STD",
+#endif /* SM_VA_STD */
+ NULL
+};
diff --git a/usr/src/cmd/sendmail/libsm/debug.c b/usr/src/cmd/sendmail/libsm/debug.c
new file mode 100644
index 0000000000..1bc7e3ad2d
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/debug.c
@@ -0,0 +1,396 @@
+/*
+ * Copyright (c) 2000, 2001, 2003, 2004 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: debug.c,v 1.30 2004/08/03 20:10:26 ca Exp $")
+
+/*
+** libsm debugging and tracing
+** For documentation, see debug.html.
+*/
+
+#include <ctype.h>
+#include <stdlib.h>
+#include <setjmp.h>
+#include <sm/io.h>
+#include <sm/assert.h>
+#include <sm/conf.h>
+#include <sm/debug.h>
+#include <sm/string.h>
+#include <sm/varargs.h>
+#include <sm/heap.h>
+
+static void sm_debug_reset __P((void));
+static const char *parse_named_setting_x __P((const char *));
+
+/*
+** Abstractions for printing trace messages.
+*/
+
+/*
+** The output file to which trace output is directed.
+** There is a controversy over whether this variable
+** should be process global or thread local.
+** To make the interface more abstract, we've hidden the
+** variable behind access functions.
+*/
+
+static SM_FILE_T *SmDebugOutput = smioout;
+
+/*
+** SM_DEBUG_FILE -- Returns current debug file pointer.
+**
+** Parameters:
+** none.
+**
+** Returns:
+** current debug file pointer.
+*/
+
+SM_FILE_T *
+sm_debug_file()
+{
+ return SmDebugOutput;
+}
+
+/*
+** SM_DEBUG_SETFILE -- Sets debug file pointer.
+**
+** Parameters:
+** fp -- new debug file pointer.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Sets SmDebugOutput.
+*/
+
+void
+sm_debug_setfile(fp)
+ SM_FILE_T *fp;
+{
+ SmDebugOutput = fp;
+}
+
+/*
+** SM_DEBUG_CLOSE -- Close debug file pointer.
+**
+** Parameters:
+** none.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Closes SmDebugOutput.
+*/
+
+void
+sm_debug_close()
+{
+ if (SmDebugOutput != NULL && SmDebugOutput != smioout)
+ {
+ sm_io_close(SmDebugOutput, SM_TIME_DEFAULT);
+ SmDebugOutput = NULL;
+ }
+}
+
+/*
+** SM_DPRINTF -- printf() for debug output.
+**
+** Parameters:
+** fmt -- format for printf()
+**
+** Returns:
+** none.
+*/
+
+void
+#if SM_VA_STD
+sm_dprintf(char *fmt, ...)
+#else /* SM_VA_STD */
+sm_dprintf(fmt, va_alist)
+ char *fmt;
+ va_dcl
+#endif /* SM_VA_STD */
+{
+ SM_VA_LOCAL_DECL
+
+ if (SmDebugOutput == NULL)
+ return;
+ SM_VA_START(ap, fmt);
+ sm_io_vfprintf(SmDebugOutput, SmDebugOutput->f_timeout, fmt, ap);
+ SM_VA_END(ap);
+}
+
+/*
+** SM_DFLUSH -- Flush debug output.
+**
+** Parameters:
+** none.
+**
+** Returns:
+** none.
+*/
+
+void
+sm_dflush()
+{
+ sm_io_flush(SmDebugOutput, SM_TIME_DEFAULT);
+}
+
+/*
+** This is the internal database of debug settings.
+** The semantics of looking up a setting in the settings database
+** are that the *last* setting specified in a -d option on the sendmail
+** command line that matches a given SM_DEBUG structure is the one that is
+** used. That is necessary to conform to the existing semantics of
+** the sendmail -d option. We store the settings as a linked list in
+** reverse order, so when we do a lookup, we take the *first* entry
+** that matches.
+*/
+
+typedef struct sm_debug_setting SM_DEBUG_SETTING_T;
+struct sm_debug_setting
+{
+ const char *ds_pattern;
+ unsigned int ds_level;
+ SM_DEBUG_SETTING_T *ds_next;
+};
+SM_DEBUG_SETTING_T *SmDebugSettings = NULL;
+
+/*
+** We keep a linked list of SM_DEBUG structures that have been initialized,
+** for use by sm_debug_reset.
+*/
+
+SM_DEBUG_T *SmDebugInitialized = NULL;
+
+const char SmDebugMagic[] = "sm_debug";
+
+/*
+** SM_DEBUG_RESET -- Reset SM_DEBUG structures.
+**
+** Reset all SM_DEBUG structures back to the uninitialized state.
+** This is used by sm_debug_addsetting to ensure that references to
+** SM_DEBUG structures that occur before sendmail processes its -d flags
+** do not cause those structures to be permanently forced to level 0.
+**
+** Parameters:
+** none.
+**
+** Returns:
+** none.
+*/
+
+static void
+sm_debug_reset()
+{
+ SM_DEBUG_T *debug;
+
+ for (debug = SmDebugInitialized;
+ debug != NULL;
+ debug = debug->debug_next)
+ {
+ debug->debug_level = SM_DEBUG_UNKNOWN;
+ }
+ SmDebugInitialized = NULL;
+}
+
+/*
+** SM_DEBUG_ADDSETTING_X -- add an entry to the database of debug settings
+**
+** Parameters:
+** pattern -- a shell-style glob pattern (see sm_match).
+** WARNING: the storage for 'pattern' will be owned by
+** the debug package, so it should either be a string
+** literal or the result of a call to sm_strdup_x.
+** level -- a non-negative integer.
+**
+** Returns:
+** none.
+**
+** Exceptions:
+** F:sm_heap -- out of memory
+*/
+
+void
+sm_debug_addsetting_x(pattern, level)
+ const char *pattern;
+ int level;
+{
+ SM_DEBUG_SETTING_T *s;
+
+ SM_REQUIRE(pattern != NULL);
+ SM_REQUIRE(level >= 0);
+ s = sm_malloc_x(sizeof(SM_DEBUG_SETTING_T));
+ s->ds_pattern = pattern;
+ s->ds_level = (unsigned int) level;
+ s->ds_next = SmDebugSettings;
+ SmDebugSettings = s;
+ sm_debug_reset();
+}
+
+/*
+** PARSE_NAMED_SETTING_X -- process a symbolic debug setting
+**
+** Parameters:
+** s -- Points to a non-empty \0 or , terminated string,
+** of which the initial character is not a digit.
+**
+** Returns:
+** pointer to terminating \0 or , character.
+**
+** Exceptions:
+** F:sm.heap -- out of memory.
+**
+** Side Effects:
+** adds the setting to the database.
+*/
+
+static const char *
+parse_named_setting_x(s)
+ const char *s;
+{
+ const char *pat, *endpat;
+ int level;
+
+ pat = s;
+ while (*s != '\0' && *s != ',' && *s != '.')
+ ++s;
+ endpat = s;
+ if (*s == '.')
+ {
+ ++s;
+ level = 0;
+ while (isascii(*s) && isdigit(*s))
+ {
+ level = level * 10 + (*s - '0');
+ ++s;
+ }
+ if (level < 0)
+ level = 0;
+ }
+ else
+ level = 1;
+
+ sm_debug_addsetting_x(sm_strndup_x(pat, endpat - pat), level);
+
+ /* skip trailing junk */
+ while (*s != '\0' && *s != ',')
+ ++s;
+
+ return s;
+}
+
+/*
+** SM_DEBUG_ADDSETTINGS_X -- process a list of debug options
+**
+** Parameters:
+** s -- a list of debug settings, eg the argument to the
+** sendmail -d option.
+**
+** The syntax of the string s is as follows:
+**
+** <settings> ::= <setting> | <settings> "," <setting>
+** <setting> ::= <categories> | <categories> "." <level>
+** <categories> ::= [a-zA-Z_*?][a-zA-Z0-9_*?]*
+**
+** However, note that we skip over anything we don't
+** understand, rather than report an error.
+**
+** Returns:
+** none.
+**
+** Exceptions:
+** F:sm.heap -- out of memory
+**
+** Side Effects:
+** updates the database of debug settings.
+*/
+
+void
+sm_debug_addsettings_x(s)
+ const char *s;
+{
+ for (;;)
+ {
+ if (*s == '\0')
+ return;
+ if (*s == ',')
+ {
+ ++s;
+ continue;
+ }
+ s = parse_named_setting_x(s);
+ }
+}
+
+/*
+** SM_DEBUG_LOADLEVEL -- Get activation level of the specified debug object.
+**
+** Parameters:
+** debug -- debug object.
+**
+** Returns:
+** Activation level of the specified debug object.
+**
+** Side Effects:
+** Ensures that the debug object is initialized.
+*/
+
+int
+sm_debug_loadlevel(debug)
+ SM_DEBUG_T *debug;
+{
+ if (debug->debug_level == SM_DEBUG_UNKNOWN)
+ {
+ SM_DEBUG_SETTING_T *s;
+
+ for (s = SmDebugSettings; s != NULL; s = s->ds_next)
+ {
+ if (sm_match(debug->debug_name, s->ds_pattern))
+ {
+ debug->debug_level = s->ds_level;
+ goto initialized;
+ }
+ }
+ debug->debug_level = 0;
+ initialized:
+ debug->debug_next = SmDebugInitialized;
+ SmDebugInitialized = debug;
+ }
+ return (int) debug->debug_level;
+}
+
+/*
+** SM_DEBUG_LOADACTIVE -- Activation level reached?
+**
+** Parameters:
+** debug -- debug object.
+** level -- level to check.
+**
+** Returns:
+** true iff the activation level of the specified debug
+** object >= level.
+**
+** Side Effects:
+** Ensures that the debug object is initialized.
+*/
+
+bool
+sm_debug_loadactive(debug, level)
+ SM_DEBUG_T *debug;
+ int level;
+{
+ return sm_debug_loadlevel(debug) >= level;
+}
diff --git a/usr/src/cmd/sendmail/libsm/errstring.c b/usr/src/cmd/sendmail/libsm/errstring.c
new file mode 100644
index 0000000000..ef2fb3749b
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/errstring.c
@@ -0,0 +1,285 @@
+/*
+ * Copyright (c) 2001, 2003 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: errstring.c,v 1.19 2003/12/10 03:53:05 gshapiro Exp $")
+
+#include <errno.h>
+#include <stdio.h> /* sys_errlist, on some platforms */
+
+#include <sm/io.h> /* sm_snprintf */
+#include <sm/string.h>
+#include <sm/errstring.h>
+
+#if NAMED_BIND
+# include <netdb.h>
+#endif
+
+#if LDAPMAP
+# include <lber.h>
+# include <ldap.h> /* for LDAP error codes */
+#endif /* LDAPMAP */
+
+/*
+** Notice: this file is used by libmilter. Please try to avoid
+** using libsm specific functions.
+*/
+
+/*
+** SM_ERRSTRING -- return string description of error code
+**
+** Parameters:
+** errnum -- the error number to translate
+**
+** Returns:
+** A string description of errnum.
+**
+** Note: this may point to a local (static) buffer.
+*/
+
+const char *
+sm_errstring(errnum)
+ int errnum;
+{
+ char *ret;
+
+
+ switch (errnum)
+ {
+ case EPERM:
+ /* SunOS gives "Not owner" -- this is the POSIX message */
+ return "Operation not permitted";
+
+ /*
+ ** Error messages used internally in sendmail.
+ */
+
+ case E_SM_OPENTIMEOUT:
+ return "Timeout on file open";
+
+ case E_SM_NOSLINK:
+ return "Symbolic links not allowed";
+
+ case E_SM_NOHLINK:
+ return "Hard links not allowed";
+
+ case E_SM_REGONLY:
+ return "Regular files only";
+
+ case E_SM_ISEXEC:
+ return "Executable files not allowed";
+
+ case E_SM_WWDIR:
+ return "World writable directory";
+
+ case E_SM_GWDIR:
+ return "Group writable directory";
+
+ case E_SM_FILECHANGE:
+ return "File changed after open";
+
+ case E_SM_WWFILE:
+ return "World writable file";
+
+ case E_SM_GWFILE:
+ return "Group writable file";
+
+ case E_SM_GRFILE:
+ return "Group readable file";
+
+ case E_SM_WRFILE:
+ return "World readable file";
+
+ /*
+ ** DNS error messages.
+ */
+
+#if NAMED_BIND
+ case HOST_NOT_FOUND + E_DNSBASE:
+ return "Name server: host not found";
+
+ case TRY_AGAIN + E_DNSBASE:
+ return "Name server: host name lookup failure";
+
+ case NO_RECOVERY + E_DNSBASE:
+ return "Name server: non-recoverable error";
+
+ case NO_DATA + E_DNSBASE:
+ return "Name server: no data known";
+#endif /* NAMED_BIND */
+
+ /*
+ ** libsmdb error messages.
+ */
+
+ case SMDBE_MALLOC:
+ return "Memory allocation failed";
+
+ case SMDBE_GDBM_IS_BAD:
+ return "GDBM is not supported";
+
+ case SMDBE_UNSUPPORTED:
+ return "Unsupported action";
+
+ case SMDBE_DUPLICATE:
+ return "Key already exists";
+
+ case SMDBE_BAD_OPEN:
+ return "Database open failed";
+
+ case SMDBE_NOT_FOUND:
+ return "Key not found";
+
+ case SMDBE_UNKNOWN_DB_TYPE:
+ return "Unknown database type";
+
+ case SMDBE_UNSUPPORTED_DB_TYPE:
+ return "Support for database type not compiled into this program";
+
+ case SMDBE_INCOMPLETE:
+ return "DB sync did not finish";
+
+ case SMDBE_KEY_EMPTY:
+ return "Key is empty";
+
+ case SMDBE_KEY_EXIST:
+ return "Key already exists";
+
+ case SMDBE_LOCK_DEADLOCK:
+ return "Locker killed to resolve deadlock";
+
+ case SMDBE_LOCK_NOT_GRANTED:
+ return "Lock unavailable";
+
+ case SMDBE_LOCK_NOT_HELD:
+ return "Lock not held by locker";
+
+ case SMDBE_RUN_RECOVERY:
+ return "Database panic, run recovery";
+
+ case SMDBE_IO_ERROR:
+ return "I/O error";
+
+ case SMDBE_READ_ONLY:
+ return "Database opened read-only";
+
+ case SMDBE_DB_NAME_TOO_LONG:
+ return "Name too long";
+
+ case SMDBE_INVALID_PARAMETER:
+ return "Invalid parameter";
+
+ case SMDBE_ONLY_SUPPORTS_ONE_CURSOR:
+ return "Only one cursor allowed";
+
+ case SMDBE_NOT_A_VALID_CURSOR:
+ return "Invalid cursor";
+
+ case SMDBE_OLD_VERSION:
+ return "Berkeley DB file is an old version, recreate it";
+
+ case SMDBE_VERSION_MISMATCH:
+ return "Berkeley DB version mismatch between include file and library";
+
+#if LDAPMAP
+
+ /*
+ ** LDAP URL error messages.
+ */
+
+ /* OpenLDAP errors */
+# ifdef LDAP_URL_ERR_MEM
+ case E_LDAPURLBASE + LDAP_URL_ERR_MEM:
+ return "LDAP URL can't allocate memory space";
+# endif /* LDAP_URL_ERR_MEM */
+
+# ifdef LDAP_URL_ERR_PARAM
+ case E_LDAPURLBASE + LDAP_URL_ERR_PARAM:
+ return "LDAP URL parameter is bad";
+# endif /* LDAP_URL_ERR_PARAM */
+
+# ifdef LDAP_URL_ERR_BADSCHEME
+ case E_LDAPURLBASE + LDAP_URL_ERR_BADSCHEME:
+ return "LDAP URL doesn't begin with \"ldap[si]://\"";
+# endif /* LDAP_URL_ERR_BADSCHEME */
+
+# ifdef LDAP_URL_ERR_BADENCLOSURE
+ case E_LDAPURLBASE + LDAP_URL_ERR_BADENCLOSURE:
+ return "LDAP URL is missing trailing \">\"";
+# endif /* LDAP_URL_ERR_BADENCLOSURE */
+
+# ifdef LDAP_URL_ERR_BADURL
+ case E_LDAPURLBASE + LDAP_URL_ERR_BADURL:
+ return "LDAP URL is bad";
+# endif /* LDAP_URL_ERR_BADURL */
+
+# ifdef LDAP_URL_ERR_BADHOST
+ case E_LDAPURLBASE + LDAP_URL_ERR_BADHOST:
+ return "LDAP URL host port is bad";
+# endif /* LDAP_URL_ERR_BADHOST */
+
+# ifdef LDAP_URL_ERR_BADATTRS
+ case E_LDAPURLBASE + LDAP_URL_ERR_BADATTRS:
+ return "LDAP URL bad (or missing) attributes";
+# endif /* LDAP_URL_ERR_BADATTRS */
+
+# ifdef LDAP_URL_ERR_BADSCOPE
+ case E_LDAPURLBASE + LDAP_URL_ERR_BADSCOPE:
+ return "LDAP URL scope string is invalid (or missing)";
+# endif /* LDAP_URL_ERR_BADSCOPE */
+
+# ifdef LDAP_URL_ERR_BADFILTER
+ case E_LDAPURLBASE + LDAP_URL_ERR_BADFILTER:
+ return "LDAP URL bad or missing filter";
+# endif /* LDAP_URL_ERR_BADFILTER */
+
+# ifdef LDAP_URL_ERR_BADEXTS
+ case E_LDAPURLBASE + LDAP_URL_ERR_BADEXTS:
+ return "LDAP URL bad or missing extensions";
+# endif /* LDAP_URL_ERR_BADEXTS */
+
+ /* Sun LDAP errors */
+# ifdef LDAP_URL_ERR_NOTLDAP
+ case E_LDAPURLBASE + LDAP_URL_ERR_NOTLDAP:
+ return "LDAP URL doesn't begin with \"ldap://\"";
+# endif /* LDAP_URL_ERR_NOTLDAP */
+
+# ifdef LDAP_URL_ERR_NODN
+ case E_LDAPURLBASE + LDAP_URL_ERR_NODN:
+ return "LDAP URL has no DN (required)";
+# endif /* LDAP_URL_ERR_NODN */
+
+#endif /* LDAPMAP */
+ }
+
+#if LDAPMAP
+
+ /*
+ ** LDAP error messages.
+ */
+
+ if (errnum >= E_LDAPBASE)
+ return ldap_err2string(errnum - E_LDAPBASE);
+#endif /* LDAPMAP */
+
+ ret = strerror(errnum);
+ if (ret == NULL)
+ {
+ static char buf[30];
+
+ (void) sm_snprintf(buf, sizeof buf, "Error %d", errnum);
+ return buf;
+ }
+ return ret;
+}
diff --git a/usr/src/cmd/sendmail/libsm/exc.c b/usr/src/cmd/sendmail/libsm/exc.c
new file mode 100644
index 0000000000..42e4da7924
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/exc.c
@@ -0,0 +1,671 @@
+/*
+ * Copyright (c) 2000-2002 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: exc.c,v 1.47.2.1 2003/12/05 22:44:17 ca Exp $")
+
+/*
+** exception handling
+** For documentation, see exc.html
+*/
+
+#include <ctype.h>
+#include <string.h>
+
+#include <sm/errstring.h>
+#include <sm/exc.h>
+#include <sm/heap.h>
+#include <sm/string.h>
+#include <sm/varargs.h>
+#include <sm/io.h>
+
+const char SmExcMagic[] = "sm_exc";
+const char SmExcTypeMagic[] = "sm_exc_type";
+
+/*
+** SM_ETYPE_PRINTF -- printf for exception types.
+**
+** Parameters:
+** exc -- exception.
+** stream -- file for output.
+**
+** Returns:
+** none.
+*/
+
+/*
+** A simple formatted print function that can be used as the print function
+** by most exception types. It prints the printcontext string, interpreting
+** occurrences of %0 through %9 as references to the argument vector.
+** If exception argument 3 is an int or long, then %3 will print the
+** argument in decimal, and %o3 or %x3 will print it in octal or hex.
+*/
+
+void
+sm_etype_printf(exc, stream)
+ SM_EXC_T *exc;
+ SM_FILE_T *stream;
+{
+ size_t n = strlen(exc->exc_type->etype_argformat);
+ const char *p, *s;
+ char format;
+
+ for (p = exc->exc_type->etype_printcontext; *p != '\0'; ++p)
+ {
+ if (*p != '%')
+ {
+ (void) sm_io_putc(stream, SM_TIME_DEFAULT, *p);
+ continue;
+ }
+ ++p;
+ if (*p == '\0')
+ {
+ (void) sm_io_putc(stream, SM_TIME_DEFAULT, '%');
+ break;
+ }
+ if (*p == '%')
+ {
+ (void) sm_io_putc(stream, SM_TIME_DEFAULT, '%');
+ continue;
+ }
+ format = '\0';
+ if (isalpha(*p))
+ {
+ format = *p++;
+ if (*p == '\0')
+ {
+ (void) sm_io_putc(stream, SM_TIME_DEFAULT, '%');
+ (void) sm_io_putc(stream, SM_TIME_DEFAULT,
+ format);
+ break;
+ }
+ }
+ if (isdigit(*p))
+ {
+ size_t i = *p - '0';
+ if (i < n)
+ {
+ switch (exc->exc_type->etype_argformat[i])
+ {
+ case 's':
+ case 'r':
+ s = exc->exc_argv[i].v_str;
+ if (s == NULL)
+ s = "(null)";
+ sm_io_fputs(stream, SM_TIME_DEFAULT, s);
+ continue;
+ case 'i':
+ sm_io_fprintf(stream,
+ SM_TIME_DEFAULT,
+ format == 'o' ? "%o"
+ : format == 'x' ? "%x"
+ : "%d",
+ exc->exc_argv[i].v_int);
+ continue;
+ case 'l':
+ sm_io_fprintf(stream,
+ SM_TIME_DEFAULT,
+ format == 'o' ? "%lo"
+ : format == 'x' ? "%lx"
+ : "%ld",
+ exc->exc_argv[i].v_long);
+ continue;
+ case 'e':
+ sm_exc_write(exc->exc_argv[i].v_exc,
+ stream);
+ continue;
+ }
+ }
+ }
+ (void) sm_io_putc(stream, SM_TIME_DEFAULT, '%');
+ if (format)
+ (void) sm_io_putc(stream, SM_TIME_DEFAULT, format);
+ (void) sm_io_putc(stream, SM_TIME_DEFAULT, *p);
+ }
+}
+
+/*
+** Standard exception types.
+*/
+
+/*
+** SM_ETYPE_OS_PRINT -- Print OS related exception.
+**
+** Parameters:
+** exc -- exception.
+** stream -- file for output.
+**
+** Returns:
+** none.
+*/
+
+static void
+sm_etype_os_print __P((
+ SM_EXC_T *exc,
+ SM_FILE_T *stream));
+
+static void
+sm_etype_os_print(exc, stream)
+ SM_EXC_T *exc;
+ SM_FILE_T *stream;
+{
+ int err = exc->exc_argv[0].v_int;
+ char *syscall = exc->exc_argv[1].v_str;
+ char *sysargs = exc->exc_argv[2].v_str;
+
+ if (sysargs)
+ sm_io_fprintf(stream, SM_TIME_DEFAULT, "%s: %s failed: %s",
+ sysargs, syscall, sm_errstring(err));
+ else
+ sm_io_fprintf(stream, SM_TIME_DEFAULT, "%s failed: %s", syscall,
+ sm_errstring(err));
+}
+
+/*
+** SmEtypeOs represents the failure of a Unix system call.
+** The three arguments are:
+** int errno (eg, ENOENT)
+** char *syscall (eg, "open")
+** char *sysargs (eg, NULL or "/etc/mail/sendmail.cf")
+*/
+
+const SM_EXC_TYPE_T SmEtypeOs =
+{
+ SmExcTypeMagic,
+ "E:sm.os",
+ "isr",
+ sm_etype_os_print,
+ NULL,
+};
+
+/*
+** SmEtypeErr is a completely generic error which should only be
+** used in applications and test programs. Libraries should use
+** more specific exception codes.
+*/
+
+const SM_EXC_TYPE_T SmEtypeErr =
+{
+ SmExcTypeMagic,
+ "E:sm.err",
+ "r",
+ sm_etype_printf,
+ "%0",
+};
+
+/*
+** SM_EXC_VNEW_X -- Construct a new exception object.
+**
+** Parameters:
+** etype -- type of exception.
+** ap -- varargs.
+**
+** Returns:
+** pointer to exception object.
+*/
+
+/*
+** This is an auxiliary function called by sm_exc_new_x and sm_exc_raisenew_x.
+**
+** If an exception is raised, then to avoid a storage leak, we must:
+** (a) Free all storage we have allocated.
+** (b) Free all exception arguments in the varargs list.
+** Getting this right is tricky.
+**
+** To see why (b) is required, consider the code fragment
+** SM_EXCEPT(exc, "*")
+** sm_exc_raisenew_x(&MyEtype, exc);
+** SM_END_TRY
+** In the normal case, sm_exc_raisenew_x will allocate and raise a new
+** exception E that owns exc. When E is eventually freed, exc is also freed.
+** In the exceptional case, sm_exc_raisenew_x must free exc before raising
+** an out-of-memory exception so that exc is not leaked.
+*/
+
+SM_EXC_T *
+sm_exc_vnew_x(etype, ap)
+ const SM_EXC_TYPE_T *etype;
+ va_list SM_NONVOLATILE ap;
+{
+ /*
+ ** All variables that are modified in the SM_TRY clause and
+ ** referenced in the SM_EXCEPT clause must be declared volatile.
+ */
+
+ /* NOTE: Type of si, i, and argc *must* match */
+ SM_EXC_T * volatile exc = NULL;
+ int volatile si = 0;
+ SM_VAL_T * volatile argv = NULL;
+ int i, argc;
+
+ SM_REQUIRE_ISA(etype, SmExcTypeMagic);
+ argc = strlen(etype->etype_argformat);
+ SM_TRY
+ {
+ /*
+ ** Step 1. Allocate the exception structure.
+ ** On failure, scan the varargs list and free all
+ ** exception arguments.
+ */
+
+ exc = sm_malloc_x(sizeof(SM_EXC_T));
+ exc->sm_magic = SmExcMagic;
+ exc->exc_refcount = 1;
+ exc->exc_type = etype;
+ exc->exc_argv = NULL;
+
+ /*
+ ** Step 2. Allocate the argument vector.
+ ** On failure, free exc, scan the varargs list and free all
+ ** exception arguments. On success, scan the varargs list,
+ ** and copy the arguments into argv.
+ */
+
+ argv = sm_malloc_x(argc * sizeof(SM_VAL_T));
+ exc->exc_argv = argv;
+ for (i = 0; i < argc; ++i)
+ {
+ switch (etype->etype_argformat[i])
+ {
+ case 'i':
+ argv[i].v_int = SM_VA_ARG(ap, int);
+ break;
+ case 'l':
+ argv[i].v_long = SM_VA_ARG(ap, long);
+ break;
+ case 'e':
+ argv[i].v_exc = SM_VA_ARG(ap, SM_EXC_T*);
+ break;
+ case 's':
+ argv[i].v_str = SM_VA_ARG(ap, char*);
+ break;
+ case 'r':
+ SM_REQUIRE(etype->etype_argformat[i+1] == '\0');
+ argv[i].v_str = SM_VA_ARG(ap, char*);
+ break;
+ default:
+ sm_abort("sm_exc_vnew_x: bad argformat '%c'",
+ etype->etype_argformat[i]);
+ }
+ }
+
+ /*
+ ** Step 3. Scan argv, and allocate space for all
+ ** string arguments. si is the number of elements
+ ** of argv that have been processed so far.
+ ** On failure, free exc, argv, all the exception arguments
+ ** and all of the strings that have been copied.
+ */
+
+ for (si = 0; si < argc; ++si)
+ {
+ switch (etype->etype_argformat[si])
+ {
+ case 's':
+ {
+ char *str = argv[si].v_str;
+ if (str != NULL)
+ argv[si].v_str = sm_strdup_x(str);
+ }
+ break;
+ case 'r':
+ {
+ char *fmt = argv[si].v_str;
+ if (fmt != NULL)
+ argv[si].v_str = sm_vstringf_x(fmt, ap);
+ }
+ break;
+ }
+ }
+ }
+ SM_EXCEPT(e, "*")
+ {
+ if (exc == NULL || argv == NULL)
+ {
+ /*
+ ** Failure in step 1 or step 2.
+ ** Scan ap and free all exception arguments.
+ */
+
+ for (i = 0; i < argc; ++i)
+ {
+ switch (etype->etype_argformat[i])
+ {
+ case 'i':
+ (void) SM_VA_ARG(ap, int);
+ break;
+ case 'l':
+ (void) SM_VA_ARG(ap, long);
+ break;
+ case 'e':
+ sm_exc_free(SM_VA_ARG(ap, SM_EXC_T*));
+ break;
+ case 's':
+ case 'r':
+ (void) SM_VA_ARG(ap, char*);
+ break;
+ }
+ }
+ }
+ else
+ {
+ /*
+ ** Failure in step 3. Scan argv and free
+ ** all exception arguments and all string
+ ** arguments that have been duplicated.
+ ** Then free argv.
+ */
+
+ for (i = 0; i < argc; ++i)
+ {
+ switch (etype->etype_argformat[i])
+ {
+ case 'e':
+ sm_exc_free(argv[i].v_exc);
+ break;
+ case 's':
+ case 'r':
+ if (i < si)
+ sm_free(argv[i].v_str);
+ break;
+ }
+ }
+ sm_free(argv);
+ }
+ sm_free(exc);
+ sm_exc_raise_x(e);
+ }
+ SM_END_TRY
+
+ return exc;
+}
+
+/*
+** SM_EXC_NEW_X -- Construct a new exception object.
+**
+** Parameters:
+** etype -- type of exception.
+** ... -- varargs.
+**
+** Returns:
+** pointer to exception object.
+*/
+
+SM_EXC_T *
+#if SM_VA_STD
+sm_exc_new_x(
+ const SM_EXC_TYPE_T *etype,
+ ...)
+#else /* SM_VA_STD */
+sm_exc_new_x(etype, va_alist)
+ const SM_EXC_TYPE_T *etype;
+ va_dcl
+#endif /* SM_VA_STD */
+{
+ SM_EXC_T *exc;
+ SM_VA_LOCAL_DECL
+
+ SM_VA_START(ap, etype);
+ exc = sm_exc_vnew_x(etype, ap);
+ SM_VA_END(ap);
+ return exc;
+}
+
+/*
+** SM_ADDREF -- Add a reference to an exception object.
+**
+** Parameters:
+** exc -- exception object.
+**
+** Returns:
+** exc itself.
+*/
+
+SM_EXC_T *
+sm_addref(exc)
+ SM_EXC_T *exc;
+{
+ SM_REQUIRE_ISA(exc, SmExcMagic);
+ if (exc->exc_refcount != 0)
+ ++exc->exc_refcount;
+ return exc;
+}
+
+/*
+** SM_EXC_FREE -- Destroy a reference to an exception object.
+**
+** Parameters:
+** exc -- exception object.
+**
+** Returns:
+** none.
+*/
+
+void
+sm_exc_free(exc)
+ SM_EXC_T *exc;
+{
+ if (exc == NULL)
+ return;
+ SM_REQUIRE(exc->sm_magic == SmExcMagic);
+ if (exc->exc_refcount == 0)
+ return;
+ if (--exc->exc_refcount == 0)
+ {
+ int i, c;
+
+ for (i = 0; (c = exc->exc_type->etype_argformat[i]) != '\0';
+ ++i)
+ {
+ switch (c)
+ {
+ case 's':
+ case 'r':
+ sm_free(exc->exc_argv[i].v_str);
+ break;
+ case 'e':
+ sm_exc_free(exc->exc_argv[i].v_exc);
+ break;
+ }
+ }
+ exc->sm_magic = NULL;
+ sm_free(exc->exc_argv);
+ sm_free(exc);
+ }
+}
+
+/*
+** SM_EXC_MATCH -- Match exception category against a glob pattern.
+**
+** Parameters:
+** exc -- exception.
+** pattern -- glob pattern.
+**
+** Returns:
+** true iff match.
+*/
+
+bool
+sm_exc_match(exc, pattern)
+ SM_EXC_T *exc;
+ const char *pattern;
+{
+ if (exc == NULL)
+ return false;
+ SM_REQUIRE(exc->sm_magic == SmExcMagic);
+ return sm_match(exc->exc_type->etype_category, pattern);
+}
+
+/*
+** SM_EXC_WRITE -- Write exception message to a stream (wo trailing newline).
+**
+** Parameters:
+** exc -- exception.
+** stream -- file for output.
+**
+** Returns:
+** none.
+*/
+
+void
+sm_exc_write(exc, stream)
+ SM_EXC_T *exc;
+ SM_FILE_T *stream;
+{
+ SM_REQUIRE_ISA(exc, SmExcMagic);
+ exc->exc_type->etype_print(exc, stream);
+}
+
+/*
+** SM_EXC_PRINT -- Print exception message to a stream (with trailing newline).
+**
+** Parameters:
+** exc -- exception.
+** stream -- file for output.
+**
+** Returns:
+** none.
+*/
+
+void
+sm_exc_print(exc, stream)
+ SM_EXC_T *exc;
+ SM_FILE_T *stream;
+{
+ SM_REQUIRE_ISA(exc, SmExcMagic);
+ exc->exc_type->etype_print(exc, stream);
+ (void) sm_io_putc(stream, SM_TIME_DEFAULT, '\n');
+}
+
+SM_EXC_HANDLER_T *SmExcHandler = NULL;
+static SM_EXC_DEFAULT_HANDLER_T SmExcDefaultHandler = NULL;
+
+/*
+** SM_EXC_NEWTHREAD -- Initialize exception handling for new process/thread.
+**
+** Parameters:
+** h -- default exception handler.
+**
+** Returns:
+** none.
+*/
+
+/*
+** Initialize a new process or a new thread by clearing the
+** exception handler stack and optionally setting a default
+** exception handler function. Call this at the beginning of main,
+** or in a new process after calling fork, or in a new thread.
+**
+** This function is a luxury, not a necessity.
+** If h != NULL then you can get the same effect by
+** wrapping the body of main, or the body of a forked child
+** or a new thread in SM_TRY ... SM_EXCEPT(e,"*") h(e); SM_END_TRY.
+*/
+
+void
+sm_exc_newthread(h)
+ SM_EXC_DEFAULT_HANDLER_T h;
+{
+ SmExcHandler = NULL;
+ SmExcDefaultHandler = h;
+}
+
+/*
+** SM_EXC_RAISE_X -- Raise an exception.
+**
+** Parameters:
+** exc -- exception.
+**
+** Returns:
+** doesn't.
+*/
+
+void SM_DEAD_D
+sm_exc_raise_x(exc)
+ SM_EXC_T *exc;
+{
+ SM_REQUIRE_ISA(exc, SmExcMagic);
+
+ if (SmExcHandler == NULL)
+ {
+ if (SmExcDefaultHandler != NULL)
+ {
+ SM_EXC_DEFAULT_HANDLER_T h;
+
+ /*
+ ** If defined, the default handler is expected
+ ** to terminate the current thread of execution
+ ** using exit() or pthread_exit().
+ ** If it instead returns normally, then we fall
+ ** through to the default case below. If it
+ ** raises an exception, then sm_exc_raise_x is
+ ** re-entered and, because we set SmExcDefaultHandler
+ ** to NULL before invoking h, we will again
+ ** end up in the default case below.
+ */
+
+ h = SmExcDefaultHandler;
+ SmExcDefaultHandler = NULL;
+ (*h)(exc);
+ }
+
+ /*
+ ** No exception handler, so print the error and exit.
+ ** To override this behaviour on a program wide basis,
+ ** call sm_exc_newthread or put an exception handler in main().
+ **
+ ** XXX TODO: map the exception category to an exit code
+ ** XXX from <sysexits.h>.
+ */
+
+ sm_exc_print(exc, smioerr);
+ exit(255);
+ }
+
+ if (SmExcHandler->eh_value == NULL)
+ SmExcHandler->eh_value = exc;
+ else
+ sm_exc_free(exc);
+
+ sm_longjmp_nosig(SmExcHandler->eh_context, 1);
+}
+
+/*
+** SM_EXC_RAISENEW_X -- shorthand for sm_exc_raise_x(sm_exc_new_x(...))
+**
+** Parameters:
+** etype -- type of exception.
+** ap -- varargs.
+**
+** Returns:
+** none.
+*/
+
+void SM_DEAD_D
+#if SM_VA_STD
+sm_exc_raisenew_x(
+ const SM_EXC_TYPE_T *etype,
+ ...)
+#else
+sm_exc_raisenew_x(etype, va_alist)
+ const SM_EXC_TYPE_T *etype;
+ va_dcl
+#endif
+{
+ SM_EXC_T *exc;
+ SM_VA_LOCAL_DECL
+
+ SM_VA_START(ap, etype);
+ exc = sm_exc_vnew_x(etype, ap);
+ SM_VA_END(ap);
+ sm_exc_raise_x(exc);
+}
diff --git a/usr/src/cmd/sendmail/libsm/fclose.c b/usr/src/cmd/sendmail/libsm/fclose.c
new file mode 100644
index 0000000000..d540d99e6f
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/fclose.c
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2000-2002, 2004 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: fclose.c,v 1.43 2004/08/03 20:17:38 ca Exp $")
+#include <errno.h>
+#include <stdlib.h>
+#include <sys/time.h>
+#include <setjmp.h>
+#include <sm/io.h>
+#include <sm/assert.h>
+#include <sm/heap.h>
+#include <sm/signal.h>
+#include <sm/conf.h>
+#include <sm/clock.h>
+#include "local.h"
+
+static void closealrm __P((int));
+static jmp_buf CloseTimeOut;
+
+/*
+** CLOSEALRM -- handler when timeout activated for sm_io_close()
+**
+** Returns flow of control to where setjmp(CloseTimeOut) was set.
+**
+** Parameters:
+** sig -- unused
+**
+** Returns:
+** does not return
+**
+** Side Effects:
+** returns flow of control to setjmp(CloseTimeOut).
+**
+** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+** DOING.
+*/
+
+/* ARGSUSED0 */
+static void
+closealrm(sig)
+ int sig;
+{
+ longjmp(CloseTimeOut, 1);
+}
+
+/*
+** SM_IO_CLOSE -- close a file handle/pointer
+**
+** Parameters:
+** fp -- file pointer to be closed
+** timeout -- maximum time allowed to perform the close (millisecs)
+**
+** Returns:
+** 0 on success
+** -1 on failure and sets errno
+**
+** Side Effects:
+** file pointer 'fp' will no longer be valid.
+*/
+
+int
+sm_io_close(fp, timeout)
+ register SM_FILE_T *fp;
+ int SM_NONVOLATILE timeout;
+{
+ register int SM_NONVOLATILE r;
+ SM_EVENT *evt = NULL;
+
+ if (fp == NULL)
+ {
+ errno = EBADF;
+ return SM_IO_EOF;
+ }
+
+ SM_REQUIRE_ISA(fp, SmFileMagic);
+
+ /* XXX this won't be reached if above macro is active */
+ if (fp->sm_magic == NULL)
+ {
+ /* not open! */
+ errno = EBADF;
+ return SM_IO_EOF;
+ }
+ if (fp->f_close == NULL)
+ {
+ /* no close function! */
+ errno = ENODEV;
+ return SM_IO_EOF;
+ }
+ if (fp->f_dup_cnt > 0)
+ {
+ /* decrement file pointer open count */
+ fp->f_dup_cnt--;
+ return 0;
+ }
+
+ /* Okay, this is where we set the timeout. */
+ if (timeout == SM_TIME_DEFAULT)
+ timeout = fp->f_timeout;
+ if (timeout == SM_TIME_IMMEDIATE)
+ {
+ errno = EAGAIN;
+ return -1;
+ }
+
+ /* No more duplicates of file pointer. Flush buffer and close */
+ r = fp->f_flags & SMWR ? sm_flush(fp, (int *) &timeout) : 0;
+
+ /* sm_flush() has updated to.it_value for the time it's used */
+ if (timeout != SM_TIME_FOREVER)
+ {
+ if (setjmp(CloseTimeOut) != 0)
+ {
+ errno = EAGAIN;
+ return SM_IO_EOF;
+ }
+ evt = sm_seteventm(timeout, closealrm, 0);
+ }
+ if ((*fp->f_close)(fp) < 0)
+ r = SM_IO_EOF;
+
+ /* We're back. So undo our timeout and handler */
+ if (evt != NULL)
+ sm_clrevent(evt);
+ if (fp->f_flags & SMMBF)
+ {
+ sm_free((char *)fp->f_bf.smb_base);
+ fp->f_bf.smb_base = NULL;
+ }
+ if (HASUB(fp))
+ FREEUB(fp);
+ fp->f_flags = 0; /* clear flags */
+ fp->sm_magic = NULL; /* Release this SM_FILE_T for reuse. */
+ fp->f_r = fp->f_w = 0; /* Mess up if reaccessed. */
+ return r;
+}
diff --git a/usr/src/cmd/sendmail/libsm/feof.c b/usr/src/cmd/sendmail/libsm/feof.c
new file mode 100644
index 0000000000..2f1efc37d0
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/feof.c
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: feof.c,v 1.11 2001/04/03 01:46:40 ca Exp $")
+#include <sm/io.h>
+#include <sm/assert.h>
+#include "local.h"
+
+/*
+** SM_IO_EOF -- subroutine version of the macro sm_io_eof.
+**
+** Tests if the file for 'fp' has reached the end.
+**
+** Parameters:
+** fp -- file pointer.
+**
+** Returns:
+** 0 (zero) when the file is not at end
+** non-zero when EOF has been found
+*/
+#undef sm_io_eof
+
+int
+sm_io_eof(fp)
+ SM_FILE_T *fp;
+{
+ SM_REQUIRE_ISA(fp, SmFileMagic);
+
+ return sm_eof(fp);
+}
diff --git a/usr/src/cmd/sendmail/libsm/ferror.c b/usr/src/cmd/sendmail/libsm/ferror.c
new file mode 100644
index 0000000000..08ee823810
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/ferror.c
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: ferror.c,v 1.11 2001/04/03 01:46:40 ca Exp $")
+#include <sm/io.h>
+#include <sm/assert.h>
+#include "local.h"
+
+/*
+** SM_IO_ERROR -- subroutine version of the macro sm_io_error.
+**
+** Parameters:
+** fp -- file pointer
+**
+** Returns:
+** 0 (zero) when 'fp' is not in an error state
+** non-zero when 'fp' is in an error state
+*/
+
+#undef sm_io_error
+
+int
+sm_io_error(fp)
+ SM_FILE_T *fp;
+{
+ SM_REQUIRE_ISA(fp, SmFileMagic);
+
+ return sm_error(fp);
+}
diff --git a/usr/src/cmd/sendmail/libsm/fflush.c b/usr/src/cmd/sendmail/libsm/fflush.c
new file mode 100644
index 0000000000..4ee32c02f4
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/fflush.c
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: fflush.c,v 1.41 2001/05/15 16:55:27 ca Exp $")
+#include <unistd.h>
+#include <errno.h>
+#include <sys/time.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sm/io.h>
+#include <sm/assert.h>
+#include <sm/setjmp.h>
+#include "local.h"
+#include <sm/conf.h>
+
+/*
+** SM_IO_FLUSH -- flush the buffer for a 'fp' to the "file"
+**
+** Flush a single file. We don't allow this function to flush
+** all open files when fp==NULL any longer.
+**
+** Parameters:
+** fp -- the file pointer buffer to flush
+** timeout -- time to complete the flush
+**
+** Results:
+** Failure: SM_IO_EOF and sets errno
+** Success: 0 (zero)
+*/
+
+int
+sm_io_flush(fp, timeout)
+ register SM_FILE_T *fp;
+ int SM_NONVOLATILE timeout;
+{
+ int fd;
+ struct timeval to;
+
+ SM_REQUIRE_ISA(fp, SmFileMagic);
+
+ if ((fp->f_flags & (SMWR | SMRW)) == 0)
+ {
+ /*
+ ** The file is not opened for writing, so it cannot be flushed
+ ** (writable means SMWR [write] or SMRW [read/write].
+ */
+
+ errno = EBADF;
+ return SM_IO_EOF;
+ }
+
+ SM_CONVERT_TIME(fp, fd, timeout, &to);
+
+ /* Now do the flush */
+ return sm_flush(fp, (int *) &timeout);
+}
+
+/*
+** SM_FLUSH -- perform the actual flush
+**
+** Assumes that 'fp' has been validated before this function called.
+**
+** Parameters:
+** fp -- file pointer to be flushed
+** timeout -- max time allowed for flush (milliseconds)
+**
+** Results:
+** Success: 0 (zero)
+** Failure: SM_IO_EOF and errno set
+**
+** Side Effects:
+** timeout will get updated with the time remaining (if any)
+*/
+
+int
+sm_flush(fp, timeout)
+ register SM_FILE_T *fp;
+ int *timeout;
+{
+ register unsigned char *p;
+ register int n, t;
+ int fd;
+
+ SM_REQUIRE_ISA(fp, SmFileMagic);
+
+ t = fp->f_flags;
+ if ((t & SMWR) == 0)
+ return 0;
+
+ if (t & SMSTR)
+ {
+ *fp->f_p = '\0';
+ return 0;
+ }
+
+ if ((p = fp->f_bf.smb_base) == NULL)
+ return 0;
+
+ n = fp->f_p - p; /* write this much */
+
+ if ((fd = sm_io_getinfo(fp, SM_IO_WHAT_FD, NULL)) == -1)
+ {
+ /* can't get an fd, likely internal 'fake' fp */
+ errno = 0;
+ fd = -1;
+ }
+
+ /*
+ ** Set these immediately to avoid problems with longjmp and to allow
+ ** exchange buffering (via setvbuf) in user write function.
+ */
+
+ fp->f_p = p;
+ fp->f_w = t & (SMLBF|SMNBF) ? 0 : fp->f_bf.smb_size; /* implies SMFBF */
+
+ for (; n > 0; n -= t, p += t)
+ {
+ errno = 0; /* needed to ensure EOF correctly found */
+
+ /* Call the file type's write function */
+ t = (*fp->f_write)(fp, (char *)p, n);
+ if (t <= 0)
+ {
+ if (t == 0 && errno == 0)
+ break; /* EOF found */
+
+ if (IS_IO_ERROR(fd, t, *timeout))
+ {
+ fp->f_flags |= SMERR;
+
+ /* errno set by fp->f_write */
+ return SM_IO_EOF;
+ }
+ SM_IO_WR_TIMEOUT(fp, fd, *timeout);
+ }
+ }
+ return 0;
+}
diff --git a/usr/src/cmd/sendmail/libsm/fget.c b/usr/src/cmd/sendmail/libsm/fget.c
new file mode 100644
index 0000000000..96dbf71cbd
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/fget.c
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: fget.c,v 1.22 2001/08/27 18:54:14 gshapiro Exp $")
+#include <stdlib.h>
+#include <string.h>
+#include <sm/io.h>
+#include <sm/assert.h>
+#include "local.h"
+
+/*
+** SM_IO_FGETS -- get a string from a file
+**
+** Read at most n-1 characters from the given file.
+** Stop when a newline has been read, or the count ('n') runs out.
+**
+** Parameters:
+** fp -- the file to read from
+** timeout -- time to complete reading the string in milliseconds
+** buf -- buffer to place read string in
+** n -- size of 'buf'
+**
+** Returns:
+** success: returns value of 'buf'
+** failure: NULL (no characters were read)
+** timeout: NULL and errno set to EAGAIN
+**
+** Side Effects:
+** may move the file pointer
+*/
+
+char *
+sm_io_fgets(fp, timeout, buf, n)
+ register SM_FILE_T *fp;
+ int timeout;
+ char *buf;
+ register int n;
+{
+ register int len;
+ register char *s;
+ register unsigned char *p, *t;
+
+ SM_REQUIRE_ISA(fp, SmFileMagic);
+ if (n <= 0) /* sanity check */
+ return NULL;
+
+ s = buf;
+ n--; /* leave space for NUL */
+ while (n > 0)
+ {
+ /* If the buffer is empty, refill it. */
+ if ((len = fp->f_r) <= 0)
+ {
+
+ /*
+ ** Timeout is only passed if we can't get the data
+ ** from the buffer (which is counted as immediately).
+ */
+
+ if (sm_refill(fp, timeout) != 0)
+ {
+ /* EOF/error: stop with partial or no line */
+ if (s == buf)
+ return NULL;
+ break;
+ }
+ len = fp->f_r;
+ }
+ p = fp->f_p;
+
+ /*
+ ** Scan through at most n bytes of the current buffer,
+ ** looking for '\n'. If found, copy up to and including
+ ** newline, and stop. Otherwise, copy entire chunk
+ ** and loop.
+ */
+
+ if (len > n)
+ len = n;
+ t = (unsigned char *) memchr((void *) p, '\n', len);
+ if (t != NULL)
+ {
+ len = ++t - p;
+ fp->f_r -= len;
+ fp->f_p = t;
+ (void) memcpy((void *) s, (void *) p, len);
+ s[len] = 0;
+ return buf;
+ }
+ fp->f_r -= len;
+ fp->f_p += len;
+ (void) memcpy((void *) s, (void *) p, len);
+ s += len;
+ n -= len;
+ }
+ *s = 0;
+ return buf;
+}
diff --git a/usr/src/cmd/sendmail/libsm/findfp.c b/usr/src/cmd/sendmail/libsm/findfp.c
new file mode 100644
index 0000000000..befb996a89
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/findfp.c
@@ -0,0 +1,427 @@
+/*
+ * Copyright (c) 2000-2002 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: findfp.c,v 1.66 2002/02/20 02:40:24 ca Exp $")
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/param.h>
+#include <errno.h>
+#include <string.h>
+#include <syslog.h>
+#include <sm/io.h>
+#include <sm/assert.h>
+#include <sm/heap.h>
+#include <sm/string.h>
+#include <sm/conf.h>
+#include "local.h"
+#include "glue.h"
+
+bool Sm_IO_DidInit; /* IO system has been initialized? */
+
+const char SmFileMagic[] = "sm_file";
+
+/* An open type to map to fopen()-like behavior */
+SM_FILE_T SmFtStdio_def =
+ {SmFileMagic, 0, 0, 0, (SMRW|SMFBF), -1, {0, 0}, 0, 0, 0,
+ sm_stdclose, sm_stdread, sm_stdseek, sm_stdwrite,
+ sm_stdopen, sm_stdsetinfo, sm_stdgetinfo, SM_TIME_FOREVER,
+ SM_TIME_BLOCK, "stdio" };
+
+/* An open type to map to fdopen()-like behavior */
+SM_FILE_T SmFtStdiofd_def =
+ {SmFileMagic, 0, 0, 0, (SMRW|SMFBF), -1, {0, 0}, 0, 0, 0,
+ sm_stdclose, sm_stdread, sm_stdseek, sm_stdwrite,
+ sm_stdfdopen, sm_stdsetinfo, sm_stdgetinfo, SM_TIME_FOREVER,
+ SM_TIME_BLOCK, "stdiofd" };
+
+/* A string file type */
+SM_FILE_T SmFtString_def =
+ {SmFileMagic, 0, 0, 0, (SMRW|SMNBF), -1, {0, 0}, 0, 0, 0,
+ sm_strclose, sm_strread, sm_strseek, sm_strwrite,
+ sm_stropen, sm_strsetinfo, sm_strgetinfo, SM_TIME_FOREVER,
+ SM_TIME_BLOCK, "string" };
+
+#if 0
+/* A file type for syslog communications */
+SM_FILE_T SmFtSyslog_def =
+ {SmFileMagic, 0, 0, 0, (SMRW|SMNBF), -1, {0, 0}, 0, 0, 0,
+ sm_syslogclose, sm_syslogread, sm_syslogseek, sm_syslogwrite,
+ sm_syslogopen, sm_syslogsetinfo, sm_sysloggetinfo, SM_TIME_FOREVER,
+ SM_TIME_BLOCK, "syslog" };
+#endif /* 0 */
+
+#define NDYNAMIC 10 /* add ten more whenever necessary */
+
+#define smio(flags, file, name) \
+ {SmFileMagic, 0, 0, 0, flags, file, {0}, 0, SmIoF+file, 0, \
+ sm_stdclose, sm_stdread, sm_stdseek, sm_stdwrite, \
+ sm_stdopen, sm_stdsetinfo, sm_stdgetinfo, SM_TIME_FOREVER, \
+ SM_TIME_BLOCK, name}
+
+/* sm_magic p r w flags file bf lbfsize cookie ival */
+#define smstd(flags, file, name) \
+ {SmFileMagic, 0, 0, 0, flags, -1, {0}, 0, 0, file, \
+ sm_stdioclose, sm_stdioread, sm_stdioseek, sm_stdiowrite, \
+ sm_stdioopen, sm_stdiosetinfo, sm_stdiogetinfo, SM_TIME_FOREVER,\
+ SM_TIME_BLOCK, name}
+
+/* A file type for interfacing to stdio FILE* streams. */
+SM_FILE_T SmFtRealStdio_def = smstd(SMRW|SMNBF, -1, "realstdio");
+
+ /* the usual - (stdin + stdout + stderr) */
+static SM_FILE_T usual[SM_IO_OPEN_MAX - 3];
+static struct sm_glue smuglue = { 0, SM_IO_OPEN_MAX - 3, usual };
+
+/* List of builtin automagically already open file pointers */
+SM_FILE_T SmIoF[6] =
+{
+ smio(SMRD|SMLBF, SMIOIN_FILENO, "smioin"), /* smioin */
+ smio(SMWR|SMLBF, SMIOOUT_FILENO, "smioout"), /* smioout */
+ smio(SMWR|SMNBF, SMIOERR_FILENO, "smioerr"), /* smioerr */
+ smstd(SMRD|SMNBF, SMIOIN_FILENO, "smiostdin"), /* smiostdin */
+ smstd(SMWR|SMNBF, SMIOOUT_FILENO, "smiostdout"),/* smiostdout */
+ smstd(SMWR|SMNBF, SMIOERR_FILENO, "smiostderr") /* smiostderr */
+};
+
+/* Structure containing list of currently open file pointers */
+struct sm_glue smglue = { &smuglue, 3, SmIoF };
+
+/*
+** SM_MOREGLUE -- adds more space for open file pointers
+**
+** Parameters:
+** n -- number of new spaces for file pointers
+**
+** Returns:
+** Raises an exception if no more memory.
+** Otherwise, returns a pointer to new spaces.
+*/
+
+static struct sm_glue *sm_moreglue_x __P((int));
+static SM_FILE_T empty;
+
+static struct sm_glue *
+sm_moreglue_x(n)
+ register int n;
+{
+ register struct sm_glue *g;
+ register SM_FILE_T *p;
+
+ g = (struct sm_glue *) sm_pmalloc_x(sizeof(*g) + SM_ALIGN_BITS +
+ n * sizeof(SM_FILE_T));
+ p = (SM_FILE_T *) SM_ALIGN(g + 1);
+ g->gl_next = NULL;
+ g->gl_niobs = n;
+ g->gl_iobs = p;
+ while (--n >= 0)
+ *p++ = empty;
+ return g;
+}
+
+/*
+** SM_FP -- allocate and initialize an SM_FILE structure
+**
+** Parameters:
+** t -- file type requested to be opened.
+** flags -- control flags for file type behavior
+** oldfp -- file pointer to reuse if available (optional)
+**
+** Returns:
+** Raises exception on memory exhaustion.
+** Aborts if type is invalid.
+** Otherwise, returns file pointer for requested file type.
+*/
+
+SM_FILE_T *
+sm_fp(t, flags, oldfp)
+ const SM_FILE_T *t;
+ const int flags;
+ SM_FILE_T *oldfp;
+{
+ register SM_FILE_T *fp;
+ register int n;
+ register struct sm_glue *g;
+
+ SM_REQUIRE(t->f_open && t->f_close && (t->f_read || t->f_write));
+
+ if (!Sm_IO_DidInit)
+ sm_init();
+
+ if (oldfp != NULL)
+ {
+ fp = oldfp;
+ goto found; /* for opening reusing an 'fp' */
+ }
+
+ for (g = &smglue;; g = g->gl_next)
+ {
+ for (fp = g->gl_iobs, n = g->gl_niobs; --n >= 0; fp++)
+ if (fp->sm_magic == NULL)
+ goto found;
+ if (g->gl_next == NULL)
+ g->gl_next = sm_moreglue_x(NDYNAMIC);
+ }
+found:
+ fp->sm_magic = SmFileMagic; /* 'fp' now valid and in-use */
+ fp->f_p = NULL; /* no current pointer */
+ fp->f_w = 0; /* nothing to write */
+ fp->f_r = 0; /* nothing to read */
+ fp->f_flags = flags;
+ fp->f_file = -1; /* no file */
+ fp->f_bf.smb_base = NULL; /* no buffer */
+ fp->f_bf.smb_size = 0; /* no buffer size with no buffer */
+ fp->f_lbfsize = 0; /* not line buffered */
+ fp->f_flushfp = NULL; /* no associated flush file */
+
+ fp->f_cookie = fp; /* default: *open* overrides cookie setting */
+ fp->f_close = t->f_close; /* assign close function */
+ fp->f_read = t->f_read; /* assign read function */
+ fp->f_seek = t->f_seek; /* assign seek function */
+ fp->f_write = t->f_write; /* assign write function */
+ fp->f_open = t->f_open; /* assign open function */
+ fp->f_setinfo = t->f_setinfo; /* assign setinfo function */
+ fp->f_getinfo = t->f_getinfo; /* assign getinfo function */
+ fp->f_type = t->f_type; /* file type */
+
+ fp->f_ub.smb_base = NULL; /* no ungetc buffer */
+ fp->f_ub.smb_size = 0; /* no size for no ungetc buffer */
+
+ if (fp->f_timeout == SM_TIME_DEFAULT)
+ fp->f_timeout = SM_TIME_FOREVER;
+ else
+ fp->f_timeout = t->f_timeout; /* traditional behavior */
+ fp->f_timeoutstate = SM_TIME_BLOCK; /* by default */
+
+ return fp;
+}
+
+/*
+** SM_CLEANUP -- cleanup function when exit called.
+**
+** This function is registered via atexit().
+**
+** Parameters:
+** none
+**
+** Returns:
+** nothing.
+**
+** Side Effects:
+** flushes open files before they are forced closed
+*/
+
+void
+sm_cleanup()
+{
+ int timeout = SM_TIME_DEFAULT;
+
+ (void) sm_fwalk(sm_flush, &timeout); /* `cheating' */
+}
+
+/*
+** SM_INIT -- called whenever sm_io's internal variables must be set up.
+**
+** Parameters:
+** none
+**
+** Returns:
+** none
+**
+** Side Effects:
+** Registers sm_cleanup() using atexit().
+*/
+
+void
+sm_init()
+{
+ if (Sm_IO_DidInit) /* paranoia */
+ return;
+
+ /* more paranoia: initialize pointers in a static variable */
+ empty.f_type = NULL;
+ empty.sm_magic = NULL;
+
+ /* make sure we clean up on exit */
+ atexit(sm_cleanup); /* conservative */
+ Sm_IO_DidInit = true;
+}
+
+/*
+** SM_IO_SETINFO -- change info for an open file type (fp)
+**
+** The generic SM_IO_WHAT_VECTORS is auto supplied for all file types.
+** If the request is to set info other than SM_IO_WHAT_VECTORS then
+** the request is passed on to the file type's specific setinfo vector.
+** WARNING: this is working on an active/open file type.
+**
+** Parameters:
+** fp -- file to make the setting on
+** what -- type of information to set
+** valp -- structure to obtain info from
+**
+** Returns:
+** 0 on success
+** -1 on error and sets errno:
+** - when what != SM_IO_WHAT_VECTORS and setinfo vector
+** not set
+** - when vectored setinfo returns -1
+*/
+
+int
+sm_io_setinfo(fp, what, valp)
+ SM_FILE_T *fp;
+ int what;
+ void *valp;
+{
+ SM_FILE_T *v = (SM_FILE_T *) valp;
+
+ SM_REQUIRE_ISA(fp, SmFileMagic);
+ switch (what)
+ {
+ case SM_IO_WHAT_VECTORS:
+
+ /*
+ ** This is the "generic" available for all.
+ ** This allows the function vectors to be replaced
+ ** while the file type is active.
+ */
+
+ fp->f_close = v->f_close;
+ fp->f_read = v->f_read;
+ fp->f_seek = v->f_seek;
+ fp->f_write = v->f_write;
+ fp->f_open = v->f_open;
+ fp->f_setinfo = v->f_setinfo;
+ fp->f_getinfo = v->f_getinfo;
+ sm_free(fp->f_type);
+ fp->f_type = sm_strdup_x(v->f_type);
+ return 0;
+ case SM_IO_WHAT_TIMEOUT:
+ fp->f_timeout = *((int *)valp);
+ return 0;
+ }
+
+ /* Otherwise the vector will check it out */
+ if (fp->f_setinfo == NULL)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+ else
+ return (*fp->f_setinfo)(fp, what, valp);
+}
+
+/*
+** SM_IO_GETINFO -- get information for an active file type (fp)
+**
+** This function supplies for all file types the answers for the
+** three requests SM_IO_WHAT_VECTORS, SM_IO_WHAT_TYPE and
+** SM_IO_WHAT_ISTYPE. Other requests are handled by the getinfo
+** vector if available for the open file type.
+** SM_IO_WHAT_VECTORS returns information for the file pointer vectors.
+** SM_IO_WHAT_TYPE returns the type identifier for the file pointer
+** SM_IO_WHAT_ISTYPE returns >0 if the passed in type matches the
+** file pointer's type.
+** SM_IO_IS_READABLE returns 1 if there is data available for reading,
+** 0 otherwise.
+**
+** Parameters:
+** fp -- file pointer for active file type
+** what -- type of information request
+** valp -- structure to place obtained info into
+**
+** Returns:
+** -1 on error and sets errno:
+** - when valp==NULL and request expects otherwise
+** - when request is not SM_IO_WHAT_VECTORS and not
+** SM_IO_WHAT_TYPE and not SM_IO_WHAT_ISTYPE
+** and getinfo vector is NULL
+** - when getinfo type vector returns -1
+** >=0 on success
+*/
+
+int
+sm_io_getinfo(fp, what, valp)
+ SM_FILE_T *fp;
+ int what;
+ void *valp;
+{
+ SM_FILE_T *v = (SM_FILE_T *) valp;
+
+ SM_REQUIRE_ISA(fp, SmFileMagic);
+
+ switch (what)
+ {
+ case SM_IO_WHAT_VECTORS:
+
+ /* This is the "generic" available for all */
+ v->f_close = fp->f_close;
+ v->f_read = fp->f_read;
+ v->f_seek = fp->f_seek;
+ v->f_write = fp->f_write;
+ v->f_open = fp->f_open;
+ v->f_setinfo = fp->f_setinfo;
+ v->f_getinfo = fp->f_getinfo;
+ v->f_type = fp->f_type;
+ return 0;
+
+ case SM_IO_WHAT_TYPE:
+ if (valp == NULL)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+ valp = sm_strdup_x(fp->f_type);
+ return 0;
+
+ case SM_IO_WHAT_ISTYPE:
+ if (valp == NULL)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+ return strcmp(fp->f_type, valp) == 0;
+
+ case SM_IO_IS_READABLE:
+
+ /* if there is data in the buffer, it must be readable */
+ if (fp->f_r > 0)
+ return 1;
+
+ /* otherwise query the underlying file */
+ break;
+
+ case SM_IO_WHAT_TIMEOUT:
+ *((int *) valp) = fp->f_timeout;
+ return 0;
+
+ case SM_IO_WHAT_FD:
+ if (fp->f_file > -1)
+ return fp->f_file;
+
+ /* try the file type specific getinfo to see if it knows */
+ break;
+ }
+
+ /* Otherwise the vector will check it out */
+ if (fp->f_getinfo == NULL)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+ return (*fp->f_getinfo)(fp, what, valp);
+}
diff --git a/usr/src/cmd/sendmail/libsm/flags.c b/usr/src/cmd/sendmail/libsm/flags.c
new file mode 100644
index 0000000000..8e4ede9546
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/flags.c
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2000-2001, 2003 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: flags.c,v 1.20.2.1 2003/09/03 18:51:56 ca Exp $")
+#include <sys/types.h>
+#include <sys/file.h>
+#include <errno.h>
+#include <sm/io.h>
+
+/*
+** SM_FLAGS -- translate external (user) flags into internal flags
+**
+** Paramters:
+** flags -- user select flags
+**
+** Returns:
+** Internal flag value matching user selected flags
+*/
+
+int
+sm_flags(flags)
+ register int flags;
+{
+ register int ret;
+
+ switch(SM_IO_MODE(flags))
+ {
+ case SM_IO_RDONLY: /* open for reading */
+ ret = SMRD;
+ break;
+
+ case SM_IO_WRONLY: /* open for writing */
+ ret = SMWR;
+ break;
+
+ case SM_IO_APPEND: /* open for appending */
+ ret = SMWR;
+ break;
+
+ case SM_IO_RDWR: /* open for read and write */
+ ret = SMRW;
+ break;
+
+ default:
+ ret = 0;
+ break;
+ }
+ if (SM_IS_BINARY(flags))
+ ret |= SM_IO_BINARY;
+ return ret;
+}
diff --git a/usr/src/cmd/sendmail/libsm/fopen.c b/usr/src/cmd/sendmail/libsm/fopen.c
new file mode 100644
index 0000000000..cfb3d4baca
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/fopen.c
@@ -0,0 +1,376 @@
+/*
+ * Copyright (c) 2000-2002, 2004 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: fopen.c,v 1.61 2004/08/03 20:17:38 ca Exp $")
+#include <errno.h>
+#include <setjmp.h>
+#include <sys/time.h>
+#include <sm/heap.h>
+#include <sm/signal.h>
+#include <sm/assert.h>
+#include <sm/io.h>
+#include <sm/clock.h>
+#include "local.h"
+
+static void openalrm __P((int));
+static void reopenalrm __P((int));
+extern int sm_io_fclose __P((SM_FILE_T *));
+
+static jmp_buf OpenTimeOut, ReopenTimeOut;
+
+/*
+** OPENALRM -- handler when timeout activated for sm_io_open()
+**
+** Returns flow of control to where setjmp(OpenTimeOut) was set.
+**
+** Parameters:
+** sig -- unused
+**
+** Returns:
+** does not return
+**
+** Side Effects:
+** returns flow of control to setjmp(OpenTimeOut).
+**
+** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+** DOING.
+*/
+
+/* ARGSUSED0 */
+static void
+openalrm(sig)
+ int sig;
+{
+ longjmp(OpenTimeOut, 1);
+}
+/*
+** REOPENALRM -- handler when timeout activated for sm_io_reopen()
+**
+** Returns flow of control to where setjmp(ReopenTimeOut) was set.
+**
+** Parameters:
+** sig -- unused
+**
+** Returns:
+** does not return
+**
+** Side Effects:
+** returns flow of control to setjmp(ReopenTimeOut).
+**
+** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+** DOING.
+*/
+
+/* ARGSUSED0 */
+static void
+reopenalrm(sig)
+ int sig;
+{
+ longjmp(ReopenTimeOut, 1);
+}
+
+/*
+** SM_IO_OPEN -- open a file of a specific type
+**
+** Parameters:
+** type -- type of file to open
+** timeout -- time to complete the open
+** info -- info describing what is to be opened (type dependant)
+** flags -- user selected flags
+** rpool -- pointer to rpool to be used for this open
+**
+** Returns:
+** Raises exception on heap exhaustion.
+** Aborts if type is invalid.
+** Returns NULL and sets errno
+** - when the type specific open fails
+** - when open vector errors
+** - when flags not set or invalid
+** Success returns a file pointer to the opened file type.
+*/
+
+SM_FILE_T *
+sm_io_open(type, timeout, info, flags, rpool)
+ const SM_FILE_T *type;
+ int SM_NONVOLATILE timeout; /* this is not the file type timeout */
+ const void *info;
+ int flags;
+ const void *rpool;
+{
+ register SM_FILE_T *fp;
+ int ioflags;
+ SM_EVENT *evt = NULL;
+
+ ioflags = sm_flags(flags);
+
+ if (ioflags == 0)
+ {
+ /* must give some indication/intent */
+ errno = EINVAL;
+ return NULL;
+ }
+
+ if (timeout == SM_TIME_DEFAULT)
+ timeout = SM_TIME_FOREVER;
+ if (timeout == SM_TIME_IMMEDIATE)
+ {
+ errno = EAGAIN;
+ return NULL;
+ }
+
+ fp = sm_fp(type, ioflags, NULL);
+
+ /* Okay, this is where we set the timeout. */
+ if (timeout != SM_TIME_FOREVER)
+ {
+ if (setjmp(OpenTimeOut) != 0)
+ {
+ errno = EAGAIN;
+ return NULL;
+ }
+ evt = sm_seteventm(timeout, openalrm, 0);
+ }
+
+ if ((*fp->f_open)(fp, info, flags, rpool) < 0)
+ {
+ fp->f_flags = 0; /* release */
+ fp->sm_magic = NULL; /* release */
+ return NULL;
+ }
+
+ /* We're back. So undo our timeout and handler */
+ if (evt != NULL)
+ sm_clrevent(evt);
+
+#if SM_RPOOL
+ if (rpool != NULL)
+ sm_rpool_attach_x(rpool, sm_io_fclose, fp);
+#endif /* SM_RPOOL */
+
+ return fp;
+}
+/*
+** SM_IO_DUP -- duplicate a file pointer
+**
+** Parameters:
+** fp -- file pointer to duplicate
+**
+** Returns:
+** Success - the duplicated file pointer
+** Failure - NULL (was an invalid file pointer or too many open)
+**
+** Increments the duplicate counter (dup_cnt) for the open file pointer.
+** The counter counts the number of duplicates. When the duplicate
+** counter is 0 (zero) then the file pointer is the only one left
+** (no duplicates, it is the only one).
+*/
+
+SM_FILE_T *
+sm_io_dup(fp)
+ SM_FILE_T *fp;
+{
+
+ SM_REQUIRE_ISA(fp, SmFileMagic);
+ if (fp->sm_magic != SmFileMagic)
+ {
+ errno = EBADF;
+ return NULL;
+ }
+ if (fp->f_dup_cnt >= INT_MAX - 1)
+ {
+ /* Can't let f_dup_cnt wrap! */
+ errno = EMFILE;
+ return NULL;
+ }
+ fp->f_dup_cnt++;
+ return fp;
+}
+/*
+** SM_IO_REOPEN -- open a new file using the old file pointer
+**
+** Parameters:
+** type -- file type to be opened
+** timeout -- time to complete the reopen
+** info -- infomation about what is to be "re-opened" (type dep.)
+** flags -- user flags to map to internal flags
+** rpool -- rpool file to be associated with
+** fp -- the file pointer to reuse
+**
+** Returns:
+** Raises an exception on heap exhaustion.
+** Aborts if type is invalid.
+** Failure: returns NULL
+** Success: returns "reopened" file pointer
+*/
+
+SM_FILE_T *
+sm_io_reopen(type, timeout, info, flags, rpool, fp)
+ const SM_FILE_T *type;
+ int SM_NONVOLATILE timeout;
+ const void *info;
+ int flags;
+ const void *rpool;
+ SM_FILE_T *fp;
+{
+ int ioflags, ret;
+ SM_FILE_T *fp2;
+ SM_EVENT *evt = NULL;
+
+ if ((ioflags = sm_flags(flags)) == 0)
+ {
+ (void) sm_io_close(fp, timeout);
+ return NULL;
+ }
+
+ if (!Sm_IO_DidInit)
+ sm_init();
+
+ if (timeout == SM_TIME_DEFAULT)
+ timeout = SM_TIME_FOREVER;
+ if (timeout == SM_TIME_IMMEDIATE)
+ {
+ /*
+ ** Filling the buffer will take time and we are wanted to
+ ** return immediately. So...
+ */
+
+ errno = EAGAIN;
+ return NULL;
+ }
+ /* Okay, this is where we set the timeout. */
+ if (timeout != SM_TIME_FOREVER)
+ {
+ if (setjmp(ReopenTimeOut) != 0)
+ {
+ errno = EAGAIN;
+ return NULL;
+ }
+
+ evt = sm_seteventm(timeout, reopenalrm, 0);
+ }
+
+ /*
+ ** There are actually programs that depend on being able to "reopen"
+ ** descriptors that weren't originally open. Keep this from breaking.
+ ** Remember whether the stream was open to begin with, and which file
+ ** descriptor (if any) was associated with it. If it was attached to
+ ** a descriptor, defer closing it; reopen("/dev/stdin", "r", stdin)
+ ** should work. This is unnecessary if it was not a Unix file.
+ */
+
+ if (fp != NULL)
+ {
+ if (fp->sm_magic != SmFileMagic)
+ fp->f_flags = SMFEOF; /* hold on to it */
+ else
+ {
+ /* flush the stream; ANSI doesn't require this. */
+ (void) sm_io_flush(fp, SM_TIME_FOREVER);
+ (void) sm_io_close(fp, SM_TIME_FOREVER);
+ }
+ }
+
+ fp2 = sm_fp(type, ioflags, fp);
+ ret = (*fp2->f_open)(fp2, info, flags, rpool);
+
+ /* We're back. So undo our timeout and handler */
+ if (evt != NULL)
+ sm_clrevent(evt);
+
+ if (ret < 0)
+ {
+ fp2->f_flags = 0; /* release */
+ fp2->sm_magic = NULL; /* release */
+ return NULL;
+ }
+
+ /*
+ ** We're not preserving this logic (below) for sm_io because it is now
+ ** abstracted at least one "layer" away. By closing and reopening
+ ** the 1st fd used should be the just released one (when Unix
+ ** behavior followed). Old comment::
+ ** If reopening something that was open before on a real file, try
+ ** to maintain the descriptor. Various C library routines (perror)
+ ** assume stderr is always fd STDERR_FILENO, even if being reopen'd.
+ */
+
+#if SM_RPOOL
+ if (rpool != NULL)
+ sm_rpool_attach_x(rpool, sm_io_close, fp2);
+#endif /* SM_RPOOL */
+
+ return fp2;
+}
+/*
+** SM_IO_AUTOFLUSH -- link another file to this for auto-flushing
+**
+** When a read occurs on fp, fp2 will be flushed iff there is no
+** data waiting on fp.
+**
+** Parameters:
+** fp -- the file opened for reading.
+** fp2 -- the file opened for writing.
+**
+** Returns:
+** The old flush file pointer.
+*/
+
+SM_FILE_T *
+sm_io_autoflush(fp, fp2)
+ SM_FILE_T *fp;
+ SM_FILE_T *fp2;
+{
+ SM_FILE_T *savefp;
+
+ SM_REQUIRE_ISA(fp, SmFileMagic);
+ if (fp2 != NULL)
+ SM_REQUIRE_ISA(fp2, SmFileMagic);
+
+ savefp = fp->f_flushfp;
+ fp->f_flushfp = fp2;
+ return savefp;
+}
+/*
+** SM_IO_AUTOMODE -- link another file to this for auto-moding
+**
+** When the mode (blocking or non-blocking) changes for fp1 then
+** update fp2's mode at the same time. This is to be used when
+** a system dup() has generated a second file descriptor for
+** another sm_io_open() by file descriptor. The modes have been
+** linked in the system and this formalizes it for sm_io internally.
+**
+** Parameters:
+** fp1 -- the first file
+** fp2 -- the second file
+**
+** Returns:
+** nothing
+*/
+
+void
+sm_io_automode(fp1, fp2)
+ SM_FILE_T *fp1;
+ SM_FILE_T *fp2;
+{
+ SM_REQUIRE_ISA(fp1, SmFileMagic);
+ SM_REQUIRE_ISA(fp2, SmFileMagic);
+
+ fp1->f_modefp = fp2;
+ fp2->f_modefp = fp1;
+}
diff --git a/usr/src/cmd/sendmail/libsm/fpos.c b/usr/src/cmd/sendmail/libsm/fpos.c
new file mode 100644
index 0000000000..b443015626
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/fpos.c
@@ -0,0 +1,155 @@
+/*
+ * Copyright (c) 2000-2001, 2004 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: fpos.c,v 1.38 2004/08/03 20:17:38 ca Exp $")
+#include <errno.h>
+#include <setjmp.h>
+#include <sys/time.h>
+#include <sm/heap.h>
+#include <sm/signal.h>
+#include <sm/clock.h>
+#include <sm/io.h>
+#include <sm/assert.h>
+#include "local.h"
+
+static void tellalrm __P((int));
+static jmp_buf TellTimeOut;
+
+/*
+** TELLALRM -- handler when timeout activated for sm_io_tell()
+**
+** Returns flow of control to where setjmp(TellTimeOut) was set.
+**
+** Parameters:
+** sig -- unused
+**
+** Returns:
+** does not return
+**
+** Side Effects:
+** returns flow of control to setjmp(TellTimeOut).
+**
+** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+** DOING.
+*/
+
+/* ARGSUSED0 */
+static void
+tellalrm(sig)
+ int sig;
+{
+ longjmp(TellTimeOut, 1);
+}
+
+/*
+** SM_IO_TELL -- position the file pointer
+**
+** Paramters:
+** fp -- the file pointer to get repositioned
+** timeout -- time to complete the tell (milliseconds)
+**
+** Returns:
+** Success -- the repositioned location.
+** Failure -- -1 (minus 1) and sets errno
+*/
+
+long
+sm_io_tell(fp, timeout)
+ register SM_FILE_T *fp;
+ int SM_NONVOLATILE timeout;
+{
+ register off_t pos;
+ SM_EVENT *evt = NULL;
+
+ SM_REQUIRE_ISA(fp, SmFileMagic);
+ if (fp->f_seek == NULL)
+ {
+ errno = ESPIPE; /* historic practice */
+ return -1L;
+ }
+
+ if (timeout == SM_TIME_DEFAULT)
+ timeout = fp->f_timeout;
+ if (timeout == SM_TIME_IMMEDIATE)
+ {
+ /*
+ ** Filling the buffer will take time and we are wanted to
+ ** return immediately. So...
+ */
+
+ errno = EAGAIN;
+ return -1L;
+ }
+
+ /*
+ ** Find offset of underlying I/O object, then adjust byte position
+ ** may adjust seek offset on append stream
+ */
+
+ (void) sm_flush(fp, (int *) &timeout);
+
+ /* This is where we start the timeout */
+ if (timeout != SM_TIME_FOREVER)
+ {
+ if (setjmp(TellTimeOut) != 0)
+ {
+ errno = EAGAIN;
+ return -1L;
+ }
+
+ evt = sm_seteventm(timeout, tellalrm, 0);
+ }
+
+ if (fp->f_flags & SMOFF)
+ pos = fp->f_lseekoff;
+ else
+ {
+ /* XXX only set the timeout here? */
+ pos = (*fp->f_seek)(fp, (off_t) 0, SM_IO_SEEK_CUR);
+ if (pos == -1L)
+ goto clean;
+ }
+ if (fp->f_flags & SMRD)
+ {
+ /*
+ ** Reading. Any unread characters (including
+ ** those from ungetc) cause the position to be
+ ** smaller than that in the underlying object.
+ */
+
+ pos -= fp->f_r;
+ if (HASUB(fp))
+ pos -= fp->f_ur;
+ }
+ else if (fp->f_flags & SMWR && fp->f_p != NULL)
+ {
+ /*
+ ** Writing. Any buffered characters cause the
+ ** position to be greater than that in the
+ ** underlying object.
+ */
+
+ pos += fp->f_p - fp->f_bf.smb_base;
+ }
+
+clean:
+ /* We're back. So undo our timeout and handler */
+ if (evt != NULL)
+ sm_clrevent(evt);
+ return pos;
+}
diff --git a/usr/src/cmd/sendmail/libsm/fprintf.c b/usr/src/cmd/sendmail/libsm/fprintf.c
new file mode 100644
index 0000000000..591e09fc09
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/fprintf.c
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: fprintf.c,v 1.15 2001/03/02 23:53:41 ca Exp $")
+#include <sm/varargs.h>
+#include <sm/io.h>
+#include <sm/assert.h>
+#include "local.h"
+
+/*
+** SM_IO_FPRINTF -- format and print a string to a file pointer
+**
+** Parameters:
+** fp -- file pointer to be printed to
+** timeout -- time to complete print
+** fmt -- markup format for the string to be printed
+** ... -- additional information for 'fmt'
+**
+** Returns:
+** Failure: returns SM_IO_EOF and sets errno
+** Success: returns the number of characters o/p
+*/
+
+int
+#if SM_VA_STD
+sm_io_fprintf(SM_FILE_T *fp, int timeout, const char *fmt, ...)
+#else /* SM_VA_STD */
+sm_io_fprintf(fp, timeout, fmt, va_alist)
+ SM_FILE_T *fp;
+ int timeout;
+ char *fmt;
+ va_dcl
+#endif /* SM_VA_STD */
+{
+ int ret;
+ SM_VA_LOCAL_DECL
+
+ SM_REQUIRE_ISA(fp, SmFileMagic);
+ SM_VA_START(ap, fmt);
+ ret = sm_io_vfprintf(fp, timeout, fmt, ap);
+ SM_VA_END(ap);
+ return ret;
+}
diff --git a/usr/src/cmd/sendmail/libsm/fpurge.c b/usr/src/cmd/sendmail/libsm/fpurge.c
new file mode 100644
index 0000000000..4484a4d546
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/fpurge.c
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: fpurge.c,v 1.18 2001/01/28 00:29:35 ca Exp $")
+#include <stdlib.h>
+#include <errno.h>
+#include <sm/io.h>
+#include <sm/assert.h>
+#include "local.h"
+
+/*
+** SM_IO_PURGE -- purge/empty the buffer without committing buffer content
+**
+** Parameters:
+** fp -- file pointer to purge
+**
+** Returns:
+** Failure: returns SM_IO_EOF and sets errno
+** Success: returns 0 (zero)
+*/
+
+int
+sm_io_purge(fp)
+ register SM_FILE_T *fp;
+{
+ SM_REQUIRE_ISA(fp, SmFileMagic);
+ if (!fp->f_flags)
+ {
+ errno = EBADF;
+ return SM_IO_EOF;
+ }
+
+ if (HASUB(fp))
+ FREEUB(fp);
+ fp->f_p = fp->f_bf.smb_base;
+ fp->f_r = 0;
+
+ /* implies SMFBF */
+ fp->f_w = fp->f_flags & (SMLBF|SMNBF) ? 0 : fp->f_bf.smb_size;
+ return 0;
+}
diff --git a/usr/src/cmd/sendmail/libsm/fput.c b/usr/src/cmd/sendmail/libsm/fput.c
new file mode 100644
index 0000000000..6a787758eb
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/fput.c
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: fput.c,v 1.18 2001/01/28 00:29:35 ca Exp $")
+#include <string.h>
+#include <errno.h>
+#include <sm/io.h>
+#include <sm/assert.h>
+#include "local.h"
+#include "fvwrite.h"
+
+/*
+** SM_IO_FPUTS -- add a string to the buffer for the file pointer
+**
+** Parameters:
+** fp -- the file pointer for the buffer to be written to
+** timeout -- time to complete the put-string
+** s -- string to be placed in the buffer
+**
+** Returns:
+** Failure: returns SM_IO_EOF
+** Success: returns 0 (zero)
+*/
+
+int
+sm_io_fputs(fp, timeout, s)
+ SM_FILE_T *fp;
+ int timeout;
+ const char *s;
+{
+ struct sm_uio uio;
+ struct sm_iov iov;
+
+ SM_REQUIRE_ISA(fp, SmFileMagic);
+ iov.iov_base = (void *) s;
+ iov.iov_len = uio.uio_resid = strlen(s);
+ uio.uio_iov = &iov;
+ uio.uio_iovcnt = 1;
+ return sm_fvwrite(fp, timeout, &uio);
+}
diff --git a/usr/src/cmd/sendmail/libsm/fread.c b/usr/src/cmd/sendmail/libsm/fread.c
new file mode 100644
index 0000000000..40cd23cfcd
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/fread.c
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: fread.c,v 1.26 2001/02/28 20:54:03 ca Exp $")
+#include <string.h>
+#include <errno.h>
+#include <sm/io.h>
+#include <sm/assert.h>
+#include "local.h"
+
+/*
+** SM_IO_READ -- read data from the file pointer
+**
+** Parameters:
+** fp -- file pointer to read from
+** timeout -- time to complete the read
+** buf -- location to place read data
+** size -- size of each chunk of data
+**
+** Returns:
+** Failure: returns 0 (zero) _and_ sets errno
+** Success: returns the number of whole chunks read.
+**
+** A read returning 0 (zero) is only an indication of error when errno
+** has been set.
+*/
+
+size_t
+sm_io_read(fp, timeout, buf, size)
+ SM_FILE_T *fp;
+ int timeout;
+ void *buf;
+ size_t size;
+{
+ register size_t resid = size;
+ register char *p;
+ register int r;
+
+ SM_REQUIRE_ISA(fp, SmFileMagic);
+
+ if (fp->f_read == NULL)
+ {
+ errno = ENODEV;
+ return 0;
+ }
+
+ /*
+ ** The ANSI standard requires a return value of 0 for a count
+ ** or a size of 0. Peculiarily, it imposes no such requirements
+ ** on fwrite; it only requires read to be broken.
+ */
+
+ if (resid == 0)
+ return 0;
+ if (fp->f_r < 0)
+ fp->f_r = 0;
+ p = buf;
+ while ((int) resid > (r = fp->f_r))
+ {
+ (void) memcpy((void *) p, (void *) fp->f_p, (size_t) r);
+ fp->f_p += r;
+ /* fp->f_r = 0 ... done in sm_refill */
+ p += r;
+ resid -= r;
+ if ((fp->f_flags & SMNOW) != 0 && r > 0)
+ {
+ /*
+ ** Take whatever we have available. Spend no more time
+ ** trying to get all that has been requested.
+ ** This is needed on some file types (such as
+ ** SASL) that would jam when given extra, untimely
+ ** reads.
+ */
+
+ fp->f_r -= r;
+ return size - resid;
+ }
+ if (sm_refill(fp, timeout) != 0)
+ {
+ /* no more input: return partial result */
+ return size - resid;
+ }
+ }
+ (void) memcpy((void *) p, (void *) fp->f_p, resid);
+ fp->f_r -= resid;
+ fp->f_p += resid;
+ return size;
+}
diff --git a/usr/src/cmd/sendmail/libsm/fscanf.c b/usr/src/cmd/sendmail/libsm/fscanf.c
new file mode 100644
index 0000000000..c456740c66
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/fscanf.c
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: fscanf.c,v 1.15 2001/03/02 23:53:41 ca Exp $")
+#include <sm/varargs.h>
+#include <sm/assert.h>
+#include <sm/io.h>
+#include "local.h"
+
+/*
+** SM_IO_FSCANF -- convert input data to translated format
+**
+** Parameters:
+** fp -- the file pointer to obtain the data from
+** timeout -- time to complete scan
+** fmt -- the format to translate the data to
+** ... -- memory locations to place the formated data
+**
+** Returns:
+** Failure: returns SM_IO_EOF
+** Success: returns the number of data units translated
+*/
+
+int
+#if SM_VA_STD
+sm_io_fscanf(SM_FILE_T *fp, int timeout, char const *fmt, ...)
+#else /* SM_VA_STD */
+sm_io_fscanf(fp, timeout, fmt, va_alist)
+ SM_FILE_T *fp;
+ int timeout;
+ char *fmt;
+ va_dcl
+#endif /* SM_VA_STD */
+{
+ int ret;
+ SM_VA_LOCAL_DECL
+
+ SM_REQUIRE_ISA(fp, SmFileMagic);
+ SM_VA_START(ap, fmt);
+ ret = sm_vfscanf(fp, timeout, fmt, ap);
+ SM_VA_END(ap);
+ return ret;
+}
diff --git a/usr/src/cmd/sendmail/libsm/fseek.c b/usr/src/cmd/sendmail/libsm/fseek.c
new file mode 100644
index 0000000000..d461aba89b
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/fseek.c
@@ -0,0 +1,338 @@
+/*
+ * Copyright (c) 2000-2001, 2004 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: fseek.c,v 1.46 2004/08/03 20:17:38 ca Exp $")
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <setjmp.h>
+#include <sys/time.h>
+#include <sm/signal.h>
+#include <sm/io.h>
+#include <sm/assert.h>
+#include <sm/clock.h>
+#include "local.h"
+
+#define POS_ERR (-(off_t)1)
+
+static void seekalrm __P((int));
+static jmp_buf SeekTimeOut;
+
+/*
+** SEEKALRM -- handler when timeout activated for sm_io_seek()
+**
+** Returns flow of control to where setjmp(SeekTimeOut) was set.
+**
+** Parameters:
+** sig -- unused
+**
+** Returns:
+** does not return
+**
+** Side Effects:
+** returns flow of control to setjmp(SeekTimeOut).
+**
+** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+** DOING.
+*/
+
+/* ARGSUSED0 */
+static void
+seekalrm(sig)
+ int sig;
+{
+ longjmp(SeekTimeOut, 1);
+}
+
+/*
+** SM_IO_SEEK -- position the file pointer
+**
+** Parameters:
+** fp -- the file pointer to be seek'd
+** timeout -- time to complete seek (milliseconds)
+** offset -- seek offset based on 'whence'
+** whence -- indicates where seek is relative from.
+** One of SM_IO_SEEK_{CUR,SET,END}.
+** Returns:
+** Failure: returns -1 (minus 1) and sets errno
+** Success: returns 0 (zero)
+*/
+
+int
+sm_io_seek(fp, timeout, offset, whence)
+ register SM_FILE_T *fp;
+ int SM_NONVOLATILE timeout;
+ long SM_NONVOLATILE offset;
+ int SM_NONVOLATILE whence;
+{
+ bool havepos;
+ off_t target, curoff;
+ size_t n;
+ struct stat st;
+ int ret;
+ SM_EVENT *evt = NULL;
+ register off_t (*seekfn) __P((SM_FILE_T *, off_t, int));
+
+ SM_REQUIRE_ISA(fp, SmFileMagic);
+
+ /* make sure stdio is set up */
+ if (!Sm_IO_DidInit)
+ sm_init();
+
+ /* Have to be able to seek. */
+ if ((seekfn = fp->f_seek) == NULL)
+ {
+ errno = ESPIPE; /* historic practice */
+ return -1;
+ }
+
+ if (timeout == SM_TIME_DEFAULT)
+ timeout = fp->f_timeout;
+ if (timeout == SM_TIME_IMMEDIATE)
+ {
+ /*
+ ** Filling the buffer will take time and we are wanted to
+ ** return immediately. So...
+ */
+
+ errno = EAGAIN;
+ return -1;
+ }
+
+#define SM_SET_ALARM() \
+ if (timeout != SM_TIME_FOREVER) \
+ { \
+ if (setjmp(SeekTimeOut) != 0) \
+ { \
+ errno = EAGAIN; \
+ return -1; \
+ } \
+ evt = sm_seteventm(timeout, seekalrm, 0); \
+ }
+
+ /*
+ ** Change any SM_IO_SEEK_CUR to SM_IO_SEEK_SET, and check `whence'
+ ** argument. After this, whence is either SM_IO_SEEK_SET or
+ ** SM_IO_SEEK_END.
+ */
+
+ switch (whence)
+ {
+ case SM_IO_SEEK_CUR:
+
+ /*
+ ** In order to seek relative to the current stream offset,
+ ** we have to first find the current stream offset a la
+ ** ftell (see ftell for details).
+ */
+
+ /* may adjust seek offset on append stream */
+ sm_flush(fp, (int *) &timeout);
+ SM_SET_ALARM();
+ if (fp->f_flags & SMOFF)
+ curoff = fp->f_lseekoff;
+ else
+ {
+ curoff = (*seekfn)(fp, (off_t) 0, SM_IO_SEEK_CUR);
+ if (curoff == -1L)
+ {
+ ret = -1;
+ goto clean;
+ }
+ }
+ if (fp->f_flags & SMRD)
+ {
+ curoff -= fp->f_r;
+ if (HASUB(fp))
+ curoff -= fp->f_ur;
+ }
+ else if (fp->f_flags & SMWR && fp->f_p != NULL)
+ curoff += fp->f_p - fp->f_bf.smb_base;
+
+ offset += curoff;
+ whence = SM_IO_SEEK_SET;
+ havepos = true;
+ break;
+
+ case SM_IO_SEEK_SET:
+ case SM_IO_SEEK_END:
+ SM_SET_ALARM();
+ curoff = 0; /* XXX just to keep gcc quiet */
+ havepos = false;
+ break;
+
+ default:
+ errno = EINVAL;
+ return -1;
+ }
+
+ /*
+ ** Can only optimise if:
+ ** reading (and not reading-and-writing);
+ ** not unbuffered; and
+ ** this is a `regular' Unix file (and hence seekfn==sm_stdseek).
+ ** We must check SMNBF first, because it is possible to have SMNBF
+ ** and SMSOPT both set.
+ */
+
+ if (fp->f_bf.smb_base == NULL)
+ sm_makebuf(fp);
+ if (fp->f_flags & (SMWR | SMRW | SMNBF | SMNPT))
+ goto dumb;
+ if ((fp->f_flags & SMOPT) == 0)
+ {
+ if (seekfn != sm_stdseek ||
+ fp->f_file < 0 || fstat(fp->f_file, &st) ||
+ (st.st_mode & S_IFMT) != S_IFREG)
+ {
+ fp->f_flags |= SMNPT;
+ goto dumb;
+ }
+ fp->f_blksize = st.st_blksize;
+ fp->f_flags |= SMOPT;
+ }
+
+ /*
+ ** We are reading; we can try to optimise.
+ ** Figure out where we are going and where we are now.
+ */
+
+ if (whence == SM_IO_SEEK_SET)
+ target = offset;
+ else
+ {
+ if (fstat(fp->f_file, &st))
+ goto dumb;
+ target = st.st_size + offset;
+ }
+
+ if (!havepos)
+ {
+ if (fp->f_flags & SMOFF)
+ curoff = fp->f_lseekoff;
+ else
+ {
+ curoff = (*seekfn)(fp, (off_t) 0, SM_IO_SEEK_CUR);
+ if (curoff == POS_ERR)
+ goto dumb;
+ }
+ curoff -= fp->f_r;
+ if (HASUB(fp))
+ curoff -= fp->f_ur;
+ }
+
+ /*
+ ** Compute the number of bytes in the input buffer (pretending
+ ** that any ungetc() input has been discarded). Adjust current
+ ** offset backwards by this count so that it represents the
+ ** file offset for the first byte in the current input buffer.
+ */
+
+ if (HASUB(fp))
+ {
+ curoff += fp->f_r; /* kill off ungetc */
+ n = fp->f_up - fp->f_bf.smb_base;
+ curoff -= n;
+ n += fp->f_ur;
+ }
+ else
+ {
+ n = fp->f_p - fp->f_bf.smb_base;
+ curoff -= n;
+ n += fp->f_r;
+ }
+
+ /*
+ ** If the target offset is within the current buffer,
+ ** simply adjust the pointers, clear SMFEOF, undo ungetc(),
+ ** and return. (If the buffer was modified, we have to
+ ** skip this; see getln in fget.c.)
+ */
+
+ if (target >= curoff && target < curoff + (off_t) n)
+ {
+ register int o = target - curoff;
+
+ fp->f_p = fp->f_bf.smb_base + o;
+ fp->f_r = n - o;
+ if (HASUB(fp))
+ FREEUB(fp);
+ fp->f_flags &= ~SMFEOF;
+ ret = 0;
+ goto clean;
+ }
+
+ /*
+ ** The place we want to get to is not within the current buffer,
+ ** but we can still be kind to the kernel copyout mechanism.
+ ** By aligning the file offset to a block boundary, we can let
+ ** the kernel use the VM hardware to map pages instead of
+ ** copying bytes laboriously. Using a block boundary also
+ ** ensures that we only read one block, rather than two.
+ */
+
+ curoff = target & ~(fp->f_blksize - 1);
+ if ((*seekfn)(fp, curoff, SM_IO_SEEK_SET) == POS_ERR)
+ goto dumb;
+ fp->f_r = 0;
+ fp->f_p = fp->f_bf.smb_base;
+ if (HASUB(fp))
+ FREEUB(fp);
+ fp->f_flags &= ~SMFEOF;
+ n = target - curoff;
+ if (n)
+ {
+ /* Note: SM_TIME_FOREVER since fn timeout already set */
+ if (sm_refill(fp, SM_TIME_FOREVER) || fp->f_r < (int) n)
+ goto dumb;
+ fp->f_p += n;
+ fp->f_r -= n;
+ }
+
+ ret = 0;
+clean:
+ /* We're back. So undo our timeout and handler */
+ if (evt != NULL)
+ sm_clrevent(evt);
+ return ret;
+dumb:
+ /*
+ ** We get here if we cannot optimise the seek ... just
+ ** do it. Allow the seek function to change fp->f_bf.smb_base.
+ */
+
+ /* Note: SM_TIME_FOREVER since fn timeout already set */
+ ret = SM_TIME_FOREVER;
+ if (sm_flush(fp, &ret) != 0 ||
+ (*seekfn)(fp, (off_t) offset, whence) == POS_ERR)
+ {
+ ret = -1;
+ goto clean;
+ }
+
+ /* success: clear SMFEOF indicator and discard ungetc() data */
+ if (HASUB(fp))
+ FREEUB(fp);
+ fp->f_p = fp->f_bf.smb_base;
+ fp->f_r = 0;
+ fp->f_flags &= ~SMFEOF;
+ ret = 0;
+ goto clean;
+}
diff --git a/usr/src/cmd/sendmail/libsm/fvwrite.c b/usr/src/cmd/sendmail/libsm/fvwrite.c
new file mode 100644
index 0000000000..4cdd022fe0
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/fvwrite.c
@@ -0,0 +1,281 @@
+/*
+ * Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: fvwrite.c,v 1.47 2001/08/27 13:02:20 ca Exp $")
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <sm/io.h>
+#include <sm/setjmp.h>
+#include <sm/conf.h>
+#include "local.h"
+#include "fvwrite.h"
+
+/*
+** SM_FVWRITE -- write memory regions and buffer for file pointer
+**
+** Parameters:
+** fp -- the file pointer to write to
+** timeout -- time length for function to return by
+** uio -- the memory regions to write
+**
+** Returns:
+** Failure: returns SM_IO_EOF and sets errno
+** Success: returns 0 (zero)
+**
+** This routine is large and unsightly, but most of the ugliness due
+** to the different kinds of output buffering handled here.
+*/
+
+#define COPY(n) (void)memcpy((void *)fp->f_p, (void *)p, (size_t)(n))
+#define GETIOV(extra_work) \
+ while (len == 0) \
+ { \
+ extra_work; \
+ p = iov->iov_base; \
+ len = iov->iov_len; \
+ iov++; \
+ }
+
+int
+sm_fvwrite(fp, timeout, uio)
+ register SM_FILE_T *fp;
+ int timeout;
+ register struct sm_uio *uio;
+{
+ register size_t len;
+ register char *p;
+ register struct sm_iov *iov;
+ register int w, s;
+ char *nl;
+ int nlknown, nldist;
+ int fd;
+ struct timeval to;
+
+ if (uio->uio_resid == 0)
+ return 0;
+
+ /* make sure we can write */
+ if (cantwrite(fp))
+ {
+ errno = EBADF;
+ return SM_IO_EOF;
+ }
+
+ SM_CONVERT_TIME(fp, fd, timeout, &to);
+
+ iov = uio->uio_iov;
+ p = iov->iov_base;
+ len = iov->iov_len;
+ iov++;
+ if (fp->f_flags & SMNBF)
+ {
+ /* Unbuffered: write up to BUFSIZ bytes at a time. */
+ do
+ {
+ GETIOV(;);
+ errno = 0; /* needed to ensure EOF correctly found */
+ w = (*fp->f_write)(fp, p, SM_MIN(len, SM_IO_BUFSIZ));
+ if (w <= 0)
+ {
+ if (w == 0 && errno == 0)
+ break; /* EOF found */
+ if (IS_IO_ERROR(fd, w, timeout))
+ goto err; /* errno set */
+
+ /* write would block */
+ SM_IO_WR_TIMEOUT(fp, fd, timeout);
+ w = 0;
+ }
+ else
+ {
+ p += w;
+ len -= w;
+ }
+ } while ((uio->uio_resid -= w) != 0);
+ }
+ else if ((fp->f_flags & SMLBF) == 0)
+ {
+ /*
+ ** Not SMLBF (line-buffered). Either SMFBF or SMNOW
+ ** buffered: fill partially full buffer, if any,
+ ** and then flush. If there is no partial buffer, write
+ ** one bf._size byte chunk directly (without copying).
+ **
+ ** String output is a special case: write as many bytes
+ ** as fit, but pretend we wrote everything. This makes
+ ** snprintf() return the number of bytes needed, rather
+ ** than the number used, and avoids its write function
+ ** (so that the write function can be invalid).
+ */
+
+ do
+ {
+ GETIOV(;);
+ if ((((fp->f_flags & (SMALC | SMSTR)) == (SMALC | SMSTR))
+ || ((fp->f_flags & SMNOW) != 0))
+ && (size_t) fp->f_w < len)
+ {
+ size_t blen = fp->f_p - fp->f_bf.smb_base;
+ unsigned char *tbase;
+ int tsize;
+
+ /* Allocate space exponentially. */
+ tsize = fp->f_bf.smb_size;
+ do
+ {
+ tsize = (tsize << 1) + 1;
+ } while ((size_t) tsize < blen + len);
+ tbase = (unsigned char *) sm_realloc(fp->f_bf.smb_base,
+ tsize + 1);
+ if (tbase == NULL)
+ {
+ errno = ENOMEM;
+ goto err; /* errno set */
+ }
+ fp->f_w += tsize - fp->f_bf.smb_size;
+ fp->f_bf.smb_base = tbase;
+ fp->f_bf.smb_size = tsize;
+ fp->f_p = tbase + blen;
+ }
+ w = fp->f_w;
+ errno = 0; /* needed to ensure EOF correctly found */
+ if (fp->f_flags & SMSTR)
+ {
+ if (len < (size_t) w)
+ w = len;
+ COPY(w); /* copy SM_MIN(fp->f_w,len), */
+ fp->f_w -= w;
+ fp->f_p += w;
+ w = len; /* but pretend copied all */
+ }
+ else if (fp->f_p > fp->f_bf.smb_base
+ && len > (size_t) w)
+ {
+ /* fill and flush */
+ COPY(w);
+ fp->f_p += w;
+ if (sm_flush(fp, &timeout))
+ goto err; /* errno set */
+ }
+ else if (len >= (size_t) (w = fp->f_bf.smb_size))
+ {
+ /* write directly */
+ w = (*fp->f_write)(fp, p, w);
+ if (w <= 0)
+ {
+ if (w == 0 && errno == 0)
+ break; /* EOF found */
+ if (IS_IO_ERROR(fd, w, timeout))
+ goto err; /* errno set */
+
+ /* write would block */
+ SM_IO_WR_TIMEOUT(fp, fd, timeout);
+ w = 0;
+ }
+ }
+ else
+ {
+ /* fill and done */
+ w = len;
+ COPY(w);
+ fp->f_w -= w;
+ fp->f_p += w;
+ }
+ p += w;
+ len -= w;
+ } while ((uio->uio_resid -= w) != 0);
+
+ if ((fp->f_flags & SMNOW) != 0 && sm_flush(fp, &timeout))
+ goto err; /* errno set */
+ }
+ else
+ {
+ /*
+ ** Line buffered: like fully buffered, but we
+ ** must check for newlines. Compute the distance
+ ** to the first newline (including the newline),
+ ** or `infinity' if there is none, then pretend
+ ** that the amount to write is SM_MIN(len,nldist).
+ */
+
+ nlknown = 0;
+ nldist = 0; /* XXX just to keep gcc happy */
+ do
+ {
+ GETIOV(nlknown = 0);
+ if (!nlknown)
+ {
+ nl = memchr((void *)p, '\n', len);
+ nldist = nl != NULL ? nl + 1 - p : len + 1;
+ nlknown = 1;
+ }
+ s = SM_MIN(len, ((size_t) nldist));
+ w = fp->f_w + fp->f_bf.smb_size;
+ errno = 0; /* needed to ensure EOF correctly found */
+ if (fp->f_p > fp->f_bf.smb_base && s > w)
+ {
+ COPY(w);
+ /* fp->f_w -= w; */
+ fp->f_p += w;
+ if (sm_flush(fp, &timeout))
+ goto err; /* errno set */
+ }
+ else if (s >= (w = fp->f_bf.smb_size))
+ {
+ w = (*fp->f_write)(fp, p, w);
+ if (w <= 0)
+ {
+ if (w == 0 && errno == 0)
+ break; /* EOF found */
+ if (IS_IO_ERROR(fd, w, timeout))
+ goto err; /* errno set */
+
+ /* write would block */
+ SM_IO_WR_TIMEOUT(fp, fd, timeout);
+ w = 0;
+ }
+ }
+ else
+ {
+ w = s;
+ COPY(w);
+ fp->f_w -= w;
+ fp->f_p += w;
+ }
+ if ((nldist -= w) == 0)
+ {
+ /* copied the newline: flush and forget */
+ if (sm_flush(fp, &timeout))
+ goto err; /* errno set */
+ nlknown = 0;
+ }
+ p += w;
+ len -= w;
+ } while ((uio->uio_resid -= w) != 0);
+ }
+
+ return 0;
+
+err:
+ /* errno set before goto places us here */
+ fp->f_flags |= SMERR;
+ return SM_IO_EOF;
+}
diff --git a/usr/src/cmd/sendmail/libsm/fvwrite.h b/usr/src/cmd/sendmail/libsm/fvwrite.h
new file mode 100644
index 0000000000..f7344e5535
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/fvwrite.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ * $Id: fvwrite.h,v 1.7 2001/03/02 00:18:19 ca Exp $
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/* I/O descriptors for sm_fvwrite() */
+struct sm_iov
+{
+ void *iov_base;
+ size_t iov_len;
+};
+struct sm_uio
+{
+ struct sm_iov *uio_iov;
+ int uio_iovcnt;
+ int uio_resid;
+};
+
+extern int sm_fvwrite __P((SM_FILE_T *, int, struct sm_uio *));
diff --git a/usr/src/cmd/sendmail/libsm/fwalk.c b/usr/src/cmd/sendmail/libsm/fwalk.c
new file mode 100644
index 0000000000..2c0522e989
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/fwalk.c
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: fwalk.c,v 1.19 2001/03/02 03:22:18 ca Exp $")
+#include <errno.h>
+#include <sm/io.h>
+#include "local.h"
+#include "glue.h"
+
+/*
+** SM_FWALK -- apply a function to all found-open file pointers
+**
+** Parameters:
+** function -- a function vector to be applied
+** timeout -- time to complete actions (milliseconds)
+**
+** Returns:
+** The (binary) OR'd result of each function call
+*/
+
+int
+sm_fwalk(function, timeout)
+ int (*function) __P((SM_FILE_T *, int *));
+ int *timeout;
+{
+ register SM_FILE_T *fp;
+ register int n, ret;
+ register struct sm_glue *g;
+ int fptimeout;
+
+ ret = 0;
+ for (g = &smglue; g != NULL; g = g->gl_next)
+ {
+ for (fp = g->gl_iobs, n = g->gl_niobs; --n >= 0; fp++)
+ {
+ if (fp->f_flags != 0)
+ {
+ if (*timeout == SM_TIME_DEFAULT)
+ fptimeout = fp->f_timeout;
+ else
+ fptimeout = *timeout;
+ if (fptimeout == SM_TIME_IMMEDIATE)
+ continue; /* skip it */
+ ret |= (*function)(fp, &fptimeout);
+ }
+ }
+ }
+ return ret;
+}
diff --git a/usr/src/cmd/sendmail/libsm/fwrite.c b/usr/src/cmd/sendmail/libsm/fwrite.c
new file mode 100644
index 0000000000..f99eab1bb2
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/fwrite.c
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: fwrite.c,v 1.22 2001/04/03 01:46:40 ca Exp $")
+#include <errno.h>
+#include <sm/io.h>
+#include <sm/assert.h>
+#include "local.h"
+#include "fvwrite.h"
+
+/*
+** SM_IO_WRITE -- write to a file pointer
+**
+** Parameters:
+** fp -- file pointer writing to
+** timeout -- time to complete the write
+** buf -- location of data to be written
+** size -- number of bytes to be written
+**
+** Result:
+** Failure: returns 0 _and_ sets errno
+** Success: returns >=0 with errno unchanged, where the
+** number returned is the number of bytes written.
+*/
+
+size_t
+sm_io_write(fp, timeout, buf, size)
+ SM_FILE_T *fp;
+ int timeout;
+ const void *buf;
+ size_t size;
+{
+ struct sm_uio uio;
+ struct sm_iov iov;
+
+ SM_REQUIRE_ISA(fp, SmFileMagic);
+
+ if (fp->f_write == NULL)
+ {
+ errno = ENODEV;
+ return 0;
+ }
+
+ iov.iov_base = (void *) buf;
+ uio.uio_resid = iov.iov_len = size;
+ uio.uio_iov = &iov;
+ uio.uio_iovcnt = 1;
+
+ /* The usual case is success (sm_fvwrite returns 0) */
+ if (sm_fvwrite(fp, timeout, &uio) == 0)
+ return size;
+
+ /* else return number of bytes actually written */
+ return size - uio.uio_resid;
+}
diff --git a/usr/src/cmd/sendmail/libsm/get.c b/usr/src/cmd/sendmail/libsm/get.c
new file mode 100644
index 0000000000..26226309f9
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/get.c
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: get.c,v 1.16 2001/01/28 00:29:35 ca Exp $")
+#include <sm/io.h>
+#include <sm/assert.h>
+#include "local.h"
+
+/*
+** SM_IO_GETC -- get a character from a file
+**
+** Parameters:
+** fp -- the file to get the character from
+** timeout -- time to complete getc
+**
+** Returns:
+** Success: the value of the character read.
+** Failure: SM_IO_EOF
+**
+** This is a function version of the macro (in <sm/io.h>).
+** It is guarded with locks (which are currently not functional)
+** for multi-threaded programs.
+*/
+
+#undef sm_io_getc
+
+int
+sm_io_getc(fp, timeout)
+ register SM_FILE_T *fp;
+ int timeout;
+{
+ SM_REQUIRE_ISA(fp, SmFileMagic);
+ return sm_getc(fp, timeout);
+}
diff --git a/usr/src/cmd/sendmail/libsm/glue.h b/usr/src/cmd/sendmail/libsm/glue.h
new file mode 100644
index 0000000000..6993bb4292
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/glue.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ * $Id: glue.h,v 1.6 2001/01/22 23:09:49 ca Exp $
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+** The first few FILEs are statically allocated; others are dynamically
+** allocated and linked in via this glue structure.
+*/
+
+extern struct sm_glue
+{
+ struct sm_glue *gl_next;
+ int gl_niobs;
+ SM_FILE_T *gl_iobs;
+} smglue;
diff --git a/usr/src/cmd/sendmail/libsm/heap.c b/usr/src/cmd/sendmail/libsm/heap.c
new file mode 100644
index 0000000000..03acf34555
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/heap.c
@@ -0,0 +1,822 @@
+/*
+ * Copyright (c) 2000-2001, 2004 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: heap.c,v 1.51 2004/08/03 20:32:00 ca Exp $")
+
+/*
+** debugging memory allocation package
+** See heap.html for documentation.
+*/
+
+#include <string.h>
+
+#include <sm/assert.h>
+#include <sm/debug.h>
+#include <sm/exc.h>
+#include <sm/heap.h>
+#include <sm/io.h>
+#include <sm/signal.h>
+#include <sm/xtrap.h>
+
+/* undef all macro versions of the "functions" so they can be specified here */
+#undef sm_malloc
+#undef sm_malloc_x
+#undef sm_malloc_tagged
+#undef sm_malloc_tagged_x
+#undef sm_free
+#undef sm_free_tagged
+#undef sm_realloc
+#if SM_HEAP_CHECK
+# undef sm_heap_register
+# undef sm_heap_checkptr
+# undef sm_heap_report
+#endif /* SM_HEAP_CHECK */
+
+#if SM_HEAP_CHECK
+SM_DEBUG_T SmHeapCheck = SM_DEBUG_INITIALIZER("sm_check_heap",
+ "@(#)$Debug: sm_check_heap - check sm_malloc, sm_realloc, sm_free calls $");
+# define HEAP_CHECK sm_debug_active(&SmHeapCheck, 1)
+static int ptrhash __P((void *p));
+#endif /* SM_HEAP_CHECK */
+
+const SM_EXC_TYPE_T SmHeapOutOfMemoryType =
+{
+ SmExcTypeMagic,
+ "F:sm.heap",
+ "",
+ sm_etype_printf,
+ "out of memory",
+};
+
+SM_EXC_T SmHeapOutOfMemory = SM_EXC_INITIALIZER(&SmHeapOutOfMemoryType, NULL);
+
+
+/*
+** The behaviour of malloc with size==0 is platform dependent (it
+** says so in the C standard): it can return NULL or non-NULL. We
+** don't want sm_malloc_x(0) to raise an exception on some platforms
+** but not others, so this case requires special handling. We've got
+** two choices: "size = 1" or "return NULL". We use the former in the
+** following.
+** If we had something like autoconf we could figure out the
+** behaviour of the platform and either use this hack or just
+** use size.
+*/
+
+#define MALLOC_SIZE(size) ((size) == 0 ? 1 : (size))
+
+/*
+** SM_MALLOC_X -- wrapper around malloc(), raises an exception on error.
+**
+** Parameters:
+** size -- size of requested memory.
+**
+** Returns:
+** Pointer to memory region.
+**
+** Note:
+** sm_malloc_x only gets called from source files in which heap
+** debugging is disabled at compile time. Otherwise, a call to
+** sm_malloc_x is macro expanded to a call to sm_malloc_tagged_x.
+**
+** Exceptions:
+** F:sm_heap -- out of memory
+*/
+
+void *
+sm_malloc_x(size)
+ size_t size;
+{
+ void *ptr;
+
+ ENTER_CRITICAL();
+ ptr = malloc(MALLOC_SIZE(size));
+ LEAVE_CRITICAL();
+ if (ptr == NULL)
+ sm_exc_raise_x(&SmHeapOutOfMemory);
+ return ptr;
+}
+
+#if !SM_HEAP_CHECK
+
+/*
+** SM_MALLOC -- wrapper around malloc()
+**
+** Parameters:
+** size -- size of requested memory.
+**
+** Returns:
+** Pointer to memory region.
+*/
+
+void *
+sm_malloc(size)
+ size_t size;
+{
+ void *ptr;
+
+ ENTER_CRITICAL();
+ ptr = malloc(MALLOC_SIZE(size));
+ LEAVE_CRITICAL();
+ return ptr;
+}
+
+/*
+** SM_REALLOC -- wrapper for realloc()
+**
+** Parameters:
+** ptr -- pointer to old memory area.
+** size -- size of requested memory.
+**
+** Returns:
+** Pointer to new memory area, NULL on failure.
+*/
+
+void *
+sm_realloc(ptr, size)
+ void *ptr;
+ size_t size;
+{
+ void *newptr;
+
+ ENTER_CRITICAL();
+ newptr = realloc(ptr, MALLOC_SIZE(size));
+ LEAVE_CRITICAL();
+ return newptr;
+}
+
+/*
+** SM_REALLOC_X -- wrapper for realloc()
+**
+** Parameters:
+** ptr -- pointer to old memory area.
+** size -- size of requested memory.
+**
+** Returns:
+** Pointer to new memory area.
+**
+** Exceptions:
+** F:sm_heap -- out of memory
+*/
+
+void *
+sm_realloc_x(ptr, size)
+ void *ptr;
+ size_t size;
+{
+ void *newptr;
+
+ ENTER_CRITICAL();
+ newptr = realloc(ptr, MALLOC_SIZE(size));
+ LEAVE_CRITICAL();
+ if (newptr == NULL)
+ sm_exc_raise_x(&SmHeapOutOfMemory);
+ return newptr;
+}
+/*
+** SM_FREE -- wrapper around free()
+**
+** Parameters:
+** ptr -- pointer to memory region.
+**
+** Returns:
+** none.
+*/
+
+void
+sm_free(ptr)
+ void *ptr;
+{
+ if (ptr == NULL)
+ return;
+ ENTER_CRITICAL();
+ free(ptr);
+ LEAVE_CRITICAL();
+ return;
+}
+
+#else /* !SM_HEAP_CHECK */
+
+/*
+** Each allocated block is assigned a "group number".
+** By default, all blocks are assigned to group #1.
+** By convention, group #0 is for memory that is never freed.
+** You can use group numbers any way you want, in order to help make
+** sense of sm_heap_report output.
+*/
+
+int SmHeapGroup = 1;
+int SmHeapMaxGroup = 1;
+
+/*
+** Total number of bytes allocated.
+** This is only maintained if the sm_check_heap debug category is active.
+*/
+
+size_t SmHeapTotal = 0;
+
+/*
+** High water mark: the most that SmHeapTotal has ever been.
+*/
+
+size_t SmHeapMaxTotal = 0;
+
+/*
+** Maximum number of bytes that may be allocated at any one time.
+** 0 means no limit.
+** This is only honoured if sm_check_heap is active.
+*/
+
+SM_DEBUG_T SmHeapLimit = SM_DEBUG_INITIALIZER("sm_heap_limit",
+ "@(#)$Debug: sm_heap_limit - max # of bytes permitted in heap $");
+
+/*
+** This is the data structure that keeps track of all currently
+** allocated blocks of memory known to the heap package.
+*/
+
+typedef struct sm_heap_item SM_HEAP_ITEM_T;
+struct sm_heap_item
+{
+ void *hi_ptr;
+ size_t hi_size;
+ char *hi_tag;
+ int hi_num;
+ int hi_group;
+ SM_HEAP_ITEM_T *hi_next;
+};
+
+#define SM_HEAP_TABLE_SIZE 256
+static SM_HEAP_ITEM_T *SmHeapTable[SM_HEAP_TABLE_SIZE];
+
+/*
+** This is a randomly generated table
+** which contains exactly one occurrence
+** of each of the numbers between 0 and 255.
+** It is used by ptrhash.
+*/
+
+static unsigned char hashtab[SM_HEAP_TABLE_SIZE] =
+{
+ 161, 71, 77,187, 15,229, 9,176,221,119,239, 21, 85,138,203, 86,
+ 102, 65, 80,199,235, 32,140, 96,224, 78,126,127,144, 0, 11,179,
+ 64, 30,120, 23,225,226, 33, 50,205,167,130,240,174, 99,206, 73,
+ 231,210,189,162, 48, 93,246, 54,213,141,135, 39, 41,192,236,193,
+ 157, 88, 95,104,188, 63,133,177,234,110,158,214,238,131,233, 91,
+ 125, 82, 94, 79, 66, 92,151, 45,252, 98, 26,183, 7,191,171,106,
+ 145,154,251,100,113, 5, 74, 62, 76,124, 14,217,200, 75,115,190,
+ 103, 28,198,196,169,219, 37,118,150, 18,152,175, 49,136, 6,142,
+ 89, 19,243,254, 47,137, 24,166,180, 10, 40,186,202, 46,184, 67,
+ 148,108,181, 81, 25,241, 13,139, 58, 38, 84,253,201, 12,116, 17,
+ 195, 22,112, 69,255, 43,147,222,111, 56,194,216,149,244, 42,173,
+ 232,220,249,105,207, 51,197,242, 72,211,208, 59,122,230,237,170,
+ 165, 44, 68,123,129,245,143,101, 8,209,215,247,185, 57,218, 53,
+ 114,121, 3,128, 4,204,212,146, 2,155, 83,250, 87, 29, 31,159,
+ 60, 27,107,156,227,182, 1, 61, 36,160,109, 97, 90, 20,168,132,
+ 223,248, 70,164, 55,172, 34, 52,163,117, 35,153,134, 16,178,228
+};
+
+/*
+** PTRHASH -- hash a pointer value
+**
+** Parameters:
+** p -- pointer.
+**
+** Returns:
+** hash value.
+**
+** ptrhash hashes a pointer value to a uniformly distributed random
+** number between 0 and 255.
+**
+** This hash algorithm is based on Peter K. Pearson,
+** "Fast Hashing of Variable-Length Text Strings",
+** in Communications of the ACM, June 1990, vol 33 no 6.
+*/
+
+static int
+ptrhash(p)
+ void *p;
+{
+ int h;
+
+ if (sizeof(void*) == 4 && sizeof(unsigned long) == 4)
+ {
+ unsigned long n = (unsigned long)p;
+
+ h = hashtab[n & 0xFF];
+ h = hashtab[h ^ ((n >> 8) & 0xFF)];
+ h = hashtab[h ^ ((n >> 16) & 0xFF)];
+ h = hashtab[h ^ ((n >> 24) & 0xFF)];
+ }
+# if 0
+ else if (sizeof(void*) == 8 && sizeof(unsigned long) == 8)
+ {
+ unsigned long n = (unsigned long)p;
+
+ h = hashtab[n & 0xFF];
+ h = hashtab[h ^ ((n >> 8) & 0xFF)];
+ h = hashtab[h ^ ((n >> 16) & 0xFF)];
+ h = hashtab[h ^ ((n >> 24) & 0xFF)];
+ h = hashtab[h ^ ((n >> 32) & 0xFF)];
+ h = hashtab[h ^ ((n >> 40) & 0xFF)];
+ h = hashtab[h ^ ((n >> 48) & 0xFF)];
+ h = hashtab[h ^ ((n >> 56) & 0xFF)];
+ }
+# endif /* 0 */
+ else
+ {
+ unsigned char *cp = (unsigned char *)&p;
+ int i;
+
+ h = 0;
+ for (i = 0; i < sizeof(void*); ++i)
+ h = hashtab[h ^ cp[i]];
+ }
+ return h;
+}
+
+/*
+** SM_MALLOC_TAGGED -- wrapper around malloc(), debugging version.
+**
+** Parameters:
+** size -- size of requested memory.
+** tag -- tag for debugging.
+** num -- additional value for debugging.
+** group -- heap group for debugging.
+**
+** Returns:
+** Pointer to memory region.
+*/
+
+void *
+sm_malloc_tagged(size, tag, num, group)
+ size_t size;
+ char *tag;
+ int num;
+ int group;
+{
+ void *ptr;
+
+ if (!HEAP_CHECK)
+ {
+ ENTER_CRITICAL();
+ ptr = malloc(MALLOC_SIZE(size));
+ LEAVE_CRITICAL();
+ return ptr;
+ }
+
+ if (sm_xtrap_check())
+ return NULL;
+ if (sm_debug_active(&SmHeapLimit, 1)
+ && sm_debug_level(&SmHeapLimit) < SmHeapTotal + size)
+ return NULL;
+ ENTER_CRITICAL();
+ ptr = malloc(MALLOC_SIZE(size));
+ LEAVE_CRITICAL();
+ if (ptr != NULL && !sm_heap_register(ptr, size, tag, num, group))
+ {
+ ENTER_CRITICAL();
+ free(ptr);
+ LEAVE_CRITICAL();
+ ptr = NULL;
+ }
+ SmHeapTotal += size;
+ if (SmHeapTotal > SmHeapMaxTotal)
+ SmHeapMaxTotal = SmHeapTotal;
+ return ptr;
+}
+
+/*
+** SM_MALLOC_TAGGED_X -- wrapper around malloc(), debugging version.
+**
+** Parameters:
+** size -- size of requested memory.
+** tag -- tag for debugging.
+** num -- additional value for debugging.
+** group -- heap group for debugging.
+**
+** Returns:
+** Pointer to memory region.
+**
+** Exceptions:
+** F:sm_heap -- out of memory
+*/
+
+void *
+sm_malloc_tagged_x(size, tag, num, group)
+ size_t size;
+ char *tag;
+ int num;
+ int group;
+{
+ void *ptr;
+
+ if (!HEAP_CHECK)
+ {
+ ENTER_CRITICAL();
+ ptr = malloc(MALLOC_SIZE(size));
+ LEAVE_CRITICAL();
+ if (ptr == NULL)
+ sm_exc_raise_x(&SmHeapOutOfMemory);
+ return ptr;
+ }
+
+ sm_xtrap_raise_x(&SmHeapOutOfMemory);
+ if (sm_debug_active(&SmHeapLimit, 1)
+ && sm_debug_level(&SmHeapLimit) < SmHeapTotal + size)
+ {
+ sm_exc_raise_x(&SmHeapOutOfMemory);
+ }
+ ENTER_CRITICAL();
+ ptr = malloc(MALLOC_SIZE(size));
+ LEAVE_CRITICAL();
+ if (ptr != NULL && !sm_heap_register(ptr, size, tag, num, group))
+ {
+ ENTER_CRITICAL();
+ free(ptr);
+ LEAVE_CRITICAL();
+ ptr = NULL;
+ }
+ if (ptr == NULL)
+ sm_exc_raise_x(&SmHeapOutOfMemory);
+ SmHeapTotal += size;
+ if (SmHeapTotal > SmHeapMaxTotal)
+ SmHeapMaxTotal = SmHeapTotal;
+ return ptr;
+}
+
+/*
+** SM_HEAP_REGISTER -- register a pointer into the heap for debugging.
+**
+** Parameters:
+** ptr -- pointer to register.
+** size -- size of requested memory.
+** tag -- tag for debugging.
+** num -- additional value for debugging.
+** group -- heap group for debugging.
+**
+** Returns:
+** true iff successfully registered (not yet in table).
+*/
+
+bool
+sm_heap_register(ptr, size, tag, num, group)
+ void *ptr;
+ size_t size;
+ char *tag;
+ int num;
+ int group;
+{
+ int i;
+ SM_HEAP_ITEM_T *hi;
+
+ if (!HEAP_CHECK)
+ return true;
+ SM_REQUIRE(ptr != NULL);
+ i = ptrhash(ptr);
+# if SM_CHECK_REQUIRE
+
+ /*
+ ** We require that ptr is not already in SmHeapTable.
+ */
+
+ for (hi = SmHeapTable[i]; hi != NULL; hi = hi->hi_next)
+ {
+ if (hi->hi_ptr == ptr)
+ sm_abort("sm_heap_register: ptr %p is already registered (%s:%d)",
+ ptr, hi->hi_tag, hi->hi_num);
+ }
+# endif /* SM_CHECK_REQUIRE */
+ ENTER_CRITICAL();
+ hi = (SM_HEAP_ITEM_T *) malloc(sizeof(SM_HEAP_ITEM_T));
+ LEAVE_CRITICAL();
+ if (hi == NULL)
+ return false;
+ hi->hi_ptr = ptr;
+ hi->hi_size = size;
+ hi->hi_tag = tag;
+ hi->hi_num = num;
+ hi->hi_group = group;
+ hi->hi_next = SmHeapTable[i];
+ SmHeapTable[i] = hi;
+ return true;
+}
+/*
+** SM_REALLOC -- wrapper for realloc(), debugging version.
+**
+** Parameters:
+** ptr -- pointer to old memory area.
+** size -- size of requested memory.
+**
+** Returns:
+** Pointer to new memory area, NULL on failure.
+*/
+
+void *
+sm_realloc(ptr, size)
+ void *ptr;
+ size_t size;
+{
+ void *newptr;
+ SM_HEAP_ITEM_T *hi, **hp;
+
+ if (!HEAP_CHECK)
+ {
+ ENTER_CRITICAL();
+ newptr = realloc(ptr, MALLOC_SIZE(size));
+ LEAVE_CRITICAL();
+ return newptr;
+ }
+
+ if (ptr == NULL)
+ return sm_malloc_tagged(size, "realloc", 0, SmHeapGroup);
+
+ for (hp = &SmHeapTable[ptrhash(ptr)]; *hp != NULL; hp = &(**hp).hi_next)
+ {
+ if ((**hp).hi_ptr == ptr)
+ {
+ if (sm_xtrap_check())
+ return NULL;
+ hi = *hp;
+ if (sm_debug_active(&SmHeapLimit, 1)
+ && sm_debug_level(&SmHeapLimit)
+ < SmHeapTotal - hi->hi_size + size)
+ {
+ return NULL;
+ }
+ ENTER_CRITICAL();
+ newptr = realloc(ptr, MALLOC_SIZE(size));
+ LEAVE_CRITICAL();
+ if (newptr == NULL)
+ return NULL;
+ SmHeapTotal = SmHeapTotal - hi->hi_size + size;
+ if (SmHeapTotal > SmHeapMaxTotal)
+ SmHeapMaxTotal = SmHeapTotal;
+ *hp = hi->hi_next;
+ hi->hi_ptr = newptr;
+ hi->hi_size = size;
+ hp = &SmHeapTable[ptrhash(newptr)];
+ hi->hi_next = *hp;
+ *hp = hi;
+ return newptr;
+ }
+ }
+ sm_abort("sm_realloc: bad argument (%p)", ptr);
+ /* NOTREACHED */
+ return NULL; /* keep Irix compiler happy */
+}
+
+/*
+** SM_REALLOC_X -- wrapper for realloc(), debugging version.
+**
+** Parameters:
+** ptr -- pointer to old memory area.
+** size -- size of requested memory.
+**
+** Returns:
+** Pointer to new memory area.
+**
+** Exceptions:
+** F:sm_heap -- out of memory
+*/
+
+void *
+sm_realloc_x(ptr, size)
+ void *ptr;
+ size_t size;
+{
+ void *newptr;
+ SM_HEAP_ITEM_T *hi, **hp;
+
+ if (!HEAP_CHECK)
+ {
+ ENTER_CRITICAL();
+ newptr = realloc(ptr, MALLOC_SIZE(size));
+ LEAVE_CRITICAL();
+ if (newptr == NULL)
+ sm_exc_raise_x(&SmHeapOutOfMemory);
+ return newptr;
+ }
+
+ if (ptr == NULL)
+ return sm_malloc_tagged_x(size, "realloc", 0, SmHeapGroup);
+
+ for (hp = &SmHeapTable[ptrhash(ptr)]; *hp != NULL; hp = &(**hp).hi_next)
+ {
+ if ((**hp).hi_ptr == ptr)
+ {
+ sm_xtrap_raise_x(&SmHeapOutOfMemory);
+ hi = *hp;
+ if (sm_debug_active(&SmHeapLimit, 1)
+ && sm_debug_level(&SmHeapLimit)
+ < SmHeapTotal - hi->hi_size + size)
+ {
+ sm_exc_raise_x(&SmHeapOutOfMemory);
+ }
+ ENTER_CRITICAL();
+ newptr = realloc(ptr, MALLOC_SIZE(size));
+ LEAVE_CRITICAL();
+ if (newptr == NULL)
+ sm_exc_raise_x(&SmHeapOutOfMemory);
+ SmHeapTotal = SmHeapTotal - hi->hi_size + size;
+ if (SmHeapTotal > SmHeapMaxTotal)
+ SmHeapMaxTotal = SmHeapTotal;
+ *hp = hi->hi_next;
+ hi->hi_ptr = newptr;
+ hi->hi_size = size;
+ hp = &SmHeapTable[ptrhash(newptr)];
+ hi->hi_next = *hp;
+ *hp = hi;
+ return newptr;
+ }
+ }
+ sm_abort("sm_realloc_x: bad argument (%p)", ptr);
+ /* NOTREACHED */
+ return NULL; /* keep Irix compiler happy */
+}
+
+/*
+** SM_FREE_TAGGED -- wrapper around free(), debugging version.
+**
+** Parameters:
+** ptr -- pointer to memory region.
+** tag -- tag for debugging.
+** num -- additional value for debugging.
+**
+** Returns:
+** none.
+*/
+
+void
+sm_free_tagged(ptr, tag, num)
+ void *ptr;
+ char *tag;
+ int num;
+{
+ SM_HEAP_ITEM_T **hp;
+
+ if (ptr == NULL)
+ return;
+ if (!HEAP_CHECK)
+ {
+ ENTER_CRITICAL();
+ free(ptr);
+ LEAVE_CRITICAL();
+ return;
+ }
+ for (hp = &SmHeapTable[ptrhash(ptr)]; *hp != NULL; hp = &(**hp).hi_next)
+ {
+ if ((**hp).hi_ptr == ptr)
+ {
+ SM_HEAP_ITEM_T *hi = *hp;
+
+ *hp = hi->hi_next;
+
+ /*
+ ** Fill the block with zeros before freeing.
+ ** This is intended to catch problems with
+ ** dangling pointers. The block is filled with
+ ** zeros, not with some non-zero value, because
+ ** it is common practice in some C code to store
+ ** a zero in a structure member before freeing the
+ ** structure, as a defense against dangling pointers.
+ */
+
+ (void) memset(ptr, 0, hi->hi_size);
+ SmHeapTotal -= hi->hi_size;
+ ENTER_CRITICAL();
+ free(ptr);
+ free(hi);
+ LEAVE_CRITICAL();
+ return;
+ }
+ }
+ sm_abort("sm_free: bad argument (%p) (%s:%d)", ptr, tag, num);
+}
+
+/*
+** SM_HEAP_CHECKPTR_TAGGED -- check whether ptr is a valid argument to sm_free
+**
+** Parameters:
+** ptr -- pointer to memory region.
+** tag -- tag for debugging.
+** num -- additional value for debugging.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** aborts if check fails.
+*/
+
+void
+sm_heap_checkptr_tagged(ptr, tag, num)
+ void *ptr;
+ char *tag;
+ int num;
+{
+ SM_HEAP_ITEM_T *hp;
+
+ if (!HEAP_CHECK)
+ return;
+ if (ptr == NULL)
+ return;
+ for (hp = SmHeapTable[ptrhash(ptr)]; hp != NULL; hp = hp->hi_next)
+ {
+ if (hp->hi_ptr == ptr)
+ return;
+ }
+ sm_abort("sm_heap_checkptr(%p): bad ptr (%s:%d)", ptr, tag, num);
+}
+
+/*
+** SM_HEAP_REPORT -- output "map" of used heap.
+**
+** Parameters:
+** stream -- the file pointer to write to.
+** verbosity -- how much info?
+**
+** Returns:
+** none.
+*/
+
+void
+sm_heap_report(stream, verbosity)
+ SM_FILE_T *stream;
+ int verbosity;
+{
+ int i;
+ unsigned long group0total, group1total, otherstotal, grandtotal;
+
+ if (!HEAP_CHECK || verbosity <= 0)
+ return;
+ group0total = group1total = otherstotal = grandtotal = 0;
+ for (i = 0; i < sizeof(SmHeapTable) / sizeof(SmHeapTable[0]); ++i)
+ {
+ SM_HEAP_ITEM_T *hi = SmHeapTable[i];
+
+ while (hi != NULL)
+ {
+ if (verbosity > 2
+ || (verbosity > 1 && hi->hi_group != 0))
+ {
+ sm_io_fprintf(stream, SM_TIME_DEFAULT,
+ "%4d %*lx %7lu bytes",
+ hi->hi_group,
+ (int) sizeof(void *) * 2,
+ (long)hi->hi_ptr,
+ (unsigned long)hi->hi_size);
+ if (hi->hi_tag != NULL)
+ {
+ sm_io_fprintf(stream, SM_TIME_DEFAULT,
+ " %s",
+ hi->hi_tag);
+ if (hi->hi_num)
+ {
+ sm_io_fprintf(stream,
+ SM_TIME_DEFAULT,
+ ":%d",
+ hi->hi_num);
+ }
+ }
+ sm_io_fprintf(stream, SM_TIME_DEFAULT, "\n");
+ }
+ switch (hi->hi_group)
+ {
+ case 0:
+ group0total += hi->hi_size;
+ break;
+ case 1:
+ group1total += hi->hi_size;
+ break;
+ default:
+ otherstotal += hi->hi_size;
+ break;
+ }
+ grandtotal += hi->hi_size;
+ hi = hi->hi_next;
+ }
+ }
+ sm_io_fprintf(stream, SM_TIME_DEFAULT,
+ "heap max=%lu, total=%lu, ",
+ (unsigned long) SmHeapMaxTotal, grandtotal);
+ sm_io_fprintf(stream, SM_TIME_DEFAULT,
+ "group 0=%lu, group 1=%lu, others=%lu\n",
+ group0total, group1total, otherstotal);
+ if (grandtotal != SmHeapTotal)
+ {
+ sm_io_fprintf(stream, SM_TIME_DEFAULT,
+ "BUG => SmHeapTotal: got %lu, expected %lu\n",
+ (unsigned long) SmHeapTotal, grandtotal);
+ }
+}
+#endif /* !SM_HEAP_CHECK */
diff --git a/usr/src/cmd/sendmail/libsm/ldap.c b/usr/src/cmd/sendmail/libsm/ldap.c
new file mode 100644
index 0000000000..f438b91823
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/ldap.c
@@ -0,0 +1,1334 @@
+/*
+ * Copyright (c) 2001-2005 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: ldap.c,v 1.62 2005/02/24 00:30:01 ca Exp $")
+
+#if LDAPMAP
+# include <sys/types.h>
+# include <errno.h>
+# include <setjmp.h>
+# include <stdlib.h>
+# include <unistd.h>
+
+# include <sm/bitops.h>
+# include <sm/clock.h>
+# include <sm/conf.h>
+# include <sm/debug.h>
+# include <sm/errstring.h>
+# include <sm/ldap.h>
+# include <sm/string.h>
+# ifdef EX_OK
+# undef EX_OK /* for SVr4.2 SMP */
+# endif /* EX_OK */
+# include <sm/sysexits.h>
+
+SM_DEBUG_T SmLDAPTrace = SM_DEBUG_INITIALIZER("sm_trace_ldap",
+ "@(#)$Debug: sm_trace_ldap - trace LDAP operations $");
+
+static void ldaptimeout __P((int));
+static bool sm_ldap_has_objectclass __P((SM_LDAP_STRUCT *, LDAPMessage *, char *));
+static SM_LDAP_RECURSE_ENTRY *sm_ldap_add_recurse __P((SM_LDAP_RECURSE_LIST **, char *, int, SM_RPOOL_T *));
+
+/*
+** SM_LDAP_CLEAR -- set default values for SM_LDAP_STRUCT
+**
+** Parameters:
+** lmap -- pointer to SM_LDAP_STRUCT to clear
+**
+** Returns:
+** None.
+**
+*/
+
+void
+sm_ldap_clear(lmap)
+ SM_LDAP_STRUCT *lmap;
+{
+ if (lmap == NULL)
+ return;
+
+ lmap->ldap_host = NULL;
+ lmap->ldap_port = LDAP_PORT;
+ lmap->ldap_uri = NULL;
+ lmap->ldap_version = 0;
+ lmap->ldap_deref = LDAP_DEREF_NEVER;
+ lmap->ldap_timelimit = LDAP_NO_LIMIT;
+ lmap->ldap_sizelimit = LDAP_NO_LIMIT;
+# ifdef LDAP_REFERRALS
+ lmap->ldap_options = LDAP_OPT_REFERRALS;
+# else /* LDAP_REFERRALS */
+ lmap->ldap_options = 0;
+# endif /* LDAP_REFERRALS */
+ lmap->ldap_attrsep = '\0';
+ lmap->ldap_binddn = NULL;
+ lmap->ldap_secret = NULL;
+ lmap->ldap_method = LDAP_AUTH_SIMPLE;
+ lmap->ldap_base = NULL;
+ lmap->ldap_scope = LDAP_SCOPE_SUBTREE;
+ lmap->ldap_attrsonly = LDAPMAP_FALSE;
+ lmap->ldap_timeout.tv_sec = 0;
+ lmap->ldap_timeout.tv_usec = 0;
+ lmap->ldap_ld = NULL;
+ lmap->ldap_filter = NULL;
+ lmap->ldap_attr[0] = NULL;
+ lmap->ldap_attr_type[0] = SM_LDAP_ATTR_NONE;
+ lmap->ldap_attr_needobjclass[0] = NULL;
+ lmap->ldap_res = NULL;
+ lmap->ldap_next = NULL;
+ lmap->ldap_pid = 0;
+}
+
+/*
+** SM_LDAP_START -- actually connect to an LDAP server
+**
+** Parameters:
+** name -- name of map for debug output.
+** lmap -- the LDAP map being opened.
+**
+** Returns:
+** true if connection is successful, false otherwise.
+**
+** Side Effects:
+** Populates lmap->ldap_ld.
+*/
+
+static jmp_buf LDAPTimeout;
+
+#define SM_LDAP_SETTIMEOUT(to) \
+do \
+{ \
+ if (to != 0) \
+ { \
+ if (setjmp(LDAPTimeout) != 0) \
+ { \
+ errno = ETIMEDOUT; \
+ return false; \
+ } \
+ ev = sm_setevent(to, ldaptimeout, 0); \
+ } \
+} while (0)
+
+#define SM_LDAP_CLEARTIMEOUT() \
+do \
+{ \
+ if (ev != NULL) \
+ sm_clrevent(ev); \
+} while (0)
+
+bool
+sm_ldap_start(name, lmap)
+ char *name;
+ SM_LDAP_STRUCT *lmap;
+{
+ int bind_result;
+ int save_errno = 0;
+ char *id;
+ SM_EVENT *ev = NULL;
+ LDAP *ld = NULL;
+
+ if (sm_debug_active(&SmLDAPTrace, 2))
+ sm_dprintf("ldapmap_start(%s)\n", name == NULL ? "" : name);
+
+ if (lmap->ldap_host != NULL)
+ id = lmap->ldap_host;
+ else if (lmap->ldap_uri != NULL)
+ id = lmap->ldap_uri;
+ else
+ id = "localhost";
+
+ if (sm_debug_active(&SmLDAPTrace, 9))
+ {
+ /* Don't print a port number for LDAP URIs */
+ if (lmap->ldap_uri != NULL)
+ sm_dprintf("ldapmap_start(%s)\n", id);
+ else
+ sm_dprintf("ldapmap_start(%s, %d)\n", id,
+ lmap->ldap_port);
+ }
+
+ if (lmap->ldap_uri != NULL)
+ {
+#if SM_CONF_LDAP_INITIALIZE
+ /* LDAP server supports URIs so use them directly */
+ save_errno = ldap_initialize(&ld, lmap->ldap_uri);
+#else /* SM_CONF_LDAP_INITIALIZE */
+ int err;
+ LDAPURLDesc *ludp = NULL;
+
+ /* Blast apart URL and use the ldap_init/ldap_open below */
+ err = ldap_url_parse(lmap->ldap_uri, &ludp);
+ if (err != 0)
+ {
+ errno = err + E_LDAPURLBASE;
+ return false;
+ }
+ lmap->ldap_host = sm_strdup_x(ludp->lud_host);
+ if (lmap->ldap_host == NULL)
+ {
+ save_errno = errno;
+ ldap_free_urldesc(ludp);
+ errno = save_errno;
+ return false;
+ }
+ lmap->ldap_port = ludp->lud_port;
+ ldap_free_urldesc(ludp);
+#endif /* SM_CONF_LDAP_INITIALIZE */
+ }
+
+ if (ld == NULL)
+ {
+# if USE_LDAP_INIT
+ ld = ldap_init(lmap->ldap_host, lmap->ldap_port);
+ save_errno = errno;
+# else /* USE_LDAP_INIT */
+ /*
+ ** If using ldap_open(), the actual connection to the server
+ ** happens now so we need the timeout here. For ldap_init(),
+ ** the connection happens at bind time.
+ */
+
+ SM_LDAP_SETTIMEOUT(lmap->ldap_timeout.tv_sec);
+ ld = ldap_open(lmap->ldap_host, lmap->ldap_port);
+ save_errno = errno;
+
+ /* clear the event if it has not sprung */
+ SM_LDAP_CLEARTIMEOUT();
+# endif /* USE_LDAP_INIT */
+ }
+
+ errno = save_errno;
+ if (ld == NULL)
+ return false;
+
+ sm_ldap_setopts(ld, lmap);
+
+# if USE_LDAP_INIT
+ /*
+ ** If using ldap_init(), the actual connection to the server
+ ** happens at ldap_bind_s() so we need the timeout here.
+ */
+
+ SM_LDAP_SETTIMEOUT(lmap->ldap_timeout.tv_sec);
+# endif /* USE_LDAP_INIT */
+
+# ifdef LDAP_AUTH_KRBV4
+ if (lmap->ldap_method == LDAP_AUTH_KRBV4 &&
+ lmap->ldap_secret != NULL)
+ {
+ /*
+ ** Need to put ticket in environment here instead of
+ ** during parseargs as there may be different tickets
+ ** for different LDAP connections.
+ */
+
+ (void) putenv(lmap->ldap_secret);
+ }
+# endif /* LDAP_AUTH_KRBV4 */
+
+ bind_result = ldap_bind_s(ld, lmap->ldap_binddn,
+ lmap->ldap_secret, lmap->ldap_method);
+
+# if USE_LDAP_INIT
+ /* clear the event if it has not sprung */
+ SM_LDAP_CLEARTIMEOUT();
+# endif /* USE_LDAP_INIT */
+
+ if (bind_result != LDAP_SUCCESS)
+ {
+ errno = bind_result + E_LDAPBASE;
+ return false;
+ }
+
+ /* Save PID to make sure only this PID closes the LDAP connection */
+ lmap->ldap_pid = getpid();
+ lmap->ldap_ld = ld;
+ return true;
+}
+
+/* ARGSUSED */
+static void
+ldaptimeout(unused)
+ int unused;
+{
+ /*
+ ** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+ ** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+ ** DOING.
+ */
+
+ errno = ETIMEDOUT;
+ longjmp(LDAPTimeout, 1);
+}
+
+/*
+** SM_LDAP_SEARCH -- initiate LDAP search
+**
+** Initiate an LDAP search, return the msgid.
+** The calling function must collect the results.
+**
+** Parameters:
+** lmap -- LDAP map information
+** key -- key to substitute in LDAP filter
+**
+** Returns:
+** -1 on failure, msgid on success
+**
+*/
+
+int
+sm_ldap_search(lmap, key)
+ SM_LDAP_STRUCT *lmap;
+ char *key;
+{
+ int msgid;
+ char *fp, *p, *q;
+ char filter[LDAPMAP_MAX_FILTER + 1];
+
+ /* substitute key into filter, perhaps multiple times */
+ memset(filter, '\0', sizeof filter);
+ fp = filter;
+ p = lmap->ldap_filter;
+ while ((q = strchr(p, '%')) != NULL)
+ {
+ if (q[1] == 's')
+ {
+ (void) sm_snprintf(fp, SPACELEFT(filter, fp),
+ "%.*s%s", (int) (q - p), p, key);
+ fp += strlen(fp);
+ p = q + 2;
+ }
+ else if (q[1] == '0')
+ {
+ char *k = key;
+
+ (void) sm_snprintf(fp, SPACELEFT(filter, fp),
+ "%.*s", (int) (q - p), p);
+ fp += strlen(fp);
+ p = q + 2;
+
+ /* Properly escape LDAP special characters */
+ while (SPACELEFT(filter, fp) > 0 &&
+ *k != '\0')
+ {
+ if (*k == '*' || *k == '(' ||
+ *k == ')' || *k == '\\')
+ {
+ (void) sm_strlcat(fp,
+ (*k == '*' ? "\\2A" :
+ (*k == '(' ? "\\28" :
+ (*k == ')' ? "\\29" :
+ (*k == '\\' ? "\\5C" :
+ "\00")))),
+ SPACELEFT(filter, fp));
+ fp += strlen(fp);
+ k++;
+ }
+ else
+ *fp++ = *k++;
+ }
+ }
+ else
+ {
+ (void) sm_snprintf(fp, SPACELEFT(filter, fp),
+ "%.*s", (int) (q - p + 1), p);
+ p = q + (q[1] == '%' ? 2 : 1);
+ fp += strlen(fp);
+ }
+ }
+ (void) sm_strlcpy(fp, p, SPACELEFT(filter, fp));
+ if (sm_debug_active(&SmLDAPTrace, 20))
+ sm_dprintf("ldap search filter=%s\n", filter);
+
+ lmap->ldap_res = NULL;
+ msgid = ldap_search(lmap->ldap_ld, lmap->ldap_base,
+ lmap->ldap_scope, filter,
+ (lmap->ldap_attr[0] == NULL ? NULL :
+ lmap->ldap_attr),
+ lmap->ldap_attrsonly);
+ return msgid;
+}
+
+/*
+** SM_LDAP_HAS_OBJECTCLASS -- determine if an LDAP entry is part of a
+** particular objectClass
+**
+** Parameters:
+** lmap -- pointer to SM_LDAP_STRUCT in use
+** entry -- current LDAP entry struct
+** ocvalue -- particular objectclass in question.
+** may be of form (fee|foo|fum) meaning
+** any entry can be part of either fee,
+** foo or fum objectclass
+**
+** Returns:
+** true if item has that objectClass
+*/
+
+static bool
+sm_ldap_has_objectclass(lmap, entry, ocvalue)
+ SM_LDAP_STRUCT *lmap;
+ LDAPMessage *entry;
+ char *ocvalue;
+{
+ char **vals = NULL;
+ int i;
+
+ if (ocvalue == NULL)
+ return false;
+
+ vals = ldap_get_values(lmap->ldap_ld, entry, "objectClass");
+ if (vals == NULL)
+ return false;
+
+ for (i = 0; vals[i] != NULL; i++)
+ {
+ char *p;
+ char *q;
+
+ p = q = ocvalue;
+ while (*p != '\0')
+ {
+ while (*p != '\0' && *p != '|')
+ p++;
+
+ if ((p - q) == strlen(vals[i]) &&
+ sm_strncasecmp(vals[i], q, p - q) == 0)
+ {
+ ldap_value_free(vals);
+ return true;
+ }
+
+ while (*p == '|')
+ p++;
+ q = p;
+ }
+ }
+
+ ldap_value_free(vals);
+ return false;
+}
+
+/*
+** SM_LDAP_RESULTS -- return results from an LDAP lookup in result
+**
+** Parameters:
+** lmap -- pointer to SM_LDAP_STRUCT in use
+** msgid -- msgid returned by sm_ldap_search()
+** flags -- flags for the lookup
+** delim -- delimiter for result concatenation
+** rpool -- memory pool for storage
+** result -- return string
+** recurse -- recursion list
+**
+** Returns:
+** status (sysexit)
+*/
+
+# define SM_LDAP_ERROR_CLEANUP() \
+{ \
+ if (lmap->ldap_res != NULL) \
+ { \
+ ldap_msgfree(lmap->ldap_res); \
+ lmap->ldap_res = NULL; \
+ } \
+ (void) ldap_abandon(lmap->ldap_ld, msgid); \
+}
+
+static SM_LDAP_RECURSE_ENTRY *
+sm_ldap_add_recurse(top, item, type, rpool)
+ SM_LDAP_RECURSE_LIST **top;
+ char *item;
+ int type;
+ SM_RPOOL_T *rpool;
+{
+ int n;
+ int m;
+ int p;
+ int insertat;
+ int moveb;
+ int oldsizeb;
+ int rc;
+ SM_LDAP_RECURSE_ENTRY *newe;
+ SM_LDAP_RECURSE_ENTRY **olddata;
+
+ /*
+ ** This code will maintain a list of
+ ** SM_LDAP_RECURSE_ENTRY structures
+ ** in ascending order.
+ */
+
+ if (*top == NULL)
+ {
+ /* Allocate an initial SM_LDAP_RECURSE_LIST struct */
+ *top = sm_rpool_malloc_x(rpool, sizeof **top);
+ (*top)->lr_cnt = 0;
+ (*top)->lr_size = 0;
+ (*top)->lr_data = NULL;
+ }
+
+ if ((*top)->lr_cnt >= (*top)->lr_size)
+ {
+ /* Grow the list of SM_LDAP_RECURSE_ENTRY ptrs */
+ olddata = (*top)->lr_data;
+ if ((*top)->lr_size == 0)
+ {
+ oldsizeb = 0;
+ (*top)->lr_size = 256;
+ }
+ else
+ {
+ oldsizeb = (*top)->lr_size * sizeof *((*top)->lr_data);
+ (*top)->lr_size *= 2;
+ }
+ (*top)->lr_data = sm_rpool_malloc_x(rpool,
+ (*top)->lr_size * sizeof *((*top)->lr_data));
+ if (oldsizeb > 0)
+ memcpy((*top)->lr_data, olddata, oldsizeb);
+ }
+
+ /*
+ ** Binary search/insert item:type into list.
+ ** Return current entry pointer if already exists.
+ */
+
+ n = 0;
+ m = (*top)->lr_cnt - 1;
+ if (m < 0)
+ insertat = 0;
+ else
+ insertat = -1;
+
+ while (insertat == -1)
+ {
+ p = (m + n) / 2;
+
+ rc = sm_strcasecmp(item, (*top)->lr_data[p]->lr_search);
+ if (rc == 0)
+ rc = type - (*top)->lr_data[p]->lr_type;
+
+ if (rc < 0)
+ m = p - 1;
+ else if (rc > 0)
+ n = p + 1;
+ else
+ return (*top)->lr_data[p];
+
+ if (m == -1)
+ insertat = 0;
+ else if (n >= (*top)->lr_cnt)
+ insertat = (*top)->lr_cnt;
+ else if (m < n)
+ insertat = m + 1;
+ }
+
+ /*
+ ** Not found in list, make room
+ ** at insert point and add it.
+ */
+
+ newe = sm_rpool_malloc_x(rpool, sizeof *newe);
+ if (newe != NULL)
+ {
+ moveb = ((*top)->lr_cnt - insertat) * sizeof *((*top)->lr_data);
+ if (moveb > 0)
+ memmove(&((*top)->lr_data[insertat + 1]),
+ &((*top)->lr_data[insertat]),
+ moveb);
+
+ newe->lr_search = sm_rpool_strdup_x(rpool, item);
+ newe->lr_type = type;
+ newe->lr_ludp = NULL;
+ newe->lr_attrs = NULL;
+ newe->lr_done = false;
+
+ ((*top)->lr_data)[insertat] = newe;
+ (*top)->lr_cnt++;
+ }
+ return newe;
+}
+
+int
+sm_ldap_results(lmap, msgid, flags, delim, rpool, result,
+ resultln, resultsz, recurse)
+ SM_LDAP_STRUCT *lmap;
+ int msgid;
+ int flags;
+ int delim;
+ SM_RPOOL_T *rpool;
+ char **result;
+ int *resultln;
+ int *resultsz;
+ SM_LDAP_RECURSE_LIST *recurse;
+{
+ bool toplevel;
+ int i;
+ int statp;
+ int vsize;
+ int ret;
+ int save_errno;
+ char *p;
+ SM_LDAP_RECURSE_ENTRY *rl;
+
+ /* Are we the top top level of the search? */
+ toplevel = (recurse == NULL);
+
+ /* Get results */
+ statp = EX_NOTFOUND;
+ while ((ret = ldap_result(lmap->ldap_ld, msgid, 0,
+ (lmap->ldap_timeout.tv_sec == 0 ? NULL :
+ &(lmap->ldap_timeout)),
+ &(lmap->ldap_res))) == LDAP_RES_SEARCH_ENTRY)
+ {
+ LDAPMessage *entry;
+
+ /* If we don't want multiple values and we have one, break */
+ if ((char) delim == '\0' &&
+ !bitset(SM_LDAP_SINGLEMATCH, flags) &&
+ *result != NULL)
+ break;
+
+ /* Cycle through all entries */
+ for (entry = ldap_first_entry(lmap->ldap_ld, lmap->ldap_res);
+ entry != NULL;
+ entry = ldap_next_entry(lmap->ldap_ld, lmap->ldap_res))
+ {
+ BerElement *ber;
+ char *attr;
+ char **vals = NULL;
+ char *dn;
+
+ /*
+ ** If matching only and found an entry,
+ ** no need to spin through attributes
+ */
+
+ if (bitset(SM_LDAP_MATCHONLY, flags))
+ {
+ statp = EX_OK;
+ continue;
+ }
+
+ /* record completed DN's to prevent loops */
+ dn = ldap_get_dn(lmap->ldap_ld, entry);
+ if (dn == NULL)
+ {
+ save_errno = sm_ldap_geterrno(lmap->ldap_ld);
+ save_errno += E_LDAPBASE;
+ SM_LDAP_ERROR_CLEANUP();
+ errno = save_errno;
+ return EX_TEMPFAIL;
+ }
+
+ rl = sm_ldap_add_recurse(&recurse, dn,
+ SM_LDAP_ATTR_DN,
+ rpool);
+
+ if (rl == NULL)
+ {
+ ldap_memfree(dn);
+ SM_LDAP_ERROR_CLEANUP();
+ errno = ENOMEM;
+ return EX_OSERR;
+ }
+ else if (rl->lr_done)
+ {
+ /* already on list, skip it */
+ ldap_memfree(dn);
+ continue;
+ }
+ ldap_memfree(dn);
+
+# if !defined(LDAP_VERSION_MAX) && !defined(LDAP_OPT_SIZELIMIT)
+ /*
+ ** Reset value to prevent lingering
+ ** LDAP_DECODING_ERROR due to
+ ** OpenLDAP 1.X's hack (see below)
+ */
+
+ lmap->ldap_ld->ld_errno = LDAP_SUCCESS;
+# endif /* !defined(LDAP_VERSION_MAX) !defined(LDAP_OPT_SIZELIMIT) */
+
+ for (attr = ldap_first_attribute(lmap->ldap_ld, entry,
+ &ber);
+ attr != NULL;
+ attr = ldap_next_attribute(lmap->ldap_ld, entry,
+ ber))
+ {
+ char *tmp, *vp_tmp;
+ int type;
+ char *needobjclass = NULL;
+
+ type = SM_LDAP_ATTR_NONE;
+ for (i = 0; lmap->ldap_attr[i] != NULL; i++)
+ {
+ if (sm_strcasecmp(lmap->ldap_attr[i],
+ attr) == 0)
+ {
+ type = lmap->ldap_attr_type[i];
+ needobjclass = lmap->ldap_attr_needobjclass[i];
+ break;
+ }
+ }
+
+ if (bitset(SM_LDAP_USE_ALLATTR, flags) &&
+ type == SM_LDAP_ATTR_NONE)
+ {
+ /* URL lookups specify attrs to use */
+ type = SM_LDAP_ATTR_NORMAL;
+ needobjclass = NULL;
+ }
+
+ if (type == SM_LDAP_ATTR_NONE)
+ {
+ /* attribute not requested */
+ ldap_memfree(attr);
+ SM_LDAP_ERROR_CLEANUP();
+ errno = EFAULT;
+ return EX_SOFTWARE;
+ }
+
+ /*
+ ** For recursion on a particular attribute,
+ ** we may need to see if this entry is
+ ** part of a particular objectclass.
+ ** Also, ignore objectClass attribute.
+ ** Otherwise we just ignore this attribute.
+ */
+
+ if (type == SM_LDAP_ATTR_OBJCLASS ||
+ (needobjclass != NULL &&
+ !sm_ldap_has_objectclass(lmap, entry,
+ needobjclass)))
+ {
+ ldap_memfree(attr);
+ continue;
+ }
+
+ if (lmap->ldap_attrsonly == LDAPMAP_FALSE)
+ {
+ vals = ldap_get_values(lmap->ldap_ld,
+ entry,
+ attr);
+ if (vals == NULL)
+ {
+ save_errno = sm_ldap_geterrno(lmap->ldap_ld);
+ if (save_errno == LDAP_SUCCESS)
+ {
+ ldap_memfree(attr);
+ continue;
+ }
+
+ /* Must be an error */
+ save_errno += E_LDAPBASE;
+ ldap_memfree(attr);
+ SM_LDAP_ERROR_CLEANUP();
+ errno = save_errno;
+ return EX_TEMPFAIL;
+ }
+ }
+
+ statp = EX_OK;
+
+# if !defined(LDAP_VERSION_MAX) && !defined(LDAP_OPT_SIZELIMIT)
+ /*
+ ** Reset value to prevent lingering
+ ** LDAP_DECODING_ERROR due to
+ ** OpenLDAP 1.X's hack (see below)
+ */
+
+ lmap->ldap_ld->ld_errno = LDAP_SUCCESS;
+# endif /* !defined(LDAP_VERSION_MAX) !defined(LDAP_OPT_SIZELIMIT) */
+
+ /*
+ ** If matching only,
+ ** no need to spin through entries
+ */
+
+ if (bitset(SM_LDAP_MATCHONLY, flags))
+ {
+ if (lmap->ldap_attrsonly == LDAPMAP_FALSE)
+ ldap_value_free(vals);
+ ldap_memfree(attr);
+ continue;
+ }
+
+ /*
+ ** If we don't want multiple values,
+ ** return first found.
+ */
+
+ if ((char) delim == '\0')
+ {
+ if (*result != NULL)
+ {
+ /* already have a value */
+ if (bitset(SM_LDAP_SINGLEMATCH,
+ flags))
+ {
+ /* only wanted one match */
+ SM_LDAP_ERROR_CLEANUP();
+ errno = ENOENT;
+ return EX_NOTFOUND;
+ }
+ break;
+ }
+
+ if (lmap->ldap_attrsonly == LDAPMAP_TRUE)
+ {
+ *result = sm_rpool_strdup_x(rpool,
+ attr);
+ ldap_memfree(attr);
+ break;
+ }
+
+ if (vals[0] == NULL)
+ {
+ ldap_value_free(vals);
+ ldap_memfree(attr);
+ continue;
+ }
+
+ vsize = strlen(vals[0]) + 1;
+ if (lmap->ldap_attrsep != '\0')
+ vsize += strlen(attr) + 1;
+ *result = sm_rpool_malloc_x(rpool,
+ vsize);
+ if (lmap->ldap_attrsep != '\0')
+ sm_snprintf(*result, vsize,
+ "%s%c%s",
+ attr,
+ lmap->ldap_attrsep,
+ vals[0]);
+ else
+ sm_strlcpy(*result, vals[0],
+ vsize);
+ ldap_value_free(vals);
+ ldap_memfree(attr);
+ break;
+ }
+
+ /* attributes only */
+ if (lmap->ldap_attrsonly == LDAPMAP_TRUE)
+ {
+ if (*result == NULL)
+ *result = sm_rpool_strdup_x(rpool,
+ attr);
+ else
+ {
+ if (bitset(SM_LDAP_SINGLEMATCH,
+ flags) &&
+ *result != NULL)
+ {
+ /* only wanted one match */
+ SM_LDAP_ERROR_CLEANUP();
+ errno = ENOENT;
+ return EX_NOTFOUND;
+ }
+
+ vsize = strlen(*result) +
+ strlen(attr) + 2;
+ tmp = sm_rpool_malloc_x(rpool,
+ vsize);
+ (void) sm_snprintf(tmp,
+ vsize, "%s%c%s",
+ *result, (char) delim,
+ attr);
+ *result = tmp;
+ }
+ ldap_memfree(attr);
+ continue;
+ }
+
+ /*
+ ** If there is more than one, munge then
+ ** into a map_coldelim separated string.
+ ** If we are recursing we may have an entry
+ ** with no 'normal' values to put in the
+ ** string.
+ ** This is not an error.
+ */
+
+ if (type == SM_LDAP_ATTR_NORMAL &&
+ bitset(SM_LDAP_SINGLEMATCH, flags) &&
+ *result != NULL)
+ {
+ /* only wanted one match */
+ SM_LDAP_ERROR_CLEANUP();
+ errno = ENOENT;
+ return EX_NOTFOUND;
+ }
+
+ vsize = 0;
+ for (i = 0; vals[i] != NULL; i++)
+ {
+ if (type == SM_LDAP_ATTR_DN ||
+ type == SM_LDAP_ATTR_FILTER ||
+ type == SM_LDAP_ATTR_URL)
+ {
+ /* add to recursion */
+ if (sm_ldap_add_recurse(&recurse,
+ vals[i],
+ type,
+ rpool) == NULL)
+ {
+ SM_LDAP_ERROR_CLEANUP();
+ errno = ENOMEM;
+ return EX_OSERR;
+ }
+ continue;
+ }
+
+ vsize += strlen(vals[i]) + 1;
+ if (lmap->ldap_attrsep != '\0')
+ vsize += strlen(attr) + 1;
+ }
+
+ /*
+ ** Create/Append to string any normal
+ ** attribute values. Otherwise, just free
+ ** memory and move on to the next
+ ** attribute in this entry.
+ */
+
+ if (type == SM_LDAP_ATTR_NORMAL && vsize > 0)
+ {
+ char *pe;
+
+ /* Grow result string if needed */
+ if ((*resultln + vsize) >= *resultsz)
+ {
+ while ((*resultln + vsize) >= *resultsz)
+ {
+ if (*resultsz == 0)
+ *resultsz = 1024;
+ else
+ *resultsz *= 2;
+ }
+
+ vp_tmp = sm_rpool_malloc_x(rpool, *resultsz);
+ *vp_tmp = '\0';
+
+ if (*result != NULL)
+ sm_strlcpy(vp_tmp,
+ *result,
+ *resultsz);
+ *result = vp_tmp;
+ }
+
+ p = *result + *resultln;
+ pe = *result + *resultsz;
+
+ for (i = 0; vals[i] != NULL; i++)
+ {
+ if (*resultln > 0 &&
+ p < pe)
+ *p++ = (char) delim;
+
+ if (lmap->ldap_attrsep != '\0')
+ {
+ p += sm_strlcpy(p, attr,
+ pe - p);
+ if (p < pe)
+ *p++ = lmap->ldap_attrsep;
+ }
+
+ p += sm_strlcpy(p, vals[i],
+ pe - p);
+ *resultln = p - (*result);
+ if (p >= pe)
+ {
+ /* Internal error: buffer too small for LDAP values */
+ SM_LDAP_ERROR_CLEANUP();
+ errno = ENOMEM;
+ return EX_OSERR;
+ }
+ }
+ }
+
+ ldap_value_free(vals);
+ ldap_memfree(attr);
+ }
+ save_errno = sm_ldap_geterrno(lmap->ldap_ld);
+
+ /*
+ ** We check save_errno != LDAP_DECODING_ERROR since
+ ** OpenLDAP 1.X has a very ugly *undocumented*
+ ** hack of returning this error code from
+ ** ldap_next_attribute() if the library freed the
+ ** ber attribute. See:
+ ** http://www.openldap.org/lists/openldap-devel/9901/msg00064.html
+ */
+
+ if (save_errno != LDAP_SUCCESS &&
+ save_errno != LDAP_DECODING_ERROR)
+ {
+ /* Must be an error */
+ save_errno += E_LDAPBASE;
+ SM_LDAP_ERROR_CLEANUP();
+ errno = save_errno;
+ return EX_TEMPFAIL;
+ }
+
+ /* mark this DN as done */
+ rl->lr_done = true;
+ if (rl->lr_ludp != NULL)
+ {
+ ldap_free_urldesc(rl->lr_ludp);
+ rl->lr_ludp = NULL;
+ }
+ if (rl->lr_attrs != NULL)
+ {
+ free(rl->lr_attrs);
+ rl->lr_attrs = NULL;
+ }
+
+ /* We don't want multiple values and we have one */
+ if ((char) delim == '\0' &&
+ !bitset(SM_LDAP_SINGLEMATCH, flags) &&
+ *result != NULL)
+ break;
+ }
+ save_errno = sm_ldap_geterrno(lmap->ldap_ld);
+ if (save_errno != LDAP_SUCCESS &&
+ save_errno != LDAP_DECODING_ERROR)
+ {
+ /* Must be an error */
+ save_errno += E_LDAPBASE;
+ SM_LDAP_ERROR_CLEANUP();
+ errno = save_errno;
+ return EX_TEMPFAIL;
+ }
+ ldap_msgfree(lmap->ldap_res);
+ lmap->ldap_res = NULL;
+ }
+
+ if (ret == 0)
+ save_errno = ETIMEDOUT;
+ else
+ save_errno = sm_ldap_geterrno(lmap->ldap_ld);
+ if (save_errno != LDAP_SUCCESS)
+ {
+ statp = EX_TEMPFAIL;
+ if (ret != 0)
+ {
+ switch (save_errno)
+ {
+#ifdef LDAP_SERVER_DOWN
+ case LDAP_SERVER_DOWN:
+#endif /* LDAP_SERVER_DOWN */
+ case LDAP_TIMEOUT:
+ case LDAP_UNAVAILABLE:
+
+ /*
+ ** server disappeared,
+ ** try reopen on next search
+ */
+
+ statp = EX_RESTART;
+ break;
+ }
+ save_errno += E_LDAPBASE;
+ }
+ SM_LDAP_ERROR_CLEANUP();
+ errno = save_errno;
+ return statp;
+ }
+
+ if (lmap->ldap_res != NULL)
+ {
+ ldap_msgfree(lmap->ldap_res);
+ lmap->ldap_res = NULL;
+ }
+
+ if (toplevel)
+ {
+ int rlidx;
+
+ /*
+ ** Spin through the built-up recurse list at the top
+ ** of the recursion. Since new items are added at the
+ ** end of the shared list, we actually only ever get
+ ** one level of recursion before things pop back to the
+ ** top. Any items added to the list during that recursion
+ ** will be expanded by the top level.
+ */
+
+ for (rlidx = 0; recurse != NULL && rlidx < recurse->lr_cnt; rlidx++)
+ {
+ int newflags;
+ int sid;
+ int status;
+
+ rl = recurse->lr_data[rlidx];
+
+ newflags = flags;
+ if (rl->lr_done)
+ {
+ /* already expanded */
+ continue;
+ }
+
+ if (rl->lr_type == SM_LDAP_ATTR_DN)
+ {
+ /* do DN search */
+ sid = ldap_search(lmap->ldap_ld,
+ rl->lr_search,
+ lmap->ldap_scope,
+ "(objectClass=*)",
+ (lmap->ldap_attr[0] == NULL ?
+ NULL : lmap->ldap_attr),
+ lmap->ldap_attrsonly);
+ }
+ else if (rl->lr_type == SM_LDAP_ATTR_FILTER)
+ {
+ /* do new search */
+ sid = ldap_search(lmap->ldap_ld,
+ lmap->ldap_base,
+ lmap->ldap_scope,
+ rl->lr_search,
+ (lmap->ldap_attr[0] == NULL ?
+ NULL : lmap->ldap_attr),
+ lmap->ldap_attrsonly);
+ }
+ else if (rl->lr_type == SM_LDAP_ATTR_URL)
+ {
+ /* Parse URL */
+ sid = ldap_url_parse(rl->lr_search,
+ &rl->lr_ludp);
+
+ if (sid != 0)
+ {
+ errno = sid + E_LDAPURLBASE;
+ return EX_TEMPFAIL;
+ }
+
+ /* We need to add objectClass */
+ if (rl->lr_ludp->lud_attrs != NULL)
+ {
+ int attrnum = 0;
+
+ while (rl->lr_ludp->lud_attrs[attrnum] != NULL)
+ {
+ if (strcasecmp(rl->lr_ludp->lud_attrs[attrnum],
+ "objectClass") == 0)
+ {
+ /* already requested */
+ attrnum = -1;
+ break;
+ }
+ attrnum++;
+ }
+
+ if (attrnum >= 0)
+ {
+ int i;
+
+ rl->lr_attrs = (char **)malloc(sizeof(char *) * (attrnum + 2));
+ if (rl->lr_attrs == NULL)
+ {
+ save_errno = errno;
+ ldap_free_urldesc(rl->lr_ludp);
+ errno = save_errno;
+ return EX_TEMPFAIL;
+ }
+ for (i = 0 ; i < attrnum; i++)
+ {
+ rl->lr_attrs[i] = rl->lr_ludp->lud_attrs[i];
+ }
+ rl->lr_attrs[i++] = "objectClass";
+ rl->lr_attrs[i++] = NULL;
+ }
+ }
+
+ /*
+ ** Use the existing connection
+ ** for this search. It really
+ ** should use lud_scheme://lud_host:lud_port/
+ ** instead but that would require
+ ** opening a new connection.
+ ** This should be fixed ASAP.
+ */
+
+ sid = ldap_search(lmap->ldap_ld,
+ rl->lr_ludp->lud_dn,
+ rl->lr_ludp->lud_scope,
+ rl->lr_ludp->lud_filter,
+ rl->lr_attrs,
+ lmap->ldap_attrsonly);
+
+ /* Use the attributes specified by URL */
+ newflags |= SM_LDAP_USE_ALLATTR;
+ }
+ else
+ {
+ /* unknown or illegal attribute type */
+ errno = EFAULT;
+ return EX_SOFTWARE;
+ }
+
+ /* Collect results */
+ if (sid == -1)
+ {
+ save_errno = sm_ldap_geterrno(lmap->ldap_ld);
+ statp = EX_TEMPFAIL;
+ switch (save_errno)
+ {
+#ifdef LDAP_SERVER_DOWN
+ case LDAP_SERVER_DOWN:
+#endif /* LDAP_SERVER_DOWN */
+ case LDAP_TIMEOUT:
+ case LDAP_UNAVAILABLE:
+
+ /*
+ ** server disappeared,
+ ** try reopen on next search
+ */
+
+ statp = EX_RESTART;
+ break;
+ }
+ errno = save_errno + E_LDAPBASE;
+ return statp;
+ }
+
+ status = sm_ldap_results(lmap, sid, newflags, delim,
+ rpool, result, resultln,
+ resultsz, recurse);
+ save_errno = errno;
+ if (status != EX_OK && status != EX_NOTFOUND)
+ {
+ errno = save_errno;
+ return status;
+ }
+
+ /* Mark as done */
+ rl->lr_done = true;
+ if (rl->lr_ludp != NULL)
+ {
+ ldap_free_urldesc(rl->lr_ludp);
+ rl->lr_ludp = NULL;
+ }
+ if (rl->lr_attrs != NULL)
+ {
+ free(rl->lr_attrs);
+ rl->lr_attrs = NULL;
+ }
+
+ /* Reset rlidx as new items may have been added */
+ rlidx = -1;
+ }
+ }
+ return statp;
+}
+
+/*
+** SM_LDAP_CLOSE -- close LDAP connection
+**
+** Parameters:
+** lmap -- LDAP map information
+**
+** Returns:
+** None.
+**
+*/
+
+void
+sm_ldap_close(lmap)
+ SM_LDAP_STRUCT *lmap;
+{
+ if (lmap->ldap_ld == NULL)
+ return;
+
+ if (lmap->ldap_pid == getpid())
+ ldap_unbind(lmap->ldap_ld);
+ lmap->ldap_ld = NULL;
+ lmap->ldap_pid = 0;
+}
+
+/*
+** SM_LDAP_SETOPTS -- set LDAP options
+**
+** Parameters:
+** ld -- LDAP session handle
+** lmap -- LDAP map information
+**
+** Returns:
+** None.
+**
+*/
+
+void
+sm_ldap_setopts(ld, lmap)
+ LDAP *ld;
+ SM_LDAP_STRUCT *lmap;
+{
+# if USE_LDAP_SET_OPTION
+ if (lmap->ldap_version != 0)
+ {
+ ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION,
+ &lmap->ldap_version);
+ }
+ ldap_set_option(ld, LDAP_OPT_DEREF, &lmap->ldap_deref);
+ if (bitset(LDAP_OPT_REFERRALS, lmap->ldap_options))
+ ldap_set_option(ld, LDAP_OPT_REFERRALS, LDAP_OPT_ON);
+ else
+ ldap_set_option(ld, LDAP_OPT_REFERRALS, LDAP_OPT_OFF);
+ ldap_set_option(ld, LDAP_OPT_SIZELIMIT, &lmap->ldap_sizelimit);
+ ldap_set_option(ld, LDAP_OPT_TIMELIMIT, &lmap->ldap_timelimit);
+# ifdef LDAP_OPT_RESTART
+ ldap_set_option(ld, LDAP_OPT_RESTART, LDAP_OPT_ON);
+# endif /* LDAP_OPT_RESTART */
+# else /* USE_LDAP_SET_OPTION */
+ /* From here on in we can use ldap internal timelimits */
+ ld->ld_deref = lmap->ldap_deref;
+ ld->ld_options = lmap->ldap_options;
+ ld->ld_sizelimit = lmap->ldap_sizelimit;
+ ld->ld_timelimit = lmap->ldap_timelimit;
+# endif /* USE_LDAP_SET_OPTION */
+}
+
+/*
+** SM_LDAP_GETERRNO -- get ldap errno value
+**
+** Parameters:
+** ld -- LDAP session handle
+**
+** Returns:
+** LDAP errno.
+**
+*/
+
+int
+sm_ldap_geterrno(ld)
+ LDAP *ld;
+{
+ int err = LDAP_SUCCESS;
+
+# if defined(LDAP_VERSION_MAX) && LDAP_VERSION_MAX >= 3
+ (void) ldap_get_option(ld, LDAP_OPT_ERROR_NUMBER, &err);
+# else /* defined(LDAP_VERSION_MAX) && LDAP_VERSION_MAX >= 3 */
+# ifdef LDAP_OPT_SIZELIMIT
+ err = ldap_get_lderrno(ld, NULL, NULL);
+# else /* LDAP_OPT_SIZELIMIT */
+ err = ld->ld_errno;
+
+ /*
+ ** Reset value to prevent lingering LDAP_DECODING_ERROR due to
+ ** OpenLDAP 1.X's hack (see above)
+ */
+
+ ld->ld_errno = LDAP_SUCCESS;
+# endif /* LDAP_OPT_SIZELIMIT */
+# endif /* defined(LDAP_VERSION_MAX) && LDAP_VERSION_MAX >= 3 */
+ return err;
+}
+# endif /* LDAPMAP */
diff --git a/usr/src/cmd/sendmail/libsm/local.h b/usr/src/cmd/sendmail/libsm/local.h
new file mode 100644
index 0000000000..a52c786329
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/local.h
@@ -0,0 +1,330 @@
+/*
+ * Copyright (c) 2000-2002, 2004 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ * $Id: local.h,v 1.51.2.2 2004/01/09 18:32:44 ca Exp $
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+** Information local to this implementation of stdio,
+** in particular, macros and private variables.
+*/
+
+#include <sys/time.h>
+#if !SM_CONF_MEMCHR
+# include <memory.h>
+#endif /* !SM_CONF_MEMCHR */
+#include <sm/heap.h>
+
+int sm_flush __P((SM_FILE_T *, int *));
+SM_FILE_T *smfp __P((void));
+int sm_refill __P((SM_FILE_T *, int));
+void sm_init __P((void));
+void sm_cleanup __P((void));
+void sm_makebuf __P((SM_FILE_T *));
+int sm_whatbuf __P((SM_FILE_T *, size_t *, int *));
+int sm_fwalk __P((int (*)(SM_FILE_T *, int *), int *));
+int sm_wsetup __P((SM_FILE_T *));
+int sm_flags __P((int));
+SM_FILE_T *sm_fp __P((const SM_FILE_T *, const int, SM_FILE_T *));
+int sm_vprintf __P((int, char const *, va_list));
+
+/* std io functions */
+ssize_t sm_stdread __P((SM_FILE_T *, char *, size_t));
+ssize_t sm_stdwrite __P((SM_FILE_T *, char const *, size_t));
+off_t sm_stdseek __P((SM_FILE_T *, off_t, int));
+int sm_stdclose __P((SM_FILE_T *));
+int sm_stdopen __P((SM_FILE_T *, const void *, int, const void *));
+int sm_stdfdopen __P((SM_FILE_T *, const void *, int, const void *));
+int sm_stdsetinfo __P((SM_FILE_T *, int , void *));
+int sm_stdgetinfo __P((SM_FILE_T *, int , void *));
+
+/* stdio io functions */
+ssize_t sm_stdioread __P((SM_FILE_T *, char *, size_t));
+ssize_t sm_stdiowrite __P((SM_FILE_T *, char const *, size_t));
+off_t sm_stdioseek __P((SM_FILE_T *, off_t, int));
+int sm_stdioclose __P((SM_FILE_T *));
+int sm_stdioopen __P((SM_FILE_T *, const void *, int, const void *));
+int sm_stdiosetinfo __P((SM_FILE_T *, int , void *));
+int sm_stdiogetinfo __P((SM_FILE_T *, int , void *));
+
+/* string io functions */
+ssize_t sm_strread __P((SM_FILE_T *, char *, size_t));
+ssize_t sm_strwrite __P((SM_FILE_T *, char const *, size_t));
+off_t sm_strseek __P((SM_FILE_T *, off_t, int));
+int sm_strclose __P((SM_FILE_T *));
+int sm_stropen __P((SM_FILE_T *, const void *, int, const void *));
+int sm_strsetinfo __P((SM_FILE_T *, int , void *));
+int sm_strgetinfo __P((SM_FILE_T *, int , void *));
+
+/* syslog io functions */
+ssize_t sm_syslogread __P((SM_FILE_T *, char *, size_t));
+ssize_t sm_syslogwrite __P((SM_FILE_T *, char const *, size_t));
+off_t sm_syslogseek __P((SM_FILE_T *, off_t, int));
+int sm_syslogclose __P((SM_FILE_T *));
+int sm_syslogopen __P((SM_FILE_T *, const void *, int, const void *));
+int sm_syslogsetinfo __P((SM_FILE_T *, int , void *));
+int sm_sysloggetinfo __P((SM_FILE_T *, int , void *));
+
+/* should be defined in sys/time.h */
+#ifndef timersub
+# define timersub(tvp, uvp, vvp) \
+ do \
+ { \
+ (vvp)->tv_sec = (tvp)->tv_sec - (uvp)->tv_sec; \
+ (vvp)->tv_usec = (tvp)->tv_usec - (uvp)->tv_usec; \
+ if ((vvp)->tv_usec < 0) \
+ { \
+ (vvp)->tv_sec--; \
+ (vvp)->tv_usec += 1000000; \
+ } \
+ } while (0)
+#endif /* !timersub */
+
+#ifndef timeradd
+# define timeradd(tvp, uvp, vvp) \
+ do \
+ { \
+ (vvp)->tv_sec = (tvp)->tv_sec + (uvp)->tv_sec; \
+ (vvp)->tv_usec = (tvp)->tv_usec + (uvp)->tv_usec; \
+ if ((vvp)->tv_usec >= 1000000) \
+ { \
+ (vvp)->tv_sec++; \
+ (vvp)->tv_usec -= 1000000; \
+ } \
+ } while (0)
+#endif /* !timeradd */
+
+#ifndef timercmp
+# define timercmp(tvp, uvp, cmp) \
+ (((tvp)->tv_sec == (uvp)->tv_sec) ? \
+ ((tvp)->tv_usec cmp (uvp)->tv_usec) : \
+ ((tvp)->tv_sec cmp (uvp)->tv_sec))
+#endif /* !timercmp */
+
+extern bool Sm_IO_DidInit;
+
+/* Return true iff the given SM_FILE_T cannot be written now. */
+#define cantwrite(fp) \
+ ((((fp)->f_flags & SMWR) == 0 || (fp)->f_bf.smb_base == NULL) && \
+ sm_wsetup(fp))
+
+/*
+** Test whether the given stdio file has an active ungetc buffer;
+** release such a buffer, without restoring ordinary unread data.
+*/
+
+#define HASUB(fp) ((fp)->f_ub.smb_base != NULL)
+#define FREEUB(fp) \
+{ \
+ if ((fp)->f_ub.smb_base != (fp)->f_ubuf) \
+ sm_free((char *)(fp)->f_ub.smb_base); \
+ (fp)->f_ub.smb_base = NULL; \
+}
+
+extern const char SmFileMagic[];
+
+#define SM_ALIGN(p) (((unsigned long)(p) + SM_ALIGN_BITS) & ~SM_ALIGN_BITS)
+
+#define sm_io_flockfile(fp) ((void) 0)
+#define sm_io_funlockfile(fp) ((void) 0)
+
+#ifndef FDSET_CAST
+# define FDSET_CAST /* empty cast for fd_set arg to select */
+#endif
+
+/*
+** SM_CONVERT_TIME -- convert the API timeout flag for select() usage.
+**
+** This takes a 'fp' (a file type pointer) and obtains the "raw"
+** file descriptor (fd) if possible. The 'fd' is needed to possibly
+** switch the mode of the file (blocking/non-blocking) to match
+** the type of timeout. If timeout is SM_TIME_FOREVER then the
+** timeout using select won't be needed and the file is best placed
+** in blocking mode. If there is to be a finite timeout then the file
+** is best placed in non-blocking mode. Then, if not enough can be
+** written, select() can be used to test when something can be written
+** yet still timeout if the wait is too long.
+** If the mode is already in the correct state we don't change it.
+** Iff (yes "iff") the 'fd' is "-1" in value then the mode change
+** will not happen. This situation arises when a late-binding-to-disk
+** file type is in use. An example of this is the sendmail buffered
+** file type (in sendmail/bf.c).
+**
+** Parameters
+** fp -- the file pointer the timeout is for
+** fd -- to become the file descriptor value from 'fp'
+** val -- the timeout value to be converted
+** time -- a struct timeval holding the converted value
+**
+** Returns
+** nothing, this is flow-through code
+**
+** Side Effects:
+** May or may not change the mode of a currently open file.
+** The file mode may be changed to O_NONBLOCK or ~O_NONBLOCK
+** (meaning block). This is done to best match the type of
+** timeout and for (possible) use with select().
+*/
+
+# define SM_CONVERT_TIME(fp, fd, val, time) { \
+ if (((fd) = sm_io_getinfo(fp, SM_IO_WHAT_FD, NULL)) == -1) \
+ { \
+ /* can't get an fd, likely internal 'fake' fp */ \
+ errno = 0; \
+ } \
+ if ((val) == SM_TIME_DEFAULT) \
+ (val) = (fp)->f_timeout; \
+ if ((val) == SM_TIME_IMMEDIATE || (val) == SM_TIME_FOREVER) \
+ { \
+ (time)->tv_sec = 0; \
+ (time)->tv_usec = 0; \
+ } \
+ else \
+ { \
+ (time)->tv_sec = (val) / 1000; \
+ (time)->tv_usec = ((val) - ((time)->tv_sec * 1000)) * 10; \
+ } \
+ if ((val) == SM_TIME_FOREVER) \
+ { \
+ if ((fp)->f_timeoutstate == SM_TIME_NONBLOCK && (fd) != -1) \
+ { \
+ int ret; \
+ ret = fcntl((fd), F_GETFL, 0); \
+ if (ret == -1 || fcntl((fd), F_SETFL, \
+ ret & ~O_NONBLOCK) == -1) \
+ { \
+ /* errno should be set */ \
+ return SM_IO_EOF; \
+ } \
+ (fp)->f_timeoutstate = SM_TIME_BLOCK; \
+ if ((fp)->f_modefp != NULL) \
+ (fp)->f_modefp->f_timeoutstate = SM_TIME_BLOCK; \
+ } \
+ } \
+ else { \
+ if ((fp)->f_timeoutstate == SM_TIME_BLOCK && (fd) != -1) \
+ { \
+ int ret; \
+ ret = fcntl((fd), F_GETFL, 0); \
+ if (ret == -1 || fcntl((fd), F_SETFL, \
+ ret | O_NONBLOCK) == -1) \
+ { \
+ /* errno should be set */ \
+ return SM_IO_EOF; \
+ } \
+ (fp)->f_timeoutstate = SM_TIME_NONBLOCK; \
+ if ((fp)->f_modefp != NULL) \
+ (fp)->f_modefp->f_timeoutstate = SM_TIME_NONBLOCK; \
+ } \
+ } \
+}
+
+/*
+** SM_IO_WR_TIMEOUT -- setup the timeout for the write
+**
+** This #define uses a select() to wait for the 'fd' to become writable.
+** The select() can be active for up to 'to' time. The select may not
+** use all of the the 'to' time. Hence, the amount of "wall-clock" time is
+** measured to decide how much to subtract from 'to' to update it. On some
+** BSD-based/like systems the timeout for a select is updated for the
+** amount of time used. On many/most systems this does not happen. Therefore
+** the updating of 'to' must be done ourselves; a copy of 'to' is passed
+** since a BSD-like system will have updated it and we don't want to
+** double the time used!
+** Note: if a valid 'fd' doesn't exist yet, don't use this (e.g. the
+** sendmail buffered file type in sendmail/bf.c; see fvwrite.c).
+**
+** Parameters
+** fd -- a file descriptor for doing select() with
+** timeout -- the original user set value.
+**
+** Returns
+** nothing, this is flow through code
+**
+** Side Effects:
+** adjusts 'timeout' for time used
+*/
+
+#define SM_IO_WR_TIMEOUT(fp, fd, to) { \
+ struct timeval sm_io_to_before, sm_io_to_after, sm_io_to_diff; \
+ struct timeval sm_io_to; \
+ int sm_io_to_sel; \
+ fd_set sm_io_to_mask, sm_io_x_mask; \
+ errno = 0; \
+ if ((to) == SM_TIME_DEFAULT) \
+ (to) = (fp)->f_timeout; \
+ if ((to) == SM_TIME_IMMEDIATE) \
+ { \
+ errno = EAGAIN; \
+ return SM_IO_EOF; \
+ } \
+ else if ((to) == SM_TIME_FOREVER) \
+ { \
+ errno = EINVAL; \
+ return SM_IO_EOF; \
+ } \
+ else \
+ { \
+ sm_io_to.tv_sec = (to) / 1000; \
+ sm_io_to.tv_usec = ((to) - (sm_io_to.tv_sec * 1000)) * 10; \
+ } \
+ if (FD_SETSIZE > 0 && (fd) >= FD_SETSIZE) \
+ { \
+ errno = EINVAL; \
+ return SM_IO_EOF; \
+ } \
+ FD_ZERO(&sm_io_to_mask); \
+ FD_SET((fd), &sm_io_to_mask); \
+ FD_ZERO(&sm_io_x_mask); \
+ FD_SET((fd), &sm_io_x_mask); \
+ if (gettimeofday(&sm_io_to_before, NULL) < 0) \
+ return SM_IO_EOF; \
+ sm_io_to_sel = select((fd) + 1, NULL, &sm_io_to_mask, &sm_io_x_mask, \
+ &sm_io_to); \
+ if (sm_io_to_sel < 0) \
+ { \
+ /* something went wrong, errno set */ \
+ return SM_IO_EOF; \
+ } \
+ else if (sm_io_to_sel == 0) \
+ { \
+ /* timeout */ \
+ errno = EAGAIN; \
+ return SM_IO_EOF; \
+ } \
+ /* else loop again */ \
+ if (gettimeofday(&sm_io_to_after, NULL) < 0) \
+ return SM_IO_EOF; \
+ timersub(&sm_io_to_before, &sm_io_to_after, &sm_io_to_diff); \
+ timersub(&sm_io_to, &sm_io_to_diff, &sm_io_to); \
+ (to) -= (sm_io_to.tv_sec * 1000); \
+ (to) -= (sm_io_to.tv_usec / 10); \
+ if ((to) < 0) \
+ (to) = 0; \
+}
+
+/*
+** If there is no 'fd' just error (we can't timeout). If the timeout
+** is SM_TIME_FOREVER then there is no need to do a timeout with
+** select since this will be a real error. If the error is not
+** EAGAIN/EWOULDBLOCK (from a nonblocking) then it's a real error.
+** Specify the condition here as macro so it can be used in several places.
+*/
+
+#define IS_IO_ERROR(fd, ret, to) \
+ ((fd) < 0 || \
+ ((ret) < 0 && errno != EAGAIN && errno != EWOULDBLOCK) || \
+ (to) == SM_TIME_FOREVER)
+
diff --git a/usr/src/cmd/sendmail/libsm/makebuf.c b/usr/src/cmd/sendmail/libsm/makebuf.c
new file mode 100644
index 0000000000..09ca853cc8
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/makebuf.c
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: makebuf.c,v 1.26 2001/10/31 16:04:08 ca Exp $")
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sm/io.h>
+#include <sm/heap.h>
+#include <sm/conf.h>
+#include "local.h"
+
+/*
+** SM_MAKEBUF -- make a buffer for the file
+**
+** Parameters:
+** fp -- the file to be buffered
+**
+** Returns:
+** nothing
+**
+** Allocate a file buffer, or switch to unbuffered I/O.
+** By default tty devices default to line buffered.
+*/
+
+void
+sm_makebuf(fp)
+ register SM_FILE_T *fp;
+{
+ register void *p;
+ register int flags;
+ size_t size;
+ int couldbetty;
+
+ if (fp->f_flags & SMNBF)
+ {
+ fp->f_bf.smb_base = fp->f_p = fp->f_nbuf;
+ fp->f_bf.smb_size = 1;
+ return;
+ }
+ flags = sm_whatbuf(fp, &size, &couldbetty);
+ if ((p = sm_malloc(size)) == NULL)
+ {
+ fp->f_flags |= SMNBF;
+ fp->f_bf.smb_base = fp->f_p = fp->f_nbuf;
+ fp->f_bf.smb_size = 1;
+ return;
+ }
+ if (!Sm_IO_DidInit)
+ sm_init();
+ flags |= SMMBF;
+ fp->f_bf.smb_base = fp->f_p = p;
+ fp->f_bf.smb_size = size;
+ if (couldbetty && isatty(fp->f_file))
+ flags |= SMLBF;
+ fp->f_flags |= flags;
+}
+
+/*
+** SM_WHATBUF -- determine proper buffer for a file (internal)
+**
+** Plus it fills in 'bufsize' for recommended buffer size and
+** fills in flag to indicate if 'fp' could be a tty (nothing
+** to do with "betty" :-) ).
+**
+** Parameters:
+** fp -- file pointer to be buffered
+** bufsize -- new buffer size (a return)
+** couldbetty -- could be a tty (returns)
+**
+** Returns:
+** Success:
+** on error:
+** SMNPT -- not seek opimized
+** SMOPT -- seek opimized
+*/
+
+int
+sm_whatbuf(fp, bufsize, couldbetty)
+ register SM_FILE_T *fp;
+ size_t *bufsize;
+ int *couldbetty;
+{
+ struct stat st;
+
+ if (fp->f_file < 0 || fstat(fp->f_file, &st) < 0)
+ {
+ *couldbetty = 0;
+ *bufsize = SM_IO_BUFSIZ;
+ return SMNPT;
+ }
+
+ /* could be a tty iff it is a character device */
+ *couldbetty = S_ISCHR(st.st_mode);
+ if (st.st_blksize == 0)
+ {
+ *bufsize = SM_IO_BUFSIZ;
+ return SMNPT;
+ }
+
+#if SM_IO_MAX_BUF_FILE > 0
+ if (S_ISREG(st.st_mode) && st.st_blksize > SM_IO_MAX_BUF_FILE)
+ st.st_blksize = SM_IO_MAX_BUF_FILE;
+#endif /* SM_IO_MAX_BUF_FILE > 0 */
+
+#if SM_IO_MAX_BUF > 0 || SM_IO_MIN_BUF > 0
+ if (!S_ISREG(st.st_mode))
+ {
+# if SM_IO_MAX_BUF > 0
+ if (st.st_blksize > SM_IO_MAX_BUF)
+ st.st_blksize = SM_IO_MAX_BUF;
+# if SM_IO_MIN_BUF > 0
+ else
+# endif /* SM_IO_MIN_BUF > 0 */
+# endif /* SM_IO_MAX_BUF > 0 */
+# if SM_IO_MIN_BUF > 0
+ if (st.st_blksize < SM_IO_MIN_BUF)
+ st.st_blksize = SM_IO_MIN_BUF;
+# endif /* SM_IO_MIN_BUF > 0 */
+ }
+#endif /* SM_IO_MAX_BUF > 0 || SM_IO_MIN_BUF > 0 */
+
+ /*
+ ** Optimise fseek() only if it is a regular file. (The test for
+ ** sm_std_seek is mainly paranoia.) It is safe to set _blksize
+ ** unconditionally; it will only be used if SMOPT is also set.
+ */
+
+ if ((fp->f_flags & SMSTR) == 0)
+ {
+ *bufsize = st.st_blksize;
+ fp->f_blksize = st.st_blksize;
+ }
+ else
+ *bufsize = SM_IO_BUFSIZ;
+ if ((st.st_mode & S_IFMT) == S_IFREG &&
+ fp->f_seek == sm_stdseek)
+ return SMOPT;
+ else
+ return SMNPT;
+}
diff --git a/usr/src/cmd/sendmail/libsm/match.c b/usr/src/cmd/sendmail/libsm/match.c
new file mode 100644
index 0000000000..944f30bc64
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/match.c
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: match.c,v 1.8 2001/03/02 19:57:08 ca Exp $")
+
+#include <sm/string.h>
+
+/*
+** SM_MATCH -- Match a character string against a glob pattern.
+**
+** Parameters:
+** str -- string.
+** par -- pattern to find in str.
+**
+** Returns:
+** true on match, false on non-match.
+**
+** A pattern consists of normal characters, which match themselves,
+** and meta-sequences. A * matches any sequence of characters.
+** A ? matches any single character. A [ introduces a character class.
+** A ] marks the end of a character class; if the ] is missing then
+** the [ matches itself rather than introducing a character class.
+** A character class matches any of the characters between the brackets.
+** The range of characters from X to Y inclusive is written X-Y.
+** If the first character after the [ is ! then the character class is
+** complemented.
+**
+** To include a ] in a character class, make it the first character
+** listed (after the !, if any). To include a -, make it the first
+** character listed (after the !, if any) or the last character.
+** It is impossible for a ] to be the final character in a range.
+** For glob patterns that literally match "*", "?" or "[",
+** use [*], [?] or [[].
+*/
+
+bool
+sm_match(str, pat)
+ const char *str;
+ const char *pat;
+{
+ bool ccnot, ccmatch, ccfirst;
+ const char *ccstart;
+ char c, c2;
+
+ for (;;)
+ {
+ switch (*pat)
+ {
+ case '\0':
+ return *str == '\0';
+ case '?':
+ if (*str == '\0')
+ return false;
+ ++pat;
+ ++str;
+ continue;
+ case '*':
+ ++pat;
+ if (*pat == '\0')
+ {
+ /* optimize case of trailing '*' */
+ return true;
+ }
+ for (;;)
+ {
+ if (sm_match(pat, str))
+ return true;
+ if (*str == '\0')
+ return false;
+ ++str;
+ }
+ /* NOTREACHED */
+ case '[':
+ ccstart = pat++;
+ ccnot = false;
+ if (*pat == '!')
+ {
+ ccnot = true;
+ ++pat;
+ }
+ ccmatch = false;
+ ccfirst = true;
+ for (;;)
+ {
+ if (*pat == '\0')
+ {
+ pat = ccstart;
+ goto defl;
+ }
+ if (*pat == ']' && !ccfirst)
+ break;
+ c = *pat++;
+ ccfirst = false;
+ if (*pat == '-' && pat[1] != ']')
+ {
+ ++pat;
+ if (*pat == '\0')
+ {
+ pat = ccstart;
+ goto defl;
+ }
+ c2 = *pat++;
+ if (*str >= c && *str <= c2)
+ ccmatch = true;
+ }
+ else
+ {
+ if (*str == c)
+ ccmatch = true;
+ }
+ }
+ if (ccmatch ^ ccnot)
+ {
+ ++pat;
+ ++str;
+ }
+ else
+ return false;
+ continue;
+ default:
+ defl:
+ if (*pat != *str)
+ return false;
+ ++pat;
+ ++str;
+ continue;
+ }
+ }
+}
diff --git a/usr/src/cmd/sendmail/libsm/mbdb.c b/usr/src/cmd/sendmail/libsm/mbdb.c
new file mode 100644
index 0000000000..d1a66d5894
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/mbdb.c
@@ -0,0 +1,776 @@
+/*
+ * Copyright (c) 2001-2002 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: mbdb.c,v 1.40 2003/12/10 03:19:07 gshapiro Exp $")
+
+#include <sys/param.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <pwd.h>
+#include <stdlib.h>
+#include <setjmp.h>
+#include <unistd.h>
+
+#include <sm/limits.h>
+#include <sm/conf.h>
+#include <sm/assert.h>
+#include <sm/bitops.h>
+#include <sm/errstring.h>
+#include <sm/heap.h>
+#include <sm/mbdb.h>
+#include <sm/string.h>
+# ifdef EX_OK
+# undef EX_OK /* for SVr4.2 SMP */
+# endif /* EX_OK */
+#include <sm/sysexits.h>
+
+#if LDAPMAP
+# if _LDAP_EXAMPLE_
+# include <sm/ldap.h>
+# endif /* _LDAP_EXAMPLE_ */
+#endif /* LDAPMAP */
+
+typedef struct
+{
+ char *mbdb_typename;
+ int (*mbdb_initialize) __P((char *));
+ int (*mbdb_lookup) __P((char *name, SM_MBDB_T *user));
+ void (*mbdb_terminate) __P((void));
+} SM_MBDB_TYPE_T;
+
+static int mbdb_pw_initialize __P((char *));
+static int mbdb_pw_lookup __P((char *name, SM_MBDB_T *user));
+static void mbdb_pw_terminate __P((void));
+
+#if LDAPMAP
+# if _LDAP_EXAMPLE_
+static struct sm_ldap_struct LDAPLMAP;
+static int mbdb_ldap_initialize __P((char *));
+static int mbdb_ldap_lookup __P((char *name, SM_MBDB_T *user));
+static void mbdb_ldap_terminate __P((void));
+# endif /* _LDAP_EXAMPLE_ */
+#endif /* LDAPMAP */
+
+static SM_MBDB_TYPE_T SmMbdbTypes[] =
+{
+ { "pw", mbdb_pw_initialize, mbdb_pw_lookup, mbdb_pw_terminate },
+#if LDAPMAP
+# if _LDAP_EXAMPLE_
+ { "ldap", mbdb_ldap_initialize, mbdb_ldap_lookup, mbdb_ldap_terminate },
+# endif /* _LDAP_EXAMPLE_ */
+#endif /* LDAPMAP */
+ { NULL, NULL, NULL, NULL }
+};
+
+static SM_MBDB_TYPE_T *SmMbdbType = &SmMbdbTypes[0];
+
+/*
+** SM_MBDB_INITIALIZE -- specify which mailbox database to use
+**
+** If this function is not called, then the "pw" implementation
+** is used by default; this implementation uses getpwnam().
+**
+** Parameters:
+** mbdb -- Which mailbox database to use.
+** The argument has the form "name" or "name.arg".
+** "pw" means use getpwnam().
+**
+** Results:
+** EX_OK on success, or an EX_* code on failure.
+*/
+
+int
+sm_mbdb_initialize(mbdb)
+ char *mbdb;
+{
+ size_t namelen;
+ int err;
+ char *name;
+ char *arg;
+ SM_MBDB_TYPE_T *t;
+
+ SM_REQUIRE(mbdb != NULL);
+
+ name = mbdb;
+ arg = strchr(mbdb, '.');
+ if (arg == NULL)
+ namelen = strlen(name);
+ else
+ {
+ namelen = arg - name;
+ ++arg;
+ }
+
+ for (t = SmMbdbTypes; t->mbdb_typename != NULL; ++t)
+ {
+ if (strlen(t->mbdb_typename) == namelen &&
+ strncmp(name, t->mbdb_typename, namelen) == 0)
+ {
+ err = EX_OK;
+ if (t->mbdb_initialize != NULL)
+ err = t->mbdb_initialize(arg);
+ if (err == EX_OK)
+ SmMbdbType = t;
+ return err;
+ }
+ }
+ return EX_UNAVAILABLE;
+}
+
+/*
+** SM_MBDB_TERMINATE -- terminate connection to the mailbox database
+**
+** Because this function closes any cached file descriptors that
+** are being held open for the connection to the mailbox database,
+** it should be called for security reasons prior to dropping privileges
+** and execing another process.
+**
+** Parameters:
+** none.
+**
+** Results:
+** none.
+*/
+
+void
+sm_mbdb_terminate()
+{
+ if (SmMbdbType->mbdb_terminate != NULL)
+ SmMbdbType->mbdb_terminate();
+}
+
+/*
+** SM_MBDB_LOOKUP -- look up a local mail recipient, given name
+**
+** Parameters:
+** name -- name of local mail recipient
+** user -- pointer to structure to fill in on success
+**
+** Results:
+** On success, fill in *user and return EX_OK.
+** If the user does not exist, return EX_NOUSER.
+** If a temporary failure (eg, a network failure) occurred,
+** return EX_TEMPFAIL. Otherwise return EX_OSERR.
+*/
+
+int
+sm_mbdb_lookup(name, user)
+ char *name;
+ SM_MBDB_T *user;
+{
+ int ret = EX_NOUSER;
+
+ if (SmMbdbType->mbdb_lookup != NULL)
+ ret = SmMbdbType->mbdb_lookup(name, user);
+ return ret;
+}
+
+/*
+** SM_MBDB_FROMPW -- copy from struct pw to SM_MBDB_T
+**
+** Parameters:
+** user -- destination user information structure
+** pw -- source passwd structure
+**
+** Results:
+** none.
+*/
+
+void
+sm_mbdb_frompw(user, pw)
+ SM_MBDB_T *user;
+ struct passwd *pw;
+{
+ SM_REQUIRE(user != NULL);
+ (void) sm_strlcpy(user->mbdb_name, pw->pw_name,
+ sizeof(user->mbdb_name));
+ user->mbdb_uid = pw->pw_uid;
+ user->mbdb_gid = pw->pw_gid;
+ sm_pwfullname(pw->pw_gecos, pw->pw_name, user->mbdb_fullname,
+ sizeof(user->mbdb_fullname));
+ (void) sm_strlcpy(user->mbdb_homedir, pw->pw_dir,
+ sizeof(user->mbdb_homedir));
+ (void) sm_strlcpy(user->mbdb_shell, pw->pw_shell,
+ sizeof(user->mbdb_shell));
+}
+
+/*
+** SM_PWFULLNAME -- build full name of user from pw_gecos field.
+**
+** This routine interprets the strange entry that would appear
+** in the GECOS field of the password file.
+**
+** Parameters:
+** gecos -- name to build.
+** user -- the login name of this user (for &).
+** buf -- place to put the result.
+** buflen -- length of buf.
+**
+** Returns:
+** none.
+*/
+
+#if _FFR_HANDLE_ISO8859_GECOS
+static char Latin1ToASCII[128] =
+{
+ 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 33,
+ 99, 80, 36, 89, 124, 36, 34, 99, 97, 60, 45, 45, 114, 45, 111, 42,
+ 50, 51, 39, 117, 80, 46, 44, 49, 111, 62, 42, 42, 42, 63, 65, 65,
+ 65, 65, 65, 65, 65, 67, 69, 69, 69, 69, 73, 73, 73, 73, 68, 78, 79,
+ 79, 79, 79, 79, 88, 79, 85, 85, 85, 85, 89, 80, 66, 97, 97, 97, 97,
+ 97, 97, 97, 99, 101, 101, 101, 101, 105, 105, 105, 105, 100, 110,
+ 111, 111, 111, 111, 111, 47, 111, 117, 117, 117, 117, 121, 112, 121
+};
+#endif /* _FFR_HANDLE_ISO8859_GECOS */
+
+void
+sm_pwfullname(gecos, user, buf, buflen)
+ register char *gecos;
+ char *user;
+ char *buf;
+ size_t buflen;
+{
+ register char *p;
+ register char *bp = buf;
+
+ if (*gecos == '*')
+ gecos++;
+
+ /* copy gecos, interpolating & to be full name */
+ for (p = gecos; *p != '\0' && *p != ',' && *p != ';' && *p != '%'; p++)
+ {
+ if (bp >= &buf[buflen - 1])
+ {
+ /* buffer overflow -- just use login name */
+ (void) sm_strlcpy(buf, user, buflen);
+ return;
+ }
+ if (*p == '&')
+ {
+ /* interpolate full name */
+ (void) sm_strlcpy(bp, user, buflen - (bp - buf));
+ *bp = toupper(*bp);
+ bp += strlen(bp);
+ }
+ else
+ {
+#if _FFR_HANDLE_ISO8859_GECOS
+ if ((unsigned char) *p >= 128)
+ *bp++ = Latin1ToASCII[(unsigned char) *p - 128];
+ else
+#endif /* _FFR_HANDLE_ISO8859_GECOS */
+ *bp++ = *p;
+ }
+ }
+ *bp = '\0';
+}
+
+/*
+** /etc/passwd implementation.
+*/
+
+/*
+** MBDB_PW_INITIALIZE -- initialize getpwnam() version
+**
+** Parameters:
+** arg -- unused.
+**
+** Results:
+** EX_OK.
+*/
+
+/* ARGSUSED0 */
+static int
+mbdb_pw_initialize(arg)
+ char *arg;
+{
+ return EX_OK;
+}
+
+/*
+** MBDB_PW_LOOKUP -- look up a local mail recipient, given name
+**
+** Parameters:
+** name -- name of local mail recipient
+** user -- pointer to structure to fill in on success
+**
+** Results:
+** On success, fill in *user and return EX_OK.
+** Failure: EX_NOUSER.
+*/
+
+static int
+mbdb_pw_lookup(name, user)
+ char *name;
+ SM_MBDB_T *user;
+{
+ struct passwd *pw;
+
+#ifdef HESIOD
+ /* DEC Hesiod getpwnam accepts numeric strings -- short circuit it */
+ {
+ char *p;
+
+ for (p = name; *p != '\0'; p++)
+ if (!isascii(*p) || !isdigit(*p))
+ break;
+ if (*p == '\0')
+ return EX_NOUSER;
+ }
+#endif /* HESIOD */
+
+ errno = 0;
+ pw = getpwnam(name);
+ if (pw == NULL)
+ {
+#if 0
+ /*
+ ** getpwnam() isn't advertised as setting errno.
+ ** In fact, under FreeBSD, non-root getpwnam() on
+ ** non-existant users returns NULL with errno = EPERM.
+ ** This test won't work.
+ */
+ switch (errno)
+ {
+ case 0:
+ return EX_NOUSER;
+ case EIO:
+ return EX_OSERR;
+ default:
+ return EX_TEMPFAIL;
+ }
+#endif /* 0 */
+ return EX_NOUSER;
+ }
+
+ sm_mbdb_frompw(user, pw);
+ return EX_OK;
+}
+
+/*
+** MBDB_PW_TERMINATE -- terminate connection to the mailbox database
+**
+** Parameters:
+** none.
+**
+** Results:
+** none.
+*/
+
+static void
+mbdb_pw_terminate()
+{
+ endpwent();
+}
+
+#if LDAPMAP
+# if _LDAP_EXAMPLE_
+/*
+** LDAP example implementation based on RFC 2307, "An Approach for Using
+** LDAP as a Network Information Service":
+**
+** ( nisSchema.1.0 NAME 'uidNumber'
+** DESC 'An integer uniquely identifying a user in an
+** administrative domain'
+** EQUALITY integerMatch SYNTAX 'INTEGER' SINGLE-VALUE )
+**
+** ( nisSchema.1.1 NAME 'gidNumber'
+** DESC 'An integer uniquely identifying a group in an
+** administrative domain'
+** EQUALITY integerMatch SYNTAX 'INTEGER' SINGLE-VALUE )
+**
+** ( nisSchema.1.2 NAME 'gecos'
+** DESC 'The GECOS field; the common name'
+** EQUALITY caseIgnoreIA5Match
+** SUBSTRINGS caseIgnoreIA5SubstringsMatch
+** SYNTAX 'IA5String' SINGLE-VALUE )
+**
+** ( nisSchema.1.3 NAME 'homeDirectory'
+** DESC 'The absolute path to the home directory'
+** EQUALITY caseExactIA5Match
+** SYNTAX 'IA5String' SINGLE-VALUE )
+**
+** ( nisSchema.1.4 NAME 'loginShell'
+** DESC 'The path to the login shell'
+** EQUALITY caseExactIA5Match
+** SYNTAX 'IA5String' SINGLE-VALUE )
+**
+** ( nisSchema.2.0 NAME 'posixAccount' SUP top AUXILIARY
+** DESC 'Abstraction of an account with POSIX attributes'
+** MUST ( cn $ uid $ uidNumber $ gidNumber $ homeDirectory )
+** MAY ( userPassword $ loginShell $ gecos $ description ) )
+**
+*/
+
+# define MBDB_LDAP_LABEL "MailboxDatabase"
+
+# ifndef MBDB_LDAP_FILTER
+# define MBDB_LDAP_FILTER "(&(objectClass=posixAccount)(uid=%0))"
+# endif /* MBDB_LDAP_FILTER */
+
+# ifndef MBDB_DEFAULT_LDAP_BASEDN
+# define MBDB_DEFAULT_LDAP_BASEDN NULL
+# endif /* MBDB_DEFAULT_LDAP_BASEDN */
+
+# ifndef MBDB_DEFAULT_LDAP_SERVER
+# define MBDB_DEFAULT_LDAP_SERVER NULL
+# endif /* MBDB_DEFAULT_LDAP_SERVER */
+
+/*
+** MBDB_LDAP_INITIALIZE -- initialize LDAP version
+**
+** Parameters:
+** arg -- LDAP specification
+**
+** Results:
+** EX_OK on success, or an EX_* code on failure.
+*/
+
+static int
+mbdb_ldap_initialize(arg)
+ char *arg;
+{
+ sm_ldap_clear(&LDAPLMAP);
+ LDAPLMAP.ldap_base = MBDB_DEFAULT_LDAP_BASEDN;
+ LDAPLMAP.ldap_host = MBDB_DEFAULT_LDAP_SERVER;
+ LDAPLMAP.ldap_filter = MBDB_LDAP_FILTER;
+
+ /* Only want one match */
+ LDAPLMAP.ldap_sizelimit = 1;
+
+ /* interpolate new ldap_base and ldap_host from arg if given */
+ if (arg != NULL && *arg != '\0')
+ {
+ char *new;
+ char *sep;
+ size_t len;
+
+ len = strlen(arg) + 1;
+ new = sm_malloc(len);
+ if (new == NULL)
+ return EX_TEMPFAIL;
+ (void) sm_strlcpy(new, arg, len);
+ sep = strrchr(new, '@');
+ if (sep != NULL)
+ {
+ *sep++ = '\0';
+ LDAPLMAP.ldap_host = sep;
+ }
+ LDAPLMAP.ldap_base = new;
+ }
+ return EX_OK;
+}
+
+
+/*
+** MBDB_LDAP_LOOKUP -- look up a local mail recipient, given name
+**
+** Parameters:
+** name -- name of local mail recipient
+** user -- pointer to structure to fill in on success
+**
+** Results:
+** On success, fill in *user and return EX_OK.
+** Failure: EX_NOUSER.
+*/
+
+#define NEED_FULLNAME 0x01
+#define NEED_HOMEDIR 0x02
+#define NEED_SHELL 0x04
+#define NEED_UID 0x08
+#define NEED_GID 0x10
+
+static int
+mbdb_ldap_lookup(name, user)
+ char *name;
+ SM_MBDB_T *user;
+{
+ int msgid;
+ int need;
+ int ret;
+ int save_errno;
+ LDAPMessage *entry;
+ BerElement *ber;
+ char *attr = NULL;
+
+ if (strlen(name) >= sizeof(user->mbdb_name))
+ {
+ errno = EINVAL;
+ return EX_NOUSER;
+ }
+
+ if (LDAPLMAP.ldap_filter == NULL)
+ {
+ /* map not initialized, but don't have arg here */
+ errno = EFAULT;
+ return EX_TEMPFAIL;
+ }
+
+ if (LDAPLMAP.ldap_pid != getpid())
+ {
+ /* re-open map in this child process */
+ LDAPLMAP.ldap_ld = NULL;
+ }
+
+ if (LDAPLMAP.ldap_ld == NULL)
+ {
+ /* map not open, try to open now */
+ if (!sm_ldap_start(MBDB_LDAP_LABEL, &LDAPLMAP))
+ return EX_TEMPFAIL;
+ }
+
+ sm_ldap_setopts(LDAPLMAP.ldap_ld, &LDAPLMAP);
+ msgid = sm_ldap_search(&LDAPLMAP, name);
+ if (msgid == -1)
+ {
+ save_errno = sm_ldap_geterrno(LDAPLMAP.ldap_ld) + E_LDAPBASE;
+# ifdef LDAP_SERVER_DOWN
+ if (errno == LDAP_SERVER_DOWN)
+ {
+ /* server disappeared, try reopen on next search */
+ sm_ldap_close(&LDAPLMAP);
+ }
+# endif /* LDAP_SERVER_DOWN */
+ errno = save_errno;
+ return EX_TEMPFAIL;
+ }
+
+ /* Get results */
+ ret = ldap_result(LDAPLMAP.ldap_ld, msgid, 1,
+ (LDAPLMAP.ldap_timeout.tv_sec == 0 ? NULL :
+ &(LDAPLMAP.ldap_timeout)),
+ &(LDAPLMAP.ldap_res));
+
+ if (ret != LDAP_RES_SEARCH_RESULT &&
+ ret != LDAP_RES_SEARCH_ENTRY)
+ {
+ if (ret == 0)
+ errno = ETIMEDOUT;
+ else
+ errno = sm_ldap_geterrno(LDAPLMAP.ldap_ld);
+ ret = EX_TEMPFAIL;
+ goto abort;
+ }
+
+ entry = ldap_first_entry(LDAPLMAP.ldap_ld, LDAPLMAP.ldap_res);
+ if (entry == NULL)
+ {
+ save_errno = sm_ldap_geterrno(LDAPLMAP.ldap_ld);
+ if (save_errno == LDAP_SUCCESS)
+ {
+ errno = ENOENT;
+ ret = EX_NOUSER;
+ }
+ else
+ {
+ errno = save_errno;
+ ret = EX_TEMPFAIL;
+ }
+ goto abort;
+ }
+
+# if !defined(LDAP_VERSION_MAX) && !defined(LDAP_OPT_SIZELIMIT)
+ /*
+ ** Reset value to prevent lingering
+ ** LDAP_DECODING_ERROR due to
+ ** OpenLDAP 1.X's hack (see below)
+ */
+
+ LDAPLMAP.ldap_ld->ld_errno = LDAP_SUCCESS;
+# endif /* !defined(LDAP_VERSION_MAX) !defined(LDAP_OPT_SIZELIMIT) */
+
+ ret = EX_OK;
+ need = NEED_FULLNAME|NEED_HOMEDIR|NEED_SHELL|NEED_UID|NEED_GID;
+ for (attr = ldap_first_attribute(LDAPLMAP.ldap_ld, entry, &ber);
+ attr != NULL;
+ attr = ldap_next_attribute(LDAPLMAP.ldap_ld, entry, ber))
+ {
+ char **vals;
+
+ vals = ldap_get_values(LDAPLMAP.ldap_ld, entry, attr);
+ if (vals == NULL)
+ {
+ errno = sm_ldap_geterrno(LDAPLMAP.ldap_ld);
+ if (errno == LDAP_SUCCESS)
+ {
+ ldap_memfree(attr);
+ continue;
+ }
+
+ /* Must be an error */
+ errno += E_LDAPBASE;
+ ret = EX_TEMPFAIL;
+ goto abort;
+ }
+
+# if !defined(LDAP_VERSION_MAX) && !defined(LDAP_OPT_SIZELIMIT)
+ /*
+ ** Reset value to prevent lingering
+ ** LDAP_DECODING_ERROR due to
+ ** OpenLDAP 1.X's hack (see below)
+ */
+
+ LDAPLMAP.ldap_ld->ld_errno = LDAP_SUCCESS;
+# endif /* !defined(LDAP_VERSION_MAX) !defined(LDAP_OPT_SIZELIMIT) */
+
+ if (vals[0] == NULL || vals[0][0] == '\0')
+ goto skip;
+
+ if (strcasecmp(attr, "gecos") == 0)
+ {
+ if (!bitset(NEED_FULLNAME, need) ||
+ strlen(vals[0]) >= sizeof(user->mbdb_fullname))
+ goto skip;
+
+ sm_pwfullname(vals[0], name, user->mbdb_fullname,
+ sizeof(user->mbdb_fullname));
+ need &= ~NEED_FULLNAME;
+ }
+ else if (strcasecmp(attr, "homeDirectory") == 0)
+ {
+ if (!bitset(NEED_HOMEDIR, need) ||
+ strlen(vals[0]) >= sizeof(user->mbdb_homedir))
+ goto skip;
+
+ (void) sm_strlcpy(user->mbdb_homedir, vals[0],
+ sizeof(user->mbdb_homedir));
+ need &= ~NEED_HOMEDIR;
+ }
+ else if (strcasecmp(attr, "loginShell") == 0)
+ {
+ if (!bitset(NEED_SHELL, need) ||
+ strlen(vals[0]) >= sizeof(user->mbdb_shell))
+ goto skip;
+
+ (void) sm_strlcpy(user->mbdb_shell, vals[0],
+ sizeof(user->mbdb_shell));
+ need &= ~NEED_SHELL;
+ }
+ else if (strcasecmp(attr, "uidNumber") == 0)
+ {
+ char *p;
+
+ if (!bitset(NEED_UID, need))
+ goto skip;
+
+ for (p = vals[0]; *p != '\0'; p++)
+ {
+ /* allow negative numbers */
+ if (p == vals[0] && *p == '-')
+ {
+ /* but not simply '-' */
+ if (*(p + 1) == '\0')
+ goto skip;
+ }
+ else if (!isascii(*p) || !isdigit(*p))
+ goto skip;
+ }
+ user->mbdb_uid = atoi(vals[0]);
+ need &= ~NEED_UID;
+ }
+ else if (strcasecmp(attr, "gidNumber") == 0)
+ {
+ char *p;
+
+ if (!bitset(NEED_GID, need))
+ goto skip;
+
+ for (p = vals[0]; *p != '\0'; p++)
+ {
+ /* allow negative numbers */
+ if (p == vals[0] && *p == '-')
+ {
+ /* but not simply '-' */
+ if (*(p + 1) == '\0')
+ goto skip;
+ }
+ else if (!isascii(*p) || !isdigit(*p))
+ goto skip;
+ }
+ user->mbdb_gid = atoi(vals[0]);
+ need &= ~NEED_GID;
+ }
+
+skip:
+ ldap_value_free(vals);
+ ldap_memfree(attr);
+ }
+
+ errno = sm_ldap_geterrno(LDAPLMAP.ldap_ld);
+
+ /*
+ ** We check errno != LDAP_DECODING_ERROR since
+ ** OpenLDAP 1.X has a very ugly *undocumented*
+ ** hack of returning this error code from
+ ** ldap_next_attribute() if the library freed the
+ ** ber attribute. See:
+ ** http://www.openldap.org/lists/openldap-devel/9901/msg00064.html
+ */
+
+ if (errno != LDAP_SUCCESS &&
+ errno != LDAP_DECODING_ERROR)
+ {
+ /* Must be an error */
+ errno += E_LDAPBASE;
+ ret = EX_TEMPFAIL;
+ goto abort;
+ }
+
+ abort:
+ save_errno = errno;
+ if (attr != NULL)
+ {
+ ldap_memfree(attr);
+ attr = NULL;
+ }
+ if (LDAPLMAP.ldap_res != NULL)
+ {
+ ldap_msgfree(LDAPLMAP.ldap_res);
+ LDAPLMAP.ldap_res = NULL;
+ }
+ if (ret == EX_OK)
+ {
+ if (need == 0)
+ {
+ (void) sm_strlcpy(user->mbdb_name, name,
+ sizeof(user->mbdb_name));
+ save_errno = 0;
+ }
+ else
+ {
+ ret = EX_NOUSER;
+ save_errno = EINVAL;
+ }
+ }
+ errno = save_errno;
+ return ret;
+}
+
+/*
+** MBDB_LDAP_TERMINATE -- terminate connection to the mailbox database
+**
+** Parameters:
+** none.
+**
+** Results:
+** none.
+*/
+
+static void
+mbdb_ldap_terminate()
+{
+ sm_ldap_close(&LDAPLMAP);
+}
+# endif /* _LDAP_EXAMPLE_ */
+#endif /* LDAPMAP */
diff --git a/usr/src/cmd/sendmail/libsm/niprop.c b/usr/src/cmd/sendmail/libsm/niprop.c
new file mode 100644
index 0000000000..ed1201d479
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/niprop.c
@@ -0,0 +1,215 @@
+/*
+ * Copyright (c) 2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: niprop.c,v 1.6 2001/09/04 22:41:27 ca Exp $")
+
+#if NETINFO
+#include <ctype.h>
+#include <stdlib.h>
+#include <sm/io.h>
+#include <sm/assert.h>
+#include <sm/debug.h>
+#include <sm/string.h>
+#include <sm/varargs.h>
+#include <sm/heap.h>
+
+/*
+** NI_PROPVAL -- NetInfo property value lookup routine
+**
+** Parameters:
+** keydir -- the NetInfo directory name in which to search
+** for the key.
+** keyprop -- the name of the property in which to find the
+** property we are interested. Defaults to "name".
+** keyval -- the value for which we are really searching.
+** valprop -- the property name for the value in which we
+** are interested.
+** sepchar -- if non-nil, this can be multiple-valued, and
+** we should return a string separated by this
+** character.
+**
+** Returns:
+** NULL -- if:
+** 1. the directory is not found
+** 2. the property name is not found
+** 3. the property contains multiple values
+** 4. some error occurred
+** else -- the value of the lookup.
+**
+** Example:
+** To search for an alias value, use:
+** ni_propval("/aliases", "name", aliasname, "members", ',')
+**
+** Notes:
+** Caller should free the return value of ni_proval
+*/
+
+# include <netinfo/ni.h>
+
+# define LOCAL_NETINFO_DOMAIN "."
+# define PARENT_NETINFO_DOMAIN ".."
+# define MAX_NI_LEVELS 256
+
+char *
+ni_propval(keydir, keyprop, keyval, valprop, sepchar)
+ char *keydir;
+ char *keyprop;
+ char *keyval;
+ char *valprop;
+ int sepchar;
+{
+ char *propval = NULL;
+ int i;
+ int j, alen, l;
+ void *ni = NULL;
+ void *lastni = NULL;
+ ni_status nis;
+ ni_id nid;
+ ni_namelist ninl;
+ register char *p;
+ char keybuf[1024];
+
+ /*
+ ** Create the full key from the two parts.
+ **
+ ** Note that directory can end with, e.g., "name=" to specify
+ ** an alternate search property.
+ */
+
+ i = strlen(keydir) + strlen(keyval) + 2;
+ if (keyprop != NULL)
+ i += strlen(keyprop) + 1;
+ if (i >= sizeof keybuf)
+ return NULL;
+ (void) sm_strlcpyn(keybuf, sizeof keybuf, 2, keydir, "/");
+ if (keyprop != NULL)
+ {
+ (void) sm_strlcat2(keybuf, keyprop, "=", sizeof keybuf);
+ }
+ (void) sm_strlcat(keybuf, keyval, sizeof keybuf);
+
+#if 0
+ if (tTd(38, 21))
+ sm_dprintf("ni_propval(%s, %s, %s, %s, %d) keybuf='%s'\n",
+ keydir, keyprop, keyval, valprop, sepchar, keybuf);
+#endif /* 0 */
+
+ /*
+ ** If the passed directory and property name are found
+ ** in one of netinfo domains we need to search (starting
+ ** from the local domain moving all the way back to the
+ ** root domain) set propval to the property's value
+ ** and return it.
+ */
+
+ for (i = 0; i < MAX_NI_LEVELS && propval == NULL; i++)
+ {
+ if (i == 0)
+ {
+ nis = ni_open(NULL, LOCAL_NETINFO_DOMAIN, &ni);
+#if 0
+ if (tTd(38, 20))
+ sm_dprintf("ni_open(LOCAL) = %d\n", nis);
+#endif /* 0 */
+ }
+ else
+ {
+ if (lastni != NULL)
+ ni_free(lastni);
+ lastni = ni;
+ nis = ni_open(lastni, PARENT_NETINFO_DOMAIN, &ni);
+#if 0
+ if (tTd(38, 20))
+ sm_dprintf("ni_open(PARENT) = %d\n", nis);
+#endif /* 0 */
+ }
+
+ /*
+ ** Don't bother if we didn't get a handle on a
+ ** proper domain. This is not necessarily an error.
+ ** We would get a positive ni_status if, for instance
+ ** we never found the directory or property and tried
+ ** to open the parent of the root domain!
+ */
+
+ if (nis != 0)
+ break;
+
+ /*
+ ** Find the path to the server information.
+ */
+
+ if (ni_pathsearch(ni, &nid, keybuf) != 0)
+ continue;
+
+ /*
+ ** Find associated value information.
+ */
+
+ if (ni_lookupprop(ni, &nid, valprop, &ninl) != 0)
+ continue;
+
+#if 0
+ if (tTd(38, 20))
+ sm_dprintf("ni_lookupprop: len=%d\n",
+ ninl.ni_namelist_len);
+#endif /* 0 */
+
+ /*
+ ** See if we have an acceptable number of values.
+ */
+
+ if (ninl.ni_namelist_len <= 0)
+ continue;
+
+ if (sepchar == '\0' && ninl.ni_namelist_len > 1)
+ {
+ ni_namelist_free(&ninl);
+ continue;
+ }
+
+ /*
+ ** Calculate number of bytes needed and build result
+ */
+
+ alen = 1;
+ for (j = 0; j < ninl.ni_namelist_len; j++)
+ alen += strlen(ninl.ni_namelist_val[j]) + 1;
+ propval = p = sm_malloc(alen);
+ if (propval == NULL)
+ goto cleanup;
+ for (j = 0; j < ninl.ni_namelist_len; j++)
+ {
+ (void) sm_strlcpy(p, ninl.ni_namelist_val[j], alen);
+ l = strlen(p);
+ p += l;
+ *p++ = sepchar;
+ alen -= l + 1;
+ }
+ *--p = '\0';
+
+ ni_namelist_free(&ninl);
+ }
+
+ cleanup:
+ if (ni != NULL)
+ ni_free(ni);
+ if (lastni != NULL && ni != lastni)
+ ni_free(lastni);
+#if 0
+ if (tTd(38, 20))
+ sm_dprintf("ni_propval returns: '%s'\n", propval);
+#endif /* 0 */
+
+ return propval;
+}
+#endif /* NETINFO */
diff --git a/usr/src/cmd/sendmail/libsm/path.c b/usr/src/cmd/sendmail/libsm/path.c
new file mode 100644
index 0000000000..517a948dd5
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/path.c
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: path.c,v 1.7 2001/08/28 16:06:59 gshapiro Exp $")
+
+#include <sm/path.h>
+#include <sm/string.h>
+
diff --git a/usr/src/cmd/sendmail/libsm/put.c b/usr/src/cmd/sendmail/libsm/put.c
new file mode 100644
index 0000000000..7940d102b3
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/put.c
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: put.c,v 1.27 2001/12/19 05:19:35 ca Exp $")
+#include <string.h>
+#include <errno.h>
+#include <sm/io.h>
+#include <sm/assert.h>
+#include <sm/errstring.h>
+#include <sm/string.h>
+#include "local.h"
+#include "fvwrite.h"
+
+/*
+** SM_IO_PUTC -- output a character to the file
+**
+** Function version of the macro sm_io_putc (in <sm/io.h>).
+**
+** Parameters:
+** fp -- file to output to
+** timeout -- time to complete putc
+** c -- int value of character to output
+**
+** Returns:
+** Failure: returns SM_IO_EOF _and_ sets errno
+** Success: returns sm_putc() value.
+**
+*/
+
+#undef sm_io_putc
+
+int
+sm_io_putc(fp, timeout, c)
+ SM_FILE_T *fp;
+ int timeout;
+ int c;
+{
+ SM_REQUIRE_ISA(fp, SmFileMagic);
+ if (cantwrite(fp))
+ {
+ errno = EBADF;
+ return SM_IO_EOF;
+ }
+ return sm_putc(fp, timeout, c);
+}
+
+
+/*
+** SM_PERROR -- print system error messages to smioerr
+**
+** Parameters:
+** s -- message to print
+**
+** Returns:
+** none
+*/
+
+void
+sm_perror(s)
+ const char *s;
+{
+ int save_errno = errno;
+
+ if (s != NULL && *s != '\0')
+ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, "%s: ", s);
+ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, "%s\n",
+ sm_errstring(save_errno));
+}
diff --git a/usr/src/cmd/sendmail/libsm/refill.c b/usr/src/cmd/sendmail/libsm/refill.c
new file mode 100644
index 0000000000..6e1eb5b2a6
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/refill.c
@@ -0,0 +1,301 @@
+/*
+ * Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: refill.c,v 1.49.2.1 2002/09/09 21:38:08 gshapiro Exp $")
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <setjmp.h>
+#include <signal.h>
+#include <sys/time.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sm/io.h>
+#include <sm/conf.h>
+#include <sm/assert.h>
+#include "local.h"
+
+static int sm_lflush __P((SM_FILE_T *, int *));
+
+/*
+** SM_IO_RD_TIMEOUT -- measured timeout for reads
+**
+** This #define uses a select() to wait for the 'fd' to become readable.
+** The select() can be active for up to 'To' time. The select() may not
+** use all of the the 'To' time. Hence, the amount of "wall-clock" time is
+** measured to decide how much to subtract from 'To' to update it. On some
+** BSD-based/like systems the timeout for a select() is updated for the
+** amount of time used. On many/most systems this does not happen. Therefore
+** the updating of 'To' must be done ourselves; a copy of 'To' is passed
+** since a BSD-like system will have updated it and we don't want to
+** double the time used!
+** Note: if a valid 'fd' doesn't exist yet, don't use this (e.g. the
+** sendmail buffered file type in sendmail/bf.c; see use below).
+**
+** Parameters
+** fp -- the file pointer for the active file
+** fd -- raw file descriptor (from 'fp') to use for select()
+** to -- struct timeval of the timeout
+** timeout -- the original timeout value
+** sel_ret -- the return value from the select()
+**
+** Returns:
+** nothing, flow through code
+*/
+
+#define SM_IO_RD_TIMEOUT(fp, fd, to, timeout, sel_ret) \
+{ \
+ struct timeval sm_io_to_before, sm_io_to_after, sm_io_to_diff; \
+ fd_set sm_io_to_mask, sm_io_x_mask; \
+ errno = 0; \
+ if (timeout == SM_TIME_IMMEDIATE) \
+ { \
+ errno = EAGAIN; \
+ return SM_IO_EOF; \
+ } \
+ if (FD_SETSIZE > 0 && (fd) >= FD_SETSIZE) \
+ { \
+ errno = EINVAL; \
+ return SM_IO_EOF; \
+ } \
+ FD_ZERO(&sm_io_to_mask); \
+ FD_SET((fd), &sm_io_to_mask); \
+ FD_ZERO(&sm_io_x_mask); \
+ FD_SET((fd), &sm_io_x_mask); \
+ if (gettimeofday(&sm_io_to_before, NULL) < 0) \
+ return SM_IO_EOF; \
+ (sel_ret) = select((fd) + 1, &sm_io_to_mask, NULL, \
+ &sm_io_x_mask, (to)); \
+ if ((sel_ret) < 0) \
+ { \
+ /* something went wrong, errno set */ \
+ fp->f_r = 0; \
+ fp->f_flags |= SMERR; \
+ return SM_IO_EOF; \
+ } \
+ else if ((sel_ret) == 0) \
+ { \
+ /* timeout */ \
+ errno = EAGAIN; \
+ return SM_IO_EOF; \
+ } \
+ /* calulate wall-clock time used */ \
+ if (gettimeofday(&sm_io_to_after, NULL) < 0) \
+ return SM_IO_EOF; \
+ timersub(&sm_io_to_before, &sm_io_to_after, &sm_io_to_diff); \
+ timersub((to), &sm_io_to_diff, (to)); \
+}
+
+/*
+** SM_LFLUSH -- flush a file if it is line buffered and writable
+**
+** Parameters:
+** fp -- file pointer to flush
+** timeout -- original timeout value (in milliseconds)
+**
+** Returns:
+** Failure: returns SM_IO_EOF and sets errno
+** Success: returns 0
+*/
+
+static int
+sm_lflush(fp, timeout)
+ SM_FILE_T *fp;
+ int *timeout;
+{
+
+ if ((fp->f_flags & (SMLBF|SMWR)) == (SMLBF|SMWR))
+ return sm_flush(fp, timeout);
+ return 0;
+}
+
+/*
+** SM_REFILL -- refill a buffer
+**
+** Parameters:
+** fp -- file pointer for buffer refill
+** timeout -- time to complete filling the buffer in milliseconds
+**
+** Returns:
+** Success: returns 0
+** Failure: returns SM_IO_EOF
+*/
+
+int
+sm_refill(fp, timeout)
+ register SM_FILE_T *fp;
+ int timeout;
+{
+ int ret, r;
+ struct timeval to;
+ int fd;
+
+ if (timeout == SM_TIME_DEFAULT)
+ timeout = fp->f_timeout;
+ if (timeout == SM_TIME_IMMEDIATE)
+ {
+ /*
+ ** Filling the buffer will take time and we are wanted to
+ ** return immediately. And we're not EOF or ERR really.
+ ** So... the failure is we couldn't do it in time.
+ */
+
+ errno = EAGAIN;
+ fp->f_r = 0; /* just to be sure */
+ return 0;
+ }
+
+ /* make sure stdio is set up */
+ if (!Sm_IO_DidInit)
+ sm_init();
+
+ fp->f_r = 0; /* largely a convenience for callers */
+
+ if (fp->f_flags & SMFEOF)
+ return SM_IO_EOF;
+
+ SM_CONVERT_TIME(fp, fd, timeout, &to);
+
+ /* if not already reading, have to be reading and writing */
+ if ((fp->f_flags & SMRD) == 0)
+ {
+ if ((fp->f_flags & SMRW) == 0)
+ {
+ errno = EBADF;
+ fp->f_flags |= SMERR;
+ return SM_IO_EOF;
+ }
+
+ /* switch to reading */
+ if (fp->f_flags & SMWR)
+ {
+ if (sm_flush(fp, &timeout))
+ return SM_IO_EOF;
+ fp->f_flags &= ~SMWR;
+ fp->f_w = 0;
+ fp->f_lbfsize = 0;
+ }
+ fp->f_flags |= SMRD;
+ }
+ else
+ {
+ /*
+ ** We were reading. If there is an ungetc buffer,
+ ** we must have been reading from that. Drop it,
+ ** restoring the previous buffer (if any). If there
+ ** is anything in that buffer, return.
+ */
+
+ if (HASUB(fp))
+ {
+ FREEUB(fp);
+ if ((fp->f_r = fp->f_ur) != 0)
+ {
+ fp->f_p = fp->f_up;
+
+ /* revert blocking state */
+ return 0;
+ }
+ }
+ }
+
+ if (fp->f_bf.smb_base == NULL)
+ sm_makebuf(fp);
+
+ /*
+ ** Before reading from a line buffered or unbuffered file,
+ ** flush all line buffered output files, per the ANSI C standard.
+ */
+
+ if (fp->f_flags & (SMLBF|SMNBF))
+ (void) sm_fwalk(sm_lflush, &timeout);
+
+ /*
+ ** If this file is linked to another, and we are going to hang
+ ** on the read, flush the linked file before continuing.
+ */
+
+ if (fp->f_flushfp != NULL &&
+ (*fp->f_getinfo)(fp, SM_IO_IS_READABLE, NULL) <= 0)
+ sm_flush(fp->f_flushfp, &timeout);
+
+ fp->f_p = fp->f_bf.smb_base;
+
+ /*
+ ** The do-while loop stops trying to read when something is read
+ ** or it appears that the timeout has expired before finding
+ ** something available to be read (via select()).
+ */
+
+ ret = 0;
+ do
+ {
+ errno = 0; /* needed to ensure EOF correctly found */
+ r = (*fp->f_read)(fp, (char *)fp->f_p, fp->f_bf.smb_size);
+ if (r <= 0)
+ {
+ if (r == 0 && errno == 0)
+ break; /* EOF found */
+ if (IS_IO_ERROR(fd, r, timeout))
+ goto err; /* errno set */
+
+ /* read would block */
+ SM_IO_RD_TIMEOUT(fp, fd, &to, timeout, ret);
+ }
+ } while (r <= 0 && ret > 0);
+
+err:
+ if (r <= 0)
+ {
+ if (r == 0)
+ fp->f_flags |= SMFEOF;
+ else
+ fp->f_flags |= SMERR;
+ fp->f_r = 0;
+ return SM_IO_EOF;
+ }
+ fp->f_r = r;
+ return 0;
+}
+
+/*
+** SM_RGET -- refills buffer and returns first character
+**
+** Handle sm_getc() when the buffer ran out:
+** Refill, then return the first character in the newly-filled buffer.
+**
+** Parameters:
+** fp -- file pointer to work on
+** timeout -- time to complete refill
+**
+** Returns:
+** Success: first character in refilled buffer as an int
+** Failure: SM_IO_EOF
+*/
+
+int
+sm_rget(fp, timeout)
+ register SM_FILE_T *fp;
+ int timeout;
+{
+ if (sm_refill(fp, timeout) == 0)
+ {
+ fp->f_r--;
+ return *fp->f_p++;
+ }
+ return SM_IO_EOF;
+}
diff --git a/usr/src/cmd/sendmail/libsm/rewind.c b/usr/src/cmd/sendmail/libsm/rewind.c
new file mode 100644
index 0000000000..dc290affcf
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/rewind.c
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: rewind.c,v 1.16 2001/04/03 01:46:40 ca Exp $")
+#include <errno.h>
+#include <sm/io.h>
+#include <sm/assert.h>
+#include "local.h"
+
+/*
+** SM_IO_REWIND -- rewind the file
+**
+** Seeks the file to the begining and clears any outstanding errors.
+**
+** Parameters:
+** fp -- the flie pointer for rewind
+** timeout -- time to complete the rewind
+**
+** Returns:
+** none.
+*/
+
+void
+sm_io_rewind(fp, timeout)
+ register SM_FILE_T *fp;
+ int timeout;
+{
+ SM_REQUIRE_ISA(fp, SmFileMagic);
+ (void) sm_io_seek(fp, timeout, 0L, SM_IO_SEEK_SET);
+ (void) sm_io_clearerr(fp);
+ errno = 0; /* not required, but seems reasonable */
+}
diff --git a/usr/src/cmd/sendmail/libsm/rpool.c b/usr/src/cmd/sendmail/libsm/rpool.c
new file mode 100644
index 0000000000..2597052f27
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/rpool.c
@@ -0,0 +1,526 @@
+/*
+ * Copyright (c) 2000-2004 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: rpool.c,v 1.28 2004/08/03 20:44:04 ca Exp $")
+
+/*
+** resource pools
+** For documentation, see rpool.html
+*/
+
+#include <sm/exc.h>
+#include <sm/heap.h>
+#include <sm/rpool.h>
+#include <sm/varargs.h>
+#include <sm/conf.h>
+#if _FFR_PERF_RPOOL
+# include <syslog.h>
+#endif /* _FFR_PERF_RPOOL */
+
+const char SmRpoolMagic[] = "sm_rpool";
+
+typedef union
+{
+ SM_POOLLINK_T link;
+ char align[SM_ALIGN_SIZE];
+} SM_POOLHDR_T;
+
+static char *sm_rpool_allocblock_x __P((SM_RPOOL_T *, size_t));
+static char *sm_rpool_allocblock __P((SM_RPOOL_T *, size_t));
+
+/*
+** Tune this later
+*/
+
+#define POOLSIZE 4096
+#define BIG_OBJECT_RATIO 10
+
+/*
+** SM_RPOOL_ALLOCBLOCK_X -- allocate a new block for an rpool.
+**
+** Parameters:
+** rpool -- rpool to which the block should be added.
+** size -- size of block.
+**
+** Returns:
+** Pointer to block.
+**
+** Exceptions:
+** F:sm_heap -- out of memory
+*/
+
+static char *
+sm_rpool_allocblock_x(rpool, size)
+ SM_RPOOL_T *rpool;
+ size_t size;
+{
+ SM_POOLLINK_T *p;
+
+ p = sm_malloc_x(sizeof(SM_POOLHDR_T) + size);
+ p->sm_pnext = rpool->sm_pools;
+ rpool->sm_pools = p;
+ return (char*) p + sizeof(SM_POOLHDR_T);
+}
+
+/*
+** SM_RPOOL_ALLOCBLOCK -- allocate a new block for an rpool.
+**
+** Parameters:
+** rpool -- rpool to which the block should be added.
+** size -- size of block.
+**
+** Returns:
+** Pointer to block, NULL on failure.
+*/
+
+static char *
+sm_rpool_allocblock(rpool, size)
+ SM_RPOOL_T *rpool;
+ size_t size;
+{
+ SM_POOLLINK_T *p;
+
+ p = sm_malloc(sizeof(SM_POOLHDR_T) + size);
+ if (p == NULL)
+ return NULL;
+ p->sm_pnext = rpool->sm_pools;
+ rpool->sm_pools = p;
+ return (char*) p + sizeof(SM_POOLHDR_T);
+}
+
+/*
+** SM_RPOOL_MALLOC_TAGGED_X -- allocate memory from rpool
+**
+** Parameters:
+** rpool -- rpool from which memory should be allocated;
+** can be NULL, use sm_malloc() then.
+** size -- size of block.
+** file -- filename.
+** line -- line number in file.
+** group -- heap group for debugging.
+**
+** Returns:
+** Pointer to block.
+**
+** Exceptions:
+** F:sm_heap -- out of memory
+**
+** Notice: XXX
+** if size == 0 and the rpool is new (no memory
+** allocated yet) NULL is returned!
+** We could solve this by
+** - wasting 1 byte (size < avail)
+** - checking for rpool->sm_poolptr != NULL
+** - not asking for 0 sized buffer
+*/
+
+void *
+#if SM_HEAP_CHECK
+sm_rpool_malloc_tagged_x(rpool, size, file, line, group)
+ SM_RPOOL_T *rpool;
+ size_t size;
+ char *file;
+ int line;
+ int group;
+#else /* SM_HEAP_CHECK */
+sm_rpool_malloc_x(rpool, size)
+ SM_RPOOL_T *rpool;
+ size_t size;
+#endif /* SM_HEAP_CHECK */
+{
+ char *ptr;
+
+ if (rpool == NULL)
+ return sm_malloc_tagged_x(size, file, line, group);
+
+ /* Ensure that size is properly aligned. */
+ if (size & SM_ALIGN_BITS)
+ size = (size & ~SM_ALIGN_BITS) + SM_ALIGN_SIZE;
+
+ /* The common case. This is optimized for speed. */
+ if (size <= rpool->sm_poolavail)
+ {
+ ptr = rpool->sm_poolptr;
+ rpool->sm_poolptr += size;
+ rpool->sm_poolavail -= size;
+ return ptr;
+ }
+
+ /*
+ ** The slow case: we need to call malloc.
+ ** The SM_REQUIRE assertion is deferred until now, for speed.
+ ** That's okay: we set rpool->sm_poolavail to 0 when we free an rpool,
+ ** so the common case code won't be triggered on a dangling pointer.
+ */
+
+ SM_REQUIRE(rpool->sm_magic == SmRpoolMagic);
+
+ /*
+ ** If size > sm_poolsize, then malloc a new block especially for
+ ** this request. Future requests will be allocated from the
+ ** current pool.
+ **
+ ** What if the current pool is mostly unallocated, and the current
+ ** request is larger than the available space, but < sm_poolsize?
+ ** If we discard the current pool, and start allocating from a new
+ ** pool, then we will be wasting a lot of space. For this reason,
+ ** we malloc a block just for the current request if size >
+ ** sm_bigobjectsize, where sm_bigobjectsize <= sm_poolsize.
+ ** Thus, the most space that we will waste at the end of a pool
+ ** is sm_bigobjectsize - 1.
+ */
+
+ if (size > rpool->sm_bigobjectsize)
+ {
+#if _FFR_PERF_RPOOL
+ ++rpool->sm_nbigblocks;
+#endif /* _FFR_PERF_RPOOL */
+ return sm_rpool_allocblock_x(rpool, size);
+ }
+ SM_ASSERT(rpool->sm_bigobjectsize <= rpool->sm_poolsize);
+ ptr = sm_rpool_allocblock_x(rpool, rpool->sm_poolsize);
+ rpool->sm_poolptr = ptr + size;
+ rpool->sm_poolavail = rpool->sm_poolsize - size;
+#if _FFR_PERF_RPOOL
+ ++rpool->sm_npools;
+#endif /* _FFR_PERF_RPOOL */
+ return ptr;
+}
+
+/*
+** SM_RPOOL_MALLOC_TAGGED -- allocate memory from rpool
+**
+** Parameters:
+** rpool -- rpool from which memory should be allocated;
+** can be NULL, use sm_malloc() then.
+** size -- size of block.
+** file -- filename.
+** line -- line number in file.
+** group -- heap group for debugging.
+**
+** Returns:
+** Pointer to block, NULL on failure.
+**
+** Notice: XXX
+** if size == 0 and the rpool is new (no memory
+** allocated yet) NULL is returned!
+** We could solve this by
+** - wasting 1 byte (size < avail)
+** - checking for rpool->sm_poolptr != NULL
+** - not asking for 0 sized buffer
+*/
+
+void *
+#if SM_HEAP_CHECK
+sm_rpool_malloc_tagged(rpool, size, file, line, group)
+ SM_RPOOL_T *rpool;
+ size_t size;
+ char *file;
+ int line;
+ int group;
+#else /* SM_HEAP_CHECK */
+sm_rpool_malloc(rpool, size)
+ SM_RPOOL_T *rpool;
+ size_t size;
+#endif /* SM_HEAP_CHECK */
+{
+ char *ptr;
+
+ if (rpool == NULL)
+ return sm_malloc_tagged(size, file, line, group);
+
+ /* Ensure that size is properly aligned. */
+ if (size & SM_ALIGN_BITS)
+ size = (size & ~SM_ALIGN_BITS) + SM_ALIGN_SIZE;
+
+ /* The common case. This is optimized for speed. */
+ if (size <= rpool->sm_poolavail)
+ {
+ ptr = rpool->sm_poolptr;
+ rpool->sm_poolptr += size;
+ rpool->sm_poolavail -= size;
+ return ptr;
+ }
+
+ /*
+ ** The slow case: we need to call malloc.
+ ** The SM_REQUIRE assertion is deferred until now, for speed.
+ ** That's okay: we set rpool->sm_poolavail to 0 when we free an rpool,
+ ** so the common case code won't be triggered on a dangling pointer.
+ */
+
+ SM_REQUIRE(rpool->sm_magic == SmRpoolMagic);
+
+ /*
+ ** If size > sm_poolsize, then malloc a new block especially for
+ ** this request. Future requests will be allocated from the
+ ** current pool.
+ **
+ ** What if the current pool is mostly unallocated, and the current
+ ** request is larger than the available space, but < sm_poolsize?
+ ** If we discard the current pool, and start allocating from a new
+ ** pool, then we will be wasting a lot of space. For this reason,
+ ** we malloc a block just for the current request if size >
+ ** sm_bigobjectsize, where sm_bigobjectsize <= sm_poolsize.
+ ** Thus, the most space that we will waste at the end of a pool
+ ** is sm_bigobjectsize - 1.
+ */
+
+ if (size > rpool->sm_bigobjectsize)
+ {
+#if _FFR_PERF_RPOOL
+ ++rpool->sm_nbigblocks;
+#endif /* _FFR_PERF_RPOOL */
+ return sm_rpool_allocblock(rpool, size);
+ }
+ SM_ASSERT(rpool->sm_bigobjectsize <= rpool->sm_poolsize);
+ ptr = sm_rpool_allocblock(rpool, rpool->sm_poolsize);
+ if (ptr == NULL)
+ return NULL;
+ rpool->sm_poolptr = ptr + size;
+ rpool->sm_poolavail = rpool->sm_poolsize - size;
+#if _FFR_PERF_RPOOL
+ ++rpool->sm_npools;
+#endif /* _FFR_PERF_RPOOL */
+ return ptr;
+}
+
+/*
+** SM_RPOOL_NEW_X -- create a new rpool.
+**
+** Parameters:
+** parent -- pointer to parent rpool, can be NULL.
+**
+** Returns:
+** Pointer to new rpool.
+*/
+
+SM_RPOOL_T *
+sm_rpool_new_x(parent)
+ SM_RPOOL_T *parent;
+{
+ SM_RPOOL_T *rpool;
+
+ rpool = sm_malloc_x(sizeof(SM_RPOOL_T));
+ if (parent == NULL)
+ rpool->sm_parentlink = NULL;
+ else
+ {
+ SM_TRY
+ rpool->sm_parentlink = sm_rpool_attach_x(parent,
+ (SM_RPOOL_RFREE_T) sm_rpool_free,
+ (void *) rpool);
+ SM_EXCEPT(exc, "*")
+ sm_free(rpool);
+ sm_exc_raise_x(exc);
+ SM_END_TRY
+ }
+ rpool->sm_magic = SmRpoolMagic;
+
+ rpool->sm_poolsize = POOLSIZE - sizeof(SM_POOLHDR_T);
+ rpool->sm_bigobjectsize = rpool->sm_poolsize / BIG_OBJECT_RATIO;
+ rpool->sm_poolptr = NULL;
+ rpool->sm_poolavail = 0;
+ rpool->sm_pools = NULL;
+
+ rpool->sm_rptr = NULL;
+ rpool->sm_ravail = 0;
+ rpool->sm_rlists = NULL;
+#if _FFR_PERF_RPOOL
+ rpool->sm_nbigblocks = 0;
+ rpool->sm_npools = 0;
+#endif /* _FFR_PERF_RPOOL */
+
+ return rpool;
+}
+
+/*
+** SM_RPOOL_SETSIZES -- set sizes for rpool.
+**
+** Parameters:
+** poolsize -- size of a single rpool block.
+** bigobjectsize -- if this size is exceeded, an individual
+** block is allocated (must be less or equal poolsize).
+**
+** Returns:
+** none.
+*/
+
+void
+sm_rpool_setsizes(rpool, poolsize, bigobjectsize)
+ SM_RPOOL_T *rpool;
+ size_t poolsize;
+ size_t bigobjectsize;
+{
+ SM_REQUIRE(poolsize >= bigobjectsize);
+ if (poolsize == 0)
+ poolsize = POOLSIZE - sizeof(SM_POOLHDR_T);
+ if (bigobjectsize == 0)
+ bigobjectsize = poolsize / BIG_OBJECT_RATIO;
+ rpool->sm_poolsize = poolsize;
+ rpool->sm_bigobjectsize = bigobjectsize;
+}
+
+/*
+** SM_RPOOL_FREE -- free an rpool and release all of its resources.
+**
+** Parameters:
+** rpool -- rpool to free.
+**
+** Returns:
+** none.
+*/
+
+void
+sm_rpool_free(rpool)
+ SM_RPOOL_T *rpool;
+{
+ SM_RLIST_T *rl, *rnext;
+ SM_RESOURCE_T *r, *rmax;
+ SM_POOLLINK_T *pp, *pnext;
+
+ if (rpool == NULL)
+ return;
+
+ /*
+ ** It's important to free the resources before the memory pools,
+ ** because the resource free functions might modify the contents
+ ** of the memory pools.
+ */
+
+ rl = rpool->sm_rlists;
+ if (rl != NULL)
+ {
+ rmax = rpool->sm_rptr;
+ for (;;)
+ {
+ for (r = rl->sm_rvec; r < rmax; ++r)
+ {
+ if (r->sm_rfree != NULL)
+ r->sm_rfree(r->sm_rcontext);
+ }
+ rnext = rl->sm_rnext;
+ sm_free(rl);
+ if (rnext == NULL)
+ break;
+ rl = rnext;
+ rmax = &rl->sm_rvec[SM_RLIST_MAX];
+ }
+ }
+
+ /*
+ ** Now free the memory pools.
+ */
+
+ for (pp = rpool->sm_pools; pp != NULL; pp = pnext)
+ {
+ pnext = pp->sm_pnext;
+ sm_free(pp);
+ }
+
+ /*
+ ** Disconnect rpool from its parent.
+ */
+
+ if (rpool->sm_parentlink != NULL)
+ *rpool->sm_parentlink = NULL;
+
+ /*
+ ** Setting these fields to zero means that any future to attempt
+ ** to use the rpool after it is freed will cause an assertion failure.
+ */
+
+ rpool->sm_magic = NULL;
+ rpool->sm_poolavail = 0;
+ rpool->sm_ravail = 0;
+
+#if _FFR_PERF_RPOOL
+ if (rpool->sm_nbigblocks > 0 || rpool->sm_npools > 1)
+ syslog(LOG_NOTICE,
+ "perf: rpool=%lx, sm_nbigblocks=%d, sm_npools=%d",
+ (long) rpool, rpool->sm_nbigblocks, rpool->sm_npools);
+ rpool->sm_nbigblocks = 0;
+ rpool->sm_npools = 0;
+#endif /* _FFR_PERF_RPOOL */
+ sm_free(rpool);
+}
+
+/*
+** SM_RPOOL_ATTACH_X -- attach a resource to an rpool.
+**
+** Parameters:
+** rpool -- rpool to which resource should be attached.
+** rfree -- function to call when rpool is freed.
+** rcontext -- argument for function to call when rpool is freed.
+**
+** Returns:
+** Pointer to allocated function.
+**
+** Exceptions:
+** F:sm_heap -- out of memory
+*/
+
+SM_RPOOL_ATTACH_T
+sm_rpool_attach_x(rpool, rfree, rcontext)
+ SM_RPOOL_T *rpool;
+ SM_RPOOL_RFREE_T rfree;
+ void *rcontext;
+{
+ SM_RLIST_T *rl;
+ SM_RPOOL_ATTACH_T a;
+
+ SM_REQUIRE_ISA(rpool, SmRpoolMagic);
+
+ if (rpool->sm_ravail == 0)
+ {
+ rl = sm_malloc_x(sizeof(SM_RLIST_T));
+ rl->sm_rnext = rpool->sm_rlists;
+ rpool->sm_rlists = rl;
+ rpool->sm_rptr = rl->sm_rvec;
+ rpool->sm_ravail = SM_RLIST_MAX;
+ }
+
+ a = &rpool->sm_rptr->sm_rfree;
+ rpool->sm_rptr->sm_rfree = rfree;
+ rpool->sm_rptr->sm_rcontext = rcontext;
+ ++rpool->sm_rptr;
+ --rpool->sm_ravail;
+ return a;
+}
+
+#if DO_NOT_USE_STRCPY
+/*
+** SM_RPOOL_STRDUP_X -- Create a copy of a C string
+**
+** Parameters:
+** rpool -- rpool to use.
+** s -- the string to copy.
+**
+** Returns:
+** pointer to newly allocated string.
+*/
+
+char *
+sm_rpool_strdup_x(rpool, s)
+ SM_RPOOL_T *rpool;
+ const char *s;
+{
+ size_t l;
+ char *n;
+
+ l = strlen(s);
+ SM_ASSERT(l + 1 > l);
+ n = sm_rpool_malloc_x(rpool, l + 1);
+ sm_strlcpy(n, s, l + 1);
+ return n;
+}
+#endif /* DO_NOT_USE_STRCPY */
diff --git a/usr/src/cmd/sendmail/libsm/sem.c b/usr/src/cmd/sendmail/libsm/sem.c
new file mode 100644
index 0000000000..2d33427ead
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/sem.c
@@ -0,0 +1,203 @@
+/*
+ * Copyright (c) 2000-2001, 2005 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: sem.c,v 1.12 2005/03/25 21:27:02 ca Exp $")
+
+#if SM_CONF_SEM
+# include <stdlib.h>
+# include <unistd.h>
+# include <sm/sem.h>
+# include <sm/heap.h>
+
+/*
+** SM_SEM_START -- initialize semaphores
+**
+** Parameters:
+** key -- key for semaphores.
+** nsem -- number of semaphores.
+** semflg -- flag for semget(), if 0, use a default.
+** owner -- create semaphores.
+**
+** Returns:
+** id for semaphores.
+** < 0 on failure.
+*/
+
+int
+sm_sem_start(key, nsem, semflg, owner)
+ key_t key;
+ int nsem;
+ int semflg;
+ bool owner;
+{
+ int semid, i;
+ unsigned short *semvals;
+
+ semvals = NULL;
+ if (semflg == 0)
+ semflg = (SEM_A|SEM_R)|((SEM_A|SEM_R) >> 3);
+ if (owner)
+ semflg |= IPC_CREAT|IPC_EXCL;
+ semid = semget(key, nsem, semflg);
+ if (semid < 0)
+ goto error;
+
+ if (owner)
+ {
+ union semun semarg;
+
+ semvals = (unsigned short *) sm_malloc(nsem * sizeof semvals);
+ if (semvals == NULL)
+ goto error;
+ semarg.array = semvals;
+
+ /* initialize semaphore values to be available */
+ for (i = 0; i < nsem; i++)
+ semvals[i] = 1;
+ if (semctl(semid, 0, SETALL, semarg) < 0)
+ goto error;
+ }
+ return semid;
+
+error:
+ if (semvals != NULL)
+ sm_free(semvals);
+ if (semid >= 0)
+ sm_sem_stop(semid);
+ return -1;
+}
+
+/*
+** SM_SEM_STOP -- stop using semaphores.
+**
+** Parameters:
+** semid -- id for semaphores.
+**
+** Returns:
+** 0 on success.
+** < 0 on failure.
+*/
+
+int
+sm_sem_stop(semid)
+ int semid;
+{
+ return semctl(semid, 0, IPC_RMID, NULL);
+}
+
+/*
+** SM_SEM_ACQ -- acquire semaphore.
+**
+** Parameters:
+** semid -- id for semaphores.
+** semnum -- number of semaphore.
+** timeout -- how long to wait for operation to succeed.
+**
+** Returns:
+** 0 on success.
+** < 0 on failure.
+*/
+
+int
+sm_sem_acq(semid, semnum, timeout)
+ int semid;
+ int semnum;
+ int timeout;
+{
+ int r;
+ struct sembuf semops[1];
+
+ semops[0].sem_num = semnum;
+ semops[0].sem_op = -1;
+ semops[0].sem_flg = SEM_UNDO |
+ (timeout != SM_TIME_FOREVER ? 0 : IPC_NOWAIT);
+ if (timeout == SM_TIME_IMMEDIATE || timeout == SM_TIME_FOREVER)
+ return semop(semid, semops, 1);
+ do
+ {
+ r = semop(semid, semops, 1);
+ if (r == 0)
+ return r;
+ sleep(1);
+ --timeout;
+ } while (timeout > 0);
+ return r;
+}
+
+/*
+** SM_SEM_REL -- release semaphore.
+**
+** Parameters:
+** semid -- id for semaphores.
+** semnum -- number of semaphore.
+** timeout -- how long to wait for operation to succeed.
+**
+** Returns:
+** 0 on success.
+** < 0 on failure.
+*/
+
+int
+sm_sem_rel(semid, semnum, timeout)
+ int semid;
+ int semnum;
+ int timeout;
+{
+ int r;
+ struct sembuf semops[1];
+
+#if PARANOID
+ /* XXX should we check whether the value is already 0 ? */
+ SM_REQUIRE(sm_get_sem(semid, semnum) > 0);
+#endif /* PARANOID */
+
+ semops[0].sem_num = semnum;
+ semops[0].sem_op = 1;
+ semops[0].sem_flg = SEM_UNDO |
+ (timeout != SM_TIME_FOREVER ? 0 : IPC_NOWAIT);
+ if (timeout == SM_TIME_IMMEDIATE || timeout == SM_TIME_FOREVER)
+ return semop(semid, semops, 1);
+ do
+ {
+ r = semop(semid, semops, 1);
+ if (r == 0)
+ return r;
+ sleep(1);
+ --timeout;
+ } while (timeout > 0);
+ return r;
+}
+
+/*
+** SM_SEM_GET -- get semaphore value.
+**
+** Parameters:
+** semid -- id for semaphores.
+** semnum -- number of semaphore.
+**
+** Returns:
+** value of semaphore on success.
+** < 0 on failure.
+*/
+
+int
+sm_sem_get(semid, semnum)
+ int semid;
+ int semnum;
+{
+ int semval;
+
+ if ((semval = semctl(semid, semnum, GETVAL, NULL)) < 0)
+ return -1;
+ return semval;
+}
+#endif /* SM_CONF_SEM */
diff --git a/usr/src/cmd/sendmail/libsm/setvbuf.c b/usr/src/cmd/sendmail/libsm/setvbuf.c
new file mode 100644
index 0000000000..ccc5da99e2
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/setvbuf.c
@@ -0,0 +1,192 @@
+/*
+ * Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: setvbuf.c,v 1.30 2001/02/28 20:25:18 rodney Exp $")
+#include <stdlib.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sm/io.h>
+#include <sm/heap.h>
+#include <sm/assert.h>
+#include <sm/conf.h>
+#include "local.h"
+
+/*
+** SM_IO_SETVBUF -- set the buffering type for a file
+**
+** Set one of the different kinds of buffering, optionally including
+** a buffer.
+** If 'size' is == 0 then an "optimal" size will be selected.
+** If 'buf' is == NULL then space will be allocated at 'size'.
+**
+** Parameters:
+** fp -- the file that buffering is to be changed for
+** timeout -- time allowed for completing the function
+** buf -- buffer to use
+** mode -- buffering method to use
+** size -- size of 'buf'
+**
+** Returns:
+** Failure: SM_IO_EOF
+** Success: 0 (zero)
+*/
+
+int
+sm_io_setvbuf(fp, timeout, buf, mode, size)
+ SM_FILE_T *fp;
+ int timeout;
+ char *buf;
+ int mode;
+ size_t size;
+{
+ int ret, flags;
+ size_t iosize;
+ int ttyflag;
+ int fd;
+ struct timeval to;
+
+ SM_REQUIRE_ISA(fp, SmFileMagic);
+
+ /*
+ ** Verify arguments. The `int' limit on `size' is due to this
+ ** particular implementation. Note, buf and size are ignored
+ ** when setting SM_IO_NBF.
+ */
+
+ if (mode != SM_IO_NBF)
+ if ((mode != SM_IO_FBF && mode != SM_IO_LBF &&
+ mode != SM_IO_NOW) || (int) size < 0)
+ return SM_IO_EOF;
+
+ /*
+ ** Write current buffer, if any. Discard unread input (including
+ ** ungetc data), cancel line buffering, and free old buffer if
+ ** malloc()ed. We also clear any eof condition, as if this were
+ ** a seek.
+ */
+
+ ret = 0;
+ SM_CONVERT_TIME(fp, fd, timeout, &to);
+ (void) sm_flush(fp, &timeout);
+ if (HASUB(fp))
+ FREEUB(fp);
+ fp->f_r = fp->f_lbfsize = 0;
+ flags = fp->f_flags;
+ if (flags & SMMBF)
+ {
+ sm_free((void *) fp->f_bf.smb_base);
+ fp->f_bf.smb_base = NULL;
+ }
+ flags &= ~(SMLBF | SMNBF | SMMBF | SMOPT | SMNPT | SMFEOF | SMNOW |
+ SMFBF);
+
+ /* If setting unbuffered mode, skip all the hard work. */
+ if (mode == SM_IO_NBF)
+ goto nbf;
+
+ /*
+ ** Find optimal I/O size for seek optimization. This also returns
+ ** a `tty flag' to suggest that we check isatty(fd), but we do not
+ ** care since our caller told us how to buffer.
+ */
+
+ flags |= sm_whatbuf(fp, &iosize, &ttyflag);
+ if (size == 0)
+ {
+ buf = NULL; /* force local allocation */
+ size = iosize;
+ }
+
+ /* Allocate buffer if needed. */
+ if (buf == NULL)
+ {
+ if ((buf = sm_malloc(size)) == NULL)
+ {
+ /*
+ ** Unable to honor user's request. We will return
+ ** failure, but try again with file system size.
+ */
+
+ ret = SM_IO_EOF;
+ if (size != iosize)
+ {
+ size = iosize;
+ buf = sm_malloc(size);
+ }
+ }
+ if (buf == NULL)
+ {
+ /* No luck; switch to unbuffered I/O. */
+nbf:
+ fp->f_flags = flags | SMNBF;
+ fp->f_w = 0;
+ fp->f_bf.smb_base = fp->f_p = fp->f_nbuf;
+ fp->f_bf.smb_size = 1;
+ return ret;
+ }
+ flags |= SMMBF;
+ }
+
+ /*
+ ** Kill any seek optimization if the buffer is not the
+ ** right size.
+ **
+ ** SHOULD WE ALLOW MULTIPLES HERE (i.e., ok iff (size % iosize) == 0)?
+ */
+
+ if (size != iosize)
+ flags |= SMNPT;
+
+ /*
+ ** Fix up the SM_FILE_T fields, and set sm_cleanup for output flush on
+ ** exit (since we are buffered in some way).
+ */
+
+ if (mode == SM_IO_LBF)
+ flags |= SMLBF;
+ else if (mode == SM_IO_NOW)
+ flags |= SMNOW;
+ else if (mode == SM_IO_FBF)
+ flags |= SMFBF;
+ fp->f_flags = flags;
+ fp->f_bf.smb_base = fp->f_p = (unsigned char *)buf;
+ fp->f_bf.smb_size = size;
+ /* fp->f_lbfsize is still 0 */
+ if (flags & SMWR)
+ {
+ /*
+ ** Begin or continue writing: see sm_wsetup(). Note
+ ** that SMNBF is impossible (it was handled earlier).
+ */
+
+ if (flags & SMLBF)
+ {
+ fp->f_w = 0;
+ fp->f_lbfsize = -fp->f_bf.smb_size;
+ }
+ else
+ fp->f_w = size;
+ }
+ else
+ {
+ /* begin/continue reading, or stay in intermediate state */
+ fp->f_w = 0;
+ }
+
+ atexit(sm_cleanup);
+ return ret;
+}
diff --git a/usr/src/cmd/sendmail/libsm/shm.c b/usr/src/cmd/sendmail/libsm/shm.c
new file mode 100644
index 0000000000..3da9c96dd1
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/shm.c
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2000-2001, 2003, 2005 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: shm.c,v 1.18 2005/02/09 01:54:51 ca Exp $")
+
+#if SM_CONF_SHM
+# include <stdlib.h>
+# include <unistd.h>
+# include <errno.h>
+# include <sm/shm.h>
+
+
+/*
+** SM_SHMSTART -- initialize shared memory segment.
+**
+** Parameters:
+** key -- key for shared memory.
+** size -- size of segment.
+** shmflag -- initial flags.
+** shmid -- pointer to return id.
+** owner -- create segment.
+**
+** Returns:
+** pointer to shared memory segment,
+** NULL on failure.
+**
+** Side Effects:
+** attaches shared memory segment.
+*/
+
+void *
+sm_shmstart(key, size, shmflg, shmid, owner)
+ key_t key;
+ int size;
+ int shmflg;
+ int *shmid;
+ bool owner;
+{
+ int save_errno;
+ void *shm = SM_SHM_NULL;
+
+ /* default: user/group accessible */
+ if (shmflg == 0)
+ shmflg = SHM_R|SHM_W|(SHM_R>>3)|(SHM_W>>3);
+ if (owner)
+ shmflg |= IPC_CREAT|IPC_EXCL;
+ *shmid = shmget(key, size, shmflg);
+ if (*shmid < 0)
+ goto error;
+
+ shm = shmat(*shmid, (void *) 0, 0);
+ if (shm == SM_SHM_NULL)
+ goto error;
+
+ return shm;
+
+ error:
+ save_errno = errno;
+ if (shm != SM_SHM_NULL || *shmid >= 0)
+ sm_shmstop(shm, *shmid, owner);
+ *shmid = SM_SHM_NO_ID;
+ errno = save_errno;
+ return (void *) 0;
+}
+
+
+/*
+** SM_SHMSTOP -- stop using shared memory segment.
+**
+** Parameters:
+** shm -- pointer to shared memory.
+** shmid -- id.
+** owner -- delete segment.
+**
+** Returns:
+** 0 on success.
+** < 0 on failure.
+**
+** Side Effects:
+** detaches (and maybe removes) shared memory segment.
+*/
+
+
+int
+sm_shmstop(shm, shmid, owner)
+ void *shm;
+ int shmid;
+ bool owner;
+{
+ int r;
+
+ if (shm != SM_SHM_NULL && (r = shmdt(shm)) < 0)
+ return r;
+ if (owner && shmid >= 0 && (r = shmctl(shmid, IPC_RMID, NULL)) < 0)
+ return r;
+ return 0;
+}
+
+
+/*
+** SM_SHMSETOWNER -- set owner/group/mode of shared memory segment.
+**
+** Parameters:
+** shmid -- id.
+** uid -- uid to use
+** gid -- gid to use
+** mode -- mode to use
+**
+** Returns:
+** 0 on success.
+** < 0 on failure.
+*/
+
+int
+sm_shmsetowner(shmid, uid, gid, mode)
+ int shmid;
+ uid_t uid;
+ gid_t gid;
+ mode_t mode;
+{
+ int r;
+ struct shmid_ds shmid_ds;
+
+ memset(&shmid_ds, 0, sizeof(shmid_ds));
+ if ((r = shmctl(shmid, IPC_STAT, &shmid_ds)) < 0)
+ return r;
+ shmid_ds.shm_perm.uid = uid;
+ shmid_ds.shm_perm.gid = gid;
+ shmid_ds.shm_perm.mode = mode;
+ if ((r = shmctl(shmid, IPC_SET, &shmid_ds)) < 0)
+ return r;
+ return 0;
+}
+#endif /* SM_CONF_SHM */
diff --git a/usr/src/cmd/sendmail/libsm/signal.c b/usr/src/cmd/sendmail/libsm/signal.c
new file mode 100644
index 0000000000..c5807e7f1f
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/signal.c
@@ -0,0 +1,342 @@
+/*
+ * Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: signal.c,v 1.13 2001/08/14 16:05:47 ca Exp $")
+
+#if SM_CONF_SETITIMER
+# include <sys/time.h>
+#endif /* SM_CONF_SETITIMER */
+#include <errno.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+#include <sm/clock.h>
+#include <sm/signal.h>
+#include <signal.h>
+#include <sm/string.h>
+
+unsigned int volatile InCriticalSection; /* >0 if inside critical section */
+int volatile PendingSignal; /* pending signal to resend */
+
+ /*
+** SM_SIGNAL -- set a signal handler
+**
+** This is essentially old BSD "signal(3)".
+**
+** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+** DOING.
+*/
+
+sigfunc_t
+sm_signal(sig, handler)
+ int sig;
+ sigfunc_t handler;
+{
+# if defined(SA_RESTART) || (!defined(SYS5SIGNALS) && !defined(BSD4_3))
+ struct sigaction n, o;
+# endif /* defined(SA_RESTART) || (!defined(SYS5SIGNALS) && !defined(BSD4_3)) */
+
+ /*
+ ** First, try for modern signal calls
+ ** and restartable syscalls
+ */
+
+# ifdef SA_RESTART
+ (void) memset(&n, '\0', sizeof n);
+# if USE_SA_SIGACTION
+ n.sa_sigaction = (void(*)(int, siginfo_t *, void *)) handler;
+ n.sa_flags = SA_RESTART|SA_SIGINFO;
+# else /* USE_SA_SIGACTION */
+ n.sa_handler = handler;
+ n.sa_flags = SA_RESTART;
+# endif /* USE_SA_SIGACTION */
+ if (sigaction(sig, &n, &o) < 0)
+ return SIG_ERR;
+ return o.sa_handler;
+# else /* SA_RESTART */
+
+ /*
+ ** Else check for SYS5SIGNALS or
+ ** BSD4_3 signals
+ */
+
+# if defined(SYS5SIGNALS) || defined(BSD4_3)
+# ifdef BSD4_3
+ return signal(sig, handler);
+# else /* BSD4_3 */
+ return sigset(sig, handler);
+# endif /* BSD4_3 */
+# else /* defined(SYS5SIGNALS) || defined(BSD4_3) */
+
+ /*
+ ** Finally, if nothing else is available,
+ ** go for a default
+ */
+
+ (void) memset(&n, '\0', sizeof n);
+ n.sa_handler = handler;
+ if (sigaction(sig, &n, &o) < 0)
+ return SIG_ERR;
+ return o.sa_handler;
+# endif /* defined(SYS5SIGNALS) || defined(BSD4_3) */
+# endif /* SA_RESTART */
+}
+ /*
+** SM_BLOCKSIGNAL -- hold a signal to prevent delivery
+**
+** Parameters:
+** sig -- the signal to block.
+**
+** Returns:
+** 1 signal was previously blocked
+** 0 signal was not previously blocked
+** -1 on failure.
+*/
+
+int
+sm_blocksignal(sig)
+ int sig;
+{
+# ifdef BSD4_3
+# ifndef sigmask
+# define sigmask(s) (1 << ((s) - 1))
+# endif /* ! sigmask */
+ return (sigblock(sigmask(sig)) & sigmask(sig)) != 0;
+# else /* BSD4_3 */
+# ifdef ALTOS_SYSTEM_V
+ sigfunc_t handler;
+
+ handler = sigset(sig, SIG_HOLD);
+ if (handler == SIG_ERR)
+ return -1;
+ else
+ return handler == SIG_HOLD;
+# else /* ALTOS_SYSTEM_V */
+ sigset_t sset, oset;
+
+ (void) sigemptyset(&sset);
+ (void) sigaddset(&sset, sig);
+ if (sigprocmask(SIG_BLOCK, &sset, &oset) < 0)
+ return -1;
+ else
+ return sigismember(&oset, sig);
+# endif /* ALTOS_SYSTEM_V */
+# endif /* BSD4_3 */
+}
+ /*
+** SM_RELEASESIGNAL -- release a held signal
+**
+** Parameters:
+** sig -- the signal to release.
+**
+** Returns:
+** 1 signal was previously blocked
+** 0 signal was not previously blocked
+** -1 on failure.
+*/
+
+int
+sm_releasesignal(sig)
+ int sig;
+{
+# ifdef BSD4_3
+ return (sigsetmask(sigblock(0) & ~sigmask(sig)) & sigmask(sig)) != 0;
+# else /* BSD4_3 */
+# ifdef ALTOS_SYSTEM_V
+ sigfunc_t handler;
+
+ handler = sigset(sig, SIG_HOLD);
+ if (sigrelse(sig) < 0)
+ return -1;
+ else
+ return handler == SIG_HOLD;
+# else /* ALTOS_SYSTEM_V */
+ sigset_t sset, oset;
+
+ (void) sigemptyset(&sset);
+ (void) sigaddset(&sset, sig);
+ if (sigprocmask(SIG_UNBLOCK, &sset, &oset) < 0)
+ return -1;
+ else
+ return sigismember(&oset, sig);
+# endif /* ALTOS_SYSTEM_V */
+# endif /* BSD4_3 */
+}
+ /*
+** PEND_SIGNAL -- Add a signal to the pending signal list
+**
+** Parameters:
+** sig -- signal to add
+**
+** Returns:
+** none.
+**
+** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+** DOING.
+*/
+
+void
+pend_signal(sig)
+ int sig;
+{
+ int sigbit;
+ int save_errno = errno;
+#if SM_CONF_SETITIMER
+ struct itimerval clr;
+#endif /* SM_CONF_SETITIMER */
+
+ /*
+ ** Don't want to interrupt something critical, hence delay
+ ** the alarm for one second. Hopefully, by then we
+ ** will be out of the critical section. If not, then
+ ** we will just delay again. The events to be run will
+ ** still all be run, maybe just a little bit late.
+ */
+
+ switch (sig)
+ {
+ case SIGHUP:
+ sigbit = PEND_SIGHUP;
+ break;
+
+ case SIGINT:
+ sigbit = PEND_SIGINT;
+ break;
+
+ case SIGTERM:
+ sigbit = PEND_SIGTERM;
+ break;
+
+ case SIGUSR1:
+ sigbit = PEND_SIGUSR1;
+ break;
+
+ case SIGALRM:
+ /* don't have to pend these */
+ sigbit = 0;
+ break;
+
+ default:
+ /* If we get here, we are in trouble */
+ abort();
+
+ /* NOTREACHED */
+ /* shut up stupid compiler warning on HP-UX 11 */
+ sigbit = 0;
+ break;
+ }
+
+ if (sigbit != 0)
+ PendingSignal |= sigbit;
+ (void) sm_signal(SIGALRM, sm_tick);
+#if SM_CONF_SETITIMER
+ clr.it_interval.tv_sec = 0;
+ clr.it_interval.tv_usec = 0;
+ clr.it_value.tv_sec = 1;
+ clr.it_value.tv_usec = 0;
+ (void) setitimer(ITIMER_REAL, &clr, NULL);
+#else /* SM_CONF_SETITIMER */
+ (void) alarm(1);
+#endif /* SM_CONF_SETITIMER */
+ errno = save_errno;
+}
+ /*
+** SM_ALLSIGNALS -- act on all signals
+**
+** Parameters:
+** block -- whether to block or release all signals.
+**
+** Returns:
+** none.
+*/
+
+void
+sm_allsignals(block)
+ bool block;
+{
+# ifdef BSD4_3
+# ifndef sigmask
+# define sigmask(s) (1 << ((s) - 1))
+# endif /* ! sigmask */
+ if (block)
+ {
+ int mask = 0;
+
+ mask |= sigmask(SIGALRM);
+ mask |= sigmask(SIGCHLD);
+ mask |= sigmask(SIGHUP);
+ mask |= sigmask(SIGINT);
+ mask |= sigmask(SIGTERM);
+ mask |= sigmask(SIGUSR1);
+
+ (void) sigblock(mask);
+ }
+ else
+ sigsetmask(0);
+# else /* BSD4_3 */
+# ifdef ALTOS_SYSTEM_V
+ if (block)
+ {
+ (void) sigset(SIGALRM, SIG_HOLD);
+ (void) sigset(SIGCHLD, SIG_HOLD);
+ (void) sigset(SIGHUP, SIG_HOLD);
+ (void) sigset(SIGINT, SIG_HOLD);
+ (void) sigset(SIGTERM, SIG_HOLD);
+ (void) sigset(SIGUSR1, SIG_HOLD);
+ }
+ else
+ {
+ (void) sigset(SIGALRM, SIG_DFL);
+ (void) sigset(SIGCHLD, SIG_DFL);
+ (void) sigset(SIGHUP, SIG_DFL);
+ (void) sigset(SIGINT, SIG_DFL);
+ (void) sigset(SIGTERM, SIG_DFL);
+ (void) sigset(SIGUSR1, SIG_DFL);
+ }
+# else /* ALTOS_SYSTEM_V */
+ sigset_t sset;
+
+ (void) sigemptyset(&sset);
+ (void) sigaddset(&sset, SIGALRM);
+ (void) sigaddset(&sset, SIGCHLD);
+ (void) sigaddset(&sset, SIGHUP);
+ (void) sigaddset(&sset, SIGINT);
+ (void) sigaddset(&sset, SIGTERM);
+ (void) sigaddset(&sset, SIGUSR1);
+ (void) sigprocmask(block ? SIG_BLOCK : SIG_UNBLOCK, &sset, NULL);
+# endif /* ALTOS_SYSTEM_V */
+# endif /* BSD4_3 */
+}
+ /*
+** SM_SIGNAL_NOOP -- A signal no-op function
+**
+** Parameters:
+** sig -- signal received
+**
+** Returns:
+** SIGFUNC_RETURN
+*/
+
+/* ARGSUSED */
+SIGFUNC_DECL
+sm_signal_noop(sig)
+ int sig;
+{
+ int save_errno = errno;
+
+ FIX_SYSV_SIGNAL(sig, sm_signal_noop);
+ errno = save_errno;
+ return SIGFUNC_RETURN;
+}
+
diff --git a/usr/src/cmd/sendmail/libsm/sm_os.h b/usr/src/cmd/sendmail/libsm/sm_os.h
new file mode 100644
index 0000000000..4c28daf335
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/sm_os.h
@@ -0,0 +1,8 @@
+/*
+ * Copyright (c) 2001 by Sun Microsystems, Inc.
+ * All rights reserved.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include "../include/sm/os/sm_os_sunos.h"
diff --git a/usr/src/cmd/sendmail/libsm/smstdio.c b/usr/src/cmd/sendmail/libsm/smstdio.c
new file mode 100644
index 0000000000..b99c5efb68
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/smstdio.c
@@ -0,0 +1,365 @@
+/*
+ * Copyright (c) 2000-2002, 2004 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_IDSTR(id, "@(#)$Id: smstdio.c,v 1.34 2004/08/03 20:46:34 ca Exp $")
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <sm/assert.h>
+#include <sm/io.h>
+#include <sm/string.h>
+#include "local.h"
+
+static void setup __P((SM_FILE_T *));
+
+/*
+** Overall:
+** This is a file type which implements a layer on top of the system
+** stdio. fp->f_cookie is the FILE* of stdio. The cookie may be
+** "bound late" because of the manner which Linux implements stdio.
+** When binding late (when fp->f_cookie==NULL) then the value of
+** fp->f_ival is used (0, 1 or 2) to map to stdio's stdin, stdout or
+** stderr.
+*/
+
+/*
+** SM_STDIOOPEN -- open a file to system stdio implementation
+**
+** Parameters:
+** fp -- file pointer assign for this open
+** info -- info about file to open
+** flags -- indicating method of opening
+** rpool -- ignored
+**
+** Returns:
+** Failure: -1
+** Success: 0 (zero)
+*/
+
+/* ARGSUSED3 */
+int
+sm_stdioopen(fp, info, flags, rpool)
+ SM_FILE_T *fp;
+ const void *info;
+ int flags;
+ const void *rpool;
+{
+ register FILE *s;
+ char *stdiomode;
+
+ switch (flags)
+ {
+ case SM_IO_RDONLY:
+ stdiomode = "r";
+ break;
+ case SM_IO_WRONLY:
+ stdiomode = "w";
+ break;
+ case SM_IO_APPEND:
+ stdiomode = "a";
+ break;
+ case SM_IO_APPENDRW:
+ stdiomode = "a+";
+ break;
+#if SM_IO_BINARY != 0
+ case SM_IO_RDONLY_B:
+ stdiomode = "rb";
+ break;
+ case SM_IO_WRONLY_B:
+ stdiomode = "wb";
+ break;
+ case SM_IO_APPEND_B:
+ stdiomode = "ab";
+ break;
+ case SM_IO_APPENDRW_B:
+ stdiomode = "a+b";
+ break;
+ case SM_IO_RDWR_B:
+ stdiomode = "r+b";
+ break;
+#endif /* SM_IO_BINARY != 0 */
+ case SM_IO_RDWR:
+ default:
+ stdiomode = "r+";
+ break;
+ }
+
+ if ((s = fopen((char *)info, stdiomode)) == NULL)
+ return -1;
+ fp->f_cookie = s;
+ return 0;
+}
+
+/*
+** SETUP -- assign file type cookie when not already assigned
+**
+** Parameters:
+** fp - the file pointer to get the cookie assigned
+**
+** Return:
+** none.
+*/
+
+static void
+setup(fp)
+ SM_FILE_T *fp;
+{
+ if (fp->f_cookie == NULL)
+ {
+ switch (fp->f_ival)
+ {
+ case 0:
+ fp->f_cookie = stdin;
+ break;
+ case 1:
+ fp->f_cookie = stdout;
+ break;
+ case 2:
+ fp->f_cookie = stderr;
+ break;
+ default:
+ sm_abort("fp->f_ival=%d: out of range (0...2)", fp->f_ival);
+ break;
+ }
+ }
+}
+
+/*
+** SM_STDIOREAD -- read from the file
+**
+** Parameters:
+** fp -- the file pointer
+** buf -- location to place the read data
+** n - number of bytes to read
+**
+** Returns:
+** result from fread().
+*/
+
+ssize_t
+sm_stdioread(fp, buf, n)
+ SM_FILE_T *fp;
+ char *buf;
+ size_t n;
+{
+ register FILE *s;
+
+ if (fp->f_cookie == NULL)
+ setup(fp);
+ s = fp->f_cookie;
+ return fread(buf, 1, n, s);
+}
+
+/*
+** SM_STDIOWRITE -- write to the file
+**
+** Parameters:
+** fp -- the file pointer
+** buf -- location of data to write
+** n - number of bytes to write
+**
+** Returns:
+** result from fwrite().
+*/
+
+ssize_t
+sm_stdiowrite(fp, buf, n)
+ SM_FILE_T *fp;
+ char const *buf;
+ size_t n;
+{
+ register FILE *s;
+
+ if (fp->f_cookie == NULL)
+ setup(fp);
+ s = fp->f_cookie;
+ return fwrite(buf, 1, n, s);
+}
+
+/*
+** SM_STDIOSEEK -- set position within file
+**
+** Parameters:
+** fp -- the file pointer
+** offset -- new location based on 'whence'
+** whence -- indicates "base" for 'offset'
+**
+** Returns:
+** result from fseek().
+*/
+
+off_t
+sm_stdioseek(fp, offset, whence)
+ SM_FILE_T *fp;
+ off_t offset;
+ int whence;
+{
+ register FILE *s;
+
+ if (fp->f_cookie == NULL)
+ setup(fp);
+ s = fp->f_cookie;
+ return fseek(s, offset, whence);
+}
+
+/*
+** SM_STDIOCLOSE -- close the file
+**
+** Parameters:
+** fp -- close file pointer
+**
+** Return:
+** status from fclose()
+*/
+
+int
+sm_stdioclose(fp)
+ SM_FILE_T *fp;
+{
+ register FILE *s;
+
+ if (fp->f_cookie == NULL)
+ setup(fp);
+ s = fp->f_cookie;
+ return fclose(s);
+}
+
+/*
+** SM_STDIOSETINFO -- set info for this open file
+**
+** Parameters:
+** fp -- the file pointer
+** what -- type of information setting
+** valp -- memory location of info to set
+**
+** Return:
+** Failure: -1 and sets errno
+** Success: none (currently).
+*/
+
+/* ARGSUSED2 */
+int
+sm_stdiosetinfo(fp, what, valp)
+ SM_FILE_T *fp;
+ int what;
+ void *valp;
+{
+ switch (what)
+ {
+ case SM_IO_WHAT_MODE:
+ default:
+ errno = EINVAL;
+ return -1;
+ }
+}
+
+/*
+** SM_STDIOGETINFO -- get info for this open file
+**
+** Parameters:
+** fp -- the file pointer
+** what -- type of information request
+** valp -- memory location to place info
+**
+** Return:
+** Failure: -1 and sets errno
+** Success: none (currently).
+*/
+
+/* ARGSUSED2 */
+int
+sm_stdiogetinfo(fp, what, valp)
+ SM_FILE_T *fp;
+ int what;
+ void *valp;
+{
+ switch (what)
+ {
+ case SM_IO_WHAT_SIZE:
+ {
+ int fd;
+ struct stat st;
+
+ if (fp->f_cookie == NULL)
+ setup(fp);
+ fd = fileno((FILE *) fp->f_cookie);
+ if (fd < 0)
+ return -1;
+ if (fstat(fd, &st) == 0)
+ return st.st_size;
+ else
+ return -1;
+ }
+
+ case SM_IO_WHAT_MODE:
+ default:
+ errno = EINVAL;
+ return -1;
+ }
+}
+
+/*
+** SM_IO_STDIOOPEN -- create an SM_FILE which interfaces to a stdio FILE
+**
+** Parameters:
+** stream -- an open stdio stream, as returned by fopen()
+** mode -- the mode argument to fopen() which describes stream
+**
+** Return:
+** On success, return a pointer to an SM_FILE object which
+** can be used for reading and writing 'stream'.
+** Abort if mode is gibberish or stream is bad.
+** Raise an exception if we can't allocate memory.
+*/
+
+SM_FILE_T *
+sm_io_stdioopen(stream, mode)
+ FILE *stream;
+ char *mode;
+{
+ int fd;
+ bool r, w;
+ int ioflags;
+ SM_FILE_T *fp;
+
+ fd = fileno(stream);
+ SM_REQUIRE(fd >= 0);
+
+ r = w = false;
+ switch (mode[0])
+ {
+ case 'r':
+ r = true;
+ break;
+ case 'w':
+ case 'a':
+ w = true;
+ break;
+ default:
+ sm_abort("sm_io_stdioopen: mode '%s' is bad", mode);
+ }
+ if (strchr(&mode[1], '+') != NULL)
+ r = w = true;
+ if (r && w)
+ ioflags = SMRW;
+ else if (r)
+ ioflags = SMRD;
+ else
+ ioflags = SMWR;
+
+ fp = sm_fp(SmFtRealStdio, ioflags, NULL);
+ fp->f_file = fd;
+ fp->f_cookie = stream;
+ return fp;
+}
diff --git a/usr/src/cmd/sendmail/libsm/snprintf.c b/usr/src/cmd/sendmail/libsm/snprintf.c
new file mode 100644
index 0000000000..c31f8cf227
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/snprintf.c
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: snprintf.c,v 1.21 2001/03/02 23:53:41 ca Exp $")
+#include <limits.h>
+#include <sm/varargs.h>
+#include <sm/io.h>
+#include "local.h"
+
+/*
+** SM_SNPRINTF -- format a string to a memory location of restricted size
+**
+** Parameters:
+** str -- memory location to place formatted string
+** n -- size of buffer pointed to by str, capped to
+** a maximum of INT_MAX
+** fmt -- the formatting directives
+** ... -- the data to satisfy the formatting
+**
+** Returns:
+** Failure: -1
+** Success: number of bytes that would have been written
+** to str, not including the trailing '\0',
+** up to a maximum of INT_MAX, as if there was
+** no buffer size limitation. If the result >= n
+** then the output was truncated.
+**
+** Side Effects:
+** If n > 0, then between 0 and n-1 bytes of formatted output
+** are written into 'str', followed by a '\0'.
+*/
+
+int
+#if SM_VA_STD
+sm_snprintf(char *str, size_t n, char const *fmt, ...)
+#else /* SM_VA_STD */
+sm_snprintf(str, n, fmt, va_alist)
+ char *str;
+ size_t n;
+ char *fmt;
+ va_dcl
+#endif /* SM_VA_STD */
+{
+ int ret;
+ SM_VA_LOCAL_DECL
+ SM_FILE_T fake;
+
+ /* While snprintf(3) specifies size_t stdio uses an int internally */
+ if (n > INT_MAX)
+ n = INT_MAX;
+ SM_VA_START(ap, fmt);
+
+ /* XXX put this into a static? */
+ fake.sm_magic = SmFileMagic;
+ fake.f_file = -1;
+ fake.f_flags = SMWR | SMSTR;
+ fake.f_cookie = &fake;
+ fake.f_bf.smb_base = fake.f_p = (unsigned char *)str;
+ fake.f_bf.smb_size = fake.f_w = n ? n - 1 : 0;
+ fake.f_timeout = SM_TIME_FOREVER;
+ fake.f_timeoutstate = SM_TIME_BLOCK;
+ fake.f_close = NULL;
+ fake.f_open = NULL;
+ fake.f_read = NULL;
+ fake.f_write = NULL;
+ fake.f_seek = NULL;
+ fake.f_setinfo = fake.f_getinfo = NULL;
+ fake.f_type = "sm_snprintf:fake";
+ ret = sm_io_vfprintf(&fake, SM_TIME_FOREVER, fmt, ap);
+ if (n > 0)
+ *fake.f_p = '\0';
+ SM_VA_END(ap);
+ return ret;
+}
diff --git a/usr/src/cmd/sendmail/libsm/sscanf.c b/usr/src/cmd/sendmail/libsm/sscanf.c
new file mode 100644
index 0000000000..9be1e58869
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/sscanf.c
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: sscanf.c,v 1.25 2002/02/01 02:28:00 ca Exp $")
+#include <string.h>
+#include <sm/varargs.h>
+#include <sm/io.h>
+#include "local.h"
+
+/*
+** SM_EOFREAD -- dummy read function for faked file below
+**
+** Parameters:
+** fp -- file pointer
+** buf -- location to place read data
+** len -- number of bytes to read
+**
+** Returns:
+** 0 (zero) always
+*/
+
+static ssize_t
+sm_eofread __P((
+ SM_FILE_T *fp,
+ char *buf,
+ size_t len));
+
+/* ARGSUSED0 */
+static ssize_t
+sm_eofread(fp, buf, len)
+ SM_FILE_T *fp;
+ char *buf;
+ size_t len;
+{
+ return 0;
+}
+
+/*
+** SM_IO_SSCANF -- scan a string to find data units
+**
+** Parameters:
+** str -- strings containing data
+** fmt -- format directive for finding data units
+** ... -- memory locations to place format found data units
+**
+** Returns:
+** Failure: SM_IO_EOF
+** Success: number of data units found
+**
+** Side Effects:
+** Attempts to strlen() 'str'; if not a '\0' terminated string
+** then the call may SEGV/fail.
+** Faking the string 'str' as a file.
+*/
+
+int
+#if SM_VA_STD
+sm_io_sscanf(const char *str, char const *fmt, ...)
+#else /* SM_VA_STD */
+sm_io_sscanf(str, fmt, va_alist)
+ const char *str;
+ char *fmt;
+ va_dcl
+#endif /* SM_VA_STD */
+{
+ int ret;
+ SM_FILE_T fake;
+ SM_VA_LOCAL_DECL
+
+ fake.sm_magic = SmFileMagic;
+ fake.f_flags = SMRD;
+ fake.f_bf.smb_base = fake.f_p = (unsigned char *) str;
+ fake.f_bf.smb_size = fake.f_r = strlen(str);
+ fake.f_file = -1;
+ fake.f_read = sm_eofread;
+ fake.f_write = NULL;
+ fake.f_close = NULL;
+ fake.f_open = NULL;
+ fake.f_seek = NULL;
+ fake.f_setinfo = fake.f_getinfo = NULL;
+ fake.f_type = "sm_io_sscanf:fake";
+ fake.f_flushfp = NULL;
+ fake.f_ub.smb_base = NULL;
+ fake.f_timeout = SM_TIME_FOREVER;
+ fake.f_timeoutstate = SM_TIME_BLOCK;
+ SM_VA_START(ap, fmt);
+ ret = sm_vfscanf(&fake, SM_TIME_FOREVER, fmt, ap);
+ SM_VA_END(ap);
+ return ret;
+}
diff --git a/usr/src/cmd/sendmail/libsm/stdio.c b/usr/src/cmd/sendmail/libsm/stdio.c
new file mode 100644
index 0000000000..84d538733f
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/stdio.c
@@ -0,0 +1,521 @@
+/*
+ * Copyright (c) 2000-2004 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: stdio.c,v 1.69 2004/08/03 20:46:34 ca Exp $")
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h> /* FreeBSD: FD_ZERO needs <string.h> */
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sm/heap.h>
+#include <sm/assert.h>
+#include <sm/varargs.h>
+#include <sm/io.h>
+#include <sm/setjmp.h>
+#include <sm/conf.h>
+#include <sm/fdset.h>
+#include "local.h"
+
+static int sm_stdsetmode __P((SM_FILE_T *, const int *));
+static int sm_stdgetmode __P((SM_FILE_T *, int *));
+
+/*
+** Overall:
+** Small standard I/O/seek/close functions.
+** These maintain the `known seek offset' for seek optimization.
+*/
+
+/*
+** SM_STDOPEN -- open a file with stdio behavior
+**
+** Not associated with the system's stdio in libc.
+**
+** Parameters:
+** fp -- file pointer to be associated with the open
+** info -- pathname of the file to be opened
+** flags -- indicates type of access methods
+** rpool -- ignored
+**
+** Returns:
+** Failure: -1 and set errno
+** Success: 0 or greater (fd of file from open(2)).
+**
+*/
+
+/* ARGSUSED3 */
+int
+sm_stdopen(fp, info, flags, rpool)
+ SM_FILE_T *fp;
+ const void *info;
+ int flags;
+ const void *rpool;
+{
+ char *path = (char *) info;
+ int oflags;
+
+ switch (SM_IO_MODE(flags))
+ {
+ case SM_IO_RDWR:
+ oflags = O_RDWR;
+ break;
+ case SM_IO_RDWRTR:
+ oflags = O_RDWR | O_CREAT | O_TRUNC;
+ break;
+ case SM_IO_RDONLY:
+ oflags = O_RDONLY;
+ break;
+ case SM_IO_WRONLY:
+ oflags = O_WRONLY | O_CREAT | O_TRUNC;
+ break;
+ case SM_IO_APPEND:
+ oflags = O_APPEND | O_WRONLY | O_CREAT;
+ break;
+ case SM_IO_APPENDRW:
+ oflags = O_APPEND | O_RDWR | O_CREAT;
+ break;
+ default:
+ errno = EINVAL;
+ return -1;
+ }
+#ifdef O_BINARY
+ if (SM_IS_BINARY(flags))
+ oflags |= O_BINARY;
+#endif /* O_BINARY */
+ fp->f_file = open(path, oflags,
+ S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
+ if (fp->f_file < 0)
+ return -1; /* errno set by open() */
+
+ if (oflags & O_APPEND)
+ (void) (*fp->f_seek)((void *)fp, (off_t)0, SEEK_END);
+
+ return fp->f_file;
+}
+
+/*
+** SM_STDREAD -- read from the file
+**
+** Parameters:
+** fp -- file pointer to read from
+** buf -- location to place read data
+** n -- number of bytes to read
+**
+** Returns:
+** Failure: -1 and sets errno
+** Success: number of bytes read
+**
+** Side Effects:
+** Updates internal offset into file.
+*/
+
+ssize_t
+sm_stdread(fp, buf, n)
+ SM_FILE_T *fp;
+ char *buf;
+ size_t n;
+{
+ register int ret;
+
+ ret = read(fp->f_file, buf, n);
+
+ /* if the read succeeded, update the current offset */
+ if (ret > 0)
+ fp->f_lseekoff += ret;
+ return ret;
+}
+
+/*
+** SM_STDWRITE -- write to the file
+**
+** Parameters:
+** fp -- file pointer ro write to
+** buf -- location of data to be written
+** n - number of bytes to write
+**
+** Returns:
+** Failure: -1 and sets errno
+** Success: number of bytes written
+*/
+
+ssize_t
+sm_stdwrite(fp, buf, n)
+ SM_FILE_T *fp;
+ char const *buf;
+ size_t n;
+{
+ return write(fp->f_file, buf, n);
+}
+
+/*
+** SM_STDSEEK -- set the file offset position
+**
+** Parmeters:
+** fp -- file pointer to position
+** offset -- how far to position from "base" (set by 'whence')
+** whence -- indicates where the "base" of the 'offset' to start
+**
+** Results:
+** Failure: -1 and sets errno
+** Success: the current offset
+**
+** Side Effects:
+** Updates the internal value of the offset.
+*/
+
+off_t
+sm_stdseek(fp, offset, whence)
+ SM_FILE_T *fp;
+ off_t offset;
+ int whence;
+{
+ register off_t ret;
+
+ ret = lseek(fp->f_file, (off_t) offset, whence);
+ if (ret != (off_t) -1)
+ fp->f_lseekoff = ret;
+ return ret;
+}
+
+/*
+** SM_STDCLOSE -- close the file
+**
+** Parameters:
+** fp -- the file pointer to close
+**
+** Returns:
+** Success: 0 (zero)
+** Failure: -1 and sets errno
+*/
+
+int
+sm_stdclose(fp)
+ SM_FILE_T *fp;
+{
+ return close(fp->f_file);
+}
+
+/*
+** SM_STDSETMODE -- set the access mode for the file
+**
+** Called by sm_stdsetinfo().
+**
+** Parameters:
+** fp -- file pointer
+** mode -- new mode to set the file access to
+**
+** Results:
+** Success: 0 (zero);
+** Failure: -1 and sets errno
+*/
+
+int
+sm_stdsetmode(fp, mode)
+ SM_FILE_T *fp;
+ const int *mode;
+{
+ int flags = 0;
+
+ switch (SM_IO_MODE(*mode))
+ {
+ case SM_IO_RDWR:
+ flags |= SMRW;
+ break;
+ case SM_IO_RDONLY:
+ flags |= SMRD;
+ break;
+ case SM_IO_WRONLY:
+ flags |= SMWR;
+ break;
+ case SM_IO_APPEND:
+ default:
+ errno = EINVAL;
+ return -1;
+ }
+ fp->f_flags = fp->f_flags & ~SMMODEMASK;
+ fp->f_flags |= flags;
+ return 0;
+}
+
+/*
+** SM_STDGETMODE -- for getinfo determine open mode
+**
+** Called by sm_stdgetinfo().
+**
+** Parameters:
+** fp -- the file mode being determined
+** mode -- internal mode to map to external value
+**
+** Results:
+** Failure: -1 and sets errno
+** Success: external mode value
+*/
+
+static int
+sm_stdgetmode(fp, mode)
+ SM_FILE_T *fp;
+ int *mode;
+{
+ switch (fp->f_flags & SMMODEMASK)
+ {
+ case SMRW:
+ *mode = SM_IO_RDWR;
+ break;
+ case SMRD:
+ *mode = SM_IO_RDONLY;
+ break;
+ case SMWR:
+ *mode = SM_IO_WRONLY;
+ break;
+ default:
+ errno = EINVAL;
+ return -1;
+ }
+ return 0;
+}
+
+/*
+** SM_STDSETINFO -- set/modify information for a file
+**
+** Parameters:
+** fp -- file to set info for
+** what -- type of info to set
+** valp -- location of data used for setting
+**
+** Returns:
+** Failure: -1 and sets errno
+** Success: >=0
+*/
+
+int
+sm_stdsetinfo(fp, what, valp)
+ SM_FILE_T *fp;
+ int what;
+ void *valp;
+{
+ switch (what)
+ {
+ case SM_IO_WHAT_MODE:
+ return sm_stdsetmode(fp, (const int *)valp);
+
+ default:
+ errno = EINVAL;
+ return -1;
+ }
+}
+
+/*
+** SM_GETINFO -- get information about the open file
+**
+** Parameters:
+** fp -- file to get info for
+** what -- type of info to get
+** valp -- location to place found info
+**
+** Returns:
+** Success: may or may not place info in 'valp' depending
+** on 'what' value, and returns values >=0. Return
+** value may be the obtained info
+** Failure: -1 and sets errno
+*/
+
+int
+sm_stdgetinfo(fp, what, valp)
+ SM_FILE_T *fp;
+ int what;
+ void *valp;
+{
+ switch (what)
+ {
+ case SM_IO_WHAT_MODE:
+ return sm_stdgetmode(fp, (int *)valp);
+
+ case SM_IO_WHAT_FD:
+ return fp->f_file;
+
+ case SM_IO_WHAT_SIZE:
+ {
+ struct stat st;
+
+ if (fstat(fp->f_file, &st) == 0)
+ return st.st_size;
+ else
+ return -1;
+ }
+
+ case SM_IO_IS_READABLE:
+ {
+ fd_set readfds;
+ struct timeval timeout;
+
+ if (SM_FD_SETSIZE > 0 && fp->f_file >= SM_FD_SETSIZE)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+ FD_ZERO(&readfds);
+ SM_FD_SET(fp->f_file, &readfds);
+ timeout.tv_sec = 0;
+ timeout.tv_usec = 0;
+ if (select(fp->f_file + 1, FDSET_CAST &readfds,
+ NULL, NULL, &timeout) > 0 &&
+ SM_FD_ISSET(fp->f_file, &readfds))
+ return 1;
+ return 0;
+ }
+
+ default:
+ errno = EINVAL;
+ return -1;
+ }
+}
+
+/*
+** SM_STDFDOPEN -- open file by primitive 'fd' rather than pathname
+**
+** I/O function to handle fdopen() stdio equivalence. The rest of
+** the functions are the same as the sm_stdopen() above.
+**
+** Parameters:
+** fp -- the file pointer to be associated with the open
+** name -- the primitive file descriptor for association
+** flags -- indicates type of access methods
+** rpool -- ignored
+**
+** Results:
+** Success: primitive file descriptor value
+** Failure: -1 and sets errno
+*/
+
+/* ARGSUSED3 */
+int
+sm_stdfdopen(fp, info, flags, rpool)
+ SM_FILE_T *fp;
+ const void *info;
+ int flags;
+ const void *rpool;
+{
+ int oflags, tmp, fdflags, fd = *((int *) info);
+
+ switch (SM_IO_MODE(flags))
+ {
+ case SM_IO_RDWR:
+ oflags = O_RDWR | O_CREAT;
+ break;
+ case SM_IO_RDONLY:
+ oflags = O_RDONLY;
+ break;
+ case SM_IO_WRONLY:
+ oflags = O_WRONLY | O_CREAT | O_TRUNC;
+ break;
+ case SM_IO_APPEND:
+ oflags = O_APPEND | O_WRONLY | O_CREAT;
+ break;
+ case SM_IO_APPENDRW:
+ oflags = O_APPEND | O_RDWR | O_CREAT;
+ break;
+ default:
+ errno = EINVAL;
+ return -1;
+ }
+#ifdef O_BINARY
+ if (SM_IS_BINARY(flags))
+ oflags |= O_BINARY;
+#endif /* O_BINARY */
+
+ /* Make sure the mode the user wants is a subset of the actual mode. */
+ if ((fdflags = fcntl(fd, F_GETFL, 0)) < 0)
+ return -1;
+ tmp = fdflags & O_ACCMODE;
+ if (tmp != O_RDWR && (tmp != (oflags & O_ACCMODE)))
+ {
+ errno = EINVAL;
+ return -1;
+ }
+ fp->f_file = fd;
+ if (oflags & O_APPEND)
+ (void) (*fp->f_seek)(fp, (off_t)0, SEEK_END);
+ return fp->f_file;
+}
+
+/*
+** SM_IO_FOPEN -- open a file
+**
+** Same interface and semantics as the open() system call,
+** except that it returns SM_FILE_T* instead of a file descriptor.
+**
+** Parameters:
+** pathname -- path of file to open
+** flags -- flags controlling the open
+** ... -- option "mode" for opening the file
+**
+** Returns:
+** Raises an exception on heap exhaustion.
+** Returns NULL and sets errno if open() fails.
+** Returns an SM_FILE_T pointer on success.
+*/
+
+SM_FILE_T *
+#if SM_VA_STD
+sm_io_fopen(char *pathname, int flags, ...)
+#else /* SM_VA_STD */
+sm_io_fopen(pathname, flags, va_alist)
+ char *pathname;
+ int flags;
+ va_dcl
+#endif /* SM_VA_STD */
+{
+ MODE_T mode;
+ SM_FILE_T *fp;
+ int ioflags;
+
+ if (flags & O_CREAT)
+ {
+ SM_VA_LOCAL_DECL
+
+ SM_VA_START(ap, flags);
+ mode = (MODE_T) SM_VA_ARG(ap, int);
+ SM_VA_END(ap);
+ }
+ else
+ mode = 0;
+
+ switch (flags & O_ACCMODE)
+ {
+ case O_RDONLY:
+ ioflags = SMRD;
+ break;
+ case O_WRONLY:
+ ioflags = SMWR;
+ break;
+ case O_RDWR:
+ ioflags = SMRW;
+ break;
+ default:
+ sm_abort("sm_io_fopen: bad flags 0%o", flags);
+ }
+
+ fp = sm_fp(SmFtStdio, ioflags, NULL);
+ fp->f_file = open(pathname, flags, mode);
+ if (fp->f_file == -1)
+ {
+ fp->f_flags = 0;
+ fp->sm_magic = NULL;
+ return NULL;
+ }
+ return fp;
+}
diff --git a/usr/src/cmd/sendmail/libsm/strcasecmp.c b/usr/src/cmd/sendmail/libsm/strcasecmp.c
new file mode 100644
index 0000000000..e2a7b798e8
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/strcasecmp.c
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1987, 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: strcasecmp.c,v 1.12 2001/08/27 22:09:15 gshapiro Exp $")
+
+#include <sm/config.h>
+#include <sm/string.h>
+#include <string.h>
+
+ /*
+** SM_STRCASECMP -- 8-bit clean version of strcasecmp
+**
+** Thank you, vendors, for making this all necessary.
+*/
+
+/*
+** This array is designed for mapping upper and lower case letter
+** together for a case independent comparison. The mappings are
+** based upon ascii character sequences.
+*/
+
+const unsigned char charmap[] =
+{
+ 0000, 0001, 0002, 0003, 0004, 0005, 0006, 0007,
+ 0010, 0011, 0012, 0013, 0014, 0015, 0016, 0017,
+ 0020, 0021, 0022, 0023, 0024, 0025, 0026, 0027,
+ 0030, 0031, 0032, 0033, 0034, 0035, 0036, 0037,
+ 0040, 0041, 0042, 0043, 0044, 0045, 0046, 0047,
+ 0050, 0051, 0052, 0053, 0054, 0055, 0056, 0057,
+ 0060, 0061, 0062, 0063, 0064, 0065, 0066, 0067,
+ 0070, 0071, 0072, 0073, 0074, 0075, 0076, 0077,
+ 0100, 0141, 0142, 0143, 0144, 0145, 0146, 0147,
+ 0150, 0151, 0152, 0153, 0154, 0155, 0156, 0157,
+ 0160, 0161, 0162, 0163, 0164, 0165, 0166, 0167,
+ 0170, 0171, 0172, 0133, 0134, 0135, 0136, 0137,
+ 0140, 0141, 0142, 0143, 0144, 0145, 0146, 0147,
+ 0150, 0151, 0152, 0153, 0154, 0155, 0156, 0157,
+ 0160, 0161, 0162, 0163, 0164, 0165, 0166, 0167,
+ 0170, 0171, 0172, 0173, 0174, 0175, 0176, 0177,
+ 0200, 0201, 0202, 0203, 0204, 0205, 0206, 0207,
+ 0210, 0211, 0212, 0213, 0214, 0215, 0216, 0217,
+ 0220, 0221, 0222, 0223, 0224, 0225, 0226, 0227,
+ 0230, 0231, 0232, 0233, 0234, 0235, 0236, 0237,
+ 0240, 0241, 0242, 0243, 0244, 0245, 0246, 0247,
+ 0250, 0251, 0252, 0253, 0254, 0255, 0256, 0257,
+ 0260, 0261, 0262, 0263, 0264, 0265, 0266, 0267,
+ 0270, 0271, 0272, 0273, 0274, 0275, 0276, 0277,
+ 0300, 0301, 0302, 0303, 0304, 0305, 0306, 0307,
+ 0310, 0311, 0312, 0313, 0314, 0315, 0316, 0317,
+ 0320, 0321, 0322, 0323, 0324, 0325, 0326, 0327,
+ 0330, 0331, 0332, 0333, 0334, 0335, 0336, 0337,
+ 0340, 0341, 0342, 0343, 0344, 0345, 0346, 0347,
+ 0350, 0351, 0352, 0353, 0354, 0355, 0356, 0357,
+ 0360, 0361, 0362, 0363, 0364, 0365, 0366, 0367,
+ 0370, 0371, 0372, 0373, 0374, 0375, 0376, 0377,
+};
+
+int
+sm_strcasecmp(s1, s2)
+ const char *s1, *s2;
+{
+ const unsigned char *us1 = (const unsigned char *)s1;
+ const unsigned char *us2 = (const unsigned char *)s2;
+
+ while (charmap[*us1] == charmap[*us2])
+ {
+ if (*us1 == '\0')
+ return 0;
+ ++us1;
+ ++us2;
+ }
+ return charmap[*us1] - charmap[*us2];
+}
+
+int
+sm_strncasecmp(s1, s2, n)
+ const char *s1, *s2;
+ register size_t n;
+{
+ if (n != 0)
+ {
+ register const unsigned char *cm = charmap;
+ register const unsigned char *us1 = (const unsigned char *)s1;
+ register const unsigned char *us2 = (const unsigned char *)s2;
+
+ do
+ {
+ if (cm[*us1] != cm[*us2++])
+ return (cm[*us1] - cm[*--us2]);
+ if (*us1++ == '\0')
+ break;
+ } while (--n != 0);
+ }
+ return 0;
+}
diff --git a/usr/src/cmd/sendmail/libsm/strdup.c b/usr/src/cmd/sendmail/libsm/strdup.c
new file mode 100644
index 0000000000..13425ddc70
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/strdup.c
@@ -0,0 +1,168 @@
+/*
+ * Copyright (c) 2000-2001, 2003 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: strdup.c,v 1.15 2003/10/10 17:56:57 ca Exp $")
+
+#include <sm/heap.h>
+#include <sm/string.h>
+
+/*
+** SM_STRNDUP_X -- Duplicate a string of a given length
+**
+** Allocates memory and copies source string (of given length) into it.
+**
+** Parameters:
+** s -- string to copy.
+** n -- length to copy.
+**
+** Returns:
+** copy of string, raises exception if out of memory.
+**
+** Side Effects:
+** allocate memory for new string.
+*/
+
+char *
+sm_strndup_x(s, n)
+ const char *s;
+ size_t n;
+{
+ char *d = sm_malloc_x(n + 1);
+
+ (void) memcpy(d, s, n);
+ d[n] = '\0';
+ return d;
+}
+
+/*
+** SM_STRDUP -- Duplicate a string
+**
+** Allocates memory and copies source string into it.
+**
+** Parameters:
+** s -- string to copy.
+**
+** Returns:
+** copy of string, NULL if out of memory.
+**
+** Side Effects:
+** allocate memory for new string.
+*/
+
+char *
+sm_strdup(s)
+ char *s;
+{
+ size_t l;
+ char *d;
+
+ l = strlen(s) + 1;
+ d = sm_malloc_tagged(l, "sm_strdup", 0, sm_heap_group());
+ if (d != NULL)
+ (void) sm_strlcpy(d, s, l);
+ return d;
+}
+
+#if DO_NOT_USE_STRCPY
+
+/*
+** SM_STRDUP_X -- Duplicate a string
+**
+** Allocates memory and copies source string into it.
+**
+** Parameters:
+** s -- string to copy.
+**
+** Returns:
+** copy of string, exception if out of memory.
+**
+** Side Effects:
+** allocate memory for new string.
+*/
+
+char *
+sm_strdup_x(s)
+ const char *s;
+{
+ size_t l;
+ char *d;
+
+ l = strlen(s) + 1;
+ d = sm_malloc_tagged_x(l, "sm_strdup_x", 0, sm_heap_group());
+ (void) sm_strlcpy(d, s, l);
+ return d;
+}
+
+/*
+** SM_PSTRDUP_X -- Duplicate a string (using "permanent" memory)
+**
+** Allocates memory and copies source string into it.
+**
+** Parameters:
+** s -- string to copy.
+**
+** Returns:
+** copy of string, exception if out of memory.
+**
+** Side Effects:
+** allocate memory for new string.
+*/
+
+char *
+sm_pstrdup_x(s)
+ const char *s;
+{
+ size_t l;
+ char *d;
+
+ l = strlen(s) + 1;
+ d = sm_pmalloc_x(l);
+ (void) sm_strlcpy(d, s, l);
+ return d;
+}
+
+/*
+** SM_STRDUP_X -- Duplicate a string
+**
+** Allocates memory and copies source string into it.
+**
+** Parameters:
+** s -- string to copy.
+** file -- name of source file
+** line -- line in source file
+** group -- heap group
+**
+** Returns:
+** copy of string, exception if out of memory.
+**
+** Side Effects:
+** allocate memory for new string.
+*/
+
+char *
+sm_strdup_tagged_x(s, file, line, group)
+ const char *s;
+ char *file;
+ int line, group;
+{
+ size_t l;
+ char *d;
+
+ l = strlen(s) + 1;
+ d = sm_malloc_tagged_x(l, file, line, group);
+ (void) sm_strlcpy(d, s, l);
+ return d;
+}
+
+#endif /* DO_NOT_USE_STRCPY */
+
diff --git a/usr/src/cmd/sendmail/libsm/strerror.c b/usr/src/cmd/sendmail/libsm/strerror.c
new file mode 100644
index 0000000000..f58f6626b2
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/strerror.c
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: strerror.c,v 1.21 2001/06/17 21:31:41 ca Exp $")
+
+/*
+** define strerror for platforms that lack it.
+*/
+
+#include <errno.h>
+#include <stdio.h> /* sys_errlist, on some platforms */
+
+#include <sm/io.h> /* sm_snprintf */
+#include <sm/string.h>
+#include <sm/conf.h>
+#include <sm/errstring.h>
+
+#if !defined(ERRLIST_PREDEFINED)
+extern char *sys_errlist[];
+extern int sys_nerr;
+#endif /* !defined(ERRLIST_PREDEFINED) */
+
+#if !HASSTRERROR
+
+/*
+** STRERROR -- return error message string corresponding to an error number.
+**
+** Parameters:
+** err -- error number.
+**
+** Returns:
+** Error string (might be pointer to static buffer).
+*/
+
+char *
+strerror(err)
+ int err;
+{
+ static char buf[64];
+
+ if (err >= 0 && err < sys_nerr)
+ return (char *) sys_errlist[err];
+ else
+ {
+ (void) sm_snprintf(buf, sizeof(buf), "Error %d", err);
+ return buf;
+ }
+}
+#endif /* !HASSTRERROR */
diff --git a/usr/src/cmd/sendmail/libsm/strexit.c b/usr/src/cmd/sendmail/libsm/strexit.c
new file mode 100644
index 0000000000..4c2b5be0ef
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/strexit.c
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: strexit.c,v 1.3 2001/01/15 18:39:11 ca Exp $")
+#include <sm/string.h>
+#include <sm/sysexits.h>
+
+/*
+** SM_STREXIT -- convert EX_* value from <sm/sysexits.h> to a character string
+**
+** This function is analogous to strerror(), except that it
+** operates on EX_* values from <sm/sysexits.h>.
+**
+** Parameters:
+** ex -- EX_* value
+**
+** Results:
+** pointer to a static message string
+*/
+
+char *
+sm_strexit(ex)
+ int ex;
+{
+ char *msg;
+ static char buf[64];
+
+ msg = sm_sysexitmsg(ex);
+ if (msg == NULL)
+ {
+ (void) sm_snprintf(buf, sizeof buf, "Unknown exit status %d",
+ ex);
+ msg = buf;
+ }
+ return msg;
+}
+
+/*
+** SM_SYSEXITMSG -- convert an EX_* value to a character string, or NULL
+**
+** Parameters:
+** ex -- EX_* value
+**
+** Results:
+** If ex is a known exit value, then a pointer to a static
+** message string is returned. Otherwise NULL is returned.
+*/
+
+char *
+sm_sysexitmsg(ex)
+ int ex;
+{
+ char *msg;
+
+ msg = sm_sysexmsg(ex);
+ if (msg != NULL)
+ return &msg[11];
+ else
+ return msg;
+}
+
+/*
+** SM_SYSEXMSG -- convert an EX_* value to a character string, or NULL
+**
+** Parameters:
+** ex -- EX_* value
+**
+** Results:
+** If ex is a known exit value, then a pointer to a static
+** string is returned. Otherwise NULL is returned.
+** The string contains the following fixed width fields:
+** [0] ':' if there is an errno value associated with this
+** exit value, otherwise ' '.
+** [1,3] 3 digit SMTP error code
+** [4] ' '
+** [5,9] 3 digit SMTP extended error code
+** [10] ' '
+** [11,] message string
+*/
+
+char *
+sm_sysexmsg(ex)
+ int ex;
+{
+ switch (ex)
+ {
+ case EX_USAGE:
+ return " 500 5.0.0 Command line usage error";
+ case EX_DATAERR:
+ return " 501 5.6.0 Data format error";
+ case EX_NOINPUT:
+ return ":550 5.3.0 Cannot open input";
+ case EX_NOUSER:
+ return " 550 5.1.1 User unknown";
+ case EX_NOHOST:
+ return " 550 5.1.2 Host unknown";
+ case EX_UNAVAILABLE:
+ return " 554 5.0.0 Service unavailable";
+ case EX_SOFTWARE:
+ return ":554 5.3.0 Internal error";
+ case EX_OSERR:
+ return ":451 4.0.0 Operating system error";
+ case EX_OSFILE:
+ return ":554 5.3.5 System file missing";
+ case EX_CANTCREAT:
+ return ":550 5.0.0 Can't create output";
+ case EX_IOERR:
+ return ":451 4.0.0 I/O error";
+ case EX_TEMPFAIL:
+ return " 450 4.0.0 Deferred";
+ case EX_PROTOCOL:
+ return " 554 5.5.0 Remote protocol error";
+ case EX_NOPERM:
+ return ":550 5.0.0 Insufficient permission";
+ case EX_CONFIG:
+ return " 554 5.3.5 Local configuration error";
+ default:
+ return NULL;
+ }
+}
diff --git a/usr/src/cmd/sendmail/libsm/string.c b/usr/src/cmd/sendmail/libsm/string.c
new file mode 100644
index 0000000000..9e7715b89c
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/string.c
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: string.c,v 1.1 2001/02/15 21:04:50 ca Exp $")
+
+#include <ctype.h>
+#include <errno.h>
+
+#include <sm/string.h>
+
+/*
+** STRIPQUOTES -- Strip quotes & quote bits from a string.
+**
+** Runs through a string and strips off unquoted quote
+** characters and quote bits. This is done in place.
+**
+** Parameters:
+** s -- the string to strip.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** none.
+*/
+
+void
+stripquotes(s)
+ char *s;
+{
+ register char *p;
+ register char *q;
+ register char c;
+
+ if (s == NULL)
+ return;
+
+ p = q = s;
+ do
+ {
+ c = *p++;
+ if (c == '\\')
+ c = *p++;
+ else if (c == '"')
+ continue;
+ *q++ = c;
+ } while (c != '\0');
+}
diff --git a/usr/src/cmd/sendmail/libsm/stringf.c b/usr/src/cmd/sendmail/libsm/stringf.c
new file mode 100644
index 0000000000..b839f2cef1
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/stringf.c
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: stringf.c,v 1.13 2001/03/03 03:40:43 ca Exp $")
+#include <errno.h>
+#include <stdio.h>
+#include <sm/exc.h>
+#include <sm/heap.h>
+#include <sm/string.h>
+#include <sm/varargs.h>
+
+/*
+** SM_STRINGF_X -- printf() to dynamically allocated string.
+**
+** Takes the same arguments as printf.
+** It returns a pointer to a dynamically allocated string
+** containing the text that printf would print to standard output.
+** It raises an exception on error.
+** The name comes from a PWB Unix function called stringf.
+**
+** Parameters:
+** fmt -- format string.
+** ... -- arguments for format.
+**
+** Returns:
+** Pointer to a dynamically allocated string.
+**
+** Exceptions:
+** F:sm_heap -- out of memory (via sm_vstringf_x()).
+*/
+
+char *
+#if SM_VA_STD
+sm_stringf_x(const char *fmt, ...)
+#else /* SM_VA_STD */
+sm_stringf_x(fmt, va_alist)
+ const char *fmt;
+ va_dcl
+#endif /* SM_VA_STD */
+{
+ SM_VA_LOCAL_DECL
+ char *s;
+
+ SM_VA_START(ap, fmt);
+ s = sm_vstringf_x(fmt, ap);
+ SM_VA_END(ap);
+ return s;
+}
+
+/*
+** SM_VSTRINGF_X -- printf() to dynamically allocated string.
+**
+** Parameters:
+** fmt -- format string.
+** ap -- arguments for format.
+**
+** Returns:
+** Pointer to a dynamically allocated string.
+**
+** Exceptions:
+** F:sm_heap -- out of memory
+*/
+
+char *
+sm_vstringf_x(fmt, ap)
+ const char *fmt;
+ SM_VA_LOCAL_DECL
+{
+ char *s;
+
+ sm_vasprintf(&s, fmt, ap);
+ if (s == NULL)
+ {
+ if (errno == ENOMEM)
+ sm_exc_raise_x(&SmHeapOutOfMemory);
+ sm_exc_raisenew_x(&SmEtypeOs, errno, "sm_vasprintf", NULL);
+ }
+ return s;
+}
diff --git a/usr/src/cmd/sendmail/libsm/strio.c b/usr/src/cmd/sendmail/libsm/strio.c
new file mode 100644
index 0000000000..7948f12613
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/strio.c
@@ -0,0 +1,492 @@
+/*
+ * Copyright (c) 2000-2002, 2004 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_IDSTR(id, "@(#)$Id: strio.c,v 1.43 2004/08/03 20:48:30 ca Exp $")
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <errno.h>
+#include <sm/rpool.h>
+#include <sm/io.h>
+#include <sm/heap.h>
+#include <sm/conf.h>
+#include "local.h"
+
+static int sm_strsetmode __P((SM_FILE_T*, const int *));
+static int sm_strgetmode __P((SM_FILE_T*, int *));
+
+/*
+** Cookie structure for the "strio" file type
+*/
+
+struct sm_str_obj
+{
+ char *strio_base;
+ char *strio_end;
+ size_t strio_size;
+ size_t strio_offset;
+ int strio_flags;
+ const void *strio_rpool;
+};
+
+typedef struct sm_str_obj SM_STR_OBJ_T;
+
+/*
+** SM_STRGROW -- increase storage space for string
+**
+** Parameters:
+** s -- current cookie
+** size -- new storage size request
+**
+** Returns:
+** true iff successful.
+*/
+
+static bool sm_strgrow __P((SM_STR_OBJ_T *, size_t));
+
+static bool
+sm_strgrow(s, size)
+ SM_STR_OBJ_T *s;
+ size_t size;
+{
+ register void *p;
+
+ if (s->strio_size >= size)
+ return true;
+ p = sm_realloc(s->strio_base, size);
+ if (p == NULL)
+ return false;
+ s->strio_base = p;
+ s->strio_end = s->strio_base + size;
+ s->strio_size = size;
+ return true;
+}
+
+/*
+** SM_STRREAD -- read a portion of the string
+**
+** Parameters:
+** fp -- the file pointer
+** buf -- location to place read data
+** n -- number of bytes to read
+**
+** Returns:
+** Failure: -1 and sets errno
+** Success: >=0, number of bytes read
+*/
+
+ssize_t
+sm_strread(fp, buf, n)
+ SM_FILE_T *fp;
+ char *buf;
+ size_t n;
+{
+ register SM_STR_OBJ_T *s = fp->f_cookie;
+ int len;
+
+ if (!(s->strio_flags & SMRD) && !(s->strio_flags & SMRW))
+ {
+ errno = EBADF;
+ return -1;
+ }
+ len = SM_MIN(s->strio_size - s->strio_offset, n);
+ (void) memmove(buf, s->strio_base + s->strio_offset, len);
+ s->strio_offset += len;
+ return len;
+}
+
+/*
+** SM_STRWRITE -- write a portion of the string
+**
+** Parameters:
+** fp -- the file pointer
+** buf -- location of data for writing
+** n -- number of bytes to write
+**
+** Returns:
+** Failure: -1 and sets errno
+** Success: >=0, number of bytes written
+*/
+
+ssize_t
+sm_strwrite(fp, buf, n)
+ SM_FILE_T *fp;
+ char const *buf;
+ size_t n;
+{
+ register SM_STR_OBJ_T *s = fp->f_cookie;
+
+ if (!(s->strio_flags & SMWR) && !(s->strio_flags & SMRW))
+ {
+ errno = EBADF;
+ return -1;
+ }
+ if (n + s->strio_offset > s->strio_size)
+ {
+ if (!sm_strgrow(s, n + s->strio_offset))
+ return 0;
+ }
+ (void) memmove(s->strio_base + s->strio_offset, buf, n);
+ s->strio_offset += n;
+ return n;
+}
+
+/*
+** SM_STRSEEK -- position the offset pointer for the string
+**
+** Only SM_IO_SEEK_SET, SM_IO_SEEK_CUR and SM_IO_SEEK_END are valid
+** values for whence.
+**
+** Parameters:
+** fp -- the file pointer
+** offset -- number of bytes offset from "base"
+** whence -- determines "base" for 'offset'
+**
+** Returns:
+** Failure: -1 and sets errno
+** Success: >=0, number of bytes read
+*/
+
+off_t
+sm_strseek(fp, offset, whence)
+ SM_FILE_T *fp;
+ off_t offset;
+ int whence;
+{
+ register off_t ret;
+ register SM_STR_OBJ_T *s = fp->f_cookie;
+
+reseek:
+ switch (whence)
+ {
+ case SM_IO_SEEK_SET:
+ ret = offset;
+ break;
+ case SM_IO_SEEK_CUR:
+ ret = s->strio_offset + offset;
+ break;
+ case SM_IO_SEEK_END:
+ ret = s->strio_size;
+ break;
+ default:
+ errno = EINVAL;
+ return -1;
+ }
+ if (ret < 0 || ret > (off_t)(size_t)(-1)) /* XXX ugly */
+ return -1;
+ if ((size_t) ret > s->strio_size)
+ {
+ if (sm_strgrow(s, (size_t)ret))
+ goto reseek;
+
+ /* errno set by sm_strgrow */
+ return -1;
+ }
+ s->strio_offset = (size_t) ret;
+ return ret;
+}
+
+/*
+** SM_STROPEN -- open a string file type
+**
+** Parameters:
+** fp -- file pointer open to be associated with
+** info -- initial contents (NULL for none)
+** flags -- flags for methods of access (was mode)
+** rpool -- resource pool to use memory from (if applicable)
+**
+** Results:
+** Success: 0 (zero)
+** Failure: -1 and sets errno
+*/
+
+int
+sm_stropen(fp, info, flags, rpool)
+ SM_FILE_T *fp;
+ const void *info;
+ int flags;
+ const void *rpool;
+{
+ register SM_STR_OBJ_T *s;
+
+#if SM_RPOOL
+ s = sm_rpool_malloc_x(rpool, sizeof(SM_STR_OBJ_T));
+#else /* SM_RPOOL */
+ s = sm_malloc(sizeof(SM_STR_OBJ_T));
+ if (s == NULL)
+ return -1;
+#endif /* SM_RPOOL */
+
+ fp->f_cookie = s;
+ s->strio_rpool = rpool;
+ s->strio_offset = 0;
+ s->strio_size = 0;
+ s->strio_base = NULL;
+ s->strio_end = 0;
+
+ switch (flags)
+ {
+ case SM_IO_RDWR:
+ s->strio_flags = SMRW;
+ break;
+ case SM_IO_RDONLY:
+ s->strio_flags = SMRD;
+ break;
+ case SM_IO_WRONLY:
+ s->strio_flags = SMWR;
+ break;
+ case SM_IO_APPEND:
+ if (s->strio_rpool == NULL)
+ sm_free(s);
+ errno = EINVAL;
+ return -1;
+ default:
+ if (s->strio_rpool == NULL)
+ sm_free(s);
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (info != NULL)
+ {
+ s->strio_base = sm_strdup_x(info);
+ if (s->strio_base == NULL)
+ {
+ int save_errno = errno;
+
+ if (s->strio_rpool == NULL)
+ sm_free(s);
+ errno = save_errno;
+ return -1;
+ }
+ s->strio_size = strlen(info);
+ s->strio_end = s->strio_base + s->strio_size;
+ }
+ return 0;
+}
+
+/*
+** SM_STRCLOSE -- close the string file type and free resources
+**
+** Parameters:
+** fp -- file pointer
+**
+** Results:
+** Success: 0 (zero)
+*/
+
+int
+sm_strclose(fp)
+ SM_FILE_T *fp;
+{
+ SM_STR_OBJ_T *s = fp->f_cookie;
+
+#if !SM_RPOOL
+ sm_free(s->strio_base);
+ s->strio_base = NULL;
+#endif /* !SM_RPOOL */
+ return 0;
+}
+
+/*
+** SM_STRSETMODE -- set mode info for the file
+**
+** Note: changing the mode can be a safe way to have the "parent"
+** set up a string that the "child" is not to modify
+**
+** Parameters:
+** fp -- the file pointer
+** mode -- location of new mode to set
+**
+** Results:
+** Success: 0 (zero)
+** Failure: -1 and sets errno
+*/
+
+static int
+sm_strsetmode(fp, mode)
+ SM_FILE_T *fp;
+ const int *mode;
+{
+ register SM_STR_OBJ_T *s = fp->f_cookie;
+ int flags;
+
+ switch (*mode)
+ {
+ case SM_IO_RDWR:
+ flags = SMRW;
+ break;
+ case SM_IO_RDONLY:
+ flags = SMRD;
+ break;
+ case SM_IO_WRONLY:
+ flags = SMWR;
+ break;
+ case SM_IO_APPEND:
+ errno = EINVAL;
+ return -1;
+ default:
+ errno = EINVAL;
+ return -1;
+ }
+ s->strio_flags &= ~SMMODEMASK;
+ s->strio_flags |= flags;
+ return 0;
+}
+
+/*
+** SM_STRGETMODE -- get mode info for the file
+**
+** Parameters:
+** fp -- the file pointer
+** mode -- location to store current mode
+**
+** Results:
+** Success: 0 (zero)
+** Failure: -1 and sets errno
+*/
+
+int
+sm_strgetmode(fp, mode)
+ SM_FILE_T *fp;
+ int *mode;
+{
+ register SM_STR_OBJ_T *s = fp->f_cookie;
+
+ switch (s->strio_flags & SMMODEMASK)
+ {
+ case SMRW:
+ *mode = SM_IO_RDWR;
+ break;
+ case SMRD:
+ *mode = SM_IO_RDONLY;
+ break;
+ case SMWR:
+ *mode = SM_IO_WRONLY;
+ break;
+ default:
+ errno = EINVAL;
+ return -1;
+ }
+ return 0;
+}
+
+/*
+** SM_STRSETINFO -- set info for the file
+**
+** Currently only SM_IO_WHAT_MODE is supported for 'what'.
+**
+** Parameters:
+** fp -- the file pointer
+** what -- type of information to set
+** valp -- location to data for doing set
+**
+** Results:
+** Failure: -1 and sets errno
+** Success: sm_strsetmode() return [0 (zero)]
+*/
+
+int
+sm_strsetinfo(fp, what, valp)
+ SM_FILE_T *fp;
+ int what;
+ void *valp;
+{
+ switch(what)
+ {
+ case SM_IO_WHAT_MODE:
+ return sm_strsetmode(fp, (int *) valp);
+ default:
+ errno = EINVAL;
+ return -1;
+ }
+}
+
+/*
+** SM_STRGETINFO -- get info for the file
+**
+** Currently only SM_IO_WHAT_MODE is supported for 'what'.
+**
+** Parameters:
+** fp -- the file pointer
+** what -- type of information requested
+** valp -- location to return information in
+**
+** Results:
+** Failure: -1 and sets errno
+** Success: sm_strgetmode() return [0 (zero)]
+*/
+
+int
+sm_strgetinfo(fp, what, valp)
+ SM_FILE_T *fp;
+ int what;
+ void *valp;
+{
+ switch(what)
+ {
+ case SM_IO_WHAT_MODE:
+ return sm_strgetmode(fp, (int *) valp);
+ default:
+ errno = EINVAL;
+ return -1;
+ }
+}
+
+/*
+** SM_STRIO_INIT -- initializes a write-only string type
+**
+** Original comments below. This function does not appear to be used anywhere.
+** The same functionality can be done by changing the mode of the file.
+** ------------
+** sm_strio_init initializes an SM_FILE_T structure as a write-only file
+** that writes into the specified buffer:
+** - Use sm_io_putc, sm_io_fprintf, etc, to write into the buffer.
+** Attempts to write more than size-1 characters into the buffer will fail
+** silently (no error is reported).
+** - Use sm_io_fflush to nul terminate the string in the buffer
+** (the write pointer is not advanced).
+** No memory is allocated either by sm_strio_init or by sm_io_{putc,write} etc.
+**
+** Parameters:
+** fp -- file pointer
+** buf -- memory location for stored data
+** size -- size of 'buf'
+**
+** Results:
+** none.
+*/
+
+void
+sm_strio_init(fp, buf, size)
+ SM_FILE_T *fp;
+ char *buf;
+ size_t size;
+{
+ fp->sm_magic = SmFileMagic;
+ fp->f_flags = SMWR | SMSTR;
+ fp->f_file = -1;
+ fp->f_bf.smb_base = fp->f_p = (unsigned char *) buf;
+ fp->f_bf.smb_size = fp->f_w = (size ? size - 1 : 0);
+ fp->f_lbfsize = 0;
+ fp->f_r = 0;
+ fp->f_read = NULL;
+ fp->f_seek = NULL;
+ fp->f_getinfo = NULL;
+ fp->f_setinfo = NULL;
+}
diff --git a/usr/src/cmd/sendmail/libsm/strl.c b/usr/src/cmd/sendmail/libsm/strl.c
new file mode 100644
index 0000000000..702c2c9291
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/strl.c
@@ -0,0 +1,326 @@
+/*
+ * Copyright (c) 1999-2002 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: strl.c,v 1.31 2002/01/20 01:41:25 gshapiro Exp $")
+#include <sm/config.h>
+#include <sm/string.h>
+
+/*
+** Notice: this file is used by libmilter. Please try to avoid
+** using libsm specific functions.
+*/
+
+/*
+** XXX the type of the length parameter has been changed
+** from size_t to ssize_t to avoid theoretical problems with negative
+** numbers passed into these functions.
+** The real solution to this problem is to make sure that this doesn't
+** happen, but for now we'll use this workaround.
+*/
+
+/*
+** SM_STRLCPY -- size bounded string copy
+**
+** This is a bounds-checking variant of strcpy.
+** If size > 0, copy up to size-1 characters from the nul terminated
+** string src to dst, nul terminating the result. If size == 0,
+** the dst buffer is not modified.
+** Additional note: this function has been "tuned" to run fast and tested
+** as such (versus versions in some OS's libc).
+**
+** The result is strlen(src). You can detect truncation (not all
+** of the characters in the source string were copied) using the
+** following idiom:
+**
+** char *s, buf[BUFSIZ];
+** ...
+** if (sm_strlcpy(buf, s, sizeof(buf)) >= sizeof(buf))
+** goto overflow;
+**
+** Parameters:
+** dst -- destination buffer
+** src -- source string
+** size -- size of destination buffer
+**
+** Returns:
+** strlen(src)
+*/
+
+size_t
+sm_strlcpy(dst, src, size)
+ register char *dst;
+ register const char *src;
+ ssize_t size;
+{
+ register ssize_t i;
+
+ if (size-- <= 0)
+ return strlen(src);
+ for (i = 0; i < size && (dst[i] = src[i]) != 0; i++)
+ continue;
+ dst[i] = '\0';
+ if (src[i] == '\0')
+ return i;
+ else
+ return i + strlen(src + i);
+}
+
+/*
+** SM_STRLCAT -- size bounded string concatenation
+**
+** This is a bounds-checking variant of strcat.
+** If strlen(dst) < size, then append at most size - strlen(dst) - 1
+** characters from the source string to the destination string,
+** nul terminating the result. Otherwise, dst is not modified.
+**
+** The result is the initial length of dst + the length of src.
+** You can detect overflow (not all of the characters in the
+** source string were copied) using the following idiom:
+**
+** char *s, buf[BUFSIZ];
+** ...
+** if (sm_strlcat(buf, s, sizeof(buf)) >= sizeof(buf))
+** goto overflow;
+**
+** Parameters:
+** dst -- nul-terminated destination string buffer
+** src -- nul-terminated source string
+** size -- size of destination buffer
+**
+** Returns:
+** total length of the string tried to create
+** (= initial length of dst + length of src)
+*/
+
+size_t
+sm_strlcat(dst, src, size)
+ register char *dst;
+ register const char *src;
+ ssize_t size;
+{
+ register ssize_t i, j, o;
+
+ o = strlen(dst);
+ if (size < o + 1)
+ return o + strlen(src);
+ size -= o + 1;
+ for (i = 0, j = o; i < size && (dst[j] = src[i]) != 0; i++, j++)
+ continue;
+ dst[j] = '\0';
+ if (src[i] == '\0')
+ return j;
+ else
+ return j + strlen(src + i);
+}
+/*
+** SM_STRLCAT2 -- append two strings to dst obeying length and
+** '\0' terminate it
+**
+** strlcat2 will append at most len - strlen(dst) - 1 chars.
+** terminates with '\0' if len > 0
+** dst = dst "+" src1 "+" src2
+** use this instead of sm_strlcat(dst,src1); sm_strlcat(dst,src2);
+** for better speed.
+**
+** Parameters:
+** dst -- "destination" string.
+** src1 -- "from" string 1.
+** src2 -- "from" string 2.
+** len -- max. length of "destination" string.
+**
+** Returns:
+** total length of the string tried to create
+** (= initial length of dst + length of src)
+** if this is greater than len then an overflow would have
+** occurred.
+**
+*/
+
+size_t
+sm_strlcat2(dst, src1, src2, len)
+ register char *dst;
+ register const char *src1;
+ register const char *src2;
+ ssize_t len;
+{
+ register ssize_t i, j, o;
+
+ /* current size of dst */
+ o = strlen(dst);
+
+ /* max. size is less than current? */
+ if (len < o + 1)
+ return o + strlen(src1) + strlen(src2);
+
+ len -= o + 1; /* space left in dst */
+
+ /* copy the first string; i: index in src1; j: index in dst */
+ for (i = 0, j = o; i < len && (dst[j] = src1[i]) != 0; i++, j++)
+ continue;
+
+ /* src1: end reached? */
+ if (src1[i] != '\0')
+ {
+ /* no: terminate dst; there is space since i < len */
+ dst[j] = '\0';
+ return j + strlen(src1 + i) + strlen(src2);
+ }
+
+ len -= i; /* space left in dst */
+
+ /* copy the second string; i: index in src2; j: index in dst */
+ for (i = 0; i < len && (dst[j] = src2[i]) != 0; i++, j++)
+ continue;
+ dst[j] = '\0'; /* terminate dst; there is space since i < len */
+ if (src2[i] == '\0')
+ return j;
+ else
+ return j + strlen(src2 + i);
+}
+
+/*
+** SM_STRLCPYN -- concatenate n strings and assign the result to dst
+** while obeying length and '\0' terminate it
+**
+** dst = src1 "+" src2 "+" ...
+** use this instead of sm_snprintf() for string values
+** and repeated sm_strlc*() calls for better speed.
+**
+** Parameters:
+** dst -- "destination" string.
+** len -- max. length of "destination" string.
+** n -- number of strings
+** strings...
+**
+** Returns:
+** total length of the string tried to create
+** (= initial length of dst + length of src)
+** if this is greater than len then an overflow would have
+** occurred.
+*/
+
+size_t
+#ifdef __STDC__
+sm_strlcpyn(char *dst, ssize_t len, int n, ...)
+#else /* __STDC__ */
+sm_strlcpyn(dst, len, n, va_alist)
+ register char *dst;
+ ssize_t len;
+ int n;
+ va_dcl
+#endif /* __STDC__ */
+{
+ register ssize_t i, j;
+ char *str;
+ SM_VA_LOCAL_DECL
+
+ SM_VA_START(ap, n);
+
+ if (len-- <= 0) /* This allows space for the terminating '\0' */
+ {
+ i = 0;
+ while (n-- > 0)
+ i += strlen(SM_VA_ARG(ap, char *));
+ SM_VA_END(ap);
+ return i;
+ }
+
+ j = 0; /* index in dst */
+
+ /* loop through all source strings */
+ while (n-- > 0)
+ {
+ str = SM_VA_ARG(ap, char *);
+
+ /* copy string; i: index in str; j: index in dst */
+ for (i = 0; j < len && (dst[j] = str[i]) != 0; i++, j++)
+ continue;
+
+ /* str: end reached? */
+ if (str[i] != '\0')
+ {
+ /* no: terminate dst; there is space since j < len */
+ dst[j] = '\0';
+ j += strlen(str + i);
+ while (n-- > 0)
+ j += strlen(SM_VA_ARG(ap, char *));
+ SM_VA_END(ap);
+ return j;
+ }
+ }
+ SM_VA_END(ap);
+
+ dst[j] = '\0'; /* terminate dst; there is space since j < len */
+ return j;
+}
+
+#if 0
+/*
+** SM_STRLAPP -- append string if it fits into buffer.
+**
+** If size > 0, copy up to size-1 characters from the nul terminated
+** string src to dst, nul terminating the result. If size == 0,
+** the dst buffer is not modified.
+**
+** This routine is useful for appending strings in a loop, e.g, instead of
+** s = buf;
+** for (ptr, ptr != NULL, ptr = next->ptr)
+** {
+** (void) sm_strlcpy(s, ptr->string, sizeof buf - (s - buf));
+** s += strlen(s);
+** }
+** replace the loop body with:
+** if (!sm_strlapp(*s, ptr->string, sizeof buf - (s - buf)))
+** break;
+** it's faster...
+**
+** XXX interface isn't completely clear (yet), hence this code is
+** not available.
+**
+**
+** Parameters:
+** dst -- (pointer to) destination buffer
+** src -- source string
+** size -- size of destination buffer
+**
+** Returns:
+** true if strlen(src) < size
+**
+** Side Effects:
+** modifies dst if append succeeds (enough space).
+*/
+
+bool
+sm_strlapp(dst, src, size)
+ register char **dst;
+ register const char *src;
+ ssize_t size;
+{
+ register size_t i;
+
+ if (size-- <= 0)
+ return false;
+ for (i = 0; i < size && ((*dst)[i] = src[i]) != '\0'; i++)
+ continue;
+ (*dst)[i] = '\0';
+ if (src[i] == '\0')
+ {
+ *dst += i;
+ return true;
+ }
+
+ /* undo */
+ (*dst)[0] = '\0';
+ return false;
+}
+#endif /* 0 */
diff --git a/usr/src/cmd/sendmail/libsm/strrevcmp.c b/usr/src/cmd/sendmail/libsm/strrevcmp.c
new file mode 100644
index 0000000000..f2714aeea8
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/strrevcmp.c
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: strrevcmp.c,v 1.2 2001/08/27 22:21:51 gshapiro Exp $")
+
+#include <sm/config.h>
+#include <sm/string.h>
+#include <string.h>
+
+/* strcasecmp.c */
+extern const unsigned char charmap[];
+
+/*
+** SM_STRREVCASECMP -- compare two strings starting at the end (ignore case)
+**
+** Parameters:
+** s1 -- first string.
+** s2 -- second string.
+**
+** Returns:
+** strcasecmp(reverse(s1), reverse(s2))
+*/
+
+int
+sm_strrevcasecmp(s1, s2)
+ const char *s1, *s2;
+{
+ register int i1, i2;
+
+ i1 = strlen(s1) - 1;
+ i2 = strlen(s2) - 1;
+ while (i1 >= 0 && i2 >= 0 &&
+ charmap[(unsigned char) s1[i1]] ==
+ charmap[(unsigned char) s2[i2]])
+ {
+ --i1;
+ --i2;
+ }
+ if (i1 < 0)
+ {
+ if (i2 < 0)
+ return 0;
+ else
+ return -1;
+ }
+ else
+ {
+ if (i2 < 0)
+ return 1;
+ else
+ return (charmap[(unsigned char) s1[i1]] -
+ charmap[(unsigned char) s2[i2]]);
+ }
+}
+ /*
+** SM_STRREVCMP -- compare two strings starting at the end
+**
+** Parameters:
+** s1 -- first string.
+** s2 -- second string.
+**
+** Returns:
+** strcmp(reverse(s1), reverse(s2))
+*/
+
+int
+sm_strrevcmp(s1, s2)
+ const char *s1, *s2;
+{
+ register int i1, i2;
+
+ i1 = strlen(s1) - 1;
+ i2 = strlen(s2) - 1;
+ while (i1 >= 0 && i2 >= 0 && s1[i1] == s2[i2])
+ {
+ --i1;
+ --i2;
+ }
+ if (i1 < 0)
+ {
+ if (i2 < 0)
+ return 0;
+ else
+ return -1;
+ }
+ else
+ {
+ if (i2 < 0)
+ return 1;
+ else
+ return s1[i1] - s2[i2];
+ }
+}
diff --git a/usr/src/cmd/sendmail/libsm/strto.c b/usr/src/cmd/sendmail/libsm/strto.c
new file mode 100644
index 0000000000..78a9dffcab
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/strto.c
@@ -0,0 +1,256 @@
+/*
+ * Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1992
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_IDSTR(id, "@(#)$Id: strto.c,v 1.18 2001/12/30 04:59:37 gshapiro Exp $")
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <errno.h>
+#include <sm/limits.h>
+#include <sm/conf.h>
+#include <sm/string.h>
+
+/*
+** SM_STRTOLL -- Convert a string to a (signed) long long integer.
+**
+** Ignores `locale' stuff. Assumes that the upper and lower case
+** alphabets and digits are each contiguous.
+**
+** Parameters:
+** nptr -- string containing number
+** endptr -- location of first invalid character
+** base -- numeric base that 'nptr' number is based in
+**
+** Returns:
+** Failure: on underflow LLONG_MIN is returned; on overflow
+** LLONG_MAX is returned and errno is set.
+** When 'endptr' == '\0' then the entire string 'nptr'
+** was valid.
+** Success: returns the converted number
+*/
+
+LONGLONG_T
+sm_strtoll(nptr, endptr, base)
+ const char *nptr;
+ char **endptr;
+ register int base;
+{
+ register bool neg;
+ register const char *s;
+ register LONGLONG_T acc, cutoff;
+ register int c;
+ register int any, cutlim;
+
+ /*
+ ** Skip white space and pick up leading +/- sign if any.
+ ** If base is 0, allow 0x for hex and 0 for octal, else
+ ** assume decimal; if base is already 16, allow 0x.
+ */
+
+ s = nptr;
+ do
+ {
+ c = (unsigned char) *s++;
+ } while (isascii(c) && isspace(c));
+ if (c == '-')
+ {
+ neg = true;
+ c = *s++;
+ }
+ else
+ {
+ neg = false;
+ if (c == '+')
+ c = *s++;
+ }
+ if ((base == 0 || base == 16) &&
+ c == '0' && (*s == 'x' || *s == 'X'))
+ {
+ c = s[1];
+ s += 2;
+ base = 16;
+ }
+ if (base == 0)
+ base = c == '0' ? 8 : 10;
+
+ /*
+ ** Compute the cutoff value between legal numbers and illegal
+ ** numbers. That is the largest legal value, divided by the
+ ** base. An input number that is greater than this value, if
+ ** followed by a legal input character, is too big. One that
+ ** is equal to this value may be valid or not; the limit
+ ** between valid and invalid numbers is then based on the last
+ ** digit. For instance, if the range for long-long's is
+ ** [-9223372036854775808..9223372036854775807] and the input base
+ ** is 10, cutoff will be set to 922337203685477580 and cutlim to
+ ** either 7 (!neg) or 8 (neg), meaning that if we have
+ ** accumulated a value > 922337203685477580, or equal but the
+ ** next digit is > 7 (or 8), the number is too big, and we will
+ ** return a range error.
+ **
+ ** Set any if any `digits' consumed; make it negative to indicate
+ ** overflow.
+ */
+
+ cutoff = neg ? LLONG_MIN : LLONG_MAX;
+ cutlim = cutoff % base;
+ cutoff /= base;
+ if (neg)
+ {
+ if (cutlim > 0)
+ {
+ cutlim -= base;
+ cutoff += 1;
+ }
+ cutlim = -cutlim;
+ }
+ for (acc = 0, any = 0;; c = (unsigned char) *s++)
+ {
+ if (isascii(c) && isdigit(c))
+ c -= '0';
+ else if (isascii(c) && isalpha(c))
+ c -= isupper(c) ? 'A' - 10 : 'a' - 10;
+ else
+ break;
+ if (c >= base)
+ break;
+ if (any < 0)
+ continue;
+ if (neg)
+ {
+ if (acc < cutoff || (acc == cutoff && c > cutlim))
+ {
+ any = -1;
+ acc = LLONG_MIN;
+ errno = ERANGE;
+ }
+ else
+ {
+ any = 1;
+ acc *= base;
+ acc -= c;
+ }
+ }
+ else
+ {
+ if (acc > cutoff || (acc == cutoff && c > cutlim))
+ {
+ any = -1;
+ acc = LLONG_MAX;
+ errno = ERANGE;
+ }
+ else
+ {
+ any = 1;
+ acc *= base;
+ acc += c;
+ }
+ }
+ }
+ if (endptr != 0)
+ *endptr = (char *) (any ? s - 1 : nptr);
+ return acc;
+}
+
+/*
+** SM_STRTOULL -- Convert a string to an unsigned long long integer.
+**
+** Ignores `locale' stuff. Assumes that the upper and lower case
+** alphabets and digits are each contiguous.
+**
+** Parameters:
+** nptr -- string containing (unsigned) number
+** endptr -- location of first invalid character
+** base -- numeric base that 'nptr' number is based in
+**
+** Returns:
+** Failure: on overflow ULLONG_MAX is returned and errno is set.
+** When 'endptr' == '\0' then the entire string 'nptr'
+** was valid.
+** Success: returns the converted number
+*/
+
+ULONGLONG_T
+sm_strtoull(nptr, endptr, base)
+ const char *nptr;
+ char **endptr;
+ register int base;
+{
+ register const char *s;
+ register ULONGLONG_T acc, cutoff;
+ register int c;
+ register bool neg;
+ register int any, cutlim;
+
+ /* See sm_strtoll for comments as to the logic used. */
+ s = nptr;
+ do
+ {
+ c = (unsigned char) *s++;
+ } while (isascii(c) && isspace(c));
+ neg = (c == '-');
+ if (neg)
+ {
+ c = *s++;
+ }
+ else
+ {
+ if (c == '+')
+ c = *s++;
+ }
+ if ((base == 0 || base == 16) &&
+ c == '0' && (*s == 'x' || *s == 'X'))
+ {
+ c = s[1];
+ s += 2;
+ base = 16;
+ }
+ if (base == 0)
+ base = c == '0' ? 8 : 10;
+
+ cutoff = ULLONG_MAX / (ULONGLONG_T)base;
+ cutlim = ULLONG_MAX % (ULONGLONG_T)base;
+ for (acc = 0, any = 0;; c = (unsigned char) *s++)
+ {
+ if (isascii(c) && isdigit(c))
+ c -= '0';
+ else if (isascii(c) && isalpha(c))
+ c -= isupper(c) ? 'A' - 10 : 'a' - 10;
+ else
+ break;
+ if (c >= base)
+ break;
+ if (any < 0)
+ continue;
+ if (acc > cutoff || (acc == cutoff && c > cutlim))
+ {
+ any = -1;
+ acc = ULLONG_MAX;
+ errno = ERANGE;
+ }
+ else
+ {
+ any = 1;
+ acc *= (ULONGLONG_T)base;
+ acc += c;
+ }
+ }
+ if (neg && any > 0)
+ acc = -((LONGLONG_T) acc);
+ if (endptr != 0)
+ *endptr = (char *) (any ? s - 1 : nptr);
+ return acc;
+}
diff --git a/usr/src/cmd/sendmail/libsm/t-event.c b/usr/src/cmd/sendmail/libsm/t-event.c
new file mode 100644
index 0000000000..0b56b07900
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/t-event.c
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2001-2002, 2004 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: t-event.c,v 1.12 2004/08/03 20:50:32 ca Exp $")
+
+#include <stdio.h>
+
+#include <stdlib.h>
+#include <unistd.h>
+# include <sys/wait.h>
+#if SM_CONF_SETITIMER
+# include <sys/time.h>
+#endif /* SM_CONF_SETITIMER */
+
+#include <sm/clock.h>
+#include <sm/test.h>
+
+static void evcheck __P((int));
+static void ev1 __P((int));
+
+static int check;
+
+static void
+evcheck(arg)
+ int arg;
+{
+ SM_TEST(arg == 3);
+ SM_TEST(check == 0);
+ check++;
+}
+
+static void
+ev1(arg)
+ int arg;
+{
+ SM_TEST(arg == 1);
+}
+
+/* define as x if you want debug output */
+#define DBG_OUT(x)
+
+int
+main(argc, argv)
+ int argc;
+ char *argv[];
+{
+ SM_EVENT *ev;
+
+ sm_test_begin(argc, argv, "test event handling");
+ fprintf(stdout, "This test may hang. If there is no output within twelve seconds, abort it\nand recompile with -DSM_CONF_SETITIMER=%d\n",
+ SM_CONF_SETITIMER == 0 ? 1 : 0);
+ sleep(1);
+ SM_TEST(1 == 1);
+ DBG_OUT(fprintf(stdout, "We're back, test 1 seems to work.\n"));
+ ev = sm_seteventm(1000, ev1, 1);
+ sleep(1);
+ SM_TEST(2 == 2);
+ DBG_OUT(fprintf(stdout, "We're back, test 2 seems to work.\n"));
+
+ /* schedule an event in 9s */
+ ev = sm_seteventm(9000, ev1, 2);
+ sleep(1);
+
+ /* clear the event before it can fire */
+ sm_clrevent(ev);
+ SM_TEST(3 == 3);
+ DBG_OUT(fprintf(stdout, "We're back, test 3 seems to work.\n"));
+
+ /* schedule an event in 1s */
+ check = 0;
+ ev = sm_seteventm(1000, evcheck, 3);
+ sleep(2);
+
+ /* clear the event */
+ sm_clrevent(ev);
+ SM_TEST(4 == 4);
+ SM_TEST(check == 1);
+ DBG_OUT(fprintf(stdout, "We're back, test 4 seems to work.\n"));
+
+ return sm_test_end();
+}
diff --git a/usr/src/cmd/sendmail/libsm/t-exc.c b/usr/src/cmd/sendmail/libsm/t-exc.c
new file mode 100644
index 0000000000..1af39e0b7f
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/t-exc.c
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_IDSTR(id, "@(#)$Id: t-exc.c,v 1.18 2001/07/05 22:46:35 gshapiro Exp $")
+
+#include <string.h>
+#include <sm/heap.h>
+#include <sm/io.h>
+#include <sm/test.h>
+
+const SM_EXC_TYPE_T EtypeTest1 =
+{
+ SmExcTypeMagic,
+ "E:test1",
+ "i",
+ sm_etype_printf,
+ "test1 exception argv[0]=%0",
+};
+
+const SM_EXC_TYPE_T EtypeTest2 =
+{
+ SmExcTypeMagic,
+ "E:test2",
+ "i",
+ sm_etype_printf,
+ "test2 exception argv[0]=%0",
+};
+
+int
+main(argc, argv)
+ int argc;
+ char **argv;
+{
+ void *p;
+ int volatile x;
+ char *unknown, *cant;
+
+ sm_test_begin(argc, argv, "test exception handling");
+
+ /*
+ ** SM_TRY
+ */
+
+ cant = "can't happen";
+ x = 0;
+ SM_TRY
+ x = 1;
+ SM_END_TRY
+ SM_TEST(x == 1);
+
+ /*
+ ** SM_FINALLY-0
+ */
+
+ x = 0;
+ SM_TRY
+ x = 1;
+ SM_FINALLY
+ x = 2;
+ SM_END_TRY
+ SM_TEST(x == 2);
+
+ /*
+ ** SM_FINALLY-1
+ */
+
+ x = 0;
+ SM_TRY
+ SM_TRY
+ x = 1;
+ sm_exc_raisenew_x(&EtypeTest1, 17);
+ SM_FINALLY
+ x = 2;
+ sm_exc_raisenew_x(&EtypeTest2, 42);
+ SM_END_TRY
+ SM_EXCEPT(exc, "E:test2")
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "got exception test2: can't happen\n");
+ SM_EXCEPT(exc, "E:test1")
+ SM_TEST(x == 2 && exc->exc_argv[0].v_int == 17);
+ if (!(x == 2 && exc->exc_argv[0].v_int == 17))
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "can't happen: x=%d argv[0]=%d\n",
+ x, exc->exc_argv[0].v_int);
+ }
+ SM_EXCEPT(exc, "*")
+ {
+ unknown = "unknown exception: ";
+ SM_TEST(strcmp(unknown, cant) == 0);
+ }
+ SM_END_TRY
+
+ x = 3;
+ SM_TRY
+ x = 4;
+ sm_exc_raisenew_x(&EtypeTest1, 94);
+ SM_FINALLY
+ x = 5;
+ sm_exc_raisenew_x(&EtypeTest2, 95);
+ SM_EXCEPT(exc, "E:test2")
+ {
+ unknown = "got exception test2: ";
+ SM_TEST(strcmp(unknown, cant) == 0);
+ }
+ SM_EXCEPT(exc, "E:test1")
+ SM_TEST(x == 5 && exc->exc_argv[0].v_int == 94);
+ if (!(x == 5 && exc->exc_argv[0].v_int == 94))
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "can't happen: x=%d argv[0]=%d\n",
+ x, exc->exc_argv[0].v_int);
+ }
+ SM_EXCEPT(exc, "*")
+ {
+ unknown = "unknown exception: ";
+ SM_TEST(strcmp(unknown, cant) == 0);
+ }
+ SM_END_TRY
+
+ SM_TRY
+ sm_exc_raisenew_x(&SmEtypeErr, "test %d", 0);
+ SM_EXCEPT(exc, "*")
+#if DEBUG
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "test 0 got an exception, as expected:\n");
+ sm_exc_print(exc, smioout);
+#endif /* DEBUG */
+ return sm_test_end();
+ SM_END_TRY
+
+ p = sm_malloc_x((size_t)(-1));
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "sm_malloc_x unexpectedly succeeded, returning %p\n", p);
+ unknown = "sm_malloc_x unexpectedly succeeded";
+ SM_TEST(strcmp(unknown, cant) == 0);
+ return sm_test_end();
+}
diff --git a/usr/src/cmd/sendmail/libsm/t-float.c b/usr/src/cmd/sendmail/libsm/t-float.c
new file mode 100644
index 0000000000..b4d416ab97
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/t-float.c
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_IDSTR(id, "@(#)$Id: t-float.c,v 1.16 2001/02/02 23:11:46 ca Exp $")
+
+#include <sm/limits.h>
+#include <sm/io.h>
+#include <sm/string.h>
+#include <sm/test.h>
+#include <sm/types.h>
+
+int
+main(argc, argv)
+ int argc;
+ char **argv;
+{
+ double d, d2;
+ double ld;
+ char buf[128];
+ char *r;
+
+ /*
+ ** Sendmail uses printf and scanf with doubles,
+ ** so make sure that this works.
+ */
+
+ sm_test_begin(argc, argv, "test floating point stuff");
+
+ d = 1.125;
+ sm_snprintf(buf, sizeof(buf), "%d %.3f %d", 0, d, 1);
+ r = "0 1.125 1";
+ if (!SM_TEST(strcmp(buf, r) == 0))
+ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ "got %s instead\n", buf);
+
+ d = 1.125;
+ sm_snprintf(buf, sizeof(buf), "%.3f", d);
+ r = "1.125";
+ if (!SM_TEST(strcmp(buf, r) == 0))
+ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ "got %s instead\n", buf);
+ d2 = 0.0;
+ sm_io_sscanf(buf, "%lf", &d2);
+#if SM_CONF_BROKEN_STRTOD
+ if (d != d2)
+ {
+ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ "wanted %f, got %f\n", d, d2);
+ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ "error ignored since SM_CONF_BROKEN_STRTOD is set for this OS\n");
+ }
+#else /* SM_CONF_BROKEN_STRTOD */
+ if (!SM_TEST(d == d2))
+ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ "wanted %f, got %f\n", d, d2);
+#endif /* SM_CONF_BROKEN_STRTOD */
+
+ ld = 2.5;
+ sm_snprintf(buf, sizeof(buf), "%.3f %.1f", d, ld);
+ r = "1.125 2.5";
+ if (!SM_TEST(strcmp(buf, r) == 0))
+ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ "got %s instead\n", buf);
+ return sm_test_end();
+}
diff --git a/usr/src/cmd/sendmail/libsm/t-fopen.c b/usr/src/cmd/sendmail/libsm/t-fopen.c
new file mode 100644
index 0000000000..2fcb2839e6
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/t-fopen.c
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2000-2002 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_IDSTR(id, "@(#)$Id: t-fopen.c,v 1.9 2002/02/06 23:57:45 ca Exp $")
+
+#include <fcntl.h>
+#include <sm/io.h>
+#include <sm/test.h>
+
+/* ARGSUSED0 */
+int
+main(argc, argv)
+ int argc;
+ char *argv[];
+{
+ int m, r;
+ SM_FILE_T *out;
+
+ sm_test_begin(argc, argv, "test sm_io_fopen");
+ out = sm_io_fopen("foo", O_WRONLY|O_APPEND|O_CREAT, 0666);
+ SM_TEST(out != NULL);
+ if (out != NULL)
+ {
+ (void) sm_io_fprintf(out, SM_TIME_DEFAULT, "foo\n");
+ r = sm_io_getinfo(out, SM_IO_WHAT_MODE, &m);
+ SM_TEST(r == 0);
+ SM_TEST(m == SM_IO_WRONLY);
+ sm_io_close(out, SM_TIME_DEFAULT);
+ }
+ return sm_test_end();
+}
diff --git a/usr/src/cmd/sendmail/libsm/t-heap.c b/usr/src/cmd/sendmail/libsm/t-heap.c
new file mode 100644
index 0000000000..290b07e17f
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/t-heap.c
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_IDSTR(id, "@(#)$Id: t-heap.c,v 1.8 2001/03/06 17:27:36 ca Exp $")
+
+#include <sm/debug.h>
+#include <sm/heap.h>
+#include <sm/io.h>
+#include <sm/test.h>
+#include <sm/xtrap.h>
+
+#if SM_HEAP_CHECK
+extern SM_DEBUG_T SmHeapCheck;
+# define HEAP_CHECK sm_debug_active(&SmHeapCheck, 1)
+#else /* SM_HEAP_CHECK */
+# define HEAP_CHECK 0
+#endif /* SM_HEAP_CHECK */
+
+int
+main(argc, argv)
+ int argc;
+ char **argv;
+{
+ void *p;
+
+ sm_test_begin(argc, argv, "test heap handling");
+ if (argc > 1)
+ sm_debug_addsettings_x(argv[1]);
+
+ p = sm_malloc(10);
+ SM_TEST(p != NULL);
+ p = sm_realloc_x(p, 20);
+ SM_TEST(p != NULL);
+ p = sm_realloc(p, 30);
+ SM_TEST(p != NULL);
+ if (HEAP_CHECK)
+ {
+ sm_dprintf("heap with 1 30-byte block allocated:\n");
+ sm_heap_report(smioout, 3);
+ }
+
+ if (HEAP_CHECK)
+ {
+ sm_free(p);
+ sm_dprintf("heap with 0 blocks allocated:\n");
+ sm_heap_report(smioout, 3);
+ sm_dprintf("xtrap count = %d\n", SmXtrapCount);
+ }
+
+#if DEBUG
+ /* this will cause a core dump */
+ sm_dprintf("about to free %p for the second time\n", p);
+ sm_free(p);
+#endif /* DEBUG */
+
+ return sm_test_end();
+}
diff --git a/usr/src/cmd/sendmail/libsm/t-match.c b/usr/src/cmd/sendmail/libsm/t-match.c
new file mode 100644
index 0000000000..01012fa188
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/t-match.c
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2000 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_IDSTR(id, "@(#)$Id: t-match.c,v 1.7 2000/12/18 18:12:12 ca Exp $")
+
+#include <sm/string.h>
+#include <sm/io.h>
+#include <sm/test.h>
+
+#define try(str, pat, want) \
+ got = sm_match(str, pat); \
+ if (!SM_TEST(got == want)) \
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, \
+ "sm_match(\"%s\", \"%s\") returns %s\n", \
+ str, pat, got ? "true" : "false");
+
+int
+main(argc, argv)
+ int argc;
+ char **argv;
+{
+ bool got;
+
+ sm_test_begin(argc, argv, "test sm_match");
+
+ try("foo", "foo", true);
+ try("foo", "bar", false);
+ try("foo[bar", "foo[bar", true);
+ try("foo[bar]", "foo[bar]", false);
+ try("foob", "foo[bar]", true);
+ try("a-b", "a[]-]b", true);
+ try("abcde", "a*e", true);
+ try("[", "[[]", true);
+ try("c", "[a-z]", true);
+ try("C", "[a-z]", false);
+ try("F:sm.heap", "[!F]*", false);
+ try("E:sm.err", "[!F]*", true);
+
+ return sm_test_end();
+}
diff --git a/usr/src/cmd/sendmail/libsm/t-path.c b/usr/src/cmd/sendmail/libsm/t-path.c
new file mode 100644
index 0000000000..066d2bbaa4
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/t-path.c
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_IDSTR(id, "@(#)$Id: t-path.c,v 1.6 2001/07/05 22:47:29 gshapiro Exp $")
+
+#include <string.h>
+#include <sm/path.h>
+#include <sm/test.h>
+
+int
+main(argc, argv)
+ int argc;
+ char **argv;
+{
+ char *r;
+
+ sm_test_begin(argc, argv, "test path handling");
+
+ SM_TEST(sm_path_isdevnull(SM_PATH_DEVNULL));
+ r = "/dev/null";
+ SM_TEST(sm_path_isdevnull(r));
+ r = "/nev/dull";
+ SM_TEST(!sm_path_isdevnull(r));
+ r = "nul";
+ SM_TEST(!sm_path_isdevnull(r));
+
+ return sm_test_end();
+}
diff --git a/usr/src/cmd/sendmail/libsm/t-rpool.c b/usr/src/cmd/sendmail/libsm/t-rpool.c
new file mode 100644
index 0000000000..98de40662e
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/t-rpool.c
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_IDSTR(id, "@(#)$Id: t-rpool.c,v 1.16 2001/03/04 18:38:47 ca Exp $")
+
+#include <sm/debug.h>
+#include <sm/heap.h>
+#include <sm/rpool.h>
+#include <sm/io.h>
+#include <sm/test.h>
+
+static void
+rfree __P((
+ void *cx));
+
+static void
+rfree(cx)
+ void *cx;
+{
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "rfree freeing `%s'\n",
+ (char *) cx);
+}
+
+int
+main(argc, argv)
+ int argc;
+ char *argv[];
+{
+ SM_RPOOL_T *rpool;
+ char *a[26];
+ int i, j;
+ SM_RPOOL_ATTACH_T att;
+
+ sm_test_begin(argc, argv, "test rpool");
+ sm_debug_addsetting_x("sm_check_heap", 1);
+ rpool = sm_rpool_new_x(NULL);
+ SM_TEST(rpool != NULL);
+ att = sm_rpool_attach_x(rpool, rfree, "attachment #1");
+ SM_TEST(att != NULL);
+ for (i = 0; i < 26; ++i)
+ {
+ size_t sz = i * i * i;
+
+ a[i] = sm_rpool_malloc_x(rpool, sz);
+ for (j = 0; j < sz; ++j)
+ a[i][j] = 'a' + i;
+ }
+ att = sm_rpool_attach_x(rpool, rfree, "attachment #2");
+ (void) sm_rpool_attach_x(rpool, rfree, "attachment #3");
+ sm_rpool_detach(att);
+
+ /* XXX more tests? */
+#if DEBUG
+ sm_dprintf("heap after filling up rpool:\n");
+ sm_heap_report(smioout, 3);
+ sm_dprintf("freeing rpool:\n");
+ sm_rpool_free(rpool);
+ sm_dprintf("heap after freeing rpool:\n");
+ sm_heap_report(smioout, 3);
+#endif /* DEBUG */
+ return sm_test_end();
+}
diff --git a/usr/src/cmd/sendmail/libsm/t-scanf.c b/usr/src/cmd/sendmail/libsm/t-scanf.c
new file mode 100644
index 0000000000..3dac7d4a8e
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/t-scanf.c
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_IDSTR(id, "@(#)$Id: t-scanf.c,v 1.5 2001/11/13 00:51:28 ca Exp $")
+
+#include <sm/limits.h>
+#include <sm/io.h>
+#include <sm/string.h>
+#include <sm/test.h>
+#include <sm/types.h>
+
+int
+main(argc, argv)
+ int argc;
+ char **argv;
+{
+ int i, d, h;
+ char buf[128];
+ char *r;
+
+ sm_test_begin(argc, argv, "test scanf point stuff");
+#if !SM_CONF_BROKEN_SIZE_T
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+"If tests for \"h == 2\" fail, check whether size_t is signed on your OS.\n\
+If that is the case, add -DSM_CONF_BROKEN_SIZE_T to confENVDEF\n\
+and start over. Otherwise contact sendmail.org.\n");
+#endif /* !SM_CONF_BROKEN_SIZE_T */
+
+ d = 2;
+ sm_snprintf(buf, sizeof(buf), "%d", d);
+ r = "2";
+ if (!SM_TEST(strcmp(buf, r) == 0))
+ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ "got %s instead\n", buf);
+
+ i = sm_io_sscanf(buf, "%d", &h);
+ SM_TEST(i == 1);
+ SM_TEST(h == 2);
+
+ d = 2;
+ sm_snprintf(buf, sizeof(buf), "%d\n", d);
+ r = "2\n";
+ if (!SM_TEST(strcmp(buf, r) == 0))
+ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ "got %s instead\n", buf);
+
+ i = sm_io_sscanf(buf, "%d", &h);
+ SM_TEST(i == 1);
+ SM_TEST(h == 2);
+
+ return sm_test_end();
+}
diff --git a/usr/src/cmd/sendmail/libsm/t-sem.c b/usr/src/cmd/sendmail/libsm/t-sem.c
new file mode 100644
index 0000000000..c8ee8a48a1
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/t-sem.c
@@ -0,0 +1,346 @@
+/*
+ * Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: t-sem.c,v 1.14 2005/03/25 21:27:41 ca Exp $")
+
+#include <stdio.h>
+
+#if SM_CONF_SEM
+# include <stdlib.h>
+# include <unistd.h>
+# include <sysexits.h>
+# include <sm/heap.h>
+# include <sm/string.h>
+# include <sm/signal.h>
+# include <sm/test.h>
+# include <sm/sem.h>
+
+static void
+delay(t, s)
+ int t;
+ char *s;
+{
+ if (t > 0)
+ {
+#if DEBUG
+ fprintf(stderr, "sleep(%d) before %s\n", t, s);
+#endif /* DEBUG */
+ sleep(t);
+ }
+#if DEBUG
+ fprintf(stderr, "%s\n", s);
+#endif /* DEBUG */
+}
+
+
+/*
+** SEMINTER -- interactive testing of semaphores.
+**
+** Parameters:
+** owner -- create semaphores.
+**
+** Returns:
+** 0 on success
+** < 0 on failure.
+*/
+
+static int
+seminter(owner)
+ bool owner;
+{
+ int semid;
+ int t;
+
+ semid = sm_sem_start(SM_SEM_KEY, SM_NSEM, 0, owner);
+ if (semid < 0)
+ {
+ perror("sm_sem_start failed");
+ return 1;
+ }
+
+ while ((t = getchar()) != EOF)
+ {
+ switch (t)
+ {
+ case 'a':
+ delay(0, "try to acq");
+ if (sm_sem_acq(semid, 0, 2) < 0)
+ {
+ perror("sm_sem_acq failed");
+ return 1;
+ }
+ delay(0, "acquired");
+ break;
+
+ case 'r':
+ delay(0, "try to rel");
+ if (sm_sem_rel(semid, 0, 2) < 0)
+ {
+ perror("sm_sem_rel failed");
+ return 1;
+ }
+ delay(0, "released");
+ break;
+
+ case 'v':
+ if ((t = sm_sem_get(semid, 0)) < 0)
+ {
+ perror("get_sem failed");
+ return 1;
+ }
+ printf("semval: %d\n", t);
+ break;
+
+ }
+ }
+ if (owner)
+ return sm_sem_stop(semid);
+ return 0;
+}
+
+/*
+** SEM_CLEANUP -- cleanup if something breaks
+**
+** Parameters:
+** sig -- signal.
+**
+** Returns:
+** none.
+*/
+
+static int semid_c = -1;
+void
+sem_cleanup(sig)
+ int sig;
+{
+ if (semid_c >= 0)
+ (void) sm_sem_stop(semid_c);
+ exit(EX_UNAVAILABLE);
+}
+
+/*
+** SEMTEST -- test of semaphores
+**
+** Parameters:
+** owner -- create semaphores.
+**
+** Returns:
+** 0 on success
+** < 0 on failure.
+*/
+
+# define MAX_CNT 10
+
+static int
+semtest(owner)
+ int owner;
+{
+ int semid, r;
+ int cnt = 0;
+
+ semid = sm_sem_start(SM_SEM_KEY, 1, 0, owner);
+ if (semid < 0)
+ {
+ perror("sm_sem_start failed");
+ return -1;
+ }
+
+ if (owner)
+ {
+ /* just in case someone kills the program... */
+ semid_c = semid;
+ (void) sm_signal(SIGHUP, sem_cleanup);
+ (void) sm_signal(SIGINT, sem_cleanup);
+ (void) sm_signal(SIGTERM, sem_cleanup);
+
+ delay(1, "parent: acquire 1");
+ cnt = 0;
+ do
+ {
+ r = sm_sem_acq(semid, 0, 0);
+ if (r < 0)
+ {
+ sleep(1);
+ ++cnt;
+ }
+ } while (r < 0 && cnt <= MAX_CNT);
+ SM_TEST(r >= 0);
+ if (r < 0)
+ return r;
+
+ delay(3, "parent: release 1");
+ cnt = 0;
+ do
+ {
+ r = sm_sem_rel(semid, 0, 0);
+ if (r < 0)
+ {
+ sleep(1);
+ ++cnt;
+ }
+ } while (r < 0 && cnt <= MAX_CNT);
+ SM_TEST(r >= 0);
+ if (r < 0)
+ return r;
+
+ delay(1, "parent: getval");
+ cnt = 0;
+ do
+ {
+ r = sm_sem_get(semid, 0);
+ if (r <= 0)
+ {
+ sleep(1);
+ ++cnt;
+ }
+ } while (r <= 0 && cnt <= MAX_CNT);
+ SM_TEST(r > 0);
+ if (r <= 0)
+ return r;
+
+ delay(1, "parent: acquire 2");
+ cnt = 0;
+ do
+ {
+ r = sm_sem_acq(semid, 0, 0);
+ if (r < 0)
+ {
+ sleep(1);
+ ++cnt;
+ }
+ } while (r < 0 && cnt <= MAX_CNT);
+ SM_TEST(r >= 0);
+ if (r < 0)
+ return r;
+
+ cnt = 0;
+ do
+ {
+ r = sm_sem_rel(semid, 0, 0);
+ if (r < 0)
+ {
+ sleep(1);
+ ++cnt;
+ }
+ } while (r < 0 && cnt <= MAX_CNT);
+ SM_TEST(r >= 0);
+ if (r < 0)
+ return r;
+ }
+ else
+ {
+ delay(1, "child: acquire 1");
+ cnt = 0;
+ do
+ {
+ r = sm_sem_acq(semid, 0, 0);
+ if (r < 0)
+ {
+ sleep(1);
+ ++cnt;
+ }
+ } while (r < 0 && cnt <= MAX_CNT);
+ SM_TEST(r >= 0);
+ if (r < 0)
+ return r;
+
+ delay(1, "child: release 1");
+ cnt = 0;
+ do
+ {
+ r = sm_sem_rel(semid, 0, 0);
+ if (r < 0)
+ {
+ sleep(1);
+ ++cnt;
+ }
+ } while (r < 0 && cnt <= MAX_CNT);
+ SM_TEST(r >= 0);
+ if (r < 0)
+ return r;
+
+ }
+ if (owner)
+ return sm_sem_stop(semid);
+ return 0;
+}
+
+int
+main(argc, argv)
+ int argc;
+ char *argv[];
+{
+ bool interactive = false;
+ bool owner = false;
+ int ch;
+ int r = 0;
+
+# define OPTIONS "io"
+ while ((ch = getopt(argc, argv, OPTIONS)) != -1)
+ {
+ switch ((char) ch)
+ {
+ case 'i':
+ interactive = true;
+ break;
+
+ case 'o':
+ owner = true;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if (interactive)
+ r = seminter(owner);
+ else
+ {
+ pid_t pid;
+
+ printf("This test takes about 8 seconds.\n");
+ printf("If it takes longer than 30 second, please interrupt it\n");
+ printf("and compile again without semaphore support, i.e.,");
+ printf("-DSM_CONF_SEM=0\n");
+ if ((pid = fork()) < 0)
+ {
+ perror("fork failed\n");
+ return -1;
+ }
+
+ sm_test_begin(argc, argv, "test semaphores");
+ if (pid == 0)
+ {
+ /* give the parent the chance to setup data */
+ sleep(1);
+ r = semtest(false);
+ }
+ else
+ {
+ r = semtest(true);
+ }
+ SM_TEST(r == 0);
+ return sm_test_end();
+ }
+ return r;
+}
+#else /* SM_CONF_SEM */
+int
+main(argc, argv)
+ int argc;
+ char *argv[];
+{
+ printf("No support for semaphores configured on this machine\n");
+ return 0;
+}
+#endif /* SM_CONF_SEM */
diff --git a/usr/src/cmd/sendmail/libsm/t-shm.c b/usr/src/cmd/sendmail/libsm/t-shm.c
new file mode 100644
index 0000000000..e44addd596
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/t-shm.c
@@ -0,0 +1,278 @@
+/*
+ * Copyright (c) 2000-2002, 2004, 2005 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: t-shm.c,v 1.22 2005/01/14 02:14:10 ca Exp $")
+
+#include <stdio.h>
+
+#if SM_CONF_SHM
+# include <stdlib.h>
+# include <unistd.h>
+# include <sys/wait.h>
+
+# include <sm/heap.h>
+# include <sm/string.h>
+# include <sm/test.h>
+# include <sm/shm.h>
+
+# define SHMSIZE 1024
+# define SHM_MAX 6400000
+# define T_SHMKEY 21
+
+
+/*
+** SHMINTER -- interactive testing of shared memory
+**
+** Parameters:
+** owner -- create segment.
+**
+** Returns:
+** 0 on success
+** < 0 on failure.
+*/
+
+int shminter __P((bool));
+
+int
+shminter(owner)
+ bool owner;
+{
+ int *shm, shmid;
+ int i, t;
+
+ shm = (int *) sm_shmstart(T_SHMKEY, SHMSIZE, 0, &shmid, owner);
+ if (shm == (int *) 0)
+ {
+ perror("shminit failed");
+ return -1;
+ }
+
+ while ((t = getchar()) != EOF)
+ {
+ switch (t)
+ {
+ case 'c':
+ *shm = 0;
+ break;
+ case 'i':
+ ++*shm;
+ break;
+ case 'd':
+ --*shm;
+ break;
+ case 's':
+ sleep(1);
+ break;
+ case 'l':
+ t = *shm;
+ for (i = 0; i < SHM_MAX; i++)
+ {
+ ++*shm;
+ }
+ if (*shm != SHM_MAX + t)
+ fprintf(stderr, "error: %d != %d\n",
+ *shm, SHM_MAX + t);
+ break;
+ case 'v':
+ printf("shmval: %d\n", *shm);
+ break;
+ case 'S':
+ i = sm_shmsetowner(shmid, getuid(), getgid(), 0644);
+ printf("sm_shmsetowner=%d\n", i);
+ break;
+ }
+ }
+ return sm_shmstop((void *) shm, shmid, owner);
+}
+
+
+/*
+** SHMBIG -- testing of shared memory
+**
+** Parameters:
+** owner -- create segment.
+** size -- size of segment.
+**
+** Returns:
+** 0 on success
+** < 0 on failure.
+*/
+
+int shmbig __P((bool, int));
+
+int
+shmbig(owner, size)
+ bool owner;
+ int size;
+{
+ int *shm, shmid;
+ int i;
+
+ shm = (int *) sm_shmstart(T_SHMKEY, size, 0, &shmid, owner);
+ if (shm == (int *) 0)
+ {
+ perror("shminit failed");
+ return -1;
+ }
+
+ for (i = 0; i < size / sizeof(int); i++)
+ shm[i] = i;
+ for (i = 0; i < size / sizeof(int); i++)
+ {
+ if (shm[i] != i)
+ {
+ fprintf(stderr, "failed at %d: %d", i, shm[i]);
+ }
+ }
+
+ return sm_shmstop((void *) shm, shmid, owner);
+}
+
+
+/*
+** SHMTEST -- test of shared memory
+**
+** Parameters:
+** owner -- create segment.
+**
+** Returns:
+** 0 on success
+** < 0 on failure.
+*/
+
+# define MAX_CNT 10
+
+int shmtest __P((int));
+
+int
+shmtest(owner)
+ int owner;
+{
+ int *shm, shmid;
+ int cnt = 0;
+
+ shm = (int *) sm_shmstart(T_SHMKEY, SHMSIZE, 0, &shmid, owner);
+ if (shm == (int *) 0)
+ {
+ perror("shminit failed");
+ return -1;
+ }
+
+ if (owner)
+ {
+ int r;
+
+ r = sm_shmsetowner(shmid, getuid(), getgid(), 0660);
+ SM_TEST(r == 0);
+ *shm = 1;
+ while (*shm == 1 && cnt++ < MAX_CNT)
+ sleep(1);
+ SM_TEST(cnt <= MAX_CNT);
+
+ /* release and re-acquire the segment */
+ r = sm_shmstop((void *) shm, shmid, owner);
+ SM_TEST(r == 0);
+ shm = (int *) sm_shmstart(T_SHMKEY, SHMSIZE, 0, &shmid, owner);
+ SM_TEST(shm != (int *) 0);
+ }
+ else
+ {
+ while (*shm != 1 && cnt++ < MAX_CNT)
+ sleep(1);
+ SM_TEST(cnt <= MAX_CNT);
+ *shm = 2;
+
+ /* wait a momemt so the segment is still in use */
+ sleep(2);
+ }
+ return sm_shmstop((void *) shm, shmid, owner);
+}
+
+int
+main(argc, argv)
+ int argc;
+ char *argv[];
+{
+ bool interactive = false;
+ bool owner = false;
+ int big = -1;
+ int ch;
+ int r = 0;
+ int status;
+ extern char *optarg;
+
+# define OPTIONS "b:io"
+ while ((ch = getopt(argc, argv, OPTIONS)) != -1)
+ {
+ switch ((char) ch)
+ {
+ case 'b':
+ big = atoi(optarg);
+ break;
+
+ case 'i':
+ interactive = true;
+ break;
+
+ case 'o':
+ owner = true;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if (interactive)
+ r = shminter(owner);
+ else if (big > 0)
+ r = shmbig(true, big);
+ else
+ {
+ pid_t pid;
+ extern int SmTestNumErrors;
+
+ if ((pid = fork()) < 0)
+ {
+ perror("fork failed\n");
+ return -1;
+ }
+
+ sm_test_begin(argc, argv, "test shared memory");
+ if (pid == 0)
+ {
+ /* give the parent the chance to setup data */
+ sleep(1);
+ r = shmtest(false);
+ }
+ else
+ {
+ r = shmtest(true);
+ (void) wait(&status);
+ }
+ SM_TEST(r == 0);
+ if (SmTestNumErrors > 0)
+ printf("add -DSM_CONF_SHM=0 to confENVDEF in devtools/Site/site.config.m4\nand start over.\n");
+ return sm_test_end();
+ }
+ return r;
+}
+#else /* SM_CONF_SHM */
+int
+main(argc, argv)
+ int argc;
+ char *argv[];
+{
+ printf("No support for shared memory configured on this machine\n");
+ return 0;
+}
+#endif /* SM_CONF_SHM */
diff --git a/usr/src/cmd/sendmail/libsm/t-smstdio.c b/usr/src/cmd/sendmail/libsm/t-smstdio.c
new file mode 100644
index 0000000000..6a555cdf57
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/t-smstdio.c
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_IDSTR(id, "@(#)$Id: t-smstdio.c,v 1.9 2001/03/21 18:30:41 ca Exp $")
+
+#include <sm/io.h>
+#include <sm/string.h>
+#include <sm/test.h>
+
+int
+main(argc, argv)
+ int argc;
+ char **argv;
+{
+ FILE *stream;
+ SM_FILE_T *fp;
+ char buf[128];
+ size_t n;
+ static char testmsg[] = "hello, world\n";
+
+ sm_test_begin(argc, argv,
+ "test sm_io_stdioopen, smiostdin, smiostdout");
+
+ stream = fopen("t-smstdio.1", "w");
+ SM_TEST(stream != NULL);
+
+ fp = sm_io_stdioopen(stream, "w");
+ SM_TEST(fp != NULL);
+
+ (void) sm_io_fprintf(fp, SM_TIME_DEFAULT, "%s", testmsg);
+ sm_io_close(fp, SM_TIME_DEFAULT);
+
+#if 0
+ /*
+ ** stream should now be closed. This is a tricky way to test
+ ** if it is still open. Alas, it core dumps on Linux.
+ */
+
+ fprintf(stream, "oops! stream is still open!\n");
+ fclose(stream);
+#endif
+
+ stream = fopen("t-smstdio.1", "r");
+ SM_TEST(stream != NULL);
+
+ fp = sm_io_stdioopen(stream, "r");
+ SM_TEST(fp != NULL);
+
+ n = sm_io_read(fp, SM_TIME_DEFAULT, buf, sizeof(buf));
+ if (SM_TEST(n == strlen(testmsg)))
+ {
+ buf[n] = '\0';
+ SM_TEST(strcmp(buf, testmsg) == 0);
+ }
+
+#if 0
+
+ /*
+ ** Copy smiostdin to smiostdout
+ ** gotta think some more about how to test smiostdin and smiostdout
+ */
+
+ while ((c = sm_io_getc(smiostdin)) != SM_IO_EOF)
+ sm_io_putc(smiostdout, c);
+#endif
+
+ return sm_test_end();
+}
diff --git a/usr/src/cmd/sendmail/libsm/t-string.c b/usr/src/cmd/sendmail/libsm/t-string.c
new file mode 100644
index 0000000000..30cef8d236
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/t-string.c
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_IDSTR(id, "@(#)$Id: t-string.c,v 1.9 2001/01/26 03:28:43 ca Exp $")
+
+#include <sm/exc.h>
+#include <sm/io.h>
+#include <sm/string.h>
+#include <sm/test.h>
+
+int
+main(argc, argv)
+ int argc;
+ char **argv;
+{
+ char *s;
+ char buf[4096];
+ char foo[4];
+ char *r;
+ int n;
+
+ sm_test_begin(argc, argv, "test string utilities");
+
+ s = sm_stringf_x("%.3s%03d", "foobar", 42);
+ r = "foo042";
+ SM_TEST(strcmp(s, r) == 0);
+
+ s = sm_stringf_x("+%*x+", 2000, 0xCAFE);
+ sm_snprintf(buf, 4096, "+%*x+", 2000, 0xCAFE);
+ SM_TEST(strcmp(s, buf) == 0);
+
+ foo[3] = 1;
+ n = sm_snprintf(foo, sizeof(foo), "foobar%dbaz", 42);
+ SM_TEST(n == 11);
+ r = "foo";
+ SM_TEST(strcmp(foo, r) == 0);
+
+ return sm_test_end();
+}
diff --git a/usr/src/cmd/sendmail/libsm/t-strio.c b/usr/src/cmd/sendmail/libsm/t-strio.c
new file mode 100644
index 0000000000..841de427c5
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/t-strio.c
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_IDSTR(id, "@(#)$Id: t-strio.c,v 1.9 2001/03/03 04:00:53 ca Exp $")
+#include <sm/string.h>
+#include <sm/io.h>
+#include <sm/test.h>
+
+int
+main(argc, argv)
+ int argc;
+ char *argv[];
+{
+ char buf[20];
+ char *r;
+ SM_FILE_T f;
+
+ sm_test_begin(argc, argv, "test strio");
+ (void) memset(buf, '.', 20);
+ sm_strio_init(&f, buf, 10);
+ (void) sm_io_fprintf(&f, SM_TIME_DEFAULT, "foobarbazoom");
+ sm_io_flush(&f, SM_TIME_DEFAULT);
+ r = "foobarbaz";
+ SM_TEST(strcmp(buf, r) == 0);
+ return sm_test_end();
+}
diff --git a/usr/src/cmd/sendmail/libsm/t-strl.c b/usr/src/cmd/sendmail/libsm/t-strl.c
new file mode 100644
index 0000000000..dbafbb9bcd
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/t-strl.c
@@ -0,0 +1,138 @@
+/*
+ * Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_IDSTR(id, "@(#)$Id: t-strl.c,v 1.13 2001/08/27 23:00:05 gshapiro Exp $")
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <sm/heap.h>
+#include <sm/string.h>
+#include <sm/test.h>
+
+#define MAXL 16
+#define N 5
+#define SIZE 128
+
+int
+main(argc, argv)
+ int argc;
+ char *argv[];
+{
+ char *s1, *s2, *s3;
+ int one, two, k;
+ char src1[N][SIZE], dst1[SIZE], dst2[SIZE];
+ char *r;
+
+ sm_test_begin(argc, argv, "test strl* string functions");
+ s1 = "abc";
+ s2 = "123";
+ s3 = sm_malloc_x(MAXL);
+
+ SM_TEST(sm_strlcpy(s3, s1, 4) == 3);
+ SM_TEST(strcmp(s1, s3) == 0);
+
+ SM_TEST(sm_strlcat(s3, s2, 8) == 6);
+ r ="abc123";
+ SM_TEST(strcmp(s3, r) == 0);
+
+ SM_TEST(sm_strlcpy(s3, s1, 2) == 3);
+ r = "a";
+ SM_TEST(strcmp(s3, r) == 0);
+
+ SM_TEST(sm_strlcat(s3, s2, 3) == 4);
+ r = "a1";
+ SM_TEST(strcmp(s3, r) == 0);
+
+ SM_TEST(sm_strlcpy(s3, s1, 4) == 3);
+ r = ":";
+ SM_TEST(sm_strlcat2(s3, r, s2, MAXL) == 7);
+ r = "abc:123";
+ SM_TEST(strcmp(s3, r) == 0);
+
+ SM_TEST(sm_strlcpy(s3, s1, 4) == 3);
+ r = ":";
+ SM_TEST(sm_strlcat2(s3, r, s2, 6) == 7);
+ r = "abc:1";
+ SM_TEST(strcmp(s3, r) == 0);
+
+ SM_TEST(sm_strlcpy(s3, s1, 4) == 3);
+ r = ":";
+ SM_TEST(sm_strlcat2(s3, r, s2, 2) == 7);
+ r = "abc";
+ SM_TEST(strcmp(s3, r) == 0);
+
+ SM_TEST(sm_strlcpy(s3, s1, 4) == 3);
+ r = ":";
+ SM_TEST(sm_strlcat2(s3, r, s2, 4) == 7);
+ r = "abc";
+ SM_TEST(strcmp(s3, r) == 0);
+
+ SM_TEST(sm_strlcpy(s3, s1, 4) == 3);
+ r = ":";
+ SM_TEST(sm_strlcat2(s3, r, s2, 5) == 7);
+ r = "abc:";
+ SM_TEST(strcmp(s3, r) == 0);
+
+ SM_TEST(sm_strlcpy(s3, s1, 4) == 3);
+ r = ":";
+ SM_TEST(sm_strlcat2(s3, r, s2, 6) == 7);
+ r = "abc:1";
+ SM_TEST(strcmp(s3, r) == 0);
+
+ for (k = 0; k < N; k++)
+ {
+ (void) sm_strlcpy(src1[k], "abcdef", sizeof src1);
+ }
+
+ one = sm_strlcpyn(dst1, sizeof dst1, 3, src1[0], "/", src1[1]);
+ two = sm_snprintf(dst2, sizeof dst2, "%s/%s", src1[0], src1[1]);
+ SM_TEST(one == two);
+ SM_TEST(strcmp(dst1, dst2) == 0);
+ one = sm_strlcpyn(dst1, 10, 3, src1[0], "/", src1[1]);
+ two = sm_snprintf(dst2, 10, "%s/%s", src1[0], src1[1]);
+ SM_TEST(one == two);
+ SM_TEST(strcmp(dst1, dst2) == 0);
+ one = sm_strlcpyn(dst1, 5, 3, src1[0], "/", src1[1]);
+ two = sm_snprintf(dst2, 5, "%s/%s", src1[0], src1[1]);
+ SM_TEST(one == two);
+ SM_TEST(strcmp(dst1, dst2) == 0);
+ one = sm_strlcpyn(dst1, 0, 3, src1[0], "/", src1[1]);
+ two = sm_snprintf(dst2, 0, "%s/%s", src1[0], src1[1]);
+ SM_TEST(one == two);
+ SM_TEST(strcmp(dst1, dst2) == 0);
+ one = sm_strlcpyn(dst1, sizeof dst1, 5, src1[0], "/", src1[1], "/", src1[2]);
+ two = sm_snprintf(dst2, sizeof dst2, "%s/%s/%s", src1[0], src1[1], src1[2]);
+ SM_TEST(one == two);
+ SM_TEST(strcmp(dst1, dst2) == 0);
+ one = sm_strlcpyn(dst1, 15, 5, src1[0], "/", src1[1], "/", src1[2]);
+ two = sm_snprintf(dst2, 15, "%s/%s/%s", src1[0], src1[1], src1[2]);
+ SM_TEST(one == two);
+ SM_TEST(strcmp(dst1, dst2) == 0);
+ one = sm_strlcpyn(dst1, 20, 5, src1[0], "/", src1[1], "/", src1[2]);
+ two = sm_snprintf(dst2, 20, "%s/%s/%s", src1[0], src1[1], src1[2]);
+ SM_TEST(one == two);
+ SM_TEST(strcmp(dst1, dst2) == 0);
+
+ one = sm_strlcpyn(dst1, sizeof dst1, 0);
+ SM_TEST(one == 0);
+ r = "";
+ SM_TEST(strcmp(dst1, r) == 0);
+ one = sm_strlcpyn(dst1, 20, 1, src1[0]);
+ two = sm_snprintf(dst2, 20, "%s", src1[0]);
+ SM_TEST(one == two);
+ one = sm_strlcpyn(dst1, 2, 1, src1[0]);
+ two = sm_snprintf(dst2, 2, "%s", src1[0]);
+ SM_TEST(one == two);
+
+ return sm_test_end();
+}
diff --git a/usr/src/cmd/sendmail/libsm/t-strrevcmp.c b/usr/src/cmd/sendmail/libsm/t-strrevcmp.c
new file mode 100644
index 0000000000..7ce06d5e0f
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/t-strrevcmp.c
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_IDSTR(id, "@(#)$Id: t-strrevcmp.c,v 1.1 2001/07/16 21:35:28 ca Exp $")
+
+#include <sm/exc.h>
+#include <sm/io.h>
+#include <sm/string.h>
+#include <sm/test.h>
+
+int
+main(argc, argv)
+ int argc;
+ char **argv;
+{
+ char *s1;
+ char *s2;
+
+ sm_test_begin(argc, argv, "test string compare");
+
+ s1 = "equal";
+ s2 = "equal";
+ SM_TEST(sm_strrevcmp(s1, s2) == 0);
+
+ s1 = "equal";
+ s2 = "qual";
+ SM_TEST(sm_strrevcmp(s1, s2) > 0);
+
+ s1 = "qual";
+ s2 = "equal";
+ SM_TEST(sm_strrevcmp(s1, s2) < 0);
+
+ s1 = "Equal";
+ s2 = "equal";
+ SM_TEST(sm_strrevcmp(s1, s2) < 0);
+
+ s1 = "Equal";
+ s2 = "equal";
+ SM_TEST(sm_strrevcasecmp(s1, s2) == 0);
+
+ s1 = "Equal";
+ s2 = "eQuaL";
+ SM_TEST(sm_strrevcasecmp(s1, s2) == 0);
+
+ return sm_test_end();
+}
diff --git a/usr/src/cmd/sendmail/libsm/test.c b/usr/src/cmd/sendmail/libsm/test.c
new file mode 100644
index 0000000000..46b3b2bdd1
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/test.c
@@ -0,0 +1,157 @@
+/*
+ * Copyright (c) 2000-2002 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_IDSTR(Id, "@(#)$Id: test.c,v 1.16 2002/01/08 17:54:40 ca Exp $")
+
+/*
+** Abstractions for writing libsm test programs.
+*/
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <sm/debug.h>
+#include <sm/test.h>
+
+extern char *optarg;
+extern int optind;
+extern int optopt;
+extern int opterr;
+
+int SmTestIndex;
+int SmTestNumErrors;
+bool SmTestVerbose;
+
+static char Help[] = "\
+%s [-h] [-d debugging] [-v]\n\
+\n\
+%s\n\
+\n\
+-h Display this help information.\n\
+-d debugging Set debug activation levels.\n\
+-v Verbose output.\n\
+";
+
+static char Usage[] = "\
+Usage: %s [-h] [-v]\n\
+Use %s -h for help.\n\
+";
+
+/*
+** SM_TEST_BEGIN -- initialize test system.
+**
+** Parameters:
+** argc -- argument counter.
+** argv -- argument vector.
+** testname -- description of tests.
+**
+** Results:
+** none.
+*/
+
+void
+sm_test_begin(argc, argv, testname)
+ int argc;
+ char **argv;
+ char *testname;
+{
+ int c;
+
+ SmTestIndex = 0;
+ SmTestNumErrors = 0;
+ SmTestVerbose = false;
+ opterr = 0;
+
+ while ((c = getopt(argc, argv, "vhd:")) != -1)
+ {
+ switch (c)
+ {
+ case 'v':
+ SmTestVerbose = true;
+ break;
+ case 'd':
+ sm_debug_addsettings_x(optarg);
+ break;
+ case 'h':
+ (void) fprintf(stdout, Help, argv[0], testname);
+ exit(0);
+ default:
+ (void) fprintf(stderr,
+ "Unknown command line option -%c\n",
+ optopt);
+ (void) fprintf(stderr, Usage, argv[0], argv[0]);
+ exit(1);
+ }
+ }
+}
+
+/*
+** SM_TEST -- single test.
+**
+** Parameters:
+** success -- did test succeeed?
+** expr -- expression that has been evaluated.
+** filename -- guess...
+** lineno -- line number.
+**
+** Results:
+** value of success.
+*/
+
+bool
+sm_test(success, expr, filename, lineno)
+ bool success;
+ char *expr;
+ char *filename;
+ int lineno;
+{
+ ++SmTestIndex;
+ if (SmTestVerbose)
+ (void) fprintf(stderr, "%d..", SmTestIndex);
+ if (!success)
+ {
+ ++SmTestNumErrors;
+ if (!SmTestVerbose)
+ (void) fprintf(stderr, "%d..", SmTestIndex);
+ (void) fprintf(stderr, "bad! %s:%d %s\n", filename, lineno,
+ expr);
+ }
+ else
+ {
+ if (SmTestVerbose)
+ (void) fprintf(stderr, "ok\n");
+ }
+ return success;
+}
+
+/*
+** SM_TEST_END -- end of test system.
+**
+** Parameters:
+** none.
+**
+** Results:
+** number of errors.
+*/
+
+int
+sm_test_end()
+{
+ (void) fprintf(stderr, "%d of %d tests completed successfully\n",
+ SmTestIndex - SmTestNumErrors, SmTestIndex);
+ if (SmTestNumErrors != 0)
+ (void) fprintf(stderr, "*** %d error%s in test! ***\n",
+ SmTestNumErrors,
+ SmTestNumErrors > 1 ? "s" : "");
+
+ return SmTestNumErrors;
+}
diff --git a/usr/src/cmd/sendmail/libsm/ungetc.c b/usr/src/cmd/sendmail/libsm/ungetc.c
new file mode 100644
index 0000000000..0b4b8c5dd9
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/ungetc.c
@@ -0,0 +1,183 @@
+/*
+ * Copyright (c) 2000-2001, 2004 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_IDSTR(id, "@(#)$Id: ungetc.c,v 1.29 2004/08/03 20:54:49 ca Exp $")
+
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <sys/time.h>
+#include <errno.h>
+#include <sm/io.h>
+#include <sm/heap.h>
+#include <sm/assert.h>
+#include <sm/conf.h>
+#include "local.h"
+
+static void sm_submore_x __P((SM_FILE_T *));
+
+/*
+** SM_SUBMORE_X -- expand ungetc buffer
+**
+** Expand the ungetc buffer `in place'. That is, adjust fp->f_p when
+** the buffer moves, so that it points the same distance from the end,
+** and move the bytes in the buffer around as necessary so that they
+** are all at the end (stack-style).
+**
+** Parameters:
+** fp -- the file pointer
+**
+** Results:
+** none.
+**
+** Exceptions:
+** F:sm_heap -- out of memory
+*/
+
+static void
+sm_submore_x(fp)
+ SM_FILE_T *fp;
+{
+ register int i;
+ register unsigned char *p;
+
+ if (fp->f_ub.smb_base == fp->f_ubuf)
+ {
+ /* Get a buffer; f_ubuf is fixed size. */
+ p = sm_malloc_x((size_t) SM_IO_BUFSIZ);
+ fp->f_ub.smb_base = p;
+ fp->f_ub.smb_size = SM_IO_BUFSIZ;
+ p += SM_IO_BUFSIZ - sizeof(fp->f_ubuf);
+ for (i = sizeof(fp->f_ubuf); --i >= 0;)
+ p[i] = fp->f_ubuf[i];
+ fp->f_p = p;
+ return;
+ }
+ i = fp->f_ub.smb_size;
+ p = sm_realloc_x(fp->f_ub.smb_base, i << 1);
+
+ /* no overlap (hence can use memcpy) because we doubled the size */
+ (void) memcpy((void *) (p + i), (void *) p, (size_t) i);
+ fp->f_p = p + i;
+ fp->f_ub.smb_base = p;
+ fp->f_ub.smb_size = i << 1;
+}
+
+/*
+** SM_IO_UNGETC -- place a character back into the buffer just read
+**
+** Parameters:
+** fp -- the file pointer affected
+** timeout -- time to complete ungetc
+** c -- the character to place back
+**
+** Results:
+** On success, returns value of character placed back, 0-255.
+** Returns SM_IO_EOF if c == SM_IO_EOF or if last operation
+** was a write and flush failed.
+**
+** Exceptions:
+** F:sm_heap -- out of memory
+*/
+
+int
+sm_io_ungetc(fp, timeout, c)
+ register SM_FILE_T *fp;
+ int timeout;
+ int c;
+{
+ SM_REQUIRE_ISA(fp, SmFileMagic);
+ if (c == SM_IO_EOF)
+ return SM_IO_EOF;
+ if (timeout == SM_TIME_IMMEDIATE)
+ {
+ /*
+ ** Ungetting the buffer will take time and we are wanted to
+ ** return immediately. So...
+ */
+
+ errno = EAGAIN;
+ return SM_IO_EOF;
+ }
+
+ if (!Sm_IO_DidInit)
+ sm_init();
+ if ((fp->f_flags & SMRD) == 0)
+ {
+ /*
+ ** Not already reading: no good unless reading-and-writing.
+ ** Otherwise, flush any current write stuff.
+ */
+
+ if ((fp->f_flags & SMRW) == 0)
+ return SM_IO_EOF;
+ if (fp->f_flags & SMWR)
+ {
+ if (sm_flush(fp, &timeout))
+ return SM_IO_EOF;
+ fp->f_flags &= ~SMWR;
+ fp->f_w = 0;
+ fp->f_lbfsize = 0;
+ }
+ fp->f_flags |= SMRD;
+ }
+ c = (unsigned char) c;
+
+ /*
+ ** If we are in the middle of ungetc'ing, just continue.
+ ** This may require expanding the current ungetc buffer.
+ */
+
+ if (HASUB(fp))
+ {
+ if (fp->f_r >= fp->f_ub.smb_size)
+ sm_submore_x(fp);
+ *--fp->f_p = c;
+ fp->f_r++;
+ return c;
+ }
+ fp->f_flags &= ~SMFEOF;
+
+ /*
+ ** If we can handle this by simply backing up, do so,
+ ** but never replace the original character.
+ ** (This makes sscanf() work when scanning `const' data.)
+ */
+
+ if (fp->f_bf.smb_base != NULL && fp->f_p > fp->f_bf.smb_base &&
+ fp->f_p[-1] == c)
+ {
+ fp->f_p--;
+ fp->f_r++;
+ return c;
+ }
+
+ /*
+ ** Create an ungetc buffer.
+ ** Initially, we will use the `reserve' buffer.
+ */
+
+ fp->f_ur = fp->f_r;
+ fp->f_up = fp->f_p;
+ fp->f_ub.smb_base = fp->f_ubuf;
+ fp->f_ub.smb_size = sizeof(fp->f_ubuf);
+ fp->f_ubuf[sizeof(fp->f_ubuf) - 1] = c;
+ fp->f_p = &fp->f_ubuf[sizeof(fp->f_ubuf) - 1];
+ fp->f_r = 1;
+
+ return c;
+}
diff --git a/usr/src/cmd/sendmail/libsm/vasprintf.c b/usr/src/cmd/sendmail/libsm/vasprintf.c
new file mode 100644
index 0000000000..d8a173ce13
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/vasprintf.c
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+/*
+ * Copyright (c) 1997 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND TODD C. MILLER DISCLAIMS ALL
+ * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL TODD C. MILLER BE LIABLE
+ * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: vasprintf.c,v 1.26.2.1 2003/06/03 02:14:09 ca Exp $")
+#include <stdlib.h>
+#include <errno.h>
+#include <sm/io.h>
+#include <sm/heap.h>
+#include "local.h"
+
+/*
+** SM_VASPRINTF -- printf to a dynamically allocated string
+**
+** Write 'printf' output to a dynamically allocated string
+** buffer which is returned to the caller.
+**
+** Parameters:
+** str -- *str receives a pointer to the allocated string
+** fmt -- format directives for printing
+** ap -- variable argument list
+**
+** Results:
+** On failure, set *str to NULL, set errno, and return -1.
+**
+** On success, set *str to a pointer to a nul-terminated
+** string buffer containing printf output, and return the
+** length of the string (not counting the nul).
+*/
+
+#define SM_VA_BUFSIZE 128
+
+int
+sm_vasprintf(str, fmt, ap)
+ char **str;
+ const char *fmt;
+ SM_VA_LOCAL_DECL
+{
+ int ret;
+ SM_FILE_T fake;
+ unsigned char *base;
+
+ fake.sm_magic = SmFileMagic;
+ fake.f_timeout = SM_TIME_FOREVER;
+ fake.f_timeoutstate = SM_TIME_BLOCK;
+ fake.f_file = -1;
+ fake.f_flags = SMWR | SMSTR | SMALC;
+ fake.f_bf.smb_base = fake.f_p = (unsigned char *)sm_malloc(SM_VA_BUFSIZE);
+ if (fake.f_bf.smb_base == NULL)
+ goto err2;
+ fake.f_close = NULL;
+ fake.f_open = NULL;
+ fake.f_read = NULL;
+ fake.f_write = NULL;
+ fake.f_seek = NULL;
+ fake.f_setinfo = fake.f_getinfo = NULL;
+ fake.f_type = "sm_vasprintf:fake";
+ fake.f_bf.smb_size = fake.f_w = SM_VA_BUFSIZE - 1;
+ fake.f_timeout = SM_TIME_FOREVER;
+ ret = sm_io_vfprintf(&fake, SM_TIME_FOREVER, fmt, ap);
+ if (ret == -1)
+ goto err;
+ *fake.f_p = '\0';
+
+ /* use no more space than necessary */
+ base = (unsigned char *) sm_realloc(fake.f_bf.smb_base, ret + 1);
+ if (base == NULL)
+ goto err;
+ *str = (char *)base;
+ return ret;
+
+err:
+ if (fake.f_bf.smb_base != NULL)
+ {
+ sm_free(fake.f_bf.smb_base);
+ fake.f_bf.smb_base = NULL;
+ }
+err2:
+ *str = NULL;
+ errno = ENOMEM;
+ return -1;
+}
diff --git a/usr/src/cmd/sendmail/libsm/vfprintf.c b/usr/src/cmd/sendmail/libsm/vfprintf.c
new file mode 100644
index 0000000000..2ca7e9e58c
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/vfprintf.c
@@ -0,0 +1,1110 @@
+/*
+ * Copyright (c) 2000-2001, 2004 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1990
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_IDSTR(id, "@(#)$Id: vfprintf.c,v 1.53 2004/08/03 20:54:49 ca Exp $")
+
+/*
+** Overall:
+** Actual printing innards.
+** This code is large and complicated...
+*/
+
+#include <sys/types.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <sm/config.h>
+#include <sm/varargs.h>
+#include <sm/io.h>
+#include <sm/heap.h>
+#include <sm/conf.h>
+#include "local.h"
+#include "fvwrite.h"
+
+static int sm_bprintf __P((SM_FILE_T *, const char *, va_list));
+static void sm_find_arguments __P((const char *, va_list , va_list **));
+static void sm_grow_type_table_x __P((unsigned char **, int *));
+static int sm_print __P((SM_FILE_T *, int, struct sm_uio *));
+
+/*
+** SM_PRINT -- print/flush to the file
+**
+** Flush out all the vectors defined by the given uio,
+** then reset it so that it can be reused.
+**
+** Parameters:
+** fp -- file pointer
+** timeout -- time to complete operation (milliseconds)
+** uio -- vector list of memory locations of data for printing
+**
+** Results:
+** Success: 0 (zero)
+** Failure:
+*/
+
+static int
+sm_print(fp, timeout, uio)
+ SM_FILE_T *fp;
+ int timeout;
+ register struct sm_uio *uio;
+{
+ register int err;
+
+ if (uio->uio_resid == 0)
+ {
+ uio->uio_iovcnt = 0;
+ return 0;
+ }
+ err = sm_fvwrite(fp, timeout, uio);
+ uio->uio_resid = 0;
+ uio->uio_iovcnt = 0;
+ return err;
+}
+
+/*
+** SM_BPRINTF -- allow formating to an unbuffered file.
+**
+** Helper function for `fprintf to unbuffered unix file': creates a
+** temporary buffer (via a "fake" file pointer).
+** We only work on write-only files; this avoids
+** worries about ungetc buffers and so forth.
+**
+** Parameters:
+** fp -- the file to send the o/p to
+** fmt -- format instructions for the o/p
+** ap -- vectors of data units used for formating
+**
+** Results:
+** Failure: SM_IO_EOF and errno set
+** Success: number of data units used in the formating
+**
+** Side effects:
+** formatted o/p can be SM_IO_BUFSIZ length maximum
+*/
+
+static int
+sm_bprintf(fp, fmt, ap)
+ SM_FILE_T *fp;
+ const char *fmt;
+ SM_VA_LOCAL_DECL
+{
+ int ret;
+ SM_FILE_T fake;
+ unsigned char buf[SM_IO_BUFSIZ];
+ extern const char SmFileMagic[];
+
+ /* copy the important variables */
+ fake.sm_magic = SmFileMagic;
+ fake.f_timeout = SM_TIME_FOREVER;
+ fake.f_timeoutstate = SM_TIME_BLOCK;
+ fake.f_flags = fp->f_flags & ~SMNBF;
+ fake.f_file = fp->f_file;
+ fake.f_cookie = fp->f_cookie;
+ fake.f_write = fp->f_write;
+ fake.f_close = NULL;
+ fake.f_open = NULL;
+ fake.f_read = NULL;
+ fake.f_seek = NULL;
+ fake.f_setinfo = fake.f_getinfo = NULL;
+ fake.f_type = "sm_bprintf:fake";
+
+ /* set up the buffer */
+ fake.f_bf.smb_base = fake.f_p = buf;
+ fake.f_bf.smb_size = fake.f_w = sizeof(buf);
+ fake.f_lbfsize = 0; /* not actually used, but Just In Case */
+
+ /* do the work, then copy any error status */
+ ret = sm_io_vfprintf(&fake, SM_TIME_FOREVER, fmt, ap);
+ if (ret >= 0 && sm_io_flush(&fake, SM_TIME_FOREVER))
+ ret = SM_IO_EOF; /* errno set by sm_io_flush */
+ if (fake.f_flags & SMERR)
+ fp->f_flags |= SMERR;
+ return ret;
+}
+
+
+#define BUF 40
+
+#define STATIC_ARG_TBL_SIZE 8 /* Size of static argument table. */
+
+
+/* Macros for converting digits to letters and vice versa */
+#define to_digit(c) ((c) - '0')
+#define is_digit(c) ((unsigned) to_digit(c) <= 9)
+#define to_char(n) ((char) (n) + '0')
+
+/* Flags used during conversion. */
+#define ALT 0x001 /* alternate form */
+#define HEXPREFIX 0x002 /* add 0x or 0X prefix */
+#define LADJUST 0x004 /* left adjustment */
+#define LONGINT 0x010 /* long integer */
+#define QUADINT 0x020 /* quad integer */
+#define SHORTINT 0x040 /* short integer */
+#define ZEROPAD 0x080 /* zero (as opposed to blank) pad */
+#define FPT 0x100 /* Floating point number */
+
+/*
+** SM_IO_VPRINTF -- performs actual formating for o/p
+**
+** Parameters:
+** fp -- file pointer for o/p
+** timeout -- time to complete the print
+** fmt0 -- formating directives
+** ap -- vectors with data units for formating
+**
+** Results:
+** Success: number of data units used for formatting
+** Failure: SM_IO_EOF and sets errno
+*/
+
+int
+sm_io_vfprintf(fp, timeout, fmt0, ap)
+ SM_FILE_T *fp;
+ int timeout;
+ const char *fmt0;
+ SM_VA_LOCAL_DECL
+{
+ register char *fmt; /* format string */
+ register int ch; /* character from fmt */
+ register int n, m, n2; /* handy integers (short term usage) */
+ register char *cp; /* handy char pointer (short term usage) */
+ register struct sm_iov *iovp;/* for PRINT macro */
+ register int flags; /* flags as above */
+ int ret; /* return value accumulator */
+ int width; /* width from format (%8d), or 0 */
+ int prec; /* precision from format (%.3d), or -1 */
+ char sign; /* sign prefix (' ', '+', '-', or \0) */
+ wchar_t wc;
+ ULONGLONG_T _uquad; /* integer arguments %[diouxX] */
+ enum { OCT, DEC, HEX } base;/* base for [diouxX] conversion */
+ int dprec; /* a copy of prec if [diouxX], 0 otherwise */
+ int realsz; /* field size expanded by dprec */
+ int size; /* size of converted field or string */
+ char *xdigs="0123456789abcdef"; /* digits for [xX] conversion */
+#define NIOV 8
+ struct sm_uio uio; /* output information: summary */
+ struct sm_iov iov[NIOV];/* ... and individual io vectors */
+ char buf[BUF]; /* space for %c, %[diouxX], %[eEfgG] */
+ char ox[2]; /* space for 0x hex-prefix */
+ va_list *argtable; /* args, built due to positional arg */
+ va_list statargtable[STATIC_ARG_TBL_SIZE];
+ int nextarg; /* 1-based argument index */
+ va_list orgap; /* original argument pointer */
+
+ /*
+ ** Choose PADSIZE to trade efficiency vs. size. If larger printf
+ ** fields occur frequently, increase PADSIZE and make the initialisers
+ ** below longer.
+ */
+#define PADSIZE 16 /* pad chunk size */
+ static char blanks[PADSIZE] =
+ {' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '};
+ static char zeroes[PADSIZE] =
+ {'0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0'};
+
+ /*
+ ** BEWARE, these `goto error' on error, and PAD uses `n'.
+ */
+#define PRINT(ptr, len) do { \
+ iovp->iov_base = (ptr); \
+ iovp->iov_len = (len); \
+ uio.uio_resid += (len); \
+ iovp++; \
+ if (++uio.uio_iovcnt >= NIOV) \
+ { \
+ if (sm_print(fp, timeout, &uio)) \
+ goto error; \
+ iovp = iov; \
+ } \
+} while (0)
+#define PAD(howmany, with) do \
+{ \
+ if ((n = (howmany)) > 0) \
+ { \
+ while (n > PADSIZE) { \
+ PRINT(with, PADSIZE); \
+ n -= PADSIZE; \
+ } \
+ PRINT(with, n); \
+ } \
+} while (0)
+#define FLUSH() do \
+{ \
+ if (uio.uio_resid && sm_print(fp, timeout, &uio)) \
+ goto error; \
+ uio.uio_iovcnt = 0; \
+ iovp = iov; \
+} while (0)
+
+ /*
+ ** To extend shorts properly, we need both signed and unsigned
+ ** argument extraction methods.
+ */
+#define SARG() \
+ (flags&QUADINT ? SM_VA_ARG(ap, LONGLONG_T) : \
+ flags&LONGINT ? GETARG(long) : \
+ flags&SHORTINT ? (long) (short) GETARG(int) : \
+ (long) GETARG(int))
+#define UARG() \
+ (flags&QUADINT ? SM_VA_ARG(ap, ULONGLONG_T) : \
+ flags&LONGINT ? GETARG(unsigned long) : \
+ flags&SHORTINT ? (unsigned long) (unsigned short) GETARG(int) : \
+ (unsigned long) GETARG(unsigned int))
+
+ /*
+ ** Get * arguments, including the form *nn$. Preserve the nextarg
+ ** that the argument can be gotten once the type is determined.
+ */
+#define GETASTER(val) \
+ n2 = 0; \
+ cp = fmt; \
+ while (is_digit(*cp)) \
+ { \
+ n2 = 10 * n2 + to_digit(*cp); \
+ cp++; \
+ } \
+ if (*cp == '$') \
+ { \
+ int hold = nextarg; \
+ if (argtable == NULL) \
+ { \
+ argtable = statargtable; \
+ sm_find_arguments(fmt0, orgap, &argtable); \
+ } \
+ nextarg = n2; \
+ val = GETARG(int); \
+ nextarg = hold; \
+ fmt = ++cp; \
+ } \
+ else \
+ { \
+ val = GETARG(int); \
+ }
+
+/*
+** Get the argument indexed by nextarg. If the argument table is
+** built, use it to get the argument. If its not, get the next
+** argument (and arguments must be gotten sequentially).
+*/
+
+#if SM_VA_STD
+# define GETARG(type) \
+ (((argtable != NULL) ? (void) (ap = argtable[nextarg]) : (void) 0), \
+ nextarg++, SM_VA_ARG(ap, type))
+#else /* SM_VA_STD */
+# define GETARG(type) \
+ ((argtable != NULL) ? (*((type*)(argtable[nextarg++]))) : \
+ (nextarg++, SM_VA_ARG(ap, type)))
+#endif /* SM_VA_STD */
+
+ /* sorry, fprintf(read_only_file, "") returns SM_IO_EOF, not 0 */
+ if (cantwrite(fp))
+ {
+ errno = EBADF;
+ return SM_IO_EOF;
+ }
+
+ /* optimise fprintf(stderr) (and other unbuffered Unix files) */
+ if ((fp->f_flags & (SMNBF|SMWR|SMRW)) == (SMNBF|SMWR) &&
+ fp->f_file >= 0)
+ return sm_bprintf(fp, fmt0, ap);
+
+ fmt = (char *) fmt0;
+ argtable = NULL;
+ nextarg = 1;
+ SM_VA_COPY(orgap, ap);
+ uio.uio_iov = iovp = iov;
+ uio.uio_resid = 0;
+ uio.uio_iovcnt = 0;
+ ret = 0;
+
+ /* Scan the format for conversions (`%' character). */
+ for (;;)
+ {
+ cp = fmt;
+ n = 0;
+ while ((wc = *fmt) != '\0')
+ {
+ if (wc == '%')
+ {
+ n = 1;
+ break;
+ }
+ fmt++;
+ }
+ if ((m = fmt - cp) != 0)
+ {
+ PRINT(cp, m);
+ ret += m;
+ }
+ if (n <= 0)
+ goto done;
+ fmt++; /* skip over '%' */
+
+ flags = 0;
+ dprec = 0;
+ width = 0;
+ prec = -1;
+ sign = '\0';
+
+rflag: ch = *fmt++;
+reswitch: switch (ch)
+ {
+ case ' ':
+
+ /*
+ ** ``If the space and + flags both appear, the space
+ ** flag will be ignored.''
+ ** -- ANSI X3J11
+ */
+
+ if (!sign)
+ sign = ' ';
+ goto rflag;
+ case '#':
+ flags |= ALT;
+ goto rflag;
+ case '*':
+
+ /*
+ ** ``A negative field width argument is taken as a
+ ** - flag followed by a positive field width.''
+ ** -- ANSI X3J11
+ ** They don't exclude field widths read from args.
+ */
+
+ GETASTER(width);
+ if (width >= 0)
+ goto rflag;
+ width = -width;
+ /* FALLTHROUGH */
+ case '-':
+ flags |= LADJUST;
+ goto rflag;
+ case '+':
+ sign = '+';
+ goto rflag;
+ case '.':
+ if ((ch = *fmt++) == '*')
+ {
+ GETASTER(n);
+ prec = n < 0 ? -1 : n;
+ goto rflag;
+ }
+ n = 0;
+ while (is_digit(ch))
+ {
+ n = 10 * n + to_digit(ch);
+ ch = *fmt++;
+ }
+ if (ch == '$')
+ {
+ nextarg = n;
+ if (argtable == NULL)
+ {
+ argtable = statargtable;
+ sm_find_arguments(fmt0, orgap,
+ &argtable);
+ }
+ goto rflag;
+ }
+ prec = n < 0 ? -1 : n;
+ goto reswitch;
+ case '0':
+
+ /*
+ ** ``Note that 0 is taken as a flag, not as the
+ ** beginning of a field width.''
+ ** -- ANSI X3J11
+ */
+
+ flags |= ZEROPAD;
+ goto rflag;
+ case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ n = 0;
+ do
+ {
+ n = 10 * n + to_digit(ch);
+ ch = *fmt++;
+ } while (is_digit(ch));
+ if (ch == '$')
+ {
+ nextarg = n;
+ if (argtable == NULL)
+ {
+ argtable = statargtable;
+ sm_find_arguments(fmt0, orgap,
+ &argtable);
+ }
+ goto rflag;
+ }
+ width = n;
+ goto reswitch;
+ case 'h':
+ flags |= SHORTINT;
+ goto rflag;
+ case 'l':
+ if (*fmt == 'l')
+ {
+ fmt++;
+ flags |= QUADINT;
+ }
+ else
+ {
+ flags |= LONGINT;
+ }
+ goto rflag;
+ case 'q':
+ flags |= QUADINT;
+ goto rflag;
+ case 'c':
+ *(cp = buf) = GETARG(int);
+ size = 1;
+ sign = '\0';
+ break;
+ case 'D':
+ flags |= LONGINT;
+ /*FALLTHROUGH*/
+ case 'd':
+ case 'i':
+ _uquad = SARG();
+ if ((LONGLONG_T) _uquad < 0)
+ {
+ _uquad = -(LONGLONG_T) _uquad;
+ sign = '-';
+ }
+ base = DEC;
+ goto number;
+ case 'e':
+ case 'E':
+ case 'f':
+ case 'g':
+ case 'G':
+ {
+ double val;
+ char *p;
+ char fmt[16];
+ char out[150];
+ size_t len;
+
+ /*
+ ** This code implements floating point output
+ ** in the most portable manner possible,
+ ** relying only on 'sprintf' as defined by
+ ** the 1989 ANSI C standard.
+ ** We silently cap width and precision
+ ** at 120, to avoid buffer overflow.
+ */
+
+ val = GETARG(double);
+
+ p = fmt;
+ *p++ = '%';
+ if (sign)
+ *p++ = sign;
+ if (flags & ALT)
+ *p++ = '#';
+ if (flags & LADJUST)
+ *p++ = '-';
+ if (flags & ZEROPAD)
+ *p++ = '0';
+ *p++ = '*';
+ if (prec >= 0)
+ {
+ *p++ = '.';
+ *p++ = '*';
+ }
+ *p++ = ch;
+ *p = '\0';
+
+ if (width > 120)
+ width = 120;
+ if (prec > 120)
+ prec = 120;
+ if (prec >= 0)
+ sprintf(out, fmt, width, prec, val);
+ else
+ sprintf(out, fmt, width, val);
+ len = strlen(out);
+ PRINT(out, len);
+ FLUSH();
+ continue;
+ }
+ case 'n':
+ if (flags & QUADINT)
+ *GETARG(LONGLONG_T *) = ret;
+ else if (flags & LONGINT)
+ *GETARG(long *) = ret;
+ else if (flags & SHORTINT)
+ *GETARG(short *) = ret;
+ else
+ *GETARG(int *) = ret;
+ continue; /* no output */
+ case 'O':
+ flags |= LONGINT;
+ /*FALLTHROUGH*/
+ case 'o':
+ _uquad = UARG();
+ base = OCT;
+ goto nosign;
+ case 'p':
+
+ /*
+ ** ``The argument shall be a pointer to void. The
+ ** value of the pointer is converted to a sequence
+ ** of printable characters, in an implementation-
+ ** defined manner.''
+ ** -- ANSI X3J11
+ */
+
+ /* NOSTRICT */
+ {
+ union
+ {
+ void *p;
+ ULONGLONG_T ll;
+ unsigned long l;
+ unsigned i;
+ } u;
+ u.p = GETARG(void *);
+ if (sizeof(void *) == sizeof(ULONGLONG_T))
+ _uquad = u.ll;
+ else if (sizeof(void *) == sizeof(long))
+ _uquad = u.l;
+ else
+ _uquad = u.i;
+ }
+ base = HEX;
+ xdigs = "0123456789abcdef";
+ flags |= HEXPREFIX;
+ ch = 'x';
+ goto nosign;
+ case 's':
+ if ((cp = GETARG(char *)) == NULL)
+ cp = "(null)";
+ if (prec >= 0)
+ {
+ /*
+ ** can't use strlen; can only look for the
+ ** NUL in the first `prec' characters, and
+ ** strlen() will go further.
+ */
+
+ char *p = memchr(cp, 0, prec);
+
+ if (p != NULL)
+ {
+ size = p - cp;
+ if (size > prec)
+ size = prec;
+ }
+ else
+ size = prec;
+ }
+ else
+ size = strlen(cp);
+ sign = '\0';
+ break;
+ case 'U':
+ flags |= LONGINT;
+ /*FALLTHROUGH*/
+ case 'u':
+ _uquad = UARG();
+ base = DEC;
+ goto nosign;
+ case 'X':
+ xdigs = "0123456789ABCDEF";
+ goto hex;
+ case 'x':
+ xdigs = "0123456789abcdef";
+hex: _uquad = UARG();
+ base = HEX;
+ /* leading 0x/X only if non-zero */
+ if (flags & ALT && _uquad != 0)
+ flags |= HEXPREFIX;
+
+ /* unsigned conversions */
+nosign: sign = '\0';
+
+ /*
+ ** ``... diouXx conversions ... if a precision is
+ ** specified, the 0 flag will be ignored.''
+ ** -- ANSI X3J11
+ */
+
+number: if ((dprec = prec) >= 0)
+ flags &= ~ZEROPAD;
+
+ /*
+ ** ``The result of converting a zero value with an
+ ** explicit precision of zero is no characters.''
+ ** -- ANSI X3J11
+ */
+
+ cp = buf + BUF;
+ if (_uquad != 0 || prec != 0)
+ {
+ /*
+ ** Unsigned mod is hard, and unsigned mod
+ ** by a constant is easier than that by
+ ** a variable; hence this switch.
+ */
+
+ switch (base)
+ {
+ case OCT:
+ do
+ {
+ *--cp = to_char(_uquad & 7);
+ _uquad >>= 3;
+ } while (_uquad);
+ /* handle octal leading 0 */
+ if (flags & ALT && *cp != '0')
+ *--cp = '0';
+ break;
+
+ case DEC:
+ /* many numbers are 1 digit */
+ while (_uquad >= 10)
+ {
+ *--cp = to_char(_uquad % 10);
+ _uquad /= 10;
+ }
+ *--cp = to_char(_uquad);
+ break;
+
+ case HEX:
+ do
+ {
+ *--cp = xdigs[_uquad & 15];
+ _uquad >>= 4;
+ } while (_uquad);
+ break;
+
+ default:
+ cp = "bug in sm_io_vfprintf: bad base";
+ size = strlen(cp);
+ goto skipsize;
+ }
+ }
+ size = buf + BUF - cp;
+ skipsize:
+ break;
+ default: /* "%?" prints ?, unless ? is NUL */
+ if (ch == '\0')
+ goto done;
+ /* pretend it was %c with argument ch */
+ cp = buf;
+ *cp = ch;
+ size = 1;
+ sign = '\0';
+ break;
+ }
+
+ /*
+ ** All reasonable formats wind up here. At this point, `cp'
+ ** points to a string which (if not flags&LADJUST) should be
+ ** padded out to `width' places. If flags&ZEROPAD, it should
+ ** first be prefixed by any sign or other prefix; otherwise,
+ ** it should be blank padded before the prefix is emitted.
+ ** After any left-hand padding and prefixing, emit zeroes
+ ** required by a decimal [diouxX] precision, then print the
+ ** string proper, then emit zeroes required by any leftover
+ ** floating precision; finally, if LADJUST, pad with blanks.
+ **
+ ** Compute actual size, so we know how much to pad.
+ ** size excludes decimal prec; realsz includes it.
+ */
+
+ realsz = dprec > size ? dprec : size;
+ if (sign)
+ realsz++;
+ else if (flags & HEXPREFIX)
+ realsz+= 2;
+
+ /* right-adjusting blank padding */
+ if ((flags & (LADJUST|ZEROPAD)) == 0)
+ PAD(width - realsz, blanks);
+
+ /* prefix */
+ if (sign)
+ {
+ PRINT(&sign, 1);
+ }
+ else if (flags & HEXPREFIX)
+ {
+ ox[0] = '0';
+ ox[1] = ch;
+ PRINT(ox, 2);
+ }
+
+ /* right-adjusting zero padding */
+ if ((flags & (LADJUST|ZEROPAD)) == ZEROPAD)
+ PAD(width - realsz, zeroes);
+
+ /* leading zeroes from decimal precision */
+ PAD(dprec - size, zeroes);
+
+ /* the string or number proper */
+ PRINT(cp, size);
+ /* left-adjusting padding (always blank) */
+ if (flags & LADJUST)
+ PAD(width - realsz, blanks);
+
+ /* finally, adjust ret */
+ ret += width > realsz ? width : realsz;
+
+ FLUSH(); /* copy out the I/O vectors */
+ }
+done:
+ FLUSH();
+error:
+ if ((argtable != NULL) && (argtable != statargtable))
+ sm_free(argtable);
+ return sm_error(fp) ? SM_IO_EOF : ret;
+ /* NOTREACHED */
+}
+
+/* Type ids for argument type table. */
+#define T_UNUSED 0
+#define T_SHORT 1
+#define T_U_SHORT 2
+#define TP_SHORT 3
+#define T_INT 4
+#define T_U_INT 5
+#define TP_INT 6
+#define T_LONG 7
+#define T_U_LONG 8
+#define TP_LONG 9
+#define T_QUAD 10
+#define T_U_QUAD 11
+#define TP_QUAD 12
+#define T_DOUBLE 13
+#define TP_CHAR 15
+#define TP_VOID 16
+
+/*
+** SM_FIND_ARGUMENTS -- find all args when a positional parameter is found.
+**
+** Find all arguments when a positional parameter is encountered. Returns a
+** table, indexed by argument number, of pointers to each arguments. The
+** initial argument table should be an array of STATIC_ARG_TBL_SIZE entries.
+** It will be replaced with a malloc-ed one if it overflows.
+**
+** Parameters:
+** fmt0 -- formating directives
+** ap -- vector list of data unit for formating consumption
+** argtable -- an indexable table (returned) of 'ap'
+**
+** Results:
+** none.
+*/
+
+static void
+sm_find_arguments(fmt0, ap, argtable)
+ const char *fmt0;
+ SM_VA_LOCAL_DECL
+ va_list **argtable;
+{
+ register char *fmt; /* format string */
+ register int ch; /* character from fmt */
+ register int n, n2; /* handy integer (short term usage) */
+ register char *cp; /* handy char pointer (short term usage) */
+ register int flags; /* flags as above */
+ unsigned char *typetable; /* table of types */
+ unsigned char stattypetable[STATIC_ARG_TBL_SIZE];
+ int tablesize; /* current size of type table */
+ int tablemax; /* largest used index in table */
+ int nextarg; /* 1-based argument index */
+
+ /* Add an argument type to the table, expanding if necessary. */
+#define ADDTYPE(type) \
+ ((nextarg >= tablesize) ? \
+ (sm_grow_type_table_x(&typetable, &tablesize), 0) : 0, \
+ typetable[nextarg++] = type, \
+ (nextarg > tablemax) ? tablemax = nextarg : 0)
+
+#define ADDSARG() \
+ ((flags & LONGINT) ? ADDTYPE(T_LONG) : \
+ ((flags & SHORTINT) ? ADDTYPE(T_SHORT) : ADDTYPE(T_INT)))
+
+#define ADDUARG() \
+ ((flags & LONGINT) ? ADDTYPE(T_U_LONG) : \
+ ((flags & SHORTINT) ? ADDTYPE(T_U_SHORT) : ADDTYPE(T_U_INT)))
+
+ /* Add * arguments to the type array. */
+#define ADDASTER() \
+ n2 = 0; \
+ cp = fmt; \
+ while (is_digit(*cp)) \
+ { \
+ n2 = 10 * n2 + to_digit(*cp); \
+ cp++; \
+ } \
+ if (*cp == '$') \
+ { \
+ int hold = nextarg; \
+ nextarg = n2; \
+ ADDTYPE (T_INT); \
+ nextarg = hold; \
+ fmt = ++cp; \
+ } \
+ else \
+ { \
+ ADDTYPE (T_INT); \
+ }
+ fmt = (char *) fmt0;
+ typetable = stattypetable;
+ tablesize = STATIC_ARG_TBL_SIZE;
+ tablemax = 0;
+ nextarg = 1;
+ (void) memset(typetable, T_UNUSED, STATIC_ARG_TBL_SIZE);
+
+ /* Scan the format for conversions (`%' character). */
+ for (;;)
+ {
+ for (cp = fmt; (ch = *fmt) != '\0' && ch != '%'; fmt++)
+ /* void */;
+ if (ch == '\0')
+ goto done;
+ fmt++; /* skip over '%' */
+
+ flags = 0;
+
+rflag: ch = *fmt++;
+reswitch: switch (ch)
+ {
+ case ' ':
+ case '#':
+ goto rflag;
+ case '*':
+ ADDASTER();
+ goto rflag;
+ case '-':
+ case '+':
+ goto rflag;
+ case '.':
+ if ((ch = *fmt++) == '*')
+ {
+ ADDASTER();
+ goto rflag;
+ }
+ while (is_digit(ch))
+ {
+ ch = *fmt++;
+ }
+ goto reswitch;
+ case '0':
+ goto rflag;
+ case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ n = 0;
+ do
+ {
+ n = 10 * n + to_digit(ch);
+ ch = *fmt++;
+ } while (is_digit(ch));
+ if (ch == '$')
+ {
+ nextarg = n;
+ goto rflag;
+ }
+ goto reswitch;
+ case 'h':
+ flags |= SHORTINT;
+ goto rflag;
+ case 'l':
+ flags |= LONGINT;
+ goto rflag;
+ case 'q':
+ flags |= QUADINT;
+ goto rflag;
+ case 'c':
+ ADDTYPE(T_INT);
+ break;
+ case 'D':
+ flags |= LONGINT;
+ /*FALLTHROUGH*/
+ case 'd':
+ case 'i':
+ if (flags & QUADINT)
+ {
+ ADDTYPE(T_QUAD);
+ }
+ else
+ {
+ ADDSARG();
+ }
+ break;
+ case 'e':
+ case 'E':
+ case 'f':
+ case 'g':
+ case 'G':
+ ADDTYPE(T_DOUBLE);
+ break;
+ case 'n':
+ if (flags & QUADINT)
+ ADDTYPE(TP_QUAD);
+ else if (flags & LONGINT)
+ ADDTYPE(TP_LONG);
+ else if (flags & SHORTINT)
+ ADDTYPE(TP_SHORT);
+ else
+ ADDTYPE(TP_INT);
+ continue; /* no output */
+ case 'O':
+ flags |= LONGINT;
+ /*FALLTHROUGH*/
+ case 'o':
+ if (flags & QUADINT)
+ ADDTYPE(T_U_QUAD);
+ else
+ ADDUARG();
+ break;
+ case 'p':
+ ADDTYPE(TP_VOID);
+ break;
+ case 's':
+ ADDTYPE(TP_CHAR);
+ break;
+ case 'U':
+ flags |= LONGINT;
+ /*FALLTHROUGH*/
+ case 'u':
+ if (flags & QUADINT)
+ ADDTYPE(T_U_QUAD);
+ else
+ ADDUARG();
+ break;
+ case 'X':
+ case 'x':
+ if (flags & QUADINT)
+ ADDTYPE(T_U_QUAD);
+ else
+ ADDUARG();
+ break;
+ default: /* "%?" prints ?, unless ? is NUL */
+ if (ch == '\0')
+ goto done;
+ break;
+ }
+ }
+done:
+ /* Build the argument table. */
+ if (tablemax >= STATIC_ARG_TBL_SIZE)
+ {
+ *argtable = (va_list *)
+ sm_malloc(sizeof(va_list) * (tablemax + 1));
+ }
+
+ for (n = 1; n <= tablemax; n++)
+ {
+ SM_VA_COPY((*argtable)[n], ap);
+ switch (typetable [n])
+ {
+ case T_UNUSED:
+ (void) SM_VA_ARG(ap, int);
+ break;
+ case T_SHORT:
+ (void) SM_VA_ARG(ap, int);
+ break;
+ case T_U_SHORT:
+ (void) SM_VA_ARG(ap, int);
+ break;
+ case TP_SHORT:
+ (void) SM_VA_ARG(ap, short *);
+ break;
+ case T_INT:
+ (void) SM_VA_ARG(ap, int);
+ break;
+ case T_U_INT:
+ (void) SM_VA_ARG(ap, unsigned int);
+ break;
+ case TP_INT:
+ (void) SM_VA_ARG(ap, int *);
+ break;
+ case T_LONG:
+ (void) SM_VA_ARG(ap, long);
+ break;
+ case T_U_LONG:
+ (void) SM_VA_ARG(ap, unsigned long);
+ break;
+ case TP_LONG:
+ (void) SM_VA_ARG(ap, long *);
+ break;
+ case T_QUAD:
+ (void) SM_VA_ARG(ap, LONGLONG_T);
+ break;
+ case T_U_QUAD:
+ (void) SM_VA_ARG(ap, ULONGLONG_T);
+ break;
+ case TP_QUAD:
+ (void) SM_VA_ARG(ap, LONGLONG_T *);
+ break;
+ case T_DOUBLE:
+ (void) SM_VA_ARG(ap, double);
+ break;
+ case TP_CHAR:
+ (void) SM_VA_ARG(ap, char *);
+ break;
+ case TP_VOID:
+ (void) SM_VA_ARG(ap, void *);
+ break;
+ }
+ }
+
+ if ((typetable != NULL) && (typetable != stattypetable))
+ sm_free(typetable);
+}
+
+/*
+** SM_GROW_TYPE_TABLE -- Increase the size of the type table.
+**
+** Parameters:
+** tabletype -- type of table to grow
+** tablesize -- requested new table size
+**
+** Results:
+** Raises an exception if can't allocate memory.
+*/
+
+static void
+sm_grow_type_table_x(typetable, tablesize)
+ unsigned char **typetable;
+ int *tablesize;
+{
+ unsigned char *oldtable = *typetable;
+ int newsize = *tablesize * 2;
+
+ if (*tablesize == STATIC_ARG_TBL_SIZE)
+ {
+ *typetable = (unsigned char *) sm_malloc_x(sizeof(unsigned char)
+ * newsize);
+ (void) memmove(*typetable, oldtable, *tablesize);
+ }
+ else
+ {
+ *typetable = (unsigned char *) sm_realloc_x(typetable,
+ sizeof(unsigned char) * newsize);
+ }
+ (void) memset(&typetable [*tablesize], T_UNUSED,
+ (newsize - *tablesize));
+
+ *tablesize = newsize;
+}
diff --git a/usr/src/cmd/sendmail/libsm/vfscanf.c b/usr/src/cmd/sendmail/libsm/vfscanf.c
new file mode 100644
index 0000000000..bc0f1d004d
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/vfscanf.c
@@ -0,0 +1,877 @@
+/*
+ * Copyright (c) 2000-2001, 2004 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_IDSTR(id, "@(#)$Id: vfscanf.c,v 1.52 2004/08/03 20:56:32 ca Exp $")
+
+#include <ctype.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <setjmp.h>
+#include <sys/time.h>
+#include <sm/varargs.h>
+#include <sm/config.h>
+#include <sm/io.h>
+#include <sm/signal.h>
+#include <sm/clock.h>
+#include <sm/string.h>
+#include "local.h"
+
+#define BUF 513 /* Maximum length of numeric string. */
+
+/* Flags used during conversion. */
+#define LONG 0x01 /* l: long or double */
+#define SHORT 0x04 /* h: short */
+#define QUAD 0x08 /* q: quad (same as ll) */
+#define SUPPRESS 0x10 /* suppress assignment */
+#define POINTER 0x20 /* weird %p pointer (`fake hex') */
+#define NOSKIP 0x40 /* do not skip blanks */
+
+/*
+** The following are used in numeric conversions only:
+** SIGNOK, NDIGITS, DPTOK, and EXPOK are for floating point;
+** SIGNOK, NDIGITS, PFXOK, and NZDIGITS are for integral.
+*/
+
+#define SIGNOK 0x080 /* +/- is (still) legal */
+#define NDIGITS 0x100 /* no digits detected */
+
+#define DPTOK 0x200 /* (float) decimal point is still legal */
+#define EXPOK 0x400 /* (float) exponent (e+3, etc) still legal */
+
+#define PFXOK 0x200 /* 0x prefix is (still) legal */
+#define NZDIGITS 0x400 /* no zero digits detected */
+
+/* Conversion types. */
+#define CT_CHAR 0 /* %c conversion */
+#define CT_CCL 1 /* %[...] conversion */
+#define CT_STRING 2 /* %s conversion */
+#define CT_INT 3 /* integer, i.e., strtoll or strtoull */
+#define CT_FLOAT 4 /* floating, i.e., strtod */
+
+static void scanalrm __P((int));
+static unsigned char *sm_sccl __P((char *, unsigned char *));
+static jmp_buf ScanTimeOut;
+
+/*
+** SCANALRM -- handler when timeout activated for sm_io_vfscanf()
+**
+** Returns flow of control to where setjmp(ScanTimeOut) was set.
+**
+** Parameters:
+** sig -- unused
+**
+** Returns:
+** does not return
+**
+** Side Effects:
+** returns flow of control to setjmp(ScanTimeOut).
+**
+** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+** DOING.
+*/
+
+/* ARGSUSED0 */
+static void
+scanalrm(sig)
+ int sig;
+{
+ longjmp(ScanTimeOut, 1);
+}
+
+/*
+** SM_VFSCANF -- convert input into data units
+**
+** Parameters:
+** fp -- file pointer for input data
+** timeout -- time intvl allowed to complete (milliseconds)
+** fmt0 -- format for finding data units
+** ap -- vectors for memory location for storing data units
+**
+** Results:
+** Success: number of data units assigned
+** Failure: SM_IO_EOF
+*/
+
+int
+sm_vfscanf(fp, timeout, fmt0, ap)
+ register SM_FILE_T *fp;
+ int SM_NONVOLATILE timeout;
+ char const *fmt0;
+ va_list SM_NONVOLATILE ap;
+{
+ register unsigned char *SM_NONVOLATILE fmt = (unsigned char *) fmt0;
+ register int c; /* character from format, or conversion */
+ register size_t width; /* field width, or 0 */
+ register char *p; /* points into all kinds of strings */
+ register int n; /* handy integer */
+ register int flags; /* flags as defined above */
+ register char *p0; /* saves original value of p when necessary */
+ int nassigned; /* number of fields assigned */
+ int nread; /* number of characters consumed from fp */
+ int base; /* base argument to strtoll/strtoull */
+ ULONGLONG_T (*ccfn)(); /* conversion function (strtoll/strtoull) */
+ char ccltab[256]; /* character class table for %[...] */
+ char buf[BUF]; /* buffer for numeric conversions */
+ SM_EVENT *evt = NULL;
+
+ /* `basefix' is used to avoid `if' tests in the integer scanner */
+ static short basefix[17] =
+ { 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 };
+
+ if (timeout == SM_TIME_DEFAULT)
+ timeout = fp->f_timeout;
+ if (timeout == SM_TIME_IMMEDIATE)
+ {
+ /*
+ ** Filling the buffer will take time and we are wanted to
+ ** return immediately. So...
+ */
+
+ errno = EAGAIN;
+ return SM_IO_EOF;
+ }
+
+ if (timeout != SM_TIME_FOREVER)
+ {
+ if (setjmp(ScanTimeOut) != 0)
+ {
+ errno = EAGAIN;
+ return SM_IO_EOF;
+ }
+
+ evt = sm_seteventm(timeout, scanalrm, 0);
+ }
+
+ nassigned = 0;
+ nread = 0;
+ base = 0; /* XXX just to keep gcc happy */
+ ccfn = NULL; /* XXX just to keep gcc happy */
+ for (;;)
+ {
+ c = *fmt++;
+ if (c == 0)
+ {
+ if (evt != NULL)
+ sm_clrevent(evt); /* undo our timeout */
+ return nassigned;
+ }
+ if (isspace(c))
+ {
+ while ((fp->f_r > 0 || sm_refill(fp, SM_TIME_FOREVER)
+ == 0) &&
+ isspace(*fp->f_p))
+ nread++, fp->f_r--, fp->f_p++;
+ continue;
+ }
+ if (c != '%')
+ goto literal;
+ width = 0;
+ flags = 0;
+
+ /*
+ ** switch on the format. continue if done;
+ ** break once format type is derived.
+ */
+
+again: c = *fmt++;
+ switch (c)
+ {
+ case '%':
+literal:
+ if (fp->f_r <= 0 && sm_refill(fp, SM_TIME_FOREVER))
+ goto input_failure;
+ if (*fp->f_p != c)
+ goto match_failure;
+ fp->f_r--, fp->f_p++;
+ nread++;
+ continue;
+
+ case '*':
+ flags |= SUPPRESS;
+ goto again;
+ case 'h':
+ flags |= SHORT;
+ goto again;
+ case 'l':
+ if (*fmt == 'l')
+ {
+ fmt++;
+ flags |= QUAD;
+ }
+ else
+ {
+ flags |= LONG;
+ }
+ goto again;
+ case 'q':
+ flags |= QUAD;
+ goto again;
+
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ width = width * 10 + c - '0';
+ goto again;
+
+ /*
+ ** Conversions.
+ ** Those marked `compat' are for 4.[123]BSD compatibility.
+ **
+ ** (According to ANSI, E and X formats are supposed
+ ** to the same as e and x. Sorry about that.)
+ */
+
+ case 'D': /* compat */
+ flags |= LONG;
+ /* FALLTHROUGH */
+ case 'd':
+ c = CT_INT;
+ ccfn = (ULONGLONG_T (*)())sm_strtoll;
+ base = 10;
+ break;
+
+ case 'i':
+ c = CT_INT;
+ ccfn = (ULONGLONG_T (*)())sm_strtoll;
+ base = 0;
+ break;
+
+ case 'O': /* compat */
+ flags |= LONG;
+ /* FALLTHROUGH */
+ case 'o':
+ c = CT_INT;
+ ccfn = sm_strtoull;
+ base = 8;
+ break;
+
+ case 'u':
+ c = CT_INT;
+ ccfn = sm_strtoull;
+ base = 10;
+ break;
+
+ case 'X':
+ case 'x':
+ flags |= PFXOK; /* enable 0x prefixing */
+ c = CT_INT;
+ ccfn = sm_strtoull;
+ base = 16;
+ break;
+
+ case 'E':
+ case 'G':
+ case 'e':
+ case 'f':
+ case 'g':
+ c = CT_FLOAT;
+ break;
+
+ case 's':
+ c = CT_STRING;
+ break;
+
+ case '[':
+ fmt = sm_sccl(ccltab, fmt);
+ flags |= NOSKIP;
+ c = CT_CCL;
+ break;
+
+ case 'c':
+ flags |= NOSKIP;
+ c = CT_CHAR;
+ break;
+
+ case 'p': /* pointer format is like hex */
+ flags |= POINTER | PFXOK;
+ c = CT_INT;
+ ccfn = sm_strtoull;
+ base = 16;
+ break;
+
+ case 'n':
+ if (flags & SUPPRESS) /* ??? */
+ continue;
+ if (flags & SHORT)
+ *SM_VA_ARG(ap, short *) = nread;
+ else if (flags & LONG)
+ *SM_VA_ARG(ap, long *) = nread;
+ else
+ *SM_VA_ARG(ap, int *) = nread;
+ continue;
+
+ /* Disgusting backwards compatibility hacks. XXX */
+ case '\0': /* compat */
+ if (evt != NULL)
+ sm_clrevent(evt); /* undo our timeout */
+ return SM_IO_EOF;
+
+ default: /* compat */
+ if (isupper(c))
+ flags |= LONG;
+ c = CT_INT;
+ ccfn = (ULONGLONG_T (*)()) sm_strtoll;
+ base = 10;
+ break;
+ }
+
+ /* We have a conversion that requires input. */
+ if (fp->f_r <= 0 && sm_refill(fp, SM_TIME_FOREVER))
+ goto input_failure;
+
+ /*
+ ** Consume leading white space, except for formats
+ ** that suppress this.
+ */
+
+ if ((flags & NOSKIP) == 0)
+ {
+ while (isspace(*fp->f_p))
+ {
+ nread++;
+ if (--fp->f_r > 0)
+ fp->f_p++;
+ else if (sm_refill(fp, SM_TIME_FOREVER))
+ goto input_failure;
+ }
+ /*
+ ** Note that there is at least one character in
+ ** the buffer, so conversions that do not set NOSKIP
+ ** can no longer result in an input failure.
+ */
+ }
+
+ /* Do the conversion. */
+ switch (c)
+ {
+ case CT_CHAR:
+ /* scan arbitrary characters (sets NOSKIP) */
+ if (width == 0)
+ width = 1;
+ if (flags & SUPPRESS)
+ {
+ size_t sum = 0;
+ for (;;)
+ {
+ if ((size_t) (n = fp->f_r) < width)
+ {
+ sum += n;
+ width -= n;
+ fp->f_p += n;
+ if (sm_refill(fp,
+ SM_TIME_FOREVER))
+ {
+ if (sum == 0)
+ goto input_failure;
+ break;
+ }
+ }
+ else
+ {
+ sum += width;
+ fp->f_r -= width;
+ fp->f_p += width;
+ break;
+ }
+ }
+ nread += sum;
+ }
+ else
+ {
+ size_t r;
+
+ r = sm_io_read(fp, SM_TIME_FOREVER,
+ (void *) SM_VA_ARG(ap, char *),
+ width);
+ if (r == 0)
+ goto input_failure;
+ nread += r;
+ nassigned++;
+ }
+ break;
+
+ case CT_CCL:
+ /* scan a (nonempty) character class (sets NOSKIP) */
+ if (width == 0)
+ width = (size_t)~0; /* `infinity' */
+
+ /* take only those things in the class */
+ if (flags & SUPPRESS)
+ {
+ n = 0;
+ while (ccltab[*fp->f_p] != '\0')
+ {
+ n++, fp->f_r--, fp->f_p++;
+ if (--width == 0)
+ break;
+ if (fp->f_r <= 0 &&
+ sm_refill(fp, SM_TIME_FOREVER))
+ {
+ if (n == 0) /* XXX how? */
+ goto input_failure;
+ break;
+ }
+ }
+ if (n == 0)
+ goto match_failure;
+ }
+ else
+ {
+ p0 = p = SM_VA_ARG(ap, char *);
+ while (ccltab[*fp->f_p] != '\0')
+ {
+ fp->f_r--;
+ *p++ = *fp->f_p++;
+ if (--width == 0)
+ break;
+ if (fp->f_r <= 0 &&
+ sm_refill(fp, SM_TIME_FOREVER))
+ {
+ if (p == p0)
+ goto input_failure;
+ break;
+ }
+ }
+ n = p - p0;
+ if (n == 0)
+ goto match_failure;
+ *p = 0;
+ nassigned++;
+ }
+ nread += n;
+ break;
+
+ case CT_STRING:
+ /* like CCL, but zero-length string OK, & no NOSKIP */
+ if (width == 0)
+ width = (size_t)~0;
+ if (flags & SUPPRESS)
+ {
+ n = 0;
+ while (!isspace(*fp->f_p))
+ {
+ n++, fp->f_r--, fp->f_p++;
+ if (--width == 0)
+ break;
+ if (fp->f_r <= 0 &&
+ sm_refill(fp, SM_TIME_FOREVER))
+ break;
+ }
+ nread += n;
+ }
+ else
+ {
+ p0 = p = SM_VA_ARG(ap, char *);
+ while (!isspace(*fp->f_p))
+ {
+ fp->f_r--;
+ *p++ = *fp->f_p++;
+ if (--width == 0)
+ break;
+ if (fp->f_r <= 0 &&
+ sm_refill(fp, SM_TIME_FOREVER))
+ break;
+ }
+ *p = 0;
+ nread += p - p0;
+ nassigned++;
+ }
+ continue;
+
+ case CT_INT:
+ /* scan an integer as if by strtoll/strtoull */
+#if SM_CONF_BROKEN_SIZE_T
+ if (width == 0 || width > sizeof(buf) - 1)
+ width = sizeof(buf) - 1;
+#else /* SM_CONF_BROKEN_SIZE_T */
+ /* size_t is unsigned, hence this optimisation */
+ if (--width > sizeof(buf) - 2)
+ width = sizeof(buf) - 2;
+ width++;
+#endif /* SM_CONF_BROKEN_SIZE_T */
+ flags |= SIGNOK | NDIGITS | NZDIGITS;
+ for (p = buf; width > 0; width--)
+ {
+ c = *fp->f_p;
+
+ /*
+ ** Switch on the character; `goto ok'
+ ** if we accept it as a part of number.
+ */
+
+ switch (c)
+ {
+
+ /*
+ ** The digit 0 is always legal, but is
+ ** special. For %i conversions, if no
+ ** digits (zero or nonzero) have been
+ ** scanned (only signs), we will have
+ ** base==0. In that case, we should set
+ ** it to 8 and enable 0x prefixing.
+ ** Also, if we have not scanned zero digits
+ ** before this, do not turn off prefixing
+ ** (someone else will turn it off if we
+ ** have scanned any nonzero digits).
+ */
+
+ case '0':
+ if (base == 0)
+ {
+ base = 8;
+ flags |= PFXOK;
+ }
+ if (flags & NZDIGITS)
+ flags &= ~(SIGNOK|NZDIGITS|NDIGITS);
+ else
+ flags &= ~(SIGNOK|PFXOK|NDIGITS);
+ goto ok;
+
+ /* 1 through 7 always legal */
+ case '1': case '2': case '3':
+ case '4': case '5': case '6': case '7':
+ base = basefix[base];
+ flags &= ~(SIGNOK | PFXOK | NDIGITS);
+ goto ok;
+
+ /* digits 8 and 9 ok iff decimal or hex */
+ case '8': case '9':
+ base = basefix[base];
+ if (base <= 8)
+ break; /* not legal here */
+ flags &= ~(SIGNOK | PFXOK | NDIGITS);
+ goto ok;
+
+ /* letters ok iff hex */
+ case 'A': case 'B': case 'C':
+ case 'D': case 'E': case 'F':
+ case 'a': case 'b': case 'c':
+ case 'd': case 'e': case 'f':
+
+ /* no need to fix base here */
+ if (base <= 10)
+ break; /* not legal here */
+ flags &= ~(SIGNOK | PFXOK | NDIGITS);
+ goto ok;
+
+ /* sign ok only as first character */
+ case '+': case '-':
+ if (flags & SIGNOK)
+ {
+ flags &= ~SIGNOK;
+ goto ok;
+ }
+ break;
+
+ /* x ok iff flag still set & 2nd char */
+ case 'x': case 'X':
+ if (flags & PFXOK && p == buf + 1)
+ {
+ base = 16; /* if %i */
+ flags &= ~PFXOK;
+ goto ok;
+ }
+ break;
+ }
+
+ /*
+ ** If we got here, c is not a legal character
+ ** for a number. Stop accumulating digits.
+ */
+
+ break;
+ ok:
+ /* c is legal: store it and look at the next. */
+ *p++ = c;
+ if (--fp->f_r > 0)
+ fp->f_p++;
+ else if (sm_refill(fp, SM_TIME_FOREVER))
+ break; /* SM_IO_EOF */
+ }
+
+ /*
+ ** If we had only a sign, it is no good; push
+ ** back the sign. If the number ends in `x',
+ ** it was [sign] '0' 'x', so push back the x
+ ** and treat it as [sign] '0'.
+ */
+
+ if (flags & NDIGITS)
+ {
+ if (p > buf)
+ (void) sm_io_ungetc(fp, SM_TIME_DEFAULT,
+ *(unsigned char *)--p);
+ goto match_failure;
+ }
+ c = ((unsigned char *)p)[-1];
+ if (c == 'x' || c == 'X')
+ {
+ --p;
+ (void) sm_io_ungetc(fp, SM_TIME_DEFAULT, c);
+ }
+ if ((flags & SUPPRESS) == 0)
+ {
+ ULONGLONG_T res;
+
+ *p = 0;
+ res = (*ccfn)(buf, (char **)NULL, base);
+ if (flags & POINTER)
+ *SM_VA_ARG(ap, void **) =
+ (void *)(long) res;
+ else if (flags & QUAD)
+ *SM_VA_ARG(ap, LONGLONG_T *) = res;
+ else if (flags & LONG)
+ *SM_VA_ARG(ap, long *) = res;
+ else if (flags & SHORT)
+ *SM_VA_ARG(ap, short *) = res;
+ else
+ *SM_VA_ARG(ap, int *) = res;
+ nassigned++;
+ }
+ nread += p - buf;
+ break;
+
+ case CT_FLOAT:
+ /* scan a floating point number as if by strtod */
+ if (width == 0 || width > sizeof(buf) - 1)
+ width = sizeof(buf) - 1;
+ flags |= SIGNOK | NDIGITS | DPTOK | EXPOK;
+ for (p = buf; width; width--)
+ {
+ c = *fp->f_p;
+
+ /*
+ ** This code mimicks the integer conversion
+ ** code, but is much simpler.
+ */
+
+ switch (c)
+ {
+
+ case '0': case '1': case '2': case '3':
+ case '4': case '5': case '6': case '7':
+ case '8': case '9':
+ flags &= ~(SIGNOK | NDIGITS);
+ goto fok;
+
+ case '+': case '-':
+ if (flags & SIGNOK)
+ {
+ flags &= ~SIGNOK;
+ goto fok;
+ }
+ break;
+ case '.':
+ if (flags & DPTOK)
+ {
+ flags &= ~(SIGNOK | DPTOK);
+ goto fok;
+ }
+ break;
+ case 'e': case 'E':
+
+ /* no exponent without some digits */
+ if ((flags&(NDIGITS|EXPOK)) == EXPOK)
+ {
+ flags =
+ (flags & ~(EXPOK|DPTOK)) |
+ SIGNOK | NDIGITS;
+ goto fok;
+ }
+ break;
+ }
+ break;
+ fok:
+ *p++ = c;
+ if (--fp->f_r > 0)
+ fp->f_p++;
+ else if (sm_refill(fp, SM_TIME_FOREVER))
+ break; /* SM_IO_EOF */
+ }
+
+ /*
+ ** If no digits, might be missing exponent digits
+ ** (just give back the exponent) or might be missing
+ ** regular digits, but had sign and/or decimal point.
+ */
+
+ if (flags & NDIGITS)
+ {
+ if (flags & EXPOK)
+ {
+ /* no digits at all */
+ while (p > buf)
+ (void) sm_io_ungetc(fp,
+ SM_TIME_DEFAULT,
+ *(unsigned char *)--p);
+ goto match_failure;
+ }
+
+ /* just a bad exponent (e and maybe sign) */
+ c = *(unsigned char *) --p;
+ if (c != 'e' && c != 'E')
+ {
+ (void) sm_io_ungetc(fp, SM_TIME_DEFAULT,
+ c); /* sign */
+ c = *(unsigned char *)--p;
+ }
+ (void) sm_io_ungetc(fp, SM_TIME_DEFAULT, c);
+ }
+ if ((flags & SUPPRESS) == 0)
+ {
+ double res;
+
+ *p = 0;
+ res = strtod(buf, (char **) NULL);
+ if (flags & LONG)
+ *SM_VA_ARG(ap, double *) = res;
+ else
+ *SM_VA_ARG(ap, float *) = res;
+ nassigned++;
+ }
+ nread += p - buf;
+ break;
+ }
+ }
+input_failure:
+ if (evt != NULL)
+ sm_clrevent(evt); /* undo our timeout */
+ return nassigned ? nassigned : -1;
+match_failure:
+ if (evt != NULL)
+ sm_clrevent(evt); /* undo our timeout */
+ return nassigned;
+}
+
+/*
+** SM_SCCL -- sequenced character comparison list
+**
+** Fill in the given table from the scanset at the given format
+** (just after `['). Return a pointer to the character past the
+** closing `]'. The table has a 1 wherever characters should be
+** considered part of the scanset.
+**
+** Parameters:
+** tab -- array flagging "active" char's to match (returned)
+** fmt -- character list (within "[]")
+**
+** Results:
+*/
+
+static unsigned char *
+sm_sccl(tab, fmt)
+ register char *tab;
+ register unsigned char *fmt;
+{
+ register int c, n, v;
+
+ /* first `clear' the whole table */
+ c = *fmt++; /* first char hat => negated scanset */
+ if (c == '^')
+ {
+ v = 1; /* default => accept */
+ c = *fmt++; /* get new first char */
+ }
+ else
+ v = 0; /* default => reject */
+
+ /* should probably use memset here */
+ for (n = 0; n < 256; n++)
+ tab[n] = v;
+ if (c == 0)
+ return fmt - 1; /* format ended before closing ] */
+
+ /*
+ ** Now set the entries corresponding to the actual scanset
+ ** to the opposite of the above.
+ **
+ ** The first character may be ']' (or '-') without being special;
+ ** the last character may be '-'.
+ */
+
+ v = 1 - v;
+ for (;;)
+ {
+ tab[c] = v; /* take character c */
+doswitch:
+ n = *fmt++; /* and examine the next */
+ switch (n)
+ {
+
+ case 0: /* format ended too soon */
+ return fmt - 1;
+
+ case '-':
+ /*
+ ** A scanset of the form
+ ** [01+-]
+ ** is defined as `the digit 0, the digit 1,
+ ** the character +, the character -', but
+ ** the effect of a scanset such as
+ ** [a-zA-Z0-9]
+ ** is implementation defined. The V7 Unix
+ ** scanf treats `a-z' as `the letters a through
+ ** z', but treats `a-a' as `the letter a, the
+ ** character -, and the letter a'.
+ **
+ ** For compatibility, the `-' is not considerd
+ ** to define a range if the character following
+ ** it is either a close bracket (required by ANSI)
+ ** or is not numerically greater than the character
+ ** we just stored in the table (c).
+ */
+
+ n = *fmt;
+ if (n == ']' || n < c)
+ {
+ c = '-';
+ break; /* resume the for(;;) */
+ }
+ fmt++;
+ do
+ {
+ /* fill in the range */
+ tab[++c] = v;
+ } while (c < n);
+#if 1 /* XXX another disgusting compatibility hack */
+
+ /*
+ ** Alas, the V7 Unix scanf also treats formats
+ ** such as [a-c-e] as `the letters a through e'.
+ ** This too is permitted by the standard....
+ */
+
+ goto doswitch;
+#else
+ c = *fmt++;
+ if (c == 0)
+ return fmt - 1;
+ if (c == ']')
+ return fmt;
+ break;
+#endif
+
+ case ']': /* end of scanset */
+ return fmt;
+
+ default: /* just another character */
+ c = n;
+ break;
+ }
+ }
+ /* NOTREACHED */
+}
diff --git a/usr/src/cmd/sendmail/libsm/vprintf.c b/usr/src/cmd/sendmail/libsm/vprintf.c
new file mode 100644
index 0000000000..57acfb471a
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/vprintf.c
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: vprintf.c,v 1.12 2001/01/24 01:27:26 gshapiro Exp $")
+#include <sm/io.h>
+#include "local.h"
+
+/*
+** SM_VPRINTF -- print to standard out with variable length args
+**
+** Parameters:
+** timeout -- length of time allow to do the print
+** fmt -- the format of the output
+** ap -- the variable number of args to be used for output
+**
+** Returns:
+** as sm_io_vfprintf() does.
+*/
+
+int
+sm_vprintf(timeout, fmt, ap)
+ int timeout;
+ char const *fmt;
+ SM_VA_LOCAL_DECL
+{
+ return sm_io_vfprintf(smiostdout, timeout, fmt, ap);
+}
diff --git a/usr/src/cmd/sendmail/libsm/vsnprintf.c b/usr/src/cmd/sendmail/libsm/vsnprintf.c
new file mode 100644
index 0000000000..67f9f0b121
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/vsnprintf.c
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: vsnprintf.c,v 1.21 2001/03/04 23:28:41 ca Exp $")
+#include <limits.h>
+#include <sm/io.h>
+#include "local.h"
+
+/*
+** SM_VSNPRINTF -- format data for "output" into a string
+**
+** Assigned 'str' to a "fake" file pointer. This allows common
+** o/p formatting function sm_vprintf() to be used.
+**
+** Parameters:
+** str -- location for output
+** n -- maximum size for o/p
+** fmt -- format directives
+** ap -- data unit vectors for use by 'fmt'
+**
+** Results:
+** result from sm_io_vfprintf()
+**
+** Side Effects:
+** Limits the size ('n') to INT_MAX.
+*/
+
+int
+sm_vsnprintf(str, n, fmt, ap)
+ char *str;
+ size_t n;
+ const char *fmt;
+ SM_VA_LOCAL_DECL
+{
+ int ret;
+ char dummy;
+ SM_FILE_T fake;
+
+ /* While snprintf(3) specifies size_t stdio uses an int internally */
+ if (n > INT_MAX)
+ n = INT_MAX;
+
+ /* Stdio internals do not deal correctly with zero length buffer */
+ if (n == 0)
+ {
+ str = &dummy;
+ n = 1;
+ }
+ fake.sm_magic = SmFileMagic;
+ fake.f_timeout = SM_TIME_FOREVER;
+ fake.f_timeoutstate = SM_TIME_BLOCK;
+ fake.f_file = -1;
+ fake.f_flags = SMWR | SMSTR;
+ fake.f_bf.smb_base = fake.f_p = (unsigned char *)str;
+ fake.f_bf.smb_size = fake.f_w = n - 1;
+ fake.f_close = NULL;
+ fake.f_open = NULL;
+ fake.f_read = NULL;
+ fake.f_write = NULL;
+ fake.f_seek = NULL;
+ fake.f_setinfo = fake.f_getinfo = NULL;
+ fake.f_type = "sm_vsnprintf:fake";
+ ret = sm_io_vfprintf(&fake, SM_TIME_FOREVER, fmt, ap);
+ *fake.f_p = 0;
+ return ret;
+}
diff --git a/usr/src/cmd/sendmail/libsm/wbuf.c b/usr/src/cmd/sendmail/libsm/wbuf.c
new file mode 100644
index 0000000000..21ac5c82b6
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/wbuf.c
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: wbuf.c,v 1.19 2001/03/05 03:22:41 ca Exp $")
+#include <errno.h>
+#include <sm/io.h>
+#include "local.h"
+
+/* Note: This function is called from a macro located in <sm/io.h> */
+
+/*
+** SM_WBUF -- write character to and flush (likely now full) buffer
+**
+** Write the given character into the (probably full) buffer for
+** the given file. Flush the buffer out if it is or becomes full,
+** or if c=='\n' and the file is line buffered.
+**
+** Parameters:
+** fp -- the file pointer
+** timeout -- time to complete operation (milliseconds)
+** c -- int representation of the character to add
+**
+** Results:
+** Failure: -1 and sets errno
+** Success: int value of 'c'
+*/
+
+int
+sm_wbuf(fp, timeout, c)
+ register SM_FILE_T *fp;
+ int timeout;
+ register int c;
+{
+ register int n;
+
+ /*
+ ** In case we cannot write, or longjmp takes us out early,
+ ** make sure w is 0 (if fully- or un-buffered) or -bf.smb_size
+ ** (if line buffered) so that we will get called again.
+ ** If we did not do this, a sufficient number of sm_io_putc()
+ ** calls might wrap w from negative to positive.
+ */
+
+ fp->f_w = fp->f_lbfsize;
+ if (cantwrite(fp))
+ {
+ errno = EBADF;
+ return SM_IO_EOF;
+ }
+ c = (unsigned char)c;
+
+ /*
+ ** If it is completely full, flush it out. Then, in any case,
+ ** stuff c into the buffer. If this causes the buffer to fill
+ ** completely, or if c is '\n' and the file is line buffered,
+ ** flush it (perhaps a second time). The second flush will always
+ ** happen on unbuffered streams, where bf.smb_size==1; sm_io_flush()
+ ** guarantees that sm_io_putc() will always call sm_wbuf() by setting
+ ** w to 0, so we need not do anything else.
+ ** Note for the timeout, only one of the sm_io_flush's will get called.
+ */
+
+ n = fp->f_p - fp->f_bf.smb_base;
+ if (n >= fp->f_bf.smb_size)
+ {
+ if (sm_io_flush(fp, timeout))
+ return SM_IO_EOF; /* sm_io_flush() sets errno */
+ n = 0;
+ }
+ fp->f_w--;
+ *fp->f_p++ = c;
+ if (++n == fp->f_bf.smb_size || (fp->f_flags & SMLBF && c == '\n'))
+ if (sm_io_flush(fp, timeout))
+ return SM_IO_EOF; /* sm_io_flush() sets errno */
+ return c;
+}
diff --git a/usr/src/cmd/sendmail/libsm/wsetup.c b/usr/src/cmd/sendmail/libsm/wsetup.c
new file mode 100644
index 0000000000..e7a5e7924b
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/wsetup.c
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2000-2002 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: wsetup.c,v 1.20 2002/02/07 18:02:45 ca Exp $")
+#include <stdlib.h>
+#include <errno.h>
+#include <sm/io.h>
+#include "local.h"
+
+/*
+** SM_WSETUP -- check writing is safe
+**
+** Various output routines call wsetup to be sure it is safe to write,
+** because either flags does not include SMMWR, or buf is NULL.
+** Used in the macro "cantwrite" found in "local.h".
+**
+** Parameters:
+** fp -- the file pointer
+**
+** Results:
+** Failure: SM_IO_EOF and sets errno
+** Success: 0 (zero)
+*/
+
+int
+sm_wsetup(fp)
+ register SM_FILE_T *fp;
+{
+ /* make sure stdio is set up */
+ if (!Sm_IO_DidInit)
+ sm_init();
+
+ /* If we are not writing, we had better be reading and writing. */
+ if ((fp->f_flags & SMWR) == 0)
+ {
+ if ((fp->f_flags & SMRW) == 0)
+ {
+ errno = EBADF;
+ return SM_IO_EOF;
+ }
+ if (fp->f_flags & SMRD)
+ {
+ /* clobber any ungetc data */
+ if (HASUB(fp))
+ FREEUB(fp);
+
+ /* discard read buffer */
+ fp->f_flags &= ~(SMRD|SMFEOF);
+ fp->f_r = 0;
+ fp->f_p = fp->f_bf.smb_base;
+ }
+ fp->f_flags |= SMWR;
+ }
+
+ /* Make a buffer if necessary, then set w. */
+ if (fp->f_bf.smb_base == NULL)
+ sm_makebuf(fp);
+ if (fp->f_flags & SMLBF)
+ {
+ /*
+ ** It is line buffered, so make lbfsize be -bufsize
+ ** for the sm_putc() macro. We will change lbfsize back
+ ** to 0 whenever we turn off SMWR.
+ */
+
+ fp->f_w = 0;
+ fp->f_lbfsize = -fp->f_bf.smb_size;
+ }
+ else
+ fp->f_w = fp->f_flags & SMNBF ? 0 : fp->f_bf.smb_size;
+ return 0;
+}
diff --git a/usr/src/cmd/sendmail/libsm/xtrap.c b/usr/src/cmd/sendmail/libsm/xtrap.c
new file mode 100644
index 0000000000..5f82c3e0a4
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsm/xtrap.c
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2000 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: xtrap.c,v 1.3 2000/12/08 08:03:09 rodney Exp $")
+
+#include <sm/xtrap.h>
+
+SM_ATOMIC_UINT_T SmXtrapCount;
+
+SM_DEBUG_T SmXtrapDebug = SM_DEBUG_INITIALIZER("sm_xtrap",
+ "@(#)$Debug: sm_xtrap - raise exception at N'th xtrap point $");
+
+SM_DEBUG_T SmXtrapReport = SM_DEBUG_INITIALIZER("sm_xtrap_report",
+ "@(#)$Debug: sm_xtrap_report - report xtrap count on exit $");
diff --git a/usr/src/cmd/sendmail/libsmdb/Makefile b/usr/src/cmd/sendmail/libsmdb/Makefile
new file mode 100644
index 0000000000..687255b9df
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsmdb/Makefile
@@ -0,0 +1,64 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License, Version 1.0 only
+# (the "License"). You may not use this file except in compliance
+# with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+#
+# Copyright 2004 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# ident "%Z%%M% %I% %E% SMI"
+#
+# cmd/sendmail/libsmdb/Makefile
+#
+
+include ../../Makefile.cmd
+include ../Makefile.cmd
+
+INCPATH= -I. -I../src -I../include -I../db
+ENVDEF= -DNEWDB -DNDBM
+CPPFLAGS= $(INCPATH) $(RLS_DEF) $(ENVDEF) $(CPPFLAGS.master)
+
+ARFLAGS= cq
+
+OBJS= smdb.o smdb1.o smdb2.o smndbm.o
+
+SRCS= $(OBJS:%.o=%.c)
+
+libsmdb= libsmdb.a
+
+.KEEP_STATE:
+all: $(libsmdb)
+
+.PARALLEL: $(OBJS)
+
+$(libsmdb): $(OBJS)
+ $(RM) $@
+ $(AR) $(ARFLAGS) $@ $(OBJS)
+
+clean:
+ $(RM) $(OBJS) $(libsmdb)
+
+depend obj:
+
+install: all
+
+lint: lint_SRCS
+
+include ../../Makefile.targ
diff --git a/usr/src/cmd/sendmail/libsmdb/smdb.c b/usr/src/cmd/sendmail/libsmdb/smdb.c
new file mode 100644
index 0000000000..d65b083c37
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsmdb/smdb.c
@@ -0,0 +1,551 @@
+/*
+** Copyright (c) 1999-2002 Sendmail, Inc. and its suppliers.
+** All rights reserved.
+**
+** By using this file, you agree to the terms and conditions set
+** forth in the LICENSE file which can be found at the top level of
+** the sendmail distribution.
+*/
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: smdb.c,v 8.58 2004/08/03 20:58:38 ca Exp $")
+
+#include <fcntl.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+
+#include <sendmail/sendmail.h>
+#include <libsmdb/smdb.h>
+
+static bool smdb_lockfile __P((int, int));
+
+/*
+** SMDB_MALLOC_DATABASE -- Allocates a database structure.
+**
+** Parameters:
+** None
+**
+** Returns:
+** An pointer to an allocated SMDB_DATABASE structure or
+** NULL if it couldn't allocate the memory.
+*/
+
+SMDB_DATABASE *
+smdb_malloc_database()
+{
+ SMDB_DATABASE *db;
+
+ db = (SMDB_DATABASE *) malloc(sizeof(SMDB_DATABASE));
+
+ if (db != NULL)
+ (void) memset(db, '\0', sizeof(SMDB_DATABASE));
+
+ return db;
+}
+
+
+/*
+** SMDB_FREE_DATABASE -- Unallocates a database structure.
+**
+** Parameters:
+** database -- a SMDB_DATABASE pointer to deallocate.
+**
+** Returns:
+** None
+*/
+
+void
+smdb_free_database(database)
+ SMDB_DATABASE *database;
+{
+ if (database != NULL)
+ free(database);
+}
+/*
+** SMDB_LOCKFILE -- lock a file using flock or (shudder) fcntl locking
+**
+** Parameters:
+** fd -- the file descriptor of the file.
+** type -- type of the lock. Bits can be:
+** LOCK_EX -- exclusive lock.
+** LOCK_NB -- non-blocking.
+**
+** Returns:
+** true if the lock was acquired.
+** false otherwise.
+*/
+
+static bool
+smdb_lockfile(fd, type)
+ int fd;
+ int type;
+{
+ int i;
+ int save_errno;
+#if !HASFLOCK
+ int action;
+ struct flock lfd;
+
+ (void) memset(&lfd, '\0', sizeof lfd);
+ if (bitset(LOCK_UN, type))
+ lfd.l_type = F_UNLCK;
+ else if (bitset(LOCK_EX, type))
+ lfd.l_type = F_WRLCK;
+ else
+ lfd.l_type = F_RDLCK;
+
+ if (bitset(LOCK_NB, type))
+ action = F_SETLK;
+ else
+ action = F_SETLKW;
+
+ while ((i = fcntl(fd, action, &lfd)) < 0 && errno == EINTR)
+ continue;
+ if (i >= 0)
+ return true;
+ save_errno = errno;
+
+ /*
+ ** On SunOS, if you are testing using -oQ/tmp/mqueue or
+ ** -oA/tmp/aliases or anything like that, and /tmp is mounted
+ ** as type "tmp" (that is, served from swap space), the
+ ** previous fcntl will fail with "Invalid argument" errors.
+ ** Since this is fairly common during testing, we will assume
+ ** that this indicates that the lock is successfully grabbed.
+ */
+
+ if (save_errno == EINVAL)
+ return true;
+
+ if (!bitset(LOCK_NB, type) ||
+ (save_errno != EACCES && save_errno != EAGAIN))
+ {
+# if 0
+ int omode = fcntl(fd, F_GETFL, NULL);
+ int euid = (int) geteuid();
+
+ syslog(LOG_ERR, "cannot lockf(%s%s, fd=%d, type=%o, omode=%o, euid=%d)",
+ filename, ext, fd, type, omode, euid);
+# endif /* 0 */
+ errno = save_errno;
+ return false;
+ }
+#else /* !HASFLOCK */
+
+ while ((i = flock(fd, type)) < 0 && errno == EINTR)
+ continue;
+ if (i >= 0)
+ return true;
+ save_errno = errno;
+
+ if (!bitset(LOCK_NB, type) || save_errno != EWOULDBLOCK)
+ {
+# if 0
+ int omode = fcntl(fd, F_GETFL, NULL);
+ int euid = (int) geteuid();
+
+ syslog(LOG_ERR, "cannot flock(%s%s, fd=%d, type=%o, omode=%o, euid=%d)",
+ filename, ext, fd, type, omode, euid);
+# endif /* 0 */
+ errno = save_errno;
+ return false;
+ }
+#endif /* !HASFLOCK */
+ errno = save_errno;
+ return false;
+}
+/*
+** SMDB_OPEN_DATABASE -- Opens a database.
+**
+** This opens a database. If type is SMDB_DEFAULT it tries to
+** use a DB1 or DB2 hash. If that isn't available, it will try
+** to use NDBM. If a specific type is given it will try to open
+** a database of that type.
+**
+** Parameters:
+** database -- An pointer to a SMDB_DATABASE pointer where the
+** opened database will be stored. This should
+** be unallocated.
+** db_name -- The name of the database to open. Do not include
+** the file name extension.
+** mode -- The mode to set on the database file or files.
+** mode_mask -- Mode bits that must match on an opened database.
+** sff -- Flags to safefile.
+** type -- The type of database to open. Supported types
+** vary depending on what was compiled in.
+** user_info -- Information on the user to use for file
+** permissions.
+** params -- Params specific to the database being opened.
+** Only supports some DB hash options right now
+** (see smdb_db_open() for details).
+**
+** Returns:
+** SMDBE_OK -- Success.
+** Anything else is an error. Look up more info about the
+** error in the comments for the specific open() used.
+*/
+
+int
+smdb_open_database(database, db_name, mode, mode_mask, sff, type, user_info,
+ params)
+ SMDB_DATABASE **database;
+ char *db_name;
+ int mode;
+ int mode_mask;
+ long sff;
+ SMDB_DBTYPE type;
+ SMDB_USER_INFO *user_info;
+ SMDB_DBPARAMS *params;
+{
+ bool type_was_default = false;
+
+ if (type == SMDB_TYPE_DEFAULT)
+ {
+ type_was_default = true;
+#ifdef NEWDB
+ type = SMDB_TYPE_HASH;
+#else /* NEWDB */
+# ifdef NDBM
+ type = SMDB_TYPE_NDBM;
+# endif /* NDBM */
+#endif /* NEWDB */
+ }
+
+ if (type == SMDB_TYPE_DEFAULT)
+ return SMDBE_UNKNOWN_DB_TYPE;
+
+ if ((strncmp(type, SMDB_TYPE_HASH, SMDB_TYPE_HASH_LEN) == 0) ||
+ (strncmp(type, SMDB_TYPE_BTREE, SMDB_TYPE_BTREE_LEN) == 0))
+ {
+#ifdef NEWDB
+ int result;
+
+ result = smdb_db_open(database, db_name, mode, mode_mask, sff,
+ type, user_info, params);
+# ifdef NDBM
+ if (result == ENOENT && type_was_default)
+ type = SMDB_TYPE_NDBM;
+ else
+# endif /* NDBM */
+ return result;
+#else /* NEWDB */
+ return SMDBE_UNSUPPORTED_DB_TYPE;
+#endif /* NEWDB */
+ }
+
+ if (strncmp(type, SMDB_TYPE_NDBM, SMDB_TYPE_NDBM_LEN) == 0)
+ {
+#ifdef NDBM
+ int result;
+
+ result = smdb_ndbm_open(database, db_name, mode, mode_mask,
+ sff, type, user_info, params);
+ return result;
+#else /* NDBM */
+ return SMDBE_UNSUPPORTED_DB_TYPE;
+#endif /* NDBM */
+ }
+
+ return SMDBE_UNKNOWN_DB_TYPE;
+}
+/*
+** SMDB_ADD_EXTENSION -- Adds an extension to a file name.
+**
+** Just adds a . followed by a string to a db_name if there
+** is room and the db_name does not already have that extension.
+**
+** Parameters:
+** full_name -- The final file name.
+** max_full_name_len -- The max length for full_name.
+** db_name -- The name of the db.
+** extension -- The extension to add.
+**
+** Returns:
+** SMDBE_OK -- Success.
+** Anything else is an error. Look up more info about the
+** error in the comments for the specific open() used.
+*/
+
+int
+smdb_add_extension(full_name, max_full_name_len, db_name, extension)
+ char *full_name;
+ int max_full_name_len;
+ char *db_name;
+ char *extension;
+{
+ int extension_len;
+ int db_name_len;
+
+ if (full_name == NULL || db_name == NULL || extension == NULL)
+ return SMDBE_INVALID_PARAMETER;
+
+ extension_len = strlen(extension);
+ db_name_len = strlen(db_name);
+
+ if (extension_len + db_name_len + 2 > max_full_name_len)
+ return SMDBE_DB_NAME_TOO_LONG;
+
+ if (db_name_len < extension_len + 1 ||
+ db_name[db_name_len - extension_len - 1] != '.' ||
+ strcmp(&db_name[db_name_len - extension_len], extension) != 0)
+ (void) sm_snprintf(full_name, max_full_name_len, "%s.%s",
+ db_name, extension);
+ else
+ (void) sm_strlcpy(full_name, db_name, max_full_name_len);
+
+ return SMDBE_OK;
+}
+/*
+** SMDB_LOCK_FILE -- Locks the database file.
+**
+** Locks the actual database file.
+**
+** Parameters:
+** lock_fd -- The resulting descriptor for the locked file.
+** db_name -- The name of the database without extension.
+** mode -- The open mode.
+** sff -- Flags to safefile.
+** extension -- The extension for the file.
+**
+** Returns:
+** SMDBE_OK -- Success, otherwise errno.
+*/
+
+int
+smdb_lock_file(lock_fd, db_name, mode, sff, extension)
+ int *lock_fd;
+ char *db_name;
+ int mode;
+ long sff;
+ char *extension;
+{
+ int result;
+ char file_name[MAXPATHLEN];
+
+ result = smdb_add_extension(file_name, sizeof file_name, db_name,
+ extension);
+ if (result != SMDBE_OK)
+ return result;
+
+ *lock_fd = safeopen(file_name, mode & ~O_TRUNC, DBMMODE, sff);
+ if (*lock_fd < 0)
+ return errno;
+
+ return SMDBE_OK;
+}
+/*
+** SMDB_UNLOCK_FILE -- Unlocks a file
+**
+** Unlocks a file.
+**
+** Parameters:
+** lock_fd -- The descriptor for the locked file.
+**
+** Returns:
+** SMDBE_OK -- Success, otherwise errno.
+*/
+
+int
+smdb_unlock_file(lock_fd)
+ int lock_fd;
+{
+ int result;
+
+ result = close(lock_fd);
+ if (result != 0)
+ return errno;
+
+ return SMDBE_OK;
+}
+/*
+** SMDB_LOCK_MAP -- Locks a database.
+**
+** Parameters:
+** database -- database description.
+** type -- type of the lock. Bits can be:
+** LOCK_EX -- exclusive lock.
+** LOCK_NB -- non-blocking.
+**
+** Returns:
+** SMDBE_OK -- Success, otherwise errno.
+*/
+
+int
+smdb_lock_map(database, type)
+ SMDB_DATABASE *database;
+ int type;
+{
+ int fd;
+
+ fd = database->smdb_lockfd(database);
+ if (fd < 0)
+ return SMDBE_NOT_FOUND;
+ if (!smdb_lockfile(fd, type))
+ return SMDBE_LOCK_NOT_GRANTED;
+ return SMDBE_OK;
+}
+/*
+** SMDB_UNLOCK_MAP -- Unlocks a database
+**
+** Parameters:
+** database -- database description.
+**
+** Returns:
+** SMDBE_OK -- Success, otherwise errno.
+*/
+
+int
+smdb_unlock_map(database)
+ SMDB_DATABASE *database;
+{
+ int fd;
+
+ fd = database->smdb_lockfd(database);
+ if (fd < 0)
+ return SMDBE_NOT_FOUND;
+ if (!smdb_lockfile(fd, LOCK_UN))
+ return SMDBE_LOCK_NOT_HELD;
+ return SMDBE_OK;
+}
+/*
+** SMDB_SETUP_FILE -- Gets db file ready for use.
+**
+** Makes sure permissions on file are safe and creates it if it
+** doesn't exist.
+**
+** Parameters:
+** db_name -- The name of the database without extension.
+** extension -- The extension.
+** sff -- Flags to safefile.
+** mode_mask -- Mode bits that must match.
+** user_info -- Information on the user to use for file
+** permissions.
+** stat_info -- A place to put the stat info for the file.
+** Returns:
+** SMDBE_OK -- Success, otherwise errno.
+*/
+
+int
+smdb_setup_file(db_name, extension, mode_mask, sff, user_info, stat_info)
+ char *db_name;
+ char *extension;
+ int mode_mask;
+ long sff;
+ SMDB_USER_INFO *user_info;
+ struct stat *stat_info;
+{
+ int st;
+ int result;
+ char db_file_name[MAXPATHLEN];
+
+ result = smdb_add_extension(db_file_name, sizeof db_file_name, db_name,
+ extension);
+ if (result != SMDBE_OK)
+ return result;
+
+ st = safefile(db_file_name, user_info->smdbu_id,
+ user_info->smdbu_group_id, user_info->smdbu_name,
+ sff, mode_mask, stat_info);
+ if (st != 0)
+ return st;
+
+ return SMDBE_OK;
+}
+/*
+** SMDB_FILECHANGED -- Checks to see if a file changed.
+**
+** Compares the passed in stat_info with a current stat on
+** the passed in file descriptor. Check filechanged for
+** return values.
+**
+** Parameters:
+** db_name -- The name of the database without extension.
+** extension -- The extension.
+** db_fd -- A file descriptor for the database file.
+** stat_info -- An old stat_info.
+** Returns:
+** SMDBE_OK -- Success, otherwise errno.
+*/
+
+int
+smdb_filechanged(db_name, extension, db_fd, stat_info)
+ char *db_name;
+ char *extension;
+ int db_fd;
+ struct stat *stat_info;
+{
+ int result;
+ char db_file_name[MAXPATHLEN];
+
+ result = smdb_add_extension(db_file_name, sizeof db_file_name, db_name,
+ extension);
+ if (result != SMDBE_OK)
+ return result;
+ return filechanged(db_file_name, db_fd, stat_info);
+}
+/*
+** SMDB_PRINT_AVAILABLE_TYPES -- Prints the names of the available types.
+**
+** Parameters:
+** None
+**
+** Returns:
+** None
+*/
+
+void
+smdb_print_available_types()
+{
+#ifdef NDBM
+ printf("dbm\n");
+#endif /* NDBM */
+#ifdef NEWDB
+ printf("hash\n");
+ printf("btree\n");
+#endif /* NEWDB */
+}
+/*
+** SMDB_DB_DEFINITION -- Given a database type, return database definition
+**
+** Reads though a structure making an association with the database
+** type and the required cpp define from sendmail/README.
+** List size is dynamic and must be NULL terminated.
+**
+** Parameters:
+** type -- The name of the database type.
+**
+** Returns:
+** definition for type, otherwise NULL.
+*/
+
+typedef struct
+{
+ SMDB_DBTYPE type;
+ char *dbdef;
+} dbtype;
+
+static dbtype DatabaseDefs[] =
+{
+ { SMDB_TYPE_HASH, "NEWDB" },
+ { SMDB_TYPE_BTREE, "NEWDB" },
+ { SMDB_TYPE_NDBM, "NDBM" },
+ { NULL, "OOPS" }
+};
+
+char *
+smdb_db_definition(type)
+ SMDB_DBTYPE type;
+{
+ dbtype *ptr = DatabaseDefs;
+
+ while (ptr != NULL && ptr->type != NULL)
+ {
+ if (strcmp(type, ptr->type) == 0)
+ return ptr->dbdef;
+ ptr++;
+ }
+ return NULL;
+}
diff --git a/usr/src/cmd/sendmail/libsmdb/smdb1.c b/usr/src/cmd/sendmail/libsmdb/smdb1.c
new file mode 100644
index 0000000000..3b8508f479
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsmdb/smdb1.c
@@ -0,0 +1,577 @@
+/*
+** Copyright (c) 1999-2002 Sendmail, Inc. and its suppliers.
+** All rights reserved.
+**
+** By using this file, you agree to the terms and conditions set
+** forth in the LICENSE file which can be found at the top level of
+** the sendmail distribution.
+*/
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: smdb1.c,v 8.59 2004/08/03 20:58:39 ca Exp $")
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <fcntl.h>
+
+#include <sendmail/sendmail.h>
+#include <libsmdb/smdb.h>
+
+#if (DB_VERSION_MAJOR == 1)
+
+# define SMDB1_FILE_EXTENSION "db"
+
+struct smdb_db1_struct
+{
+ DB *smdb1_db;
+ int smdb1_lock_fd;
+ bool smdb1_cursor_in_use;
+};
+typedef struct smdb_db1_struct SMDB_DB1_DATABASE;
+
+struct smdb_db1_cursor
+{
+ SMDB_DB1_DATABASE *db;
+};
+typedef struct smdb_db1_cursor SMDB_DB1_CURSOR;
+
+static DBTYPE smdb_type_to_db1_type __P((SMDB_DBTYPE));
+static unsigned int smdb_put_flags_to_db1_flags __P((SMDB_FLAG));
+static int smdb_cursor_get_flags_to_smdb1 __P((SMDB_FLAG));
+static SMDB_DB1_DATABASE *smdb1_malloc_database __P((void));
+static int smdb1_close __P((SMDB_DATABASE *));
+static int smdb1_del __P((SMDB_DATABASE *, SMDB_DBENT *, unsigned int));
+static int smdb1_fd __P((SMDB_DATABASE *, int *));
+static int smdb1_lockfd __P((SMDB_DATABASE *));
+static int smdb1_get __P((SMDB_DATABASE *, SMDB_DBENT *, SMDB_DBENT *, unsigned int));
+static int smdb1_put __P((SMDB_DATABASE *, SMDB_DBENT *, SMDB_DBENT *, unsigned int));
+static int smdb1_set_owner __P((SMDB_DATABASE *, uid_t, gid_t));
+static int smdb1_sync __P((SMDB_DATABASE *, unsigned int));
+static int smdb1_cursor_close __P((SMDB_CURSOR *));
+static int smdb1_cursor_del __P((SMDB_CURSOR *, unsigned int));
+static int smdb1_cursor_get __P((SMDB_CURSOR *, SMDB_DBENT *, SMDB_DBENT *, SMDB_FLAG));
+static int smdb1_cursor_put __P((SMDB_CURSOR *, SMDB_DBENT *, SMDB_DBENT *, SMDB_FLAG));
+static int smdb1_cursor __P((SMDB_DATABASE *, SMDB_CURSOR **, unsigned int));
+
+/*
+** SMDB_TYPE_TO_DB1_TYPE -- Translates smdb database type to db1 type.
+**
+** Parameters:
+** type -- The type to translate.
+**
+** Returns:
+** The DB1 type that corresponsds to the passed in SMDB type.
+** Returns -1 if there is no equivalent type.
+**
+*/
+
+static DBTYPE
+smdb_type_to_db1_type(type)
+ SMDB_DBTYPE type;
+{
+ if (type == SMDB_TYPE_DEFAULT)
+ return DB_HASH;
+
+ if (strncmp(type, SMDB_TYPE_HASH, SMDB_TYPE_HASH_LEN) == 0)
+ return DB_HASH;
+
+ if (strncmp(type, SMDB_TYPE_BTREE, SMDB_TYPE_BTREE_LEN) == 0)
+ return DB_BTREE;
+
+ /* Should never get here thanks to test in smdb_db_open() */
+ return DB_HASH;
+}
+/*
+** SMDB_PUT_FLAGS_TO_DB1_FLAGS -- Translates smdb put flags to db1 put flags.
+**
+** Parameters:
+** flags -- The flags to translate.
+**
+** Returns:
+** The db1 flags that are equivalent to the smdb flags.
+**
+** Notes:
+** Any invalid flags are ignored.
+**
+*/
+
+static unsigned int
+smdb_put_flags_to_db1_flags(flags)
+ SMDB_FLAG flags;
+{
+ int return_flags;
+
+ return_flags = 0;
+
+ if (bitset(SMDBF_NO_OVERWRITE, flags))
+ return_flags |= R_NOOVERWRITE;
+
+ return return_flags;
+}
+/*
+** SMDB_CURSOR_GET_FLAGS_TO_SMDB1
+**
+** Parameters:
+** flags -- The flags to translate.
+**
+** Returns:
+** The db1 flags that are equivalent to the smdb flags.
+**
+** Notes:
+** Returns -1 if we don't support the flag.
+**
+*/
+
+static int
+smdb_cursor_get_flags_to_smdb1(flags)
+ SMDB_FLAG flags;
+{
+ switch(flags)
+ {
+ case SMDB_CURSOR_GET_FIRST:
+ return R_FIRST;
+
+ case SMDB_CURSOR_GET_LAST:
+ return R_LAST;
+
+ case SMDB_CURSOR_GET_NEXT:
+ return R_NEXT;
+
+ case SMDB_CURSOR_GET_RANGE:
+ return R_CURSOR;
+
+ default:
+ return -1;
+ }
+}
+
+/*
+** The rest of these functions correspond to the interface laid out in smdb.h.
+*/
+
+static SMDB_DB1_DATABASE *
+smdb1_malloc_database()
+{
+ SMDB_DB1_DATABASE *db1;
+
+ db1 = (SMDB_DB1_DATABASE *) malloc(sizeof(SMDB_DB1_DATABASE));
+
+ if (db1 != NULL)
+ {
+ db1->smdb1_lock_fd = -1;
+ db1->smdb1_cursor_in_use = false;
+ }
+
+ return db1;
+}
+
+static int
+smdb1_close(database)
+ SMDB_DATABASE *database;
+{
+ int result;
+ SMDB_DB1_DATABASE *db1 = (SMDB_DB1_DATABASE *) database->smdb_impl;
+ DB *db = ((SMDB_DB1_DATABASE *) database->smdb_impl)->smdb1_db;
+
+ result = db->close(db);
+ if (db1->smdb1_lock_fd != -1)
+ (void) close(db1->smdb1_lock_fd);
+
+ free(db1);
+ database->smdb_impl = NULL;
+
+ return result;
+}
+
+static int
+smdb1_del(database, key, flags)
+ SMDB_DATABASE *database;
+ SMDB_DBENT *key;
+ unsigned int flags;
+{
+ DB *db = ((SMDB_DB1_DATABASE *) database->smdb_impl)->smdb1_db;
+ DBT dbkey;
+
+ (void) memset(&dbkey, '\0', sizeof dbkey);
+ dbkey.data = key->data;
+ dbkey.size = key->size;
+ return db->del(db, &dbkey, flags);
+}
+
+static int
+smdb1_fd(database, fd)
+ SMDB_DATABASE *database;
+ int *fd;
+{
+ DB *db = ((SMDB_DB1_DATABASE *) database->smdb_impl)->smdb1_db;
+
+ *fd = db->fd(db);
+ if (*fd == -1)
+ return errno;
+
+ return SMDBE_OK;
+}
+
+static int
+smdb1_lockfd(database)
+ SMDB_DATABASE *database;
+{
+ SMDB_DB1_DATABASE *db1 = (SMDB_DB1_DATABASE *) database->smdb_impl;
+
+ return db1->smdb1_lock_fd;
+}
+
+
+static int
+smdb1_get(database, key, data, flags)
+ SMDB_DATABASE *database;
+ SMDB_DBENT *key;
+ SMDB_DBENT *data;
+ unsigned int flags;
+{
+ int result;
+ DB *db = ((SMDB_DB1_DATABASE *) database->smdb_impl)->smdb1_db;
+ DBT dbkey, dbdata;
+
+ (void) memset(&dbdata, '\0', sizeof dbdata);
+ (void) memset(&dbkey, '\0', sizeof dbkey);
+ dbkey.data = key->data;
+ dbkey.size = key->size;
+
+ result = db->get(db, &dbkey, &dbdata, flags);
+ if (result != 0)
+ {
+ if (result == 1)
+ return SMDBE_NOT_FOUND;
+ return errno;
+ }
+ data->data = dbdata.data;
+ data->size = dbdata.size;
+ return SMDBE_OK;
+}
+
+static int
+smdb1_put(database, key, data, flags)
+ SMDB_DATABASE *database;
+ SMDB_DBENT *key;
+ SMDB_DBENT *data;
+ unsigned int flags;
+{
+ DB *db = ((SMDB_DB1_DATABASE *) database->smdb_impl)->smdb1_db;
+ DBT dbkey, dbdata;
+
+ (void) memset(&dbdata, '\0', sizeof dbdata);
+ (void) memset(&dbkey, '\0', sizeof dbkey);
+ dbkey.data = key->data;
+ dbkey.size = key->size;
+ dbdata.data = data->data;
+ dbdata.size = data->size;
+
+ return db->put(db, &dbkey, &dbdata,
+ smdb_put_flags_to_db1_flags(flags));
+}
+
+static int
+smdb1_set_owner(database, uid, gid)
+ SMDB_DATABASE *database;
+ uid_t uid;
+ gid_t gid;
+{
+# if HASFCHOWN
+ int fd;
+ int result;
+ DB *db = ((SMDB_DB1_DATABASE *) database->smdb_impl)->smdb1_db;
+
+ fd = db->fd(db);
+ if (fd == -1)
+ return errno;
+
+ result = fchown(fd, uid, gid);
+ if (result < 0)
+ return errno;
+# endif /* HASFCHOWN */
+
+ return SMDBE_OK;
+}
+
+static int
+smdb1_sync(database, flags)
+ SMDB_DATABASE *database;
+ unsigned int flags;
+{
+ DB *db = ((SMDB_DB1_DATABASE *) database->smdb_impl)->smdb1_db;
+
+ return db->sync(db, flags);
+}
+
+static int
+smdb1_cursor_close(cursor)
+ SMDB_CURSOR *cursor;
+{
+ SMDB_DB1_CURSOR *db1_cursor = (SMDB_DB1_CURSOR *) cursor->smdbc_impl;
+ SMDB_DB1_DATABASE *db1 = db1_cursor->db;
+
+ if (!db1->smdb1_cursor_in_use)
+ return SMDBE_NOT_A_VALID_CURSOR;
+
+ db1->smdb1_cursor_in_use = false;
+ free(cursor);
+
+ return SMDBE_OK;
+}
+
+static int
+smdb1_cursor_del(cursor, flags)
+ SMDB_CURSOR *cursor;
+ unsigned int flags;
+{
+ SMDB_DB1_CURSOR *db1_cursor = (SMDB_DB1_CURSOR *) cursor->smdbc_impl;
+ SMDB_DB1_DATABASE *db1 = db1_cursor->db;
+ DB *db = db1->smdb1_db;
+
+ return db->del(db, NULL, R_CURSOR);
+}
+
+static int
+smdb1_cursor_get(cursor, key, value, flags)
+ SMDB_CURSOR *cursor;
+ SMDB_DBENT *key;
+ SMDB_DBENT *value;
+ SMDB_FLAG flags;
+{
+ int db1_flags;
+ int result;
+ SMDB_DB1_CURSOR *db1_cursor = (SMDB_DB1_CURSOR *) cursor->smdbc_impl;
+ SMDB_DB1_DATABASE *db1 = db1_cursor->db;
+ DB *db = db1->smdb1_db;
+ DBT dbkey, dbdata;
+
+ (void) memset(&dbdata, '\0', sizeof dbdata);
+ (void) memset(&dbkey, '\0', sizeof dbkey);
+
+ db1_flags = smdb_cursor_get_flags_to_smdb1(flags);
+ result = db->seq(db, &dbkey, &dbdata, db1_flags);
+ if (result == -1)
+ return errno;
+ if (result == 1)
+ return SMDBE_LAST_ENTRY;
+ value->data = dbdata.data;
+ value->size = dbdata.size;
+ key->data = dbkey.data;
+ key->size = dbkey.size;
+ return SMDBE_OK;
+}
+
+static int
+smdb1_cursor_put(cursor, key, value, flags)
+ SMDB_CURSOR *cursor;
+ SMDB_DBENT *key;
+ SMDB_DBENT *value;
+ SMDB_FLAG flags;
+{
+ SMDB_DB1_CURSOR *db1_cursor = (SMDB_DB1_CURSOR *) cursor->smdbc_impl;
+ SMDB_DB1_DATABASE *db1 = db1_cursor->db;
+ DB *db = db1->smdb1_db;
+ DBT dbkey, dbdata;
+
+ (void) memset(&dbdata, '\0', sizeof dbdata);
+ (void) memset(&dbkey, '\0', sizeof dbkey);
+ dbkey.data = key->data;
+ dbkey.size = key->size;
+ dbdata.data = value->data;
+ dbdata.size = value->size;
+
+ return db->put(db, &dbkey, &dbdata, R_CURSOR);
+}
+
+static int
+smdb1_cursor(database, cursor, flags)
+ SMDB_DATABASE *database;
+ SMDB_CURSOR **cursor;
+ unsigned int flags;
+{
+ SMDB_DB1_DATABASE *db1 = (SMDB_DB1_DATABASE *) database->smdb_impl;
+ SMDB_CURSOR *cur;
+ SMDB_DB1_CURSOR *db1_cursor;
+
+ if (db1->smdb1_cursor_in_use)
+ return SMDBE_ONLY_SUPPORTS_ONE_CURSOR;
+
+ db1->smdb1_cursor_in_use = true;
+ db1_cursor = (SMDB_DB1_CURSOR *) malloc(sizeof(SMDB_DB1_CURSOR));
+ db1_cursor->db = db1;
+
+ cur = (SMDB_CURSOR *) malloc(sizeof(SMDB_CURSOR));
+
+ if (cur == NULL)
+ return SMDBE_MALLOC;
+
+ cur->smdbc_impl = db1_cursor;
+ cur->smdbc_close = smdb1_cursor_close;
+ cur->smdbc_del = smdb1_cursor_del;
+ cur->smdbc_get = smdb1_cursor_get;
+ cur->smdbc_put = smdb1_cursor_put;
+ *cursor = cur;
+
+ return SMDBE_OK;
+}
+/*
+** SMDB_DB_OPEN -- Opens a db1 database.
+**
+** Parameters:
+** database -- An unallocated database pointer to a pointer.
+** db_name -- The name of the database without extension.
+** mode -- File permisions on the database if created.
+** mode_mask -- Mode bits that must match on an existing database.
+** sff -- Flags for safefile.
+** type -- The type of database to open
+** See smdb_type_to_db1_type for valid types.
+** user_info -- Information on the user to use for file
+** permissions.
+** db_params --
+** An SMDB_DBPARAMS struct including params. These
+** are processed according to the type of the
+** database. Currently supported params (only for
+** HASH type) are:
+** num_elements
+** cache_size
+**
+** Returns:
+** SMDBE_OK -- Success, otherwise errno.
+*/
+
+int
+smdb_db_open(database, db_name, mode, mode_mask, sff, type, user_info,
+ db_params)
+ SMDB_DATABASE **database;
+ char *db_name;
+ int mode;
+ int mode_mask;
+ long sff;
+ SMDB_DBTYPE type;
+ SMDB_USER_INFO *user_info;
+ SMDB_DBPARAMS *db_params;
+{
+ bool lockcreated = false;
+ int db_fd;
+ int lock_fd;
+ int result;
+ void *params;
+ SMDB_DATABASE *smdb_db;
+ SMDB_DB1_DATABASE *db1;
+ DB *db;
+ HASHINFO hash_info;
+ BTREEINFO btree_info;
+ DBTYPE db_type;
+ struct stat stat_info;
+ char db_file_name[MAXPATHLEN];
+
+ if (type == NULL ||
+ (strncmp(SMDB_TYPE_HASH, type, SMDB_TYPE_HASH_LEN) != 0 &&
+ strncmp(SMDB_TYPE_BTREE, type, SMDB_TYPE_BTREE_LEN) != 0))
+ return SMDBE_UNKNOWN_DB_TYPE;
+
+ result = smdb_add_extension(db_file_name, sizeof db_file_name,
+ db_name, SMDB1_FILE_EXTENSION);
+ if (result != SMDBE_OK)
+ return result;
+
+ result = smdb_setup_file(db_name, SMDB1_FILE_EXTENSION, mode_mask,
+ sff, user_info, &stat_info);
+ if (result != SMDBE_OK)
+ return result;
+
+ if (stat_info.st_mode == ST_MODE_NOFILE &&
+ bitset(mode, O_CREAT))
+ lockcreated = true;
+
+ lock_fd = -1;
+ result = smdb_lock_file(&lock_fd, db_name, mode, sff,
+ SMDB1_FILE_EXTENSION);
+ if (result != SMDBE_OK)
+ return result;
+
+ if (lockcreated)
+ {
+ mode |= O_TRUNC;
+ mode &= ~(O_CREAT|O_EXCL);
+ }
+
+ *database = NULL;
+
+ smdb_db = smdb_malloc_database();
+ db1 = smdb1_malloc_database();
+ if (smdb_db == NULL || db1 == NULL)
+ return SMDBE_MALLOC;
+ db1->smdb1_lock_fd = lock_fd;
+
+ params = NULL;
+ if (db_params != NULL &&
+ (strncmp(SMDB_TYPE_HASH, type, SMDB_TYPE_HASH_LEN) == 0))
+ {
+ (void) memset(&hash_info, '\0', sizeof hash_info);
+ hash_info.nelem = db_params->smdbp_num_elements;
+ hash_info.cachesize = db_params->smdbp_cache_size;
+ params = &hash_info;
+ }
+
+ if (db_params != NULL &&
+ (strncmp(SMDB_TYPE_BTREE, type, SMDB_TYPE_BTREE_LEN) == 0))
+ {
+ (void) memset(&btree_info, '\0', sizeof btree_info);
+ btree_info.cachesize = db_params->smdbp_cache_size;
+ if (db_params->smdbp_allow_dup)
+ btree_info.flags |= R_DUP;
+ params = &btree_info;
+ }
+
+ db_type = smdb_type_to_db1_type(type);
+ db = dbopen(db_file_name, mode, DBMMODE, db_type, params);
+ if (db != NULL)
+ {
+ db_fd = db->fd(db);
+ result = smdb_filechanged(db_name, SMDB1_FILE_EXTENSION, db_fd,
+ &stat_info);
+ }
+ else
+ {
+ if (errno == 0)
+ result = SMDBE_BAD_OPEN;
+ else
+ result = errno;
+ }
+
+ if (result == SMDBE_OK)
+ {
+ /* Everything is ok. Setup driver */
+ db1->smdb1_db = db;
+
+ smdb_db->smdb_close = smdb1_close;
+ smdb_db->smdb_del = smdb1_del;
+ smdb_db->smdb_fd = smdb1_fd;
+ smdb_db->smdb_lockfd = smdb1_lockfd;
+ smdb_db->smdb_get = smdb1_get;
+ smdb_db->smdb_put = smdb1_put;
+ smdb_db->smdb_set_owner = smdb1_set_owner;
+ smdb_db->smdb_sync = smdb1_sync;
+ smdb_db->smdb_cursor = smdb1_cursor;
+ smdb_db->smdb_impl = db1;
+
+ *database = smdb_db;
+ return SMDBE_OK;
+ }
+
+ if (db != NULL)
+ (void) db->close(db);
+
+ /* Error opening database */
+ (void) smdb_unlock_file(db1->smdb1_lock_fd);
+ free(db1);
+ smdb_free_database(smdb_db);
+
+ return result;
+}
+
+#endif /* (DB_VERSION_MAJOR == 1) */
diff --git a/usr/src/cmd/sendmail/libsmdb/smdb2.c b/usr/src/cmd/sendmail/libsmdb/smdb2.c
new file mode 100644
index 0000000000..e7cf7e4274
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsmdb/smdb2.c
@@ -0,0 +1,700 @@
+/*
+** Copyright (c) 1999-2003 Sendmail, Inc. and its suppliers.
+** All rights reserved.
+**
+** By using this file, you agree to the terms and conditions set
+** forth in the LICENSE file which can be found at the top level of
+** the sendmail distribution.
+*/
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: smdb2.c,v 8.72.2.7 2003/06/24 17:16:10 ca Exp $")
+
+#include <fcntl.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+
+#include <sendmail/sendmail.h>
+#include <libsmdb/smdb.h>
+
+#if (DB_VERSION_MAJOR >= 2)
+
+# define SMDB2_FILE_EXTENSION "db"
+
+struct smdb_db2_database
+{
+ DB *smdb2_db;
+ int smdb2_lock_fd;
+};
+typedef struct smdb_db2_database SMDB_DB2_DATABASE;
+
+/*
+** SMDB_TYPE_TO_DB2_TYPE -- Translates smdb database type to db2 type.
+**
+** Parameters:
+** type -- The type to translate.
+**
+** Returns:
+** The DB2 type that corresponsds to the passed in SMDB type.
+** Returns -1 if there is no equivalent type.
+**
+*/
+
+DBTYPE
+smdb_type_to_db2_type(type)
+ SMDB_DBTYPE type;
+{
+ if (type == SMDB_TYPE_DEFAULT)
+ return DB_HASH;
+
+ if (strncmp(type, SMDB_TYPE_HASH, SMDB_TYPE_HASH_LEN) == 0)
+ return DB_HASH;
+
+ if (strncmp(type, SMDB_TYPE_BTREE, SMDB_TYPE_BTREE_LEN) == 0)
+ return DB_BTREE;
+
+ return DB_UNKNOWN;
+}
+/*
+** DB2_ERROR_TO_SMDB -- Translates db2 errors to smdbe errors
+**
+** Parameters:
+** error -- The error to translate.
+**
+** Returns:
+** The SMDBE error corresponding to the db2 error.
+** If we don't have a corresponding error, it returs errno.
+**
+*/
+
+int
+db2_error_to_smdb(error)
+ int error;
+{
+ int result;
+
+ switch (error)
+ {
+# ifdef DB_INCOMPLETE
+ case DB_INCOMPLETE:
+ result = SMDBE_INCOMPLETE;
+ break;
+# endif /* DB_INCOMPLETE */
+
+# ifdef DB_NOTFOUND
+ case DB_NOTFOUND:
+ result = SMDBE_NOT_FOUND;
+ break;
+# endif /* DB_NOTFOUND */
+
+# ifdef DB_KEYEMPTY
+ case DB_KEYEMPTY:
+ result = SMDBE_KEY_EMPTY;
+ break;
+# endif /* DB_KEYEMPTY */
+
+# ifdef DB_KEYEXIST
+ case DB_KEYEXIST:
+ result = SMDBE_KEY_EXIST;
+ break;
+# endif /* DB_KEYEXIST */
+
+# ifdef DB_LOCK_DEADLOCK
+ case DB_LOCK_DEADLOCK:
+ result = SMDBE_LOCK_DEADLOCK;
+ break;
+# endif /* DB_LOCK_DEADLOCK */
+
+# ifdef DB_LOCK_NOTGRANTED
+ case DB_LOCK_NOTGRANTED:
+ result = SMDBE_LOCK_NOT_GRANTED;
+ break;
+# endif /* DB_LOCK_NOTGRANTED */
+
+# ifdef DB_LOCK_NOTHELD
+ case DB_LOCK_NOTHELD:
+ result = SMDBE_LOCK_NOT_HELD;
+ break;
+# endif /* DB_LOCK_NOTHELD */
+
+# ifdef DB_RUNRECOVERY
+ case DB_RUNRECOVERY:
+ result = SMDBE_RUN_RECOVERY;
+ break;
+# endif /* DB_RUNRECOVERY */
+
+# ifdef DB_OLD_VERSION
+ case DB_OLD_VERSION:
+ result = SMDBE_OLD_VERSION;
+ break;
+# endif /* DB_OLD_VERSION */
+
+ case 0:
+ result = SMDBE_OK;
+ break;
+
+ default:
+ result = error;
+ }
+ return result;
+}
+/*
+** SMDB_PUT_FLAGS_TO_DB2_FLAGS -- Translates smdb put flags to db2 put flags.
+**
+** Parameters:
+** flags -- The flags to translate.
+**
+** Returns:
+** The db2 flags that are equivalent to the smdb flags.
+**
+** Notes:
+** Any invalid flags are ignored.
+**
+*/
+
+unsigned int
+smdb_put_flags_to_db2_flags(flags)
+ SMDB_FLAG flags;
+{
+ int return_flags;
+
+ return_flags = 0;
+
+ if (bitset(SMDBF_NO_OVERWRITE, flags))
+ return_flags |= DB_NOOVERWRITE;
+
+ return return_flags;
+}
+/*
+** SMDB_CURSOR_GET_FLAGS_TO_DB2 -- Translates smdb cursor get flags to db2
+** getflags.
+**
+** Parameters:
+** flags -- The flags to translate.
+**
+** Returns:
+** The db2 flags that are equivalent to the smdb flags.
+**
+** Notes:
+** -1 is returned if flag is unknown.
+**
+*/
+
+int
+smdb_cursor_get_flags_to_db2(flags)
+ SMDB_FLAG flags;
+{
+ switch (flags)
+ {
+ case SMDB_CURSOR_GET_FIRST:
+ return DB_FIRST;
+
+ case SMDB_CURSOR_GET_LAST:
+ return DB_LAST;
+
+ case SMDB_CURSOR_GET_NEXT:
+ return DB_NEXT;
+
+ case SMDB_CURSOR_GET_RANGE:
+ return DB_SET_RANGE;
+
+ default:
+ return -1;
+ }
+}
+
+/*
+** Except for smdb_db_open, the rest of these functions correspond to the
+** interface laid out in smdb.h.
+*/
+
+SMDB_DB2_DATABASE *
+smdb2_malloc_database()
+{
+ SMDB_DB2_DATABASE *db2;
+
+ db2 = (SMDB_DB2_DATABASE *) malloc(sizeof(SMDB_DB2_DATABASE));
+ if (db2 != NULL)
+ db2->smdb2_lock_fd = -1;
+
+ return db2;
+}
+
+int
+smdb2_close(database)
+ SMDB_DATABASE *database;
+{
+ int result;
+ SMDB_DB2_DATABASE *db2 = (SMDB_DB2_DATABASE *) database->smdb_impl;
+ DB *db = ((SMDB_DB2_DATABASE *) database->smdb_impl)->smdb2_db;
+
+ result = db2_error_to_smdb(db->close(db, 0));
+ if (db2->smdb2_lock_fd != -1)
+ close(db2->smdb2_lock_fd);
+
+ free(db2);
+ database->smdb_impl = NULL;
+
+ return result;
+}
+
+int
+smdb2_del(database, key, flags)
+ SMDB_DATABASE *database;
+ SMDB_DBENT *key;
+ unsigned int flags;
+{
+ DB *db = ((SMDB_DB2_DATABASE *) database->smdb_impl)->smdb2_db;
+ DBT dbkey;
+
+ (void) memset(&dbkey, '\0', sizeof dbkey);
+ dbkey.data = key->data;
+ dbkey.size = key->size;
+ return db2_error_to_smdb(db->del(db, NULL, &dbkey, flags));
+}
+
+int
+smdb2_fd(database, fd)
+ SMDB_DATABASE *database;
+ int *fd;
+{
+ DB *db = ((SMDB_DB2_DATABASE *) database->smdb_impl)->smdb2_db;
+
+ return db2_error_to_smdb(db->fd(db, fd));
+}
+
+int
+smdb2_lockfd(database)
+ SMDB_DATABASE *database;
+{
+ SMDB_DB2_DATABASE *db2 = (SMDB_DB2_DATABASE *) database->smdb_impl;
+
+ return db2->smdb2_lock_fd;
+}
+
+int
+smdb2_get(database, key, data, flags)
+ SMDB_DATABASE *database;
+ SMDB_DBENT *key;
+ SMDB_DBENT *data;
+ unsigned int flags;
+{
+ int result;
+ DB *db = ((SMDB_DB2_DATABASE *) database->smdb_impl)->smdb2_db;
+ DBT dbkey, dbdata;
+
+ (void) memset(&dbdata, '\0', sizeof dbdata);
+ (void) memset(&dbkey, '\0', sizeof dbkey);
+ dbkey.data = key->data;
+ dbkey.size = key->size;
+
+ result = db->get(db, NULL, &dbkey, &dbdata, flags);
+ data->data = dbdata.data;
+ data->size = dbdata.size;
+ return db2_error_to_smdb(result);
+}
+
+int
+smdb2_put(database, key, data, flags)
+ SMDB_DATABASE *database;
+ SMDB_DBENT *key;
+ SMDB_DBENT *data;
+ unsigned int flags;
+{
+ DB *db = ((SMDB_DB2_DATABASE *) database->smdb_impl)->smdb2_db;
+ DBT dbkey, dbdata;
+
+ (void) memset(&dbdata, '\0', sizeof dbdata);
+ (void) memset(&dbkey, '\0', sizeof dbkey);
+ dbkey.data = key->data;
+ dbkey.size = key->size;
+ dbdata.data = data->data;
+ dbdata.size = data->size;
+
+ return db2_error_to_smdb(db->put(db, NULL, &dbkey, &dbdata,
+ smdb_put_flags_to_db2_flags(flags)));
+}
+
+
+int
+smdb2_set_owner(database, uid, gid)
+ SMDB_DATABASE *database;
+ uid_t uid;
+ gid_t gid;
+{
+# if HASFCHOWN
+ int fd;
+ int result;
+ DB *db = ((SMDB_DB2_DATABASE *) database->smdb_impl)->smdb2_db;
+
+ result = db->fd(db, &fd);
+ if (result != 0)
+ return result;
+
+ result = fchown(fd, uid, gid);
+ if (result < 0)
+ return errno;
+# endif /* HASFCHOWN */
+
+ return SMDBE_OK;
+}
+
+int
+smdb2_sync(database, flags)
+ SMDB_DATABASE *database;
+ unsigned int flags;
+{
+ DB *db = ((SMDB_DB2_DATABASE *) database->smdb_impl)->smdb2_db;
+
+ return db2_error_to_smdb(db->sync(db, flags));
+}
+
+int
+smdb2_cursor_close(cursor)
+ SMDB_CURSOR *cursor;
+{
+ int ret;
+ DBC *dbc = (DBC *) cursor->smdbc_impl;
+
+ ret = db2_error_to_smdb(dbc->c_close(dbc));
+ free(cursor);
+ return ret;
+}
+
+int
+smdb2_cursor_del(cursor, flags)
+ SMDB_CURSOR *cursor;
+ SMDB_FLAG flags;
+{
+ DBC *dbc = (DBC *) cursor->smdbc_impl;
+
+ return db2_error_to_smdb(dbc->c_del(dbc, 0));
+}
+
+int
+smdb2_cursor_get(cursor, key, value, flags)
+ SMDB_CURSOR *cursor;
+ SMDB_DBENT *key;
+ SMDB_DBENT *value;
+ SMDB_FLAG flags;
+{
+ int db2_flags;
+ int result;
+ DBC *dbc = (DBC *) cursor->smdbc_impl;
+ DBT dbkey, dbdata;
+
+ (void) memset(&dbdata, '\0', sizeof dbdata);
+ (void) memset(&dbkey, '\0', sizeof dbkey);
+
+ db2_flags = smdb_cursor_get_flags_to_db2(flags);
+ result = dbc->c_get(dbc, &dbkey, &dbdata, db2_flags);
+ if (result == DB_NOTFOUND)
+ return SMDBE_LAST_ENTRY;
+ key->data = dbkey.data;
+ key->size = dbkey.size;
+ value->data = dbdata.data;
+ value->size = dbdata.size;
+ return db2_error_to_smdb(result);
+}
+
+int
+smdb2_cursor_put(cursor, key, value, flags)
+ SMDB_CURSOR *cursor;
+ SMDB_DBENT *key;
+ SMDB_DBENT *value;
+ SMDB_FLAG flags;
+{
+ DBC *dbc = (DBC *) cursor->smdbc_impl;
+ DBT dbkey, dbdata;
+
+ (void) memset(&dbdata, '\0', sizeof dbdata);
+ (void) memset(&dbkey, '\0', sizeof dbkey);
+ dbkey.data = key->data;
+ dbkey.size = key->size;
+ dbdata.data = value->data;
+ dbdata.size = value->size;
+
+ return db2_error_to_smdb(dbc->c_put(dbc, &dbkey, &dbdata, 0));
+}
+
+int
+smdb2_cursor(database, cursor, flags)
+ SMDB_DATABASE *database;
+ SMDB_CURSOR **cursor;
+ SMDB_FLAG flags;
+{
+ int result;
+ DB *db = ((SMDB_DB2_DATABASE *) database->smdb_impl)->smdb2_db;
+ DBC *db2_cursor;
+
+# if DB_VERSION_MAJOR > 2 || DB_VERSION_MINOR >= 6
+ result = db->cursor(db, NULL, &db2_cursor, 0);
+# else /* DB_VERSION_MAJOR > 2 || DB_VERSION_MINOR >= 6 */
+ result = db->cursor(db, NULL, &db2_cursor);
+# endif /* DB_VERSION_MAJOR > 2 || DB_VERSION_MINOR >= 6 */
+ if (result != 0)
+ return db2_error_to_smdb(result);
+
+ *cursor = (SMDB_CURSOR *) malloc(sizeof(SMDB_CURSOR));
+ if (*cursor == NULL)
+ return SMDBE_MALLOC;
+
+ (*cursor)->smdbc_close = smdb2_cursor_close;
+ (*cursor)->smdbc_del = smdb2_cursor_del;
+ (*cursor)->smdbc_get = smdb2_cursor_get;
+ (*cursor)->smdbc_put = smdb2_cursor_put;
+ (*cursor)->smdbc_impl = db2_cursor;
+
+ return SMDBE_OK;
+}
+
+# if DB_VERSION_MAJOR == 2
+static int
+smdb_db_open_internal(db_name, db_type, db_flags, db_params, db)
+ char *db_name;
+ DBTYPE db_type;
+ int db_flags;
+ SMDB_DBPARAMS *db_params;
+ DB **db;
+{
+ void *params;
+ DB_INFO db_info;
+
+ params = NULL;
+ (void) memset(&db_info, '\0', sizeof db_info);
+ if (db_params != NULL)
+ {
+ db_info.db_cachesize = db_params->smdbp_cache_size;
+ if (db_type == DB_HASH)
+ db_info.h_nelem = db_params->smdbp_num_elements;
+ if (db_params->smdbp_allow_dup)
+ db_info.flags |= DB_DUP;
+ params = &db_info;
+ }
+ return db_open(db_name, db_type, db_flags, DBMMODE, NULL, params, db);
+}
+# endif /* DB_VERSION_MAJOR == 2 */
+
+# if DB_VERSION_MAJOR > 2
+static int
+smdb_db_open_internal(db_name, db_type, db_flags, db_params, db)
+ char *db_name;
+ DBTYPE db_type;
+ int db_flags;
+ SMDB_DBPARAMS *db_params;
+ DB **db;
+{
+ int result;
+
+ result = db_create(db, NULL, 0);
+ if (result != 0 || *db == NULL)
+ return result;
+
+ if (db_params != NULL)
+ {
+ result = (*db)->set_cachesize(*db, 0,
+ db_params->smdbp_cache_size, 0);
+ if (result != 0)
+ {
+ (void) (*db)->close((*db), 0);
+ *db = NULL;
+ return db2_error_to_smdb(result);
+ }
+ if (db_type == DB_HASH)
+ {
+ result = (*db)->set_h_nelem(*db, db_params->smdbp_num_elements);
+ if (result != 0)
+ {
+ (void) (*db)->close(*db, 0);
+ *db = NULL;
+ return db2_error_to_smdb(result);
+ }
+ }
+ if (db_params->smdbp_allow_dup)
+ {
+ result = (*db)->set_flags(*db, DB_DUP);
+ if (result != 0)
+ {
+ (void) (*db)->close(*db, 0);
+ *db = NULL;
+ return db2_error_to_smdb(result);
+ }
+ }
+ }
+
+ result = (*db)->open(*db,
+ DBTXN /* transaction for DB 4.1 */
+ db_name, NULL, db_type, db_flags, DBMMODE);
+ if (result != 0)
+ {
+ (void) (*db)->close(*db, 0);
+ *db = NULL;
+ }
+ return db2_error_to_smdb(result);
+}
+# endif /* DB_VERSION_MAJOR > 2 */
+/*
+** SMDB_DB_OPEN -- Opens a db database.
+**
+** Parameters:
+** database -- An unallocated database pointer to a pointer.
+** db_name -- The name of the database without extension.
+** mode -- File permisions for a created database.
+** mode_mask -- Mode bits that must match on an opened database.
+** sff -- Flags for safefile.
+** type -- The type of database to open
+** See smdb_type_to_db2_type for valid types.
+** user_info -- User information for file permissions.
+** db_params --
+** An SMDB_DBPARAMS struct including params. These
+** are processed according to the type of the
+** database. Currently supported params (only for
+** HASH type) are:
+** num_elements
+** cache_size
+**
+** Returns:
+** SMDBE_OK -- Success, other errno:
+** SMDBE_MALLOC -- Cannot allocate memory.
+** SMDBE_BAD_OPEN -- db_open didn't return an error, but
+** somehow the DB pointer is NULL.
+** Anything else: translated error from db2
+*/
+
+int
+smdb_db_open(database, db_name, mode, mode_mask, sff, type, user_info, db_params)
+ SMDB_DATABASE **database;
+ char *db_name;
+ int mode;
+ int mode_mask;
+ long sff;
+ SMDB_DBTYPE type;
+ SMDB_USER_INFO *user_info;
+ SMDB_DBPARAMS *db_params;
+{
+ bool lockcreated = false;
+ int result;
+ int db_flags;
+ int lock_fd;
+ int db_fd;
+ int major_v, minor_v, patch_v;
+ SMDB_DATABASE *smdb_db;
+ SMDB_DB2_DATABASE *db2;
+ DB *db;
+ DBTYPE db_type;
+ struct stat stat_info;
+ char db_file_name[MAXPATHLEN];
+
+ (void) db_version(&major_v, &minor_v, &patch_v);
+ if (major_v != DB_VERSION_MAJOR || minor_v != DB_VERSION_MINOR)
+ return SMDBE_VERSION_MISMATCH;
+
+ *database = NULL;
+
+ result = smdb_add_extension(db_file_name, sizeof db_file_name,
+ db_name, SMDB2_FILE_EXTENSION);
+ if (result != SMDBE_OK)
+ return result;
+
+ result = smdb_setup_file(db_name, SMDB2_FILE_EXTENSION,
+ mode_mask, sff, user_info, &stat_info);
+ if (result != SMDBE_OK)
+ return result;
+
+ lock_fd = -1;
+
+ if (stat_info.st_mode == ST_MODE_NOFILE &&
+ bitset(mode, O_CREAT))
+ lockcreated = true;
+
+ result = smdb_lock_file(&lock_fd, db_name, mode, sff,
+ SMDB2_FILE_EXTENSION);
+ if (result != SMDBE_OK)
+ return result;
+
+ if (lockcreated)
+ {
+ mode |= O_TRUNC;
+ mode &= ~(O_CREAT|O_EXCL);
+ }
+
+ smdb_db = smdb_malloc_database();
+ if (smdb_db == NULL)
+ return SMDBE_MALLOC;
+
+ db2 = smdb2_malloc_database();
+ if (db2 == NULL)
+ return SMDBE_MALLOC;
+
+ db2->smdb2_lock_fd = lock_fd;
+
+ db_type = smdb_type_to_db2_type(type);
+
+ db = NULL;
+
+ db_flags = 0;
+ if (bitset(O_CREAT, mode))
+ db_flags |= DB_CREATE;
+ if (bitset(O_TRUNC, mode))
+ db_flags |= DB_TRUNCATE;
+ if (mode == O_RDONLY)
+ db_flags |= DB_RDONLY;
+ SM_DB_FLAG_ADD(db_flags);
+
+ result = smdb_db_open_internal(db_file_name, db_type,
+ db_flags, db_params, &db);
+
+ if (result == 0 && db != NULL)
+ {
+ result = db->fd(db, &db_fd);
+ if (result == 0)
+ result = SMDBE_OK;
+ }
+ else
+ {
+ /* Try and narrow down on the problem */
+ if (result != 0)
+ result = db2_error_to_smdb(result);
+ else
+ result = SMDBE_BAD_OPEN;
+ }
+
+ if (result == SMDBE_OK)
+ result = smdb_filechanged(db_name, SMDB2_FILE_EXTENSION, db_fd,
+ &stat_info);
+
+ if (result == SMDBE_OK)
+ {
+ /* Everything is ok. Setup driver */
+ db2->smdb2_db = db;
+
+ smdb_db->smdb_close = smdb2_close;
+ smdb_db->smdb_del = smdb2_del;
+ smdb_db->smdb_fd = smdb2_fd;
+ smdb_db->smdb_lockfd = smdb2_lockfd;
+ smdb_db->smdb_get = smdb2_get;
+ smdb_db->smdb_put = smdb2_put;
+ smdb_db->smdb_set_owner = smdb2_set_owner;
+ smdb_db->smdb_sync = smdb2_sync;
+ smdb_db->smdb_cursor = smdb2_cursor;
+ smdb_db->smdb_impl = db2;
+
+ *database = smdb_db;
+
+ return SMDBE_OK;
+ }
+
+ if (db != NULL)
+ db->close(db, 0);
+
+ smdb_unlock_file(db2->smdb2_lock_fd);
+ free(db2);
+ smdb_free_database(smdb_db);
+
+ return result;
+}
+
+#endif /* (DB_VERSION_MAJOR >= 2) */
diff --git a/usr/src/cmd/sendmail/libsmdb/smndbm.c b/usr/src/cmd/sendmail/libsmdb/smndbm.c
new file mode 100644
index 0000000000..3327346bd2
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsmdb/smndbm.c
@@ -0,0 +1,629 @@
+/*
+** Copyright (c) 1999-2002 Sendmail, Inc. and its suppliers.
+** All rights reserved.
+**
+** By using this file, you agree to the terms and conditions set
+** forth in the LICENSE file which can be found at the top level of
+** the sendmail distribution.
+*/
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: smndbm.c,v 8.52 2002/05/21 22:30:30 gshapiro Exp $")
+
+#include <fcntl.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <sendmail/sendmail.h>
+#include <libsmdb/smdb.h>
+
+#ifdef NDBM
+
+# define SMNDB_DIR_FILE_EXTENSION "dir"
+# define SMNDB_PAG_FILE_EXTENSION "pag"
+
+struct smdb_dbm_database_struct
+{
+ DBM *smndbm_dbm;
+ int smndbm_lock_fd;
+ bool smndbm_cursor_in_use;
+};
+typedef struct smdb_dbm_database_struct SMDB_DBM_DATABASE;
+
+struct smdb_dbm_cursor_struct
+{
+ SMDB_DBM_DATABASE *smndbmc_db;
+ datum smndbmc_current_key;
+};
+typedef struct smdb_dbm_cursor_struct SMDB_DBM_CURSOR;
+
+/*
+** SMDB_PUT_FLAGS_TO_NDBM_FLAGS -- Translates smdb put flags to ndbm put flags.
+**
+** Parameters:
+** flags -- The flags to translate.
+**
+** Returns:
+** The ndbm flags that are equivalent to the smdb flags.
+**
+** Notes:
+** Any invalid flags are ignored.
+**
+*/
+
+int
+smdb_put_flags_to_ndbm_flags(flags)
+ SMDB_FLAG flags;
+{
+ int return_flags;
+
+ return_flags = 0;
+ if (bitset(SMDBF_NO_OVERWRITE, flags))
+ return_flags = DBM_INSERT;
+ else
+ return_flags = DBM_REPLACE;
+
+ return return_flags;
+}
+
+/*
+** Except for smdb_ndbm_open, the rest of these function correspond to the
+** interface laid out in smdb.h.
+*/
+
+SMDB_DBM_DATABASE *
+smdbm_malloc_database()
+{
+ SMDB_DBM_DATABASE *db;
+
+ db = (SMDB_DBM_DATABASE *) malloc(sizeof(SMDB_DBM_DATABASE));
+ if (db != NULL)
+ {
+ db->smndbm_dbm = NULL;
+ db->smndbm_lock_fd = -1;
+ db->smndbm_cursor_in_use = false;
+ }
+
+ return db;
+}
+
+int
+smdbm_close(database)
+ SMDB_DATABASE *database;
+{
+ SMDB_DBM_DATABASE *db = (SMDB_DBM_DATABASE *) database->smdb_impl;
+ DBM *dbm = ((SMDB_DBM_DATABASE *) database->smdb_impl)->smndbm_dbm;
+
+ dbm_close(dbm);
+ if (db->smndbm_lock_fd != -1)
+ close(db->smndbm_lock_fd);
+
+ free(db);
+ database->smdb_impl = NULL;
+
+ return SMDBE_OK;
+}
+
+int
+smdbm_del(database, key, flags)
+ SMDB_DATABASE *database;
+ SMDB_DBENT *key;
+ unsigned int flags;
+{
+ int result;
+ DBM *dbm = ((SMDB_DBM_DATABASE *) database->smdb_impl)->smndbm_dbm;
+ datum dbkey;
+
+ (void) memset(&dbkey, '\0', sizeof dbkey);
+ dbkey.dptr = key->data;
+ dbkey.dsize = key->size;
+
+ errno = 0;
+ result = dbm_delete(dbm, dbkey);
+ if (result != 0)
+ {
+ int save_errno = errno;
+
+ if (dbm_error(dbm))
+ return SMDBE_IO_ERROR;
+
+ if (save_errno != 0)
+ return save_errno;
+
+ return SMDBE_NOT_FOUND;
+ }
+ return SMDBE_OK;
+}
+
+int
+smdbm_fd(database, fd)
+ SMDB_DATABASE *database;
+ int *fd;
+{
+ DBM *dbm = ((SMDB_DBM_DATABASE *) database->smdb_impl)->smndbm_dbm;
+
+ *fd = dbm_dirfno(dbm);
+ if (*fd <= 0)
+ return EINVAL;
+
+ return SMDBE_OK;
+}
+
+int
+smdbm_lockfd(database)
+ SMDB_DATABASE *database;
+{
+ SMDB_DBM_DATABASE *db = (SMDB_DBM_DATABASE *) database->smdb_impl;
+
+ return db->smndbm_lock_fd;
+}
+
+int
+smdbm_get(database, key, data, flags)
+ SMDB_DATABASE *database;
+ SMDB_DBENT *key;
+ SMDB_DBENT *data;
+ unsigned int flags;
+{
+ DBM *dbm = ((SMDB_DBM_DATABASE *) database->smdb_impl)->smndbm_dbm;
+ datum dbkey, dbdata;
+
+ (void) memset(&dbkey, '\0', sizeof dbkey);
+ (void) memset(&dbdata, '\0', sizeof dbdata);
+ dbkey.dptr = key->data;
+ dbkey.dsize = key->size;
+
+ errno = 0;
+ dbdata = dbm_fetch(dbm, dbkey);
+ if (dbdata.dptr == NULL)
+ {
+ int save_errno = errno;
+
+ if (dbm_error(dbm))
+ return SMDBE_IO_ERROR;
+
+ if (save_errno != 0)
+ return save_errno;
+
+ return SMDBE_NOT_FOUND;
+ }
+ data->data = dbdata.dptr;
+ data->size = dbdata.dsize;
+ return SMDBE_OK;
+}
+
+int
+smdbm_put(database, key, data, flags)
+ SMDB_DATABASE *database;
+ SMDB_DBENT *key;
+ SMDB_DBENT *data;
+ unsigned int flags;
+{
+ int result;
+ int save_errno;
+ DBM *dbm = ((SMDB_DBM_DATABASE *) database->smdb_impl)->smndbm_dbm;
+ datum dbkey, dbdata;
+
+ (void) memset(&dbkey, '\0', sizeof dbkey);
+ (void) memset(&dbdata, '\0', sizeof dbdata);
+ dbkey.dptr = key->data;
+ dbkey.dsize = key->size;
+ dbdata.dptr = data->data;
+ dbdata.dsize = data->size;
+
+ errno = 0;
+ result = dbm_store(dbm, dbkey, dbdata,
+ smdb_put_flags_to_ndbm_flags(flags));
+ switch (result)
+ {
+ case 1:
+ return SMDBE_DUPLICATE;
+
+ case 0:
+ return SMDBE_OK;
+
+ default:
+ save_errno = errno;
+
+ if (dbm_error(dbm))
+ return SMDBE_IO_ERROR;
+
+ if (save_errno != 0)
+ return save_errno;
+
+ return SMDBE_IO_ERROR;
+ }
+ /* NOTREACHED */
+}
+
+int
+smndbm_set_owner(database, uid, gid)
+ SMDB_DATABASE *database;
+ uid_t uid;
+ gid_t gid;
+{
+# if HASFCHOWN
+ int fd;
+ int result;
+ DBM *dbm = ((SMDB_DBM_DATABASE *) database->smdb_impl)->smndbm_dbm;
+
+ fd = dbm_dirfno(dbm);
+ if (fd <= 0)
+ return EINVAL;
+
+ result = fchown(fd, uid, gid);
+ if (result < 0)
+ return errno;
+
+ fd = dbm_pagfno(dbm);
+ if (fd <= 0)
+ return EINVAL;
+
+ result = fchown(fd, uid, gid);
+ if (result < 0)
+ return errno;
+# endif /* HASFCHOWN */
+
+ return SMDBE_OK;
+}
+
+int
+smdbm_sync(database, flags)
+ SMDB_DATABASE *database;
+ unsigned int flags;
+{
+ return SMDBE_UNSUPPORTED;
+}
+
+int
+smdbm_cursor_close(cursor)
+ SMDB_CURSOR *cursor;
+{
+ SMDB_DBM_CURSOR *dbm_cursor = (SMDB_DBM_CURSOR *) cursor->smdbc_impl;
+ SMDB_DBM_DATABASE *db = dbm_cursor->smndbmc_db;
+
+ if (!db->smndbm_cursor_in_use)
+ return SMDBE_NOT_A_VALID_CURSOR;
+
+ db->smndbm_cursor_in_use = false;
+ free(dbm_cursor);
+ free(cursor);
+
+ return SMDBE_OK;
+}
+
+int
+smdbm_cursor_del(cursor, flags)
+ SMDB_CURSOR *cursor;
+ unsigned int flags;
+{
+ int result;
+ SMDB_DBM_CURSOR *dbm_cursor = (SMDB_DBM_CURSOR *) cursor->smdbc_impl;
+ SMDB_DBM_DATABASE *db = dbm_cursor->smndbmc_db;
+ DBM *dbm = db->smndbm_dbm;
+
+ errno = 0;
+ result = dbm_delete(dbm, dbm_cursor->smndbmc_current_key);
+ if (result != 0)
+ {
+ int save_errno = errno;
+
+ if (dbm_error(dbm))
+ return SMDBE_IO_ERROR;
+
+ if (save_errno != 0)
+ return save_errno;
+
+ return SMDBE_NOT_FOUND;
+ }
+ return SMDBE_OK;
+}
+
+int
+smdbm_cursor_get(cursor, key, value, flags)
+ SMDB_CURSOR *cursor;
+ SMDB_DBENT *key;
+ SMDB_DBENT *value;
+ SMDB_FLAG flags;
+{
+ SMDB_DBM_CURSOR *dbm_cursor = (SMDB_DBM_CURSOR *) cursor->smdbc_impl;
+ SMDB_DBM_DATABASE *db = dbm_cursor->smndbmc_db;
+ DBM *dbm = db->smndbm_dbm;
+ datum dbkey, dbdata;
+
+ (void) memset(&dbkey, '\0', sizeof dbkey);
+ (void) memset(&dbdata, '\0', sizeof dbdata);
+
+ if (flags == SMDB_CURSOR_GET_RANGE)
+ return SMDBE_UNSUPPORTED;
+
+ if (dbm_cursor->smndbmc_current_key.dptr == NULL)
+ {
+ dbm_cursor->smndbmc_current_key = dbm_firstkey(dbm);
+ if (dbm_cursor->smndbmc_current_key.dptr == NULL)
+ {
+ if (dbm_error(dbm))
+ return SMDBE_IO_ERROR;
+ return SMDBE_LAST_ENTRY;
+ }
+ }
+ else
+ {
+ dbm_cursor->smndbmc_current_key = dbm_nextkey(dbm);
+ if (dbm_cursor->smndbmc_current_key.dptr == NULL)
+ {
+ if (dbm_error(dbm))
+ return SMDBE_IO_ERROR;
+ return SMDBE_LAST_ENTRY;
+ }
+ }
+
+ errno = 0;
+ dbdata = dbm_fetch(dbm, dbm_cursor->smndbmc_current_key);
+ if (dbdata.dptr == NULL)
+ {
+ int save_errno = errno;
+
+ if (dbm_error(dbm))
+ return SMDBE_IO_ERROR;
+
+ if (save_errno != 0)
+ return save_errno;
+
+ return SMDBE_NOT_FOUND;
+ }
+ value->data = dbdata.dptr;
+ value->size = dbdata.dsize;
+ key->data = dbm_cursor->smndbmc_current_key.dptr;
+ key->size = dbm_cursor->smndbmc_current_key.dsize;
+
+ return SMDBE_OK;
+}
+
+int
+smdbm_cursor_put(cursor, key, value, flags)
+ SMDB_CURSOR *cursor;
+ SMDB_DBENT *key;
+ SMDB_DBENT *value;
+ SMDB_FLAG flags;
+{
+ int result;
+ int save_errno;
+ SMDB_DBM_CURSOR *dbm_cursor = (SMDB_DBM_CURSOR *) cursor->smdbc_impl;
+ SMDB_DBM_DATABASE *db = dbm_cursor->smndbmc_db;
+ DBM *dbm = db->smndbm_dbm;
+ datum dbdata;
+
+ (void) memset(&dbdata, '\0', sizeof dbdata);
+ dbdata.dptr = value->data;
+ dbdata.dsize = value->size;
+
+ errno = 0;
+ result = dbm_store(dbm, dbm_cursor->smndbmc_current_key, dbdata,
+ smdb_put_flags_to_ndbm_flags(flags));
+ switch (result)
+ {
+ case 1:
+ return SMDBE_DUPLICATE;
+
+ case 0:
+ return SMDBE_OK;
+
+ default:
+ save_errno = errno;
+
+ if (dbm_error(dbm))
+ return SMDBE_IO_ERROR;
+
+ if (save_errno != 0)
+ return save_errno;
+
+ return SMDBE_IO_ERROR;
+ }
+ /* NOTREACHED */
+}
+
+int
+smdbm_cursor(database, cursor, flags)
+ SMDB_DATABASE *database;
+ SMDB_CURSOR **cursor;
+ SMDB_FLAG flags;
+{
+ SMDB_DBM_DATABASE *db = (SMDB_DBM_DATABASE *) database->smdb_impl;
+ SMDB_CURSOR *cur;
+ SMDB_DBM_CURSOR *dbm_cursor;
+
+ if (db->smndbm_cursor_in_use)
+ return SMDBE_ONLY_SUPPORTS_ONE_CURSOR;
+
+ db->smndbm_cursor_in_use = true;
+ dbm_cursor = (SMDB_DBM_CURSOR *) malloc(sizeof(SMDB_DBM_CURSOR));
+ dbm_cursor->smndbmc_db = db;
+ dbm_cursor->smndbmc_current_key.dptr = NULL;
+ dbm_cursor->smndbmc_current_key.dsize = 0;
+
+ cur = (SMDB_CURSOR*) malloc(sizeof(SMDB_CURSOR));
+ if (cur == NULL)
+ return SMDBE_MALLOC;
+
+ cur->smdbc_impl = dbm_cursor;
+ cur->smdbc_close = smdbm_cursor_close;
+ cur->smdbc_del = smdbm_cursor_del;
+ cur->smdbc_get = smdbm_cursor_get;
+ cur->smdbc_put = smdbm_cursor_put;
+ *cursor = cur;
+
+ return SMDBE_OK;
+}
+/*
+** SMDB_NDBM_OPEN -- Opens a ndbm database.
+**
+** Parameters:
+** database -- An unallocated database pointer to a pointer.
+** db_name -- The name of the database without extension.
+** mode -- File permisions on a created database.
+** mode_mask -- Mode bits that much match on an opened database.
+** sff -- Flags to safefile.
+** type -- The type of database to open.
+** Only SMDB_NDBM is supported.
+** user_info -- Information on the user to use for file
+** permissions.
+** db_params -- No params are supported.
+**
+** Returns:
+** SMDBE_OK -- Success, otherwise errno:
+** SMDBE_MALLOC -- Cannot allocate memory.
+** SMDBE_UNSUPPORTED -- The type is not supported.
+** SMDBE_GDBM_IS_BAD -- We have detected GDBM and we don't
+** like it.
+** SMDBE_BAD_OPEN -- dbm_open failed and errno was not set.
+** Anything else: errno
+*/
+
+int
+smdb_ndbm_open(database, db_name, mode, mode_mask, sff, type, user_info,
+ db_params)
+ SMDB_DATABASE **database;
+ char *db_name;
+ int mode;
+ int mode_mask;
+ long sff;
+ SMDB_DBTYPE type;
+ SMDB_USER_INFO *user_info;
+ SMDB_DBPARAMS *db_params;
+{
+ bool lockcreated = false;
+ int result;
+ int lock_fd;
+ SMDB_DATABASE *smdb_db;
+ SMDB_DBM_DATABASE *db;
+ DBM *dbm = NULL;
+ struct stat dir_stat_info;
+ struct stat pag_stat_info;
+
+ result = SMDBE_OK;
+ *database = NULL;
+
+ if (type == NULL)
+ return SMDBE_UNKNOWN_DB_TYPE;
+
+ result = smdb_setup_file(db_name, SMNDB_DIR_FILE_EXTENSION, mode_mask,
+ sff, user_info, &dir_stat_info);
+ if (result != SMDBE_OK)
+ return result;
+
+ result = smdb_setup_file(db_name, SMNDB_PAG_FILE_EXTENSION, mode_mask,
+ sff, user_info, &pag_stat_info);
+ if (result != SMDBE_OK)
+ return result;
+
+ if ((dir_stat_info.st_mode == ST_MODE_NOFILE ||
+ pag_stat_info.st_mode == ST_MODE_NOFILE) &&
+ bitset(mode, O_CREAT))
+ lockcreated = true;
+
+ lock_fd = -1;
+ result = smdb_lock_file(&lock_fd, db_name, mode, sff,
+ SMNDB_DIR_FILE_EXTENSION);
+ if (result != SMDBE_OK)
+ return result;
+
+ if (lockcreated)
+ {
+ int pag_fd;
+
+ /* Need to pre-open the .pag file as well with O_EXCL */
+ result = smdb_lock_file(&pag_fd, db_name, mode, sff,
+ SMNDB_PAG_FILE_EXTENSION);
+ if (result != SMDBE_OK)
+ {
+ (void) close(lock_fd);
+ return result;
+ }
+ (void) close(pag_fd);
+
+ mode |= O_TRUNC;
+ mode &= ~(O_CREAT|O_EXCL);
+ }
+
+ smdb_db = smdb_malloc_database();
+ if (smdb_db == NULL)
+ result = SMDBE_MALLOC;
+
+ db = smdbm_malloc_database();
+ if (db == NULL)
+ result = SMDBE_MALLOC;
+
+ /* Try to open database */
+ if (result == SMDBE_OK)
+ {
+ db->smndbm_lock_fd = lock_fd;
+
+ errno = 0;
+ dbm = dbm_open(db_name, mode, DBMMODE);
+ if (dbm == NULL)
+ {
+ if (errno == 0)
+ result = SMDBE_BAD_OPEN;
+ else
+ result = errno;
+ }
+ db->smndbm_dbm = dbm;
+ }
+
+ /* Check for GDBM */
+ if (result == SMDBE_OK)
+ {
+ if (dbm_dirfno(dbm) == dbm_pagfno(dbm))
+ result = SMDBE_GDBM_IS_BAD;
+ }
+
+ /* Check for filechanged */
+ if (result == SMDBE_OK)
+ {
+ result = smdb_filechanged(db_name, SMNDB_DIR_FILE_EXTENSION,
+ dbm_dirfno(dbm), &dir_stat_info);
+ if (result == SMDBE_OK)
+ {
+ result = smdb_filechanged(db_name,
+ SMNDB_PAG_FILE_EXTENSION,
+ dbm_pagfno(dbm),
+ &pag_stat_info);
+ }
+ }
+
+ /* XXX Got to get fchown stuff in here */
+
+ /* Setup driver if everything is ok */
+ if (result == SMDBE_OK)
+ {
+ *database = smdb_db;
+
+ smdb_db->smdb_close = smdbm_close;
+ smdb_db->smdb_del = smdbm_del;
+ smdb_db->smdb_fd = smdbm_fd;
+ smdb_db->smdb_lockfd = smdbm_lockfd;
+ smdb_db->smdb_get = smdbm_get;
+ smdb_db->smdb_put = smdbm_put;
+ smdb_db->smdb_set_owner = smndbm_set_owner;
+ smdb_db->smdb_sync = smdbm_sync;
+ smdb_db->smdb_cursor = smdbm_cursor;
+
+ smdb_db->smdb_impl = db;
+
+ return SMDBE_OK;
+ }
+
+ /* If we're here, something bad happened, clean up */
+ if (dbm != NULL)
+ dbm_close(dbm);
+
+ smdb_unlock_file(db->smndbm_lock_fd);
+ free(db);
+ smdb_free_database(smdb_db);
+
+ return result;
+}
+#endif /* NDBM */
diff --git a/usr/src/cmd/sendmail/libsmutil/Makefile b/usr/src/cmd/sendmail/libsmutil/Makefile
new file mode 100644
index 0000000000..110326def3
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsmutil/Makefile
@@ -0,0 +1,63 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License, Version 1.0 only
+# (the "License"). You may not use this file except in compliance
+# with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+#
+# Copyright 2004 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# ident "%Z%%M% %I% %E% SMI"
+#
+# cmd/sendmail/libsmutil/Makefile
+#
+
+include ../../Makefile.cmd
+include ../Makefile.cmd
+
+INCPATH= -I. -I../src -I../include
+CPPFLAGS= $(INCPATH) $(RLS_DEF) $(CPPFLAGS.master)
+
+ARFLAGS= cq
+
+OBJS= cf.o debug.o err.o lockfile.o safefile.o snprintf.o
+
+SRCS= $(OBJS:%.o=%.c)
+
+libsmutil= libsmutil.a
+
+.KEEP_STATE:
+all: $(libsmutil)
+
+.PARALLEL: $(OBJS)
+
+$(libsmutil): $(OBJS)
+ $(RM) $@
+ $(AR) $(ARFLAGS) $@ $(OBJS)
+
+clean:
+ $(RM) $(OBJS) $(libsmutil)
+
+lint: lint_SRCS
+
+depend obj:
+
+install: all
+
+include ../../Makefile.targ
diff --git a/usr/src/cmd/sendmail/libsmutil/cf.c b/usr/src/cmd/sendmail/libsmutil/cf.c
new file mode 100644
index 0000000000..820a083779
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsmutil/cf.c
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2000-2002 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sendmail.h>
+SM_RCSID("@(#)$Id: cf.c,v 8.18.2.1 2002/09/24 21:48:23 ca Exp $")
+#include <sendmail/pathnames.h>
+
+/*
+** GETCFNAME -- return the name of the .cf file to use.
+**
+** Some systems (e.g., NeXT) determine this dynamically.
+**
+** For others: returns submit.cf or sendmail.cf depending
+** on the modes.
+**
+** Parameters:
+** opmode -- operation mode.
+** submitmode -- submit mode.
+** cftype -- may request a certain cf file.
+** conffile -- if set, return it.
+**
+** Returns:
+** name of .cf file.
+*/
+
+char *
+getcfname(opmode, submitmode, cftype, conffile)
+ int opmode;
+ int submitmode;
+ int cftype;
+ char *conffile;
+{
+#if NETINFO
+ char *cflocation;
+#endif /* NETINFO */
+
+ if (conffile != NULL)
+ return conffile;
+
+ if (cftype == SM_GET_SUBMIT_CF ||
+ ((submitmode != SUBMIT_UNKNOWN ||
+ opmode == MD_DELIVER ||
+ opmode == MD_ARPAFTP ||
+ opmode == MD_SMTP) &&
+ cftype != SM_GET_SENDMAIL_CF))
+ {
+ struct stat sbuf;
+ static char cf[MAXPATHLEN];
+
+#if NETINFO
+ cflocation = ni_propval("/locations", NULL, "sendmail",
+ "submit.cf", '\0');
+ if (cflocation != NULL)
+ (void) sm_strlcpy(cf, cflocation, sizeof cf);
+ else
+#endif /* NETINFO */
+ (void) sm_strlcpyn(cf, sizeof cf, 2, _DIR_SENDMAILCF,
+ "submit.cf");
+ if (cftype == SM_GET_SUBMIT_CF || stat(cf, &sbuf) == 0)
+ return cf;
+ }
+#if NETINFO
+ cflocation = ni_propval("/locations", NULL, "sendmail",
+ "sendmail.cf", '\0');
+ if (cflocation != NULL)
+ return cflocation;
+#endif /* NETINFO */
+ return _PATH_SENDMAILCF;
+}
diff --git a/usr/src/cmd/sendmail/libsmutil/debug.c b/usr/src/cmd/sendmail/libsmutil/debug.c
new file mode 100644
index 0000000000..aff57baec5
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsmutil/debug.c
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) 1999-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sendmail.h>
+
+SM_RCSID("@(#)$Id: debug.c,v 8.7 2001/06/27 21:46:54 gshapiro Exp $")
+
+unsigned char tTdvect[100]; /* trace vector */
diff --git a/usr/src/cmd/sendmail/libsmutil/err.c b/usr/src/cmd/sendmail/libsmutil/err.c
new file mode 100644
index 0000000000..1f017ba186
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsmutil/err.c
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sendmail.h>
+
+SM_RCSID("@(#)$Id: err.c,v 8.3 2001/01/24 01:27:30 gshapiro Exp $")
+
+#include <ctype.h>
+
+/*VARARGS1*/
+void
+#ifdef __STDC__
+message(const char *msg, ...)
+#else /* __STDC__ */
+message(msg, va_alist)
+ const char *msg;
+ va_dcl
+#endif /* __STDC__ */
+{
+ const char *m;
+ SM_VA_LOCAL_DECL
+
+ m = msg;
+ if (isascii(m[0]) && isdigit(m[0]) &&
+ isascii(m[1]) && isdigit(m[1]) &&
+ isascii(m[2]) && isdigit(m[2]) && m[3] == ' ')
+ m += 4;
+ SM_VA_START(ap, msg);
+ (void) vfprintf(stderr, m, ap);
+ SM_VA_END(ap);
+ (void) fprintf(stderr, "\n");
+}
+
+/*VARARGS1*/
+void
+#ifdef __STDC__
+syserr(const char *msg, ...)
+#else /* __STDC__ */
+syserr(msg, va_alist)
+ const char *msg;
+ va_dcl
+#endif /* __STDC__ */
+{
+ const char *m;
+ SM_VA_LOCAL_DECL
+
+ m = msg;
+ if (isascii(m[0]) && isdigit(m[0]) &&
+ isascii(m[1]) && isdigit(m[1]) &&
+ isascii(m[2]) && isdigit(m[2]) && m[3] == ' ')
+ m += 4;
+ SM_VA_START(ap, msg);
+ (void) vfprintf(stderr, m, ap);
+ SM_VA_END(ap);
+ (void) fprintf(stderr, "\n");
+}
diff --git a/usr/src/cmd/sendmail/libsmutil/lockfile.c b/usr/src/cmd/sendmail/libsmutil/lockfile.c
new file mode 100644
index 0000000000..2b96d5a63c
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsmutil/lockfile.c
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 1998-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sendmail.h>
+
+SM_RCSID("@(#)$Id: lockfile.c,v 8.21 2003/11/10 22:57:38 ca Exp $")
+
+
+/*
+** LOCKFILE -- lock a file using flock or (shudder) fcntl locking
+**
+** Parameters:
+** fd -- the file descriptor of the file.
+** filename -- the file name (for error messages). [unused]
+** ext -- the filename extension. [unused]
+** type -- type of the lock. Bits can be:
+** LOCK_EX -- exclusive lock.
+** LOCK_NB -- non-blocking.
+** LOCK_UN -- unlock.
+**
+** Returns:
+** true if the lock was acquired.
+** false otherwise.
+*/
+
+bool
+lockfile(fd, filename, ext, type)
+ int fd;
+ char *filename;
+ char *ext;
+ int type;
+{
+#if !HASFLOCK
+ int action;
+ struct flock lfd;
+
+ memset(&lfd, '\0', sizeof lfd);
+ if (bitset(LOCK_UN, type))
+ lfd.l_type = F_UNLCK;
+ else if (bitset(LOCK_EX, type))
+ lfd.l_type = F_WRLCK;
+ else
+ lfd.l_type = F_RDLCK;
+ if (bitset(LOCK_NB, type))
+ action = F_SETLK;
+ else
+ action = F_SETLKW;
+
+ if (fcntl(fd, action, &lfd) >= 0)
+ return true;
+
+ /*
+ ** On SunOS, if you are testing using -oQ/tmp/mqueue or
+ ** -oA/tmp/aliases or anything like that, and /tmp is mounted
+ ** as type "tmp" (that is, served from swap space), the
+ ** previous fcntl will fail with "Invalid argument" errors.
+ ** Since this is fairly common during testing, we will assume
+ ** that this indicates that the lock is successfully grabbed.
+ */
+
+ if (errno == EINVAL)
+ return true;
+
+#else /* !HASFLOCK */
+
+ if (flock(fd, type) >= 0)
+ return true;
+
+#endif /* !HASFLOCK */
+
+ return false;
+}
diff --git a/usr/src/cmd/sendmail/libsmutil/safefile.c b/usr/src/cmd/sendmail/libsmutil/safefile.c
new file mode 100644
index 0000000000..133e933845
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsmutil/safefile.c
@@ -0,0 +1,985 @@
+/*
+ * Copyright (c) 1998-2004 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sendmail.h>
+#include <sm/io.h>
+#include <sm/errstring.h>
+
+SM_RCSID("@(#)$Id: safefile.c,v 8.128 2004/09/30 18:15:49 ca Exp $")
+
+
+/*
+** SAFEFILE -- return 0 if a file exists and is safe for a user.
+**
+** Parameters:
+** fn -- filename to check.
+** uid -- user id to compare against.
+** gid -- group id to compare against.
+** user -- user name to compare against (used for group
+** sets).
+** flags -- modifiers:
+** SFF_MUSTOWN -- "uid" must own this file.
+** SFF_NOSLINK -- file cannot be a symbolic link.
+** mode -- mode bits that must match.
+** st -- if set, points to a stat structure that will
+** get the stat info for the file.
+**
+** Returns:
+** 0 if fn exists, is owned by uid, and matches mode.
+** An errno otherwise. The actual errno is cleared.
+**
+** Side Effects:
+** none.
+*/
+
+int
+safefile(fn, uid, gid, user, flags, mode, st)
+ char *fn;
+ UID_T uid;
+ GID_T gid;
+ char *user;
+ long flags;
+ int mode;
+ struct stat *st;
+{
+ register char *p;
+ register struct group *gr = NULL;
+ int file_errno = 0;
+ bool checkpath;
+ struct stat stbuf;
+ struct stat fstbuf;
+ char fbuf[MAXPATHLEN];
+
+ if (tTd(44, 4))
+ sm_dprintf("safefile(%s, uid=%d, gid=%d, flags=%lx, mode=%o):\n",
+ fn, (int) uid, (int) gid, flags, mode);
+ errno = 0;
+ if (sm_strlcpy(fbuf, fn, sizeof fbuf) >= sizeof fbuf)
+ {
+ if (tTd(44, 4))
+ sm_dprintf("\tpathname too long\n");
+ return ENAMETOOLONG;
+ }
+ fn = fbuf;
+ if (st == NULL)
+ st = &fstbuf;
+
+ /* ignore SFF_SAFEDIRPATH if we are debugging */
+ if (RealUid != 0 && RunAsUid == RealUid)
+ flags &= ~SFF_SAFEDIRPATH;
+
+ /* first check to see if the file exists at all */
+# if HASLSTAT
+ if ((bitset(SFF_NOSLINK, flags) ? lstat(fn, st)
+ : stat(fn, st)) < 0)
+# else /* HASLSTAT */
+ if (stat(fn, st) < 0)
+# endif /* HASLSTAT */
+ {
+ file_errno = errno;
+ }
+ else if (bitset(SFF_SETUIDOK, flags) &&
+ !bitset(S_IXUSR|S_IXGRP|S_IXOTH, st->st_mode) &&
+ S_ISREG(st->st_mode))
+ {
+ /*
+ ** If final file is set-user-ID, run as the owner of that
+ ** file. Gotta be careful not to reveal anything too
+ ** soon here!
+ */
+
+# ifdef SUID_ROOT_FILES_OK
+ if (bitset(S_ISUID, st->st_mode))
+# else /* SUID_ROOT_FILES_OK */
+ if (bitset(S_ISUID, st->st_mode) && st->st_uid != 0 &&
+ st->st_uid != TrustedUid)
+# endif /* SUID_ROOT_FILES_OK */
+ {
+ uid = st->st_uid;
+ user = NULL;
+ }
+# ifdef SUID_ROOT_FILES_OK
+ if (bitset(S_ISGID, st->st_mode))
+# else /* SUID_ROOT_FILES_OK */
+ if (bitset(S_ISGID, st->st_mode) && st->st_gid != 0)
+# endif /* SUID_ROOT_FILES_OK */
+ gid = st->st_gid;
+ }
+
+ checkpath = !bitset(SFF_NOPATHCHECK, flags) ||
+ (uid == 0 && !bitset(SFF_ROOTOK|SFF_OPENASROOT, flags));
+ if (bitset(SFF_NOWLINK, flags) && !bitset(SFF_SAFEDIRPATH, flags))
+ {
+ int ret;
+
+ /* check the directory */
+ p = strrchr(fn, '/');
+ if (p == NULL)
+ {
+ ret = safedirpath(".", uid, gid, user,
+ flags|SFF_SAFEDIRPATH, 0, 0);
+ }
+ else
+ {
+ *p = '\0';
+ ret = safedirpath(fn, uid, gid, user,
+ flags|SFF_SAFEDIRPATH, 0, 0);
+ *p = '/';
+ }
+ if (ret == 0)
+ {
+ /* directory is safe */
+ checkpath = false;
+ }
+ else
+ {
+# if HASLSTAT
+ /* Need lstat() information if called stat() before */
+ if (!bitset(SFF_NOSLINK, flags) && lstat(fn, st) < 0)
+ {
+ ret = errno;
+ if (tTd(44, 4))
+ sm_dprintf("\t%s\n", sm_errstring(ret));
+ return ret;
+ }
+# endif /* HASLSTAT */
+ /* directory is writable: disallow links */
+ flags |= SFF_NOLINK;
+ }
+ }
+
+ if (checkpath)
+ {
+ int ret;
+
+ p = strrchr(fn, '/');
+ if (p == NULL)
+ {
+ ret = safedirpath(".", uid, gid, user, flags, 0, 0);
+ }
+ else
+ {
+ *p = '\0';
+ ret = safedirpath(fn, uid, gid, user, flags, 0, 0);
+ *p = '/';
+ }
+ if (ret != 0)
+ return ret;
+ }
+
+ /*
+ ** If the target file doesn't exist, check the directory to
+ ** ensure that it is writable by this user.
+ */
+
+ if (file_errno != 0)
+ {
+ int ret = file_errno;
+ char *dir = fn;
+
+ if (tTd(44, 4))
+ sm_dprintf("\t%s\n", sm_errstring(ret));
+
+ errno = 0;
+ if (!bitset(SFF_CREAT, flags) || file_errno != ENOENT)
+ return ret;
+
+ /* check to see if legal to create the file */
+ p = strrchr(dir, '/');
+ if (p == NULL)
+ dir = ".";
+ else if (p == dir)
+ dir = "/";
+ else
+ *p = '\0';
+ if (stat(dir, &stbuf) >= 0)
+ {
+ int md = S_IWRITE|S_IEXEC;
+
+ ret = 0;
+ if (stbuf.st_uid == uid)
+ /* EMPTY */
+ ;
+ else if (uid == 0 && stbuf.st_uid == TrustedUid)
+ /* EMPTY */
+ ;
+ else
+ {
+ md >>= 3;
+ if (stbuf.st_gid == gid)
+ /* EMPTY */
+ ;
+# ifndef NO_GROUP_SET
+ else if (user != NULL && !DontInitGroups &&
+ ((gr != NULL &&
+ gr->gr_gid == stbuf.st_gid) ||
+ (gr = getgrgid(stbuf.st_gid)) != NULL))
+ {
+ register char **gp;
+
+ for (gp = gr->gr_mem; *gp != NULL; gp++)
+ if (strcmp(*gp, user) == 0)
+ break;
+ if (*gp == NULL)
+ md >>= 3;
+ }
+# endif /* ! NO_GROUP_SET */
+ else
+ md >>= 3;
+ }
+ if ((stbuf.st_mode & md) != md)
+ ret = errno = EACCES;
+ }
+ else
+ ret = errno;
+ if (tTd(44, 4))
+ sm_dprintf("\t[final dir %s uid %d mode %lo] %s\n",
+ dir, (int) stbuf.st_uid,
+ (unsigned long) stbuf.st_mode,
+ sm_errstring(ret));
+ if (p != NULL)
+ *p = '/';
+ st->st_mode = ST_MODE_NOFILE;
+ return ret;
+ }
+
+# ifdef S_ISLNK
+ if (bitset(SFF_NOSLINK, flags) && S_ISLNK(st->st_mode))
+ {
+ if (tTd(44, 4))
+ sm_dprintf("\t[slink mode %lo]\tE_SM_NOSLINK\n",
+ (unsigned long) st->st_mode);
+ return E_SM_NOSLINK;
+ }
+# endif /* S_ISLNK */
+ if (bitset(SFF_REGONLY, flags) && !S_ISREG(st->st_mode))
+ {
+ if (tTd(44, 4))
+ sm_dprintf("\t[non-reg mode %lo]\tE_SM_REGONLY\n",
+ (unsigned long) st->st_mode);
+ return E_SM_REGONLY;
+ }
+ if (bitset(SFF_NOGWFILES, flags) &&
+ bitset(S_IWGRP, st->st_mode))
+ {
+ if (tTd(44, 4))
+ sm_dprintf("\t[write bits %lo]\tE_SM_GWFILE\n",
+ (unsigned long) st->st_mode);
+ return E_SM_GWFILE;
+ }
+ if (bitset(SFF_NOWWFILES, flags) &&
+ bitset(S_IWOTH, st->st_mode))
+ {
+ if (tTd(44, 4))
+ sm_dprintf("\t[write bits %lo]\tE_SM_WWFILE\n",
+ (unsigned long) st->st_mode);
+ return E_SM_WWFILE;
+ }
+ if (bitset(SFF_NOGRFILES, flags) && bitset(S_IRGRP, st->st_mode))
+ {
+ if (tTd(44, 4))
+ sm_dprintf("\t[read bits %lo]\tE_SM_GRFILE\n",
+ (unsigned long) st->st_mode);
+ return E_SM_GRFILE;
+ }
+ if (bitset(SFF_NOWRFILES, flags) && bitset(S_IROTH, st->st_mode))
+ {
+ if (tTd(44, 4))
+ sm_dprintf("\t[read bits %lo]\tE_SM_WRFILE\n",
+ (unsigned long) st->st_mode);
+ return E_SM_WRFILE;
+ }
+ if (!bitset(SFF_EXECOK, flags) &&
+ bitset(S_IWUSR|S_IWGRP|S_IWOTH, mode) &&
+ bitset(S_IXUSR|S_IXGRP|S_IXOTH, st->st_mode))
+ {
+ if (tTd(44, 4))
+ sm_dprintf("\t[exec bits %lo]\tE_SM_ISEXEC\n",
+ (unsigned long) st->st_mode);
+ return E_SM_ISEXEC;
+ }
+ if (bitset(SFF_NOHLINK, flags) && st->st_nlink != 1)
+ {
+ if (tTd(44, 4))
+ sm_dprintf("\t[link count %d]\tE_SM_NOHLINK\n",
+ (int) st->st_nlink);
+ return E_SM_NOHLINK;
+ }
+
+ if (uid == 0 && bitset(SFF_OPENASROOT, flags))
+ /* EMPTY */
+ ;
+ else if (uid == 0 && !bitset(SFF_ROOTOK, flags))
+ mode >>= 6;
+ else if (st->st_uid == uid)
+ /* EMPTY */
+ ;
+ else if (uid == 0 && st->st_uid == TrustedUid)
+ /* EMPTY */
+ ;
+ else
+ {
+ mode >>= 3;
+ if (st->st_gid == gid)
+ /* EMPTY */
+ ;
+# ifndef NO_GROUP_SET
+ else if (user != NULL && !DontInitGroups &&
+ ((gr != NULL && gr->gr_gid == st->st_gid) ||
+ (gr = getgrgid(st->st_gid)) != NULL))
+ {
+ register char **gp;
+
+ for (gp = gr->gr_mem; *gp != NULL; gp++)
+ if (strcmp(*gp, user) == 0)
+ break;
+ if (*gp == NULL)
+ mode >>= 3;
+ }
+# endif /* ! NO_GROUP_SET */
+ else
+ mode >>= 3;
+ }
+ if (tTd(44, 4))
+ sm_dprintf("\t[uid %d, nlink %d, stat %lo, mode %lo] ",
+ (int) st->st_uid, (int) st->st_nlink,
+ (unsigned long) st->st_mode, (unsigned long) mode);
+ if ((st->st_uid == uid || st->st_uid == 0 ||
+ st->st_uid == TrustedUid ||
+ !bitset(SFF_MUSTOWN, flags)) &&
+ (st->st_mode & mode) == mode)
+ {
+ if (tTd(44, 4))
+ sm_dprintf("\tOK\n");
+ return 0;
+ }
+ if (tTd(44, 4))
+ sm_dprintf("\tEACCES\n");
+ return EACCES;
+}
+/*
+** SAFEDIRPATH -- check to make sure a path to a directory is safe
+**
+** Safe means not writable and owned by the right folks.
+**
+** Parameters:
+** fn -- filename to check.
+** uid -- user id to compare against.
+** gid -- group id to compare against.
+** user -- user name to compare against (used for group
+** sets).
+** flags -- modifiers:
+** SFF_ROOTOK -- ok to use root permissions to open.
+** SFF_SAFEDIRPATH -- writable directories are considered
+** to be fatal errors.
+** level -- symlink recursive level.
+** offset -- offset into fn to start checking from.
+**
+** Returns:
+** 0 -- if the directory path is "safe".
+** else -- an error number associated with the path.
+*/
+
+int
+safedirpath(fn, uid, gid, user, flags, level, offset)
+ char *fn;
+ UID_T uid;
+ GID_T gid;
+ char *user;
+ long flags;
+ int level;
+ int offset;
+{
+ int ret = 0;
+ int mode = S_IWOTH;
+ char save = '\0';
+ char *saveptr = NULL;
+ char *p, *enddir;
+ register struct group *gr = NULL;
+ char s[MAXLINKPATHLEN];
+ struct stat stbuf;
+
+ /* make sure we aren't in a symlink loop */
+ if (level > MAXSYMLINKS)
+ return ELOOP;
+
+ if (level < 0 || offset < 0 || offset > strlen(fn))
+ return EINVAL;
+
+ /* special case root directory */
+ if (*fn == '\0')
+ fn = "/";
+
+ if (tTd(44, 4))
+ sm_dprintf("safedirpath(%s, uid=%ld, gid=%ld, flags=%lx, level=%d, offset=%d):\n",
+ fn, (long) uid, (long) gid, flags, level, offset);
+
+ if (!bitnset(DBS_GROUPWRITABLEDIRPATHSAFE, DontBlameSendmail))
+ mode |= S_IWGRP;
+
+ /* Make a modifiable copy of the filename */
+ if (sm_strlcpy(s, fn, sizeof s) >= sizeof s)
+ return EINVAL;
+
+ p = s + offset;
+ while (p != NULL)
+ {
+ /* put back character */
+ if (saveptr != NULL)
+ {
+ *saveptr = save;
+ saveptr = NULL;
+ p++;
+ }
+
+ if (*p == '\0')
+ break;
+
+ p = strchr(p, '/');
+
+ /* Special case for root directory */
+ if (p == s)
+ {
+ save = *(p + 1);
+ saveptr = p + 1;
+ *(p + 1) = '\0';
+ }
+ else if (p != NULL)
+ {
+ save = *p;
+ saveptr = p;
+ *p = '\0';
+ }
+
+ /* Heuristic: . and .. have already been checked */
+ enddir = strrchr(s, '/');
+ if (enddir != NULL &&
+ (strcmp(enddir, "/..") == 0 ||
+ strcmp(enddir, "/.") == 0))
+ continue;
+
+ if (tTd(44, 20))
+ sm_dprintf("\t[dir %s]\n", s);
+
+# if HASLSTAT
+ ret = lstat(s, &stbuf);
+# else /* HASLSTAT */
+ ret = stat(s, &stbuf);
+# endif /* HASLSTAT */
+ if (ret < 0)
+ {
+ ret = errno;
+ break;
+ }
+
+# ifdef S_ISLNK
+ /* Follow symlinks */
+ if (S_ISLNK(stbuf.st_mode))
+ {
+ int linklen;
+ char *target;
+ char buf[MAXPATHLEN];
+ char fullbuf[MAXLINKPATHLEN];
+
+ memset(buf, '\0', sizeof buf);
+ linklen = readlink(s, buf, sizeof buf);
+ if (linklen < 0)
+ {
+ ret = errno;
+ break;
+ }
+ if (linklen >= sizeof buf)
+ {
+ /* file name too long for buffer */
+ ret = errno = EINVAL;
+ break;
+ }
+
+ offset = 0;
+ if (*buf == '/')
+ {
+ target = buf;
+
+ /* If path is the same, avoid rechecks */
+ while (s[offset] == buf[offset] &&
+ s[offset] != '\0')
+ offset++;
+
+ if (s[offset] == '\0' && buf[offset] == '\0')
+ {
+ /* strings match, symlink loop */
+ return ELOOP;
+ }
+
+ /* back off from the mismatch */
+ if (offset > 0)
+ offset--;
+
+ /* Make sure we are at a directory break */
+ if (offset > 0 &&
+ s[offset] != '/' &&
+ s[offset] != '\0')
+ {
+ while (buf[offset] != '/' &&
+ offset > 0)
+ offset--;
+ }
+ if (offset > 0 &&
+ s[offset] == '/' &&
+ buf[offset] == '/')
+ {
+ /* Include the trailing slash */
+ offset++;
+ }
+ }
+ else
+ {
+ char *sptr;
+
+ sptr = strrchr(s, '/');
+ if (sptr != NULL)
+ {
+ *sptr = '\0';
+ offset = sptr + 1 - s;
+ if (sm_strlcpyn(fullbuf,
+ sizeof fullbuf, 2,
+ s, "/") >=
+ sizeof fullbuf ||
+ sm_strlcat(fullbuf, buf,
+ sizeof fullbuf) >=
+ sizeof fullbuf)
+ {
+ ret = EINVAL;
+ break;
+ }
+ *sptr = '/';
+ }
+ else
+ {
+ if (sm_strlcpy(fullbuf, buf,
+ sizeof fullbuf) >=
+ sizeof fullbuf)
+ {
+ ret = EINVAL;
+ break;
+ }
+ }
+ target = fullbuf;
+ }
+ ret = safedirpath(target, uid, gid, user, flags,
+ level + 1, offset);
+ if (ret != 0)
+ break;
+
+ /* Don't check permissions on the link file itself */
+ continue;
+ }
+#endif /* S_ISLNK */
+
+ if ((uid == 0 || bitset(SFF_SAFEDIRPATH, flags)) &&
+#ifdef S_ISVTX
+ !(bitnset(DBS_TRUSTSTICKYBIT, DontBlameSendmail) &&
+ bitset(S_ISVTX, stbuf.st_mode)) &&
+#endif /* S_ISVTX */
+ bitset(mode, stbuf.st_mode))
+ {
+ if (tTd(44, 4))
+ sm_dprintf("\t[dir %s] mode %lo ",
+ s, (unsigned long) stbuf.st_mode);
+ if (bitset(SFF_SAFEDIRPATH, flags))
+ {
+ if (bitset(S_IWOTH, stbuf.st_mode))
+ ret = E_SM_WWDIR;
+ else
+ ret = E_SM_GWDIR;
+ if (tTd(44, 4))
+ sm_dprintf("FATAL\n");
+ break;
+ }
+ if (tTd(44, 4))
+ sm_dprintf("WARNING\n");
+ if (Verbose > 1)
+ message("051 WARNING: %s writable directory %s",
+ bitset(S_IWOTH, stbuf.st_mode)
+ ? "World"
+ : "Group",
+ s);
+ }
+ if (uid == 0 && !bitset(SFF_ROOTOK|SFF_OPENASROOT, flags))
+ {
+ if (bitset(S_IXOTH, stbuf.st_mode))
+ continue;
+ ret = EACCES;
+ break;
+ }
+
+ /*
+ ** Let OS determine access to file if we are not
+ ** running as a privileged user. This allows ACLs
+ ** to work. Also, if opening as root, assume we can
+ ** scan the directory.
+ */
+ if (geteuid() != 0 || bitset(SFF_OPENASROOT, flags))
+ continue;
+
+ if (stbuf.st_uid == uid &&
+ bitset(S_IXUSR, stbuf.st_mode))
+ continue;
+ if (stbuf.st_gid == gid &&
+ bitset(S_IXGRP, stbuf.st_mode))
+ continue;
+# ifndef NO_GROUP_SET
+ if (user != NULL && !DontInitGroups &&
+ ((gr != NULL && gr->gr_gid == stbuf.st_gid) ||
+ (gr = getgrgid(stbuf.st_gid)) != NULL))
+ {
+ register char **gp;
+
+ for (gp = gr->gr_mem; gp != NULL && *gp != NULL; gp++)
+ if (strcmp(*gp, user) == 0)
+ break;
+ if (gp != NULL && *gp != NULL &&
+ bitset(S_IXGRP, stbuf.st_mode))
+ continue;
+ }
+# endif /* ! NO_GROUP_SET */
+ if (!bitset(S_IXOTH, stbuf.st_mode))
+ {
+ ret = EACCES;
+ break;
+ }
+ }
+ if (tTd(44, 4))
+ sm_dprintf("\t[dir %s] %s\n", fn,
+ ret == 0 ? "OK" : sm_errstring(ret));
+ return ret;
+}
+/*
+** SAFEOPEN -- do a file open with extra checking
+**
+** Parameters:
+** fn -- the file name to open.
+** omode -- the open-style mode flags.
+** cmode -- the create-style mode flags.
+** sff -- safefile flags.
+**
+** Returns:
+** Same as open.
+*/
+
+int
+safeopen(fn, omode, cmode, sff)
+ char *fn;
+ int omode;
+ int cmode;
+ long sff;
+{
+#if !NOFTRUNCATE
+ bool truncate;
+#endif /* !NOFTRUNCATE */
+ int rval;
+ int fd;
+ int smode;
+ struct stat stb;
+
+ if (tTd(44, 10))
+ sm_dprintf("safeopen: fn=%s, omode=%x, cmode=%x, sff=%lx\n",
+ fn, omode, cmode, sff);
+
+ if (bitset(O_CREAT, omode))
+ sff |= SFF_CREAT;
+ omode &= ~O_CREAT;
+ smode = 0;
+ switch (omode & O_ACCMODE)
+ {
+ case O_RDONLY:
+ smode = S_IREAD;
+ break;
+
+ case O_WRONLY:
+ smode = S_IWRITE;
+ break;
+
+ case O_RDWR:
+ smode = S_IREAD|S_IWRITE;
+ break;
+
+ default:
+ smode = 0;
+ break;
+ }
+ if (bitset(SFF_OPENASROOT, sff))
+ rval = safefile(fn, RunAsUid, RunAsGid, RunAsUserName,
+ sff, smode, &stb);
+ else
+ rval = safefile(fn, RealUid, RealGid, RealUserName,
+ sff, smode, &stb);
+ if (rval != 0)
+ {
+ errno = rval;
+ return -1;
+ }
+ if (stb.st_mode == ST_MODE_NOFILE && bitset(SFF_CREAT, sff))
+ omode |= O_CREAT | (bitset(SFF_NOTEXCL, sff) ? 0 : O_EXCL);
+ else if (bitset(SFF_CREAT, sff) && bitset(O_EXCL, omode))
+ {
+ /* The file exists so an exclusive create would fail */
+ errno = EEXIST;
+ return -1;
+ }
+
+#if !NOFTRUNCATE
+ truncate = bitset(O_TRUNC, omode);
+ if (truncate)
+ omode &= ~O_TRUNC;
+#endif /* !NOFTRUNCATE */
+
+ fd = dfopen(fn, omode, cmode, sff);
+ if (fd < 0)
+ return fd;
+ if (filechanged(fn, fd, &stb))
+ {
+ syserr("554 5.3.0 cannot open: file %s changed after open", fn);
+ (void) close(fd);
+ errno = E_SM_FILECHANGE;
+ return -1;
+ }
+
+#if !NOFTRUNCATE
+ if (truncate &&
+ ftruncate(fd, (off_t) 0) < 0)
+ {
+ int save_errno;
+
+ save_errno = errno;
+ syserr("554 5.3.0 cannot open: file %s could not be truncated",
+ fn);
+ (void) close(fd);
+ errno = save_errno;
+ return -1;
+ }
+#endif /* !NOFTRUNCATE */
+
+ return fd;
+}
+/*
+** SAFEFOPEN -- do a file open with extra checking
+**
+** Parameters:
+** fn -- the file name to open.
+** omode -- the open-style mode flags.
+** cmode -- the create-style mode flags.
+** sff -- safefile flags.
+**
+** Returns:
+** Same as fopen.
+*/
+
+SM_FILE_T *
+safefopen(fn, omode, cmode, sff)
+ char *fn;
+ int omode;
+ int cmode;
+ long sff;
+{
+ int fd;
+ int save_errno;
+ SM_FILE_T *fp;
+ int fmode;
+
+ switch (omode & O_ACCMODE)
+ {
+ case O_RDONLY:
+ fmode = SM_IO_RDONLY;
+ break;
+
+ case O_WRONLY:
+ if (bitset(O_APPEND, omode))
+ fmode = SM_IO_APPEND;
+ else
+ fmode = SM_IO_WRONLY;
+ break;
+
+ case O_RDWR:
+ if (bitset(O_TRUNC, omode))
+ fmode = SM_IO_RDWRTR;
+ else if (bitset(O_APPEND, omode))
+ fmode = SM_IO_APPENDRW;
+ else
+ fmode = SM_IO_RDWR;
+ break;
+
+ default:
+ syserr("554 5.3.5 safefopen: unknown omode %o", omode);
+ fmode = 0;
+ }
+ fd = safeopen(fn, omode, cmode, sff);
+ if (fd < 0)
+ {
+ save_errno = errno;
+ if (tTd(44, 10))
+ sm_dprintf("safefopen: safeopen failed: %s\n",
+ sm_errstring(errno));
+ errno = save_errno;
+ return NULL;
+ }
+ fp = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT,
+ (void *) &fd, fmode, NULL);
+ if (fp != NULL)
+ return fp;
+
+ save_errno = errno;
+ if (tTd(44, 10))
+ {
+ sm_dprintf("safefopen: fdopen(%s, %d) failed: omode=%x, sff=%lx, err=%s\n",
+ fn, fmode, omode, sff, sm_errstring(errno));
+ }
+ (void) close(fd);
+ errno = save_errno;
+ return NULL;
+}
+/*
+** FILECHANGED -- check to see if file changed after being opened
+**
+** Parameters:
+** fn -- pathname of file to check.
+** fd -- file descriptor to check.
+** stb -- stat structure from before open.
+**
+** Returns:
+** true -- if a problem was detected.
+** false -- if this file is still the same.
+*/
+
+bool
+filechanged(fn, fd, stb)
+ char *fn;
+ int fd;
+ struct stat *stb;
+{
+ struct stat sta;
+
+ if (stb->st_mode == ST_MODE_NOFILE)
+ {
+# if HASLSTAT && BOGUS_O_EXCL
+ /* only necessary if exclusive open follows symbolic links */
+ if (lstat(fn, stb) < 0 || stb->st_nlink != 1)
+ return true;
+# else /* HASLSTAT && BOGUS_O_EXCL */
+ return false;
+# endif /* HASLSTAT && BOGUS_O_EXCL */
+ }
+ if (fstat(fd, &sta) < 0)
+ return true;
+
+ if (sta.st_nlink != stb->st_nlink ||
+ sta.st_dev != stb->st_dev ||
+ sta.st_ino != stb->st_ino ||
+# if HAS_ST_GEN && 0 /* AFS returns garbage in st_gen */
+ sta.st_gen != stb->st_gen ||
+# endif /* HAS_ST_GEN && 0 */
+ sta.st_uid != stb->st_uid ||
+ sta.st_gid != stb->st_gid)
+ {
+ if (tTd(44, 8))
+ {
+ sm_dprintf("File changed after opening:\n");
+ sm_dprintf(" nlink = %ld/%ld\n",
+ (long) stb->st_nlink, (long) sta.st_nlink);
+ sm_dprintf(" dev = %ld/%ld\n",
+ (long) stb->st_dev, (long) sta.st_dev);
+ sm_dprintf(" ino = %llu/%llu\n",
+ (ULONGLONG_T) stb->st_ino,
+ (ULONGLONG_T) sta.st_ino);
+# if HAS_ST_GEN
+ sm_dprintf(" gen = %ld/%ld\n",
+ (long) stb->st_gen, (long) sta.st_gen);
+# endif /* HAS_ST_GEN */
+ sm_dprintf(" uid = %ld/%ld\n",
+ (long) stb->st_uid, (long) sta.st_uid);
+ sm_dprintf(" gid = %ld/%ld\n",
+ (long) stb->st_gid, (long) sta.st_gid);
+ }
+ return true;
+ }
+
+ return false;
+}
+/*
+** DFOPEN -- determined file open
+**
+** This routine has the semantics of open, except that it will
+** keep trying a few times to make this happen. The idea is that
+** on very loaded systems, we may run out of resources (inodes,
+** whatever), so this tries to get around it.
+*/
+
+int
+dfopen(filename, omode, cmode, sff)
+ char *filename;
+ int omode;
+ int cmode;
+ long sff;
+{
+ register int tries;
+ int fd = -1;
+ struct stat st;
+
+ for (tries = 0; tries < 10; tries++)
+ {
+ (void) sleep((unsigned) (10 * tries));
+ errno = 0;
+ fd = open(filename, omode, cmode);
+ if (fd >= 0)
+ break;
+ switch (errno)
+ {
+ case ENFILE: /* system file table full */
+ case EINTR: /* interrupted syscall */
+#ifdef ETXTBSY
+ case ETXTBSY: /* Apollo: net file locked */
+#endif /* ETXTBSY */
+ continue;
+ }
+ break;
+ }
+ if (!bitset(SFF_NOLOCK, sff) &&
+ fd >= 0 &&
+ fstat(fd, &st) >= 0 &&
+ S_ISREG(st.st_mode))
+ {
+ int locktype;
+
+ /* lock the file to avoid accidental conflicts */
+ if ((omode & O_ACCMODE) != O_RDONLY)
+ locktype = LOCK_EX;
+ else
+ locktype = LOCK_SH;
+ if (bitset(SFF_NBLOCK, sff))
+ locktype |= LOCK_NB;
+
+ if (!lockfile(fd, filename, NULL, locktype))
+ {
+ int save_errno = errno;
+
+ (void) close(fd);
+ fd = -1;
+ errno = save_errno;
+ }
+ else
+ errno = 0;
+ }
+ return fd;
+}
diff --git a/usr/src/cmd/sendmail/libsmutil/snprintf.c b/usr/src/cmd/sendmail/libsmutil/snprintf.c
new file mode 100644
index 0000000000..223668edda
--- /dev/null
+++ b/usr/src/cmd/sendmail/libsmutil/snprintf.c
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 1998-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sendmail.h>
+
+SM_RCSID("@(#)$Id: snprintf.c,v 8.41 2001/08/28 23:07:01 gshapiro Exp $")
+
+ /*
+** SHORTENSTRING -- return short version of a string
+**
+** If the string is already short, just return it. If it is too
+** long, return the head and tail of the string.
+**
+** Parameters:
+** s -- the string to shorten.
+** m -- the max length of the string (strlen()).
+**
+** Returns:
+** Either s or a short version of s.
+*/
+
+char *
+shortenstring(s, m)
+ register const char *s;
+ size_t m;
+{
+ size_t l;
+ static char buf[MAXSHORTSTR + 1];
+
+ l = strlen(s);
+ if (l < m)
+ return (char *) s;
+ if (m > MAXSHORTSTR)
+ m = MAXSHORTSTR;
+ else if (m < 10)
+ {
+ if (m < 5)
+ {
+ (void) sm_strlcpy(buf, s, m + 1);
+ return buf;
+ }
+ (void) sm_strlcpy(buf, s, m - 2);
+ (void) sm_strlcat(buf, "...", sizeof buf);
+ return buf;
+ }
+ m = (m - 3) / 2;
+ (void) sm_strlcpy(buf, s, m + 1);
+ (void) sm_strlcat2(buf, "...", s + l - m, sizeof buf);
+ return buf;
+}
diff --git a/usr/src/cmd/sendmail/src/Makefile b/usr/src/cmd/sendmail/src/Makefile
new file mode 100644
index 0000000000..6fdba589cf
--- /dev/null
+++ b/usr/src/cmd/sendmail/src/Makefile
@@ -0,0 +1,104 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License, Version 1.0 only
+# (the "License"). You may not use this file except in compliance
+# with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+#
+# Copyright 2005 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# ident "%Z%%M% %I% %E% SMI"
+#
+# cmd/sendmail/src/Makefile
+#
+
+PROG= sendmail
+
+include ../../Makefile.cmd
+include ../Makefile.cmd
+
+# additional .o files needed
+OBJADD=sun_compat.o
+
+OBJS= alias.o arpadate.o bf.o collect.o conf.o control.o convtime.o daemon.o \
+ deliver.o domain.o envelope.o err.o headers.o macro.o main.o map.o \
+ mci.o milter.o mime.o parseaddr.o queue.o ratectrl.o readcf.o \
+ recipient.o savemail.o sfsasl.o sm_resolve.o srvrsmtp.o stab.o stats.o \
+ sysexits.o tls.o trace.o udb.o usersmtp.o util.o version.o ${OBJADD}
+
+SRCS= $(OBJS:%.o=%.c)
+
+LDFLAGS += -R$(SFW_ROOT)/lib
+
+# EXPORT DELETE START
+CRYPTOLIBS= -lssl -lcrypto
+# EXPORT DELETE END
+LDLIBS += ../libsmutil/libsmutil.a ../libsm/libsm.a -lresolv -lsocket \
+ -lnsl ../db/libdb.a -lldap -lsldap -L$(ROOTSFWLIB) -lwrap \
+ $(CRYPTOLIBS)
+
+INCPATH= -I. -I../include -I../db -I$(ROOTSFWINCLUDE)
+
+# EXPORT DELETE START
+CRYPTOENVDEF= -DSTARTTLS
+# EXPORT DELETE END
+ENVDEF= $(RLS_DEF) -DNETINET6 -DTCPWRAPPERS $(CRYPTOENVDEF)
+SUNENVDEF= -DSUN_EXTENSIONS -DVENDOR_DEFAULT=VENDOR_SUN \
+ -DSUN_INIT_DOMAIN -DSUN_SIMPLIFIED_LDAP
+
+CPPFLAGS = $(INCPATH) $(ENVDEF) $(SUNENVDEF) $(DBMDEF) $(CPPFLAGS.master)
+
+FILEMODE= 2555
+OWNER= root
+GROUP= smmsp
+
+ROOTSYMLINKS= $(ROOTUSRSBIN)/newaliases $(ROOTUSRSBIN)/sendmail
+
+# build rule
+#
+
+.KEEP_STATE:
+all: $(PROG)
+
+.PARALLEL: $(OBJS)
+
+$(PROG): $(OBJS) ../libsmutil/libsmutil.a ../libsm/libsm.a ../db/libdb.a
+ $(LINK.c) -o $@ $(OBJS) $(LDLIBS)
+ $(POST_PROCESS)
+
+install: $(ROOTLIBPROG) $(ROOTSYMLINKS)
+
+$(ROOTSYMLINKS):
+ $(RM) $@; $(SYMLINK) ../lib/sendmail $@
+
+clean:
+ $(RM) $(PROG) $(OBJS)
+
+lint: lint_SRCS
+
+# EXPORT DELETE START
+EXPORT_SRC:
+ $(RM) Makefile+
+ $(SED) -e "/^# EXPORT DELETE START/,/^# EXPORT DELETE END/d" \
+ < Makefile > Makefile+
+ $(MV) Makefile+ Makefile
+ $(CHMOD) 444 Makefile
+# EXPORT DELETE END
+
+include ../../Makefile.targ
diff --git a/usr/src/cmd/sendmail/src/READ_ME b/usr/src/cmd/sendmail/src/READ_ME
new file mode 100644
index 0000000000..da5fa109c1
--- /dev/null
+++ b/usr/src/cmd/sendmail/src/READ_ME
@@ -0,0 +1,92 @@
+#
+# Copyright 2005 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License, Version 1.0 only
+# (the "License"). You may not use this file except in compliance
+# with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+#
+#ident "%Z%%M% %I% %E% SMI"
+
+This directory contains the source files for sendmail.
+
+The following list describes the files in this directory:
+
+Makefile The makefile used here.
+READ_ME This file.
+alias.c Does name aliasing in all forms.
+arpadate.c A subroutine which creates ARPANET standard dates.
+bf.c Routines to implement memory-buffered file system using
+ hooks provided by libsm now.
+bf.h Buffered file I/O function declarations and
+ data structure and function declarations for bf.c.
+collect.c The routine that actually reads the mail into a temp
+ file. It also does a certain amount of parsing of
+ the header, etc.
+conf.c The configuration file. This contains information
+ that is presumed to be quite static and non-
+ controversial, or code compiled in for efficiency
+ reasons. Most of the configuration is in sendmail.cf.
+conf.h Configuration that must be known everywhere.
+control.c Code for sendmail's daemon control socket.
+convtime.c A routine to sanely process times.
+daemon.c Routines to implement daemon mode.
+deliver.c Routines to deliver mail.
+domain.c Routines that interface with DNS (the Domain Name System).
+envelope.c Routines to manipulate the envelope structure.
+err.c Routines to print error messages.
+headers.c Routines to process message headers.
+macro.c The macro expander. This is used internally to
+ insert information from the configuration file.
+main.c The main routine to sendmail. This file also
+ contains some miscellaneous routines.
+map.c Key database map routines.
+mci.c Routines that handle mail connection information caching.
+milter.c MTA portions of the mail filter API.
+mime.c Multipurpose Internet Mail Extensions conversion routines.
+parseaddr.c The routines which do address parsing.
+queue.c Routines to implement message queueing.
+readcf.c The routine that reads the configuration file and
+ translates it to internal form.
+recipient.c Routines that manipulate the recipient list.
+savemail.c Routines which save the letter on processing errors.
+sendmail.h Main header file for sendmail.
+sm_resolve.c Routines for DNS lookups (for DNS map type).
+sm_resolve.h Header file for sm_resolve.c.
+srvrsmtp.c Routines to implement server SMTP.
+stab.c Routines to manage the symbol table.
+stats.c Routines to collect and post the statistics.
+statusd_shm.h Data structure and function declarations for shmticklib.c.
+sun_compat.c Lots of hacks, mostly for backwards compatibility.
+sysexits.c List of error messages associated with error codes
+ in sysexits.h.
+sysexits.h List of error codes for systems that lack their own.
+timers.h Header file for timer stuff.
+trace.c The trace package. These routines allow setting and
+ testing of trace flags with a high granularity.
+trace.h Definitions needed for the trace package.
+udb.c The user database interface module.
+usersmtp.c Routines to implement user SMTP.
+util.c Some general purpose routines used by sendmail.
+version.c The current version of sendmail.
+
+Eric Allman
+
+(Version %I%, last update %E%)
diff --git a/usr/src/cmd/sendmail/src/alias.c b/usr/src/cmd/sendmail/src/alias.c
new file mode 100644
index 0000000000..559d44a928
--- /dev/null
+++ b/usr/src/cmd/sendmail/src/alias.c
@@ -0,0 +1,1016 @@
+/*
+ * Copyright (c) 1998-2003 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sendmail.h>
+
+SM_RCSID("@(#)$Id: alias.c,v 8.217 2003/07/28 17:47:18 ca Exp $")
+
+#define SEPARATOR ':'
+# define ALIAS_SPEC_SEPARATORS " ,/:"
+
+static MAP *AliasFileMap = NULL; /* the actual aliases.files map */
+static int NAliasFileMaps; /* the number of entries in AliasFileMap */
+
+static char *aliaslookup __P((char *, int *, char *));
+
+/*
+** ALIAS -- Compute aliases.
+**
+** Scans the alias file for an alias for the given address.
+** If found, it arranges to deliver to the alias list instead.
+** Uses libdbm database if -DDBM.
+**
+** Parameters:
+** a -- address to alias.
+** sendq -- a pointer to the head of the send queue
+** to put the aliases in.
+** aliaslevel -- the current alias nesting depth.
+** e -- the current envelope.
+**
+** Returns:
+** none
+**
+** Side Effects:
+** Aliases found are expanded.
+**
+** Deficiencies:
+** It should complain about names that are aliased to
+** nothing.
+*/
+
+void
+alias(a, sendq, aliaslevel, e)
+ register ADDRESS *a;
+ ADDRESS **sendq;
+ int aliaslevel;
+ register ENVELOPE *e;
+{
+ register char *p;
+ char *owner;
+ auto int status = EX_OK;
+ char obuf[MAXNAME + 7];
+
+ if (tTd(27, 1))
+ sm_dprintf("alias(%s)\n", a->q_user);
+
+ /* don't realias already aliased names */
+ if (!QS_IS_OK(a->q_state))
+ return;
+
+ if (NoAlias)
+ return;
+
+ e->e_to = a->q_paddr;
+
+ /*
+ ** Look up this name.
+ **
+ ** If the map was unavailable, we will queue this message
+ ** until the map becomes available; otherwise, we could
+ ** bounce messages inappropriately.
+ */
+
+#if _FFR_REDIRECTEMPTY
+ /*
+ ** envelope <> can't be sent to mailing lists, only owner-
+ ** send spam of this type to owner- of the list
+ ** ---- to stop spam from going to mailing lists!
+ */
+
+ if (e->e_sender != NULL && *e->e_sender == '\0')
+ {
+ /* Look for owner of alias */
+ (void) sm_strlcpyn(obuf, sizeof obuf, 2, "owner-", a->q_user);
+ if (aliaslookup(obuf, &status, a->q_host) != NULL)
+ {
+ if (LogLevel > 8)
+ sm_syslog(LOG_WARNING, e->e_id,
+ "possible spam from <> to list: %s, redirected to %s\n",
+ a->q_user, obuf);
+ a->q_user = sm_rpool_strdup_x(e->e_rpool, obuf);
+ }
+ }
+#endif /* _FFR_REDIRECTEMPTY */
+
+ p = aliaslookup(a->q_user, &status, a->q_host);
+ if (status == EX_TEMPFAIL || status == EX_UNAVAILABLE)
+ {
+ a->q_state = QS_QUEUEUP;
+ if (e->e_message == NULL)
+ e->e_message = "alias database unavailable";
+
+ /* XXX msg only per recipient? */
+ if (a->q_message == NULL)
+ a->q_message = "alias database unavailable";
+ return;
+ }
+ if (p == NULL)
+ return;
+
+ /*
+ ** Match on Alias.
+ ** Deliver to the target list.
+ */
+
+ if (tTd(27, 1))
+ sm_dprintf("%s (%s, %s) aliased to %s\n",
+ a->q_paddr, a->q_host, a->q_user, p);
+ if (bitset(EF_VRFYONLY, e->e_flags))
+ {
+ a->q_state = QS_VERIFIED;
+ return;
+ }
+ message("aliased to %s", shortenstring(p, MAXSHORTSTR));
+ if (LogLevel > 10)
+ sm_syslog(LOG_INFO, e->e_id,
+ "alias %.100s => %s",
+ a->q_paddr, shortenstring(p, MAXSHORTSTR));
+ a->q_flags &= ~QSELFREF;
+ if (tTd(27, 5))
+ {
+ sm_dprintf("alias: QS_EXPANDED ");
+ printaddr(sm_debug_file(), a, false);
+ }
+ a->q_state = QS_EXPANDED;
+
+ /*
+ ** Always deliver aliased items as the default user.
+ ** Setting q_gid to 0 forces deliver() to use DefUser
+ ** instead of the alias name for the call to initgroups().
+ */
+
+ a->q_uid = DefUid;
+ a->q_gid = 0;
+ a->q_fullname = NULL;
+ a->q_flags |= QGOODUID|QALIAS;
+
+ (void) sendtolist(p, a, sendq, aliaslevel + 1, e);
+
+ if (bitset(QSELFREF, a->q_flags) && QS_IS_EXPANDED(a->q_state))
+ a->q_state = QS_OK;
+
+ /*
+ ** Look for owner of alias
+ */
+
+ if (strncmp(a->q_user, "owner-", 6) == 0 ||
+ strlen(a->q_user) > sizeof obuf - 7)
+ (void) sm_strlcpy(obuf, "owner-owner", sizeof obuf);
+ else
+ (void) sm_strlcpyn(obuf, sizeof obuf, 2, "owner-", a->q_user);
+ owner = aliaslookup(obuf, &status, a->q_host);
+ if (owner == NULL)
+ return;
+
+ /* reflect owner into envelope sender */
+ if (strpbrk(owner, ",:/|\"") != NULL)
+ owner = obuf;
+ a->q_owner = sm_rpool_strdup_x(e->e_rpool, owner);
+
+ /* announce delivery to this alias; NORECEIPT bit set later */
+ if (e->e_xfp != NULL)
+ (void) sm_io_fprintf(e->e_xfp, SM_TIME_DEFAULT,
+ "Message delivered to mailing list %s\n",
+ a->q_paddr);
+ e->e_flags |= EF_SENDRECEIPT;
+ a->q_flags |= QDELIVERED|QEXPANDED;
+}
+/*
+** ALIASLOOKUP -- look up a name in the alias file.
+**
+** Parameters:
+** name -- the name to look up.
+** pstat -- a pointer to a place to put the status.
+** av -- argument for %1 expansion.
+**
+** Returns:
+** the value of name.
+** NULL if unknown.
+**
+** Side Effects:
+** none.
+**
+** Warnings:
+** The return value will be trashed across calls.
+*/
+
+static char *
+aliaslookup(name, pstat, av)
+ char *name;
+ int *pstat;
+ char *av;
+{
+ static MAP *map = NULL;
+#if _FFR_ALIAS_DETAIL
+ int i;
+ char *argv[4];
+#endif /* _FFR_ALIAS_DETAIL */
+
+ if (map == NULL)
+ {
+ STAB *s = stab("aliases", ST_MAP, ST_FIND);
+
+ if (s == NULL)
+ return NULL;
+ map = &s->s_map;
+ }
+ DYNOPENMAP(map);
+
+ /* special case POstMastER -- always use lower case */
+ if (sm_strcasecmp(name, "postmaster") == 0)
+ name = "postmaster";
+
+#if _FFR_ALIAS_DETAIL
+ i = 0;
+ argv[i++] = name;
+ argv[i++] = av;
+
+ /* XXX '+' is hardwired here as delimiter! */
+ if (av != NULL && *av == '+')
+ argv[i++] = av + 1;
+ argv[i++] = NULL;
+ return (*map->map_class->map_lookup)(map, name, argv, pstat);
+#else /* _FFR_ALIAS_DETAIL */
+ return (*map->map_class->map_lookup)(map, name, NULL, pstat);
+#endif /* _FFR_ALIAS_DETAIL */
+}
+/*
+** SETALIAS -- set up an alias map
+**
+** Called when reading configuration file.
+**
+** Parameters:
+** spec -- the alias specification
+**
+** Returns:
+** none.
+*/
+
+void
+setalias(spec)
+ char *spec;
+{
+ register char *p;
+ register MAP *map;
+ char *class;
+ STAB *s;
+
+ if (tTd(27, 8))
+ sm_dprintf("setalias(%s)\n", spec);
+
+ for (p = spec; p != NULL; )
+ {
+ char buf[50];
+
+ while (isascii(*p) && isspace(*p))
+ p++;
+ if (*p == '\0')
+ break;
+ spec = p;
+
+ if (NAliasFileMaps >= MAXMAPSTACK)
+ {
+ syserr("Too many alias databases defined, %d max",
+ MAXMAPSTACK);
+ return;
+ }
+ if (AliasFileMap == NULL)
+ {
+ (void) sm_strlcpy(buf, "aliases.files sequence",
+ sizeof buf);
+ AliasFileMap = makemapentry(buf);
+ if (AliasFileMap == NULL)
+ {
+ syserr("setalias: cannot create aliases.files map");
+ return;
+ }
+ }
+ (void) sm_snprintf(buf, sizeof buf, "Alias%d", NAliasFileMaps);
+ s = stab(buf, ST_MAP, ST_ENTER);
+ map = &s->s_map;
+ memset(map, '\0', sizeof *map);
+ map->map_mname = s->s_name;
+ p = strpbrk(p, ALIAS_SPEC_SEPARATORS);
+ if (p != NULL && *p == SEPARATOR)
+ {
+ /* map name */
+ *p++ = '\0';
+ class = spec;
+ spec = p;
+ }
+ else
+ {
+ class = "implicit";
+ map->map_mflags = MF_INCLNULL;
+ }
+
+ /* find end of spec */
+ if (p != NULL)
+ {
+ bool quoted = false;
+
+ for (; *p != '\0'; p++)
+ {
+ /*
+ ** Don't break into a quoted string.
+ ** Needed for ldap maps which use
+ ** commas in their specifications.
+ */
+
+ if (*p == '"')
+ quoted = !quoted;
+ else if (*p == ',' && !quoted)
+ break;
+ }
+
+ /* No more alias specifications follow */
+ if (*p == '\0')
+ p = NULL;
+ }
+ if (p != NULL)
+ *p++ = '\0';
+
+ if (tTd(27, 20))
+ sm_dprintf(" map %s:%s %s\n", class, s->s_name, spec);
+
+ /* look up class */
+ s = stab(class, ST_MAPCLASS, ST_FIND);
+ if (s == NULL)
+ {
+ syserr("setalias: unknown alias class %s", class);
+ }
+ else if (!bitset(MCF_ALIASOK, s->s_mapclass.map_cflags))
+ {
+ syserr("setalias: map class %s can't handle aliases",
+ class);
+ }
+ else
+ {
+ map->map_class = &s->s_mapclass;
+ map->map_mflags |= MF_ALIAS;
+ if (map->map_class->map_parse(map, spec))
+ {
+ map->map_mflags |= MF_VALID;
+ AliasFileMap->map_stack[NAliasFileMaps++] = map;
+ }
+ }
+ }
+}
+/*
+** ALIASWAIT -- wait for distinguished @:@ token to appear.
+**
+** This can decide to reopen or rebuild the alias file
+**
+** Parameters:
+** map -- a pointer to the map descriptor for this alias file.
+** ext -- the filename extension (e.g., ".db") for the
+** database file.
+** isopen -- if set, the database is already open, and we
+** should check for validity; otherwise, we are
+** just checking to see if it should be created.
+**
+** Returns:
+** true -- if the database is open when we return.
+** false -- if the database is closed when we return.
+*/
+
+bool
+aliaswait(map, ext, isopen)
+ MAP *map;
+ char *ext;
+ bool isopen;
+{
+ bool attimeout = false;
+ time_t mtime;
+ struct stat stb;
+ char buf[MAXPATHLEN];
+
+ if (tTd(27, 3))
+ sm_dprintf("aliaswait(%s:%s)\n",
+ map->map_class->map_cname, map->map_file);
+ if (bitset(MF_ALIASWAIT, map->map_mflags))
+ return isopen;
+ map->map_mflags |= MF_ALIASWAIT;
+
+ if (SafeAlias > 0)
+ {
+ auto int st;
+ unsigned int sleeptime = 2;
+ unsigned int loopcount = 0; /* only used for debugging */
+ time_t toolong = curtime() + SafeAlias;
+
+ while (isopen &&
+ map->map_class->map_lookup(map, "@", NULL, &st) == NULL)
+ {
+ if (curtime() > toolong)
+ {
+ /* we timed out */
+ attimeout = true;
+ break;
+ }
+
+ /*
+ ** Close and re-open the alias database in case
+ ** the one is mv'ed instead of cp'ed in.
+ */
+
+ if (tTd(27, 2))
+ {
+ loopcount++;
+ sm_dprintf("aliaswait: sleeping for %u seconds (loopcount = %u)\n",
+ sleeptime, loopcount);
+ }
+
+ map->map_mflags |= MF_CLOSING;
+ map->map_class->map_close(map);
+ map->map_mflags &= ~(MF_OPEN|MF_WRITABLE|MF_CLOSING);
+ (void) sleep(sleeptime);
+ sleeptime *= 2;
+ if (sleeptime > 60)
+ sleeptime = 60;
+ isopen = map->map_class->map_open(map, O_RDONLY);
+ }
+ }
+
+ /* see if we need to go into auto-rebuild mode */
+ if (!bitset(MCF_REBUILDABLE, map->map_class->map_cflags))
+ {
+ if (tTd(27, 3))
+ sm_dprintf("aliaswait: not rebuildable\n");
+ map->map_mflags &= ~MF_ALIASWAIT;
+ return isopen;
+ }
+ if (stat(map->map_file, &stb) < 0)
+ {
+ if (tTd(27, 3))
+ sm_dprintf("aliaswait: no source file\n");
+ map->map_mflags &= ~MF_ALIASWAIT;
+ return isopen;
+ }
+ mtime = stb.st_mtime;
+ if (sm_strlcpyn(buf, sizeof buf, 2,
+ map->map_file, ext == NULL ? "" : ext) >= sizeof buf)
+ {
+ if (LogLevel > 3)
+ sm_syslog(LOG_INFO, NOQID,
+ "alias database %s%s name too long",
+ map->map_file, ext == NULL ? "" : ext);
+ message("alias database %s%s name too long",
+ map->map_file, ext == NULL ? "" : ext);
+ }
+
+ if (stat(buf, &stb) < 0 || stb.st_mtime < mtime || attimeout)
+ {
+ if (LogLevel > 3)
+ sm_syslog(LOG_INFO, NOQID,
+ "alias database %s out of date", buf);
+ message("Warning: alias database %s out of date", buf);
+ }
+ map->map_mflags &= ~MF_ALIASWAIT;
+ return isopen;
+}
+/*
+** REBUILDALIASES -- rebuild the alias database.
+**
+** Parameters:
+** map -- the database to rebuild.
+** automatic -- set if this was automatically generated.
+**
+** Returns:
+** true if successful; false otherwise.
+**
+** Side Effects:
+** Reads the text version of the database, builds the
+** DBM or DB version.
+*/
+
+bool
+rebuildaliases(map, automatic)
+ register MAP *map;
+ bool automatic;
+{
+ SM_FILE_T *af;
+ bool nolock = false;
+ bool success = false;
+ long sff = SFF_OPENASROOT|SFF_REGONLY|SFF_NOLOCK;
+ sigfunc_t oldsigint, oldsigquit;
+#ifdef SIGTSTP
+ sigfunc_t oldsigtstp;
+#endif /* SIGTSTP */
+
+ if (!bitset(MCF_REBUILDABLE, map->map_class->map_cflags))
+ return false;
+
+ if (!bitnset(DBS_LINKEDALIASFILEINWRITABLEDIR, DontBlameSendmail))
+ sff |= SFF_NOWLINK;
+ if (!bitnset(DBS_GROUPWRITABLEALIASFILE, DontBlameSendmail))
+ sff |= SFF_NOGWFILES;
+ if (!bitnset(DBS_WORLDWRITABLEALIASFILE, DontBlameSendmail))
+ sff |= SFF_NOWWFILES;
+
+ /* try to lock the source file */
+ if ((af = safefopen(map->map_file, O_RDWR, 0, sff)) == NULL)
+ {
+ struct stat stb;
+
+ if ((errno != EACCES && errno != EROFS) || automatic ||
+ (af = safefopen(map->map_file, O_RDONLY, 0, sff)) == NULL)
+ {
+ int saveerr = errno;
+
+ if (tTd(27, 1))
+ sm_dprintf("Can't open %s: %s\n",
+ map->map_file, sm_errstring(saveerr));
+ if (!automatic && !bitset(MF_OPTIONAL, map->map_mflags))
+ message("newaliases: cannot open %s: %s",
+ map->map_file, sm_errstring(saveerr));
+ errno = 0;
+ return false;
+ }
+ nolock = true;
+ if (tTd(27, 1) ||
+ fstat(sm_io_getinfo(af, SM_IO_WHAT_FD, NULL), &stb) < 0 ||
+ bitset(S_IWUSR|S_IWGRP|S_IWOTH, stb.st_mode))
+ message("warning: cannot lock %s: %s",
+ map->map_file, sm_errstring(errno));
+ }
+
+ /* see if someone else is rebuilding the alias file */
+ if (!nolock &&
+ !lockfile(sm_io_getinfo(af, SM_IO_WHAT_FD, NULL), map->map_file,
+ NULL, LOCK_EX|LOCK_NB))
+ {
+ /* yes, they are -- wait until done */
+ message("Alias file %s is locked (maybe being rebuilt)",
+ map->map_file);
+ if (OpMode != MD_INITALIAS)
+ {
+ /* wait for other rebuild to complete */
+ (void) lockfile(sm_io_getinfo(af, SM_IO_WHAT_FD, NULL),
+ map->map_file, NULL, LOCK_EX);
+ }
+ (void) sm_io_close(af, SM_TIME_DEFAULT);
+ errno = 0;
+ return false;
+ }
+
+ oldsigint = sm_signal(SIGINT, SIG_IGN);
+ oldsigquit = sm_signal(SIGQUIT, SIG_IGN);
+#ifdef SIGTSTP
+ oldsigtstp = sm_signal(SIGTSTP, SIG_IGN);
+#endif /* SIGTSTP */
+
+ if (map->map_class->map_open(map, O_RDWR))
+ {
+ if (LogLevel > 7)
+ {
+ sm_syslog(LOG_NOTICE, NOQID,
+ "alias database %s %srebuilt by %s",
+ map->map_file, automatic ? "auto" : "",
+ username());
+ }
+ map->map_mflags |= MF_OPEN|MF_WRITABLE;
+ map->map_pid = CurrentPid;
+ readaliases(map, af, !automatic, true);
+ success = true;
+ }
+ else
+ {
+ if (tTd(27, 1))
+ sm_dprintf("Can't create database for %s: %s\n",
+ map->map_file, sm_errstring(errno));
+ if (!automatic)
+ syserr("Cannot create database for alias file %s",
+ map->map_file);
+ }
+
+ /* close the file, thus releasing locks */
+ (void) sm_io_close(af, SM_TIME_DEFAULT);
+
+ /* add distinguished entries and close the database */
+ if (bitset(MF_OPEN, map->map_mflags))
+ {
+ map->map_mflags |= MF_CLOSING;
+ map->map_class->map_close(map);
+ map->map_mflags &= ~(MF_OPEN|MF_WRITABLE|MF_CLOSING);
+ }
+
+ /* restore the old signals */
+ (void) sm_signal(SIGINT, oldsigint);
+ (void) sm_signal(SIGQUIT, oldsigquit);
+#ifdef SIGTSTP
+ (void) sm_signal(SIGTSTP, oldsigtstp);
+#endif /* SIGTSTP */
+ return success;
+}
+/*
+** READALIASES -- read and process the alias file.
+**
+** This routine implements the part of initaliases that occurs
+** when we are not going to use the DBM stuff.
+**
+** Parameters:
+** map -- the alias database descriptor.
+** af -- file to read the aliases from.
+** announcestats -- announce statistics regarding number of
+** aliases, longest alias, etc.
+** logstats -- lot the same info.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Reads aliasfile into the symbol table.
+** Optionally, builds the .dir & .pag files.
+*/
+
+void
+readaliases(map, af, announcestats, logstats)
+ register MAP *map;
+ SM_FILE_T *af;
+ bool announcestats;
+ bool logstats;
+{
+ register char *p;
+ char *rhs;
+ bool skipping;
+ long naliases, bytes, longest;
+ ADDRESS al, bl;
+ char line[BUFSIZ];
+
+ /*
+ ** Read and interpret lines
+ */
+
+ FileName = map->map_file;
+ LineNumber = 0;
+ naliases = bytes = longest = 0;
+ skipping = false;
+ while (sm_io_fgets(af, SM_TIME_DEFAULT, line, sizeof line) != NULL)
+ {
+ int lhssize, rhssize;
+ int c;
+
+ LineNumber++;
+ p = strchr(line, '\n');
+
+ /* XXX what if line="a\\" ? */
+ while (p != NULL && p > line && p[-1] == '\\')
+ {
+ p--;
+ if (sm_io_fgets(af, SM_TIME_DEFAULT, p,
+ SPACELEFT(line, p)) == NULL)
+ break;
+ LineNumber++;
+ p = strchr(p, '\n');
+ }
+ if (p != NULL)
+ *p = '\0';
+ else if (!sm_io_eof(af))
+ {
+ errno = 0;
+ syserr("554 5.3.0 alias line too long");
+
+ /* flush to end of line */
+ while ((c = sm_io_getc(af, SM_TIME_DEFAULT)) !=
+ SM_IO_EOF && c != '\n')
+ continue;
+
+ /* skip any continuation lines */
+ skipping = true;
+ continue;
+ }
+ switch (line[0])
+ {
+ case '#':
+ case '\0':
+ skipping = false;
+ continue;
+
+ case ' ':
+ case '\t':
+ if (!skipping)
+ syserr("554 5.3.5 Non-continuation line starts with space");
+ skipping = true;
+ continue;
+ }
+ skipping = false;
+
+ /*
+ ** Process the LHS
+ ** Find the colon separator, and parse the address.
+ ** It should resolve to a local name -- this will
+ ** be checked later (we want to optionally do
+ ** parsing of the RHS first to maximize error
+ ** detection).
+ */
+
+ for (p = line; *p != '\0' && *p != ':' && *p != '\n'; p++)
+ continue;
+ if (*p++ != ':')
+ {
+ syserr("554 5.3.5 missing colon");
+ continue;
+ }
+ if (parseaddr(line, &al, RF_COPYALL, ':', NULL, CurEnv, true)
+ == NULL)
+ {
+ syserr("554 5.3.5 %.40s... illegal alias name", line);
+ continue;
+ }
+
+ /*
+ ** Process the RHS.
+ ** 'al' is the internal form of the LHS address.
+ ** 'p' points to the text of the RHS.
+ */
+
+ while (isascii(*p) && isspace(*p))
+ p++;
+ rhs = p;
+ for (;;)
+ {
+ register char *nlp;
+
+ nlp = &p[strlen(p)];
+ if (nlp > p && nlp[-1] == '\n')
+ *--nlp = '\0';
+
+ if (CheckAliases)
+ {
+ /* do parsing & compression of addresses */
+ while (*p != '\0')
+ {
+ auto char *delimptr;
+
+ while ((isascii(*p) && isspace(*p)) ||
+ *p == ',')
+ p++;
+ if (*p == '\0')
+ break;
+ if (parseaddr(p, &bl, RF_COPYNONE, ',',
+ &delimptr, CurEnv, true)
+ == NULL)
+ usrerr("553 5.3.5 %s... bad address", p);
+ p = delimptr;
+ }
+ }
+ else
+ {
+ p = nlp;
+ }
+
+ /* see if there should be a continuation line */
+ c = sm_io_getc(af, SM_TIME_DEFAULT);
+ if (!sm_io_eof(af))
+ (void) sm_io_ungetc(af, SM_TIME_DEFAULT, c);
+ if (c != ' ' && c != '\t')
+ break;
+
+ /* read continuation line */
+ if (sm_io_fgets(af, SM_TIME_DEFAULT, p,
+ sizeof line - (p-line)) == NULL)
+ break;
+ LineNumber++;
+
+ /* check for line overflow */
+ if (strchr(p, '\n') == NULL && !sm_io_eof(af))
+ {
+ usrerr("554 5.3.5 alias too long");
+ while ((c = sm_io_getc(af, SM_TIME_DEFAULT))
+ != SM_IO_EOF && c != '\n')
+ continue;
+ skipping = true;
+ break;
+ }
+ }
+
+ if (skipping)
+ continue;
+
+ if (!bitnset(M_ALIASABLE, al.q_mailer->m_flags))
+ {
+ syserr("554 5.3.5 %s... cannot alias non-local names",
+ al.q_paddr);
+ continue;
+ }
+
+ /*
+ ** Insert alias into symbol table or database file.
+ **
+ ** Special case pOStmaStER -- always make it lower case.
+ */
+
+ if (sm_strcasecmp(al.q_user, "postmaster") == 0)
+ makelower(al.q_user);
+
+ lhssize = strlen(al.q_user);
+ rhssize = strlen(rhs);
+ if (rhssize > 0)
+ {
+ /* is RHS empty (just spaces)? */
+ p = rhs;
+ while (isascii(*p) && isspace(*p))
+ p++;
+ }
+ if (rhssize == 0 || *p == '\0')
+ {
+ syserr("554 5.3.5 %.40s... missing value for alias",
+ line);
+
+ }
+ else
+ {
+ map->map_class->map_store(map, al.q_user, rhs);
+
+ /* statistics */
+ naliases++;
+ bytes += lhssize + rhssize;
+ if (rhssize > longest)
+ longest = rhssize;
+ }
+
+#if 0
+ /*
+ ** address strings are now stored in the envelope rpool,
+ ** and therefore cannot be freed.
+ */
+ if (al.q_paddr != NULL)
+ sm_free(al.q_paddr); /* disabled */
+ if (al.q_host != NULL)
+ sm_free(al.q_host); /* disabled */
+ if (al.q_user != NULL)
+ sm_free(al.q_user); /* disabled */
+#endif /* 0 */
+ }
+
+ CurEnv->e_to = NULL;
+ FileName = NULL;
+ if (Verbose || announcestats)
+ message("%s: %ld aliases, longest %ld bytes, %ld bytes total",
+ map->map_file, naliases, longest, bytes);
+ if (LogLevel > 7 && logstats)
+ sm_syslog(LOG_INFO, NOQID,
+ "%s: %ld aliases, longest %ld bytes, %ld bytes total",
+ map->map_file, naliases, longest, bytes);
+}
+/*
+** FORWARD -- Try to forward mail
+**
+** This is similar but not identical to aliasing.
+**
+** Parameters:
+** user -- the name of the user who's mail we would like
+** to forward to. It must have been verified --
+** i.e., the q_home field must have been filled
+** in.
+** sendq -- a pointer to the head of the send queue to
+** put this user's aliases in.
+** aliaslevel -- the current alias nesting depth.
+** e -- the current envelope.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** New names are added to send queues.
+*/
+
+void
+forward(user, sendq, aliaslevel, e)
+ ADDRESS *user;
+ ADDRESS **sendq;
+ int aliaslevel;
+ register ENVELOPE *e;
+{
+ char *pp;
+ char *ep;
+ bool got_transient;
+
+ if (tTd(27, 1))
+ sm_dprintf("forward(%s)\n", user->q_paddr);
+
+ if (!bitnset(M_HASPWENT, user->q_mailer->m_flags) ||
+ !QS_IS_OK(user->q_state))
+ return;
+ if (ForwardPath != NULL && *ForwardPath == '\0')
+ return;
+ if (user->q_home == NULL)
+ {
+ syserr("554 5.3.0 forward: no home");
+ user->q_home = "/no/such/directory";
+ }
+
+ /* good address -- look for .forward file in home */
+ macdefine(&e->e_macro, A_PERM, 'z', user->q_home);
+ macdefine(&e->e_macro, A_PERM, 'u', user->q_user);
+ macdefine(&e->e_macro, A_PERM, 'h', user->q_host);
+ if (ForwardPath == NULL)
+ ForwardPath = newstr("\201z/.forward");
+
+ got_transient = false;
+ for (pp = ForwardPath; pp != NULL; pp = ep)
+ {
+ int err;
+ char buf[MAXPATHLEN];
+ struct stat st;
+
+ ep = strchr(pp, SEPARATOR);
+ if (ep != NULL)
+ *ep = '\0';
+ expand(pp, buf, sizeof buf, e);
+ if (ep != NULL)
+ *ep++ = SEPARATOR;
+ if (buf[0] == '\0')
+ continue;
+ if (tTd(27, 3))
+ sm_dprintf("forward: trying %s\n", buf);
+
+ err = include(buf, true, user, sendq, aliaslevel, e);
+ if (err == 0)
+ break;
+ else if (transienterror(err))
+ {
+ /* we may have to suspend this message */
+ got_transient = true;
+ if (tTd(27, 2))
+ sm_dprintf("forward: transient error on %s\n",
+ buf);
+ if (LogLevel > 2)
+ {
+ char *curhost = CurHostName;
+
+ CurHostName = NULL;
+ sm_syslog(LOG_ERR, e->e_id,
+ "forward %s: transient error: %s",
+ buf, sm_errstring(err));
+ CurHostName = curhost;
+ }
+
+ }
+ else
+ {
+ switch (err)
+ {
+ case ENOENT:
+ break;
+
+ case E_SM_WWDIR:
+ case E_SM_GWDIR:
+ /* check if it even exists */
+ if (stat(buf, &st) < 0 && errno == ENOENT)
+ {
+ if (bitnset(DBS_DONTWARNFORWARDFILEINUNSAFEDIRPATH,
+ DontBlameSendmail))
+ break;
+ }
+ /* FALLTHROUGH */
+
+#if _FFR_FORWARD_SYSERR
+ case E_SM_NOSLINK:
+ case E_SM_NOHLINK:
+ case E_SM_REGONLY:
+ case E_SM_ISEXEC:
+ case E_SM_WWFILE:
+ case E_SM_GWFILE:
+ syserr("forward: %s: %s", buf, sm_errstring(err));
+ break;
+#endif /* _FFR_FORWARD_SYSERR */
+
+ default:
+ if (LogLevel > (RunAsUid == 0 ? 2 : 10))
+ sm_syslog(LOG_WARNING, e->e_id,
+ "forward %s: %s", buf,
+ sm_errstring(err));
+ if (Verbose)
+ message("forward: %s: %s",
+ buf, sm_errstring(err));
+ break;
+ }
+ }
+ }
+ if (pp == NULL && got_transient)
+ {
+ /*
+ ** There was no successful .forward open and at least one
+ ** transient open. We have to defer this address for
+ ** further delivery.
+ */
+
+ message("transient .forward open error: message queued");
+ user->q_state = QS_QUEUEUP;
+ return;
+ }
+}
diff --git a/usr/src/cmd/sendmail/src/arpadate.c b/usr/src/cmd/sendmail/src/arpadate.c
new file mode 100644
index 0000000000..8211a656f3
--- /dev/null
+++ b/usr/src/cmd/sendmail/src/arpadate.c
@@ -0,0 +1,205 @@
+/*
+ * Copyright (c) 1998-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sendmail.h>
+
+SM_RCSID("@(#)$Id: arpadate.c,v 8.28 2001/02/14 14:45:47 ca Exp $")
+
+/*
+** ARPADATE -- Create date in ARPANET format
+**
+** Parameters:
+** ud -- unix style date string. if NULL, one is created.
+**
+** Returns:
+** pointer to an ARPANET date field
+**
+** Side Effects:
+** none
+**
+** WARNING:
+** date is stored in a local buffer -- subsequent
+** calls will overwrite.
+**
+** Bugs:
+** Timezone is computed from local time, rather than
+** from wherever (and whenever) the message was sent.
+** To do better is very hard.
+**
+** Some sites are now inserting the timezone into the
+** local date. This routine should figure out what
+** the format is and work appropriately.
+*/
+
+#ifndef TZNAME_MAX
+# define TZNAME_MAX 50 /* max size of timezone */
+#endif /* ! TZNAME_MAX */
+
+/* values for TZ_TYPE */
+#define TZ_NONE 0 /* no character timezone support */
+#define TZ_TM_NAME 1 /* use tm->tm_name */
+#define TZ_TM_ZONE 2 /* use tm->tm_zone */
+#define TZ_TZNAME 3 /* use tzname[] */
+#define TZ_TIMEZONE 4 /* use timezone() */
+
+char *
+arpadate(ud)
+ register char *ud;
+{
+ register char *p;
+ register char *q;
+ register int off;
+ register int i;
+ register struct tm *lt;
+ time_t t;
+ struct tm gmt;
+ char *tz;
+ static char b[43 + TZNAME_MAX];
+
+ /*
+ ** Get current time.
+ ** This will be used if a null argument is passed and
+ ** to resolve the timezone.
+ */
+
+ /* SM_REQUIRE(ud == NULL || strlen(ud) >= 23); */
+ t = curtime();
+ if (ud == NULL)
+ ud = ctime(&t);
+
+ /*
+ ** Crack the UNIX date line in a singularly unoriginal way.
+ */
+
+ q = b;
+
+ p = &ud[0]; /* Mon */
+ *q++ = *p++;
+ *q++ = *p++;
+ *q++ = *p++;
+ *q++ = ',';
+ *q++ = ' ';
+
+ p = &ud[8]; /* 16 */
+ if (*p == ' ')
+ p++;
+ else
+ *q++ = *p++;
+ *q++ = *p++;
+ *q++ = ' ';
+
+ p = &ud[4]; /* Sep */
+ *q++ = *p++;
+ *q++ = *p++;
+ *q++ = *p++;
+ *q++ = ' ';
+
+ p = &ud[20]; /* 1979 */
+ *q++ = *p++;
+ *q++ = *p++;
+ *q++ = *p++;
+ *q++ = *p++;
+ *q++ = ' ';
+
+ p = &ud[11]; /* 01:03:52 */
+ for (i = 8; i > 0; i--)
+ *q++ = *p++;
+
+ /*
+ ** should really get the timezone from the time in "ud" (which
+ ** is only different if a non-null arg was passed which is different
+ ** from the current time), but for all practical purposes, returning
+ ** the current local zone will do (its all that is ever needed).
+ */
+
+ gmt = *gmtime(&t);
+ lt = localtime(&t);
+
+ off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min;
+
+ /* assume that offset isn't more than a day ... */
+ if (lt->tm_year < gmt.tm_year)
+ off -= 24 * 60;
+ else if (lt->tm_year > gmt.tm_year)
+ off += 24 * 60;
+ else if (lt->tm_yday < gmt.tm_yday)
+ off -= 24 * 60;
+ else if (lt->tm_yday > gmt.tm_yday)
+ off += 24 * 60;
+
+ *q++ = ' ';
+ if (off == 0)
+ {
+ *q++ = 'G';
+ *q++ = 'M';
+ *q++ = 'T';
+ }
+ else
+ {
+ tz = NULL;
+#if TZ_TYPE == TZ_TM_NAME
+ tz = lt->tm_name;
+#endif /* TZ_TYPE == TZ_TM_NAME */
+#if TZ_TYPE == TZ_TM_ZONE
+ tz = lt->tm_zone;
+#endif /* TZ_TYPE == TZ_TM_ZONE */
+#if TZ_TYPE == TZ_TZNAME
+ {
+ extern char *tzname[];
+
+ if (lt->tm_isdst > 0)
+ tz = tzname[1];
+ else if (lt->tm_isdst == 0)
+ tz = tzname[0];
+ else
+ tz = NULL;
+ }
+#endif /* TZ_TYPE == TZ_TZNAME */
+#if TZ_TYPE == TZ_TIMEZONE
+ {
+ extern char *timezone();
+
+ tz = timezone(off, lt->tm_isdst);
+ }
+#endif /* TZ_TYPE == TZ_TIMEZONE */
+ if (off < 0)
+ {
+ off = -off;
+ *q++ = '-';
+ }
+ else
+ *q++ = '+';
+
+ if (off >= 24*60) /* should be impossible */
+ off = 23*60+59; /* if not, insert silly value */
+
+ *q++ = (off / 600) + '0';
+ *q++ = (off / 60) % 10 + '0';
+ off %= 60;
+ *q++ = (off / 10) + '0';
+ *q++ = (off % 10) + '0';
+ if (tz != NULL && *tz != '\0')
+ {
+ *q++ = ' ';
+ *q++ = '(';
+ while (*tz != '\0' && q < &b[sizeof b - 3])
+ *q++ = *tz++;
+ *q++ = ')';
+ }
+ }
+ *q = '\0';
+
+ return b;
+}
diff --git a/usr/src/cmd/sendmail/src/bf.c b/usr/src/cmd/sendmail/src/bf.c
new file mode 100644
index 0000000000..3d71dca1ab
--- /dev/null
+++ b/usr/src/cmd/sendmail/src/bf.c
@@ -0,0 +1,863 @@
+/*
+ * Copyright (c) 1999-2002, 2004 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ * Contributed by Exactis.com, Inc.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+** This is in transition. Changed from the original bf_torek.c code
+** to use sm_io function calls directly rather than through stdio
+** translation layer. Will be made a built-in file type of libsm
+** next (once safeopen() linkable from libsm).
+*/
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: bf.c,v 8.61 2004/08/03 23:59:02 ca Exp $")
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include "sendmail.h"
+#include "bf.h"
+
+#include <syslog.h>
+
+/* bf io functions */
+static ssize_t sm_bfread __P((SM_FILE_T *, char *, size_t));
+static ssize_t sm_bfwrite __P((SM_FILE_T *, const char *, size_t));
+static off_t sm_bfseek __P((SM_FILE_T *, off_t, int));
+static int sm_bfclose __P((SM_FILE_T *));
+static int sm_bfcommit __P((SM_FILE_T *));
+static int sm_bftruncate __P((SM_FILE_T *));
+
+static int sm_bfopen __P((SM_FILE_T *, const void *, int, const void *));
+static int sm_bfsetinfo __P((SM_FILE_T *, int , void *));
+static int sm_bfgetinfo __P((SM_FILE_T *, int , void *));
+
+/*
+** Data structure for storing information about each buffered file
+** (Originally in sendmail/bf_torek.h for the curious.)
+*/
+
+struct bf
+{
+ bool bf_committed; /* Has this buffered file been committed? */
+ bool bf_ondisk; /* On disk: committed or buffer overflow */
+ long bf_flags;
+ int bf_disk_fd; /* If on disk, associated file descriptor */
+ char *bf_buf; /* Memory buffer */
+ int bf_bufsize; /* Length of above buffer */
+ int bf_buffilled; /* Bytes of buffer actually filled */
+ char *bf_filename; /* Name of buffered file, if ever committed */
+ MODE_T bf_filemode; /* Mode of buffered file, if ever committed */
+ off_t bf_offset; /* Currect file offset */
+ int bf_size; /* Total current size of file */
+};
+
+#ifdef BF_STANDALONE
+# define OPEN(fn, omode, cmode, sff) open(fn, omode, cmode)
+#else /* BF_STANDALONE */
+# define OPEN(fn, omode, cmode, sff) safeopen(fn, omode, cmode, sff)
+#endif /* BF_STANDALONE */
+
+struct bf_info
+{
+ char *bi_filename;
+ MODE_T bi_fmode;
+ size_t bi_bsize;
+ long bi_flags;
+};
+
+/*
+** SM_BFOPEN -- the "base" open function called by sm_io_open() for the
+** internal, file-type-specific info setup.
+**
+** Parameters:
+** fp -- file pointer being filled-in for file being open'd
+** info -- information about file being opened
+** flags -- ignored
+** rpool -- ignored (currently)
+**
+** Returns:
+** Failure: -1 and sets errno
+** Success: 0 (zero)
+*/
+
+static int
+sm_bfopen(fp, info, flags, rpool)
+ SM_FILE_T *fp;
+ const void *info;
+ int flags;
+ const void *rpool;
+{
+ char *filename;
+ MODE_T fmode;
+ size_t bsize;
+ long sflags;
+ struct bf *bfp;
+ int l;
+ struct stat st;
+
+ filename = ((struct bf_info *) info)->bi_filename;
+ fmode = ((struct bf_info *) info)->bi_fmode;
+ bsize = ((struct bf_info *) info)->bi_bsize;
+ sflags = ((struct bf_info *) info)->bi_flags;
+
+ /* Sanity checks */
+ if (*filename == '\0')
+ {
+ /* Empty filename string */
+ errno = ENOENT;
+ return -1;
+ }
+ if (stat(filename, &st) == 0)
+ {
+ /* File already exists on disk */
+ errno = EEXIST;
+ return -1;
+ }
+
+ /* Allocate memory */
+ bfp = (struct bf *) sm_malloc(sizeof(struct bf));
+ if (bfp == NULL)
+ {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ /* Assign data buffer */
+ /* A zero bsize is valid, just don't allocate memory */
+ if (bsize > 0)
+ {
+ bfp->bf_buf = (char *) sm_malloc(bsize);
+ if (bfp->bf_buf == NULL)
+ {
+ bfp->bf_bufsize = 0;
+ sm_free(bfp);
+ errno = ENOMEM;
+ return -1;
+ }
+ }
+ else
+ bfp->bf_buf = NULL;
+
+ /* Nearly home free, just set all the parameters now */
+ bfp->bf_committed = false;
+ bfp->bf_ondisk = false;
+ bfp->bf_flags = sflags;
+ bfp->bf_bufsize = bsize;
+ bfp->bf_buffilled = 0;
+ l = strlen(filename) + 1;
+ bfp->bf_filename = (char *) sm_malloc(l);
+ if (bfp->bf_filename == NULL)
+ {
+ if (bfp->bf_buf != NULL)
+ sm_free(bfp->bf_buf);
+ sm_free(bfp);
+ errno = ENOMEM;
+ return -1;
+ }
+ (void) sm_strlcpy(bfp->bf_filename, filename, l);
+ bfp->bf_filemode = fmode;
+ bfp->bf_offset = 0;
+ bfp->bf_size = 0;
+ bfp->bf_disk_fd = -1;
+ fp->f_cookie = bfp;
+
+ if (tTd(58, 8))
+ sm_dprintf("sm_bfopen(%s)\n", filename);
+
+ return 0;
+}
+
+/*
+** BFOPEN -- create a new buffered file
+**
+** Parameters:
+** filename -- the file's name
+** fmode -- what mode the file should be created as
+** bsize -- amount of buffer space to allocate (may be 0)
+** flags -- if running under sendmail, passed directly to safeopen
+**
+** Returns:
+** a SM_FILE_T * which may then be used with stdio functions,
+** or NULL on failure. SM_FILE_T * is opened for writing
+** "SM_IO_WHAT_VECTORS").
+**
+** Side Effects:
+** none.
+**
+** Sets errno:
+** any value of errno specified by sm_io_setinfo_type()
+** any value of errno specified by sm_io_open()
+** any value of errno specified by sm_io_setinfo()
+*/
+
+#ifdef __STDC__
+/*
+** XXX This is a temporary hack since MODE_T on HP-UX 10.x is short.
+** If we use K&R here, the compiler will complain about
+** Inconsistent parameter list declaration
+** due to the change from short to int.
+*/
+
+SM_FILE_T *
+bfopen(char *filename, MODE_T fmode, size_t bsize, long flags)
+#else /* __STDC__ */
+SM_FILE_T *
+bfopen(filename, fmode, bsize, flags)
+ char *filename;
+ MODE_T fmode;
+ size_t bsize;
+ long flags;
+#endif /* __STDC__ */
+{
+ MODE_T omask;
+ SM_FILE_T SM_IO_SET_TYPE(vector, BF_FILE_TYPE, sm_bfopen, sm_bfclose,
+ sm_bfread, sm_bfwrite, sm_bfseek, sm_bfgetinfo, sm_bfsetinfo,
+ SM_TIME_FOREVER);
+ struct bf_info info;
+
+ /*
+ ** Apply current umask to fmode as it may change by the time
+ ** the file is actually created. fmode becomes the true
+ ** permissions of the file, which OPEN() must obey.
+ */
+
+ omask = umask(0);
+ fmode &= ~omask;
+ (void) umask(omask);
+
+ SM_IO_INIT_TYPE(vector, BF_FILE_TYPE, sm_bfopen, sm_bfclose,
+ sm_bfread, sm_bfwrite, sm_bfseek, sm_bfgetinfo, sm_bfsetinfo,
+ SM_TIME_FOREVER);
+ info.bi_filename = filename;
+ info.bi_fmode = fmode;
+ info.bi_bsize = bsize;
+ info.bi_flags = flags;
+
+ return sm_io_open(&vector, SM_TIME_DEFAULT, &info, SM_IO_RDWR, NULL);
+}
+
+/*
+** SM_BFGETINFO -- returns info about an open file pointer
+**
+** Parameters:
+** fp -- file pointer to get info about
+** what -- type of info to obtain
+** valp -- thing to return the info in
+*/
+
+static int
+sm_bfgetinfo(fp, what, valp)
+ SM_FILE_T *fp;
+ int what;
+ void *valp;
+{
+ struct bf *bfp;
+
+ bfp = (struct bf *) fp->f_cookie;
+ switch (what)
+ {
+ case SM_IO_WHAT_FD:
+ return bfp->bf_disk_fd;
+ case SM_IO_WHAT_SIZE:
+ return bfp->bf_size;
+ default:
+ return -1;
+ }
+}
+
+/*
+** SM_BFCLOSE -- close a buffered file
+**
+** Parameters:
+** fp -- cookie of file to close
+**
+** Returns:
+** 0 to indicate success
+**
+** Side Effects:
+** deletes backing file, sm_frees memory.
+**
+** Sets errno:
+** never.
+*/
+
+static int
+sm_bfclose(fp)
+ SM_FILE_T *fp;
+{
+ struct bf *bfp;
+
+ /* Cast cookie back to correct type */
+ bfp = (struct bf *) fp->f_cookie;
+
+ /* Need to clean up the file */
+ if (bfp->bf_ondisk && !bfp->bf_committed)
+ unlink(bfp->bf_filename);
+ sm_free(bfp->bf_filename);
+
+ if (bfp->bf_disk_fd != -1)
+ close(bfp->bf_disk_fd);
+
+ /* Need to sm_free the buffer */
+ if (bfp->bf_bufsize > 0)
+ sm_free(bfp->bf_buf);
+
+ /* Finally, sm_free the structure */
+ sm_free(bfp);
+ return 0;
+}
+
+/*
+** SM_BFREAD -- read a buffered file
+**
+** Parameters:
+** cookie -- cookie of file to read
+** buf -- buffer to fill
+** nbytes -- how many bytes to read
+**
+** Returns:
+** number of bytes read or -1 indicate failure
+**
+** Side Effects:
+** none.
+**
+*/
+
+static ssize_t
+sm_bfread(fp, buf, nbytes)
+ SM_FILE_T *fp;
+ char *buf;
+ size_t nbytes;
+{
+ struct bf *bfp;
+ ssize_t count = 0; /* Number of bytes put in buf so far */
+ int retval;
+
+ /* Cast cookie back to correct type */
+ bfp = (struct bf *) fp->f_cookie;
+
+ if (bfp->bf_offset < bfp->bf_buffilled)
+ {
+ /* Need to grab some from buffer */
+ count = nbytes;
+ if ((bfp->bf_offset + count) > bfp->bf_buffilled)
+ count = bfp->bf_buffilled - bfp->bf_offset;
+
+ memcpy(buf, bfp->bf_buf + bfp->bf_offset, count);
+ }
+
+ if ((bfp->bf_offset + nbytes) > bfp->bf_buffilled)
+ {
+ /* Need to grab some from file */
+ if (!bfp->bf_ondisk)
+ {
+ /* Oops, the file doesn't exist. EOF. */
+ if (tTd(58, 8))
+ sm_dprintf("sm_bfread(%s): to disk\n",
+ bfp->bf_filename);
+ goto finished;
+ }
+
+ /* Catch a read() on an earlier failed write to disk */
+ if (bfp->bf_disk_fd < 0)
+ {
+ errno = EIO;
+ return -1;
+ }
+
+ if (lseek(bfp->bf_disk_fd,
+ bfp->bf_offset + count, SEEK_SET) < 0)
+ {
+ if ((errno == EINVAL) || (errno == ESPIPE))
+ {
+ /*
+ ** stdio won't be expecting these
+ ** errnos from read()! Change them
+ ** into something it can understand.
+ */
+
+ errno = EIO;
+ }
+ return -1;
+ }
+
+ while (count < nbytes)
+ {
+ retval = read(bfp->bf_disk_fd,
+ buf + count,
+ nbytes - count);
+ if (retval < 0)
+ {
+ /* errno is set implicitly by read() */
+ return -1;
+ }
+ else if (retval == 0)
+ goto finished;
+ else
+ count += retval;
+ }
+ }
+
+finished:
+ bfp->bf_offset += count;
+ return count;
+}
+
+/*
+** SM_BFSEEK -- seek to a position in a buffered file
+**
+** Parameters:
+** fp -- fp of file to seek
+** offset -- position to seek to
+** whence -- how to seek
+**
+** Returns:
+** new file offset or -1 indicate failure
+**
+** Side Effects:
+** none.
+**
+*/
+
+static off_t
+sm_bfseek(fp, offset, whence)
+ SM_FILE_T *fp;
+ off_t offset;
+ int whence;
+
+{
+ struct bf *bfp;
+
+ /* Cast cookie back to correct type */
+ bfp = (struct bf *) fp->f_cookie;
+
+ switch (whence)
+ {
+ case SEEK_SET:
+ bfp->bf_offset = offset;
+ break;
+
+ case SEEK_CUR:
+ bfp->bf_offset += offset;
+ break;
+
+ case SEEK_END:
+ bfp->bf_offset = bfp->bf_size + offset;
+ break;
+
+ default:
+ errno = EINVAL;
+ return -1;
+ }
+ return bfp->bf_offset;
+}
+
+/*
+** SM_BFWRITE -- write to a buffered file
+**
+** Parameters:
+** fp -- fp of file to write
+** buf -- data buffer
+** nbytes -- how many bytes to write
+**
+** Returns:
+** number of bytes written or -1 indicate failure
+**
+** Side Effects:
+** may create backing file if over memory limit for file.
+**
+*/
+
+static ssize_t
+sm_bfwrite(fp, buf, nbytes)
+ SM_FILE_T *fp;
+ const char *buf;
+ size_t nbytes;
+{
+ struct bf *bfp;
+ ssize_t count = 0; /* Number of bytes written so far */
+ int retval;
+
+ /* Cast cookie back to correct type */
+ bfp = (struct bf *) fp->f_cookie;
+
+ /* If committed, go straight to disk */
+ if (bfp->bf_committed)
+ {
+ if (lseek(bfp->bf_disk_fd, bfp->bf_offset, SEEK_SET) < 0)
+ {
+ if ((errno == EINVAL) || (errno == ESPIPE))
+ {
+ /*
+ ** stdio won't be expecting these
+ ** errnos from write()! Change them
+ ** into something it can understand.
+ */
+
+ errno = EIO;
+ }
+ return -1;
+ }
+
+ count = write(bfp->bf_disk_fd, buf, nbytes);
+ if (count < 0)
+ {
+ /* errno is set implicitly by write() */
+ return -1;
+ }
+ goto finished;
+ }
+
+ if (bfp->bf_offset < bfp->bf_bufsize)
+ {
+ /* Need to put some in buffer */
+ count = nbytes;
+ if ((bfp->bf_offset + count) > bfp->bf_bufsize)
+ count = bfp->bf_bufsize - bfp->bf_offset;
+
+ memcpy(bfp->bf_buf + bfp->bf_offset, buf, count);
+ if ((bfp->bf_offset + count) > bfp->bf_buffilled)
+ bfp->bf_buffilled = bfp->bf_offset + count;
+ }
+
+ if ((bfp->bf_offset + nbytes) > bfp->bf_bufsize)
+ {
+ /* Need to put some in file */
+ if (!bfp->bf_ondisk)
+ {
+ MODE_T omask;
+
+ /* Clear umask as bf_filemode are the true perms */
+ omask = umask(0);
+ retval = OPEN(bfp->bf_filename,
+ O_RDWR | O_CREAT | O_TRUNC | QF_O_EXTRA,
+ bfp->bf_filemode, bfp->bf_flags);
+ (void) umask(omask);
+
+ /* Couldn't create file: failure */
+ if (retval < 0)
+ {
+ /*
+ ** stdio may not be expecting these
+ ** errnos from write()! Change to
+ ** something which it can understand.
+ ** Note that ENOSPC and EDQUOT are saved
+ ** because they are actually valid for
+ ** write().
+ */
+
+ if (!(errno == ENOSPC
+#ifdef EDQUOT
+ || errno == EDQUOT
+#endif /* EDQUOT */
+ ))
+ errno = EIO;
+
+ return -1;
+ }
+ bfp->bf_disk_fd = retval;
+ bfp->bf_ondisk = true;
+ }
+
+ /* Catch a write() on an earlier failed write to disk */
+ if (bfp->bf_ondisk && bfp->bf_disk_fd < 0)
+ {
+ errno = EIO;
+ return -1;
+ }
+
+ if (lseek(bfp->bf_disk_fd,
+ bfp->bf_offset + count, SEEK_SET) < 0)
+ {
+ if ((errno == EINVAL) || (errno == ESPIPE))
+ {
+ /*
+ ** stdio won't be expecting these
+ ** errnos from write()! Change them into
+ ** something which it can understand.
+ */
+
+ errno = EIO;
+ }
+ return -1;
+ }
+
+ while (count < nbytes)
+ {
+ retval = write(bfp->bf_disk_fd, buf + count,
+ nbytes - count);
+ if (retval < 0)
+ {
+ /* errno is set implicitly by write() */
+ return -1;
+ }
+ else
+ count += retval;
+ }
+ }
+
+finished:
+ bfp->bf_offset += count;
+ if (bfp->bf_offset > bfp->bf_size)
+ bfp->bf_size = bfp->bf_offset;
+ return count;
+}
+
+/*
+** BFREWIND -- rewinds the SM_FILE_T *
+**
+** Parameters:
+** fp -- SM_FILE_T * to rewind
+**
+** Returns:
+** 0 on success, -1 on error
+**
+** Side Effects:
+** rewinds the SM_FILE_T * and puts it into read mode. Normally
+** one would bfopen() a file, write to it, then bfrewind() and
+** fread(). If fp is not a buffered file, this is equivalent to
+** rewind().
+**
+** Sets errno:
+** any value of errno specified by sm_io_rewind()
+*/
+
+int
+bfrewind(fp)
+ SM_FILE_T *fp;
+{
+ (void) sm_io_flush(fp, SM_TIME_DEFAULT);
+ sm_io_clearerr(fp); /* quicker just to do it */
+ return sm_io_seek(fp, SM_TIME_DEFAULT, 0, SM_IO_SEEK_SET);
+}
+
+/*
+** SM_BFCOMMIT -- "commits" the buffered file
+**
+** Parameters:
+** fp -- SM_FILE_T * to commit to disk
+**
+** Returns:
+** 0 on success, -1 on error
+**
+** Side Effects:
+** Forces the given SM_FILE_T * to be written to disk if it is not
+** already, and ensures that it will be kept after closing. If
+** fp is not a buffered file, this is a no-op.
+**
+** Sets errno:
+** any value of errno specified by open()
+** any value of errno specified by write()
+** any value of errno specified by lseek()
+*/
+
+static int
+sm_bfcommit(fp)
+ SM_FILE_T *fp;
+{
+ struct bf *bfp;
+ int retval;
+ int byteswritten;
+
+ /* Get associated bf structure */
+ bfp = (struct bf *) fp->f_cookie;
+
+ /* If already committed, noop */
+ if (bfp->bf_committed)
+ return 0;
+
+ /* Do we need to open a file? */
+ if (!bfp->bf_ondisk)
+ {
+ int save_errno;
+ MODE_T omask;
+ struct stat st;
+
+ if (tTd(58, 8))
+ {
+ sm_dprintf("bfcommit(%s): to disk\n", bfp->bf_filename);
+ if (tTd(58, 32))
+ sm_dprintf("bfcommit(): filemode %o flags %ld\n",
+ bfp->bf_filemode, bfp->bf_flags);
+ }
+
+ if (stat(bfp->bf_filename, &st) == 0)
+ {
+ errno = EEXIST;
+ return -1;
+ }
+
+ /* Clear umask as bf_filemode are the true perms */
+ omask = umask(0);
+ retval = OPEN(bfp->bf_filename,
+ O_RDWR | O_CREAT | O_EXCL | QF_O_EXTRA,
+ bfp->bf_filemode, bfp->bf_flags);
+ save_errno = errno;
+ (void) umask(omask);
+
+ /* Couldn't create file: failure */
+ if (retval < 0)
+ {
+ /* errno is set implicitly by open() */
+ errno = save_errno;
+ return -1;
+ }
+
+ bfp->bf_disk_fd = retval;
+ bfp->bf_ondisk = true;
+ }
+
+ /* Write out the contents of our buffer, if we have any */
+ if (bfp->bf_buffilled > 0)
+ {
+ byteswritten = 0;
+
+ if (lseek(bfp->bf_disk_fd, 0, SEEK_SET) < 0)
+ {
+ /* errno is set implicitly by lseek() */
+ return -1;
+ }
+
+ while (byteswritten < bfp->bf_buffilled)
+ {
+ retval = write(bfp->bf_disk_fd,
+ bfp->bf_buf + byteswritten,
+ bfp->bf_buffilled - byteswritten);
+ if (retval < 0)
+ {
+ /* errno is set implicitly by write() */
+ return -1;
+ }
+ else
+ byteswritten += retval;
+ }
+ }
+ bfp->bf_committed = true;
+
+ /* Invalidate buf; all goes to file now */
+ bfp->bf_buffilled = 0;
+ if (bfp->bf_bufsize > 0)
+ {
+ /* Don't need buffer anymore; free it */
+ bfp->bf_bufsize = 0;
+ sm_free(bfp->bf_buf);
+ }
+ return 0;
+}
+
+/*
+** SM_BFTRUNCATE -- rewinds and truncates the SM_FILE_T *
+**
+** Parameters:
+** fp -- SM_FILE_T * to truncate
+**
+** Returns:
+** 0 on success, -1 on error
+**
+** Side Effects:
+** rewinds the SM_FILE_T *, truncates it to zero length, and puts
+** it into write mode.
+**
+** Sets errno:
+** any value of errno specified by fseek()
+** any value of errno specified by ftruncate()
+*/
+
+static int
+sm_bftruncate(fp)
+ SM_FILE_T *fp;
+{
+ struct bf *bfp;
+
+ if (bfrewind(fp) < 0)
+ return -1;
+
+ /* Get bf structure */
+ bfp = (struct bf *) fp->f_cookie;
+ bfp->bf_buffilled = 0;
+ bfp->bf_size = 0;
+
+ /* Need to zero the buffer */
+ if (bfp->bf_bufsize > 0)
+ memset(bfp->bf_buf, '\0', bfp->bf_bufsize);
+ if (bfp->bf_ondisk)
+ {
+#if NOFTRUNCATE
+ /* XXX: Not much we can do except rewind it */
+ errno = EINVAL;
+ return -1;
+#else /* NOFTRUNCATE */
+ return ftruncate(bfp->bf_disk_fd, 0);
+#endif /* NOFTRUNCATE */
+ }
+ return 0;
+}
+
+/*
+** SM_BFSETINFO -- set/change info for an open file pointer
+**
+** Parameters:
+** fp -- file pointer to get info about
+** what -- type of info to set/change
+** valp -- thing to set/change the info to
+**
+*/
+
+static int
+sm_bfsetinfo(fp, what, valp)
+ SM_FILE_T *fp;
+ int what;
+ void *valp;
+{
+ struct bf *bfp;
+ int bsize;
+
+ /* Get bf structure */
+ bfp = (struct bf *) fp->f_cookie;
+ switch (what)
+ {
+ case SM_BF_SETBUFSIZE:
+ bsize = *((int *) valp);
+ bfp->bf_bufsize = bsize;
+
+ /* A zero bsize is valid, just don't allocate memory */
+ if (bsize > 0)
+ {
+ bfp->bf_buf = (char *) sm_malloc(bsize);
+ if (bfp->bf_buf == NULL)
+ {
+ bfp->bf_bufsize = 0;
+ errno = ENOMEM;
+ return -1;
+ }
+ }
+ else
+ bfp->bf_buf = NULL;
+ return 0;
+ case SM_BF_COMMIT:
+ return sm_bfcommit(fp);
+ case SM_BF_TRUNCATE:
+ return sm_bftruncate(fp);
+ case SM_BF_TEST:
+ return 1; /* always */
+ default:
+ errno = EINVAL;
+ return -1;
+ }
+}
diff --git a/usr/src/cmd/sendmail/src/bf.h b/usr/src/cmd/sendmail/src/bf.h
new file mode 100644
index 0000000000..fa7f6814f2
--- /dev/null
+++ b/usr/src/cmd/sendmail/src/bf.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 1999-2002 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ * $Id: bf.h,v 8.16 2002/04/15 02:37:09 ca Exp $
+ *
+ * Contributed by Exactis.com, Inc.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#ifndef BF_H
+# define BF_H 1
+
+extern SM_FILE_T *bfopen __P((char *, MODE_T, size_t, long));
+extern int bfcommit __P((SM_FILE_T *));
+extern int bfrewind __P((SM_FILE_T *));
+extern int bftruncate __P((SM_FILE_T *));
+extern int bfclose __P((SM_FILE_T *));
+extern bool bftest __P((SM_FILE_T *));
+
+/* "what" flags for sm_io_setinfo() for the SM_FILE_TYPE file type */
+# define SM_BF_SETBUFSIZE 1000 /* set buffer size */
+# define SM_BF_COMMIT 1001 /* commit file to disk */
+# define SM_BF_TRUNCATE 1002 /* truncate the file */
+# define SM_BF_TEST 1003 /* historical support; temp */
+
+# define BF_FILE_TYPE "SendmailBufferedFile"
+#endif /* ! BF_H */
diff --git a/usr/src/cmd/sendmail/src/collect.c b/usr/src/cmd/sendmail/src/collect.c
new file mode 100644
index 0000000000..5544320e3f
--- /dev/null
+++ b/usr/src/cmd/sendmail/src/collect.c
@@ -0,0 +1,1124 @@
+/*
+ * Copyright (c) 1998-2004 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sendmail.h>
+
+SM_RCSID("@(#)$Id: collect.c,v 8.261 2005/02/16 23:38:51 ca Exp $")
+
+static void collecttimeout __P((int));
+static void eatfrom __P((char *volatile, ENVELOPE *));
+static void collect_doheader __P((ENVELOPE *));
+static SM_FILE_T *collect_dfopen __P((ENVELOPE *));
+static SM_FILE_T *collect_eoh __P((ENVELOPE *, int, int));
+
+/*
+** COLLECT_EOH -- end-of-header processing in collect()
+**
+** Called by collect() when it encounters the blank line
+** separating the header from the message body, or when it
+** encounters EOF in a message that contains only a header.
+**
+** Parameters:
+** e -- envelope
+** numhdrs -- number of headers
+** hdrslen -- length of headers
+**
+** Results:
+** NULL, or handle to open data file
+**
+** Side Effects:
+** end-of-header check ruleset is invoked.
+** envelope state is updated.
+** headers may be added and deleted.
+** selects the queue.
+** opens the data file.
+*/
+
+static SM_FILE_T *
+collect_eoh(e, numhdrs, hdrslen)
+ ENVELOPE *e;
+ int numhdrs;
+ int hdrslen;
+{
+ char hnum[16];
+ char hsize[16];
+
+ /* call the end-of-header check ruleset */
+ (void) sm_snprintf(hnum, sizeof hnum, "%d", numhdrs);
+ (void) sm_snprintf(hsize, sizeof hsize, "%d", hdrslen);
+ if (tTd(30, 10))
+ sm_dprintf("collect: rscheck(\"check_eoh\", \"%s $| %s\")\n",
+ hnum, hsize);
+ (void) rscheck("check_eoh", hnum, hsize, e, RSF_UNSTRUCTURED|RSF_COUNT,
+ 3, NULL, e->e_id);
+
+ /*
+ ** Process the header,
+ ** select the queue, open the data file.
+ */
+
+ collect_doheader(e);
+ return collect_dfopen(e);
+}
+
+/*
+** COLLECT_DOHEADER -- process header in collect()
+**
+** Called by collect() after it has finished parsing the header,
+** but before it selects the queue and creates the data file.
+** The results of processing the header will affect queue selection.
+**
+** Parameters:
+** e -- envelope
+**
+** Results:
+** none.
+**
+** Side Effects:
+** envelope state is updated.
+** headers may be added and deleted.
+*/
+
+static void
+collect_doheader(e)
+ ENVELOPE *e;
+{
+ /*
+ ** Find out some information from the headers.
+ ** Examples are who is the from person & the date.
+ */
+
+ eatheader(e, true, false);
+
+ if (GrabTo && e->e_sendqueue == NULL)
+ usrerr("No recipient addresses found in header");
+
+ /*
+ ** If we have a Return-Receipt-To:, turn it into a DSN.
+ */
+
+ if (RrtImpliesDsn && hvalue("return-receipt-to", e->e_header) != NULL)
+ {
+ ADDRESS *q;
+
+ for (q = e->e_sendqueue; q != NULL; q = q->q_next)
+ if (!bitset(QHASNOTIFY, q->q_flags))
+ q->q_flags |= QHASNOTIFY|QPINGONSUCCESS;
+ }
+
+ /*
+ ** Add an appropriate recipient line if we have none.
+ */
+
+ if (hvalue("to", e->e_header) != NULL ||
+ hvalue("cc", e->e_header) != NULL ||
+ hvalue("apparently-to", e->e_header) != NULL)
+ {
+ /* have a valid recipient header -- delete Bcc: headers */
+ e->e_flags |= EF_DELETE_BCC;
+ }
+ else if (hvalue("bcc", e->e_header) == NULL)
+ {
+ /* no valid recipient headers */
+ register ADDRESS *q;
+ char *hdr = NULL;
+
+ /* create a recipient field */
+ switch (NoRecipientAction)
+ {
+ case NRA_ADD_APPARENTLY_TO:
+ hdr = "Apparently-To";
+ break;
+
+ case NRA_ADD_TO:
+ hdr = "To";
+ break;
+
+ case NRA_ADD_BCC:
+ addheader("Bcc", " ", 0, e);
+ break;
+
+ case NRA_ADD_TO_UNDISCLOSED:
+ addheader("To", "undisclosed-recipients:;", 0, e);
+ break;
+ }
+
+ if (hdr != NULL)
+ {
+ for (q = e->e_sendqueue; q != NULL; q = q->q_next)
+ {
+ if (q->q_alias != NULL)
+ continue;
+ if (tTd(30, 3))
+ sm_dprintf("Adding %s: %s\n",
+ hdr, q->q_paddr);
+ addheader(hdr, q->q_paddr, 0, e);
+ }
+ }
+ }
+}
+
+/*
+** COLLECT_DFOPEN -- open the message data file
+**
+** Called by collect() after it has finished processing the header.
+** Queue selection occurs at this point, possibly based on the
+** envelope's recipient list and on header information.
+**
+** Parameters:
+** e -- envelope
+**
+** Results:
+** NULL, or a pointer to an open data file,
+** into which the message body will be written by collect().
+**
+** Side Effects:
+** Calls syserr, sets EF_FATALERRS and returns NULL
+** if there is insufficient disk space.
+** Aborts process if data file could not be opened.
+** Otherwise, the queue is selected,
+** e->e_{dfino,dfdev,msgsize,flags} are updated,
+** and a pointer to an open data file is returned.
+*/
+
+static SM_FILE_T *
+collect_dfopen(e)
+ ENVELOPE *e;
+{
+ MODE_T oldumask = 0;
+ int dfd;
+ struct stat stbuf;
+ SM_FILE_T *df;
+ char *dfname;
+
+ if (!setnewqueue(e))
+ return NULL;
+
+ dfname = queuename(e, DATAFL_LETTER);
+ if (bitset(S_IWGRP, QueueFileMode))
+ oldumask = umask(002);
+ df = bfopen(dfname, QueueFileMode, DataFileBufferSize,
+ SFF_OPENASROOT);
+ if (bitset(S_IWGRP, QueueFileMode))
+ (void) umask(oldumask);
+ if (df == NULL)
+ {
+ syserr("@Cannot create %s", dfname);
+ e->e_flags |= EF_NO_BODY_RETN;
+ flush_errors(true);
+ finis(false, true, ExitStat);
+ /* NOTREACHED */
+ }
+ dfd = sm_io_getinfo(df, SM_IO_WHAT_FD, NULL);
+ if (dfd < 0 || fstat(dfd, &stbuf) < 0)
+ e->e_dfino = -1;
+ else
+ {
+ e->e_dfdev = stbuf.st_dev;
+ e->e_dfino = stbuf.st_ino;
+ }
+ e->e_flags |= EF_HAS_DF;
+ return df;
+}
+
+/*
+** COLLECT -- read & parse message header & make temp file.
+**
+** Creates a temporary file name and copies the standard
+** input to that file. Leading UNIX-style "From" lines are
+** stripped off (after important information is extracted).
+**
+** Parameters:
+** fp -- file to read.
+** smtpmode -- if set, we are running SMTP: give an RFC821
+** style message to say we are ready to collect
+** input, and never ignore a single dot to mean
+** end of message.
+** hdrp -- the location to stash the header.
+** e -- the current envelope.
+** rsetsize -- reset e_msgsize?
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** If successful,
+** - Data file is created and filled, and e->e_dfp is set.
+** - The from person may be set.
+** If the "enough disk space" check fails,
+** - syserr is called.
+** - e->e_dfp is NULL.
+** - e->e_flags & EF_FATALERRS is set.
+** - collect() returns.
+** If data file cannot be created, the process is terminated.
+*/
+
+static jmp_buf CtxCollectTimeout;
+static bool volatile CollectProgress;
+static SM_EVENT *volatile CollectTimeout = NULL;
+
+/* values for input state machine */
+#define IS_NORM 0 /* middle of line */
+#define IS_BOL 1 /* beginning of line */
+#define IS_DOT 2 /* read a dot at beginning of line */
+#define IS_DOTCR 3 /* read ".\r" at beginning of line */
+#define IS_CR 4 /* read a carriage return */
+
+/* values for message state machine */
+#define MS_UFROM 0 /* reading Unix from line */
+#define MS_HEADER 1 /* reading message header */
+#define MS_BODY 2 /* reading message body */
+#define MS_DISCARD 3 /* discarding rest of message */
+
+void
+collect(fp, smtpmode, hdrp, e, rsetsize)
+ SM_FILE_T *fp;
+ bool smtpmode;
+ HDR **hdrp;
+ register ENVELOPE *e;
+ bool rsetsize;
+{
+ register SM_FILE_T *volatile df;
+ volatile bool ignrdot;
+ volatile int dbto;
+ register char *volatile bp;
+ volatile int c;
+ volatile bool inputerr;
+ bool headeronly;
+ char *volatile buf;
+ volatile int buflen;
+ volatile int istate;
+ volatile int mstate;
+ volatile int hdrslen;
+ volatile int numhdrs;
+ volatile int afd;
+ unsigned char *volatile pbp;
+ unsigned char peekbuf[8];
+ char bufbuf[MAXLINE];
+
+ df = NULL;
+ ignrdot = smtpmode ? false : IgnrDot;
+ dbto = smtpmode ? (int) TimeOuts.to_datablock : 0;
+ c = SM_IO_EOF;
+ inputerr = false;
+ headeronly = hdrp != NULL;
+ hdrslen = 0;
+ numhdrs = 0;
+ HasEightBits = false;
+ buf = bp = bufbuf;
+ buflen = sizeof bufbuf;
+ pbp = peekbuf;
+ istate = IS_BOL;
+ mstate = SaveFrom ? MS_HEADER : MS_UFROM;
+ CollectProgress = false;
+
+ /*
+ ** Tell ARPANET to go ahead.
+ */
+
+ if (smtpmode)
+ message("354 Enter mail, end with \".\" on a line by itself");
+
+ if (tTd(30, 2))
+ sm_dprintf("collect\n");
+
+ /*
+ ** Read the message.
+ **
+ ** This is done using two interleaved state machines.
+ ** The input state machine is looking for things like
+ ** hidden dots; the message state machine is handling
+ ** the larger picture (e.g., header versus body).
+ */
+
+ if (dbto != 0)
+ {
+ /* handle possible input timeout */
+ if (setjmp(CtxCollectTimeout) != 0)
+ {
+ if (LogLevel > 2)
+ sm_syslog(LOG_NOTICE, e->e_id,
+ "timeout waiting for input from %s during message collect",
+ CURHOSTNAME);
+ errno = 0;
+ if (smtpmode)
+ {
+ /*
+ ** Override e_message in usrerr() as this
+ ** is the reason for failure that should
+ ** be logged for undelivered recipients.
+ */
+
+ e->e_message = NULL;
+ }
+ usrerr("451 4.4.1 timeout waiting for input during message collect");
+ goto readerr;
+ }
+ CollectTimeout = sm_setevent(dbto, collecttimeout, dbto);
+ }
+
+ if (rsetsize)
+ e->e_msgsize = 0;
+ for (;;)
+ {
+ if (tTd(30, 35))
+ sm_dprintf("top, istate=%d, mstate=%d\n", istate,
+ mstate);
+ for (;;)
+ {
+ if (pbp > peekbuf)
+ c = *--pbp;
+ else
+ {
+ while (!sm_io_eof(fp) && !sm_io_error(fp))
+ {
+ errno = 0;
+ c = sm_io_getc(fp, SM_TIME_DEFAULT);
+ if (c == SM_IO_EOF && errno == EINTR)
+ {
+ /* Interrupted, retry */
+ sm_io_clearerr(fp);
+ continue;
+ }
+ break;
+ }
+ CollectProgress = true;
+ if (TrafficLogFile != NULL && !headeronly)
+ {
+ if (istate == IS_BOL)
+ (void) sm_io_fprintf(TrafficLogFile,
+ SM_TIME_DEFAULT,
+ "%05d <<< ",
+ (int) CurrentPid);
+ if (c == SM_IO_EOF)
+ (void) sm_io_fprintf(TrafficLogFile,
+ SM_TIME_DEFAULT,
+ "[EOF]\n");
+ else
+ (void) sm_io_putc(TrafficLogFile,
+ SM_TIME_DEFAULT,
+ c);
+ }
+ if (c == SM_IO_EOF)
+ goto readerr;
+ if (SevenBitInput)
+ c &= 0x7f;
+ else
+ HasEightBits |= bitset(0x80, c);
+ }
+ if (tTd(30, 94))
+ sm_dprintf("istate=%d, c=%c (0x%x)\n",
+ istate, (char) c, c);
+ switch (istate)
+ {
+ case IS_BOL:
+ if (c == '.')
+ {
+ istate = IS_DOT;
+ continue;
+ }
+ break;
+
+ case IS_DOT:
+ if (c == '\n' && !ignrdot &&
+ !bitset(EF_NL_NOT_EOL, e->e_flags))
+ goto readerr;
+ else if (c == '\r' &&
+ !bitset(EF_CRLF_NOT_EOL, e->e_flags))
+ {
+ istate = IS_DOTCR;
+ continue;
+ }
+ else if (ignrdot ||
+ (c != '.' &&
+ OpMode != MD_SMTP &&
+ OpMode != MD_DAEMON &&
+ OpMode != MD_ARPAFTP))
+
+ {
+ SM_ASSERT(pbp < peekbuf + sizeof(peekbuf));
+ *pbp++ = c;
+ c = '.';
+ }
+ break;
+
+ case IS_DOTCR:
+ if (c == '\n' && !ignrdot)
+ goto readerr;
+ else
+ {
+ /* push back the ".\rx" */
+ SM_ASSERT(pbp < peekbuf + sizeof(peekbuf));
+ *pbp++ = c;
+ if (OpMode != MD_SMTP &&
+ OpMode != MD_DAEMON &&
+ OpMode != MD_ARPAFTP)
+ {
+ SM_ASSERT(pbp < peekbuf +
+ sizeof(peekbuf));
+ *pbp++ = '\r';
+ c = '.';
+ }
+ else
+ c = '\r';
+ }
+ break;
+
+ case IS_CR:
+ if (c == '\n')
+ istate = IS_BOL;
+ else
+ {
+ (void) sm_io_ungetc(fp, SM_TIME_DEFAULT,
+ c);
+ c = '\r';
+ istate = IS_NORM;
+ }
+ goto bufferchar;
+ }
+
+ if (c == '\r' && !bitset(EF_CRLF_NOT_EOL, e->e_flags))
+ {
+ istate = IS_CR;
+ continue;
+ }
+ else if (c == '\n' && !bitset(EF_NL_NOT_EOL,
+ e->e_flags))
+ istate = IS_BOL;
+ else
+ istate = IS_NORM;
+
+bufferchar:
+ if (!headeronly)
+ {
+ /* no overflow? */
+ if (e->e_msgsize >= 0)
+ {
+ e->e_msgsize++;
+ if (MaxMessageSize > 0 &&
+ !bitset(EF_TOOBIG, e->e_flags) &&
+ e->e_msgsize > MaxMessageSize)
+ e->e_flags |= EF_TOOBIG;
+ }
+ }
+ switch (mstate)
+ {
+ case MS_BODY:
+ /* just put the character out */
+ if (!bitset(EF_TOOBIG, e->e_flags))
+ (void) sm_io_putc(df, SM_TIME_DEFAULT,
+ c);
+
+ /* FALLTHROUGH */
+
+ case MS_DISCARD:
+ continue;
+ }
+
+ SM_ASSERT(mstate == MS_UFROM || mstate == MS_HEADER);
+
+ /* header -- buffer up */
+ if (bp >= &buf[buflen - 2])
+ {
+ char *obuf;
+
+ /* out of space for header */
+ obuf = buf;
+ if (buflen < MEMCHUNKSIZE)
+ buflen *= 2;
+ else
+ buflen += MEMCHUNKSIZE;
+ buf = xalloc(buflen);
+ memmove(buf, obuf, bp - obuf);
+ bp = &buf[bp - obuf];
+ if (obuf != bufbuf)
+ sm_free(obuf); /* XXX */
+ }
+
+ /*
+ ** XXX Notice: the logic here is broken.
+ ** An input to sendmail that doesn't contain a
+ ** header but starts immediately with the body whose
+ ** first line contain characters which match the
+ ** following "if" will cause problems: those
+ ** characters will NOT appear in the output...
+ ** Do we care?
+ */
+
+ if (c >= 0200 && c <= 0237)
+ {
+#if 0 /* causes complaints -- figure out something for 8.n+1 */
+ usrerr("Illegal character 0x%x in header", c);
+#else /* 0 */
+ /* EMPTY */
+#endif /* 0 */
+ }
+ else if (c != '\0')
+ {
+ *bp++ = c;
+ ++hdrslen;
+ if (!headeronly &&
+ MaxHeadersLength > 0 &&
+ hdrslen > MaxHeadersLength)
+ {
+ sm_syslog(LOG_NOTICE, e->e_id,
+ "headers too large (%d max) from %s during message collect",
+ MaxHeadersLength,
+ CURHOSTNAME);
+ errno = 0;
+ e->e_flags |= EF_CLRQUEUE;
+ e->e_status = "5.6.0";
+ usrerrenh(e->e_status,
+ "552 Headers too large (%d max)",
+ MaxHeadersLength);
+ mstate = MS_DISCARD;
+ }
+ }
+ if (istate == IS_BOL)
+ break;
+ }
+ *bp = '\0';
+
+nextstate:
+ if (tTd(30, 35))
+ sm_dprintf("nextstate, istate=%d, mstate=%d, line = \"%s\"\n",
+ istate, mstate, buf);
+ switch (mstate)
+ {
+ case MS_UFROM:
+ mstate = MS_HEADER;
+#ifndef NOTUNIX
+ if (strncmp(buf, "From ", 5) == 0)
+ {
+ bp = buf;
+ eatfrom(buf, e);
+ continue;
+ }
+#endif /* ! NOTUNIX */
+ /* FALLTHROUGH */
+
+ case MS_HEADER:
+ if (!isheader(buf))
+ {
+ mstate = MS_BODY;
+ goto nextstate;
+ }
+
+ /* check for possible continuation line */
+ do
+ {
+ sm_io_clearerr(fp);
+ errno = 0;
+ c = sm_io_getc(fp, SM_TIME_DEFAULT);
+ } while (c == SM_IO_EOF && errno == EINTR);
+ if (c != SM_IO_EOF)
+ (void) sm_io_ungetc(fp, SM_TIME_DEFAULT, c);
+ if (c == ' ' || c == '\t')
+ {
+ /* yep -- defer this */
+ continue;
+ }
+
+ /* trim off trailing CRLF or NL */
+ SM_ASSERT(bp > buf);
+ if (*--bp != '\n' || *--bp != '\r')
+ bp++;
+ *bp = '\0';
+
+ if (bitset(H_EOH, chompheader(buf,
+ CHHDR_CHECK | CHHDR_USER,
+ hdrp, e)))
+ {
+ mstate = MS_BODY;
+ goto nextstate;
+ }
+ numhdrs++;
+ break;
+
+ case MS_BODY:
+ if (tTd(30, 1))
+ sm_dprintf("EOH\n");
+
+ if (headeronly)
+ goto readerr;
+
+ df = collect_eoh(e, numhdrs, hdrslen);
+ if (df == NULL)
+ e->e_flags |= EF_TOOBIG;
+
+ bp = buf;
+
+ /* toss blank line */
+ if ((!bitset(EF_CRLF_NOT_EOL, e->e_flags) &&
+ bp[0] == '\r' && bp[1] == '\n') ||
+ (!bitset(EF_NL_NOT_EOL, e->e_flags) &&
+ bp[0] == '\n'))
+ {
+ break;
+ }
+
+ /* if not a blank separator, write it out */
+ if (!bitset(EF_TOOBIG, e->e_flags))
+ {
+ while (*bp != '\0')
+ (void) sm_io_putc(df, SM_TIME_DEFAULT,
+ *bp++);
+ }
+ break;
+ }
+ bp = buf;
+ }
+
+readerr:
+ if ((sm_io_eof(fp) && smtpmode) || sm_io_error(fp))
+ {
+ const char *errmsg;
+
+ if (sm_io_eof(fp))
+ errmsg = "unexpected close";
+ else
+ errmsg = sm_errstring(errno);
+ if (tTd(30, 1))
+ sm_dprintf("collect: premature EOM: %s\n", errmsg);
+ if (LogLevel > 1)
+ sm_syslog(LOG_WARNING, e->e_id,
+ "collect: premature EOM: %s", errmsg);
+ inputerr = true;
+ }
+
+ /* reset global timer */
+ if (CollectTimeout != NULL)
+ sm_clrevent(CollectTimeout);
+
+ if (headeronly)
+ return;
+
+ if (mstate != MS_BODY)
+ {
+ /* no body or discard, so we never opened the data file */
+ SM_ASSERT(df == NULL);
+ df = collect_eoh(e, numhdrs, hdrslen);
+ }
+
+ if (df == NULL)
+ {
+ /* skip next few clauses */
+ /* EMPTY */
+ }
+ else if (sm_io_flush(df, SM_TIME_DEFAULT) != 0 || sm_io_error(df))
+ {
+ dferror(df, "sm_io_flush||sm_io_error", e);
+ flush_errors(true);
+ finis(true, true, ExitStat);
+ /* NOTREACHED */
+ }
+ else if (SuperSafe == SAFE_NO ||
+ SuperSafe == SAFE_INTERACTIVE ||
+ (SuperSafe == SAFE_REALLY_POSTMILTER && smtpmode))
+ {
+ /* skip next few clauses */
+ /* EMPTY */
+ /* Note: updfs() is not called in this case! */
+ }
+ else if (sm_io_setinfo(df, SM_BF_COMMIT, NULL) < 0 && errno != EINVAL)
+ {
+ int save_errno = errno;
+
+ if (save_errno == EEXIST)
+ {
+ char *dfile;
+ struct stat st;
+ int dfd;
+
+ dfile = queuename(e, DATAFL_LETTER);
+ if (stat(dfile, &st) < 0)
+ st.st_size = -1;
+ errno = EEXIST;
+ syserr("@collect: bfcommit(%s): already on disk, size=%ld",
+ dfile, (long) st.st_size);
+ dfd = sm_io_getinfo(df, SM_IO_WHAT_FD, NULL);
+ if (dfd >= 0)
+ dumpfd(dfd, true, true);
+ }
+ errno = save_errno;
+ dferror(df, "bfcommit", e);
+ flush_errors(true);
+ finis(save_errno != EEXIST, true, ExitStat);
+ }
+ else if ((afd = sm_io_getinfo(df, SM_IO_WHAT_FD, NULL)) < 0)
+ {
+ dferror(df, "sm_io_getinfo", e);
+ flush_errors(true);
+ finis(true, true, ExitStat);
+ /* NOTREACHED */
+ }
+ else if (fsync(afd) < 0)
+ {
+ dferror(df, "fsync", e);
+ flush_errors(true);
+ finis(true, true, ExitStat);
+ /* NOTREACHED */
+ }
+ else if (sm_io_close(df, SM_TIME_DEFAULT) < 0)
+ {
+ dferror(df, "sm_io_close", e);
+ flush_errors(true);
+ finis(true, true, ExitStat);
+ /* NOTREACHED */
+ }
+ else
+ {
+ /* everything is happily flushed to disk */
+ df = NULL;
+
+ /* remove from available space in filesystem */
+ updfs(e, 0, 1, "collect");
+ }
+
+ /* An EOF when running SMTP is an error */
+ if (inputerr && (OpMode == MD_SMTP || OpMode == MD_DAEMON))
+ {
+ char *host;
+ char *problem;
+ ADDRESS *q;
+
+ host = RealHostName;
+ if (host == NULL)
+ host = "localhost";
+
+ if (sm_io_eof(fp))
+ problem = "unexpected close";
+ else if (sm_io_error(fp))
+ problem = "I/O error";
+ else
+ problem = "read timeout";
+ if (LogLevel > 0 && sm_io_eof(fp))
+ sm_syslog(LOG_NOTICE, e->e_id,
+ "collect: %s on connection from %.100s, sender=%s",
+ problem, host,
+ shortenstring(e->e_from.q_paddr, MAXSHORTSTR));
+ if (sm_io_eof(fp))
+ usrerr("451 4.4.1 collect: %s on connection from %s, from=%s",
+ problem, host,
+ shortenstring(e->e_from.q_paddr, MAXSHORTSTR));
+ else
+ syserr("451 4.4.1 collect: %s on connection from %s, from=%s",
+ problem, host,
+ shortenstring(e->e_from.q_paddr, MAXSHORTSTR));
+
+ /* don't return an error indication */
+ e->e_to = NULL;
+ e->e_flags &= ~EF_FATALERRS;
+ e->e_flags |= EF_CLRQUEUE;
+
+ /* Don't send any message notification to sender */
+ for (q = e->e_sendqueue; q != NULL; q = q->q_next)
+ {
+ if (QS_IS_DEAD(q->q_state))
+ continue;
+ q->q_state = QS_FATALERR;
+ }
+
+ finis(true, true, ExitStat);
+ /* NOTREACHED */
+ }
+
+ /* Log collection information. */
+ if (bitset(EF_LOGSENDER, e->e_flags) && LogLevel > 4)
+ {
+ logsender(e, e->e_msgid);
+ e->e_flags &= ~EF_LOGSENDER;
+ }
+
+ /* check for message too large */
+ if (bitset(EF_TOOBIG, e->e_flags))
+ {
+ e->e_flags |= EF_NO_BODY_RETN|EF_CLRQUEUE;
+ if (!bitset(EF_FATALERRS, e->e_flags))
+ {
+ e->e_status = "5.2.3";
+ usrerrenh(e->e_status,
+ "552 Message exceeds maximum fixed size (%ld)",
+ MaxMessageSize);
+ if (LogLevel > 6)
+ sm_syslog(LOG_NOTICE, e->e_id,
+ "message size (%ld) exceeds maximum (%ld)",
+ e->e_msgsize, MaxMessageSize);
+ }
+ }
+
+ /* check for illegal 8-bit data */
+ if (HasEightBits)
+ {
+ e->e_flags |= EF_HAS8BIT;
+ if (!bitset(MM_PASS8BIT|MM_MIME8BIT, MimeMode) &&
+ !bitset(EF_IS_MIME, e->e_flags))
+ {
+ e->e_status = "5.6.1";
+ usrerrenh(e->e_status, "554 Eight bit data not allowed");
+ }
+ }
+ else
+ {
+ /* if it claimed to be 8 bits, well, it lied.... */
+ if (e->e_bodytype != NULL &&
+ sm_strcasecmp(e->e_bodytype, "8BITMIME") == 0)
+ e->e_bodytype = "7BIT";
+ }
+
+ if (SuperSafe == SAFE_REALLY && !bitset(EF_FATALERRS, e->e_flags))
+ {
+ char *dfname = queuename(e, DATAFL_LETTER);
+ if ((e->e_dfp = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, dfname,
+ SM_IO_RDONLY_B, NULL)) == NULL)
+ {
+ /* we haven't acked receipt yet, so just chuck this */
+ syserr("@Cannot reopen %s", dfname);
+ finis(true, true, ExitStat);
+ /* NOTREACHED */
+ }
+ }
+ else
+ e->e_dfp = df;
+
+ /* collect statistics */
+ if (OpMode != MD_VERIFY)
+ {
+ /*
+ ** Recalculate e_msgpriority, it is done at in eatheader()
+ ** which is called (in 8.12) after the header is collected,
+ ** hence e_msgsize is (most likely) incorrect.
+ */
+
+ e->e_msgpriority = e->e_msgsize
+ - e->e_class * WkClassFact
+ + e->e_nrcpts * WkRecipFact;
+ markstats(e, (ADDRESS *) NULL, STATS_NORMAL);
+ }
+}
+
+static void
+collecttimeout(timeout)
+ int timeout;
+{
+ int save_errno = errno;
+
+ /*
+ ** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+ ** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+ ** DOING.
+ */
+
+ if (CollectProgress)
+ {
+ /* reset the timeout */
+ CollectTimeout = sm_sigsafe_setevent(timeout, collecttimeout,
+ timeout);
+ CollectProgress = false;
+ }
+ else
+ {
+ /* event is done */
+ CollectTimeout = NULL;
+ }
+
+ /* if no progress was made or problem resetting event, die now */
+ if (CollectTimeout == NULL)
+ {
+ errno = ETIMEDOUT;
+ longjmp(CtxCollectTimeout, 1);
+ }
+ errno = save_errno;
+}
+/*
+** DFERROR -- signal error on writing the data file.
+**
+** Called by collect(). Collect() always terminates the process
+** immediately after calling dferror(), which means that the SMTP
+** session will be terminated, which means that any error message
+** issued by dferror must be a 421 error, as per RFC 821.
+**
+** Parameters:
+** df -- the file pointer for the data file.
+** msg -- detailed message.
+** e -- the current envelope.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Gives an error message.
+** Arranges for following output to go elsewhere.
+*/
+
+void
+dferror(df, msg, e)
+ SM_FILE_T *volatile df;
+ char *msg;
+ register ENVELOPE *e;
+{
+ char *dfname;
+
+ dfname = queuename(e, DATAFL_LETTER);
+ setstat(EX_IOERR);
+ if (errno == ENOSPC)
+ {
+#if STAT64 > 0
+ struct stat64 st;
+#else /* STAT64 > 0 */
+ struct stat st;
+#endif /* STAT64 > 0 */
+ long avail;
+ long bsize;
+
+ e->e_flags |= EF_NO_BODY_RETN;
+
+ if (
+#if STAT64 > 0
+ fstat64(sm_io_getinfo(df, SM_IO_WHAT_FD, NULL), &st)
+#else /* STAT64 > 0 */
+ fstat(sm_io_getinfo(df, SM_IO_WHAT_FD, NULL), &st)
+#endif /* STAT64 > 0 */
+ < 0)
+ st.st_size = 0;
+ (void) sm_io_reopen(SmFtStdio, SM_TIME_DEFAULT, dfname,
+ SM_IO_WRONLY_B, NULL, df);
+ if (st.st_size <= 0)
+ (void) sm_io_fprintf(df, SM_TIME_DEFAULT,
+ "\n*** Mail could not be accepted");
+ else
+ (void) sm_io_fprintf(df, SM_TIME_DEFAULT,
+ "\n*** Mail of at least %llu bytes could not be accepted\n",
+ (ULONGLONG_T) st.st_size);
+ (void) sm_io_fprintf(df, SM_TIME_DEFAULT,
+ "*** at %s due to lack of disk space for temp file.\n",
+ MyHostName);
+ avail = freediskspace(qid_printqueue(e->e_qgrp, e->e_qdir),
+ &bsize);
+ if (avail > 0)
+ {
+ if (bsize > 1024)
+ avail *= bsize / 1024;
+ else if (bsize < 1024)
+ avail /= 1024 / bsize;
+ (void) sm_io_fprintf(df, SM_TIME_DEFAULT,
+ "*** Currently, %ld kilobytes are available for mail temp files.\n",
+ avail);
+ }
+#if 0
+ /* Wrong response code; should be 421. */
+ e->e_status = "4.3.1";
+ usrerrenh(e->e_status, "452 Out of disk space for temp file");
+#else /* 0 */
+ syserr("421 4.3.1 Out of disk space for temp file");
+#endif /* 0 */
+ }
+ else
+ syserr("421 4.3.0 collect: Cannot write %s (%s, uid=%d, gid=%d)",
+ dfname, msg, (int) geteuid(), (int) getegid());
+ if (sm_io_reopen(SmFtStdio, SM_TIME_DEFAULT, SM_PATH_DEVNULL,
+ SM_IO_WRONLY, NULL, df) == NULL)
+ sm_syslog(LOG_ERR, e->e_id,
+ "dferror: sm_io_reopen(\"/dev/null\") failed: %s",
+ sm_errstring(errno));
+}
+/*
+** EATFROM -- chew up a UNIX style from line and process
+**
+** This does indeed make some assumptions about the format
+** of UNIX messages.
+**
+** Parameters:
+** fm -- the from line.
+** e -- envelope
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** extracts what information it can from the header,
+** such as the date.
+*/
+
+#ifndef NOTUNIX
+
+static char *DowList[] =
+{
+ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", NULL
+};
+
+static char *MonthList[] =
+{
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
+ NULL
+};
+
+static void
+eatfrom(fm, e)
+ char *volatile fm;
+ register ENVELOPE *e;
+{
+ register char *p;
+ register char **dt;
+
+ if (tTd(30, 2))
+ sm_dprintf("eatfrom(%s)\n", fm);
+
+ /* find the date part */
+ p = fm;
+ while (*p != '\0')
+ {
+ /* skip a word */
+ while (*p != '\0' && *p != ' ')
+ p++;
+ while (*p == ' ')
+ p++;
+ if (strlen(p) < 17)
+ {
+ /* no room for the date */
+ return;
+ }
+ if (!(isascii(*p) && isupper(*p)) ||
+ p[3] != ' ' || p[13] != ':' || p[16] != ':')
+ continue;
+
+ /* we have a possible date */
+ for (dt = DowList; *dt != NULL; dt++)
+ if (strncmp(*dt, p, 3) == 0)
+ break;
+ if (*dt == NULL)
+ continue;
+
+ for (dt = MonthList; *dt != NULL; dt++)
+ {
+ if (strncmp(*dt, &p[4], 3) == 0)
+ break;
+ }
+ if (*dt != NULL)
+ break;
+ }
+
+ if (*p != '\0')
+ {
+ char *q, buf[25];
+
+ /* we have found a date */
+ (void) sm_strlcpy(buf, p, sizeof(buf));
+ q = arpadate(buf);
+ macdefine(&e->e_macro, A_TEMP, 'a', q);
+ }
+}
+#endif /* ! NOTUNIX */
diff --git a/usr/src/cmd/sendmail/src/conf.c b/usr/src/cmd/sendmail/src/conf.c
new file mode 100644
index 0000000000..9a42cb0ff4
--- /dev/null
+++ b/usr/src/cmd/sendmail/src/conf.c
@@ -0,0 +1,6323 @@
+/*
+ * Copyright (c) 1998-2005 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+/*
+ * Copyright 1999-2004 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sendmail.h>
+
+SM_RCSID("@(#)$Id: conf.c,v 8.1061 2005/03/07 17:18:44 ca Exp $")
+SM_IDSTR(i2, "%W% (Sun) %G%")
+
+#include <sendmail/pathnames.h>
+#if NEWDB
+# include "sm/bdb.h"
+#endif /* NEWDB */
+
+# include <sys/ioctl.h>
+# include <sys/param.h>
+
+#include <limits.h>
+#if NETINET || NETINET6
+# include <arpa/inet.h>
+#endif /* NETINET || NETINET6 */
+#if HASULIMIT && defined(HPUX11)
+# include <ulimit.h>
+#endif /* HASULIMIT && defined(HPUX11) */
+
+static void setupmaps __P((void));
+static void setupmailers __P((void));
+static void setupqueues __P((void));
+static int get_num_procs_online __P((void));
+static int add_hostnames __P((SOCKADDR *));
+
+#if NETINET6 && NEEDSGETIPNODE
+static struct hostent *getipnodebyname __P((char *, int, int, int *));
+static struct hostent *getipnodebyaddr __P((char *, int, int, int *));
+#endif /* NETINET6 && NEEDSGETIPNODE */
+
+
+/*
+** CONF.C -- Sendmail Configuration Tables.
+**
+** Defines the configuration of this installation.
+**
+** Configuration Variables:
+** HdrInfo -- a table describing well-known header fields.
+** Each entry has the field name and some flags,
+** which are described in sendmail.h.
+**
+** Notes:
+** I have tried to put almost all the reasonable
+** configuration information into the configuration
+** file read at runtime. My intent is that anything
+** here is a function of the version of UNIX you
+** are running, or is really static -- for example
+** the headers are a superset of widely used
+** protocols. If you find yourself playing with
+** this file too much, you may be making a mistake!
+*/
+
+
+/*
+** Header info table
+** Final (null) entry contains the flags used for any other field.
+**
+** Not all of these are actually handled specially by sendmail
+** at this time. They are included as placeholders, to let
+** you know that "someday" I intend to have sendmail do
+** something with them.
+*/
+
+struct hdrinfo HdrInfo[] =
+{
+ /* originator fields, most to least significant */
+ { "resent-sender", H_FROM|H_RESENT, NULL },
+ { "resent-from", H_FROM|H_RESENT, NULL },
+ { "resent-reply-to", H_FROM|H_RESENT, NULL },
+ { "sender", H_FROM, NULL },
+ { "from", H_FROM, NULL },
+ { "reply-to", H_FROM, NULL },
+ { "errors-to", H_FROM|H_ERRORSTO, NULL },
+ { "full-name", H_ACHECK, NULL },
+ { "return-receipt-to", H_RECEIPTTO, NULL },
+ { "delivery-receipt-to", H_RECEIPTTO, NULL },
+ { "disposition-notification-to", H_FROM, NULL },
+
+ /* destination fields */
+ { "to", H_RCPT, NULL },
+ { "resent-to", H_RCPT|H_RESENT, NULL },
+ { "cc", H_RCPT, NULL },
+ { "resent-cc", H_RCPT|H_RESENT, NULL },
+ { "bcc", H_RCPT|H_BCC, NULL },
+ { "resent-bcc", H_RCPT|H_BCC|H_RESENT, NULL },
+ { "apparently-to", H_RCPT, NULL },
+
+ /* message identification and control */
+ { "message-id", 0, NULL },
+ { "resent-message-id", H_RESENT, NULL },
+ { "message", H_EOH, NULL },
+ { "text", H_EOH, NULL },
+
+ /* date fields */
+ { "date", 0, NULL },
+ { "resent-date", H_RESENT, NULL },
+
+ /* trace fields */
+ { "received", H_TRACE|H_FORCE, NULL },
+ { "x400-received", H_TRACE|H_FORCE, NULL },
+ { "via", H_TRACE|H_FORCE, NULL },
+ { "mail-from", H_TRACE|H_FORCE, NULL },
+
+ /* miscellaneous fields */
+ { "comments", H_FORCE|H_ENCODABLE, NULL },
+ { "return-path", H_FORCE|H_ACHECK|H_BINDLATE, NULL },
+ { "content-transfer-encoding", H_CTE, NULL },
+ { "content-type", H_CTYPE, NULL },
+ { "content-length", H_ACHECK, NULL },
+ { "subject", H_ENCODABLE, NULL },
+ { "x-authentication-warning", H_FORCE, NULL },
+
+ { NULL, 0, NULL }
+};
+
+
+
+/*
+** Privacy values
+*/
+
+struct prival PrivacyValues[] =
+{
+ { "public", PRIV_PUBLIC },
+ { "needmailhelo", PRIV_NEEDMAILHELO },
+ { "needexpnhelo", PRIV_NEEDEXPNHELO },
+ { "needvrfyhelo", PRIV_NEEDVRFYHELO },
+ { "noexpn", PRIV_NOEXPN },
+ { "novrfy", PRIV_NOVRFY },
+ { "restrictexpand", PRIV_RESTRICTEXPAND },
+ { "restrictmailq", PRIV_RESTRICTMAILQ },
+ { "restrictqrun", PRIV_RESTRICTQRUN },
+ { "noetrn", PRIV_NOETRN },
+ { "noverb", PRIV_NOVERB },
+ { "authwarnings", PRIV_AUTHWARNINGS },
+ { "noreceipts", PRIV_NORECEIPTS },
+ { "nobodyreturn", PRIV_NOBODYRETN },
+ { "goaway", PRIV_GOAWAY },
+#if _FFR_PRIV_NOACTUALRECIPIENT
+ { "noactualrecipient", PRIV_NOACTUALRECIPIENT },
+#endif /* _FFR_PRIV_NOACTUALRECIPIENT */
+ { NULL, 0 }
+};
+
+/*
+** DontBlameSendmail values
+*/
+
+struct dbsval DontBlameSendmailValues[] =
+{
+ { "safe", DBS_SAFE },
+ { "assumesafechown", DBS_ASSUMESAFECHOWN },
+ { "groupwritabledirpathsafe", DBS_GROUPWRITABLEDIRPATHSAFE },
+ { "groupwritableforwardfilesafe",
+ DBS_GROUPWRITABLEFORWARDFILESAFE },
+ { "groupwritableincludefilesafe",
+ DBS_GROUPWRITABLEINCLUDEFILESAFE },
+ { "groupwritablealiasfile", DBS_GROUPWRITABLEALIASFILE },
+ { "worldwritablealiasfile", DBS_WORLDWRITABLEALIASFILE },
+ { "forwardfileinunsafedirpath", DBS_FORWARDFILEINUNSAFEDIRPATH },
+ { "includefileinunsafedirpath", DBS_INCLUDEFILEINUNSAFEDIRPATH },
+ { "mapinunsafedirpath", DBS_MAPINUNSAFEDIRPATH },
+ { "linkedaliasfileinwritabledir",
+ DBS_LINKEDALIASFILEINWRITABLEDIR },
+ { "linkedclassfileinwritabledir",
+ DBS_LINKEDCLASSFILEINWRITABLEDIR },
+ { "linkedforwardfileinwritabledir",
+ DBS_LINKEDFORWARDFILEINWRITABLEDIR },
+ { "linkedincludefileinwritabledir",
+ DBS_LINKEDINCLUDEFILEINWRITABLEDIR },
+ { "linkedmapinwritabledir", DBS_LINKEDMAPINWRITABLEDIR },
+ { "linkedserviceswitchfileinwritabledir",
+ DBS_LINKEDSERVICESWITCHFILEINWRITABLEDIR },
+ { "filedeliverytohardlink", DBS_FILEDELIVERYTOHARDLINK },
+ { "filedeliverytosymlink", DBS_FILEDELIVERYTOSYMLINK },
+ { "writemaptohardlink", DBS_WRITEMAPTOHARDLINK },
+ { "writemaptosymlink", DBS_WRITEMAPTOSYMLINK },
+ { "writestatstohardlink", DBS_WRITESTATSTOHARDLINK },
+ { "writestatstosymlink", DBS_WRITESTATSTOSYMLINK },
+ { "forwardfileingroupwritabledirpath",
+ DBS_FORWARDFILEINGROUPWRITABLEDIRPATH },
+ { "includefileingroupwritabledirpath",
+ DBS_INCLUDEFILEINGROUPWRITABLEDIRPATH },
+ { "classfileinunsafedirpath", DBS_CLASSFILEINUNSAFEDIRPATH },
+ { "errorheaderinunsafedirpath", DBS_ERRORHEADERINUNSAFEDIRPATH },
+ { "helpfileinunsafedirpath", DBS_HELPFILEINUNSAFEDIRPATH },
+ { "forwardfileinunsafedirpathsafe",
+ DBS_FORWARDFILEINUNSAFEDIRPATHSAFE },
+ { "includefileinunsafedirpathsafe",
+ DBS_INCLUDEFILEINUNSAFEDIRPATHSAFE },
+ { "runprograminunsafedirpath", DBS_RUNPROGRAMINUNSAFEDIRPATH },
+ { "runwritableprogram", DBS_RUNWRITABLEPROGRAM },
+ { "nonrootsafeaddr", DBS_NONROOTSAFEADDR },
+ { "truststickybit", DBS_TRUSTSTICKYBIT },
+ { "dontwarnforwardfileinunsafedirpath",
+ DBS_DONTWARNFORWARDFILEINUNSAFEDIRPATH },
+ { "insufficiententropy", DBS_INSUFFICIENTENTROPY },
+ { "groupreadablesasldbfile", DBS_GROUPREADABLESASLDBFILE },
+ { "groupwritablesasldbfile", DBS_GROUPWRITABLESASLDBFILE },
+ { "groupwritableforwardfile", DBS_GROUPWRITABLEFORWARDFILE },
+ { "groupwritableincludefile", DBS_GROUPWRITABLEINCLUDEFILE },
+ { "worldwritableforwardfile", DBS_WORLDWRITABLEFORWARDFILE },
+ { "worldwritableincludefile", DBS_WORLDWRITABLEINCLUDEFILE },
+ { "groupreadablekeyfile", DBS_GROUPREADABLEKEYFILE },
+#if _FFR_GROUPREADABLEAUTHINFOFILE
+ { "groupreadableadefaultauthinfofile",
+ DBS_GROUPREADABLEAUTHINFOFILE },
+#endif /* _FFR_GROUPREADABLEAUTHINFOFILE */
+ { NULL, 0 }
+};
+
+/*
+** Miscellaneous stuff.
+*/
+
+int DtableSize = 50; /* max open files; reset in 4.2bsd */
+/*
+** SETDEFAULTS -- set default values
+**
+** Some of these must be initialized using direct code since they
+** depend on run-time values. So let's do all of them this way.
+**
+** Parameters:
+** e -- the default envelope.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Initializes a bunch of global variables to their
+** default values.
+*/
+
+#define MINUTES * 60
+#define HOURS * 60 MINUTES
+#define DAYS * 24 HOURS
+
+#ifndef MAXRULERECURSION
+# define MAXRULERECURSION 50 /* max ruleset recursion depth */
+#endif /* ! MAXRULERECURSION */
+
+void
+setdefaults(e)
+ register ENVELOPE *e;
+{
+ int i;
+ int numprocs;
+ struct passwd *pw;
+
+ numprocs = get_num_procs_online();
+ SpaceSub = ' '; /* option B */
+ QueueLA = 8 * numprocs; /* option x */
+ RefuseLA = 12 * numprocs; /* option X */
+ WkRecipFact = 30000L; /* option y */
+ WkClassFact = 1800L; /* option z */
+ WkTimeFact = 90000L; /* option Z */
+ QueueFactor = WkRecipFact * 20; /* option q */
+ QueueMode = QM_NORMAL; /* what queue items to act upon */
+ FileMode = (RealUid != geteuid()) ? 0644 : 0600;
+ /* option F */
+ QueueFileMode = (RealUid != geteuid()) ? 0644 : 0600;
+ /* option QueueFileMode */
+
+ if (((pw = sm_getpwnam("mailnull")) != NULL && pw->pw_uid != 0) ||
+ ((pw = sm_getpwnam("sendmail")) != NULL && pw->pw_uid != 0) ||
+ ((pw = sm_getpwnam("daemon")) != NULL && pw->pw_uid != 0))
+ {
+ DefUid = pw->pw_uid; /* option u */
+ DefGid = pw->pw_gid; /* option g */
+ DefUser = newstr(pw->pw_name);
+ }
+ else
+ {
+ DefUid = 1; /* option u */
+ DefGid = 1; /* option g */
+ setdefuser();
+ }
+ TrustedUid = 0;
+ if (tTd(37, 4))
+ sm_dprintf("setdefaults: DefUser=%s, DefUid=%d, DefGid=%d\n",
+ DefUser != NULL ? DefUser : "<1:1>",
+ (int) DefUid, (int) DefGid);
+ CheckpointInterval = 10; /* option C */
+ MaxHopCount = 25; /* option h */
+ set_delivery_mode(SM_FORK, e); /* option d */
+ e->e_errormode = EM_PRINT; /* option e */
+ e->e_qgrp = NOQGRP;
+ e->e_qdir = NOQDIR;
+ e->e_xfqgrp = NOQGRP;
+ e->e_xfqdir = NOQDIR;
+ e->e_ctime = curtime();
+ SevenBitInput = false; /* option 7 */
+ MaxMciCache = 1; /* option k */
+ MciCacheTimeout = 5 MINUTES; /* option K */
+ LogLevel = 9; /* option L */
+#if MILTER
+ MilterLogLevel = -1;
+#endif /* MILTER */
+ inittimeouts(NULL, false); /* option r */
+ PrivacyFlags = PRIV_PUBLIC; /* option p */
+ MeToo = true; /* option m */
+ SendMIMEErrors = true; /* option f */
+ SuperSafe = SAFE_REALLY; /* option s */
+ clrbitmap(DontBlameSendmail); /* DontBlameSendmail option */
+#if MIME8TO7
+ MimeMode = MM_CVTMIME|MM_PASS8BIT; /* option 8 */
+#else /* MIME8TO7 */
+ MimeMode = MM_PASS8BIT;
+#endif /* MIME8TO7 */
+ for (i = 0; i < MAXTOCLASS; i++)
+ {
+ TimeOuts.to_q_return[i] = 5 DAYS; /* option T */
+ TimeOuts.to_q_warning[i] = 0; /* option T */
+ }
+ ServiceSwitchFile = "/etc/mail/service.switch";
+ ServiceCacheMaxAge = (time_t) 10;
+ HostsFile = _PATH_HOSTS;
+ PidFile = newstr(_PATH_SENDMAILPID);
+ MustQuoteChars = "@,;:\\()[].'";
+ MciInfoTimeout = 30 MINUTES;
+ MaxRuleRecursion = MAXRULERECURSION;
+ MaxAliasRecursion = 10;
+ MaxMacroRecursion = 10;
+ ColonOkInAddr = true;
+ DontLockReadFiles = true;
+ DontProbeInterfaces = DPI_PROBEALL;
+ DoubleBounceAddr = "postmaster";
+ MaxHeadersLength = MAXHDRSLEN;
+ MaxMimeHeaderLength = MAXLINE;
+ MaxMimeFieldLength = MaxMimeHeaderLength / 2;
+ MaxForwardEntries = 0;
+ FastSplit = 1;
+#if SASL
+ AuthMechanisms = newstr(AUTH_MECHANISMS);
+ AuthRealm = NULL;
+ MaxSLBits = INT_MAX;
+#endif /* SASL */
+#if STARTTLS
+ TLS_Srv_Opts = TLS_I_SRV;
+#endif /* STARTTLS */
+#ifdef HESIOD_INIT
+ HesiodContext = NULL;
+#endif /* HESIOD_INIT */
+#if NETINET6
+ /* Detect if IPv6 is available at run time */
+ i = socket(AF_INET6, SOCK_STREAM, 0);
+ if (i >= 0)
+ {
+ InetMode = AF_INET6;
+ (void) close(i);
+ }
+ else
+ InetMode = AF_INET;
+#else /* NETINET6 */
+ InetMode = AF_INET;
+#endif /* NETINET6 */
+ ControlSocketName = NULL;
+ memset(&ConnectOnlyTo, '\0', sizeof ConnectOnlyTo);
+ DataFileBufferSize = 4096;
+ XscriptFileBufferSize = 4096;
+ for (i = 0; i < MAXRWSETS; i++)
+ RuleSetNames[i] = NULL;
+#if MILTER
+ InputFilters[0] = NULL;
+#endif /* MILTER */
+ RejectLogInterval = 3 HOURS;
+#if REQUIRES_DIR_FSYNC
+ RequiresDirfsync = true;
+#endif /* REQUIRES_DIR_FSYNC */
+ ConnectionRateWindowSize = 60;
+ setupmaps();
+ setupqueues();
+ setupmailers();
+ setupheaders();
+}
+
+
+/*
+** SETDEFUSER -- set/reset DefUser using DefUid (for initgroups())
+*/
+
+void
+setdefuser()
+{
+ struct passwd *defpwent;
+ static char defuserbuf[40];
+
+ DefUser = defuserbuf;
+ defpwent = sm_getpwuid(DefUid);
+ (void) sm_strlcpy(defuserbuf,
+ (defpwent == NULL || defpwent->pw_name == NULL)
+ ? "nobody" : defpwent->pw_name,
+ sizeof defuserbuf);
+ if (tTd(37, 4))
+ sm_dprintf("setdefuser: DefUid=%d, DefUser=%s\n",
+ (int) DefUid, DefUser);
+}
+/*
+** SETUPQUEUES -- initialize default queues
+**
+** The mqueue QUEUE structure gets filled in after readcf() but
+** we need something to point to now for the mailer setup,
+** which use "mqueue" as default queue.
+*/
+
+static void
+setupqueues()
+{
+ char buf[100];
+
+ MaxRunnersPerQueue = 1;
+ (void) sm_strlcpy(buf, "mqueue, P=/var/spool/mqueue", sizeof buf);
+ makequeue(buf, false);
+}
+/*
+** SETUPMAILERS -- initialize default mailers
+*/
+
+static void
+setupmailers()
+{
+ char buf[100];
+
+ (void) sm_strlcpy(buf, "prog, P=/bin/sh, F=lsouDq9, T=X-Unix/X-Unix/X-Unix, A=sh -c \201u",
+ sizeof buf);
+ makemailer(buf);
+
+ (void) sm_strlcpy(buf, "*file*, P=[FILE], F=lsDFMPEouq9, T=X-Unix/X-Unix/X-Unix, A=FILE \201u",
+ sizeof buf);
+ makemailer(buf);
+
+ (void) sm_strlcpy(buf, "*include*, P=/dev/null, F=su, A=INCLUDE \201u",
+ sizeof buf);
+ makemailer(buf);
+ initerrmailers();
+}
+/*
+** SETUPMAPS -- set up map classes
+*/
+
+#define MAPDEF(name, ext, flags, parse, open, close, lookup, store) \
+ { \
+ extern bool parse __P((MAP *, char *)); \
+ extern bool open __P((MAP *, int)); \
+ extern void close __P((MAP *)); \
+ extern char *lookup __P((MAP *, char *, char **, int *)); \
+ extern void store __P((MAP *, char *, char *)); \
+ s = stab(name, ST_MAPCLASS, ST_ENTER); \
+ s->s_mapclass.map_cname = name; \
+ s->s_mapclass.map_ext = ext; \
+ s->s_mapclass.map_cflags = flags; \
+ s->s_mapclass.map_parse = parse; \
+ s->s_mapclass.map_open = open; \
+ s->s_mapclass.map_close = close; \
+ s->s_mapclass.map_lookup = lookup; \
+ s->s_mapclass.map_store = store; \
+ }
+
+static void
+setupmaps()
+{
+ register STAB *s;
+
+#if NEWDB
+# if DB_VERSION_MAJOR > 1
+ int major_v, minor_v, patch_v;
+
+ (void) db_version(&major_v, &minor_v, &patch_v);
+ if (major_v != DB_VERSION_MAJOR || minor_v != DB_VERSION_MINOR)
+ {
+ errno = 0;
+ syserr("Berkeley DB version mismatch: compiled against %d.%d.%d, run-time linked against %d.%d.%d",
+ DB_VERSION_MAJOR, DB_VERSION_MINOR, DB_VERSION_PATCH,
+ major_v, minor_v, patch_v);
+ }
+# endif /* DB_VERSION_MAJOR > 1 */
+
+ MAPDEF("hash", ".db", MCF_ALIASOK|MCF_REBUILDABLE,
+ map_parseargs, hash_map_open, db_map_close,
+ db_map_lookup, db_map_store);
+
+ MAPDEF("btree", ".db", MCF_ALIASOK|MCF_REBUILDABLE,
+ map_parseargs, bt_map_open, db_map_close,
+ db_map_lookup, db_map_store);
+#endif /* NEWDB */
+
+#if NDBM
+ MAPDEF("dbm", ".dir", MCF_ALIASOK|MCF_REBUILDABLE,
+ map_parseargs, ndbm_map_open, ndbm_map_close,
+ ndbm_map_lookup, ndbm_map_store);
+#endif /* NDBM */
+
+#if NIS
+ MAPDEF("nis", NULL, MCF_ALIASOK,
+ map_parseargs, nis_map_open, null_map_close,
+ nis_map_lookup, null_map_store);
+#endif /* NIS */
+
+#if NISPLUS
+ MAPDEF("nisplus", NULL, MCF_ALIASOK,
+ map_parseargs, nisplus_map_open, null_map_close,
+ nisplus_map_lookup, null_map_store);
+#endif /* NISPLUS */
+
+#if LDAPMAP
+ MAPDEF("ldap", NULL, MCF_ALIASOK|MCF_NOTPERSIST,
+ ldapmap_parseargs, ldapmap_open, ldapmap_close,
+ ldapmap_lookup, null_map_store);
+#endif /* LDAPMAP */
+
+#if PH_MAP
+ MAPDEF("ph", NULL, MCF_NOTPERSIST,
+ ph_map_parseargs, ph_map_open, ph_map_close,
+ ph_map_lookup, null_map_store);
+#endif /* PH_MAP */
+
+#if MAP_NSD
+ /* IRIX 6.5 nsd support */
+ MAPDEF("nsd", NULL, MCF_ALIASOK,
+ map_parseargs, null_map_open, null_map_close,
+ nsd_map_lookup, null_map_store);
+#endif /* MAP_NSD */
+
+#if HESIOD
+ MAPDEF("hesiod", NULL, MCF_ALIASOK|MCF_ALIASONLY,
+ map_parseargs, hes_map_open, hes_map_close,
+ hes_map_lookup, null_map_store);
+#endif /* HESIOD */
+
+#if NETINFO
+ MAPDEF("netinfo", NULL, MCF_ALIASOK,
+ map_parseargs, ni_map_open, null_map_close,
+ ni_map_lookup, null_map_store);
+#endif /* NETINFO */
+
+#if 0
+ MAPDEF("dns", NULL, 0,
+ dns_map_init, null_map_open, null_map_close,
+ dns_map_lookup, null_map_store);
+#endif /* 0 */
+
+#if NAMED_BIND
+# if DNSMAP
+# if _FFR_DNSMAP_ALIASABLE
+ MAPDEF("dns", NULL, MCF_ALIASOK,
+ dns_map_parseargs, dns_map_open, null_map_close,
+ dns_map_lookup, null_map_store);
+# else /* _FFR_DNSMAP_ALIASABLE */
+ MAPDEF("dns", NULL, 0,
+ dns_map_parseargs, dns_map_open, null_map_close,
+ dns_map_lookup, null_map_store);
+# endif /* _FFR_DNSMAP_ALIASABLE */
+# endif /* DNSMAP */
+#endif /* NAMED_BIND */
+
+#if NAMED_BIND
+ /* best MX DNS lookup */
+ MAPDEF("bestmx", NULL, MCF_OPTFILE,
+ map_parseargs, null_map_open, null_map_close,
+ bestmx_map_lookup, null_map_store);
+#endif /* NAMED_BIND */
+
+ MAPDEF("host", NULL, 0,
+ host_map_init, null_map_open, null_map_close,
+ host_map_lookup, null_map_store);
+
+ MAPDEF("text", NULL, MCF_ALIASOK,
+ map_parseargs, text_map_open, null_map_close,
+ text_map_lookup, null_map_store);
+
+ MAPDEF("stab", NULL, MCF_ALIASOK|MCF_ALIASONLY,
+ map_parseargs, stab_map_open, null_map_close,
+ stab_map_lookup, stab_map_store);
+
+ MAPDEF("implicit", NULL, MCF_ALIASOK|MCF_ALIASONLY|MCF_REBUILDABLE,
+ map_parseargs, impl_map_open, impl_map_close,
+ impl_map_lookup, impl_map_store);
+
+ /* access to system passwd file */
+ MAPDEF("user", NULL, MCF_OPTFILE,
+ map_parseargs, user_map_open, null_map_close,
+ user_map_lookup, null_map_store);
+
+ /* dequote map */
+ MAPDEF("dequote", NULL, 0,
+ dequote_init, null_map_open, null_map_close,
+ dequote_map, null_map_store);
+
+#if MAP_REGEX
+ MAPDEF("regex", NULL, 0,
+ regex_map_init, null_map_open, null_map_close,
+ regex_map_lookup, null_map_store);
+#endif /* MAP_REGEX */
+
+#if USERDB
+ /* user database */
+ MAPDEF("userdb", ".db", 0,
+ map_parseargs, null_map_open, null_map_close,
+ udb_map_lookup, null_map_store);
+#endif /* USERDB */
+
+ /* arbitrary programs */
+ MAPDEF("program", NULL, MCF_ALIASOK,
+ map_parseargs, null_map_open, null_map_close,
+ prog_map_lookup, null_map_store);
+
+ /* sequenced maps */
+ MAPDEF("sequence", NULL, MCF_ALIASOK,
+ seq_map_parse, null_map_open, null_map_close,
+ seq_map_lookup, seq_map_store);
+
+ /* switched interface to sequenced maps */
+ MAPDEF("switch", NULL, MCF_ALIASOK,
+ map_parseargs, switch_map_open, null_map_close,
+ seq_map_lookup, seq_map_store);
+
+ /* null map lookup -- really for internal use only */
+ MAPDEF("null", NULL, MCF_ALIASOK|MCF_OPTFILE,
+ map_parseargs, null_map_open, null_map_close,
+ null_map_lookup, null_map_store);
+
+ /* syslog map -- logs information to syslog */
+ MAPDEF("syslog", NULL, 0,
+ syslog_map_parseargs, null_map_open, null_map_close,
+ syslog_map_lookup, null_map_store);
+
+ /* macro storage map -- rulesets can set macros */
+ MAPDEF("macro", NULL, 0,
+ dequote_init, null_map_open, null_map_close,
+ macro_map_lookup, null_map_store);
+
+ /* arithmetic map -- add/subtract/compare */
+ MAPDEF("arith", NULL, 0,
+ dequote_init, null_map_open, null_map_close,
+ arith_map_lookup, null_map_store);
+
+#if SOCKETMAP
+ /* arbitrary daemons */
+ MAPDEF("socket", NULL, MCF_ALIASOK,
+ map_parseargs, socket_map_open, socket_map_close,
+ socket_map_lookup, null_map_store);
+#endif /* SOCKETMAP */
+
+ if (tTd(38, 2))
+ {
+ /* bogus map -- always return tempfail */
+ MAPDEF("bogus", NULL, MCF_ALIASOK|MCF_OPTFILE,
+ map_parseargs, null_map_open, null_map_close,
+ bogus_map_lookup, null_map_store);
+ }
+}
+
+#undef MAPDEF
+/*
+** INITHOSTMAPS -- initial host-dependent maps
+**
+** This should act as an interface to any local service switch
+** provided by the host operating system.
+**
+** Parameters:
+** none
+**
+** Returns:
+** none
+**
+** Side Effects:
+** Should define maps "host" and "users" as necessary
+** for this OS. If they are not defined, they will get
+** a default value later. It should check to make sure
+** they are not defined first, since it's possible that
+** the config file has provided an override.
+*/
+
+void
+inithostmaps()
+{
+ register int i;
+ int nmaps;
+ char *maptype[MAXMAPSTACK];
+ short mapreturn[MAXMAPACTIONS];
+ char buf[MAXLINE];
+
+ /*
+ ** Set up default hosts maps.
+ */
+
+#if 0
+ nmaps = switch_map_find("hosts", maptype, mapreturn);
+ for (i = 0; i < nmaps; i++)
+ {
+ if (strcmp(maptype[i], "files") == 0 &&
+ stab("hosts.files", ST_MAP, ST_FIND) == NULL)
+ {
+ (void) sm_strlcpy(buf, "hosts.files text -k 0 -v 1 /etc/hosts",
+ sizeof buf);
+ (void) makemapentry(buf);
+ }
+# if NAMED_BIND
+ else if (strcmp(maptype[i], "dns") == 0 &&
+ stab("hosts.dns", ST_MAP, ST_FIND) == NULL)
+ {
+ (void) sm_strlcpy(buf, "hosts.dns dns A", sizeof buf);
+ (void) makemapentry(buf);
+ }
+# endif /* NAMED_BIND */
+# if NISPLUS
+ else if (strcmp(maptype[i], "nisplus") == 0 &&
+ stab("hosts.nisplus", ST_MAP, ST_FIND) == NULL)
+ {
+ (void) sm_strlcpy(buf, "hosts.nisplus nisplus -k name -v address hosts.org_dir",
+ sizeof buf);
+ (void) makemapentry(buf);
+ }
+# endif /* NISPLUS */
+# if NIS
+ else if (strcmp(maptype[i], "nis") == 0 &&
+ stab("hosts.nis", ST_MAP, ST_FIND) == NULL)
+ {
+ (void) sm_strlcpy(buf, "hosts.nis nis -k 0 -v 1 hosts.byname",
+ sizeof buf);
+ (void) makemapentry(buf);
+ }
+# endif /* NIS */
+# if NETINFO
+ else if (strcmp(maptype[i], "netinfo") == 0 &&
+ stab("hosts.netinfo", ST_MAP, ST_FIND) == NULL)
+ {
+ (void) sm_strlcpy(buf, "hosts.netinfo netinfo -v name /machines",
+ sizeof buf);
+ (void) makemapentry(buf);
+ }
+# endif /* NETINFO */
+ }
+#endif /* 0 */
+
+ /*
+ ** Make sure we have a host map.
+ */
+
+ if (stab("host", ST_MAP, ST_FIND) == NULL)
+ {
+ /* user didn't initialize: set up host map */
+ (void) sm_strlcpy(buf, "host host", sizeof buf);
+#if NAMED_BIND
+ if (ConfigLevel >= 2)
+ (void) sm_strlcat(buf, " -a. -D", sizeof buf);
+#endif /* NAMED_BIND */
+ (void) makemapentry(buf);
+ }
+
+ /*
+ ** Set up default aliases maps
+ */
+
+ nmaps = switch_map_find("aliases", maptype, mapreturn);
+ for (i = 0; i < nmaps; i++)
+ {
+ if (strcmp(maptype[i], "files") == 0 &&
+ stab("aliases.files", ST_MAP, ST_FIND) == NULL)
+ {
+ (void) sm_strlcpy(buf, "aliases.files null",
+ sizeof buf);
+ (void) makemapentry(buf);
+ }
+#if NISPLUS
+ else if (strcmp(maptype[i], "nisplus") == 0 &&
+ stab("aliases.nisplus", ST_MAP, ST_FIND) == NULL)
+ {
+ (void) sm_strlcpy(buf, "aliases.nisplus nisplus -kalias -vexpansion mail_aliases.org_dir",
+ sizeof buf);
+ (void) makemapentry(buf);
+ }
+#endif /* NISPLUS */
+#if NIS
+ else if (strcmp(maptype[i], "nis") == 0 &&
+ stab("aliases.nis", ST_MAP, ST_FIND) == NULL)
+ {
+ (void) sm_strlcpy(buf, "aliases.nis nis mail.aliases",
+ sizeof buf);
+ (void) makemapentry(buf);
+ }
+#endif /* NIS */
+#if NETINFO
+ else if (strcmp(maptype[i], "netinfo") == 0 &&
+ stab("aliases.netinfo", ST_MAP, ST_FIND) == NULL)
+ {
+ (void) sm_strlcpy(buf, "aliases.netinfo netinfo -z, /aliases",
+ sizeof buf);
+ (void) makemapentry(buf);
+ }
+#endif /* NETINFO */
+#if HESIOD
+ else if (strcmp(maptype[i], "hesiod") == 0 &&
+ stab("aliases.hesiod", ST_MAP, ST_FIND) == NULL)
+ {
+ (void) sm_strlcpy(buf, "aliases.hesiod hesiod aliases",
+ sizeof buf);
+ (void) makemapentry(buf);
+ }
+#endif /* HESIOD */
+#if defined(LDAPMAP) && defined(SUN_EXTENSIONS) && \
+ defined(SUN_SIMPLIFIED_LDAP) && defined(HASLDAPGETALIASBYNAME)
+ else if (strcmp(maptype[i], "ldap") == 0 &&
+ stab("aliases.ldap", ST_MAP, ST_FIND) == NULL)
+ {
+ (void) strlcpy(buf, "aliases.ldap ldap -b . -h localhost -k mail=%0 -v mailgroup",
+ sizeof buf);
+ (void) makemapentry(buf);
+ }
+#endif
+ }
+ if (stab("aliases", ST_MAP, ST_FIND) == NULL)
+ {
+ (void) sm_strlcpy(buf, "aliases switch aliases", sizeof buf);
+ (void) makemapentry(buf);
+ }
+
+#if 0 /* "user" map class is a better choice */
+ /*
+ ** Set up default users maps.
+ */
+
+ nmaps = switch_map_find("passwd", maptype, mapreturn);
+ for (i = 0; i < nmaps; i++)
+ {
+ if (strcmp(maptype[i], "files") == 0 &&
+ stab("users.files", ST_MAP, ST_FIND) == NULL)
+ {
+ (void) sm_strlcpy(buf, "users.files text -m -z: -k0 -v6 /etc/passwd",
+ sizeof buf);
+ (void) makemapentry(buf);
+ }
+# if NISPLUS
+ else if (strcmp(maptype[i], "nisplus") == 0 &&
+ stab("users.nisplus", ST_MAP, ST_FIND) == NULL)
+ {
+ (void) sm_strlcpy(buf, "users.nisplus nisplus -m -kname -vhome passwd.org_dir",
+ sizeof buf);
+ (void) makemapentry(buf);
+ }
+# endif /* NISPLUS */
+# if NIS
+ else if (strcmp(maptype[i], "nis") == 0 &&
+ stab("users.nis", ST_MAP, ST_FIND) == NULL)
+ {
+ (void) sm_strlcpy(buf, "users.nis nis -m passwd.byname",
+ sizeof buf);
+ (void) makemapentry(buf);
+ }
+# endif /* NIS */
+# if HESIOD
+ else if (strcmp(maptype[i], "hesiod") == 0 &&
+ stab("users.hesiod", ST_MAP, ST_FIND) == NULL)
+ {
+ (void) sm_strlcpy(buf, "users.hesiod hesiod", sizeof buf);
+ (void) makemapentry(buf);
+ }
+# endif /* HESIOD */
+ }
+ if (stab("users", ST_MAP, ST_FIND) == NULL)
+ {
+ (void) sm_strlcpy(buf, "users switch -m passwd", sizeof buf);
+ (void) makemapentry(buf);
+ }
+#endif /* 0 */
+}
+/*
+** SWITCH_MAP_FIND -- find the list of types associated with a map
+**
+** This is the system-dependent interface to the service switch.
+**
+** Parameters:
+** service -- the name of the service of interest.
+** maptype -- an out-array of strings containing the types
+** of access to use for this service. There can
+** be at most MAXMAPSTACK types for a single service.
+** mapreturn -- an out-array of return information bitmaps
+** for the map.
+**
+** Returns:
+** The number of map types filled in, or -1 for failure.
+**
+** Side effects:
+** Preserves errno so nothing in the routine clobbers it.
+*/
+
+#if defined(SOLARIS) || (defined(sony_news) && defined(__svr4))
+# define _USE_SUN_NSSWITCH_
+#endif /* defined(SOLARIS) || (defined(sony_news) && defined(__svr4)) */
+
+#if _FFR_HPUX_NSSWITCH
+# ifdef __hpux
+# define _USE_SUN_NSSWITCH_
+# endif /* __hpux */
+#endif /* _FFR_HPUX_NSSWITCH */
+
+#ifdef _USE_SUN_NSSWITCH_
+# include <nsswitch.h>
+#endif /* _USE_SUN_NSSWITCH_ */
+
+#if defined(ultrix) || (defined(__osf__) && defined(__alpha))
+# define _USE_DEC_SVC_CONF_
+#endif /* defined(ultrix) || (defined(__osf__) && defined(__alpha)) */
+
+#ifdef _USE_DEC_SVC_CONF_
+# include <sys/svcinfo.h>
+#endif /* _USE_DEC_SVC_CONF_ */
+
+int
+switch_map_find(service, maptype, mapreturn)
+ char *service;
+ char *maptype[MAXMAPSTACK];
+ short mapreturn[MAXMAPACTIONS];
+{
+ int svcno = 0;
+ int save_errno = errno;
+
+#ifdef _USE_SUN_NSSWITCH_
+ struct __nsw_switchconfig *nsw_conf;
+ enum __nsw_parse_err pserr;
+ struct __nsw_lookup *lk;
+ static struct __nsw_lookup lkp0 =
+ { "files", {1, 0, 0, 0}, NULL, NULL };
+ static struct __nsw_switchconfig lkp_default =
+ { 0, "sendmail", 3, &lkp0 };
+
+ for (svcno = 0; svcno < MAXMAPACTIONS; svcno++)
+ mapreturn[svcno] = 0;
+
+ if ((nsw_conf = __nsw_getconfig(service, &pserr)) == NULL)
+ lk = lkp_default.lookups;
+ else
+ lk = nsw_conf->lookups;
+ svcno = 0;
+ while (lk != NULL && svcno < MAXMAPSTACK)
+ {
+ maptype[svcno] = lk->service_name;
+ if (lk->actions[__NSW_NOTFOUND] == __NSW_RETURN)
+ mapreturn[MA_NOTFOUND] |= 1 << svcno;
+ if (lk->actions[__NSW_TRYAGAIN] == __NSW_RETURN)
+ mapreturn[MA_TRYAGAIN] |= 1 << svcno;
+ if (lk->actions[__NSW_UNAVAIL] == __NSW_RETURN)
+ mapreturn[MA_TRYAGAIN] |= 1 << svcno;
+ svcno++;
+ lk = lk->next;
+ }
+ errno = save_errno;
+ return svcno;
+#endif /* _USE_SUN_NSSWITCH_ */
+
+#ifdef _USE_DEC_SVC_CONF_
+ struct svcinfo *svcinfo;
+ int svc;
+
+ for (svcno = 0; svcno < MAXMAPACTIONS; svcno++)
+ mapreturn[svcno] = 0;
+
+ svcinfo = getsvc();
+ if (svcinfo == NULL)
+ goto punt;
+ if (strcmp(service, "hosts") == 0)
+ svc = SVC_HOSTS;
+ else if (strcmp(service, "aliases") == 0)
+ svc = SVC_ALIASES;
+ else if (strcmp(service, "passwd") == 0)
+ svc = SVC_PASSWD;
+ else
+ {
+ errno = save_errno;
+ return -1;
+ }
+ for (svcno = 0; svcno < SVC_PATHSIZE && svcno < MAXMAPSTACK; svcno++)
+ {
+ switch (svcinfo->svcpath[svc][svcno])
+ {
+ case SVC_LOCAL:
+ maptype[svcno] = "files";
+ break;
+
+ case SVC_YP:
+ maptype[svcno] = "nis";
+ break;
+
+ case SVC_BIND:
+ maptype[svcno] = "dns";
+ break;
+
+# ifdef SVC_HESIOD
+ case SVC_HESIOD:
+ maptype[svcno] = "hesiod";
+ break;
+# endif /* SVC_HESIOD */
+
+ case SVC_LAST:
+ errno = save_errno;
+ return svcno;
+ }
+ }
+ errno = save_errno;
+ return svcno;
+#endif /* _USE_DEC_SVC_CONF_ */
+
+#if !defined(_USE_SUN_NSSWITCH_) && !defined(_USE_DEC_SVC_CONF_)
+ /*
+ ** Fall-back mechanism.
+ */
+
+ STAB *st;
+ static time_t servicecachetime; /* time service switch was cached */
+ time_t now = curtime();
+
+ for (svcno = 0; svcno < MAXMAPACTIONS; svcno++)
+ mapreturn[svcno] = 0;
+
+ if ((now - servicecachetime) > (time_t) ServiceCacheMaxAge)
+ {
+ /* (re)read service switch */
+ register SM_FILE_T *fp;
+ long sff = SFF_REGONLY|SFF_OPENASROOT|SFF_NOLOCK;
+
+ if (!bitnset(DBS_LINKEDSERVICESWITCHFILEINWRITABLEDIR,
+ DontBlameSendmail))
+ sff |= SFF_NOWLINK;
+
+ if (ConfigFileRead)
+ servicecachetime = now;
+ fp = safefopen(ServiceSwitchFile, O_RDONLY, 0, sff);
+ if (fp != NULL)
+ {
+ char buf[MAXLINE];
+
+ while (sm_io_fgets(fp, SM_TIME_DEFAULT, buf,
+ sizeof buf) != NULL)
+ {
+ register char *p;
+
+ p = strpbrk(buf, "#\n");
+ if (p != NULL)
+ *p = '\0';
+ p = strpbrk(buf, " \t");
+ if (p != NULL)
+ *p++ = '\0';
+ if (buf[0] == '\0')
+ continue;
+ if (p == NULL)
+ {
+ sm_syslog(LOG_ERR, NOQID,
+ "Bad line on %.100s: %.100s",
+ ServiceSwitchFile,
+ buf);
+ continue;
+ }
+ while (isspace(*p))
+ p++;
+ if (*p == '\0')
+ continue;
+
+ /*
+ ** Find/allocate space for this service entry.
+ ** Space for all of the service strings
+ ** are allocated at once. This means
+ ** that we only have to free the first
+ ** one to free all of them.
+ */
+
+ st = stab(buf, ST_SERVICE, ST_ENTER);
+ if (st->s_service[0] != NULL)
+ sm_free((void *) st->s_service[0]); /* XXX */
+ p = newstr(p);
+ for (svcno = 0; svcno < MAXMAPSTACK; )
+ {
+ if (*p == '\0')
+ break;
+ st->s_service[svcno++] = p;
+ p = strpbrk(p, " \t");
+ if (p == NULL)
+ break;
+ *p++ = '\0';
+ while (isspace(*p))
+ p++;
+ }
+ if (svcno < MAXMAPSTACK)
+ st->s_service[svcno] = NULL;
+ }
+ (void) sm_io_close(fp, SM_TIME_DEFAULT);
+ }
+ }
+
+ /* look up entry in cache */
+ st = stab(service, ST_SERVICE, ST_FIND);
+ if (st != NULL && st->s_service[0] != NULL)
+ {
+ /* extract data */
+ svcno = 0;
+ while (svcno < MAXMAPSTACK)
+ {
+ maptype[svcno] = st->s_service[svcno];
+ if (maptype[svcno++] == NULL)
+ break;
+ }
+ errno = save_errno;
+ return --svcno;
+ }
+#endif /* !defined(_USE_SUN_NSSWITCH_) && !defined(_USE_DEC_SVC_CONF_) */
+
+#if !defined(_USE_SUN_NSSWITCH_)
+ /* if the service file doesn't work, use an absolute fallback */
+# ifdef _USE_DEC_SVC_CONF_
+ punt:
+# endif /* _USE_DEC_SVC_CONF_ */
+ for (svcno = 0; svcno < MAXMAPACTIONS; svcno++)
+ mapreturn[svcno] = 0;
+ svcno = 0;
+ if (strcmp(service, "aliases") == 0)
+ {
+ maptype[svcno++] = "files";
+# if defined(AUTO_NETINFO_ALIASES) && defined (NETINFO)
+ maptype[svcno++] = "netinfo";
+# endif /* defined(AUTO_NETINFO_ALIASES) && defined (NETINFO) */
+# ifdef AUTO_NIS_ALIASES
+# if NISPLUS
+ maptype[svcno++] = "nisplus";
+# endif /* NISPLUS */
+# if NIS
+ maptype[svcno++] = "nis";
+# endif /* NIS */
+# endif /* AUTO_NIS_ALIASES */
+ errno = save_errno;
+ return svcno;
+ }
+ if (strcmp(service, "hosts") == 0)
+ {
+# if NAMED_BIND
+ maptype[svcno++] = "dns";
+# else /* NAMED_BIND */
+# if defined(sun) && !defined(BSD)
+ /* SunOS */
+ maptype[svcno++] = "nis";
+# endif /* defined(sun) && !defined(BSD) */
+# endif /* NAMED_BIND */
+# if defined(AUTO_NETINFO_HOSTS) && defined (NETINFO)
+ maptype[svcno++] = "netinfo";
+# endif /* defined(AUTO_NETINFO_HOSTS) && defined (NETINFO) */
+ maptype[svcno++] = "files";
+ errno = save_errno;
+ return svcno;
+ }
+ errno = save_errno;
+ return -1;
+#endif /* !defined(_USE_SUN_NSSWITCH_) */
+}
+/*
+** USERNAME -- return the user id of the logged in user.
+**
+** Parameters:
+** none.
+**
+** Returns:
+** The login name of the logged in user.
+**
+** Side Effects:
+** none.
+**
+** Notes:
+** The return value is statically allocated.
+*/
+
+char *
+username()
+{
+ static char *myname = NULL;
+ extern char *getlogin();
+ register struct passwd *pw;
+
+ /* cache the result */
+ if (myname == NULL)
+ {
+ myname = getlogin();
+ if (myname == NULL || myname[0] == '\0')
+ {
+ pw = sm_getpwuid(RealUid);
+ if (pw != NULL)
+ myname = pw->pw_name;
+ }
+ else
+ {
+ uid_t uid = RealUid;
+
+ if ((pw = sm_getpwnam(myname)) == NULL ||
+ (uid != 0 && uid != pw->pw_uid))
+ {
+ pw = sm_getpwuid(uid);
+ if (pw != NULL)
+ myname = pw->pw_name;
+ }
+ }
+ if (myname == NULL || myname[0] == '\0')
+ {
+ syserr("554 5.3.0 Who are you?");
+ myname = "postmaster";
+ }
+ else if (strpbrk(myname, ",;:/|\"\\") != NULL)
+ myname = addquotes(myname, NULL);
+ else
+ myname = sm_pstrdup_x(myname);
+ }
+ return myname;
+}
+/*
+** TTYPATH -- Get the path of the user's tty
+**
+** Returns the pathname of the user's tty. Returns NULL if
+** the user is not logged in or if s/he has write permission
+** denied.
+**
+** Parameters:
+** none
+**
+** Returns:
+** pathname of the user's tty.
+** NULL if not logged in or write permission denied.
+**
+** Side Effects:
+** none.
+**
+** WARNING:
+** Return value is in a local buffer.
+**
+** Called By:
+** savemail
+*/
+
+char *
+ttypath()
+{
+ struct stat stbuf;
+ register char *pathn;
+ extern char *ttyname();
+ extern char *getlogin();
+
+ /* compute the pathname of the controlling tty */
+ if ((pathn = ttyname(2)) == NULL && (pathn = ttyname(1)) == NULL &&
+ (pathn = ttyname(0)) == NULL)
+ {
+ errno = 0;
+ return NULL;
+ }
+
+ /* see if we have write permission */
+ if (stat(pathn, &stbuf) < 0 || !bitset(S_IWOTH, stbuf.st_mode))
+ {
+ errno = 0;
+ return NULL;
+ }
+
+ /* see if the user is logged in */
+ if (getlogin() == NULL)
+ return NULL;
+
+ /* looks good */
+ return pathn;
+}
+/*
+** CHECKCOMPAT -- check for From and To person compatible.
+**
+** This routine can be supplied on a per-installation basis
+** to determine whether a person is allowed to send a message.
+** This allows restriction of certain types of internet
+** forwarding or registration of users.
+**
+** If the hosts are found to be incompatible, an error
+** message should be given using "usrerr" and an EX_ code
+** should be returned. You can also set to->q_status to
+** a DSN-style status code.
+**
+** EF_NO_BODY_RETN can be set in e->e_flags to suppress the
+** body during the return-to-sender function; this should be done
+** on huge messages. This bit may already be set by the ESMTP
+** protocol.
+**
+** Parameters:
+** to -- the person being sent to.
+**
+** Returns:
+** an exit status
+**
+** Side Effects:
+** none (unless you include the usrerr stuff)
+*/
+
+int
+checkcompat(to, e)
+ register ADDRESS *to;
+ register ENVELOPE *e;
+{
+ if (tTd(49, 1))
+ sm_dprintf("checkcompat(to=%s, from=%s)\n",
+ to->q_paddr, e->e_from.q_paddr);
+
+#ifdef EXAMPLE_CODE
+ /* this code is intended as an example only */
+ register STAB *s;
+
+ s = stab("arpa", ST_MAILER, ST_FIND);
+ if (s != NULL && strcmp(e->e_from.q_mailer->m_name, "local") != 0 &&
+ to->q_mailer == s->s_mailer)
+ {
+ usrerr("553 No ARPA mail through this machine: see your system administration");
+ /* e->e_flags |= EF_NO_BODY_RETN; to suppress body on return */
+ to->q_status = "5.7.1";
+ return EX_UNAVAILABLE;
+ }
+#endif /* EXAMPLE_CODE */
+ return EX_OK;
+}
+/*
+** INIT_MD -- do machine dependent initializations
+**
+** Systems that have global modes that should be set should do
+** them here rather than in main.
+*/
+
+#ifdef _AUX_SOURCE
+# include <compat.h>
+#endif /* _AUX_SOURCE */
+
+#if SHARE_V1
+# include <shares.h>
+#endif /* SHARE_V1 */
+
+void
+init_md(argc, argv)
+ int argc;
+ char **argv;
+{
+#ifdef _AUX_SOURCE
+ setcompat(getcompat() | COMPAT_BSDPROT);
+#endif /* _AUX_SOURCE */
+
+#ifdef SUN_EXTENSIONS
+ init_md_sun();
+#endif /* SUN_EXTENSIONS */
+
+#if _CONVEX_SOURCE
+ /* keep gethostby*() from stripping the local domain name */
+ set_domain_trim_off();
+#endif /* _CONVEX_SOURCE */
+#ifdef __QNX__
+ /*
+ ** Due to QNX's network distributed nature, you can target a tcpip
+ ** stack on a different node in the qnx network; this patch lets
+ ** this feature work. The __sock_locate() must be done before the
+ ** environment is clear.
+ */
+ __sock_locate();
+#endif /* __QNX__ */
+#if SECUREWARE || defined(_SCO_unix_)
+ set_auth_parameters(argc, argv);
+
+# ifdef _SCO_unix_
+ /*
+ ** This is required for highest security levels (the kernel
+ ** won't let it call set*uid() or run setuid binaries without
+ ** it). It may be necessary on other SECUREWARE systems.
+ */
+
+ if (getluid() == -1)
+ setluid(0);
+# endif /* _SCO_unix_ */
+#endif /* SECUREWARE || defined(_SCO_unix_) */
+
+
+#ifdef VENDOR_DEFAULT
+ VendorCode = VENDOR_DEFAULT;
+#else /* VENDOR_DEFAULT */
+ VendorCode = VENDOR_BERKELEY;
+#endif /* VENDOR_DEFAULT */
+}
+/*
+** INIT_VENDOR_MACROS -- vendor-dependent macro initializations
+**
+** Called once, on startup.
+**
+** Parameters:
+** e -- the global envelope.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** vendor-dependent.
+*/
+
+void
+init_vendor_macros(e)
+ register ENVELOPE *e;
+{
+}
+/*
+** GETLA -- get the current load average
+**
+** This code stolen from la.c.
+**
+** Parameters:
+** none.
+**
+** Returns:
+** The current load average as an integer.
+**
+** Side Effects:
+** none.
+*/
+
+/* try to guess what style of load average we have */
+#define LA_ZERO 1 /* always return load average as zero */
+#define LA_INT 2 /* read kmem for avenrun; interpret as long */
+#define LA_FLOAT 3 /* read kmem for avenrun; interpret as float */
+#define LA_SUBR 4 /* call getloadavg */
+#define LA_MACH 5 /* MACH load averages (as on NeXT boxes) */
+#define LA_SHORT 6 /* read kmem for avenrun; interpret as short */
+#define LA_PROCSTR 7 /* read string ("1.17") from /proc/loadavg */
+#define LA_READKSYM 8 /* SVR4: use MIOC_READKSYM ioctl call */
+#define LA_DGUX 9 /* special DGUX implementation */
+#define LA_HPUX 10 /* special HPUX implementation */
+#define LA_IRIX6 11 /* special IRIX 6.2 implementation */
+#define LA_KSTAT 12 /* special Solaris kstat(3k) implementation */
+#define LA_DEVSHORT 13 /* read short from a device */
+#define LA_ALPHAOSF 14 /* Digital UNIX (OSF/1 on Alpha) table() call */
+#define LA_PSET 15 /* Solaris per-processor-set load average */
+#define LA_LONGLONG 17 /* read kmem for avenrun; interpret as long long */
+
+/* do guesses based on general OS type */
+#ifndef LA_TYPE
+# define LA_TYPE LA_ZERO
+#endif /* ! LA_TYPE */
+
+#ifndef FSHIFT
+# if defined(unixpc)
+# define FSHIFT 5
+# endif /* defined(unixpc) */
+
+# if defined(__alpha) || defined(IRIX)
+# define FSHIFT 10
+# endif /* defined(__alpha) || defined(IRIX) */
+
+#endif /* ! FSHIFT */
+
+#ifndef FSHIFT
+# define FSHIFT 8
+#endif /* ! FSHIFT */
+
+#ifndef FSCALE
+# define FSCALE (1 << FSHIFT)
+#endif /* ! FSCALE */
+
+#ifndef LA_AVENRUN
+# ifdef SYSTEM5
+# define LA_AVENRUN "avenrun"
+# else /* SYSTEM5 */
+# define LA_AVENRUN "_avenrun"
+# endif /* SYSTEM5 */
+#endif /* ! LA_AVENRUN */
+
+/* _PATH_KMEM should be defined in <paths.h> */
+#ifndef _PATH_KMEM
+# define _PATH_KMEM "/dev/kmem"
+#endif /* ! _PATH_KMEM */
+
+#if (LA_TYPE == LA_INT) || (LA_TYPE == LA_FLOAT) || (LA_TYPE == LA_SHORT) || (LA_TYPE == LA_LONGLONG)
+
+# include <nlist.h>
+
+/* _PATH_UNIX should be defined in <paths.h> */
+# ifndef _PATH_UNIX
+# if defined(SYSTEM5)
+# define _PATH_UNIX "/unix"
+# else /* defined(SYSTEM5) */
+# define _PATH_UNIX "/vmunix"
+# endif /* defined(SYSTEM5) */
+# endif /* ! _PATH_UNIX */
+
+# ifdef _AUX_SOURCE
+struct nlist Nl[2];
+# else /* _AUX_SOURCE */
+struct nlist Nl[] =
+{
+ { LA_AVENRUN },
+ { 0 },
+};
+# endif /* _AUX_SOURCE */
+# define X_AVENRUN 0
+
+int
+getla()
+{
+ int j;
+ static int kmem = -1;
+# if LA_TYPE == LA_INT
+ long avenrun[3];
+# else /* LA_TYPE == LA_INT */
+# if LA_TYPE == LA_SHORT
+ short avenrun[3];
+# else
+# if LA_TYPE == LA_LONGLONG
+ long long avenrun[3];
+# else /* LA_TYPE == LA_LONGLONG */
+ double avenrun[3];
+# endif /* LA_TYPE == LA_LONGLONG */
+# endif /* LA_TYPE == LA_SHORT */
+# endif /* LA_TYPE == LA_INT */
+ extern off_t lseek();
+
+ if (kmem < 0)
+ {
+# ifdef _AUX_SOURCE
+ (void) sm_strlcpy(Nl[X_AVENRUN].n_name, LA_AVENRUN,
+ sizeof Nl[X_AVENRUN].n_name);
+ Nl[1].n_name[0] = '\0';
+# endif /* _AUX_SOURCE */
+
+# if defined(_AIX3) || defined(_AIX4)
+ if (knlist(Nl, 1, sizeof Nl[0]) < 0)
+# else /* defined(_AIX3) || defined(_AIX4) */
+ if (nlist(_PATH_UNIX, Nl) < 0)
+# endif /* defined(_AIX3) || defined(_AIX4) */
+ {
+ if (tTd(3, 1))
+ sm_dprintf("getla: nlist(%s): %s\n", _PATH_UNIX,
+ sm_errstring(errno));
+ return -1;
+ }
+ if (Nl[X_AVENRUN].n_value == 0)
+ {
+ if (tTd(3, 1))
+ sm_dprintf("getla: nlist(%s, %s) ==> 0\n",
+ _PATH_UNIX, LA_AVENRUN);
+ return -1;
+ }
+# ifdef NAMELISTMASK
+ Nl[X_AVENRUN].n_value &= NAMELISTMASK;
+# endif /* NAMELISTMASK */
+
+ kmem = open(_PATH_KMEM, 0, 0);
+ if (kmem < 0)
+ {
+ if (tTd(3, 1))
+ sm_dprintf("getla: open(/dev/kmem): %s\n",
+ sm_errstring(errno));
+ return -1;
+ }
+ if ((j = fcntl(kmem, F_GETFD, 0)) < 0 ||
+ fcntl(kmem, F_SETFD, j | FD_CLOEXEC) < 0)
+ {
+ if (tTd(3, 1))
+ sm_dprintf("getla: fcntl(/dev/kmem, FD_CLOEXEC): %s\n",
+ sm_errstring(errno));
+ (void) close(kmem);
+ kmem = -1;
+ return -1;
+ }
+ }
+ if (tTd(3, 20))
+ sm_dprintf("getla: symbol address = %#lx\n",
+ (unsigned long) Nl[X_AVENRUN].n_value);
+ if (lseek(kmem, (off_t) Nl[X_AVENRUN].n_value, SEEK_SET) == -1 ||
+ read(kmem, (char *) avenrun, sizeof(avenrun)) < sizeof(avenrun))
+ {
+ /* thank you Ian */
+ if (tTd(3, 1))
+ sm_dprintf("getla: lseek or read: %s\n",
+ sm_errstring(errno));
+ return -1;
+ }
+# if (LA_TYPE == LA_INT) || (LA_TYPE == LA_SHORT) || (LA_TYPE == LA_LONGLONG)
+ if (tTd(3, 5))
+ {
+# if LA_TYPE == LA_SHORT
+ sm_dprintf("getla: avenrun = %d", avenrun[0]);
+ if (tTd(3, 15))
+ sm_dprintf(", %d, %d", avenrun[1], avenrun[2]);
+# else /* LA_TYPE == LA_SHORT */
+# if LA_TYPE == LA_LONGLONG
+ sm_dprintf("getla: avenrun = %lld", avenrun[0]);
+ if (tTd(3, 15))
+ sm_dprintf(", %lld, %lld", avenrun[1], avenrun[2]);
+# else /* LA_TYPE == LA_LONGLONG */
+ sm_dprintf("getla: avenrun = %ld", avenrun[0]);
+ if (tTd(3, 15))
+ sm_dprintf(", %ld, %ld", avenrun[1], avenrun[2]);
+# endif /* LA_TYPE == LA_LONGLONG */
+# endif /* LA_TYPE == LA_SHORT */
+ sm_dprintf("\n");
+ }
+ if (tTd(3, 1))
+ sm_dprintf("getla: %d\n",
+ (int) (avenrun[0] + FSCALE/2) >> FSHIFT);
+ return ((int) (avenrun[0] + FSCALE/2) >> FSHIFT);
+# else /* (LA_TYPE == LA_INT) || (LA_TYPE == LA_SHORT) || (LA_TYPE == LA_LONGLONG) */
+ if (tTd(3, 5))
+ {
+ sm_dprintf("getla: avenrun = %g", avenrun[0]);
+ if (tTd(3, 15))
+ sm_dprintf(", %g, %g", avenrun[1], avenrun[2]);
+ sm_dprintf("\n");
+ }
+ if (tTd(3, 1))
+ sm_dprintf("getla: %d\n", (int) (avenrun[0] +0.5));
+ return ((int) (avenrun[0] + 0.5));
+# endif /* (LA_TYPE == LA_INT) || (LA_TYPE == LA_SHORT) || (LA_TYPE == LA_LONGLONG) */
+}
+
+#endif /* (LA_TYPE == LA_INT) || (LA_TYPE == LA_FLOAT) || (LA_TYPE == LA_SHORT) || (LA_TYPE == LA_LONGLONG) */
+
+#if LA_TYPE == LA_READKSYM
+
+# include <sys/ksym.h>
+
+int
+getla()
+{
+ int j;
+ static int kmem = -1;
+ long avenrun[3];
+ struct mioc_rksym mirk;
+
+ if (kmem < 0)
+ {
+ kmem = open("/dev/kmem", 0, 0);
+ if (kmem < 0)
+ {
+ if (tTd(3, 1))
+ sm_dprintf("getla: open(/dev/kmem): %s\n",
+ sm_errstring(errno));
+ return -1;
+ }
+ if ((j = fcntl(kmem, F_GETFD, 0)) < 0 ||
+ fcntl(kmem, F_SETFD, j | FD_CLOEXEC) < 0)
+ {
+ if (tTd(3, 1))
+ sm_dprintf("getla: fcntl(/dev/kmem, FD_CLOEXEC): %s\n",
+ sm_errstring(errno));
+ (void) close(kmem);
+ kmem = -1;
+ return -1;
+ }
+ }
+ mirk.mirk_symname = LA_AVENRUN;
+ mirk.mirk_buf = avenrun;
+ mirk.mirk_buflen = sizeof(avenrun);
+ if (ioctl(kmem, MIOC_READKSYM, &mirk) < 0)
+ {
+ if (tTd(3, 1))
+ sm_dprintf("getla: ioctl(MIOC_READKSYM) failed: %s\n",
+ sm_errstring(errno));
+ return -1;
+ }
+ if (tTd(3, 5))
+ {
+ sm_dprintf("getla: avenrun = %d", avenrun[0]);
+ if (tTd(3, 15))
+ sm_dprintf(", %d, %d", avenrun[1], avenrun[2]);
+ sm_dprintf("\n");
+ }
+ if (tTd(3, 1))
+ sm_dprintf("getla: %d\n",
+ (int) (avenrun[0] + FSCALE/2) >> FSHIFT);
+ return ((int) (avenrun[0] + FSCALE/2) >> FSHIFT);
+}
+
+#endif /* LA_TYPE == LA_READKSYM */
+
+#if LA_TYPE == LA_DGUX
+
+# include <sys/dg_sys_info.h>
+
+int
+getla()
+{
+ struct dg_sys_info_load_info load_info;
+
+ dg_sys_info((long *)&load_info,
+ DG_SYS_INFO_LOAD_INFO_TYPE, DG_SYS_INFO_LOAD_VERSION_0);
+
+ if (tTd(3, 1))
+ sm_dprintf("getla: %d\n", (int) (load_info.one_minute + 0.5));
+
+ return ((int) (load_info.one_minute + 0.5));
+}
+
+#endif /* LA_TYPE == LA_DGUX */
+
+#if LA_TYPE == LA_HPUX
+
+/* forward declarations to keep gcc from complaining */
+struct pst_dynamic;
+struct pst_status;
+struct pst_static;
+struct pst_vminfo;
+struct pst_diskinfo;
+struct pst_processor;
+struct pst_lv;
+struct pst_swapinfo;
+
+# include <sys/param.h>
+# include <sys/pstat.h>
+
+int
+getla()
+{
+ struct pst_dynamic pstd;
+
+ if (pstat_getdynamic(&pstd, sizeof(struct pst_dynamic),
+ (size_t) 1, 0) == -1)
+ return 0;
+
+ if (tTd(3, 1))
+ sm_dprintf("getla: %d\n", (int) (pstd.psd_avg_1_min + 0.5));
+
+ return (int) (pstd.psd_avg_1_min + 0.5);
+}
+
+#endif /* LA_TYPE == LA_HPUX */
+
+#if LA_TYPE == LA_SUBR
+
+int
+getla()
+{
+ double avenrun[3];
+
+ if (getloadavg(avenrun, sizeof(avenrun) / sizeof(avenrun[0])) < 0)
+ {
+ if (tTd(3, 1))
+ sm_dprintf("getla: getloadavg failed: %s",
+ sm_errstring(errno));
+ return -1;
+ }
+ if (tTd(3, 1))
+ sm_dprintf("getla: %d\n", (int) (avenrun[0] +0.5));
+ return ((int) (avenrun[0] + 0.5));
+}
+
+#endif /* LA_TYPE == LA_SUBR */
+
+#if LA_TYPE == LA_MACH
+
+/*
+** This has been tested on NEXTSTEP release 2.1/3.X.
+*/
+
+# if defined(NX_CURRENT_COMPILER_RELEASE) && NX_CURRENT_COMPILER_RELEASE > NX_COMPILER_RELEASE_3_0
+# include <mach/mach.h>
+# else /* defined(NX_CURRENT_COMPILER_RELEASE) && NX_CURRENT_COMPILER_RELEASE > NX_COMPILER_RELEASE_3_0 */
+# include <mach.h>
+# endif /* defined(NX_CURRENT_COMPILER_RELEASE) && NX_CURRENT_COMPILER_RELEASE > NX_COMPILER_RELEASE_3_0 */
+
+int
+getla()
+{
+ processor_set_t default_set;
+ kern_return_t error;
+ unsigned int info_count;
+ struct processor_set_basic_info info;
+ host_t host;
+
+ error = processor_set_default(host_self(), &default_set);
+ if (error != KERN_SUCCESS)
+ {
+ if (tTd(3, 1))
+ sm_dprintf("getla: processor_set_default failed: %s",
+ sm_errstring(errno));
+ return -1;
+ }
+ info_count = PROCESSOR_SET_BASIC_INFO_COUNT;
+ if (processor_set_info(default_set, PROCESSOR_SET_BASIC_INFO,
+ &host, (processor_set_info_t)&info,
+ &info_count) != KERN_SUCCESS)
+ {
+ if (tTd(3, 1))
+ sm_dprintf("getla: processor_set_info failed: %s",
+ sm_errstring(errno));
+ return -1;
+ }
+ if (tTd(3, 1))
+ sm_dprintf("getla: %d\n",
+ (int) ((info.load_average + (LOAD_SCALE / 2)) /
+ LOAD_SCALE));
+ return (int) (info.load_average + (LOAD_SCALE / 2)) / LOAD_SCALE;
+}
+
+#endif /* LA_TYPE == LA_MACH */
+
+#if LA_TYPE == LA_PROCSTR
+# if SM_CONF_BROKEN_STRTOD
+ ERROR: This OS has most likely a broken strtod() implemenentation.
+ ERROR: The function is required for getla().
+ ERROR: Check the compilation options _LA_PROCSTR and
+ ERROR: _SM_CONF_BROKEN_STRTOD (without the leading _).
+# endif /* SM_CONF_BROKEN_STRTOD */
+
+/*
+** Read /proc/loadavg for the load average. This is assumed to be
+** in a format like "0.15 0.12 0.06".
+**
+** Initially intended for Linux. This has been in the kernel
+** since at least 0.99.15.
+*/
+
+# ifndef _PATH_LOADAVG
+# define _PATH_LOADAVG "/proc/loadavg"
+# endif /* ! _PATH_LOADAVG */
+
+int
+getla()
+{
+ double avenrun;
+ register int result;
+ SM_FILE_T *fp;
+
+ fp = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, _PATH_LOADAVG, SM_IO_RDONLY,
+ NULL);
+ if (fp == NULL)
+ {
+ if (tTd(3, 1))
+ sm_dprintf("getla: sm_io_open(%s): %s\n",
+ _PATH_LOADAVG, sm_errstring(errno));
+ return -1;
+ }
+ result = sm_io_fscanf(fp, SM_TIME_DEFAULT, "%lf", &avenrun);
+ (void) sm_io_close(fp, SM_TIME_DEFAULT);
+ if (result != 1)
+ {
+ if (tTd(3, 1))
+ sm_dprintf("getla: sm_io_fscanf() = %d: %s\n",
+ result, sm_errstring(errno));
+ return -1;
+ }
+
+ if (tTd(3, 1))
+ sm_dprintf("getla(): %.2f\n", avenrun);
+
+ return ((int) (avenrun + 0.5));
+}
+
+#endif /* LA_TYPE == LA_PROCSTR */
+
+#if LA_TYPE == LA_IRIX6
+
+# include <sys/sysmp.h>
+
+# ifdef _UNICOSMP
+# define CAST_SYSMP(x) (x)
+# else /* _UNICOSMP */
+# define CAST_SYSMP(x) ((x) & 0x7fffffff)
+# endif /* _UNICOSMP */
+
+int
+getla(void)
+{
+ int j;
+ static int kmem = -1;
+ int avenrun[3];
+
+ if (kmem < 0)
+ {
+ kmem = open(_PATH_KMEM, 0, 0);
+ if (kmem < 0)
+ {
+ if (tTd(3, 1))
+ sm_dprintf("getla: open(%s): %s\n", _PATH_KMEM,
+ sm_errstring(errno));
+ return -1;
+ }
+ if ((j = fcntl(kmem, F_GETFD, 0)) < 0 ||
+ fcntl(kmem, F_SETFD, j | FD_CLOEXEC) < 0)
+ {
+ if (tTd(3, 1))
+ sm_dprintf("getla: fcntl(/dev/kmem, FD_CLOEXEC): %s\n",
+ sm_errstring(errno));
+ (void) close(kmem);
+ kmem = -1;
+ return -1;
+ }
+ }
+
+ if (lseek(kmem, CAST_SYSMP(sysmp(MP_KERNADDR, MPKA_AVENRUN)), SEEK_SET)
+ == -1 ||
+ read(kmem, (char *) avenrun, sizeof(avenrun)) < sizeof(avenrun))
+ {
+ if (tTd(3, 1))
+ sm_dprintf("getla: lseek or read: %s\n",
+ sm_errstring(errno));
+ return -1;
+ }
+ if (tTd(3, 5))
+ {
+ sm_dprintf("getla: avenrun = %ld", (long int) avenrun[0]);
+ if (tTd(3, 15))
+ sm_dprintf(", %ld, %ld",
+ (long int) avenrun[1], (long int) avenrun[2]);
+ sm_dprintf("\n");
+ }
+
+ if (tTd(3, 1))
+ sm_dprintf("getla: %d\n",
+ (int) (avenrun[0] + FSCALE/2) >> FSHIFT);
+ return ((int) (avenrun[0] + FSCALE/2) >> FSHIFT);
+
+}
+#endif /* LA_TYPE == LA_IRIX6 */
+
+#if LA_TYPE == LA_KSTAT
+
+# include <kstat.h>
+
+int
+getla()
+{
+ static kstat_ctl_t *kc = NULL;
+ static kstat_t *ksp = NULL;
+ kstat_named_t *ksn;
+ int la;
+
+ if (kc == NULL) /* if not initialized before */
+ kc = kstat_open();
+ if (kc == NULL)
+ {
+ if (tTd(3, 1))
+ sm_dprintf("getla: kstat_open(): %s\n",
+ sm_errstring(errno));
+ return -1;
+ }
+ if (ksp == NULL)
+ ksp = kstat_lookup(kc, "unix", 0, "system_misc");
+ if (ksp == NULL)
+ {
+ if (tTd(3, 1))
+ sm_dprintf("getla: kstat_lookup(): %s\n",
+ sm_errstring(errno));
+ return -1;
+ }
+ if (kstat_read(kc, ksp, NULL) < 0)
+ {
+ if (tTd(3, 1))
+ sm_dprintf("getla: kstat_read(): %s\n",
+ sm_errstring(errno));
+ return -1;
+ }
+ ksn = (kstat_named_t *) kstat_data_lookup(ksp, "avenrun_1min");
+ la = ((double) ksn->value.ul + FSCALE/2) / FSCALE;
+ /* kstat_close(kc); /o do not close for fast access */
+ return la;
+}
+
+#endif /* LA_TYPE == LA_KSTAT */
+
+#if LA_TYPE == LA_DEVSHORT
+
+/*
+** Read /dev/table/avenrun for the load average. This should contain
+** three shorts for the 1, 5, and 15 minute loads. We only read the
+** first, since that's all we care about.
+**
+** Intended for SCO OpenServer 5.
+*/
+
+# ifndef _PATH_AVENRUN
+# define _PATH_AVENRUN "/dev/table/avenrun"
+# endif /* ! _PATH_AVENRUN */
+
+int
+getla()
+{
+ static int afd = -1;
+ short avenrun;
+ int loadav;
+ int r;
+
+ errno = EBADF;
+
+ if (afd == -1 || lseek(afd, 0L, SEEK_SET) == -1)
+ {
+ if (errno != EBADF)
+ return -1;
+ afd = open(_PATH_AVENRUN, O_RDONLY|O_SYNC);
+ if (afd < 0)
+ {
+ sm_syslog(LOG_ERR, NOQID,
+ "can't open %s: %s",
+ _PATH_AVENRUN, sm_errstring(errno));
+ return -1;
+ }
+ }
+
+ r = read(afd, &avenrun, sizeof avenrun);
+
+ if (tTd(3, 5))
+ sm_dprintf("getla: avenrun = %d\n", avenrun);
+ loadav = (int) (avenrun + FSCALE/2) >> FSHIFT;
+ if (tTd(3, 1))
+ sm_dprintf("getla: %d\n", loadav);
+ return loadav;
+}
+
+#endif /* LA_TYPE == LA_DEVSHORT */
+
+#if LA_TYPE == LA_ALPHAOSF
+struct rtentry;
+struct mbuf;
+# include <sys/table.h>
+
+int
+getla()
+{
+ int ave = 0;
+ struct tbl_loadavg tab;
+
+ if (table(TBL_LOADAVG, 0, &tab, 1, sizeof(tab)) == -1)
+ {
+ if (tTd(3, 1))
+ sm_dprintf("getla: table %s\n", sm_errstring(errno));
+ return -1;
+ }
+
+ if (tTd(3, 1))
+ sm_dprintf("getla: scale = %d\n", tab.tl_lscale);
+
+ if (tab.tl_lscale)
+ ave = ((tab.tl_avenrun.l[2] + (tab.tl_lscale/2)) /
+ tab.tl_lscale);
+ else
+ ave = (int) (tab.tl_avenrun.d[2] + 0.5);
+
+ if (tTd(3, 1))
+ sm_dprintf("getla: %d\n", ave);
+
+ return ave;
+}
+
+#endif /* LA_TYPE == LA_ALPHAOSF */
+
+#if LA_TYPE == LA_PSET
+
+int
+getla()
+{
+ double avenrun[3];
+
+ if (pset_getloadavg(PS_MYID, avenrun,
+ sizeof(avenrun) / sizeof(avenrun[0])) < 0)
+ {
+ if (tTd(3, 1))
+ sm_dprintf("getla: pset_getloadavg failed: %s",
+ sm_errstring(errno));
+ return -1;
+ }
+ if (tTd(3, 1))
+ sm_dprintf("getla: %d\n", (int) (avenrun[0] +0.5));
+ return ((int) (avenrun[0] + 0.5));
+}
+
+#endif /* LA_TYPE == LA_PSET */
+
+#if LA_TYPE == LA_ZERO
+
+int
+getla()
+{
+ if (tTd(3, 1))
+ sm_dprintf("getla: ZERO\n");
+ return 0;
+}
+
+#endif /* LA_TYPE == LA_ZERO */
+
+/*
+ * Copyright 1989 Massachusetts Institute of Technology
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation, and that the name of M.I.T. not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission. M.I.T. makes no representations about the
+ * suitability of this software for any purpose. It is provided "as is"
+ * without express or implied warranty.
+ *
+ * M.I.T. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL M.I.T.
+ * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Authors: Many and varied...
+ */
+
+/* Non Apollo stuff removed by Don Lewis 11/15/93 */
+#ifndef lint
+SM_UNUSED(static char rcsid[]) = "@(#)$OrigId: getloadavg.c,v 1.16 1991/06/21 12:51:15 paul Exp $";
+#endif /* ! lint */
+
+#ifdef apollo
+# undef volatile
+# include <apollo/base.h>
+
+/* ARGSUSED */
+int getloadavg( call_data )
+ caddr_t call_data; /* pointer to (double) return value */
+{
+ double *avenrun = (double *) call_data;
+ int i;
+ status_$t st;
+ long loadav[3];
+
+ proc1_$get_loadav(loadav, &st);
+ *avenrun = loadav[0] / (double) (1 << 16);
+ return 0;
+}
+#endif /* apollo */
+/*
+** SM_GETLA -- get the current load average
+**
+** Parameters:
+** none
+**
+** Returns:
+** none
+**
+** Side Effects:
+** Set CurrentLA to the current load average.
+** Set {load_avg} in GlobalMacros to the current load average.
+*/
+
+void
+sm_getla()
+{
+ char labuf[8];
+
+ CurrentLA = getla();
+ (void) sm_snprintf(labuf, sizeof labuf, "%d", CurrentLA);
+ macdefine(&GlobalMacros, A_TEMP, macid("{load_avg}"), labuf);
+}
+/*
+** SHOULDQUEUE -- should this message be queued or sent?
+**
+** Compares the message cost to the load average to decide.
+**
+** Note: Do NOT change this API! It is documented in op.me
+** and theoretically the user can change this function...
+**
+** Parameters:
+** pri -- the priority of the message in question.
+** ct -- the message creation time (unused, but see above).
+**
+** Returns:
+** true -- if this message should be queued up for the
+** time being.
+** false -- if the load is low enough to send this message.
+**
+** Side Effects:
+** none.
+*/
+
+/* ARGSUSED1 */
+bool
+shouldqueue(pri, ct)
+ long pri;
+ time_t ct;
+{
+ bool rval;
+
+ if (tTd(3, 30))
+ sm_dprintf("shouldqueue: CurrentLA=%d, pri=%ld: ",
+ CurrentLA, pri);
+ if (CurrentLA < QueueLA)
+ {
+ if (tTd(3, 30))
+ sm_dprintf("false (CurrentLA < QueueLA)\n");
+ return false;
+ }
+# if 0 /* this code is reported to cause oscillation around RefuseLA */
+ if (CurrentLA >= RefuseLA && QueueLA < RefuseLA)
+ {
+ if (tTd(3, 30))
+ sm_dprintf("TRUE (CurrentLA >= RefuseLA)\n");
+ return true;
+ }
+# endif /* 0 */
+ rval = pri > (QueueFactor / (CurrentLA - QueueLA + 1));
+ if (tTd(3, 30))
+ sm_dprintf("%s (by calculation)\n", rval ? "true" : "false");
+ return rval;
+}
+/*
+** REFUSECONNECTIONS -- decide if connections should be refused
+**
+** Parameters:
+** name -- daemon name (for error messages only)
+** e -- the current envelope.
+** d -- number of daemon
+** active -- was this daemon actually active?
+**
+** Returns:
+** true if incoming SMTP connections should be refused
+** (for now).
+** false if we should accept new work.
+**
+** Side Effects:
+** Sets process title when it is rejecting connections.
+*/
+
+bool
+refuseconnections(name, e, d, active)
+ char *name;
+ ENVELOPE *e;
+ int d;
+ bool active;
+{
+ static time_t lastconn[MAXDAEMONS];
+ static int conncnt[MAXDAEMONS];
+ static time_t firstrejtime[MAXDAEMONS];
+ static time_t nextlogtime[MAXDAEMONS];
+
+#if XLA
+ if (!xla_smtp_ok())
+ return true;
+#endif /* XLA */
+
+ SM_ASSERT(d >= 0);
+ SM_ASSERT(d < MAXDAEMONS);
+ if (ConnRateThrottle > 0)
+ {
+ time_t now;
+
+ now = curtime();
+ if (active)
+ {
+ if (now != lastconn[d])
+ {
+ lastconn[d] = now;
+ conncnt[d] = 1;
+ }
+ else if (conncnt[d]++ > ConnRateThrottle)
+ {
+#define D_MSG_CRT "deferring connections on daemon %s: %d per second"
+ /* sleep to flatten out connection load */
+ sm_setproctitle(true, e, D_MSG_CRT,
+ name, ConnRateThrottle);
+ if (LogLevel > 8)
+ sm_syslog(LOG_INFO, NOQID, D_MSG_CRT,
+ name, ConnRateThrottle);
+ (void) sleep(1);
+ }
+ }
+ else if (now != lastconn[d])
+ conncnt[d] = 0;
+ }
+
+ sm_getla();
+ if (RefuseLA > 0 && CurrentLA >= RefuseLA)
+ {
+ time_t now;
+
+# define R_MSG_LA "rejecting connections on daemon %s: load average: %d"
+# define R2_MSG_LA "have been rejecting connections on daemon %s for %s"
+ sm_setproctitle(true, e, R_MSG_LA, name, CurrentLA);
+ if (LogLevel > 8)
+ sm_syslog(LOG_NOTICE, NOQID, R_MSG_LA, name, CurrentLA);
+ now = curtime();
+ if (firstrejtime[d] == 0)
+ {
+ firstrejtime[d] = now;
+ nextlogtime[d] = now + RejectLogInterval;
+ }
+ else if (nextlogtime[d] < now)
+ {
+ sm_syslog(LOG_ERR, NOQID, R2_MSG_LA, name,
+ pintvl(now - firstrejtime[d], true));
+ nextlogtime[d] = now + RejectLogInterval;
+ }
+ return true;
+ }
+ else
+ firstrejtime[d] = 0;
+
+ if (DelayLA > 0 && CurrentLA >= DelayLA)
+ {
+ time_t now;
+ static time_t log_delay = (time_t) 0;
+
+# define MIN_DELAY_LOG 90 /* wait before logging this again */
+# define D_MSG_LA "delaying connections on daemon %s: load average=%d >= %d"
+ /* sleep to flatten out connection load */
+ sm_setproctitle(true, e, D_MSG_LA, name, DelayLA);
+ if (LogLevel > 8 && (now = curtime()) > log_delay)
+ {
+ sm_syslog(LOG_INFO, NOQID, D_MSG_LA,
+ name, CurrentLA, DelayLA);
+ log_delay = now + MIN_DELAY_LOG;
+ }
+ (void) sleep(1);
+ }
+
+ if (MaxChildren > 0 && CurChildren >= MaxChildren)
+ {
+ proc_list_probe();
+ if (CurChildren >= MaxChildren)
+ {
+#define R_MSG_CHILD "rejecting connections on daemon %s: %d children, max %d"
+ sm_setproctitle(true, e, R_MSG_CHILD,
+ name, CurChildren, MaxChildren);
+ if (LogLevel > 8)
+ sm_syslog(LOG_INFO, NOQID, R_MSG_CHILD,
+ name, CurChildren, MaxChildren);
+ return true;
+ }
+ }
+ return false;
+}
+/*
+** SETPROCTITLE -- set process title for ps
+**
+** Parameters:
+** fmt -- a printf style format string.
+** a, b, c -- possible parameters to fmt.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Clobbers argv of our main procedure so ps(1) will
+** display the title.
+*/
+
+#define SPT_NONE 0 /* don't use it at all */
+#define SPT_REUSEARGV 1 /* cover argv with title information */
+#define SPT_BUILTIN 2 /* use libc builtin */
+#define SPT_PSTAT 3 /* use pstat(PSTAT_SETCMD, ...) */
+#define SPT_PSSTRINGS 4 /* use PS_STRINGS->... */
+#define SPT_SYSMIPS 5 /* use sysmips() supported by NEWS-OS 6 */
+#define SPT_SCO 6 /* write kernel u. area */
+#define SPT_CHANGEARGV 7 /* write our own strings into argv[] */
+
+#ifndef SPT_TYPE
+# define SPT_TYPE SPT_REUSEARGV
+#endif /* ! SPT_TYPE */
+
+
+#if SPT_TYPE != SPT_NONE && SPT_TYPE != SPT_BUILTIN
+
+# if SPT_TYPE == SPT_PSTAT
+# include <sys/pstat.h>
+# endif /* SPT_TYPE == SPT_PSTAT */
+# if SPT_TYPE == SPT_PSSTRINGS
+# include <machine/vmparam.h>
+# include <sys/exec.h>
+# ifndef PS_STRINGS /* hmmmm.... apparently not available after all */
+# undef SPT_TYPE
+# define SPT_TYPE SPT_REUSEARGV
+# else /* ! PS_STRINGS */
+# ifndef NKPDE /* FreeBSD 2.0 */
+# define NKPDE 63
+typedef unsigned int *pt_entry_t;
+# endif /* ! NKPDE */
+# endif /* ! PS_STRINGS */
+# endif /* SPT_TYPE == SPT_PSSTRINGS */
+
+# if SPT_TYPE == SPT_PSSTRINGS || SPT_TYPE == SPT_CHANGEARGV
+# define SETPROC_STATIC static
+# else /* SPT_TYPE == SPT_PSSTRINGS || SPT_TYPE == SPT_CHANGEARGV */
+# define SETPROC_STATIC
+# endif /* SPT_TYPE == SPT_PSSTRINGS || SPT_TYPE == SPT_CHANGEARGV */
+
+# if SPT_TYPE == SPT_SYSMIPS
+# include <sys/sysmips.h>
+# include <sys/sysnews.h>
+# endif /* SPT_TYPE == SPT_SYSMIPS */
+
+# if SPT_TYPE == SPT_SCO
+# include <sys/immu.h>
+# include <sys/dir.h>
+# include <sys/user.h>
+# include <sys/fs/s5param.h>
+# if PSARGSZ > MAXLINE
+# define SPT_BUFSIZE PSARGSZ
+# endif /* PSARGSZ > MAXLINE */
+# endif /* SPT_TYPE == SPT_SCO */
+
+# ifndef SPT_PADCHAR
+# define SPT_PADCHAR ' '
+# endif /* ! SPT_PADCHAR */
+
+#endif /* SPT_TYPE != SPT_NONE && SPT_TYPE != SPT_BUILTIN */
+
+#ifndef SPT_BUFSIZE
+# define SPT_BUFSIZE MAXLINE
+#endif /* ! SPT_BUFSIZE */
+
+#if _FFR_SPT_ALIGN
+
+/*
+** It looks like the Compaq Tru64 5.1A now aligns argv and envp to
+** 64 bit alignment, so unless each piece of argv and envp is a multiple
+** of 8 bytes (including terminating NULL), initsetproctitle() won't use
+** any of the space beyond argv[0]. Be sure to set SPT_ALIGN_SIZE if
+** you use this FFR.
+*/
+
+# ifdef SPT_ALIGN_SIZE
+# define SPT_ALIGN(x, align) (((((x) + SPT_ALIGN_SIZE) >> (align)) << (align)) - 1)
+# else /* SPT_ALIGN_SIZE */
+# define SPT_ALIGN(x, align) (x)
+# endif /* SPT_ALIGN_SIZE */
+#else /* _FFR_SPT_ALIGN */
+# define SPT_ALIGN(x, align) (x)
+#endif /* _FFR_SPT_ALIGN */
+
+/*
+** Pointers for setproctitle.
+** This allows "ps" listings to give more useful information.
+*/
+
+static char **Argv = NULL; /* pointer to argument vector */
+static char *LastArgv = NULL; /* end of argv */
+#if SPT_TYPE != SPT_BUILTIN
+static void setproctitle __P((const char *, ...));
+#endif /* SPT_TYPE != SPT_BUILTIN */
+
+void
+initsetproctitle(argc, argv, envp)
+ int argc;
+ char **argv;
+ char **envp;
+{
+ register int i;
+ int align;
+ extern char **environ;
+
+ /*
+ ** Move the environment so setproctitle can use the space at
+ ** the top of memory.
+ */
+
+ if (envp != NULL)
+ {
+ for (i = 0; envp[i] != NULL; i++)
+ continue;
+ environ = (char **) xalloc(sizeof (char *) * (i + 1));
+ for (i = 0; envp[i] != NULL; i++)
+ environ[i] = newstr(envp[i]);
+ environ[i] = NULL;
+ }
+
+ /*
+ ** Save start and extent of argv for setproctitle.
+ */
+
+ Argv = argv;
+
+ /*
+ ** Determine how much space we can use for setproctitle.
+ ** Use all contiguous argv and envp pointers starting at argv[0]
+ */
+
+ align = -1;
+# if _FFR_SPT_ALIGN
+# ifdef SPT_ALIGN_SIZE
+ for (i = SPT_ALIGN_SIZE; i > 0; i >>= 1)
+ align++;
+# endif /* SPT_ALIGN_SIZE */
+# endif /* _FFR_SPT_ALIGN */
+
+ for (i = 0; i < argc; i++)
+ {
+ if (i == 0 || LastArgv + 1 == argv[i])
+ LastArgv = argv[i] + SPT_ALIGN(strlen(argv[i]), align);
+ }
+ for (i = 0; LastArgv != NULL && envp != NULL && envp[i] != NULL; i++)
+ {
+ if (LastArgv + 1 == envp[i])
+ LastArgv = envp[i] + SPT_ALIGN(strlen(envp[i]), align);
+ }
+}
+
+#if SPT_TYPE != SPT_BUILTIN
+
+/*VARARGS1*/
+static void
+# ifdef __STDC__
+setproctitle(const char *fmt, ...)
+# else /* __STDC__ */
+setproctitle(fmt, va_alist)
+ const char *fmt;
+ va_dcl
+# endif /* __STDC__ */
+{
+# if SPT_TYPE != SPT_NONE
+ register int i;
+ register char *p;
+ SETPROC_STATIC char buf[SPT_BUFSIZE];
+ SM_VA_LOCAL_DECL
+# if SPT_TYPE == SPT_PSTAT
+ union pstun pst;
+# endif /* SPT_TYPE == SPT_PSTAT */
+# if SPT_TYPE == SPT_SCO
+ int j;
+ off_t seek_off;
+ static int kmem = -1;
+ static pid_t kmempid = -1;
+ struct user u;
+# endif /* SPT_TYPE == SPT_SCO */
+
+ p = buf;
+
+ /* print sendmail: heading for grep */
+ (void) sm_strlcpy(p, "sendmail: ", SPACELEFT(buf, p));
+ p += strlen(p);
+
+ /* print the argument string */
+ SM_VA_START(ap, fmt);
+ (void) sm_vsnprintf(p, SPACELEFT(buf, p), fmt, ap);
+ SM_VA_END(ap);
+
+ i = (int) strlen(buf);
+ if (i < 0)
+ return;
+
+# if SPT_TYPE == SPT_PSTAT
+ pst.pst_command = buf;
+ pstat(PSTAT_SETCMD, pst, i, 0, 0);
+# endif /* SPT_TYPE == SPT_PSTAT */
+# if SPT_TYPE == SPT_PSSTRINGS
+ PS_STRINGS->ps_nargvstr = 1;
+ PS_STRINGS->ps_argvstr = buf;
+# endif /* SPT_TYPE == SPT_PSSTRINGS */
+# if SPT_TYPE == SPT_SYSMIPS
+ sysmips(SONY_SYSNEWS, NEWS_SETPSARGS, buf);
+# endif /* SPT_TYPE == SPT_SYSMIPS */
+# if SPT_TYPE == SPT_SCO
+ if (kmem < 0 || kmempid != CurrentPid)
+ {
+ if (kmem >= 0)
+ (void) close(kmem);
+ kmem = open(_PATH_KMEM, O_RDWR, 0);
+ if (kmem < 0)
+ return;
+ if ((j = fcntl(kmem, F_GETFD, 0)) < 0 ||
+ fcntl(kmem, F_SETFD, j | FD_CLOEXEC) < 0)
+ {
+ (void) close(kmem);
+ kmem = -1;
+ return;
+ }
+ kmempid = CurrentPid;
+ }
+ buf[PSARGSZ - 1] = '\0';
+ seek_off = UVUBLK + (off_t) u.u_psargs - (off_t) &u;
+ if (lseek(kmem, (off_t) seek_off, SEEK_SET) == seek_off)
+ (void) write(kmem, buf, PSARGSZ);
+# endif /* SPT_TYPE == SPT_SCO */
+# if SPT_TYPE == SPT_REUSEARGV
+ if (LastArgv == NULL)
+ return;
+
+ if (i > LastArgv - Argv[0] - 2)
+ {
+ i = LastArgv - Argv[0] - 2;
+ buf[i] = '\0';
+ }
+ (void) sm_strlcpy(Argv[0], buf, i + 1);
+ p = &Argv[0][i];
+ while (p < LastArgv)
+ *p++ = SPT_PADCHAR;
+ Argv[1] = NULL;
+# endif /* SPT_TYPE == SPT_REUSEARGV */
+# if SPT_TYPE == SPT_CHANGEARGV
+ Argv[0] = buf;
+ Argv[1] = 0;
+# endif /* SPT_TYPE == SPT_CHANGEARGV */
+# endif /* SPT_TYPE != SPT_NONE */
+}
+
+#endif /* SPT_TYPE != SPT_BUILTIN */
+/*
+** SM_SETPROCTITLE -- set process task and set process title for ps
+**
+** Possibly set process status and call setproctitle() to
+** change the ps display.
+**
+** Parameters:
+** status -- whether or not to store as process status
+** e -- the current envelope.
+** fmt -- a printf style format string.
+** a, b, c -- possible parameters to fmt.
+**
+** Returns:
+** none.
+*/
+
+/*VARARGS2*/
+void
+#ifdef __STDC__
+sm_setproctitle(bool status, ENVELOPE *e, const char *fmt, ...)
+#else /* __STDC__ */
+sm_setproctitle(status, e, fmt, va_alist)
+ bool status;
+ ENVELOPE *e;
+ const char *fmt;
+ va_dcl
+#endif /* __STDC__ */
+{
+ char buf[SPT_BUFSIZE];
+ SM_VA_LOCAL_DECL
+
+ /* print the argument string */
+ SM_VA_START(ap, fmt);
+ (void) sm_vsnprintf(buf, sizeof buf, fmt, ap);
+ SM_VA_END(ap);
+
+ if (status)
+ proc_list_set(CurrentPid, buf);
+
+ if (ProcTitlePrefix != NULL)
+ {
+ char prefix[SPT_BUFSIZE];
+
+ expand(ProcTitlePrefix, prefix, sizeof prefix, e);
+ setproctitle("%s: %s", prefix, buf);
+ }
+ else
+ setproctitle("%s", buf);
+}
+/*
+** WAITFOR -- wait for a particular process id.
+**
+** Parameters:
+** pid -- process id to wait for.
+**
+** Returns:
+** status of pid.
+** -1 if pid never shows up.
+**
+** Side Effects:
+** none.
+*/
+
+int
+waitfor(pid)
+ pid_t pid;
+{
+ int st;
+ pid_t i;
+
+ do
+ {
+ errno = 0;
+ i = sm_wait(&st);
+ if (i > 0)
+ proc_list_drop(i, st, NULL);
+ } while ((i >= 0 || errno == EINTR) && i != pid);
+ if (i < 0)
+ return -1;
+ return st;
+}
+/*
+** SM_WAIT -- wait
+**
+** Parameters:
+** status -- pointer to status (return value)
+**
+** Returns:
+** pid
+*/
+
+pid_t
+sm_wait(status)
+ int *status;
+{
+# ifdef WAITUNION
+ union wait st;
+# else /* WAITUNION */
+ auto int st;
+# endif /* WAITUNION */
+ pid_t i;
+# if defined(ISC_UNIX) || defined(_SCO_unix_)
+ int savesig;
+# endif /* defined(ISC_UNIX) || defined(_SCO_unix_) */
+
+# if defined(ISC_UNIX) || defined(_SCO_unix_)
+ savesig = sm_releasesignal(SIGCHLD);
+# endif /* defined(ISC_UNIX) || defined(_SCO_unix_) */
+ i = wait(&st);
+# if defined(ISC_UNIX) || defined(_SCO_unix_)
+ if (savesig > 0)
+ sm_blocksignal(SIGCHLD);
+# endif /* defined(ISC_UNIX) || defined(_SCO_unix_) */
+# ifdef WAITUNION
+ *status = st.w_status;
+# else /* WAITUNION */
+ *status = st;
+# endif /* WAITUNION */
+ return i;
+}
+/*
+** REAPCHILD -- pick up the body of my child, lest it become a zombie
+**
+** Parameters:
+** sig -- the signal that got us here (unused).
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Picks up extant zombies.
+** Control socket exits may restart/shutdown daemon.
+**
+** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+** DOING.
+*/
+
+/* ARGSUSED0 */
+SIGFUNC_DECL
+reapchild(sig)
+ int sig;
+{
+ int save_errno = errno;
+ int st;
+ pid_t pid;
+# if HASWAITPID
+ auto int status;
+ int count;
+
+ count = 0;
+ while ((pid = waitpid(-1, &status, WNOHANG)) > 0)
+ {
+ st = status;
+ if (count++ > 1000)
+ break;
+# else /* HASWAITPID */
+# ifdef WNOHANG
+ union wait status;
+
+ while ((pid = wait3(&status, WNOHANG, (struct rusage *) NULL)) > 0)
+ {
+ st = status.w_status;
+# else /* WNOHANG */
+ auto int status;
+
+ /*
+ ** Catch one zombie -- we will be re-invoked (we hope) if there
+ ** are more. Unreliable signals probably break this, but this
+ ** is the "old system" situation -- waitpid or wait3 are to be
+ ** strongly preferred.
+ */
+
+ if ((pid = wait(&status)) > 0)
+ {
+ st = status;
+# endif /* WNOHANG */
+# endif /* HASWAITPID */
+ /* Drop PID and check if it was a control socket child */
+ proc_list_drop(pid, st, NULL);
+ }
+ FIX_SYSV_SIGNAL(sig, reapchild);
+ errno = save_errno;
+ return SIGFUNC_RETURN;
+}
+/*
+** GETDTABLESIZE -- return number of file descriptors
+**
+** Only on non-BSD systems
+**
+** Parameters:
+** none
+**
+** Returns:
+** size of file descriptor table
+**
+** Side Effects:
+** none
+*/
+
+#ifdef SOLARIS
+# include <sys/resource.h>
+#endif /* SOLARIS */
+
+int
+getdtsize()
+{
+# ifdef RLIMIT_NOFILE
+ struct rlimit rl;
+
+ if (getrlimit(RLIMIT_NOFILE, &rl) >= 0)
+ return rl.rlim_cur;
+# endif /* RLIMIT_NOFILE */
+
+# if HASGETDTABLESIZE
+ return getdtablesize();
+# else /* HASGETDTABLESIZE */
+# ifdef _SC_OPEN_MAX
+ return sysconf(_SC_OPEN_MAX);
+# else /* _SC_OPEN_MAX */
+ return NOFILE;
+# endif /* _SC_OPEN_MAX */
+# endif /* HASGETDTABLESIZE */
+}
+/*
+** UNAME -- get the UUCP name of this system.
+*/
+
+#if !HASUNAME
+
+int
+uname(name)
+ struct utsname *name;
+{
+ SM_FILE_T *file;
+ char *n;
+
+ name->nodename[0] = '\0';
+
+ /* try /etc/whoami -- one line with the node name */
+ if ((file = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, "/etc/whoami",
+ SM_IO_RDONLY, NULL)) != NULL)
+ {
+ (void) sm_io_fgets(file, SM_TIME_DEFAULT, name->nodename,
+ NODE_LENGTH + 1);
+ (void) sm_io_close(file, SM_TIME_DEFAULT);
+ n = strchr(name->nodename, '\n');
+ if (n != NULL)
+ *n = '\0';
+ if (name->nodename[0] != '\0')
+ return 0;
+ }
+
+ /* try /usr/include/whoami.h -- has a #define somewhere */
+ if ((file = sm_io_open(SmFtStdio, SM_TIME_DEFAULT,
+ "/usr/include/whoami.h", SM_IO_RDONLY, NULL))
+ != NULL)
+ {
+ char buf[MAXLINE];
+
+ while (sm_io_fgets(file, SM_TIME_DEFAULT,
+ buf, sizeof buf) != NULL)
+ {
+ if (sm_io_sscanf(buf, "#define sysname \"%*[^\"]\"",
+ NODE_LENGTH, name->nodename) > 0)
+ break;
+ }
+ (void) sm_io_close(file, SM_TIME_DEFAULT);
+ if (name->nodename[0] != '\0')
+ return 0;
+ }
+
+ return -1;
+}
+#endif /* !HASUNAME */
+/*
+** INITGROUPS -- initialize groups
+**
+** Stub implementation for System V style systems
+*/
+
+#if !HASINITGROUPS
+
+initgroups(name, basegid)
+ char *name;
+ int basegid;
+{
+ return 0;
+}
+
+#endif /* !HASINITGROUPS */
+/*
+** SETGROUPS -- set group list
+**
+** Stub implementation for systems that don't have group lists
+*/
+
+#ifndef NGROUPS_MAX
+
+int
+setgroups(ngroups, grouplist)
+ int ngroups;
+ GIDSET_T grouplist[];
+{
+ return 0;
+}
+
+#endif /* ! NGROUPS_MAX */
+/*
+** SETSID -- set session id (for non-POSIX systems)
+*/
+
+#if !HASSETSID
+
+pid_t
+setsid __P ((void))
+{
+# ifdef TIOCNOTTY
+ int fd;
+
+ fd = open("/dev/tty", O_RDWR, 0);
+ if (fd >= 0)
+ {
+ (void) ioctl(fd, TIOCNOTTY, (char *) 0);
+ (void) close(fd);
+ }
+# endif /* TIOCNOTTY */
+# ifdef SYS5SETPGRP
+ return setpgrp();
+# else /* SYS5SETPGRP */
+ return setpgid(0, CurrentPid);
+# endif /* SYS5SETPGRP */
+}
+
+#endif /* !HASSETSID */
+/*
+** FSYNC -- dummy fsync
+*/
+
+#if NEEDFSYNC
+
+fsync(fd)
+ int fd;
+{
+# ifdef O_SYNC
+ return fcntl(fd, F_SETFL, O_SYNC);
+# else /* O_SYNC */
+ /* nothing we can do */
+ return 0;
+# endif /* O_SYNC */
+}
+
+#endif /* NEEDFSYNC */
+/*
+** DGUX_INET_ADDR -- inet_addr for DG/UX
+**
+** Data General DG/UX version of inet_addr returns a struct in_addr
+** instead of a long. This patches things. Only needed on versions
+** prior to 5.4.3.
+*/
+
+#ifdef DGUX_5_4_2
+
+# undef inet_addr
+
+long
+dgux_inet_addr(host)
+ char *host;
+{
+ struct in_addr haddr;
+
+ haddr = inet_addr(host);
+ return haddr.s_addr;
+}
+
+#endif /* DGUX_5_4_2 */
+/*
+** GETOPT -- for old systems or systems with bogus implementations
+*/
+
+#if !SM_CONF_GETOPT
+
+/*
+ * Copyright (c) 1985 Regents of the University of California.
+ * All rights reserved. The Berkeley software License Agreement
+ * specifies the terms and conditions for redistribution.
+ */
+
+
+/*
+** this version hacked to add `atend' flag to allow state machine
+** to reset if invoked by the program to scan args for a 2nd time
+*/
+
+# if defined(LIBC_SCCS) && !defined(lint)
+static char sccsid[] = "@(#)getopt.c 4.3 (Berkeley) 3/9/86";
+# endif /* defined(LIBC_SCCS) && !defined(lint) */
+
+/*
+** get option letter from argument vector
+*/
+# ifdef _CONVEX_SOURCE
+extern int optind, opterr, optopt;
+extern char *optarg;
+# else /* _CONVEX_SOURCE */
+int opterr = 1; /* if error message should be printed */
+int optind = 1; /* index into parent argv vector */
+int optopt = 0; /* character checked for validity */
+char *optarg = NULL; /* argument associated with option */
+# endif /* _CONVEX_SOURCE */
+
+# define BADCH (int)'?'
+# define EMSG ""
+# define tell(s) if (opterr) \
+ {sm_io_fputs(smioerr, SM_TIME_DEFAULT, *nargv); \
+ (void) sm_io_fputs(smioerr, SM_TIME_DEFAULT, s); \
+ (void) sm_io_putc(smioerr, SM_TIME_DEFAULT, optopt); \
+ (void) sm_io_putc(smioerr, SM_TIME_DEFAULT, '\n'); \
+ return BADCH;}
+
+int
+getopt(nargc,nargv,ostr)
+ int nargc;
+ char *const *nargv;
+ const char *ostr;
+{
+ static char *place = EMSG; /* option letter processing */
+ static char atend = 0;
+ register char *oli = NULL; /* option letter list index */
+
+ if (atend) {
+ atend = 0;
+ place = EMSG;
+ }
+ if(!*place) { /* update scanning pointer */
+ if (optind >= nargc || *(place = nargv[optind]) != '-' || !*++place) {
+ atend++;
+ return -1;
+ }
+ if (*place == '-') { /* found "--" */
+ ++optind;
+ atend++;
+ return -1;
+ }
+ } /* option letter okay? */
+ if ((optopt = (int)*place++) == (int)':' || !(oli = strchr(ostr,optopt))) {
+ if (!*place) ++optind;
+ tell(": illegal option -- ");
+ }
+ if (oli && *++oli != ':') { /* don't need argument */
+ optarg = NULL;
+ if (!*place) ++optind;
+ }
+ else { /* need an argument */
+ if (*place) optarg = place; /* no white space */
+ else if (nargc <= ++optind) { /* no arg */
+ place = EMSG;
+ tell(": option requires an argument -- ");
+ }
+ else optarg = nargv[optind]; /* white space */
+ place = EMSG;
+ ++optind;
+ }
+ return optopt; /* dump back option letter */
+}
+
+#endif /* !SM_CONF_GETOPT */
+/*
+** USERSHELLOK -- tell if a user's shell is ok for unrestricted use
+**
+** Parameters:
+** user -- the name of the user we are checking.
+** shell -- the user's shell from /etc/passwd
+**
+** Returns:
+** true -- if it is ok to use this for unrestricted access.
+** false -- if the shell is restricted.
+*/
+
+#if !HASGETUSERSHELL
+
+# ifndef _PATH_SHELLS
+# define _PATH_SHELLS "/etc/shells"
+# endif /* ! _PATH_SHELLS */
+
+# if defined(_AIX3) || defined(_AIX4)
+# include <userconf.h>
+# if _AIX4 >= 40200
+# include <userpw.h>
+# endif /* _AIX4 >= 40200 */
+# include <usersec.h>
+# endif /* defined(_AIX3) || defined(_AIX4) */
+
+static char *DefaultUserShells[] =
+{
+ "/bin/sh", /* standard shell */
+# ifdef MPE
+ "/SYS/PUB/CI",
+# else /* MPE */
+ "/usr/bin/sh",
+ "/bin/csh", /* C shell */
+ "/usr/bin/csh",
+# endif /* MPE */
+# ifdef __hpux
+# ifdef V4FS
+ "/usr/bin/rsh", /* restricted Bourne shell */
+ "/usr/bin/ksh", /* Korn shell */
+ "/usr/bin/rksh", /* restricted Korn shell */
+ "/usr/bin/pam",
+ "/usr/bin/keysh", /* key shell (extended Korn shell) */
+ "/usr/bin/posix/sh",
+# else /* V4FS */
+ "/bin/rsh", /* restricted Bourne shell */
+ "/bin/ksh", /* Korn shell */
+ "/bin/rksh", /* restricted Korn shell */
+ "/bin/pam",
+ "/usr/bin/keysh", /* key shell (extended Korn shell) */
+ "/bin/posix/sh",
+ "/sbin/sh",
+# endif /* V4FS */
+# endif /* __hpux */
+# if defined(_AIX3) || defined(_AIX4)
+ "/bin/ksh", /* Korn shell */
+ "/usr/bin/ksh",
+ "/bin/tsh", /* trusted shell */
+ "/usr/bin/tsh",
+ "/bin/bsh", /* Bourne shell */
+ "/usr/bin/bsh",
+# endif /* defined(_AIX3) || defined(_AIX4) */
+# if defined(__svr4__) || defined(__svr5__)
+ "/bin/ksh", /* Korn shell */
+ "/usr/bin/ksh",
+# endif /* defined(__svr4__) || defined(__svr5__) */
+# ifdef sgi
+ "/sbin/sh", /* SGI's shells really live in /sbin */
+ "/usr/bin/sh",
+ "/sbin/bsh", /* classic Bourne shell */
+ "/bin/bsh",
+ "/usr/bin/bsh",
+ "/sbin/csh", /* standard csh */
+ "/bin/csh",
+ "/usr/bin/csh",
+ "/sbin/jsh", /* classic Bourne shell w/ job control*/
+ "/bin/jsh",
+ "/usr/bin/jsh",
+ "/bin/ksh", /* Korn shell */
+ "/sbin/ksh",
+ "/usr/bin/ksh",
+ "/sbin/tcsh", /* Extended csh */
+ "/bin/tcsh",
+ "/usr/bin/tcsh",
+# endif /* sgi */
+ NULL
+};
+
+#endif /* !HASGETUSERSHELL */
+
+#define WILDCARD_SHELL "/SENDMAIL/ANY/SHELL/"
+
+bool
+usershellok(user, shell)
+ char *user;
+ char *shell;
+{
+# if HASGETUSERSHELL
+ register char *p;
+ extern char *getusershell();
+
+ if (shell == NULL || shell[0] == '\0' || wordinclass(user, 't') ||
+ ConfigLevel <= 1)
+ return true;
+
+ setusershell();
+ while ((p = getusershell()) != NULL)
+ if (strcmp(p, shell) == 0 || strcmp(p, WILDCARD_SHELL) == 0)
+ break;
+ endusershell();
+ return p != NULL;
+# else /* HASGETUSERSHELL */
+# if USEGETCONFATTR
+ auto char *v;
+# endif /* USEGETCONFATTR */
+ register SM_FILE_T *shellf;
+ char buf[MAXLINE];
+
+ if (shell == NULL || shell[0] == '\0' || wordinclass(user, 't') ||
+ ConfigLevel <= 1)
+ return true;
+
+# if USEGETCONFATTR
+ /*
+ ** Naturally IBM has a "better" idea.....
+ **
+ ** What a crock. This interface isn't documented, it is
+ ** considered part of the security library (-ls), and it
+ ** only works if you are running as root (since the list
+ ** of valid shells is obviously a source of great concern).
+ ** I recommend that you do NOT define USEGETCONFATTR,
+ ** especially since you are going to have to set up an
+ ** /etc/shells anyhow to handle the cases where getconfattr
+ ** fails.
+ */
+
+ if (getconfattr(SC_SYS_LOGIN, SC_SHELLS, &v, SEC_LIST) == 0 && v != NULL)
+ {
+ while (*v != '\0')
+ {
+ if (strcmp(v, shell) == 0 || strcmp(v, WILDCARD_SHELL) == 0)
+ return true;
+ v += strlen(v) + 1;
+ }
+ return false;
+ }
+# endif /* USEGETCONFATTR */
+
+ shellf = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, _PATH_SHELLS,
+ SM_IO_RDONLY, NULL);
+ if (shellf == NULL)
+ {
+ /* no /etc/shells; see if it is one of the std shells */
+ char **d;
+
+ if (errno != ENOENT && LogLevel > 3)
+ sm_syslog(LOG_ERR, NOQID,
+ "usershellok: cannot open %s: %s",
+ _PATH_SHELLS, sm_errstring(errno));
+
+ for (d = DefaultUserShells; *d != NULL; d++)
+ {
+ if (strcmp(shell, *d) == 0)
+ return true;
+ }
+ return false;
+ }
+
+ while (sm_io_fgets(shellf, SM_TIME_DEFAULT, buf, sizeof buf) != NULL)
+ {
+ register char *p, *q;
+
+ p = buf;
+ while (*p != '\0' && *p != '#' && *p != '/')
+ p++;
+ if (*p == '#' || *p == '\0')
+ continue;
+ q = p;
+ while (*p != '\0' && *p != '#' && !(isascii(*p) && isspace(*p)))
+ p++;
+ *p = '\0';
+ if (strcmp(shell, q) == 0 || strcmp(WILDCARD_SHELL, q) == 0)
+ {
+ (void) sm_io_close(shellf, SM_TIME_DEFAULT);
+ return true;
+ }
+ }
+ (void) sm_io_close(shellf, SM_TIME_DEFAULT);
+ return false;
+# endif /* HASGETUSERSHELL */
+}
+/*
+** FREEDISKSPACE -- see how much free space is on the queue filesystem
+**
+** Only implemented if you have statfs.
+**
+** Parameters:
+** dir -- the directory in question.
+** bsize -- a variable into which the filesystem
+** block size is stored.
+**
+** Returns:
+** The number of blocks free on the queue filesystem.
+** -1 if the statfs call fails.
+**
+** Side effects:
+** Puts the filesystem block size into bsize.
+*/
+
+/* statfs types */
+# define SFS_NONE 0 /* no statfs implementation */
+# define SFS_USTAT 1 /* use ustat */
+# define SFS_4ARGS 2 /* use four-argument statfs call */
+# define SFS_VFS 3 /* use <sys/vfs.h> implementation */
+# define SFS_MOUNT 4 /* use <sys/mount.h> implementation */
+# define SFS_STATFS 5 /* use <sys/statfs.h> implementation */
+# define SFS_STATVFS 6 /* use <sys/statvfs.h> implementation */
+
+# ifndef SFS_TYPE
+# define SFS_TYPE SFS_NONE
+# endif /* ! SFS_TYPE */
+
+# if SFS_TYPE == SFS_USTAT
+# include <ustat.h>
+# endif /* SFS_TYPE == SFS_USTAT */
+# if SFS_TYPE == SFS_4ARGS || SFS_TYPE == SFS_STATFS
+# include <sys/statfs.h>
+# endif /* SFS_TYPE == SFS_4ARGS || SFS_TYPE == SFS_STATFS */
+# if SFS_TYPE == SFS_VFS
+# include <sys/vfs.h>
+# endif /* SFS_TYPE == SFS_VFS */
+# if SFS_TYPE == SFS_MOUNT
+# include <sys/mount.h>
+# endif /* SFS_TYPE == SFS_MOUNT */
+# if SFS_TYPE == SFS_STATVFS
+# include <sys/statvfs.h>
+# endif /* SFS_TYPE == SFS_STATVFS */
+
+long
+freediskspace(dir, bsize)
+ char *dir;
+ long *bsize;
+{
+# if SFS_TYPE == SFS_NONE
+ if (bsize != NULL)
+ *bsize = 4096L;
+
+ /* assume free space is plentiful */
+ return (long) LONG_MAX;
+# else /* SFS_TYPE == SFS_NONE */
+# if SFS_TYPE == SFS_USTAT
+ struct ustat fs;
+ struct stat statbuf;
+# define FSBLOCKSIZE DEV_BSIZE
+# define SFS_BAVAIL f_tfree
+# else /* SFS_TYPE == SFS_USTAT */
+# if defined(ultrix)
+ struct fs_data fs;
+# define SFS_BAVAIL fd_bfreen
+# define FSBLOCKSIZE 1024L
+# else /* defined(ultrix) */
+# if SFS_TYPE == SFS_STATVFS
+ struct statvfs fs;
+# define FSBLOCKSIZE fs.f_frsize
+# else /* SFS_TYPE == SFS_STATVFS */
+ struct statfs fs;
+# define FSBLOCKSIZE fs.f_bsize
+# endif /* SFS_TYPE == SFS_STATVFS */
+# endif /* defined(ultrix) */
+# endif /* SFS_TYPE == SFS_USTAT */
+# ifndef SFS_BAVAIL
+# define SFS_BAVAIL f_bavail
+# endif /* ! SFS_BAVAIL */
+
+# if SFS_TYPE == SFS_USTAT
+ if (stat(dir, &statbuf) == 0 && ustat(statbuf.st_dev, &fs) == 0)
+# else /* SFS_TYPE == SFS_USTAT */
+# if SFS_TYPE == SFS_4ARGS
+ if (statfs(dir, &fs, sizeof fs, 0) == 0)
+# else /* SFS_TYPE == SFS_4ARGS */
+# if SFS_TYPE == SFS_STATVFS
+ if (statvfs(dir, &fs) == 0)
+# else /* SFS_TYPE == SFS_STATVFS */
+# if defined(ultrix)
+ if (statfs(dir, &fs) > 0)
+# else /* defined(ultrix) */
+ if (statfs(dir, &fs) == 0)
+# endif /* defined(ultrix) */
+# endif /* SFS_TYPE == SFS_STATVFS */
+# endif /* SFS_TYPE == SFS_4ARGS */
+# endif /* SFS_TYPE == SFS_USTAT */
+ {
+ if (bsize != NULL)
+ *bsize = FSBLOCKSIZE;
+ if (fs.SFS_BAVAIL <= 0)
+ return 0;
+ else if (fs.SFS_BAVAIL > LONG_MAX)
+ return (long) LONG_MAX;
+ else
+ return (long) fs.SFS_BAVAIL;
+ }
+ return -1;
+# endif /* SFS_TYPE == SFS_NONE */
+}
+/*
+** ENOUGHDISKSPACE -- is there enough free space on the queue file systems?
+**
+** Parameters:
+** msize -- the size to check against. If zero, we don't yet
+** know how big the message will be, so just check for
+** a "reasonable" amount.
+** e -- envelope, or NULL -- controls logging
+**
+** Returns:
+** true if in every queue group there is at least one
+** queue directory whose file system contains enough free space.
+** false otherwise.
+**
+** Side Effects:
+** If there is not enough disk space and e != NULL
+** then sm_syslog is called.
+*/
+
+bool
+enoughdiskspace(msize, e)
+ long msize;
+ ENVELOPE *e;
+{
+ int i;
+
+ if (MinBlocksFree <= 0 && msize <= 0)
+ {
+ if (tTd(4, 80))
+ sm_dprintf("enoughdiskspace: no threshold\n");
+ return true;
+ }
+
+ filesys_update();
+ for (i = 0; i < NumQueue; ++i)
+ {
+ if (pickqdir(Queue[i], msize, e) < 0)
+ return false;
+ }
+ return true;
+}
+/*
+** TRANSIENTERROR -- tell if an error code indicates a transient failure
+**
+** This looks at an errno value and tells if this is likely to
+** go away if retried later.
+**
+** Parameters:
+** err -- the errno code to classify.
+**
+** Returns:
+** true if this is probably transient.
+** false otherwise.
+*/
+
+bool
+transienterror(err)
+ int err;
+{
+ switch (err)
+ {
+ case EIO: /* I/O error */
+ case ENXIO: /* Device not configured */
+ case EAGAIN: /* Resource temporarily unavailable */
+ case ENOMEM: /* Cannot allocate memory */
+ case ENODEV: /* Operation not supported by device */
+ case ENFILE: /* Too many open files in system */
+ case EMFILE: /* Too many open files */
+ case ENOSPC: /* No space left on device */
+ case ETIMEDOUT: /* Connection timed out */
+#ifdef ESTALE
+ case ESTALE: /* Stale NFS file handle */
+#endif /* ESTALE */
+#ifdef ENETDOWN
+ case ENETDOWN: /* Network is down */
+#endif /* ENETDOWN */
+#ifdef ENETUNREACH
+ case ENETUNREACH: /* Network is unreachable */
+#endif /* ENETUNREACH */
+#ifdef ENETRESET
+ case ENETRESET: /* Network dropped connection on reset */
+#endif /* ENETRESET */
+#ifdef ECONNABORTED
+ case ECONNABORTED: /* Software caused connection abort */
+#endif /* ECONNABORTED */
+#ifdef ECONNRESET
+ case ECONNRESET: /* Connection reset by peer */
+#endif /* ECONNRESET */
+#ifdef ENOBUFS
+ case ENOBUFS: /* No buffer space available */
+#endif /* ENOBUFS */
+#ifdef ESHUTDOWN
+ case ESHUTDOWN: /* Can't send after socket shutdown */
+#endif /* ESHUTDOWN */
+#ifdef ECONNREFUSED
+ case ECONNREFUSED: /* Connection refused */
+#endif /* ECONNREFUSED */
+#ifdef EHOSTDOWN
+ case EHOSTDOWN: /* Host is down */
+#endif /* EHOSTDOWN */
+#ifdef EHOSTUNREACH
+ case EHOSTUNREACH: /* No route to host */
+#endif /* EHOSTUNREACH */
+#ifdef EDQUOT
+ case EDQUOT: /* Disc quota exceeded */
+#endif /* EDQUOT */
+#ifdef EPROCLIM
+ case EPROCLIM: /* Too many processes */
+#endif /* EPROCLIM */
+#ifdef EUSERS
+ case EUSERS: /* Too many users */
+#endif /* EUSERS */
+#ifdef EDEADLK
+ case EDEADLK: /* Resource deadlock avoided */
+#endif /* EDEADLK */
+#ifdef EISCONN
+ case EISCONN: /* Socket already connected */
+#endif /* EISCONN */
+#ifdef EINPROGRESS
+ case EINPROGRESS: /* Operation now in progress */
+#endif /* EINPROGRESS */
+#ifdef EALREADY
+ case EALREADY: /* Operation already in progress */
+#endif /* EALREADY */
+#ifdef EADDRINUSE
+ case EADDRINUSE: /* Address already in use */
+#endif /* EADDRINUSE */
+#ifdef EADDRNOTAVAIL
+ case EADDRNOTAVAIL: /* Can't assign requested address */
+#endif /* EADDRNOTAVAIL */
+#ifdef ETXTBSY
+ case ETXTBSY: /* (Apollo) file locked */
+#endif /* ETXTBSY */
+#if defined(ENOSR) && (!defined(ENOBUFS) || (ENOBUFS != ENOSR))
+ case ENOSR: /* Out of streams resources */
+#endif /* defined(ENOSR) && (!defined(ENOBUFS) || (ENOBUFS != ENOSR)) */
+#ifdef ENOLCK
+ case ENOLCK: /* No locks available */
+#endif /* ENOLCK */
+ case E_SM_OPENTIMEOUT: /* PSEUDO: open timed out */
+ return true;
+ }
+
+ /* nope, must be permanent */
+ return false;
+}
+/*
+** LOCKFILE -- lock a file using flock or (shudder) fcntl locking
+**
+** Parameters:
+** fd -- the file descriptor of the file.
+** filename -- the file name (for error messages).
+** ext -- the filename extension.
+** type -- type of the lock. Bits can be:
+** LOCK_EX -- exclusive lock.
+** LOCK_NB -- non-blocking.
+** LOCK_UN -- unlock.
+**
+** Returns:
+** true if the lock was acquired.
+** false otherwise.
+*/
+
+bool
+lockfile(fd, filename, ext, type)
+ int fd;
+ char *filename;
+ char *ext;
+ int type;
+{
+ int i;
+ int save_errno;
+# if !HASFLOCK
+ int action;
+ struct flock lfd;
+
+ if (ext == NULL)
+ ext = "";
+
+ memset(&lfd, '\0', sizeof lfd);
+ if (bitset(LOCK_UN, type))
+ lfd.l_type = F_UNLCK;
+ else if (bitset(LOCK_EX, type))
+ lfd.l_type = F_WRLCK;
+ else
+ lfd.l_type = F_RDLCK;
+
+ if (bitset(LOCK_NB, type))
+ action = F_SETLK;
+ else
+ action = F_SETLKW;
+
+ if (tTd(55, 60))
+ sm_dprintf("lockfile(%s%s, action=%d, type=%d): ",
+ filename, ext, action, lfd.l_type);
+
+ while ((i = fcntl(fd, action, &lfd)) < 0 && errno == EINTR)
+ continue;
+ if (i >= 0)
+ {
+ if (tTd(55, 60))
+ sm_dprintf("SUCCESS\n");
+ return true;
+ }
+ save_errno = errno;
+
+ if (tTd(55, 60))
+ sm_dprintf("(%s) ", sm_errstring(save_errno));
+
+ /*
+ ** On SunOS, if you are testing using -oQ/tmp/mqueue or
+ ** -oA/tmp/aliases or anything like that, and /tmp is mounted
+ ** as type "tmp" (that is, served from swap space), the
+ ** previous fcntl will fail with "Invalid argument" errors.
+ ** Since this is fairly common during testing, we will assume
+ ** that this indicates that the lock is successfully grabbed.
+ */
+
+ if (save_errno == EINVAL)
+ {
+ if (tTd(55, 60))
+ sm_dprintf("SUCCESS\n");
+ return true;
+ }
+
+ if (!bitset(LOCK_NB, type) ||
+ (save_errno != EACCES && save_errno != EAGAIN))
+ {
+ int omode = fcntl(fd, F_GETFL, 0);
+ uid_t euid = geteuid();
+
+ errno = save_errno;
+ syserr("cannot lockf(%s%s, fd=%d, type=%o, omode=%o, euid=%d)",
+ filename, ext, fd, type, omode, euid);
+ dumpfd(fd, true, true);
+ }
+# else /* !HASFLOCK */
+ if (ext == NULL)
+ ext = "";
+
+ if (tTd(55, 60))
+ sm_dprintf("lockfile(%s%s, type=%o): ", filename, ext, type);
+
+ while ((i = flock(fd, type)) < 0 && errno == EINTR)
+ continue;
+ if (i >= 0)
+ {
+ if (tTd(55, 60))
+ sm_dprintf("SUCCESS\n");
+ return true;
+ }
+ save_errno = errno;
+
+ if (tTd(55, 60))
+ sm_dprintf("(%s) ", sm_errstring(save_errno));
+
+ if (!bitset(LOCK_NB, type) || save_errno != EWOULDBLOCK)
+ {
+ int omode = fcntl(fd, F_GETFL, 0);
+ uid_t euid = geteuid();
+
+ errno = save_errno;
+ syserr("cannot flock(%s%s, fd=%d, type=%o, omode=%o, euid=%d)",
+ filename, ext, fd, type, omode, euid);
+ dumpfd(fd, true, true);
+ }
+# endif /* !HASFLOCK */
+ if (tTd(55, 60))
+ sm_dprintf("FAIL\n");
+ errno = save_errno;
+ return false;
+}
+/*
+** CHOWNSAFE -- tell if chown is "safe" (executable only by root)
+**
+** Unfortunately, given that we can't predict other systems on which
+** a remote mounted (NFS) filesystem will be mounted, the answer is
+** almost always that this is unsafe.
+**
+** Note also that many operating systems have non-compliant
+** implementations of the _POSIX_CHOWN_RESTRICTED variable and the
+** fpathconf() routine. According to IEEE 1003.1-1990, if
+** _POSIX_CHOWN_RESTRICTED is defined and not equal to -1, then
+** no non-root process can give away the file. However, vendors
+** don't take NFS into account, so a comfortable value of
+** _POSIX_CHOWN_RESTRICTED tells us nothing.
+**
+** Also, some systems (e.g., IRIX 6.2) return 1 from fpathconf()
+** even on files where chown is not restricted. Many systems get
+** this wrong on NFS-based filesystems (that is, they say that chown
+** is restricted [safe] on NFS filesystems where it may not be, since
+** other systems can access the same filesystem and do file giveaway;
+** only the NFS server knows for sure!) Hence, it is important to
+** get the value of SAFENFSPATHCONF correct -- it should be defined
+** _only_ after testing (see test/t_pathconf.c) a system on an unsafe
+** NFS-based filesystem to ensure that you can get meaningful results.
+** If in doubt, assume unsafe!
+**
+** You may also need to tweak IS_SAFE_CHOWN -- it should be a
+** condition indicating whether the return from pathconf indicates
+** that chown is safe (typically either > 0 or >= 0 -- there isn't
+** even any agreement about whether a zero return means that a file
+** is or is not safe). It defaults to "> 0".
+**
+** If the parent directory is safe (writable only by owner back
+** to the root) then we can relax slightly and trust fpathconf
+** in more circumstances. This is really a crock -- if this is an
+** NFS mounted filesystem then we really know nothing about the
+** underlying implementation. However, most systems pessimize and
+** return an error (EINVAL or EOPNOTSUPP) on NFS filesystems, which
+** we interpret as unsafe, as we should. Thus, this heuristic gets
+** us into a possible problem only on systems that have a broken
+** pathconf implementation and which are also poorly configured
+** (have :include: files in group- or world-writable directories).
+**
+** Parameters:
+** fd -- the file descriptor to check.
+** safedir -- set if the parent directory is safe.
+**
+** Returns:
+** true -- if the chown(2) operation is "safe" -- that is,
+** only root can chown the file to an arbitrary user.
+** false -- if an arbitrary user can give away a file.
+*/
+
+#ifndef IS_SAFE_CHOWN
+# define IS_SAFE_CHOWN > 0
+#endif /* ! IS_SAFE_CHOWN */
+
+bool
+chownsafe(fd, safedir)
+ int fd;
+ bool safedir;
+{
+# if (!defined(_POSIX_CHOWN_RESTRICTED) || _POSIX_CHOWN_RESTRICTED != -1) && \
+ (defined(_PC_CHOWN_RESTRICTED) || defined(_GNU_TYPES_H))
+ int rval;
+
+ /* give the system administrator a chance to override */
+ if (bitnset(DBS_ASSUMESAFECHOWN, DontBlameSendmail))
+ return true;
+
+ /*
+ ** Some systems (e.g., SunOS) seem to have the call and the
+ ** #define _PC_CHOWN_RESTRICTED, but don't actually implement
+ ** the call. This heuristic checks for that.
+ */
+
+ errno = 0;
+ rval = fpathconf(fd, _PC_CHOWN_RESTRICTED);
+# if SAFENFSPATHCONF
+ return errno == 0 && rval IS_SAFE_CHOWN;
+# else /* SAFENFSPATHCONF */
+ return safedir && errno == 0 && rval IS_SAFE_CHOWN;
+# endif /* SAFENFSPATHCONF */
+# else /* (!defined(_POSIX_CHOWN_RESTRICTED) || _POSIX_CHOWN_RESTRICTED != -1) && ... */
+ return bitnset(DBS_ASSUMESAFECHOWN, DontBlameSendmail);
+# endif /* (!defined(_POSIX_CHOWN_RESTRICTED) || _POSIX_CHOWN_RESTRICTED != -1) && ... */
+}
+/*
+** RESETLIMITS -- reset system controlled resource limits
+**
+** This is to avoid denial-of-service attacks
+**
+** Parameters:
+** none
+**
+** Returns:
+** none
+*/
+
+#if HASSETRLIMIT
+# ifdef RLIMIT_NEEDS_SYS_TIME_H
+# include <sys/time.h>
+# endif /* RLIMIT_NEEDS_SYS_TIME_H */
+# include <sys/resource.h>
+#endif /* HASSETRLIMIT */
+
+void
+resetlimits()
+{
+#if HASSETRLIMIT
+ struct rlimit lim;
+
+ lim.rlim_cur = lim.rlim_max = RLIM_INFINITY;
+ (void) setrlimit(RLIMIT_CPU, &lim);
+ (void) setrlimit(RLIMIT_FSIZE, &lim);
+# ifdef RLIMIT_NOFILE
+ lim.rlim_cur = lim.rlim_max = FD_SETSIZE;
+ (void) setrlimit(RLIMIT_NOFILE, &lim);
+# endif /* RLIMIT_NOFILE */
+#else /* HASSETRLIMIT */
+# if HASULIMIT
+ (void) ulimit(2, 0x3fffff);
+ (void) ulimit(4, FD_SETSIZE);
+# endif /* HASULIMIT */
+#endif /* HASSETRLIMIT */
+ errno = 0;
+}
+/*
+** SETVENDOR -- process vendor code from V configuration line
+**
+** Parameters:
+** vendor -- string representation of vendor.
+**
+** Returns:
+** true -- if ok.
+** false -- if vendor code could not be processed.
+**
+** Side Effects:
+** It is reasonable to set mode flags here to tweak
+** processing in other parts of the code if necessary.
+** For example, if you are a vendor that uses $%y to
+** indicate YP lookups, you could enable that here.
+*/
+
+bool
+setvendor(vendor)
+ char *vendor;
+{
+ if (sm_strcasecmp(vendor, "Berkeley") == 0)
+ {
+ VendorCode = VENDOR_BERKELEY;
+ return true;
+ }
+
+ /* add vendor extensions here */
+
+#ifdef SUN_EXTENSIONS
+ if (sm_strcasecmp(vendor, "Sun") == 0)
+ {
+ VendorCode = VENDOR_SUN;
+ return true;
+ }
+#endif /* SUN_EXTENSIONS */
+
+#if defined(VENDOR_NAME) && defined(VENDOR_CODE)
+ if (sm_strcasecmp(vendor, VENDOR_NAME) == 0)
+ {
+ VendorCode = VENDOR_CODE;
+ return true;
+ }
+#endif /* defined(VENDOR_NAME) && defined(VENDOR_CODE) */
+
+ return false;
+}
+/*
+** GETVENDOR -- return vendor name based on vendor code
+**
+** Parameters:
+** vendorcode -- numeric representation of vendor.
+**
+** Returns:
+** string containing vendor name.
+*/
+
+char *
+getvendor(vendorcode)
+ int vendorcode;
+{
+#if defined(VENDOR_NAME) && defined(VENDOR_CODE)
+ /*
+ ** Can't have the same switch case twice so need to
+ ** handle VENDOR_CODE outside of switch. It might
+ ** match one of the existing VENDOR_* codes.
+ */
+
+ if (vendorcode == VENDOR_CODE)
+ return VENDOR_NAME;
+#endif /* defined(VENDOR_NAME) && defined(VENDOR_CODE) */
+
+ switch (vendorcode)
+ {
+ case VENDOR_BERKELEY:
+ return "Berkeley";
+
+ case VENDOR_SUN:
+ return "Sun";
+
+ case VENDOR_HP:
+ return "HP";
+
+ case VENDOR_IBM:
+ return "IBM";
+
+ case VENDOR_SENDMAIL:
+ return "Sendmail";
+
+ default:
+ return "Unknown";
+ }
+}
+/*
+** VENDOR_PRE_DEFAULTS, VENDOR_POST_DEFAULTS -- set vendor-specific defaults
+**
+** Vendor_pre_defaults is called before reading the configuration
+** file; vendor_post_defaults is called immediately after.
+**
+** Parameters:
+** e -- the global environment to initialize.
+**
+** Returns:
+** none.
+*/
+
+#if SHARE_V1
+int DefShareUid; /* default share uid to run as -- unused??? */
+#endif /* SHARE_V1 */
+
+void
+vendor_pre_defaults(e)
+ ENVELOPE *e;
+{
+#if SHARE_V1
+ /* OTHERUID is defined in shares.h, do not be alarmed */
+ DefShareUid = OTHERUID;
+#endif /* SHARE_V1 */
+#if defined(SUN_EXTENSIONS) && defined(SUN_DEFAULT_VALUES)
+ sun_pre_defaults(e);
+#endif /* defined(SUN_EXTENSIONS) && defined(SUN_DEFAULT_VALUES) */
+#ifdef apollo
+ /*
+ ** stupid domain/os can't even open
+ ** /etc/mail/sendmail.cf without this
+ */
+
+ setuserenv("ISP", NULL);
+ setuserenv("SYSTYPE", NULL);
+#endif /* apollo */
+}
+
+
+void
+vendor_post_defaults(e)
+ ENVELOPE *e;
+{
+#ifdef __QNX__
+ char *p;
+
+ /* Makes sure the SOCK environment variable remains */
+ if (p = getextenv("SOCK"))
+ setuserenv("SOCK", p);
+#endif /* __QNX__ */
+#if defined(SUN_EXTENSIONS) && defined(SUN_DEFAULT_VALUES)
+ sun_post_defaults(e);
+#endif /* defined(SUN_EXTENSIONS) && defined(SUN_DEFAULT_VALUES) */
+}
+/*
+** VENDOR_DAEMON_SETUP -- special vendor setup needed for daemon mode
+*/
+
+void
+vendor_daemon_setup(e)
+ ENVELOPE *e;
+{
+#if HASSETLOGIN
+ (void) setlogin(RunAsUserName);
+#endif /* HASSETLOGIN */
+#if SECUREWARE
+ if (getluid() != -1)
+ {
+ usrerr("Daemon cannot have LUID");
+ finis(false, true, EX_USAGE);
+ }
+#endif /* SECUREWARE */
+}
+/*
+** VENDOR_SET_UID -- do setup for setting a user id
+**
+** This is called when we are still root.
+**
+** Parameters:
+** uid -- the uid we are about to become.
+**
+** Returns:
+** none.
+*/
+
+void
+vendor_set_uid(uid)
+ UID_T uid;
+{
+ /*
+ ** We need to setup the share groups (lnodes)
+ ** and add auditing information (luid's)
+ ** before we loose our ``root''ness.
+ */
+#if SHARE_V1
+ if (setupshares(uid, syserr) != 0)
+ syserr("Unable to set up shares");
+#endif /* SHARE_V1 */
+#if SECUREWARE
+ (void) setup_secure(uid);
+#endif /* SECUREWARE */
+}
+/*
+** VALIDATE_CONNECTION -- check connection for rationality
+**
+** If the connection is rejected, this routine should log an
+** appropriate message -- but should never issue any SMTP protocol.
+**
+** Parameters:
+** sap -- a pointer to a SOCKADDR naming the peer.
+** hostname -- the name corresponding to sap.
+** e -- the current envelope.
+**
+** Returns:
+** error message from rejection.
+** NULL if not rejected.
+*/
+
+#if TCPWRAPPERS
+# include <tcpd.h>
+
+/* tcpwrappers does no logging, but you still have to declare these -- ugh */
+int allow_severity = LOG_INFO;
+int deny_severity = LOG_NOTICE;
+#endif /* TCPWRAPPERS */
+
+char *
+validate_connection(sap, hostname, e)
+ SOCKADDR *sap;
+ char *hostname;
+ ENVELOPE *e;
+{
+#if TCPWRAPPERS
+ char *host;
+ char *addr;
+ extern int hosts_ctl();
+#endif /* TCPWRAPPERS */
+
+ if (tTd(48, 3))
+ sm_dprintf("validate_connection(%s, %s)\n",
+ hostname, anynet_ntoa(sap));
+
+ connection_rate_check(sap, e);
+ if (rscheck("check_relay", hostname, anynet_ntoa(sap),
+ e, RSF_RMCOMM|RSF_COUNT, 3, NULL, NOQID) != EX_OK)
+ {
+ static char reject[BUFSIZ*2];
+ extern char MsgBuf[];
+
+ if (tTd(48, 4))
+ sm_dprintf(" ... validate_connection: BAD (rscheck)\n");
+
+ if (strlen(MsgBuf) >= 3)
+ (void) sm_strlcpy(reject, MsgBuf, sizeof reject);
+ else
+ (void) sm_strlcpy(reject, "Access denied", sizeof reject);
+
+ return reject;
+ }
+
+#if TCPWRAPPERS
+ if (hostname[0] == '[' && hostname[strlen(hostname) - 1] == ']')
+ host = "unknown";
+ else
+ host = hostname;
+ addr = anynet_ntoa(sap);
+
+# if NETINET6
+ /* TCP/Wrappers don't want the IPv6: protocol label */
+ if (addr != NULL && sm_strncasecmp(addr, "IPv6:", 5) == 0)
+ addr += 5;
+# endif /* NETINET6 */
+
+ if (!hosts_ctl("sendmail", host, addr, STRING_UNKNOWN))
+ {
+ if (tTd(48, 4))
+ sm_dprintf(" ... validate_connection: BAD (tcpwrappers)\n");
+ if (LogLevel > 3)
+ sm_syslog(LOG_NOTICE, e->e_id,
+ "tcpwrappers (%s, %s) rejection",
+ host, addr);
+ return "Access denied";
+ }
+#endif /* TCPWRAPPERS */
+ if (tTd(48, 4))
+ sm_dprintf(" ... validate_connection: OK\n");
+ return NULL;
+}
+
+/*
+** STRTOL -- convert string to long integer
+**
+** For systems that don't have it in the C library.
+**
+** This is taken verbatim from the 4.4-Lite C library.
+*/
+
+#if NEEDSTRTOL
+
+# if defined(LIBC_SCCS) && !defined(lint)
+static char sccsid[] = "@(#)strtol.c 8.1 (Berkeley) 6/4/93";
+# endif /* defined(LIBC_SCCS) && !defined(lint) */
+
+/*
+** Convert a string to a long integer.
+**
+** Ignores `locale' stuff. Assumes that the upper and lower case
+** alphabets and digits are each contiguous.
+*/
+
+long
+strtol(nptr, endptr, base)
+ const char *nptr;
+ char **endptr;
+ register int base;
+{
+ register const char *s = nptr;
+ register unsigned long acc;
+ register int c;
+ register unsigned long cutoff;
+ register int neg = 0, any, cutlim;
+
+ /*
+ ** Skip white space and pick up leading +/- sign if any.
+ ** If base is 0, allow 0x for hex and 0 for octal, else
+ ** assume decimal; if base is already 16, allow 0x.
+ */
+ do {
+ c = *s++;
+ } while (isspace(c));
+ if (c == '-') {
+ neg = 1;
+ c = *s++;
+ } else if (c == '+')
+ c = *s++;
+ if ((base == 0 || base == 16) &&
+ c == '0' && (*s == 'x' || *s == 'X')) {
+ c = s[1];
+ s += 2;
+ base = 16;
+ }
+ if (base == 0)
+ base = c == '0' ? 8 : 10;
+
+ /*
+ ** Compute the cutoff value between legal numbers and illegal
+ ** numbers. That is the largest legal value, divided by the
+ ** base. An input number that is greater than this value, if
+ ** followed by a legal input character, is too big. One that
+ ** is equal to this value may be valid or not; the limit
+ ** between valid and invalid numbers is then based on the last
+ ** digit. For instance, if the range for longs is
+ ** [-2147483648..2147483647] and the input base is 10,
+ ** cutoff will be set to 214748364 and cutlim to either
+ ** 7 (neg==0) or 8 (neg==1), meaning that if we have accumulated
+ ** a value > 214748364, or equal but the next digit is > 7 (or 8),
+ ** the number is too big, and we will return a range error.
+ **
+ ** Set any if any `digits' consumed; make it negative to indicate
+ ** overflow.
+ */
+ cutoff = neg ? -(unsigned long) LONG_MIN : LONG_MAX;
+ cutlim = cutoff % (unsigned long) base;
+ cutoff /= (unsigned long) base;
+ for (acc = 0, any = 0;; c = *s++) {
+ if (isdigit(c))
+ c -= '0';
+ else if (isalpha(c))
+ c -= isupper(c) ? 'A' - 10 : 'a' - 10;
+ else
+ break;
+ if (c >= base)
+ break;
+ if (any < 0 || acc > cutoff || acc == cutoff && c > cutlim)
+ any = -1;
+ else {
+ any = 1;
+ acc *= base;
+ acc += c;
+ }
+ }
+ if (any < 0) {
+ acc = neg ? LONG_MIN : LONG_MAX;
+ errno = ERANGE;
+ } else if (neg)
+ acc = -acc;
+ if (endptr != 0)
+ *endptr = (char *)(any ? s - 1 : nptr);
+ return acc;
+}
+
+#endif /* NEEDSTRTOL */
+/*
+** STRSTR -- find first substring in string
+**
+** Parameters:
+** big -- the big (full) string.
+** little -- the little (sub) string.
+**
+** Returns:
+** A pointer to the first instance of little in big.
+** big if little is the null string.
+** NULL if little is not contained in big.
+*/
+
+#if NEEDSTRSTR
+
+char *
+strstr(big, little)
+ char *big;
+ char *little;
+{
+ register char *p = big;
+ int l;
+
+ if (*little == '\0')
+ return big;
+ l = strlen(little);
+
+ while ((p = strchr(p, *little)) != NULL)
+ {
+ if (strncmp(p, little, l) == 0)
+ return p;
+ p++;
+ }
+ return NULL;
+}
+
+#endif /* NEEDSTRSTR */
+/*
+** SM_GETHOSTBY{NAME,ADDR} -- compatibility routines for gethostbyXXX
+**
+** Some operating systems have wierd problems with the gethostbyXXX
+** routines. For example, Solaris versions at least through 2.3
+** don't properly deliver a canonical h_name field. This tries to
+** work around these problems.
+**
+** Support IPv6 as well as IPv4.
+*/
+
+#if NETINET6 && NEEDSGETIPNODE
+
+# ifndef AI_DEFAULT
+# define AI_DEFAULT 0 /* dummy */
+# endif /* ! AI_DEFAULT */
+# ifndef AI_ADDRCONFIG
+# define AI_ADDRCONFIG 0 /* dummy */
+# endif /* ! AI_ADDRCONFIG */
+# ifndef AI_V4MAPPED
+# define AI_V4MAPPED 0 /* dummy */
+# endif /* ! AI_V4MAPPED */
+# ifndef AI_ALL
+# define AI_ALL 0 /* dummy */
+# endif /* ! AI_ALL */
+
+static struct hostent *
+getipnodebyname(name, family, flags, err)
+ char *name;
+ int family;
+ int flags;
+ int *err;
+{
+ bool resv6 = true;
+ struct hostent *h;
+
+ if (family == AF_INET6)
+ {
+ /* From RFC2133, section 6.1 */
+ resv6 = bitset(RES_USE_INET6, _res.options);
+ _res.options |= RES_USE_INET6;
+ }
+ SM_SET_H_ERRNO(0);
+ h = gethostbyname(name);
+ if (!resv6)
+ _res.options &= ~RES_USE_INET6;
+ *err = h_errno;
+ return h;
+}
+
+static struct hostent *
+getipnodebyaddr(addr, len, family, err)
+ char *addr;
+ int len;
+ int family;
+ int *err;
+{
+ struct hostent *h;
+
+ SM_SET_H_ERRNO(0);
+ h = gethostbyaddr(addr, len, family);
+ *err = h_errno;
+ return h;
+}
+
+void
+freehostent(h)
+ struct hostent *h;
+{
+ /*
+ ** Stub routine -- if they don't have getipnodeby*(),
+ ** they probably don't have the free routine either.
+ */
+
+ return;
+}
+#endif /* NETINET6 && NEEDSGETIPNODE */
+
+struct hostent *
+sm_gethostbyname(name, family)
+ char *name;
+ int family;
+{
+ int save_errno;
+ struct hostent *h = NULL;
+#if (SOLARIS > 10000 && SOLARIS < 20400) || (defined(SOLARIS) && SOLARIS < 204) || (defined(sony_news) && defined(__svr4))
+# if SOLARIS == 20300 || SOLARIS == 203
+ static struct hostent hp;
+ static char buf[1000];
+ extern struct hostent *_switch_gethostbyname_r();
+
+ if (tTd(61, 10))
+ sm_dprintf("_switch_gethostbyname_r(%s)... ", name);
+ h = _switch_gethostbyname_r(name, &hp, buf, sizeof(buf), &h_errno);
+ save_errno = errno;
+# else /* SOLARIS == 20300 || SOLARIS == 203 */
+ extern struct hostent *__switch_gethostbyname();
+
+ if (tTd(61, 10))
+ sm_dprintf("__switch_gethostbyname(%s)... ", name);
+ h = __switch_gethostbyname(name);
+ save_errno = errno;
+# endif /* SOLARIS == 20300 || SOLARIS == 203 */
+#else /* (SOLARIS > 10000 && SOLARIS < 20400) || (defined(SOLARIS) && SOLARIS < 204) || (defined(sony_news) && defined(__svr4)) */
+ int nmaps;
+# if NETINET6
+ int flags = AI_DEFAULT|AI_ALL;
+ int err;
+# endif /* NETINET6 */
+ char *maptype[MAXMAPSTACK];
+ short mapreturn[MAXMAPACTIONS];
+ char hbuf[MAXNAME];
+
+ if (tTd(61, 10))
+ sm_dprintf("sm_gethostbyname(%s, %d)... ", name, family);
+
+# if NETINET6
+# if ADDRCONFIG_IS_BROKEN
+ flags &= ~AI_ADDRCONFIG;
+# endif /* ADDRCONFIG_IS_BROKEN */
+ h = getipnodebyname(name, family, flags, &err);
+ SM_SET_H_ERRNO(err);
+# else /* NETINET6 */
+ h = gethostbyname(name);
+# endif /* NETINET6 */
+
+ save_errno = errno;
+ if (h == NULL)
+ {
+ if (tTd(61, 10))
+ sm_dprintf("failure\n");
+
+ nmaps = switch_map_find("hosts", maptype, mapreturn);
+ while (--nmaps >= 0)
+ {
+ if (strcmp(maptype[nmaps], "nis") == 0 ||
+ strcmp(maptype[nmaps], "files") == 0)
+ break;
+ }
+
+ if (nmaps >= 0)
+ {
+ /* try short name */
+ if (strlen(name) > sizeof hbuf - 1)
+ {
+ errno = save_errno;
+ return NULL;
+ }
+ (void) sm_strlcpy(hbuf, name, sizeof hbuf);
+ (void) shorten_hostname(hbuf);
+
+ /* if it hasn't been shortened, there's no point */
+ if (strcmp(hbuf, name) != 0)
+ {
+ if (tTd(61, 10))
+ sm_dprintf("sm_gethostbyname(%s, %d)... ",
+ hbuf, family);
+
+# if NETINET6
+ h = getipnodebyname(hbuf, family, flags, &err);
+ SM_SET_H_ERRNO(err);
+ save_errno = errno;
+# else /* NETINET6 */
+ h = gethostbyname(hbuf);
+ save_errno = errno;
+# endif /* NETINET6 */
+ }
+ }
+ }
+#endif /* (SOLARIS > 10000 && SOLARIS < 20400) || (defined(SOLARIS) && SOLARIS < 204) || (defined(sony_news) && defined(__svr4)) */
+ if (tTd(61, 10))
+ {
+ if (h == NULL)
+ sm_dprintf("failure\n");
+ else
+ {
+ sm_dprintf("%s\n", h->h_name);
+ if (tTd(61, 11))
+ {
+#if NETINET6
+ struct in6_addr ia6;
+ char buf6[INET6_ADDRSTRLEN];
+#else /* NETINET6 */
+ struct in_addr ia;
+#endif /* NETINET6 */
+ size_t i;
+
+ if (h->h_aliases != NULL)
+ for (i = 0; h->h_aliases[i] != NULL;
+ i++)
+ sm_dprintf("\talias: %s\n",
+ h->h_aliases[i]);
+ for (i = 0; h->h_addr_list[i] != NULL; i++)
+ {
+ char *addr;
+
+#if NETINET6
+ memmove(&ia6, h->h_addr_list[i],
+ IN6ADDRSZ);
+ addr = anynet_ntop(&ia6,
+ buf6, sizeof buf6);
+#else /* NETINET6 */
+ memmove(&ia, h->h_addr_list[i],
+ INADDRSZ);
+ addr = (char *) inet_ntoa(ia);
+#endif /* NETINET6 */
+ if (addr != NULL)
+ sm_dprintf("\taddr: %s\n", addr);
+ }
+ }
+ }
+ }
+ errno = save_errno;
+ return h;
+}
+
+struct hostent *
+sm_gethostbyaddr(addr, len, type)
+ char *addr;
+ int len;
+ int type;
+{
+ struct hostent *hp;
+
+#if NETINET6
+ if (type == AF_INET6 &&
+ IN6_IS_ADDR_UNSPECIFIED((struct in6_addr *) addr))
+ {
+ /* Avoid reverse lookup for IPv6 unspecified address */
+ SM_SET_H_ERRNO(HOST_NOT_FOUND);
+ return NULL;
+ }
+#endif /* NETINET6 */
+
+#if (SOLARIS > 10000 && SOLARIS < 20400) || (defined(SOLARIS) && SOLARIS < 204)
+# if SOLARIS == 20300 || SOLARIS == 203
+ {
+ static struct hostent he;
+ static char buf[1000];
+ extern struct hostent *_switch_gethostbyaddr_r();
+
+ hp = _switch_gethostbyaddr_r(addr, len, type, &he,
+ buf, sizeof(buf), &h_errno);
+ }
+# else /* SOLARIS == 20300 || SOLARIS == 203 */
+ {
+ extern struct hostent *__switch_gethostbyaddr();
+
+ hp = __switch_gethostbyaddr(addr, len, type);
+ }
+# endif /* SOLARIS == 20300 || SOLARIS == 203 */
+#else /* (SOLARIS > 10000 && SOLARIS < 20400) || (defined(SOLARIS) && SOLARIS < 204) */
+# if NETINET6
+ {
+ int err;
+
+ hp = getipnodebyaddr(addr, len, type, &err);
+ SM_SET_H_ERRNO(err);
+ }
+# else /* NETINET6 */
+ hp = gethostbyaddr(addr, len, type);
+# endif /* NETINET6 */
+#endif /* (SOLARIS > 10000 && SOLARIS < 20400) || (defined(SOLARIS) && SOLARIS < 204) */
+ return hp;
+}
+/*
+** SM_GETPW{NAM,UID} -- wrapper for getpwnam and getpwuid
+*/
+
+struct passwd *
+sm_getpwnam(user)
+ char *user;
+{
+#ifdef _AIX4
+ extern struct passwd *_getpwnam_shadow(const char *, const int);
+
+ return _getpwnam_shadow(user, 0);
+#else /* _AIX4 */
+ return getpwnam(user);
+#endif /* _AIX4 */
+}
+
+struct passwd *
+sm_getpwuid(uid)
+ UID_T uid;
+{
+#if defined(_AIX4) && 0
+ extern struct passwd *_getpwuid_shadow(const int, const int);
+
+ return _getpwuid_shadow(uid,0);
+#else /* defined(_AIX4) && 0 */
+ return getpwuid(uid);
+#endif /* defined(_AIX4) && 0 */
+}
+/*
+** SECUREWARE_SETUP_SECURE -- Convex SecureWare setup
+**
+** Set up the trusted computing environment for C2 level security
+** under SecureWare.
+**
+** Parameters:
+** uid -- uid of the user to initialize in the TCB
+**
+** Returns:
+** none
+**
+** Side Effects:
+** Initialized the user in the trusted computing base
+*/
+
+#if SECUREWARE
+
+# include <sys/security.h>
+# include <prot.h>
+
+void
+secureware_setup_secure(uid)
+ UID_T uid;
+{
+ int rc;
+
+ if (getluid() != -1)
+ return;
+
+ if ((rc = set_secure_info(uid)) != SSI_GOOD_RETURN)
+ {
+ switch (rc)
+ {
+ case SSI_NO_PRPW_ENTRY:
+ syserr("No protected passwd entry, uid = %d",
+ (int) uid);
+ break;
+
+ case SSI_LOCKED:
+ syserr("Account has been disabled, uid = %d",
+ (int) uid);
+ break;
+
+ case SSI_RETIRED:
+ syserr("Account has been retired, uid = %d",
+ (int) uid);
+ break;
+
+ case SSI_BAD_SET_LUID:
+ syserr("Could not set LUID, uid = %d", (int) uid);
+ break;
+
+ case SSI_BAD_SET_PRIVS:
+ syserr("Could not set kernel privs, uid = %d",
+ (int) uid);
+
+ default:
+ syserr("Unknown return code (%d) from set_secure_info(%d)",
+ rc, (int) uid);
+ break;
+ }
+ finis(false, true, EX_NOPERM);
+ }
+}
+#endif /* SECUREWARE */
+/*
+** ADD_HOSTNAMES -- Add a hostname to class 'w' based on IP address
+**
+** Add hostnames to class 'w' based on the IP address read from
+** the network interface.
+**
+** Parameters:
+** sa -- a pointer to a SOCKADDR containing the address
+**
+** Returns:
+** 0 if successful, -1 if host lookup fails.
+*/
+
+static int
+add_hostnames(sa)
+ SOCKADDR *sa;
+{
+ struct hostent *hp;
+ char **ha;
+ char hnb[MAXHOSTNAMELEN];
+
+ /* lookup name with IP address */
+ switch (sa->sa.sa_family)
+ {
+#if NETINET
+ case AF_INET:
+ hp = sm_gethostbyaddr((char *) &sa->sin.sin_addr,
+ sizeof(sa->sin.sin_addr),
+ sa->sa.sa_family);
+ break;
+#endif /* NETINET */
+
+#if NETINET6
+ case AF_INET6:
+ hp = sm_gethostbyaddr((char *) &sa->sin6.sin6_addr,
+ sizeof(sa->sin6.sin6_addr),
+ sa->sa.sa_family);
+ break;
+#endif /* NETINET6 */
+
+ default:
+ /* Give warning about unsupported family */
+ if (LogLevel > 3)
+ sm_syslog(LOG_WARNING, NOQID,
+ "Unsupported address family %d: %.100s",
+ sa->sa.sa_family, anynet_ntoa(sa));
+ return -1;
+ }
+
+ if (hp == NULL)
+ {
+ int save_errno = errno;
+
+ if (LogLevel > 3 &&
+#if NETINET6
+ !(sa->sa.sa_family == AF_INET6 &&
+ IN6_IS_ADDR_LINKLOCAL(&sa->sin6.sin6_addr)) &&
+#endif /* NETINET6 */
+ true)
+ sm_syslog(LOG_WARNING, NOQID,
+ "gethostbyaddr(%.100s) failed: %d",
+ anynet_ntoa(sa),
+#if NAMED_BIND
+ h_errno
+#else /* NAMED_BIND */
+ -1
+#endif /* NAMED_BIND */
+ );
+ errno = save_errno;
+ return -1;
+ }
+
+ /* save its cname */
+ if (!wordinclass((char *) hp->h_name, 'w'))
+ {
+ setclass('w', (char *) hp->h_name);
+ if (tTd(0, 4))
+ sm_dprintf("\ta.k.a.: %s\n", hp->h_name);
+
+ if (sm_snprintf(hnb, sizeof hnb, "[%s]", hp->h_name) < sizeof hnb
+ && !wordinclass((char *) hnb, 'w'))
+ setclass('w', hnb);
+ }
+ else
+ {
+ if (tTd(0, 43))
+ sm_dprintf("\ta.k.a.: %s (already in $=w)\n", hp->h_name);
+ }
+
+ /* save all it aliases name */
+ for (ha = hp->h_aliases; ha != NULL && *ha != NULL; ha++)
+ {
+ if (!wordinclass(*ha, 'w'))
+ {
+ setclass('w', *ha);
+ if (tTd(0, 4))
+ sm_dprintf("\ta.k.a.: %s\n", *ha);
+ if (sm_snprintf(hnb, sizeof hnb,
+ "[%s]", *ha) < sizeof hnb &&
+ !wordinclass((char *) hnb, 'w'))
+ setclass('w', hnb);
+ }
+ else
+ {
+ if (tTd(0, 43))
+ sm_dprintf("\ta.k.a.: %s (already in $=w)\n",
+ *ha);
+ }
+ }
+#if NETINET6
+ freehostent(hp);
+#endif /* NETINET6 */
+ return 0;
+}
+/*
+** LOAD_IF_NAMES -- load interface-specific names into $=w
+**
+** Parameters:
+** none.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Loads $=w with the names of all the interfaces.
+*/
+
+#if !NETINET
+# define SIOCGIFCONF_IS_BROKEN 1 /* XXX */
+#endif /* !NETINET */
+
+#if defined(SIOCGIFCONF) && !SIOCGIFCONF_IS_BROKEN
+struct rtentry;
+struct mbuf;
+# ifndef SUNOS403
+# include <sys/time.h>
+# endif /* ! SUNOS403 */
+# if (_AIX4 >= 40300) && !defined(_NET_IF_H)
+# undef __P
+# endif /* (_AIX4 >= 40300) && !defined(_NET_IF_H) */
+# include <net/if.h>
+#endif /* defined(SIOCGIFCONF) && !SIOCGIFCONF_IS_BROKEN */
+
+void
+load_if_names()
+{
+# if NETINET6 && defined(SIOCGLIFCONF)
+# ifdef __hpux
+
+ /*
+ ** Unfortunately, HP has changed all of the structures,
+ ** making life difficult for implementors.
+ */
+
+# define lifconf if_laddrconf
+# define lifc_len iflc_len
+# define lifc_buf iflc_buf
+# define lifreq if_laddrreq
+# define lifr_addr iflr_addr
+# define lifr_name iflr_name
+# define lifr_flags iflr_flags
+# define ss_family sa_family
+# undef SIOCGLIFNUM
+# endif /* __hpux */
+
+ int s;
+ int i;
+ size_t len;
+ int numifs;
+ char *buf;
+ struct lifconf lifc;
+# ifdef SIOCGLIFNUM
+ struct lifnum lifn;
+# endif /* SIOCGLIFNUM */
+
+ s = socket(InetMode, SOCK_DGRAM, 0);
+ if (s == -1)
+ return;
+
+ /* get the list of known IP address from the kernel */
+# ifdef __hpux
+ i = ioctl(s, SIOCGIFNUM, (char *) &numifs);
+# endif /* __hpux */
+# ifdef SIOCGLIFNUM
+ lifn.lifn_family = AF_UNSPEC;
+ lifn.lifn_flags = 0;
+ i = ioctl(s, SIOCGLIFNUM, (char *)&lifn);
+ numifs = lifn.lifn_count;
+# endif /* SIOCGLIFNUM */
+
+# if defined(__hpux) || defined(SIOCGLIFNUM)
+ if (i < 0)
+ {
+ /* can't get number of interfaces -- fall back */
+ if (tTd(0, 4))
+ sm_dprintf("SIOCGLIFNUM failed: %s\n",
+ sm_errstring(errno));
+ numifs = -1;
+ }
+ else if (tTd(0, 42))
+ sm_dprintf("system has %d interfaces\n", numifs);
+ if (numifs < 0)
+# endif /* defined(__hpux) || defined(SIOCGLIFNUM) */
+ numifs = MAXINTERFACES;
+
+ if (numifs <= 0)
+ {
+ (void) close(s);
+ return;
+ }
+
+ len = lifc.lifc_len = numifs * sizeof (struct lifreq);
+ buf = lifc.lifc_buf = xalloc(lifc.lifc_len);
+# ifndef __hpux
+ lifc.lifc_family = AF_UNSPEC;
+ lifc.lifc_flags = 0;
+# endif /* ! __hpux */
+ if (ioctl(s, SIOCGLIFCONF, (char *)&lifc) < 0)
+ {
+ if (tTd(0, 4))
+ sm_dprintf("SIOCGLIFCONF failed: %s\n",
+ sm_errstring(errno));
+ (void) close(s);
+ sm_free(buf);
+ return;
+ }
+
+ /* scan the list of IP address */
+ if (tTd(0, 40))
+ sm_dprintf("scanning for interface specific names, lifc_len=%ld\n",
+ (long) len);
+
+ for (i = 0; i < len && i >= 0; )
+ {
+ int flags;
+ struct lifreq *ifr = (struct lifreq *)&buf[i];
+ SOCKADDR *sa = (SOCKADDR *) &ifr->lifr_addr;
+ int af = ifr->lifr_addr.ss_family;
+ char *addr;
+ char *name;
+ struct in6_addr ia6;
+ struct in_addr ia;
+# ifdef SIOCGLIFFLAGS
+ struct lifreq ifrf;
+# endif /* SIOCGLIFFLAGS */
+ char ip_addr[256];
+ char buf6[INET6_ADDRSTRLEN];
+
+ /*
+ ** We must close and recreate the socket each time
+ ** since we don't know what type of socket it is now
+ ** (each status function may change it).
+ */
+
+ (void) close(s);
+
+ s = socket(af, SOCK_DGRAM, 0);
+ if (s == -1)
+ {
+ sm_free(buf); /* XXX */
+ return;
+ }
+
+ /*
+ ** If we don't have a complete ifr structure,
+ ** don't try to use it.
+ */
+
+ if ((len - i) < sizeof *ifr)
+ break;
+
+# ifdef BSD4_4_SOCKADDR
+ if (sa->sa.sa_len > sizeof ifr->lifr_addr)
+ i += sizeof ifr->lifr_name + sa->sa.sa_len;
+ else
+# endif /* BSD4_4_SOCKADDR */
+ i += sizeof *ifr;
+
+ if (tTd(0, 20))
+ sm_dprintf("%s\n", anynet_ntoa(sa));
+
+ if (af != AF_INET && af != AF_INET6)
+ continue;
+
+# ifdef SIOCGLIFFLAGS
+ memset(&ifrf, '\0', sizeof(struct lifreq));
+ (void) sm_strlcpy(ifrf.lifr_name, ifr->lifr_name,
+ sizeof(ifrf.lifr_name));
+ if (ioctl(s, SIOCGLIFFLAGS, (char *) &ifrf) < 0)
+ {
+ if (tTd(0, 4))
+ sm_dprintf("SIOCGLIFFLAGS failed: %s\n",
+ sm_errstring(errno));
+ continue;
+ }
+
+ name = ifr->lifr_name;
+ flags = ifrf.lifr_flags;
+
+ if (tTd(0, 41))
+ sm_dprintf("\tflags: %lx\n", (unsigned long) flags);
+
+ if (!bitset(IFF_UP, flags))
+ continue;
+# endif /* SIOCGLIFFLAGS */
+
+ ip_addr[0] = '\0';
+
+ /* extract IP address from the list*/
+ switch (af)
+ {
+ case AF_INET6:
+# ifdef __KAME__
+ /* convert into proper scoped address */
+ if ((IN6_IS_ADDR_LINKLOCAL(&sa->sin6.sin6_addr) ||
+ IN6_IS_ADDR_SITELOCAL(&sa->sin6.sin6_addr)) &&
+ sa->sin6.sin6_scope_id == 0)
+ {
+ struct in6_addr *ia6p;
+
+ ia6p = &sa->sin6.sin6_addr;
+ sa->sin6.sin6_scope_id = ntohs(ia6p->s6_addr[3] |
+ ((unsigned int)ia6p->s6_addr[2] << 8));
+ ia6p->s6_addr[2] = ia6p->s6_addr[3] = 0;
+ }
+# endif /* __KAME__ */
+ ia6 = sa->sin6.sin6_addr;
+ if (IN6_IS_ADDR_UNSPECIFIED(&ia6))
+ {
+ addr = anynet_ntop(&ia6, buf6, sizeof buf6);
+ message("WARNING: interface %s is UP with %s address",
+ name, addr == NULL ? "(NULL)" : addr);
+ continue;
+ }
+
+ /* save IP address in text from */
+ addr = anynet_ntop(&ia6, buf6, sizeof buf6);
+ if (addr != NULL)
+ (void) sm_snprintf(ip_addr, sizeof ip_addr,
+ "[%.*s]",
+ (int) sizeof ip_addr - 3,
+ addr);
+ break;
+
+ case AF_INET:
+ ia = sa->sin.sin_addr;
+ if (ia.s_addr == INADDR_ANY ||
+ ia.s_addr == INADDR_NONE)
+ {
+ message("WARNING: interface %s is UP with %s address",
+ name, inet_ntoa(ia));
+ continue;
+ }
+
+ /* save IP address in text from */
+ (void) sm_snprintf(ip_addr, sizeof ip_addr, "[%.*s]",
+ (int) sizeof ip_addr - 3, inet_ntoa(ia));
+ break;
+ }
+
+ if (*ip_addr == '\0')
+ continue;
+
+ if (!wordinclass(ip_addr, 'w'))
+ {
+ setclass('w', ip_addr);
+ if (tTd(0, 4))
+ sm_dprintf("\ta.k.a.: %s\n", ip_addr);
+ }
+
+# ifdef SIOCGLIFFLAGS
+ /* skip "loopback" interface "lo" */
+ if (DontProbeInterfaces == DPI_SKIPLOOPBACK &&
+ bitset(IFF_LOOPBACK, flags))
+ continue;
+# endif /* SIOCGLIFFLAGS */
+ (void) add_hostnames(sa);
+ }
+ sm_free(buf); /* XXX */
+ (void) close(s);
+# else /* NETINET6 && defined(SIOCGLIFCONF) */
+# if defined(SIOCGIFCONF) && !SIOCGIFCONF_IS_BROKEN
+ int s;
+ int i;
+ struct ifconf ifc;
+ int numifs;
+
+ s = socket(AF_INET, SOCK_DGRAM, 0);
+ if (s == -1)
+ return;
+
+ /* get the list of known IP address from the kernel */
+# if defined(SIOCGIFNUM) && !SIOCGIFNUM_IS_BROKEN
+ if (ioctl(s, SIOCGIFNUM, (char *) &numifs) < 0)
+ {
+ /* can't get number of interfaces -- fall back */
+ if (tTd(0, 4))
+ sm_dprintf("SIOCGIFNUM failed: %s\n",
+ sm_errstring(errno));
+ numifs = -1;
+ }
+ else if (tTd(0, 42))
+ sm_dprintf("system has %d interfaces\n", numifs);
+ if (numifs < 0)
+# endif /* defined(SIOCGIFNUM) && !SIOCGIFNUM_IS_BROKEN */
+ numifs = MAXINTERFACES;
+
+ if (numifs <= 0)
+ {
+ (void) close(s);
+ return;
+ }
+ ifc.ifc_len = numifs * sizeof (struct ifreq);
+ ifc.ifc_buf = xalloc(ifc.ifc_len);
+ if (ioctl(s, SIOCGIFCONF, (char *)&ifc) < 0)
+ {
+ if (tTd(0, 4))
+ sm_dprintf("SIOCGIFCONF failed: %s\n",
+ sm_errstring(errno));
+ (void) close(s);
+ return;
+ }
+
+ /* scan the list of IP address */
+ if (tTd(0, 40))
+ sm_dprintf("scanning for interface specific names, ifc_len=%d\n",
+ ifc.ifc_len);
+
+ for (i = 0; i < ifc.ifc_len && i >= 0; )
+ {
+ int af;
+ struct ifreq *ifr = (struct ifreq *) &ifc.ifc_buf[i];
+ SOCKADDR *sa = (SOCKADDR *) &ifr->ifr_addr;
+# if NETINET6
+ char *addr;
+ struct in6_addr ia6;
+# endif /* NETINET6 */
+ struct in_addr ia;
+# ifdef SIOCGIFFLAGS
+ struct ifreq ifrf;
+# endif /* SIOCGIFFLAGS */
+ char ip_addr[256];
+# if NETINET6
+ char buf6[INET6_ADDRSTRLEN];
+# endif /* NETINET6 */
+
+ /*
+ ** If we don't have a complete ifr structure,
+ ** don't try to use it.
+ */
+
+ if ((ifc.ifc_len - i) < sizeof *ifr)
+ break;
+
+# ifdef BSD4_4_SOCKADDR
+ if (sa->sa.sa_len > sizeof ifr->ifr_addr)
+ i += sizeof ifr->ifr_name + sa->sa.sa_len;
+ else
+# endif /* BSD4_4_SOCKADDR */
+ i += sizeof *ifr;
+
+ if (tTd(0, 20))
+ sm_dprintf("%s\n", anynet_ntoa(sa));
+
+ af = ifr->ifr_addr.sa_family;
+ if (af != AF_INET
+# if NETINET6
+ && af != AF_INET6
+# endif /* NETINET6 */
+ )
+ continue;
+
+# ifdef SIOCGIFFLAGS
+ memset(&ifrf, '\0', sizeof(struct ifreq));
+ (void) sm_strlcpy(ifrf.ifr_name, ifr->ifr_name,
+ sizeof(ifrf.ifr_name));
+ (void) ioctl(s, SIOCGIFFLAGS, (char *) &ifrf);
+ if (tTd(0, 41))
+ sm_dprintf("\tflags: %lx\n",
+ (unsigned long) ifrf.ifr_flags);
+# define IFRFREF ifrf
+# else /* SIOCGIFFLAGS */
+# define IFRFREF (*ifr)
+# endif /* SIOCGIFFLAGS */
+
+ if (!bitset(IFF_UP, IFRFREF.ifr_flags))
+ continue;
+
+ ip_addr[0] = '\0';
+
+ /* extract IP address from the list*/
+ switch (af)
+ {
+ case AF_INET:
+ ia = sa->sin.sin_addr;
+ if (ia.s_addr == INADDR_ANY ||
+ ia.s_addr == INADDR_NONE)
+ {
+ message("WARNING: interface %s is UP with %s address",
+ ifr->ifr_name, inet_ntoa(ia));
+ continue;
+ }
+
+ /* save IP address in text from */
+ (void) sm_snprintf(ip_addr, sizeof ip_addr, "[%.*s]",
+ (int) sizeof ip_addr - 3,
+ inet_ntoa(ia));
+ break;
+
+# if NETINET6
+ case AF_INET6:
+# ifdef __KAME__
+ /* convert into proper scoped address */
+ if ((IN6_IS_ADDR_LINKLOCAL(&sa->sin6.sin6_addr) ||
+ IN6_IS_ADDR_SITELOCAL(&sa->sin6.sin6_addr)) &&
+ sa->sin6.sin6_scope_id == 0)
+ {
+ struct in6_addr *ia6p;
+
+ ia6p = &sa->sin6.sin6_addr;
+ sa->sin6.sin6_scope_id = ntohs(ia6p->s6_addr[3] |
+ ((unsigned int)ia6p->s6_addr[2] << 8));
+ ia6p->s6_addr[2] = ia6p->s6_addr[3] = 0;
+ }
+# endif /* __KAME__ */
+ ia6 = sa->sin6.sin6_addr;
+ if (IN6_IS_ADDR_UNSPECIFIED(&ia6))
+ {
+ addr = anynet_ntop(&ia6, buf6, sizeof buf6);
+ message("WARNING: interface %s is UP with %s address",
+ ifr->ifr_name,
+ addr == NULL ? "(NULL)" : addr);
+ continue;
+ }
+
+ /* save IP address in text from */
+ addr = anynet_ntop(&ia6, buf6, sizeof buf6);
+ if (addr != NULL)
+ (void) sm_snprintf(ip_addr, sizeof ip_addr,
+ "[%.*s]",
+ (int) sizeof ip_addr - 3,
+ addr);
+ break;
+
+# endif /* NETINET6 */
+ }
+
+ if (ip_addr[0] == '\0')
+ continue;
+
+ if (!wordinclass(ip_addr, 'w'))
+ {
+ setclass('w', ip_addr);
+ if (tTd(0, 4))
+ sm_dprintf("\ta.k.a.: %s\n", ip_addr);
+ }
+
+ /* skip "loopback" interface "lo" */
+ if (DontProbeInterfaces == DPI_SKIPLOOPBACK &&
+ bitset(IFF_LOOPBACK, IFRFREF.ifr_flags))
+ continue;
+
+ (void) add_hostnames(sa);
+ }
+ sm_free(ifc.ifc_buf); /* XXX */
+ (void) close(s);
+# undef IFRFREF
+# endif /* defined(SIOCGIFCONF) && !SIOCGIFCONF_IS_BROKEN */
+# endif /* NETINET6 && defined(SIOCGLIFCONF) */
+}
+/*
+** ISLOOPBACK -- is socket address in the loopback net?
+**
+** Parameters:
+** sa -- socket address.
+**
+** Returns:
+** true -- is socket address in the loopback net?
+** false -- otherwise
+**
+*/
+
+bool
+isloopback(sa)
+ SOCKADDR sa;
+{
+#if NETINET6
+ if (IN6_IS_ADDR_LOOPBACK(&sa.sin6.sin6_addr))
+ return true;
+#else /* NETINET6 */
+ /* XXX how to correctly extract IN_LOOPBACKNET part? */
+ if (((ntohl(sa.sin.sin_addr.s_addr) & IN_CLASSA_NET)
+ >> IN_CLASSA_NSHIFT) == IN_LOOPBACKNET)
+ return true;
+#endif /* NETINET6 */
+ return false;
+}
+/*
+** GET_NUM_PROCS_ONLINE -- return the number of processors currently online
+**
+** Parameters:
+** none.
+**
+** Returns:
+** The number of processors online.
+*/
+
+static int
+get_num_procs_online()
+{
+ int nproc = 0;
+
+#ifdef USESYSCTL
+# if defined(CTL_HW) && defined(HW_NCPU)
+ size_t sz;
+ int mib[2];
+
+ mib[0] = CTL_HW;
+ mib[1] = HW_NCPU;
+ sz = (size_t) sizeof nproc;
+ (void) sysctl(mib, 2, &nproc, &sz, NULL, 0);
+# endif /* defined(CTL_HW) && defined(HW_NCPU) */
+#else /* USESYSCTL */
+# ifdef _SC_NPROCESSORS_ONLN
+ nproc = (int) sysconf(_SC_NPROCESSORS_ONLN);
+# else /* _SC_NPROCESSORS_ONLN */
+# ifdef __hpux
+# include <sys/pstat.h>
+ struct pst_dynamic psd;
+
+ if (pstat_getdynamic(&psd, sizeof(psd), (size_t)1, 0) != -1)
+ nproc = psd.psd_proc_cnt;
+# endif /* __hpux */
+# endif /* _SC_NPROCESSORS_ONLN */
+#endif /* USESYSCTL */
+
+ if (nproc <= 0)
+ nproc = 1;
+ return nproc;
+}
+/*
+** SM_CLOSEFROM -- close file descriptors
+**
+** Parameters:
+** lowest -- first fd to close
+** highest -- last fd + 1 to close
+**
+** Returns:
+** none
+*/
+
+void
+sm_closefrom(lowest, highest)
+ int lowest, highest;
+{
+#if HASCLOSEFROM
+ closefrom(lowest);
+#else /* HASCLOSEFROM */
+ int i;
+
+ for (i = lowest; i < highest; i++)
+ (void) close(i);
+#endif /* HASCLOSEFROM */
+}
+#if HASFDWALK
+/*
+** CLOSEFD_WALK -- walk fd's arranging to close them
+** Callback for fdwalk()
+**
+** Parameters:
+** lowest -- first fd to arrange to be closed
+** fd -- fd to arrange to be closed
+**
+** Returns:
+** zero
+*/
+
+static int
+closefd_walk(lowest, fd)
+ void *lowest;
+ int fd;
+{
+ if (fd >= *(int *)lowest)
+ (void) fcntl(fd, F_SETFD, FD_CLOEXEC);
+ return 0;
+}
+#endif /* HASFDWALK */
+/*
+** SM_CLOSE_ON_EXEC -- arrange for file descriptors to be closed
+**
+** Parameters:
+** lowest -- first fd to arrange to be closed
+** highest -- last fd + 1 to arrange to be closed
+**
+** Returns:
+** none
+*/
+
+void
+sm_close_on_exec(highest, lowest)
+ int highest, lowest;
+{
+#if HASFDWALK
+ (void) fdwalk(closefd_walk, &lowest);
+#else /* HASFDWALK */
+ int i, j;
+
+ for (i = lowest; i < highest; i++)
+ {
+ if ((j = fcntl(i, F_GETFD, 0)) != -1)
+ (void) fcntl(i, F_SETFD, j | FD_CLOEXEC);
+ }
+#endif /* HASFDWALK */
+}
+/*
+** SEED_RANDOM -- seed the random number generator
+**
+** Parameters:
+** none
+**
+** Returns:
+** none
+*/
+
+void
+seed_random()
+{
+#if HASSRANDOMDEV
+ srandomdev();
+#else /* HASSRANDOMDEV */
+ long seed;
+ struct timeval t;
+
+ seed = (long) CurrentPid;
+ if (gettimeofday(&t, NULL) >= 0)
+ seed += t.tv_sec + t.tv_usec;
+
+# if HASRANDOM
+ (void) srandom(seed);
+# else /* HASRANDOM */
+ (void) srand((unsigned int) seed);
+# endif /* HASRANDOM */
+#endif /* HASSRANDOMDEV */
+}
+/*
+** SM_SYSLOG -- syslog wrapper to keep messages under SYSLOG_BUFSIZE
+**
+** Parameters:
+** level -- syslog level
+** id -- envelope ID or NULL (NOQUEUE)
+** fmt -- format string
+** arg... -- arguments as implied by fmt.
+**
+** Returns:
+** none
+*/
+
+/* VARARGS3 */
+void
+#ifdef __STDC__
+sm_syslog(int level, const char *id, const char *fmt, ...)
+#else /* __STDC__ */
+sm_syslog(level, id, fmt, va_alist)
+ int level;
+ const char *id;
+ const char *fmt;
+ va_dcl
+#endif /* __STDC__ */
+{
+ static char *buf = NULL;
+ static size_t bufsize;
+ char *begin, *end;
+ int save_errno;
+ int seq = 1;
+ int idlen;
+ char buf0[MAXLINE];
+ char *newstring;
+ extern int SyslogPrefixLen;
+ SM_VA_LOCAL_DECL
+
+ save_errno = errno;
+ if (id == NULL)
+ {
+ id = "NOQUEUE";
+ idlen = strlen(id) + SyslogPrefixLen;
+ }
+ else if (strcmp(id, NOQID) == 0)
+ {
+ id = "";
+ idlen = SyslogPrefixLen;
+ }
+ else
+ idlen = strlen(id) + SyslogPrefixLen;
+
+ if (buf == NULL)
+ {
+ buf = buf0;
+ bufsize = sizeof buf0;
+ }
+
+ for (;;)
+ {
+ int n;
+
+ /* print log message into buf */
+ SM_VA_START(ap, fmt);
+ n = sm_vsnprintf(buf, bufsize, fmt, ap);
+ SM_VA_END(ap);
+ SM_ASSERT(n > 0);
+ if (n < bufsize)
+ break;
+
+ /* String too small, redo with correct size */
+ bufsize = n + 1;
+ if (buf != buf0)
+ {
+ sm_free(buf);
+ buf = NULL;
+ }
+ buf = sm_malloc_x(bufsize);
+ }
+
+ /* clean up buf after it has been expanded with args */
+ newstring = str2prt(buf);
+ if ((strlen(newstring) + idlen + 1) < SYSLOG_BUFSIZE)
+ {
+#if LOG
+ if (*id == '\0')
+ syslog(level, "%s", newstring);
+ else
+ syslog(level, "%s: %s", id, newstring);
+#else /* LOG */
+ /*XXX should do something more sensible */
+ if (*id == '\0')
+ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, "%s\n",
+ newstring);
+ else
+ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ "%s: %s\n", id, newstring);
+#endif /* LOG */
+ if (buf == buf0)
+ buf = NULL;
+ errno = save_errno;
+ return;
+ }
+
+/*
+** additional length for splitting: " ..." + 3, where 3 is magic to
+** have some data for the next entry.
+*/
+
+#define SL_SPLIT 7
+
+ begin = newstring;
+ idlen += 5; /* strlen("[999]"), see below */
+ while (*begin != '\0' &&
+ (strlen(begin) + idlen) > SYSLOG_BUFSIZE)
+ {
+ char save;
+
+ if (seq >= 999)
+ {
+ /* Too many messages */
+ break;
+ }
+ end = begin + SYSLOG_BUFSIZE - idlen - SL_SPLIT;
+ while (end > begin)
+ {
+ /* Break on comma or space */
+ if (*end == ',' || *end == ' ')
+ {
+ end++; /* Include separator */
+ break;
+ }
+ end--;
+ }
+ /* No separator, break midstring... */
+ if (end == begin)
+ end = begin + SYSLOG_BUFSIZE - idlen - SL_SPLIT;
+ save = *end;
+ *end = 0;
+#if LOG
+ syslog(level, "%s[%d]: %s ...", id, seq++, begin);
+#else /* LOG */
+ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ "%s[%d]: %s ...\n", id, seq++, begin);
+#endif /* LOG */
+ *end = save;
+ begin = end;
+ }
+ if (seq >= 999)
+#if LOG
+ syslog(level, "%s[%d]: log terminated, too many parts",
+ id, seq);
+#else /* LOG */
+ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ "%s[%d]: log terminated, too many parts\n", id, seq);
+#endif /* LOG */
+ else if (*begin != '\0')
+#if LOG
+ syslog(level, "%s[%d]: %s", id, seq, begin);
+#else /* LOG */
+ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ "%s[%d]: %s\n", id, seq, begin);
+#endif /* LOG */
+ if (buf == buf0)
+ buf = NULL;
+ errno = save_errno;
+}
+/*
+** HARD_SYSLOG -- call syslog repeatedly until it works
+**
+** Needed on HP-UX, which apparently doesn't guarantee that
+** syslog succeeds during interrupt handlers.
+*/
+
+#if defined(__hpux) && !defined(HPUX11)
+
+# define MAXSYSLOGTRIES 100
+# undef syslog
+# ifdef V4FS
+# define XCNST const
+# define CAST (const char *)
+# else /* V4FS */
+# define XCNST
+# define CAST
+# endif /* V4FS */
+
+void
+# ifdef __STDC__
+hard_syslog(int pri, XCNST char *msg, ...)
+# else /* __STDC__ */
+hard_syslog(pri, msg, va_alist)
+ int pri;
+ XCNST char *msg;
+ va_dcl
+# endif /* __STDC__ */
+{
+ int i;
+ char buf[SYSLOG_BUFSIZE];
+ SM_VA_LOCAL_DECL
+
+ SM_VA_START(ap, msg);
+ (void) sm_vsnprintf(buf, sizeof buf, msg, ap);
+ SM_VA_END(ap);
+
+ for (i = MAXSYSLOGTRIES; --i >= 0 && syslog(pri, CAST "%s", buf) < 0; )
+ continue;
+}
+
+# undef CAST
+#endif /* defined(__hpux) && !defined(HPUX11) */
+#if NEEDLOCAL_HOSTNAME_LENGTH
+/*
+** LOCAL_HOSTNAME_LENGTH
+**
+** This is required to get sendmail to compile against BIND 4.9.x
+** on Ultrix.
+**
+** Unfortunately, a Compaq Y2K patch kit provides it without
+** bumping __RES in /usr/include/resolv.h so we can't automatically
+** figure out whether it is needed.
+*/
+
+int
+local_hostname_length(hostname)
+ char *hostname;
+{
+ size_t len_host, len_domain;
+
+ if (!*_res.defdname)
+ res_init();
+ len_host = strlen(hostname);
+ len_domain = strlen(_res.defdname);
+ if (len_host > len_domain &&
+ (sm_strcasecmp(hostname + len_host - len_domain,
+ _res.defdname) == 0) &&
+ hostname[len_host - len_domain - 1] == '.')
+ return len_host - len_domain - 1;
+ else
+ return 0;
+}
+#endif /* NEEDLOCAL_HOSTNAME_LENGTH */
+
+#if NEEDLINK
+/*
+** LINK -- clone a file
+**
+** Some OS's lacks link() and hard links. Since sendmail is using
+** link() as an efficient way to clone files, this implementation
+** will simply do a file copy.
+**
+** NOTE: This link() replacement is not a generic replacement as it
+** does not handle all of the semantics of the real link(2).
+**
+** Parameters:
+** source -- pathname of existing file.
+** target -- pathname of link (clone) to be created.
+**
+** Returns:
+** 0 -- success.
+** -1 -- failure, see errno for details.
+*/
+
+int
+link(source, target)
+ const char *source;
+ const char *target;
+{
+ int save_errno;
+ int sff;
+ int src = -1, dst = -1;
+ ssize_t readlen;
+ ssize_t writelen;
+ char buf[BUFSIZ];
+ struct stat st;
+
+ sff = SFF_REGONLY|SFF_OPENASROOT;
+ if (DontLockReadFiles)
+ sff |= SFF_NOLOCK;
+
+ /* Open the original file */
+ src = safeopen((char *)source, O_RDONLY, 0, sff);
+ if (src < 0)
+ goto fail;
+
+ /* Obtain the size and the mode */
+ if (fstat(src, &st) < 0)
+ goto fail;
+
+ /* Create the duplicate copy */
+ sff &= ~SFF_NOLOCK;
+ sff |= SFF_CREAT;
+ dst = safeopen((char *)target, O_CREAT|O_EXCL|O_WRONLY,
+ st.st_mode, sff);
+ if (dst < 0)
+ goto fail;
+
+ /* Copy all of the bytes one buffer at a time */
+ while ((readlen = read(src, &buf, sizeof(buf))) > 0)
+ {
+ ssize_t left = readlen;
+ char *p = buf;
+
+ while (left > 0 &&
+ (writelen = write(dst, p, (size_t) left)) >= 0)
+ {
+ left -= writelen;
+ p += writelen;
+ }
+ if (writelen < 0)
+ break;
+ }
+
+ /* Any trouble reading? */
+ if (readlen < 0 || writelen < 0)
+ goto fail;
+
+ /* Close the input file */
+ if (close(src) < 0)
+ {
+ src = -1;
+ goto fail;
+ }
+ src = -1;
+
+ /* Close the output file */
+ if (close(dst) < 0)
+ {
+ /* don't set dst = -1 here so we unlink the file */
+ goto fail;
+ }
+
+ /* Success */
+ return 0;
+
+ fail:
+ save_errno = errno;
+ if (src >= 0)
+ (void) close(src);
+ if (dst >= 0)
+ {
+ (void) unlink(target);
+ (void) close(dst);
+ }
+ errno = save_errno;
+ return -1;
+}
+#endif /* NEEDLINK */
+
+/*
+** Compile-Time options
+*/
+
+char *CompileOptions[] =
+{
+#if ALLOW_255
+ "ALLOW_255",
+#endif /* ALLOW_255 */
+#if NAMED_BIND
+# if DNSMAP
+ "DNSMAP",
+# endif /* DNSMAP */
+#endif /* NAMED_BIND */
+#if EGD
+ "EGD",
+#endif /* EGD */
+#if HESIOD
+ "HESIOD",
+#endif /* HESIOD */
+#if HES_GETMAILHOST
+ "HES_GETMAILHOST",
+#endif /* HES_GETMAILHOST */
+#if LDAPMAP
+ "LDAPMAP",
+#endif /* LDAPMAP */
+#if LOG
+ "LOG",
+#endif /* LOG */
+#if MAP_NSD
+ "MAP_NSD",
+#endif /* MAP_NSD */
+#if MAP_REGEX
+ "MAP_REGEX",
+#endif /* MAP_REGEX */
+#if MATCHGECOS
+ "MATCHGECOS",
+#endif /* MATCHGECOS */
+#if MILTER
+ "MILTER",
+#endif /* MILTER */
+#if MIME7TO8
+ "MIME7TO8",
+#endif /* MIME7TO8 */
+#if MIME7TO8_OLD
+ "MIME7TO8_OLD",
+#endif /* MIME7TO8_OLD */
+#if MIME8TO7
+ "MIME8TO7",
+#endif /* MIME8TO7 */
+#if NAMED_BIND
+ "NAMED_BIND",
+#endif /* NAMED_BIND */
+#if NDBM
+ "NDBM",
+#endif /* NDBM */
+#if NETINET
+ "NETINET",
+#endif /* NETINET */
+#if NETINET6
+ "NETINET6",
+#endif /* NETINET6 */
+#if NETINFO
+ "NETINFO",
+#endif /* NETINFO */
+#if NETISO
+ "NETISO",
+#endif /* NETISO */
+#if NETNS
+ "NETNS",
+#endif /* NETNS */
+#if NETUNIX
+ "NETUNIX",
+#endif /* NETUNIX */
+#if NETX25
+ "NETX25",
+#endif /* NETX25 */
+#if NEWDB
+ "NEWDB",
+#endif /* NEWDB */
+#if NIS
+ "NIS",
+#endif /* NIS */
+#if NISPLUS
+ "NISPLUS",
+#endif /* NISPLUS */
+#if NO_DH
+ "NO_DH",
+#endif /* NO_DH */
+#if PH_MAP
+ "PH_MAP",
+#endif /* PH_MAP */
+#ifdef PICKY_HELO_CHECK
+ "PICKY_HELO_CHECK",
+#endif /* PICKY_HELO_CHECK */
+#if PIPELINING
+ "PIPELINING",
+#endif /* PIPELINING */
+#if SASL
+# if SASL >= 20000
+ "SASLv2",
+# else /* SASL >= 20000 */
+ "SASL",
+# endif /* SASL >= 20000 */
+#endif /* SASL */
+#if SCANF
+ "SCANF",
+#endif /* SCANF */
+#if SMTPDEBUG
+ "SMTPDEBUG",
+#endif /* SMTPDEBUG */
+#if SOCKETMAP
+ "SOCKETMAP",
+#endif /* SOCKETMAP */
+#if STARTTLS
+ "STARTTLS",
+#endif /* STARTTLS */
+#if SUID_ROOT_FILES_OK
+ "SUID_ROOT_FILES_OK",
+#endif /* SUID_ROOT_FILES_OK */
+#if TCPWRAPPERS
+ "TCPWRAPPERS",
+#endif /* TCPWRAPPERS */
+#if TLS_NO_RSA
+ "TLS_NO_RSA",
+#endif /* TLS_NO_RSA */
+#if TLS_VRFY_PER_CTX
+ "TLS_VRFY_PER_CTX",
+#endif /* TLS_VRFY_PER_CTX */
+#if USERDB
+ "USERDB",
+#endif /* USERDB */
+#if USE_LDAP_INIT
+ "USE_LDAP_INIT",
+#endif /* USE_LDAP_INIT */
+#if USE_TTYPATH
+ "USE_TTYPATH",
+#endif /* USE_TTYPATH */
+#if XDEBUG
+ "XDEBUG",
+#endif /* XDEBUG */
+#if XLA
+ "XLA",
+#endif /* XLA */
+ NULL
+};
+
+
+/*
+** OS compile options.
+*/
+
+char *OsCompileOptions[] =
+{
+#if ADDRCONFIG_IS_BROKEN
+ "ADDRCONFIG_IS_BROKEN",
+#endif /* ADDRCONFIG_IS_BROKEN */
+#ifdef AUTO_NETINFO_HOSTS
+ "AUTO_NETINFO_HOSTS",
+#endif /* AUTO_NETINFO_HOSTS */
+#ifdef AUTO_NIS_ALIASES
+ "AUTO_NIS_ALIASES",
+#endif /* AUTO_NIS_ALIASES */
+#if BROKEN_RES_SEARCH
+ "BROKEN_RES_SEARCH",
+#endif /* BROKEN_RES_SEARCH */
+#ifdef BSD4_4_SOCKADDR
+ "BSD4_4_SOCKADDR",
+#endif /* BSD4_4_SOCKADDR */
+#if BOGUS_O_EXCL
+ "BOGUS_O_EXCL",
+#endif /* BOGUS_O_EXCL */
+#if DEC_OSF_BROKEN_GETPWENT
+ "DEC_OSF_BROKEN_GETPWENT",
+#endif /* DEC_OSF_BROKEN_GETPWENT */
+#if FAST_PID_RECYCLE
+ "FAST_PID_RECYCLE",
+#endif /* FAST_PID_RECYCLE */
+#if HASCLOSEFROM
+ "HASCLOSEFROM",
+#endif /* HASCLOSEFROM */
+#if HASFCHOWN
+ "HASFCHOWN",
+#endif /* HASFCHOWN */
+#if HASFCHMOD
+ "HASFCHMOD",
+#endif /* HASFCHMOD */
+#if HASFDWALK
+ "HASFDWALK",
+#endif /* HASFDWALK */
+#if HASFLOCK
+ "HASFLOCK",
+#endif /* HASFLOCK */
+#if HASGETDTABLESIZE
+ "HASGETDTABLESIZE",
+#endif /* HASGETDTABLESIZE */
+#if HASGETUSERSHELL
+ "HASGETUSERSHELL",
+#endif /* HASGETUSERSHELL */
+#if HASINITGROUPS
+ "HASINITGROUPS",
+#endif /* HASINITGROUPS */
+#if HASLDAPGETALIASBYNAME
+ "HASLDAPGETALIASBYNAME",
+#endif /* HASLDAPGETALIASBYNAME */
+#if HASLSTAT
+ "HASLSTAT",
+#endif /* HASLSTAT */
+#if HASNICE
+ "HASNICE",
+#endif /* HASNICE */
+#if HASRANDOM
+ "HASRANDOM",
+#endif /* HASRANDOM */
+#if HASRRESVPORT
+ "HASRRESVPORT",
+#endif /* HASRRESVPORT */
+#if HASSETEGID
+ "HASSETEGID",
+#endif /* HASSETEGID */
+#if HASSETLOGIN
+ "HASSETLOGIN",
+#endif /* HASSETLOGIN */
+#if HASSETREGID
+ "HASSETREGID",
+#endif /* HASSETREGID */
+#if HASSETRESGID
+ "HASSETRESGID",
+#endif /* HASSETRESGID */
+#if HASSETREUID
+ "HASSETREUID",
+#endif /* HASSETREUID */
+#if HASSETRLIMIT
+ "HASSETRLIMIT",
+#endif /* HASSETRLIMIT */
+#if HASSETSID
+ "HASSETSID",
+#endif /* HASSETSID */
+#if HASSETUSERCONTEXT
+ "HASSETUSERCONTEXT",
+#endif /* HASSETUSERCONTEXT */
+#if HASSETVBUF
+ "HASSETVBUF",
+#endif /* HASSETVBUF */
+#if HAS_ST_GEN
+ "HAS_ST_GEN",
+#endif /* HAS_ST_GEN */
+#if HASSRANDOMDEV
+ "HASSRANDOMDEV",
+#endif /* HASSRANDOMDEV */
+#if HASURANDOMDEV
+ "HASURANDOMDEV",
+#endif /* HASURANDOMDEV */
+#if HASSTRERROR
+ "HASSTRERROR",
+#endif /* HASSTRERROR */
+#if HASULIMIT
+ "HASULIMIT",
+#endif /* HASULIMIT */
+#if HASUNAME
+ "HASUNAME",
+#endif /* HASUNAME */
+#if HASUNSETENV
+ "HASUNSETENV",
+#endif /* HASUNSETENV */
+#if HASWAITPID
+ "HASWAITPID",
+#endif /* HASWAITPID */
+#if IDENTPROTO
+ "IDENTPROTO",
+#endif /* IDENTPROTO */
+#if IP_SRCROUTE
+ "IP_SRCROUTE",
+#endif /* IP_SRCROUTE */
+#if O_EXLOCK && HASFLOCK && !BOGUS_O_EXCL
+ "LOCK_ON_OPEN",
+#endif /* O_EXLOCK && HASFLOCK && !BOGUS_O_EXCL */
+#if NEEDFSYNC
+ "NEEDFSYNC",
+#endif /* NEEDFSYNC */
+#if NEEDLINK
+ "NEEDLINK",
+#endif /* NEEDLINK */
+#if NEEDLOCAL_HOSTNAME_LENGTH
+ "NEEDLOCAL_HOSTNAME_LENGTH",
+#endif /* NEEDLOCAL_HOSTNAME_LENGTH */
+#if NEEDSGETIPNODE
+ "NEEDSGETIPNODE",
+#endif /* NEEDSGETIPNODE */
+#if NEEDSTRSTR
+ "NEEDSTRSTR",
+#endif /* NEEDSTRSTR */
+#if NEEDSTRTOL
+ "NEEDSTRTOL",
+#endif /* NEEDSTRTOL */
+#ifdef NO_GETSERVBYNAME
+ "NO_GETSERVBYNAME",
+#endif /* NO_GETSERVBYNAME */
+#if NOFTRUNCATE
+ "NOFTRUNCATE",
+#endif /* NOFTRUNCATE */
+#if REQUIRES_DIR_FSYNC
+ "REQUIRES_DIR_FSYNC",
+#endif /* REQUIRES_DIR_FSYNC */
+#if RLIMIT_NEEDS_SYS_TIME_H
+ "RLIMIT_NEEDS_SYS_TIME_H",
+#endif /* RLIMIT_NEEDS_SYS_TIME_H */
+#if SAFENFSPATHCONF
+ "SAFENFSPATHCONF",
+#endif /* SAFENFSPATHCONF */
+#if SECUREWARE
+ "SECUREWARE",
+#endif /* SECUREWARE */
+#if SHARE_V1
+ "SHARE_V1",
+#endif /* SHARE_V1 */
+#if SIOCGIFCONF_IS_BROKEN
+ "SIOCGIFCONF_IS_BROKEN",
+#endif /* SIOCGIFCONF_IS_BROKEN */
+#if SIOCGIFNUM_IS_BROKEN
+ "SIOCGIFNUM_IS_BROKEN",
+#endif /* SIOCGIFNUM_IS_BROKEN */
+#if SNPRINTF_IS_BROKEN
+ "SNPRINTF_IS_BROKEN",
+#endif /* SNPRINTF_IS_BROKEN */
+#if SO_REUSEADDR_IS_BROKEN
+ "SO_REUSEADDR_IS_BROKEN",
+#endif /* SO_REUSEADDR_IS_BROKEN */
+#if SYS5SETPGRP
+ "SYS5SETPGRP",
+#endif /* SYS5SETPGRP */
+#if SYSTEM5
+ "SYSTEM5",
+#endif /* SYSTEM5 */
+#if USE_DOUBLE_FORK
+ "USE_DOUBLE_FORK",
+#endif /* USE_DOUBLE_FORK */
+#if USE_ENVIRON
+ "USE_ENVIRON",
+#endif /* USE_ENVIRON */
+#if USE_SA_SIGACTION
+ "USE_SA_SIGACTION",
+#endif /* USE_SA_SIGACTION */
+#if USE_SIGLONGJMP
+ "USE_SIGLONGJMP",
+#endif /* USE_SIGLONGJMP */
+#if USEGETCONFATTR
+ "USEGETCONFATTR",
+#endif /* USEGETCONFATTR */
+#if USESETEUID
+ "USESETEUID",
+#endif /* USESETEUID */
+#ifdef USESYSCTL
+ "USESYSCTL",
+#endif /* USESYSCTL */
+#if USING_NETSCAPE_LDAP
+ "USING_NETSCAPE_LDAP",
+#endif /* USING_NETSCAPE_LDAP */
+#ifdef WAITUNION
+ "WAITUNION",
+#endif /* WAITUNION */
+ NULL
+};
+
+/*
+** FFR compile options.
+*/
+
+char *FFRCompileOptions[] =
+{
+#if _FFR_ALLOW_SASLINFO
+ /* DefaultAuthInfo can be specified by user. */
+ /* DefaultAuthInfo doesn't really work in 8.13 anymore. */
+ "_FFR_ALLOW_SASLINFO",
+#endif /* _FFR_ALLOW_SASLINFO */
+#if _FFR_BESTMX_BETTER_TRUNCATION
+ /* Better truncation of list of MX records for dns map. */
+ "_FFR_BESTMX_BETTER_TRUNCATION",
+#endif /* _FFR_BESTMX_BETTER_TRUNCATION */
+#if _FFR_BLOCK_PROXIES
+ /*
+ ** Try to deal with open HTTP proxies that are used to send spam
+ ** by recognizing some commands from them.
+ */
+
+ "_FFR_BLOCK_PROXIES",
+#endif /* _FFR_BLOCK_PROXIES */
+#if _FFR_CATCH_BROKEN_MTAS
+ /* Deal with MTAs that send a reply during the DATA phase. */
+ "_FFR_CATCH_BROKEN_MTAS",
+#endif /* _FFR_CATCH_BROKEN_MTAS */
+#if _FFR_CHECK_EOM
+ /* Enable check_eom ruleset */
+ "_FFR_CHECK_EOM",
+#endif /* _FFR_CHECK_EOM */
+#if _FFR_CHK_QUEUE
+ /* Stricter checks about queue directory permissions. */
+ "_FFR_CHK_QUEUE",
+#endif /* _FFR_CHK_QUEUE */
+#if _FFR_CLIENT_SIZE
+ /* Don't try to send mail if its size exceeds SIZE= of server. */
+ "_FFR_CLIENT_SIZE",
+#endif /* _FFR_CLIENT_SIZE */
+#if _FFR_CONTROL_MSTAT
+ /* Extended daemon status. */
+ "_FFR_CONTROL_MSTAT",
+#endif /* _FFR_CONTROL_MSTAT */
+#if _FFR_CRLPATH
+ /* CRLPath; needs documentation; Al Smith */
+ "_FFR_CRLPATH",
+#endif /* _FFR_CRLPATH */
+#if _FFR_DAEMON_NETUNIX
+ /* Allow local (not just TCP) socket connection to server. */
+ "_FFR_DAEMON_NETUNIX",
+#endif /* _FFR_DAEMON_NETUNIX */
+#if _FFR_DEPRECATE_MAILER_FLAG_I
+ /* What it says :-) */
+ "_FFR_DEPRECATE_MAILER_FLAG_I",
+#endif /* _FFR_DEPRECATE_MAILER_FLAG_I */
+#if _FFR_DIGUNIX_SAFECHOWN
+ /* Properly set SAFECHOWN (include/sm/conf.h) for Digital UNIX */
+/* Problem noted by Anne Bennett of Concordia University */
+ "_FFR_DIGUNIX_SAFECHOWN",
+#endif /* _FFR_DIGUNIX_SAFECHOWN */
+#if _FFR_DM_PER_DAEMON
+ /* DeliveryMode per DaemonPortOptions: 'D' */
+ "_FFR_DM_PER_DAEMON",
+#endif /* _FFR_DM_PER_DAEMON */
+#if _FFR_DNSMAP_ALIASABLE
+ /* Allow dns map type to be used for aliases. */
+/* Don Lewis of TDK */
+ "_FFR_DNSMAP_ALIASABLE",
+#endif /* _FFR_DNSMAP_ALIASABLE */
+#if _FFR_DNSMAP_BASE
+ /* Specify a "base" domain for DNS lookups. */
+ "_FFR_DNSMAP_BASE",
+#endif /* _FFR_DNSMAP_BASE */
+#if _FFR_DNSMAP_MULTI
+ /* Allow multiple return values for DNS map. */
+ "_FFR_DNSMAP_MULTI",
+# if _FFR_DNSMAP_MULTILIMIT
+ /* Limit number of return values for DNS map. */
+ "_FFR_DNSMAP_MULTILIMIT",
+# endif /* _FFR_DNSMAP_MULTILIMIT */
+#endif /* _FFR_DNSMAP_MULTI */
+#if _FFR_DONTLOCKFILESFORREAD_OPTION
+ /* Enable DontLockFilesForRead option. */
+ "_FFR_DONTLOCKFILESFORREAD_OPTION",
+#endif /* _FFR_DONTLOCKFILESFORREAD_OPTION */
+#if _FFR_DOTTED_USERNAMES
+ /* Allow usernames with '.' */
+ "_FFR_DOTTED_USERNAMES",
+#endif /* _FFR_DOTTED_USERNAMES */
+#if _FFR_DROP_TRUSTUSER_WARNING
+ /*
+ ** Don't issue this warning:
+ ** "readcf: option TrustedUser may cause problems on systems
+ ** which do not support fchown() if UseMSP is not set.
+ */
+
+ "_FFR_DROP_TRUSTUSER_WARNING",
+#endif /* _FFR_DROP_TRUSTUSER_WARNING */
+#if _FFR_EXTRA_MAP_CHECK
+ /* perform extra checks on $( $) in R lines */
+ "_FFR_EXTRA_MAP_CHECK",
+#endif /* _FFR_EXTRA_MAP_CHECK */
+#if _FFR_FIX_DASHT
+ /*
+ ** If using -t, force not sending to argv recipients, even
+ ** if they are mentioned in the headers.
+ */
+
+ "_FFR_FIX_DASHT",
+#endif /* _FFR_FIX_DASHT */
+#if _FFR_FORWARD_SYSERR
+ /* Cause a "syserr" if forward file isn't "safe". */
+ "_FFR_FORWARD_SYSERR",
+#endif /* _FFR_FORWARD_SYSERR */
+#if _FFR_GEN_ORCPT
+ /* Generate a ORCPT DSN arg if not already provided */
+ "_FFR_GEN_ORCPT",
+#endif /* _FFR_GEN_ORCPT */
+#if _FFR_GROUPREADABLEAUTHINFOFILE
+ /* Allow group readable DefaultAuthInfo file. */
+ "_FFR_GROUPREADABLEAUTHINFOFILE",
+#endif /* _FFR_GROUPREADABLEAUTHINFOFILE */
+#if _FFR_HANDLE_ISO8859_GECOS
+ /*
+ ** Allow ISO 8859 characters in GECOS field: replace them
+ ** ith ASCII "equivalent".
+ */
+
+/* Peter Eriksson of Linkopings universitet */
+ "_FFR_HANDLE_ISO8859_GECOS",
+#endif /* _FFR_HANDLE_ISO8859_GECOS */
+#if _FFR_HDR_TYPE
+ /* Set 'h' in {addr_type} for headers. */
+ "_FFR_HDR_TYPE",
+#endif /* _FFR_HDR_TYPE */
+#if _FFR_HELONAME
+ /* option to set heloname; Nik Clayton of FreeBSD */
+ "_FFR_HELONAME",
+#endif /* _FFR_HELONAME */
+#if _FFR_HPUX_NSSWITCH
+ /* Use nsswitch on HP-UX */
+ "_FFR_HPUX_NSSWITCH",
+#endif /* _FFR_HPUX_NSSWITCH */
+#if _FFR_IGNORE_BOGUS_ADDR
+ /* Ignore addresses for which prescan() failed */
+ "_FFR_IGNORE_BOGUS_ADDR",
+#endif /* _FFR_IGNORE_BOGUS_ADDR */
+#if _FFR_IGNORE_EXT_ON_HELO
+ /* Ignore extensions offered in response to HELO */
+ "_FFR_IGNORE_EXT_ON_HELO",
+#endif /* _FFR_IGNORE_EXT_ON_HELO */
+#if _FFR_MAXDATASIZE
+ /*
+ ** It is possible that a header is larger than MILTER_CHUNK_SIZE,
+ ** hence this shouldn't be used as limit for milter communication.
+ ** see also libmilter/comm.c
+ ** Gurusamy Sarathy of ActiveState
+ */
+
+ "_FFR_MAXDATASIZE",
+#endif /* _FFR_MAXDATASIZE */
+#if _FFR_MAX_FORWARD_ENTRIES
+ /* Try to limit number of .forward entries */
+ /* (doesn't work) */
+/* Randall S. Winchester of the University of Maryland */
+ "_FFR_MAX_FORWARD_ENTRIES",
+#endif /* _FFR_MAX_FORWARD_ENTRIES */
+#if _FFR_MAX_SLEEP_TIME
+ /* Limit sleep(2) time in libsm/clock.c */
+ "_FFR_MAX_SLEEP_TIME",
+#endif /* _FFR_MAX_SLEEP_TIME */
+#if _FFR_MILTER_NAGLE
+ /* milter: turn off Nagle ("cork" on Linux) */
+ /* John Gardiner Myers of Proofpoint */
+ "_FFR_MILTER_NAGLE ",
+#endif /* _FFR_MILTER_NAGLE */
+#if _FFR_MILTER_NOHDR_RESP
+ /* milter: no response expected when sending headers */
+ /* John Gardiner Myers of Proofpoint */
+ "_FFR_MILTER_NOHDR_RESP",
+#endif /* _FFR_MILTER_NOHDR_RESP */
+#if _FFR_MIME7TO8_OLD
+ /* Old mime7to8 code, the new is broken for at least one example. */
+ "_FFR_MIME7TO8_OLD",
+#endif /* _FFR_MAX_SLEEP_TIME */
+#if _FFR_NODELAYDSN_ON_HOLD
+ /* Do not issue a DELAY DSN for mailers that use the hold flag. */
+/* Steven Pitzl */
+ "_FFR_NODELAYDSN_ON_HOLD",
+#endif /* _FFR_NODELAYDSN_ON_HOLD */
+#if _FFR_NO_PIPE
+ /* Disable PIPELINING, delay client if used. */
+ "_FFR_NO_PIPE",
+#endif /* _FFR_NO_PIPE */
+#if _FFR_LOG_NTRIES
+ /* log ntries=, from Nik Clayton of FreeBSD */
+ "_FFR_LOG_NTRIES",
+#endif /* _FFR_LOG_NTRIES */
+#if _FFR_PRIV_NOACTUALRECIPIENT
+ /*
+ ** PrivacyOptions=noactualrecipient stops sendmail from putting
+ ** X-Actual-Recipient lines in DSNs revealing the actual
+ ** account that addresses map to. Patch from Dan Harkless.
+ */
+
+ "_FFR_PRIV_NOACTUALRECIPIENT",
+#endif /* _FFR_PRIV_NOACTUALRECIPIENT */
+#if _FFR_QUEUEDELAY
+ /* Exponential queue delay; disabled in 8.13 since it isn't used. */
+ "_FFR_QUEUEDELAY",
+#endif /* _FFR_QUEUEDELAY */
+#if _FFR_QUEUE_GROUP_SORTORDER
+ /* Allow QueueSortOrder per queue group. */
+/* XXX: Still need to actually use qgrp->qg_sortorder */
+ "_FFR_QUEUE_GROUP_SORTORDER",
+#endif /* _FFR_QUEUE_GROUP_SORTORDER */
+#if _FFR_QUEUE_MACRO
+ /* Define {queue} macro. */
+ "_FFR_QUEUE_MACRO",
+#endif /* _FFR_QUEUE_MACRO */
+#if _FFR_QUEUE_RUN_PARANOIA
+ /* Additional checks when doing queue runs. */
+ "_FFR_QUEUE_RUN_PARANOIA",
+#endif /* _FFR_QUEUE_RUN_PARANOIA */
+#if _FFR_QUEUE_SCHED_DBG
+ /* Debug output for the queue scheduler. */
+ "_FFR_QUEUE_SCHED_DBG",
+#endif /* _FFR_QUEUE_SCHED_DBG */
+#if _FFR_REDIRECTEMPTY
+ /*
+ ** envelope <> can't be sent to mailing lists, only owner-
+ ** send spam of this type to owner- of the list
+ ** ---- to stop spam from going to mailing lists.
+ */
+
+ "_FFR_REDIRECTEMPTY",
+#endif /* _FFR_REDIRECTEMPTY */
+#if _FFR_RESET_MACRO_GLOBALS
+ /* Allow macro 'j' to be set dynamically via rulesets. */
+ "_FFR_RESET_MACRO_GLOBALS",
+#endif /* _FFR_RESET_MACRO_GLOBALS */
+#if _FFR_RHS
+ /* Random shuffle for queue sorting. */
+ "_FFR_RHS",
+#endif /* _FFR_RHS */
+#if _FFR_SELECT_SHM
+ /* Auto-select of shared memory key */
+ "_FFR_SELECT_SHM",
+#endif /* _FFR_SELECT_SHM */
+#if _FFR_SHM_STATUS
+ /* Donated code (unused). */
+ "_FFR_SHM_STATUS",
+#endif /* _FFR_SHM_STATUS */
+#if _FFR_SKIP_DOMAINS
+ /* process every N'th domain instead of every N'th message */
+ "_FFR_SKIP_DOMAINS",
+#endif /* _FFR_SKIP_DOMAINS */
+#if _FFR_SLEEP_USE_SELECT
+ /* Use select(2) in libsm/clock.c to emulate sleep(2) */
+ "_FFR_SLEEP_USE_SELECT ",
+#endif /* _FFR_SLEEP_USE_SELECT */
+#if _FFR_SOFT_BOUNCE
+ /* Turn all errors into temporary errors. */
+ "_FFR_SOFT_BOUNCE",
+#endif /* _FFR_SOFT_BOUNCE */
+#if _FFR_SPT_ALIGN
+ /*
+ ** It looks like the Compaq Tru64 5.1A now aligns argv and envp to 64
+ ** bit alignment, so unless each piece of argv and envp is a multiple
+ ** of 8 bytes (including terminating NULL), initsetproctitle() won't
+ ** use any of the space beyond argv[0]. Be sure to set SPT_ALIGN_SIZE
+ ** if you use this FFR.
+ */
+
+/* Chris Adams of HiWAAY Informations Services */
+ "_FFR_SPT_ALIGN",
+#endif /* _FFR_SPT_ALIGN */
+#if _FFR_SS_PER_DAEMON
+ /* SuperSafe per DaemonPortOptions: 'T' (better letter?) */
+ "_FFR_SS_PER_DAEMON",
+#endif /* _FFR_SS_PER_DAEMON */
+#if _FFR_TIMERS
+ /* Donated code (unused). */
+ "_FFR_TIMERS",
+#endif /* _FFR_TIMERS */
+#if _FFR_TLS_1
+ /* More STARTTLS options, e.g., secondary certs. */
+ "_FFR_TLS_1",
+#endif /* _FFR_TLS_1 */
+#if _FFR_TRUSTED_QF
+ /*
+ ** If we don't own the file mark it as unsafe.
+ ** However, allow TrustedUser to own it as well
+ ** in case TrustedUser manipulates the queue.
+ */
+
+ "_FFR_TRUSTED_QF",
+#endif /* _FFR_TRUSTED_QF */
+#if _FFR_USE_SEM_LOCKING
+ "_FFR_USE_SEM_LOCKING",
+#endif /* _FFR_USE_SEM_LOCKING */
+#if _FFR_USE_SETLOGIN
+ /* Use setlogin() */
+/* Peter Philipp */
+ "_FFR_USE_SETLOGIN",
+#endif /* _FFR_USE_SETLOGIN */
+ NULL
+};
+
diff --git a/usr/src/cmd/sendmail/src/conf.h b/usr/src/cmd/sendmail/src/conf.h
new file mode 100644
index 0000000000..a2ac00fc8a
--- /dev/null
+++ b/usr/src/cmd/sendmail/src/conf.h
@@ -0,0 +1,224 @@
+/*
+ * Copyright (c) 1998-2002 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ *
+ * $Id: conf.h,v 8.567 2004/07/23 20:45:01 gshapiro Exp $
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+** CONF.H -- All user-configurable parameters for sendmail
+**
+** Send updates to sendmail@Sendmail.ORG so they will be
+** included in the next release.
+*/
+
+#ifndef CONF_H
+#define CONF_H 1
+
+#ifdef __GNUC__
+struct rusage; /* forward declaration to get gcc to shut up in wait.h */
+#endif /* __GNUC__ */
+
+# include <sys/param.h>
+# include <sys/types.h>
+# include <sys/stat.h>
+# ifndef __QNX__
+/* in QNX this grabs bogus LOCK_* manifests */
+# include <sys/file.h>
+# endif /* ! __QNX__ */
+# include <sys/wait.h>
+# include <limits.h>
+# include <fcntl.h>
+# include <signal.h>
+# include <netdb.h>
+# include <pwd.h>
+# include <grp.h>
+
+/* make sure TOBUFSIZ isn't larger than system limit for size of exec() args */
+#ifdef ARG_MAX
+# if ARG_MAX > 4096
+# define SM_ARG_MAX 4096
+# else /* ARG_MAX > 4096 */
+# define SM_ARG_MAX ARG_MAX
+# endif /* ARG_MAX > 4096 */
+#else /* ARG_MAX */
+# define SM_ARG_MAX 4096
+#endif /* ARG_MAX */
+
+/**********************************************************************
+** Table sizes, etc....
+** There shouldn't be much need to change these....
+** If you do, be careful, none should be set anywhere near INT_MAX
+**********************************************************************/
+
+#define MAXLINE 2048 /* max line length */
+#define MAXNAME 256 /* max length of a name */
+#ifndef MAXAUTHINFO
+# define MAXAUTHINFO 100 /* max length of authinfo token */
+#endif /* ! MAXAUTHINFO */
+#define MAXPV 256 /* max # of parms to mailers */
+#define MAXATOM 1000 /* max atoms per address */
+#define MAXRWSETS 200 /* max # of sets of rewriting rules */
+#define MAXPRIORITIES 25 /* max values for Precedence: field */
+#define MAXMXHOSTS 100 /* max # of MX records for one host */
+#define SMTPLINELIM 990 /* maximum SMTP line length */
+#define MAXKEY 128 /* maximum size of a database key */
+#define MEMCHUNKSIZE 1024 /* chunk size for memory allocation */
+#define MAXUSERENVIRON 100 /* max envars saved, must be >= 3 */
+#define MAXMAPSTACK 12 /* max # of stacked or sequenced maps */
+#if MILTER
+# define MAXFILTERS 25 /* max # of milter filters */
+# define MAXFILTERMACROS 50 /* max # of macros per milter cmd */
+#endif /* MILTER */
+#define MAXSMTPARGS 20 /* max # of ESMTP args for MAIL/RCPT */
+#define MAXTOCLASS 8 /* max # of message timeout classes */
+#define MAXRESTOTYPES 3 /* max # of resolver timeout types */
+#define MAXMIMEARGS 20 /* max args in Content-Type: */
+#define MAXMIMENESTING 20 /* max MIME multipart nesting */
+#define QUEUESEGSIZE 1000 /* increment for queue size */
+
+/*
+** MAXQFNAME == 2 (size of "qf", "df" prefix)
+** + 8 (base 60 encoded date, time & sequence number)
+** + 10 (base 10 encoded 32 bit process id)
+** + 1 (terminating NUL character).
+*/
+
+#define MAXQFNAME 21 /* max qf file name length + 1 */
+#define MACBUFSIZE 4096 /* max expanded macro buffer size */
+#define TOBUFSIZE SM_ARG_MAX /* max buffer to hold address list */
+#define MAXSHORTSTR 203 /* max short string length */
+#define MAXMACNAMELEN 25 /* max macro name length */
+#define MAXMACROID 0377 /* max macro id number */
+ /* Must match (BITMAPBITS - 1) */
+#ifndef MAXHDRSLEN
+# define MAXHDRSLEN (32 * 1024) /* max size of message headers */
+#endif /* ! MAXHDRSLEN */
+#define MAXDAEMONS 10 /* max number of ports to listen to */
+#ifndef MAXINTERFACES
+# define MAXINTERFACES 512 /* number of interfaces to probe */
+#endif /* MAXINTERFACES */
+#ifndef MAXSYMLINKS
+# define MAXSYMLINKS 32 /* max number of symlinks in a path */
+#endif /* ! MAXSYMLINKS */
+#define MAXLINKPATHLEN (MAXPATHLEN * MAXSYMLINKS) /* max link-expanded file */
+#define DATA_PROGRESS_TIMEOUT 300 /* how often to check DATA progress */
+#define ENHSCLEN 10 /* max len of enhanced status code */
+#define DEFAULT_MAX_RCPT 100 /* max number of RCPTs per envelope */
+#define MAXQUEUEGROUPS 50 /* max # of queue groups */
+ /* must be less than BITMAPBITS for DoQueueRun */
+#define MAXWORKGROUPS 50 /* max # of work groups */
+#define MAXFILESYS BITMAPBITS /* max # of queue file systems
+ * must be <= BITMAPBITS */
+#ifndef FILESYS_UPDATE_INTERVAL
+# define FILESYS_UPDATE_INTERVAL 300 /* how often to update FileSys table */
+#endif /* FILESYS_UPDATE_INTERVAL */
+
+#ifndef SM_DEFAULT_TTL
+# define SM_DEFAULT_TTL 3600 /* default TTL for services that don't have one */
+#endif /* SM_DEFAULT_TTL */
+
+#if SASL
+# ifndef AUTH_MECHANISMS
+# if STARTTLS
+# define AUTH_MECHANISMS "EXTERNAL GSSAPI KERBEROS_V4 DIGEST-MD5 CRAM-MD5"
+# else /* STARTTLS */
+# define AUTH_MECHANISMS "GSSAPI KERBEROS_V4 DIGEST-MD5 CRAM-MD5"
+# endif /* STARTTLS */
+# endif /* ! AUTH_MECHANISMS */
+#endif /* SASL */
+
+/*
+** Default database permissions (alias, maps, etc.)
+** Used by sendmail and libsmdb
+*/
+
+#ifndef DBMMODE
+# define DBMMODE 0640
+#endif /* ! DBMMODE */
+
+/*
+** Value which means a uid or gid value should not change
+*/
+
+#ifndef NO_UID
+# define NO_UID -1
+#endif /* ! NO_UID */
+#ifndef NO_GID
+# define NO_GID -1
+#endif /* ! NO_GID */
+
+/**********************************************************************
+** Compilation options.
+** #define these to 1 if they are available;
+** #define them to 0 otherwise.
+** All can be overridden from Makefile.
+**********************************************************************/
+
+#ifndef NETINET
+# define NETINET 1 /* include internet support */
+#endif /* ! NETINET */
+
+#ifndef NETINET6
+# define NETINET6 0 /* do not include IPv6 support */
+#endif /* ! NETINET6 */
+
+#ifndef NETISO
+# define NETISO 0 /* do not include ISO socket support */
+#endif /* ! NETISO */
+
+#ifndef NAMED_BIND
+# define NAMED_BIND 1 /* use Berkeley Internet Domain Server */
+#endif /* ! NAMED_BIND */
+
+#ifndef XDEBUG
+# define XDEBUG 1 /* enable extended debugging */
+#endif /* ! XDEBUG */
+
+#ifndef MATCHGECOS
+# define MATCHGECOS 1 /* match user names from gecos field */
+#endif /* ! MATCHGECOS */
+
+#ifndef DSN
+# define DSN 1 /* include delivery status notification code */
+#endif /* ! DSN */
+
+#if !defined(USERDB) && (defined(NEWDB) || defined(HESIOD))
+# define USERDB 1 /* look in user database */
+#endif /* !defined(USERDB) && (defined(NEWDB) || defined(HESIOD)) */
+
+#ifndef MIME8TO7
+# define MIME8TO7 1 /* 8->7 bit MIME conversions */
+#endif /* ! MIME8TO7 */
+
+#ifndef MIME7TO8
+# define MIME7TO8 1 /* 7->8 bit MIME conversions */
+#endif /* ! MIME7TO8 */
+
+#if NAMED_BIND
+# ifndef DNSMAP
+# define DNSMAP 1 /* DNS map type */
+# endif /* ! DNSMAP */
+#endif /* NAMED_BIND */
+
+#ifndef PIPELINING
+# define PIPELINING 1 /* SMTP PIPELINING */
+#endif /* PIPELINING */
+
+/**********************************************************************
+** End of site-specific configuration.
+**********************************************************************/
+
+#include <sm/conf.h>
+
+#endif /* ! CONF_H */
diff --git a/usr/src/cmd/sendmail/src/control.c b/usr/src/cmd/sendmail/src/control.c
new file mode 100644
index 0000000000..088623b11c
--- /dev/null
+++ b/usr/src/cmd/sendmail/src/control.c
@@ -0,0 +1,439 @@
+/*
+ * Copyright (c) 1998-2004 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sendmail.h>
+
+SM_RCSID("@(#)$Id: control.c,v 8.126 2004/08/04 20:54:00 ca Exp $")
+
+#include <sm/fdset.h>
+
+/* values for cmd_code */
+#define CMDERROR 0 /* bad command */
+#define CMDRESTART 1 /* restart daemon */
+#define CMDSHUTDOWN 2 /* end daemon */
+#define CMDHELP 3 /* help */
+#define CMDSTATUS 4 /* daemon status */
+#define CMDMEMDUMP 5 /* dump memory, to find memory leaks */
+#if _FFR_CONTROL_MSTAT
+# define CMDMSTAT 6 /* daemon status, more info, tagged data */
+#endif /* _FFR_CONTROL_MSTAT */
+
+struct cmd
+{
+ char *cmd_name; /* command name */
+ int cmd_code; /* internal code, see below */
+};
+
+static struct cmd CmdTab[] =
+{
+ { "help", CMDHELP },
+ { "restart", CMDRESTART },
+ { "shutdown", CMDSHUTDOWN },
+ { "status", CMDSTATUS },
+ { "memdump", CMDMEMDUMP },
+#if _FFR_CONTROL_MSTAT
+ { "mstat", CMDMSTAT },
+#endif /* _FFR_CONTROL_MSTAT */
+ { NULL, CMDERROR }
+};
+
+
+
+static void controltimeout __P((int));
+int ControlSocket = -1;
+
+/*
+** OPENCONTROLSOCKET -- create/open the daemon control named socket
+**
+** Creates and opens a named socket for external control over
+** the sendmail daemon.
+**
+** Parameters:
+** none.
+**
+** Returns:
+** 0 if successful, -1 otherwise
+*/
+
+int
+opencontrolsocket()
+{
+# if NETUNIX
+ int save_errno;
+ int rval;
+ long sff = SFF_SAFEDIRPATH|SFF_OPENASROOT|SFF_NOLINK|SFF_CREAT|SFF_MUSTOWN;
+ struct sockaddr_un controladdr;
+
+ if (ControlSocketName == NULL || *ControlSocketName == '\0')
+ return 0;
+
+ if (strlen(ControlSocketName) >= sizeof controladdr.sun_path)
+ {
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+
+ rval = safefile(ControlSocketName, RunAsUid, RunAsGid, RunAsUserName,
+ sff, S_IRUSR|S_IWUSR, NULL);
+
+ /* if not safe, don't create */
+ if (rval != 0)
+ {
+ errno = rval;
+ return -1;
+ }
+
+ ControlSocket = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (ControlSocket < 0)
+ return -1;
+ if (SM_FD_SETSIZE > 0 && ControlSocket >= SM_FD_SETSIZE)
+ {
+ clrcontrol();
+ errno = EINVAL;
+ return -1;
+ }
+
+ (void) unlink(ControlSocketName);
+ memset(&controladdr, '\0', sizeof controladdr);
+ controladdr.sun_family = AF_UNIX;
+ (void) sm_strlcpy(controladdr.sun_path, ControlSocketName,
+ sizeof controladdr.sun_path);
+
+ if (bind(ControlSocket, (struct sockaddr *) &controladdr,
+ sizeof controladdr) < 0)
+ {
+ save_errno = errno;
+ clrcontrol();
+ errno = save_errno;
+ return -1;
+ }
+
+ if (geteuid() == 0)
+ {
+ uid_t u = 0;
+
+ if (RunAsUid != 0)
+ u = RunAsUid;
+ else if (TrustedUid != 0)
+ u = TrustedUid;
+
+ if (u != 0 &&
+ chown(ControlSocketName, u, -1) < 0)
+ {
+ save_errno = errno;
+ sm_syslog(LOG_ALERT, NOQID,
+ "ownership change on %s to uid %d failed: %s",
+ ControlSocketName, (int) u,
+ sm_errstring(save_errno));
+ message("050 ownership change on %s to uid %d failed: %s",
+ ControlSocketName, (int) u,
+ sm_errstring(save_errno));
+ closecontrolsocket(true);
+ errno = save_errno;
+ return -1;
+ }
+ }
+
+ if (chmod(ControlSocketName, S_IRUSR|S_IWUSR) < 0)
+ {
+ save_errno = errno;
+ closecontrolsocket(true);
+ errno = save_errno;
+ return -1;
+ }
+
+ if (listen(ControlSocket, 8) < 0)
+ {
+ save_errno = errno;
+ closecontrolsocket(true);
+ errno = save_errno;
+ return -1;
+ }
+# endif /* NETUNIX */
+ return 0;
+}
+/*
+** CLOSECONTROLSOCKET -- close the daemon control named socket
+**
+** Close a named socket.
+**
+** Parameters:
+** fullclose -- if set, close the socket and remove it;
+** otherwise, just remove it
+**
+** Returns:
+** none.
+*/
+
+void
+closecontrolsocket(fullclose)
+ bool fullclose;
+{
+# if NETUNIX
+ long sff = SFF_SAFEDIRPATH|SFF_OPENASROOT|SFF_NOLINK|SFF_CREAT|SFF_MUSTOWN;
+
+ if (ControlSocket >= 0)
+ {
+ int rval;
+
+ if (fullclose)
+ {
+ (void) close(ControlSocket);
+ ControlSocket = -1;
+ }
+
+ rval = safefile(ControlSocketName, RunAsUid, RunAsGid,
+ RunAsUserName, sff, S_IRUSR|S_IWUSR, NULL);
+
+ /* if not safe, don't unlink */
+ if (rval != 0)
+ return;
+
+ if (unlink(ControlSocketName) < 0)
+ {
+ sm_syslog(LOG_WARNING, NOQID,
+ "Could not remove control socket: %s",
+ sm_errstring(errno));
+ return;
+ }
+ }
+# endif /* NETUNIX */
+ return;
+}
+/*
+** CLRCONTROL -- reset the control connection
+**
+** Parameters:
+** none.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** releases any resources used by the control interface.
+*/
+
+void
+clrcontrol()
+{
+# if NETUNIX
+ if (ControlSocket >= 0)
+ (void) close(ControlSocket);
+ ControlSocket = -1;
+# endif /* NETUNIX */
+}
+/*
+** CONTROL_COMMAND -- read and process command from named socket
+**
+** Read and process the command from the opened socket.
+** Exits when done since it is running in a forked child.
+**
+** Parameters:
+** sock -- the opened socket from getrequests()
+** e -- the current envelope
+**
+** Returns:
+** none.
+*/
+
+static jmp_buf CtxControlTimeout;
+
+/* ARGSUSED0 */
+static void
+controltimeout(timeout)
+ int timeout;
+{
+ /*
+ ** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+ ** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+ ** DOING.
+ */
+
+ errno = ETIMEDOUT;
+ longjmp(CtxControlTimeout, 1);
+}
+
+void
+control_command(sock, e)
+ int sock;
+ ENVELOPE *e;
+{
+ volatile int exitstat = EX_OK;
+ SM_FILE_T *s = NULL;
+ SM_EVENT *ev = NULL;
+ SM_FILE_T *traffic;
+ SM_FILE_T *oldout;
+ char *cmd;
+ char *p;
+ struct cmd *c;
+ char cmdbuf[MAXLINE];
+ char inp[MAXLINE];
+
+ sm_setproctitle(false, e, "control cmd read");
+
+ if (TimeOuts.to_control > 0)
+ {
+ /* handle possible input timeout */
+ if (setjmp(CtxControlTimeout) != 0)
+ {
+ if (LogLevel > 2)
+ sm_syslog(LOG_NOTICE, e->e_id,
+ "timeout waiting for input during control command");
+ exit(EX_IOERR);
+ }
+ ev = sm_setevent(TimeOuts.to_control, controltimeout,
+ TimeOuts.to_control);
+ }
+
+ s = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT, (void *) &sock,
+ SM_IO_RDWR, NULL);
+ if (s == NULL)
+ {
+ int save_errno = errno;
+
+ (void) close(sock);
+ errno = save_errno;
+ exit(EX_IOERR);
+ }
+ (void) sm_io_setvbuf(s, SM_TIME_DEFAULT, NULL,
+ SM_IO_NBF, SM_IO_BUFSIZ);
+
+ if (sm_io_fgets(s, SM_TIME_DEFAULT, inp, sizeof inp) == NULL)
+ {
+ (void) sm_io_close(s, SM_TIME_DEFAULT);
+ exit(EX_IOERR);
+ }
+ (void) sm_io_flush(s, SM_TIME_DEFAULT);
+
+ /* clean up end of line */
+ fixcrlf(inp, true);
+
+ sm_setproctitle(false, e, "control: %s", inp);
+
+ /* break off command */
+ for (p = inp; isascii(*p) && isspace(*p); p++)
+ continue;
+ cmd = cmdbuf;
+ while (*p != '\0' &&
+ !(isascii(*p) && isspace(*p)) &&
+ cmd < &cmdbuf[sizeof cmdbuf - 2])
+ *cmd++ = *p++;
+ *cmd = '\0';
+
+ /* throw away leading whitespace */
+ while (isascii(*p) && isspace(*p))
+ p++;
+
+ /* decode command */
+ for (c = CmdTab; c->cmd_name != NULL; c++)
+ {
+ if (sm_strcasecmp(c->cmd_name, cmdbuf) == 0)
+ break;
+ }
+
+ switch (c->cmd_code)
+ {
+ case CMDHELP: /* get help */
+ traffic = TrafficLogFile;
+ TrafficLogFile = NULL;
+ oldout = OutChannel;
+ OutChannel = s;
+ help("control", e);
+ TrafficLogFile = traffic;
+ OutChannel = oldout;
+ break;
+
+ case CMDRESTART: /* restart the daemon */
+ (void) sm_io_fprintf(s, SM_TIME_DEFAULT, "OK\r\n");
+ exitstat = EX_RESTART;
+ break;
+
+ case CMDSHUTDOWN: /* kill the daemon */
+ (void) sm_io_fprintf(s, SM_TIME_DEFAULT, "OK\r\n");
+ exitstat = EX_SHUTDOWN;
+ break;
+
+ case CMDSTATUS: /* daemon status */
+ proc_list_probe();
+ {
+ int qgrp;
+ long bsize;
+ long free;
+
+ /* XXX need to deal with different partitions */
+ qgrp = e->e_qgrp;
+ if (!ISVALIDQGRP(qgrp))
+ qgrp = 0;
+ free = freediskspace(Queue[qgrp]->qg_qdir, &bsize);
+
+ /*
+ ** Prevent overflow and don't lose
+ ** precision (if bsize == 512)
+ */
+
+ if (free > 0)
+ free = (long)((double) free *
+ ((double) bsize / 1024));
+
+ (void) sm_io_fprintf(s, SM_TIME_DEFAULT,
+ "%d/%d/%ld/%d\r\n",
+ CurChildren, MaxChildren,
+ free, getla());
+ }
+ proc_list_display(s, "");
+ break;
+
+# if _FFR_CONTROL_MSTAT
+ case CMDMSTAT: /* daemon status, extended, tagged format */
+ proc_list_probe();
+ (void) sm_io_fprintf(s, SM_TIME_DEFAULT,
+ "C:%d\r\nM:%d\r\nL:%d\r\n",
+ CurChildren, MaxChildren,
+ getla());
+ printnqe(s, "Q:");
+ disk_status(s, "D:");
+ proc_list_display(s, "P:");
+ break;
+# endif /* _FFR_CONTROL_MSTAT */
+
+ case CMDMEMDUMP: /* daemon memory dump, to find memory leaks */
+# if SM_HEAP_CHECK
+ /* dump the heap, if we are checking for memory leaks */
+ if (sm_debug_active(&SmHeapCheck, 2))
+ {
+ sm_heap_report(s, sm_debug_level(&SmHeapCheck) - 1);
+ }
+ else
+ {
+ (void) sm_io_fprintf(s, SM_TIME_DEFAULT,
+ "Memory dump unavailable.\r\n");
+ (void) sm_io_fprintf(s, SM_TIME_DEFAULT,
+ "To fix, run sendmail with -dsm_check_heap.4\r\n");
+ }
+# else /* SM_HEAP_CHECK */
+ (void) sm_io_fprintf(s, SM_TIME_DEFAULT,
+ "Memory dump unavailable.\r\n");
+ (void) sm_io_fprintf(s, SM_TIME_DEFAULT,
+ "To fix, rebuild with -DSM_HEAP_CHECK\r\n");
+# endif /* SM_HEAP_CHECK */
+ break;
+
+ case CMDERROR: /* unknown command */
+ (void) sm_io_fprintf(s, SM_TIME_DEFAULT,
+ "Bad command (%s)\r\n", cmdbuf);
+ break;
+ }
+ (void) sm_io_close(s, SM_TIME_DEFAULT);
+ if (ev != NULL)
+ sm_clrevent(ev);
+ exit(exitstat);
+}
diff --git a/usr/src/cmd/sendmail/src/convtime.c b/usr/src/cmd/sendmail/src/convtime.c
new file mode 100644
index 0000000000..43d124aaf5
--- /dev/null
+++ b/usr/src/cmd/sendmail/src/convtime.c
@@ -0,0 +1,204 @@
+/*
+ * Copyright (c) 1998-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sendmail.h>
+
+SM_RCSID("@(#)$Id: convtime.c,v 8.36 2001/02/13 22:32:08 ca Exp $")
+
+/*
+** CONVTIME -- convert time
+**
+** Takes a time as an ascii string with a trailing character
+** giving units:
+** s -- seconds
+** m -- minutes
+** h -- hours
+** d -- days (default)
+** w -- weeks
+** For example, "3d12h" is three and a half days.
+**
+** Parameters:
+** p -- pointer to ascii time.
+** units -- default units if none specified.
+**
+** Returns:
+** time in seconds.
+**
+** Side Effects:
+** none.
+*/
+
+time_t
+convtime(p, units)
+ char *p;
+ int units;
+{
+ register time_t t, r;
+ register char c;
+ bool pos = true;
+
+ r = 0;
+ if (sm_strcasecmp(p, "now") == 0)
+ return NOW;
+ if (*p == '-')
+ {
+ pos = false;
+ ++p;
+ }
+ while (*p != '\0')
+ {
+ t = 0;
+ while ((c = *p++) != '\0' && isascii(c) && isdigit(c))
+ t = t * 10 + (c - '0');
+ if (c == '\0')
+ {
+ c = units;
+ p--;
+ }
+ else if (strchr("wdhms", c) == NULL)
+ {
+ usrerr("Invalid time unit `%c'", c);
+ c = units;
+ }
+ switch (c)
+ {
+ case 'w': /* weeks */
+ t *= 7;
+ /* FALLTHROUGH */
+
+ case 'd': /* days */
+ /* FALLTHROUGH */
+ default:
+ t *= 24;
+ /* FALLTHROUGH */
+
+ case 'h': /* hours */
+ t *= 60;
+ /* FALLTHROUGH */
+
+ case 'm': /* minutes */
+ t *= 60;
+ /* FALLTHROUGH */
+
+ case 's': /* seconds */
+ break;
+ }
+ r += t;
+ }
+
+ return pos ? r : -r;
+}
+ /*
+** PINTVL -- produce printable version of a time interval
+**
+** Parameters:
+** intvl -- the interval to be converted
+** brief -- if true, print this in an extremely compact form
+** (basically used for logging).
+**
+** Returns:
+** A pointer to a string version of intvl suitable for
+** printing or framing.
+**
+** Side Effects:
+** none.
+**
+** Warning:
+** The string returned is in a static buffer.
+*/
+
+#define PLURAL(n) ((n) == 1 ? "" : "s")
+
+char *
+pintvl(intvl, brief)
+ time_t intvl;
+ bool brief;
+{
+ static char buf[256];
+ register char *p;
+ int wk, dy, hr, mi, se;
+
+ if (intvl == 0 && !brief)
+ return "zero seconds";
+ if (intvl == NOW)
+ return "too long";
+
+ /* decode the interval into weeks, days, hours, minutes, seconds */
+ se = intvl % 60;
+ intvl /= 60;
+ mi = intvl % 60;
+ intvl /= 60;
+ hr = intvl % 24;
+ intvl /= 24;
+ if (brief)
+ {
+ dy = intvl;
+ wk = 0;
+ }
+ else
+ {
+ dy = intvl % 7;
+ intvl /= 7;
+ wk = intvl;
+ }
+
+ /* now turn it into a sexy form */
+ p = buf;
+ if (brief)
+ {
+ if (dy > 0)
+ {
+ (void) sm_snprintf(p, SPACELEFT(buf, p), "%d+", dy);
+ p += strlen(p);
+ }
+ (void) sm_snprintf(p, SPACELEFT(buf, p), "%02d:%02d:%02d",
+ hr, mi, se);
+ return buf;
+ }
+
+ /* use the verbose form */
+ if (wk > 0)
+ {
+ (void) sm_snprintf(p, SPACELEFT(buf, p), ", %d week%s", wk,
+ PLURAL(wk));
+ p += strlen(p);
+ }
+ if (dy > 0)
+ {
+ (void) sm_snprintf(p, SPACELEFT(buf, p), ", %d day%s", dy,
+ PLURAL(dy));
+ p += strlen(p);
+ }
+ if (hr > 0)
+ {
+ (void) sm_snprintf(p, SPACELEFT(buf, p), ", %d hour%s", hr,
+ PLURAL(hr));
+ p += strlen(p);
+ }
+ if (mi > 0)
+ {
+ (void) sm_snprintf(p, SPACELEFT(buf, p), ", %d minute%s", mi,
+ PLURAL(mi));
+ p += strlen(p);
+ }
+ if (se > 0)
+ {
+ (void) sm_snprintf(p, SPACELEFT(buf, p), ", %d second%s", se,
+ PLURAL(se));
+ p += strlen(p);
+ }
+
+ return (buf + 2);
+}
diff --git a/usr/src/cmd/sendmail/src/daemon.c b/usr/src/cmd/sendmail/src/daemon.c
new file mode 100644
index 0000000000..2faf143a5d
--- /dev/null
+++ b/usr/src/cmd/sendmail/src/daemon.c
@@ -0,0 +1,4459 @@
+/*
+ * Copyright (c) 1998-2005 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sendmail.h>
+
+SM_RCSID("@(#)$Id: daemon.c,v 8.658 2005/02/02 18:19:28 ca Exp $")
+
+#if defined(SOCK_STREAM) || defined(__GNU_LIBRARY__)
+# define USE_SOCK_STREAM 1
+#endif /* defined(SOCK_STREAM) || defined(__GNU_LIBRARY__) */
+
+#if defined(USE_SOCK_STREAM)
+# if NETINET || NETINET6
+# include <arpa/inet.h>
+# endif /* NETINET || NETINET6 */
+# if NAMED_BIND
+# ifndef NO_DATA
+# define NO_DATA NO_ADDRESS
+# endif /* ! NO_DATA */
+# endif /* NAMED_BIND */
+#endif /* defined(USE_SOCK_STREAM) */
+
+#if STARTTLS
+# include <openssl/rand.h>
+#endif /* STARTTLS */
+
+#include <sys/time.h>
+
+#if IP_SRCROUTE && NETINET
+# include <netinet/in_systm.h>
+# include <netinet/ip.h>
+# if HAS_IN_H
+# include <netinet/in.h>
+# ifndef IPOPTION
+# define IPOPTION ip_opts
+# define IP_LIST ip_opts
+# define IP_DST ip_dst
+# endif /* ! IPOPTION */
+# else /* HAS_IN_H */
+# include <netinet/ip_var.h>
+# ifndef IPOPTION
+# define IPOPTION ipoption
+# define IP_LIST ipopt_list
+# define IP_DST ipopt_dst
+# endif /* ! IPOPTION */
+# endif /* HAS_IN_H */
+#endif /* IP_SRCROUTE && NETINET */
+
+#include <sm/fdset.h>
+
+/* structure to describe a daemon or a client */
+struct daemon
+{
+ int d_socket; /* fd for socket */
+ SOCKADDR d_addr; /* socket for incoming */
+ unsigned short d_port; /* port number */
+ int d_listenqueue; /* size of listen queue */
+ int d_tcprcvbufsize; /* size of TCP receive buffer */
+ int d_tcpsndbufsize; /* size of TCP send buffer */
+ time_t d_refuse_connections_until;
+ bool d_firsttime;
+ int d_socksize;
+ BITMAP256 d_flags; /* flags; see sendmail.h */
+ char *d_mflags; /* flags for use in macro */
+ char *d_name; /* user-supplied name */
+#if MILTER
+ char *d_inputfilterlist;
+ struct milter *d_inputfilters[MAXFILTERS];
+#endif /* MILTER */
+#if _FFR_SS_PER_DAEMON
+ int d_supersafe;
+#endif /* _FFR_SS_PER_DAEMON */
+#if _FFR_DM_PER_DAEMON
+ int d_dm; /* DeliveryMode */
+#endif /* _FFR_DM_PER_DAEMON */
+};
+
+typedef struct daemon DAEMON_T;
+
+#define SAFE_NOTSET (-1) /* SuperSafe (per daemon) option not set */
+/* see also sendmail.h: SuperSafe values */
+
+#define DM_NOTSET (-1) /* DeliveryMode (per daemon) option not set */
+/* see also sendmail.h: values for e_sendmode -- send modes */
+
+static void connecttimeout __P((int));
+static int opendaemonsocket __P((DAEMON_T *, bool));
+static unsigned short setupdaemon __P((SOCKADDR *));
+static void getrequests_checkdiskspace __P((ENVELOPE *e));
+static void setsockaddroptions __P((char *, DAEMON_T *));
+static void printdaemonflags __P((DAEMON_T *));
+static int addr_family __P((char *));
+static int addrcmp __P((struct hostent *, char *, SOCKADDR *));
+static void authtimeout __P((int));
+
+/*
+** DAEMON.C -- routines to use when running as a daemon.
+**
+** This entire file is highly dependent on the 4.2 BSD
+** interprocess communication primitives. No attempt has
+** been made to make this file portable to Version 7,
+** Version 6, MPX files, etc. If you should try such a
+** thing yourself, I recommend chucking the entire file
+** and starting from scratch. Basic semantics are:
+**
+** getrequests(e)
+** Opens a port and initiates a connection.
+** Returns in a child. Must set InChannel and
+** OutChannel appropriately.
+** clrdaemon()
+** Close any open files associated with getting
+** the connection; this is used when running the queue,
+** etc., to avoid having extra file descriptors during
+** the queue run and to avoid confusing the network
+** code (if it cares).
+** makeconnection(host, port, mci, e, enough)
+** Make a connection to the named host on the given
+** port. Returns zero on success, else an exit status
+** describing the error.
+** host_map_lookup(map, hbuf, avp, pstat)
+** Convert the entry in hbuf into a canonical form.
+*/
+
+static DAEMON_T Daemons[MAXDAEMONS];
+static int NDaemons = 0; /* actual number of daemons */
+
+static time_t NextDiskSpaceCheck = 0;
+
+/*
+** GETREQUESTS -- open mail IPC port and get requests.
+**
+** Parameters:
+** e -- the current envelope.
+**
+** Returns:
+** pointer to flags.
+**
+** Side Effects:
+** Waits until some interesting activity occurs. When
+** it does, a child is created to process it, and the
+** parent waits for completion. Return from this
+** routine is always in the child. The file pointers
+** "InChannel" and "OutChannel" should be set to point
+** to the communication channel.
+** May restart persistent queue runners if they have ended
+** for some reason.
+*/
+
+BITMAP256 *
+getrequests(e)
+ ENVELOPE *e;
+{
+ int t;
+ int idx, curdaemon = -1;
+ int i, olddaemon = 0;
+#if XDEBUG
+ bool j_has_dot;
+#endif /* XDEBUG */
+ char status[MAXLINE];
+ SOCKADDR sa;
+ SOCKADDR_LEN_T len = sizeof sa;
+#if _FFR_QUEUE_RUN_PARANOIA
+ time_t lastrun;
+#endif /* _FFR_QUEUE_RUN_PARANOIA */
+# if NETUNIX
+ extern int ControlSocket;
+# endif /* NETUNIX */
+ extern ENVELOPE BlankEnvelope;
+ extern bool refuseconnections __P((char *, ENVELOPE *, int, bool));
+
+
+ /* initialize data for function that generates queue ids */
+ init_qid_alg();
+ for (idx = 0; idx < NDaemons; idx++)
+ {
+ Daemons[idx].d_port = setupdaemon(&(Daemons[idx].d_addr));
+ Daemons[idx].d_firsttime = true;
+ Daemons[idx].d_refuse_connections_until = (time_t) 0;
+ }
+
+ /*
+ ** Try to actually open the connection.
+ */
+
+ if (tTd(15, 1))
+ {
+ for (idx = 0; idx < NDaemons; idx++)
+ {
+ sm_dprintf("getrequests: daemon %s: port %d\n",
+ Daemons[idx].d_name,
+ ntohs(Daemons[idx].d_port));
+ }
+ }
+
+ /* get a socket for the SMTP connection */
+ for (idx = 0; idx < NDaemons; idx++)
+ Daemons[idx].d_socksize = opendaemonsocket(&Daemons[idx], true);
+
+ if (opencontrolsocket() < 0)
+ sm_syslog(LOG_WARNING, NOQID,
+ "daemon could not open control socket %s: %s",
+ ControlSocketName, sm_errstring(errno));
+
+ /* If there are any queue runners released reapchild() co-ord's */
+ (void) sm_signal(SIGCHLD, reapchild);
+
+ /* write the pid to file, command line args to syslog */
+ log_sendmail_pid(e);
+
+#if XDEBUG
+ {
+ char jbuf[MAXHOSTNAMELEN];
+
+ expand("\201j", jbuf, sizeof jbuf, e);
+ j_has_dot = strchr(jbuf, '.') != NULL;
+ }
+#endif /* XDEBUG */
+
+ /* Add parent process as first item */
+ proc_list_add(CurrentPid, "Sendmail daemon", PROC_DAEMON, 0, -1, NULL);
+
+ if (tTd(15, 1))
+ {
+ for (idx = 0; idx < NDaemons; idx++)
+ sm_dprintf("getrequests: daemon %s: %d\n",
+ Daemons[idx].d_name,
+ Daemons[idx].d_socket);
+ }
+
+ for (;;)
+ {
+ register pid_t pid;
+ auto SOCKADDR_LEN_T lotherend;
+ bool timedout = false;
+ bool control = false;
+ int save_errno;
+ int pipefd[2];
+ time_t now;
+#if STARTTLS
+ long seed;
+#endif /* STARTTLS */
+
+ /* see if we are rejecting connections */
+ (void) sm_blocksignal(SIGALRM);
+ CHECK_RESTART;
+
+ for (idx = 0; idx < NDaemons; idx++)
+ {
+ /*
+ ** XXX do this call outside the loop?
+ ** no: refuse_connections may sleep().
+ */
+
+ now = curtime();
+ if (now < Daemons[idx].d_refuse_connections_until)
+ continue;
+ if (bitnset(D_DISABLE, Daemons[idx].d_flags))
+ continue;
+ if (refuseconnections(Daemons[idx].d_name, e, idx,
+ curdaemon == idx))
+ {
+ if (Daemons[idx].d_socket >= 0)
+ {
+ /* close socket so peer fails quickly */
+ (void) close(Daemons[idx].d_socket);
+ Daemons[idx].d_socket = -1;
+ }
+
+ /* refuse connections for next 15 seconds */
+ Daemons[idx].d_refuse_connections_until = now + 15;
+ }
+ else if (Daemons[idx].d_socket < 0 ||
+ Daemons[idx].d_firsttime)
+ {
+ if (!Daemons[idx].d_firsttime && LogLevel > 8)
+ sm_syslog(LOG_INFO, NOQID,
+ "accepting connections again for daemon %s",
+ Daemons[idx].d_name);
+
+ /* arrange to (re)open the socket if needed */
+ (void) opendaemonsocket(&Daemons[idx], false);
+ Daemons[idx].d_firsttime = false;
+ }
+ }
+
+ /* May have been sleeping above, check again */
+ CHECK_RESTART;
+
+ getrequests_checkdiskspace(e);
+
+#if XDEBUG
+ /* check for disaster */
+ {
+ char jbuf[MAXHOSTNAMELEN];
+
+ expand("\201j", jbuf, sizeof jbuf, e);
+ if (!wordinclass(jbuf, 'w'))
+ {
+ dumpstate("daemon lost $j");
+ sm_syslog(LOG_ALERT, NOQID,
+ "daemon process doesn't have $j in $=w; see syslog");
+ abort();
+ }
+ else if (j_has_dot && strchr(jbuf, '.') == NULL)
+ {
+ dumpstate("daemon $j lost dot");
+ sm_syslog(LOG_ALERT, NOQID,
+ "daemon process $j lost dot; see syslog");
+ abort();
+ }
+ }
+#endif /* XDEBUG */
+
+#if 0
+ /*
+ ** Andrew Sun <asun@ieps-sun.ml.com> claims that this will
+ ** fix the SVr4 problem. But it seems to have gone away,
+ ** so is it worth doing this?
+ */
+
+ if (DaemonSocket >= 0 &&
+ SetNonBlocking(DaemonSocket, false) < 0)
+ log an error here;
+#endif /* 0 */
+ (void) sm_releasesignal(SIGALRM);
+
+ for (;;)
+ {
+ bool setproc = false;
+ int highest = -1;
+ fd_set readfds;
+ struct timeval timeout;
+
+ CHECK_RESTART;
+ FD_ZERO(&readfds);
+ for (idx = 0; idx < NDaemons; idx++)
+ {
+ /* wait for a connection */
+ if (Daemons[idx].d_socket >= 0)
+ {
+ if (!setproc &&
+ !bitnset(D_ETRNONLY,
+ Daemons[idx].d_flags))
+ {
+ sm_setproctitle(true, e,
+ "accepting connections");
+ setproc = true;
+ }
+ if (Daemons[idx].d_socket > highest)
+ highest = Daemons[idx].d_socket;
+ SM_FD_SET(Daemons[idx].d_socket,
+ &readfds);
+ }
+ }
+
+#if NETUNIX
+ if (ControlSocket >= 0)
+ {
+ if (ControlSocket > highest)
+ highest = ControlSocket;
+ SM_FD_SET(ControlSocket, &readfds);
+ }
+#endif /* NETUNIX */
+
+ timeout.tv_sec = 5;
+ timeout.tv_usec = 0;
+
+ t = select(highest + 1, FDSET_CAST &readfds,
+ NULL, NULL, &timeout);
+
+ /* Did someone signal while waiting? */
+ CHECK_RESTART;
+
+ curdaemon = -1;
+ if (doqueuerun())
+ {
+ (void) runqueue(true, false, false, false);
+#if _FFR_QUEUE_RUN_PARANOIA
+ lastrun = now;
+#endif /* _FFR_QUEUE_RUN_PARANOIA */
+ }
+#if _FFR_QUEUE_RUN_PARANOIA
+ else if (QueueIntvl > 0 &&
+ lastrun + QueueIntvl + 60 < now)
+ {
+
+ /*
+ ** set lastrun unconditionally to avoid
+ ** calling checkqueuerunner() all the time.
+ ** That's also why we currently ignore the
+ ** result of the function call.
+ */
+
+ (void) checkqueuerunner();
+ lastrun = now;
+ }
+#endif /* _FFR_QUEUE_RUN_PARANOIA */
+
+ if (t <= 0)
+ {
+ timedout = true;
+ break;
+ }
+
+ control = false;
+ errno = 0;
+
+ /* look "round-robin" for an active socket */
+ if ((idx = olddaemon + 1) >= NDaemons)
+ idx = 0;
+ for (i = 0; i < NDaemons; i++)
+ {
+ if (Daemons[idx].d_socket >= 0 &&
+ SM_FD_ISSET(Daemons[idx].d_socket,
+ &readfds))
+ {
+ lotherend = Daemons[idx].d_socksize;
+ memset(&RealHostAddr, '\0',
+ sizeof RealHostAddr);
+ t = accept(Daemons[idx].d_socket,
+ (struct sockaddr *)&RealHostAddr,
+ &lotherend);
+
+ /*
+ ** If remote side closes before
+ ** accept() finishes, sockaddr
+ ** might not be fully filled in.
+ */
+
+ if (t >= 0 &&
+ (lotherend == 0 ||
+# ifdef BSD4_4_SOCKADDR
+ RealHostAddr.sa.sa_len == 0 ||
+# endif /* BSD4_4_SOCKADDR */
+ RealHostAddr.sa.sa_family != Daemons[idx].d_addr.sa.sa_family))
+ {
+ (void) close(t);
+ t = -1;
+ errno = EINVAL;
+ }
+ olddaemon = curdaemon = idx;
+ break;
+ }
+ if (++idx >= NDaemons)
+ idx = 0;
+ }
+#if NETUNIX
+ if (curdaemon == -1 && ControlSocket >= 0 &&
+ SM_FD_ISSET(ControlSocket, &readfds))
+ {
+ struct sockaddr_un sa_un;
+
+ lotherend = sizeof sa_un;
+ memset(&sa_un, '\0', sizeof sa_un);
+ t = accept(ControlSocket,
+ (struct sockaddr *)&sa_un,
+ &lotherend);
+
+ /*
+ ** If remote side closes before
+ ** accept() finishes, sockaddr
+ ** might not be fully filled in.
+ */
+
+ if (t >= 0 &&
+ (lotherend == 0 ||
+# ifdef BSD4_4_SOCKADDR
+ sa_un.sun_len == 0 ||
+# endif /* BSD4_4_SOCKADDR */
+ sa_un.sun_family != AF_UNIX))
+ {
+ (void) close(t);
+ t = -1;
+ errno = EINVAL;
+ }
+ if (t >= 0)
+ control = true;
+ }
+#else /* NETUNIX */
+ if (curdaemon == -1)
+ {
+ /* No daemon to service */
+ continue;
+ }
+#endif /* NETUNIX */
+ if (t >= 0 || errno != EINTR)
+ break;
+ }
+ if (timedout)
+ {
+ timedout = false;
+ continue;
+ }
+ save_errno = errno;
+ (void) sm_blocksignal(SIGALRM);
+ if (t < 0)
+ {
+ errno = save_errno;
+
+ /* let's ignore these temporary errors */
+ if (save_errno == EINTR
+#ifdef EAGAIN
+ || save_errno == EAGAIN
+#endif /* EAGAIN */
+#ifdef ECONNABORTED
+ || save_errno == ECONNABORTED
+#endif /* ECONNABORTED */
+#ifdef EWOULDBLOCK
+ || save_errno == EWOULDBLOCK
+#endif /* EWOULDBLOCK */
+ )
+ continue;
+
+ syserr("getrequests: accept");
+
+ /* arrange to re-open the socket next time around */
+ (void) close(Daemons[curdaemon].d_socket);
+ Daemons[curdaemon].d_socket = -1;
+#if SO_REUSEADDR_IS_BROKEN
+ /*
+ ** Give time for bound socket to be released.
+ ** This creates a denial-of-service if you can
+ ** force accept() to fail on affected systems.
+ */
+
+ Daemons[curdaemon].d_refuse_connections_until = curtime() + 15;
+#endif /* SO_REUSEADDR_IS_BROKEN */
+ continue;
+ }
+
+ if (!control)
+ {
+ /* set some daemon related macros */
+ switch (Daemons[curdaemon].d_addr.sa.sa_family)
+ {
+ case AF_UNSPEC:
+ macdefine(&BlankEnvelope.e_macro, A_PERM,
+ macid("{daemon_family}"), "unspec");
+ break;
+#if _FFR_DAEMON_NETUNIX
+# if NETUNIX
+ case AF_UNIX:
+ macdefine(&BlankEnvelope.e_macro, A_PERM,
+ macid("{daemon_family}"), "local");
+ break;
+# endif /* NETUNIX */
+#endif /* _FFR_DAEMON_NETUNIX */
+#if NETINET
+ case AF_INET:
+ macdefine(&BlankEnvelope.e_macro, A_PERM,
+ macid("{daemon_family}"), "inet");
+ break;
+#endif /* NETINET */
+#if NETINET6
+ case AF_INET6:
+ macdefine(&BlankEnvelope.e_macro, A_PERM,
+ macid("{daemon_family}"), "inet6");
+ break;
+#endif /* NETINET6 */
+#if NETISO
+ case AF_ISO:
+ macdefine(&BlankEnvelope.e_macro, A_PERM,
+ macid("{daemon_family}"), "iso");
+ break;
+#endif /* NETISO */
+#if NETNS
+ case AF_NS:
+ macdefine(&BlankEnvelope.e_macro, A_PERM,
+ macid("{daemon_family}"), "ns");
+ break;
+#endif /* NETNS */
+#if NETX25
+ case AF_CCITT:
+ macdefine(&BlankEnvelope.e_macro, A_PERM,
+ macid("{daemon_family}"), "x.25");
+ break;
+#endif /* NETX25 */
+ }
+ macdefine(&BlankEnvelope.e_macro, A_PERM,
+ macid("{daemon_name}"),
+ Daemons[curdaemon].d_name);
+ if (Daemons[curdaemon].d_mflags != NULL)
+ macdefine(&BlankEnvelope.e_macro, A_PERM,
+ macid("{daemon_flags}"),
+ Daemons[curdaemon].d_mflags);
+ else
+ macdefine(&BlankEnvelope.e_macro, A_PERM,
+ macid("{daemon_flags}"), "");
+ }
+
+ /*
+ ** If connection rate is exceeded here, connection shall be
+ ** refused later by a new call after fork() by the
+ ** validate_connection() function. Closing the connection
+ ** at this point violates RFC 2821.
+ ** Do NOT remove this call, its side effects are needed.
+ */
+
+ connection_rate_check(&RealHostAddr, NULL);
+
+ /*
+ ** Create a subprocess to process the mail.
+ */
+
+ if (tTd(15, 2))
+ sm_dprintf("getrequests: forking (fd = %d)\n", t);
+
+ /*
+ ** Advance state of PRNG.
+ ** This is necessary because otherwise all child processes
+ ** will produce the same PRN sequence and hence the selection
+ ** of a queue directory (and other things, e.g., MX selection)
+ ** are not "really" random.
+ */
+#if STARTTLS
+ /* XXX get some better "random" data? */
+ seed = get_random();
+ RAND_seed((void *) &NextDiskSpaceCheck,
+ sizeof NextDiskSpaceCheck);
+ RAND_seed((void *) &now, sizeof now);
+ RAND_seed((void *) &seed, sizeof seed);
+#else /* STARTTLS */
+ (void) get_random();
+#endif /* STARTTLS */
+
+#if NAMED_BIND
+ /*
+ ** Update MX records for FallbackMX.
+ ** Let's hope this is fast otherwise we screw up the
+ ** response time.
+ */
+
+ if (FallbackMX != NULL)
+ (void) getfallbackmxrr(FallbackMX);
+#endif /* NAMED_BIND */
+
+ if (tTd(93, 100))
+ {
+ /* don't fork, handle connection in this process */
+ pid = 0;
+ pipefd[0] = pipefd[1] = -1;
+ }
+ else
+ {
+ /*
+ ** Create a pipe to keep the child from writing to
+ ** the socket until after the parent has closed
+ ** it. Otherwise the parent may hang if the child
+ ** has closed it first.
+ */
+
+ if (pipe(pipefd) < 0)
+ pipefd[0] = pipefd[1] = -1;
+
+ (void) sm_blocksignal(SIGCHLD);
+ pid = fork();
+ if (pid < 0)
+ {
+ syserr("daemon: cannot fork");
+ if (pipefd[0] != -1)
+ {
+ (void) close(pipefd[0]);
+ (void) close(pipefd[1]);
+ }
+ (void) sm_releasesignal(SIGCHLD);
+ (void) sleep(10);
+ (void) close(t);
+ continue;
+ }
+ }
+
+ if (pid == 0)
+ {
+ char *p;
+ SM_FILE_T *inchannel, *outchannel = NULL;
+
+ /*
+ ** CHILD -- return to caller.
+ ** Collect verified idea of sending host.
+ ** Verify calling user id if possible here.
+ */
+
+ /* Reset global flags */
+ RestartRequest = NULL;
+ RestartWorkGroup = false;
+ ShutdownRequest = NULL;
+ PendingSignal = 0;
+ CurrentPid = getpid();
+ close_sendmail_pid();
+
+ (void) sm_releasesignal(SIGALRM);
+ (void) sm_releasesignal(SIGCHLD);
+ (void) sm_signal(SIGCHLD, SIG_DFL);
+ (void) sm_signal(SIGHUP, SIG_DFL);
+ (void) sm_signal(SIGTERM, intsig);
+
+ /* turn on profiling */
+ /* SM_PROF(0); */
+
+ /*
+ ** Initialize exception stack and default exception
+ ** handler for child process.
+ */
+
+ sm_exc_newthread(fatal_error);
+
+ if (!control)
+ {
+ macdefine(&BlankEnvelope.e_macro, A_TEMP,
+ macid("{daemon_addr}"),
+ anynet_ntoa(&Daemons[curdaemon].d_addr));
+ (void) sm_snprintf(status, sizeof status, "%d",
+ ntohs(Daemons[curdaemon].d_port));
+ macdefine(&BlankEnvelope.e_macro, A_TEMP,
+ macid("{daemon_port}"), status);
+ }
+
+ for (idx = 0; idx < NDaemons; idx++)
+ {
+ if (Daemons[idx].d_socket >= 0)
+ (void) close(Daemons[idx].d_socket);
+ Daemons[idx].d_socket = -1;
+ }
+ clrcontrol();
+
+ /* Avoid SMTP daemon actions if control command */
+ if (control)
+ {
+ /* Add control socket process */
+ proc_list_add(CurrentPid,
+ "console socket child",
+ PROC_CONTROL_CHILD, 0, -1, NULL);
+ }
+ else
+ {
+ proc_list_clear();
+
+ /* clean up background delivery children */
+ (void) sm_signal(SIGCHLD, reapchild);
+
+ /* Add parent process as first child item */
+ proc_list_add(CurrentPid, "daemon child",
+ PROC_DAEMON_CHILD, 0, -1, NULL);
+
+ /* don't schedule queue runs if ETRN */
+ QueueIntvl = 0;
+#if _FFR_SS_PER_DAEMON
+ if (Daemons[curdaemon].d_supersafe !=
+ SAFE_NOTSET)
+ SuperSafe = Daemons[curdaemon].d_supersafe;
+#endif /* _FFR_SS_PER_DAEMON */
+#if _FFR_DM_PER_DAEMON
+ if (Daemons[curdaemon].d_dm != DM_NOTSET)
+ set_delivery_mode(
+ Daemons[curdaemon].d_dm, e);
+#endif /* _FFR_DM_PER_DAEMON */
+
+
+ sm_setproctitle(true, e, "startup with %s",
+ anynet_ntoa(&RealHostAddr));
+ }
+
+ if (pipefd[0] != -1)
+ {
+ auto char c;
+
+ /*
+ ** Wait for the parent to close the write end
+ ** of the pipe, which we will see as an EOF.
+ ** This guarantees that we won't write to the
+ ** socket until after the parent has closed
+ ** the pipe.
+ */
+
+ /* close the write end of the pipe */
+ (void) close(pipefd[1]);
+
+ /* we shouldn't be interrupted, but ... */
+ while (read(pipefd[0], &c, 1) < 0 &&
+ errno == EINTR)
+ continue;
+ (void) close(pipefd[0]);
+ }
+
+ /* control socket processing */
+ if (control)
+ {
+ control_command(t, e);
+ /* NOTREACHED */
+ exit(EX_SOFTWARE);
+ }
+
+ /* determine host name */
+ p = hostnamebyanyaddr(&RealHostAddr);
+ if (strlen(p) > MAXNAME) /* XXX - 1 ? */
+ p[MAXNAME] = '\0';
+ RealHostName = newstr(p);
+ if (RealHostName[0] == '[')
+ {
+ macdefine(&BlankEnvelope.e_macro, A_PERM,
+ macid("{client_resolve}"),
+ h_errno == TRY_AGAIN ? "TEMP" : "FAIL");
+ }
+ else
+ {
+ macdefine(&BlankEnvelope.e_macro, A_PERM,
+ macid("{client_resolve}"), "OK");
+ }
+ sm_setproctitle(true, e, "startup with %s", p);
+ markstats(e, NULL, STATS_CONNECT);
+
+ if ((inchannel = sm_io_open(SmFtStdiofd,
+ SM_TIME_DEFAULT,
+ (void *) &t,
+ SM_IO_RDONLY_B,
+ NULL)) == NULL ||
+ (t = dup(t)) < 0 ||
+ (outchannel = sm_io_open(SmFtStdiofd,
+ SM_TIME_DEFAULT,
+ (void *) &t,
+ SM_IO_WRONLY_B,
+ NULL)) == NULL)
+ {
+ syserr("cannot open SMTP server channel, fd=%d",
+ t);
+ finis(false, true, EX_OK);
+ }
+ sm_io_automode(inchannel, outchannel);
+
+ InChannel = inchannel;
+ OutChannel = outchannel;
+ DisConnected = false;
+
+#if XLA
+ if (!xla_host_ok(RealHostName))
+ {
+ message("421 4.4.5 Too many SMTP sessions for this host");
+ finis(false, true, EX_OK);
+ }
+#endif /* XLA */
+ /* find out name for interface of connection */
+ if (getsockname(sm_io_getinfo(InChannel, SM_IO_WHAT_FD,
+ NULL), &sa.sa, &len) == 0)
+ {
+ p = hostnamebyanyaddr(&sa);
+ if (tTd(15, 9))
+ sm_dprintf("getreq: got name %s\n", p);
+ macdefine(&BlankEnvelope.e_macro, A_TEMP,
+ macid("{if_name}"), p);
+
+ /*
+ ** Do this only if it is not the loopback
+ ** interface.
+ */
+
+ if (!isloopback(sa))
+ {
+ char *addr;
+ char family[5];
+
+ addr = anynet_ntoa(&sa);
+ (void) sm_snprintf(family,
+ sizeof(family),
+ "%d", sa.sa.sa_family);
+ macdefine(&BlankEnvelope.e_macro,
+ A_TEMP,
+ macid("{if_addr}"), addr);
+ macdefine(&BlankEnvelope.e_macro,
+ A_TEMP,
+ macid("{if_family}"), family);
+ if (tTd(15, 7))
+ sm_dprintf("getreq: got addr %s and family %s\n",
+ addr, family);
+ }
+ else
+ {
+ macdefine(&BlankEnvelope.e_macro,
+ A_PERM,
+ macid("{if_addr}"), NULL);
+ macdefine(&BlankEnvelope.e_macro,
+ A_PERM,
+ macid("{if_family}"), NULL);
+ }
+ }
+ else
+ {
+ if (tTd(15, 7))
+ sm_dprintf("getreq: getsockname failed\n");
+ macdefine(&BlankEnvelope.e_macro, A_PERM,
+ macid("{if_name}"), NULL);
+ macdefine(&BlankEnvelope.e_macro, A_PERM,
+ macid("{if_addr}"), NULL);
+ macdefine(&BlankEnvelope.e_macro, A_PERM,
+ macid("{if_family}"), NULL);
+ }
+ break;
+ }
+
+ /* parent -- keep track of children */
+ if (control)
+ {
+ (void) sm_snprintf(status, sizeof status,
+ "control socket server child");
+ proc_list_add(pid, status, PROC_CONTROL, 0, -1, NULL);
+ }
+ else
+ {
+ (void) sm_snprintf(status, sizeof status,
+ "SMTP server child for %s",
+ anynet_ntoa(&RealHostAddr));
+ proc_list_add(pid, status, PROC_DAEMON, 0, -1,
+ &RealHostAddr);
+ }
+ (void) sm_releasesignal(SIGCHLD);
+
+ /* close the read end of the synchronization pipe */
+ if (pipefd[0] != -1)
+ {
+ (void) close(pipefd[0]);
+ pipefd[0] = -1;
+ }
+
+ /* close the port so that others will hang (for a while) */
+ (void) close(t);
+
+ /* release the child by closing the read end of the sync pipe */
+ if (pipefd[1] != -1)
+ {
+ (void) close(pipefd[1]);
+ pipefd[1] = -1;
+ }
+ }
+ if (tTd(15, 2))
+ sm_dprintf("getreq: returning\n");
+
+#if MILTER
+ /* set the filters for this daemon */
+ if (Daemons[curdaemon].d_inputfilterlist != NULL)
+ {
+ for (i = 0;
+ (i < MAXFILTERS &&
+ Daemons[curdaemon].d_inputfilters[i] != NULL);
+ i++)
+ {
+ InputFilters[i] = Daemons[curdaemon].d_inputfilters[i];
+ }
+ if (i < MAXFILTERS)
+ InputFilters[i] = NULL;
+ }
+#endif /* MILTER */
+ return &Daemons[curdaemon].d_flags;
+}
+
+/*
+** GETREQUESTS_CHECKDISKSPACE -- check available diskspace.
+**
+** Parameters:
+** e -- envelope.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Modifies Daemon flags (D_ETRNONLY) if not enough disk space.
+*/
+
+static void
+getrequests_checkdiskspace(e)
+ ENVELOPE *e;
+{
+ bool logged = false;
+ int idx;
+ time_t now;
+
+ now = curtime();
+ if (now < NextDiskSpaceCheck)
+ return;
+
+ /* Check if there is available disk space in all queue groups. */
+ if (!enoughdiskspace(0, NULL))
+ {
+ for (idx = 0; idx < NDaemons; ++idx)
+ {
+ if (bitnset(D_ETRNONLY, Daemons[idx].d_flags))
+ continue;
+
+ /* log only if not logged before */
+ if (!logged)
+ {
+ if (LogLevel > 8)
+ sm_syslog(LOG_INFO, NOQID,
+ "rejecting new messages: min free: %ld",
+ MinBlocksFree);
+ sm_setproctitle(true, e,
+ "rejecting new messages: min free: %ld",
+ MinBlocksFree);
+ logged = true;
+ }
+ setbitn(D_ETRNONLY, Daemons[idx].d_flags);
+ }
+ }
+ else
+ {
+ for (idx = 0; idx < NDaemons; ++idx)
+ {
+ if (!bitnset(D_ETRNONLY, Daemons[idx].d_flags))
+ continue;
+
+ /* log only if not logged before */
+ if (!logged)
+ {
+ if (LogLevel > 8)
+ sm_syslog(LOG_INFO, NOQID,
+ "accepting new messages (again)");
+ logged = true;
+ }
+
+ /* title will be set later */
+ clrbitn(D_ETRNONLY, Daemons[idx].d_flags);
+ }
+ }
+
+ /* only check disk space once a minute */
+ NextDiskSpaceCheck = now + 60;
+}
+
+/*
+** OPENDAEMONSOCKET -- open SMTP socket
+**
+** Deals with setting all appropriate options.
+**
+** Parameters:
+** d -- the structure for the daemon to open.
+** firsttime -- set if this is the initial open.
+**
+** Returns:
+** Size in bytes of the daemon socket addr.
+**
+** Side Effects:
+** Leaves DaemonSocket set to the open socket.
+** Exits if the socket cannot be created.
+*/
+
+#define MAXOPENTRIES 10 /* maximum number of tries to open connection */
+
+static int
+opendaemonsocket(d, firsttime)
+ DAEMON_T *d;
+ bool firsttime;
+{
+ int on = 1;
+ int fdflags;
+ SOCKADDR_LEN_T socksize = 0;
+ int ntries = 0;
+ int save_errno;
+
+ if (tTd(15, 2))
+ sm_dprintf("opendaemonsocket(%s)\n", d->d_name);
+
+ do
+ {
+ if (ntries > 0)
+ (void) sleep(5);
+ if (firsttime || d->d_socket < 0)
+ {
+#if _FFR_DAEMON_NETUNIX
+# if NETUNIX
+ if (d->d_addr.sa.sa_family == AF_UNIX)
+ {
+ int rval;
+ long sff = SFF_SAFEDIRPATH|SFF_OPENASROOT|SFF_NOLINK|SFF_ROOTOK|SFF_EXECOK|SFF_CREAT;
+
+ /* if not safe, don't use it */
+ rval = safefile(d->d_addr.sunix.sun_path,
+ RunAsUid, RunAsGid,
+ RunAsUserName, sff,
+ S_IRUSR|S_IWUSR, NULL);
+ if (rval != 0)
+ {
+ save_errno = errno;
+ syserr("opendaemonsocket: daemon %s: unsafe domain socket %s",
+ d->d_name,
+ d->d_addr.sunix.sun_path);
+ goto fail;
+ }
+
+ /* Don't try to overtake an existing socket */
+ (void) unlink(d->d_addr.sunix.sun_path);
+ }
+# endif /* NETUNIX */
+#endif /* _FFR_DOMAIN_NETUNIX */
+ d->d_socket = socket(d->d_addr.sa.sa_family,
+ SOCK_STREAM, 0);
+ if (d->d_socket < 0)
+ {
+ save_errno = errno;
+ syserr("opendaemonsocket: daemon %s: can't create server SMTP socket",
+ d->d_name);
+ fail:
+ if (bitnset(D_OPTIONAL, d->d_flags) &&
+ (!transienterror(save_errno) ||
+ ntries >= MAXOPENTRIES - 1))
+ {
+ syserr("opendaemonsocket: daemon %s: optional socket disabled",
+ d->d_name);
+ setbitn(D_DISABLE, d->d_flags);
+ d->d_socket = -1;
+ return -1;
+ }
+ severe:
+ if (LogLevel > 0)
+ sm_syslog(LOG_ALERT, NOQID,
+ "daemon %s: problem creating SMTP socket",
+ d->d_name);
+ d->d_socket = -1;
+ continue;
+ }
+
+ if (SM_FD_SETSIZE > 0 && d->d_socket >= SM_FD_SETSIZE)
+ {
+ save_errno = EINVAL;
+ syserr("opendaemonsocket: daemon %s: server SMTP socket (%d) too large",
+ d->d_name, d->d_socket);
+ goto fail;
+ }
+
+ /* turn on network debugging? */
+ if (tTd(15, 101))
+ (void) setsockopt(d->d_socket, SOL_SOCKET,
+ SO_DEBUG, (char *)&on,
+ sizeof on);
+
+ (void) setsockopt(d->d_socket, SOL_SOCKET,
+ SO_REUSEADDR, (char *)&on, sizeof on);
+ (void) setsockopt(d->d_socket, SOL_SOCKET,
+ SO_KEEPALIVE, (char *)&on, sizeof on);
+
+#ifdef SO_RCVBUF
+ if (d->d_tcprcvbufsize > 0)
+ {
+ if (setsockopt(d->d_socket, SOL_SOCKET,
+ SO_RCVBUF,
+ (char *) &d->d_tcprcvbufsize,
+ sizeof(d->d_tcprcvbufsize)) < 0)
+ syserr("opendaemonsocket: daemon %s: setsockopt(SO_RCVBUF)", d->d_name);
+ }
+#endif /* SO_RCVBUF */
+#ifdef SO_SNDBUF
+ if (d->d_tcpsndbufsize > 0)
+ {
+ if (setsockopt(d->d_socket, SOL_SOCKET,
+ SO_SNDBUF,
+ (char *) &d->d_tcpsndbufsize,
+ sizeof(d->d_tcpsndbufsize)) < 0)
+ syserr("opendaemonsocket: daemon %s: setsockopt(SO_SNDBUF)", d->d_name);
+ }
+#endif /* SO_SNDBUF */
+
+ if ((fdflags = fcntl(d->d_socket, F_GETFD, 0)) == -1 ||
+ fcntl(d->d_socket, F_SETFD,
+ fdflags | FD_CLOEXEC) == -1)
+ {
+ save_errno = errno;
+ syserr("opendaemonsocket: daemon %s: failed to %s close-on-exec flag: %s",
+ d->d_name,
+ fdflags == -1 ? "get" : "set",
+ sm_errstring(save_errno));
+ (void) close(d->d_socket);
+ goto severe;
+ }
+
+ switch (d->d_addr.sa.sa_family)
+ {
+#if _FFR_DAEMON_NETUNIX
+# ifdef NETUNIX
+ case AF_UNIX:
+ socksize = sizeof d->d_addr.sunix;
+ break;
+# endif /* NETUNIX */
+#endif /* _FFR_DAEMON_NETUNIX */
+#if NETINET
+ case AF_INET:
+ socksize = sizeof d->d_addr.sin;
+ break;
+#endif /* NETINET */
+
+#if NETINET6
+ case AF_INET6:
+ socksize = sizeof d->d_addr.sin6;
+ break;
+#endif /* NETINET6 */
+
+#if NETISO
+ case AF_ISO:
+ socksize = sizeof d->d_addr.siso;
+ break;
+#endif /* NETISO */
+
+ default:
+ socksize = sizeof d->d_addr;
+ break;
+ }
+
+ if (bind(d->d_socket, &d->d_addr.sa, socksize) < 0)
+ {
+ /* probably another daemon already */
+ save_errno = errno;
+ syserr("opendaemonsocket: daemon %s: cannot bind",
+ d->d_name);
+ (void) close(d->d_socket);
+ goto fail;
+ }
+ }
+ if (!firsttime &&
+ listen(d->d_socket, d->d_listenqueue) < 0)
+ {
+ save_errno = errno;
+ syserr("opendaemonsocket: daemon %s: cannot listen",
+ d->d_name);
+ (void) close(d->d_socket);
+ goto severe;
+ }
+ return socksize;
+ } while (ntries++ < MAXOPENTRIES && transienterror(save_errno));
+ syserr("!opendaemonsocket: daemon %s: server SMTP socket wedged: exiting",
+ d->d_name);
+ /* NOTREACHED */
+ return -1; /* avoid compiler warning on IRIX */
+}
+/*
+** SETUPDAEMON -- setup socket for daemon
+**
+** Parameters:
+** daemonaddr -- socket for daemon
+**
+** Returns:
+** port number on which daemon should run
+**
+*/
+
+static unsigned short
+setupdaemon(daemonaddr)
+ SOCKADDR *daemonaddr;
+{
+ unsigned short port;
+
+ /*
+ ** Set up the address for the mailer.
+ */
+
+ if (daemonaddr->sa.sa_family == AF_UNSPEC)
+ {
+ memset(daemonaddr, '\0', sizeof *daemonaddr);
+#if NETINET
+ daemonaddr->sa.sa_family = AF_INET;
+#endif /* NETINET */
+ }
+
+ switch (daemonaddr->sa.sa_family)
+ {
+#if NETINET
+ case AF_INET:
+ if (daemonaddr->sin.sin_addr.s_addr == 0)
+ daemonaddr->sin.sin_addr.s_addr = INADDR_ANY;
+ port = daemonaddr->sin.sin_port;
+ break;
+#endif /* NETINET */
+
+#if NETINET6
+ case AF_INET6:
+ if (IN6_IS_ADDR_UNSPECIFIED(&daemonaddr->sin6.sin6_addr))
+ daemonaddr->sin6.sin6_addr = in6addr_any;
+ port = daemonaddr->sin6.sin6_port;
+ break;
+#endif /* NETINET6 */
+
+ default:
+ /* unknown protocol */
+ port = 0;
+ break;
+ }
+ if (port == 0)
+ {
+#ifdef NO_GETSERVBYNAME
+ port = htons(25);
+#else /* NO_GETSERVBYNAME */
+ {
+ register struct servent *sp;
+
+ sp = getservbyname("smtp", "tcp");
+ if (sp == NULL)
+ {
+ syserr("554 5.3.5 service \"smtp\" unknown");
+ port = htons(25);
+ }
+ else
+ port = sp->s_port;
+ }
+#endif /* NO_GETSERVBYNAME */
+ }
+
+ switch (daemonaddr->sa.sa_family)
+ {
+#if NETINET
+ case AF_INET:
+ daemonaddr->sin.sin_port = port;
+ break;
+#endif /* NETINET */
+
+#if NETINET6
+ case AF_INET6:
+ daemonaddr->sin6.sin6_port = port;
+ break;
+#endif /* NETINET6 */
+
+ default:
+ /* unknown protocol */
+ break;
+ }
+ return port;
+}
+/*
+** CLRDAEMON -- reset the daemon connection
+**
+** Parameters:
+** none.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** releases any resources used by the passive daemon.
+*/
+
+void
+clrdaemon()
+{
+ int i;
+
+ for (i = 0; i < NDaemons; i++)
+ {
+ if (Daemons[i].d_socket >= 0)
+ (void) close(Daemons[i].d_socket);
+ Daemons[i].d_socket = -1;
+ }
+}
+
+/*
+** GETMODIFIERS -- get modifier flags
+**
+** Parameters:
+** v -- the modifiers (input text line).
+** modifiers -- pointer to flag field to represent modifiers.
+**
+** Returns:
+** (xallocat()ed) string representation of modifiers.
+**
+** Side Effects:
+** fills in modifiers.
+*/
+
+char *
+getmodifiers(v, modifiers)
+ char *v;
+ BITMAP256 modifiers;
+{
+ int l;
+ char *h, *f, *flags;
+
+ /* maximum length of flags: upper case Option -> "OO " */
+ l = 3 * strlen(v) + 3;
+
+ /* is someone joking? */
+ if (l < 0 || l > 256)
+ {
+ if (LogLevel > 2)
+ sm_syslog(LOG_ERR, NOQID,
+ "getmodifiers too long, ignored");
+ return NULL;
+ }
+ flags = xalloc(l);
+ f = flags;
+ clrbitmap(modifiers);
+ for (h = v; *h != '\0'; h++)
+ {
+ if (isascii(*h) && !isspace(*h) && isprint(*h))
+ {
+ setbitn(*h, modifiers);
+ if (flags != f)
+ *flags++ = ' ';
+ *flags++ = *h;
+ if (isupper(*h))
+ *flags++ = *h;
+ }
+ }
+ *flags++ = '\0';
+ return f;
+}
+
+/*
+** CHKDAEMONMODIFIERS -- check whether all daemons have set a flag.
+**
+** Parameters:
+** flag -- the flag to test.
+**
+** Returns:
+** true iff all daemons have set flag.
+*/
+
+bool
+chkdaemonmodifiers(flag)
+ int flag;
+{
+ int i;
+
+ for (i = 0; i < NDaemons; i++)
+ if (!bitnset((char) flag, Daemons[i].d_flags))
+ return false;
+ return true;
+}
+
+/*
+** SETSOCKADDROPTIONS -- set options for SOCKADDR (daemon or client)
+**
+** Parameters:
+** p -- the options line.
+** d -- the daemon structure to fill in.
+**
+** Returns:
+** none.
+*/
+
+static void
+setsockaddroptions(p, d)
+ char *p;
+ DAEMON_T *d;
+{
+#if NETISO
+ short portno;
+#endif /* NETISO */
+ char *port = NULL;
+ char *addr = NULL;
+
+#if NETINET
+ if (d->d_addr.sa.sa_family == AF_UNSPEC)
+ d->d_addr.sa.sa_family = AF_INET;
+#endif /* NETINET */
+
+ while (p != NULL)
+ {
+ register char *f;
+ register char *v;
+
+ while (isascii(*p) && isspace(*p))
+ p++;
+ if (*p == '\0')
+ break;
+ f = p;
+ p = strchr(p, ',');
+ if (p != NULL)
+ *p++ = '\0';
+ v = strchr(f, '=');
+ if (v == NULL)
+ continue;
+ while (isascii(*++v) && isspace(*v))
+ continue;
+ if (isascii(*f) && islower(*f))
+ *f = toupper(*f);
+#if _FFR_SS_PER_DAEMON
+ d->d_supersafe = SAFE_NOTSET;
+#endif /* _FFR_SS_PER_DAEMON */
+#if _FFR_DM_PER_DAEMON
+ d->d_dm = DM_NOTSET;
+#endif /* _FFR_DM_PER_DAEMON */
+
+ switch (*f)
+ {
+ case 'A': /* address */
+ addr = v;
+ break;
+
+#if _FFR_DM_PER_DAEMON
+ case 'D': /* DeliveryMode */
+ switch (*v)
+ {
+ case SM_QUEUE:
+ case SM_DEFER:
+ case SM_DELIVER:
+ case SM_FORK:
+ d->d_dm = *v;
+ break;
+ default:
+ syserr("554 5.3.5 Unknown delivery mode %c",
+ *v);
+ break;
+ }
+ break;
+#endif /* _FFR_DM_PER_DAEMON */
+
+ case 'F': /* address family */
+ if (isascii(*v) && isdigit(*v))
+ d->d_addr.sa.sa_family = atoi(v);
+#if _FFR_DAEMON_NETUNIX
+# ifdef NETUNIX
+ else if (sm_strcasecmp(v, "unix") == 0 ||
+ sm_strcasecmp(v, "local") == 0)
+ d->d_addr.sa.sa_family = AF_UNIX;
+# endif /* NETUNIX */
+#endif /* _FFR_DAEMON_NETUNIX */
+#if NETINET
+ else if (sm_strcasecmp(v, "inet") == 0)
+ d->d_addr.sa.sa_family = AF_INET;
+#endif /* NETINET */
+#if NETINET6
+ else if (sm_strcasecmp(v, "inet6") == 0)
+ d->d_addr.sa.sa_family = AF_INET6;
+#endif /* NETINET6 */
+#if NETISO
+ else if (sm_strcasecmp(v, "iso") == 0)
+ d->d_addr.sa.sa_family = AF_ISO;
+#endif /* NETISO */
+#if NETNS
+ else if (sm_strcasecmp(v, "ns") == 0)
+ d->d_addr.sa.sa_family = AF_NS;
+#endif /* NETNS */
+#if NETX25
+ else if (sm_strcasecmp(v, "x.25") == 0)
+ d->d_addr.sa.sa_family = AF_CCITT;
+#endif /* NETX25 */
+ else
+ syserr("554 5.3.5 Unknown address family %s in Family=option",
+ v);
+ break;
+
+#if MILTER
+ case 'I':
+ d->d_inputfilterlist = v;
+ break;
+#endif /* MILTER */
+
+ case 'L': /* listen queue size */
+ d->d_listenqueue = atoi(v);
+ break;
+
+ case 'M': /* modifiers (flags) */
+ d->d_mflags = getmodifiers(v, d->d_flags);
+ break;
+
+ case 'N': /* name */
+ d->d_name = v;
+ break;
+
+ case 'P': /* port */
+ port = v;
+ break;
+
+ case 'R': /* receive buffer size */
+ d->d_tcprcvbufsize = atoi(v);
+ break;
+
+ case 'S': /* send buffer size */
+ d->d_tcpsndbufsize = atoi(v);
+ break;
+
+#if _FFR_SS_PER_DAEMON
+ case 'T': /* SuperSafe */
+ if (tolower(*v) == 'i')
+ d->d_supersafe = SAFE_INTERACTIVE;
+ else if (tolower(*v) == 'p')
+# if MILTER
+ d->d_supersafe = SAFE_REALLY_POSTMILTER;
+# else /* MILTER */
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Warning: SuperSafe=PostMilter requires Milter support (-DMILTER)\n");
+# endif /* MILTER */
+ else
+ d->d_supersafe = atobool(v) ? SAFE_REALLY
+ : SAFE_NO;
+ break;
+#endif /* _FFR_SS_PER_DAEMON */
+
+ default:
+ syserr("554 5.3.5 PortOptions parameter \"%s\" unknown",
+ f);
+ }
+ }
+
+ /* Check addr and port after finding family */
+ if (addr != NULL)
+ {
+ switch (d->d_addr.sa.sa_family)
+ {
+#if _FFR_DAEMON_NETUNIX
+# if NETUNIX
+ case AF_UNIX:
+ if (strlen(addr) >= sizeof(d->d_addr.sunix.sun_path))
+ {
+ errno = ENAMETOOLONG;
+ syserr("setsockaddroptions: domain socket name too long: %s > %d",
+ addr, sizeof(d->d_addr.sunix.sun_path));
+ break;
+ }
+
+ /* file safety check done in opendaemonsocket() */
+ (void) memset(&d->d_addr.sunix.sun_path, '\0',
+ sizeof(d->d_addr.sunix.sun_path));
+ (void) sm_strlcpy((char *)&d->d_addr.sunix.sun_path,
+ addr,
+ sizeof(d->d_addr.sunix.sun_path));
+ break;
+# endif /* NETUNIX */
+#endif /* _FFR_DAEMON_NETUNIX */
+#if NETINET
+ case AF_INET:
+ if (!isascii(*addr) || !isdigit(*addr) ||
+ ((d->d_addr.sin.sin_addr.s_addr = inet_addr(addr))
+ == INADDR_NONE))
+ {
+ register struct hostent *hp;
+
+ hp = sm_gethostbyname(addr, AF_INET);
+ if (hp == NULL)
+ syserr("554 5.3.0 host \"%s\" unknown",
+ addr);
+ else
+ {
+ while (*(hp->h_addr_list) != NULL &&
+ hp->h_addrtype != AF_INET)
+ hp->h_addr_list++;
+ if (*(hp->h_addr_list) == NULL)
+ syserr("554 5.3.0 host \"%s\" unknown",
+ addr);
+ else
+ memmove(&d->d_addr.sin.sin_addr,
+ *(hp->h_addr_list),
+ INADDRSZ);
+# if NETINET6
+ freehostent(hp);
+ hp = NULL;
+# endif /* NETINET6 */
+ }
+ }
+ break;
+#endif /* NETINET */
+
+#if NETINET6
+ case AF_INET6:
+ if (anynet_pton(AF_INET6, addr,
+ &d->d_addr.sin6.sin6_addr) != 1)
+ {
+ register struct hostent *hp;
+
+ hp = sm_gethostbyname(addr, AF_INET6);
+ if (hp == NULL)
+ syserr("554 5.3.0 host \"%s\" unknown",
+ addr);
+ else
+ {
+ while (*(hp->h_addr_list) != NULL &&
+ hp->h_addrtype != AF_INET6)
+ hp->h_addr_list++;
+ if (*(hp->h_addr_list) == NULL)
+ syserr("554 5.3.0 host \"%s\" unknown",
+ addr);
+ else
+ memmove(&d->d_addr.sin6.sin6_addr,
+ *(hp->h_addr_list),
+ IN6ADDRSZ);
+ freehostent(hp);
+ hp = NULL;
+ }
+ }
+ break;
+#endif /* NETINET6 */
+
+ default:
+ syserr("554 5.3.5 address= option unsupported for family %d",
+ d->d_addr.sa.sa_family);
+ break;
+ }
+ }
+
+ if (port != NULL)
+ {
+ switch (d->d_addr.sa.sa_family)
+ {
+#if NETINET
+ case AF_INET:
+ if (isascii(*port) && isdigit(*port))
+ d->d_addr.sin.sin_port = htons((unsigned short)
+ atoi((const char *) port));
+ else
+ {
+# ifdef NO_GETSERVBYNAME
+ syserr("554 5.3.5 invalid port number: %s",
+ port);
+# else /* NO_GETSERVBYNAME */
+ register struct servent *sp;
+
+ sp = getservbyname(port, "tcp");
+ if (sp == NULL)
+ syserr("554 5.3.5 service \"%s\" unknown",
+ port);
+ else
+ d->d_addr.sin.sin_port = sp->s_port;
+# endif /* NO_GETSERVBYNAME */
+ }
+ break;
+#endif /* NETINET */
+
+#if NETINET6
+ case AF_INET6:
+ if (isascii(*port) && isdigit(*port))
+ d->d_addr.sin6.sin6_port = htons((unsigned short)
+ atoi(port));
+ else
+ {
+# ifdef NO_GETSERVBYNAME
+ syserr("554 5.3.5 invalid port number: %s",
+ port);
+# else /* NO_GETSERVBYNAME */
+ register struct servent *sp;
+
+ sp = getservbyname(port, "tcp");
+ if (sp == NULL)
+ syserr("554 5.3.5 service \"%s\" unknown",
+ port);
+ else
+ d->d_addr.sin6.sin6_port = sp->s_port;
+# endif /* NO_GETSERVBYNAME */
+ }
+ break;
+#endif /* NETINET6 */
+
+#if NETISO
+ case AF_ISO:
+ /* assume two byte transport selector */
+ if (isascii(*port) && isdigit(*port))
+ portno = htons((unsigned short) atoi(port));
+ else
+ {
+# ifdef NO_GETSERVBYNAME
+ syserr("554 5.3.5 invalid port number: %s",
+ port);
+# else /* NO_GETSERVBYNAME */
+ register struct servent *sp;
+
+ sp = getservbyname(port, "tcp");
+ if (sp == NULL)
+ syserr("554 5.3.5 service \"%s\" unknown",
+ port);
+ else
+ portno = sp->s_port;
+# endif /* NO_GETSERVBYNAME */
+ }
+ memmove(TSEL(&d->d_addr.siso),
+ (char *) &portno, 2);
+ break;
+#endif /* NETISO */
+
+ default:
+ syserr("554 5.3.5 Port= option unsupported for family %d",
+ d->d_addr.sa.sa_family);
+ break;
+ }
+ }
+}
+/*
+** SETDAEMONOPTIONS -- set options for running the MTA daemon
+**
+** Parameters:
+** p -- the options line.
+**
+** Returns:
+** true if successful, false otherwise.
+**
+** Side Effects:
+** increments number of daemons.
+*/
+
+#define DEF_LISTENQUEUE 10
+
+struct dflags
+{
+ char *d_name;
+ int d_flag;
+};
+
+static struct dflags DaemonFlags[] =
+{
+ { "AUTHREQ", D_AUTHREQ },
+ { "BINDIF", D_BINDIF },
+ { "CANONREQ", D_CANONREQ },
+ { "IFNHELO", D_IFNHELO },
+ { "FQMAIL", D_FQMAIL },
+ { "FQRCPT", D_FQRCPT },
+ { "SMTPS", D_SMTPS },
+ { "UNQUALOK", D_UNQUALOK },
+ { "NOAUTH", D_NOAUTH },
+ { "NOCANON", D_NOCANON },
+ { "NOETRN", D_NOETRN },
+ { "NOTLS", D_NOTLS },
+ { "ETRNONLY", D_ETRNONLY },
+ { "OPTIONAL", D_OPTIONAL },
+ { "DISABLE", D_DISABLE },
+ { "ISSET", D_ISSET },
+ { NULL, 0 }
+};
+
+static void
+printdaemonflags(d)
+ DAEMON_T *d;
+{
+ register struct dflags *df;
+ bool first = true;
+
+ for (df = DaemonFlags; df->d_name != NULL; df++)
+ {
+ if (!bitnset(df->d_flag, d->d_flags))
+ continue;
+ if (first)
+ sm_dprintf("<%s", df->d_name);
+ else
+ sm_dprintf(",%s", df->d_name);
+ first = false;
+ }
+ if (!first)
+ sm_dprintf(">");
+}
+
+bool
+setdaemonoptions(p)
+ register char *p;
+{
+ if (NDaemons >= MAXDAEMONS)
+ return false;
+ Daemons[NDaemons].d_socket = -1;
+ Daemons[NDaemons].d_listenqueue = DEF_LISTENQUEUE;
+ clrbitmap(Daemons[NDaemons].d_flags);
+ setsockaddroptions(p, &Daemons[NDaemons]);
+
+#if MILTER
+ if (Daemons[NDaemons].d_inputfilterlist != NULL)
+ Daemons[NDaemons].d_inputfilterlist = newstr(Daemons[NDaemons].d_inputfilterlist);
+#endif /* MILTER */
+
+ if (Daemons[NDaemons].d_name != NULL)
+ Daemons[NDaemons].d_name = newstr(Daemons[NDaemons].d_name);
+ else
+ {
+ char num[30];
+
+ (void) sm_snprintf(num, sizeof num, "Daemon%d", NDaemons);
+ Daemons[NDaemons].d_name = newstr(num);
+ }
+
+ if (tTd(37, 1))
+ {
+ sm_dprintf("Daemon %s flags: ", Daemons[NDaemons].d_name);
+ printdaemonflags(&Daemons[NDaemons]);
+ sm_dprintf("\n");
+ }
+ ++NDaemons;
+ return true;
+}
+/*
+** INITDAEMON -- initialize daemon if not yet done.
+**
+** Parameters:
+** none
+**
+** Returns:
+** none
+**
+** Side Effects:
+** initializes structure for one daemon.
+*/
+
+void
+initdaemon()
+{
+ if (NDaemons == 0)
+ {
+ Daemons[NDaemons].d_socket = -1;
+ Daemons[NDaemons].d_listenqueue = DEF_LISTENQUEUE;
+ Daemons[NDaemons].d_name = "Daemon0";
+ NDaemons = 1;
+ }
+}
+/*
+** SETCLIENTOPTIONS -- set options for running the client
+**
+** Parameters:
+** p -- the options line.
+**
+** Returns:
+** none.
+*/
+
+static DAEMON_T ClientSettings[AF_MAX + 1];
+
+void
+setclientoptions(p)
+ register char *p;
+{
+ int family;
+ DAEMON_T d;
+
+ memset(&d, '\0', sizeof d);
+ setsockaddroptions(p, &d);
+
+ /* grab what we need */
+ family = d.d_addr.sa.sa_family;
+ STRUCTCOPY(d, ClientSettings[family]);
+ setbitn(D_ISSET, ClientSettings[family].d_flags); /* mark as set */
+ if (d.d_name != NULL)
+ ClientSettings[family].d_name = newstr(d.d_name);
+ else
+ {
+ char num[30];
+
+ (void) sm_snprintf(num, sizeof num, "Client%d", family);
+ ClientSettings[family].d_name = newstr(num);
+ }
+}
+/*
+** ADDR_FAMILY -- determine address family from address
+**
+** Parameters:
+** addr -- the string representation of the address
+**
+** Returns:
+** AF_INET, AF_INET6 or AF_UNSPEC
+**
+** Side Effects:
+** none.
+*/
+
+static int
+addr_family(addr)
+ char *addr;
+{
+#if NETINET6
+ SOCKADDR clt_addr;
+#endif /* NETINET6 */
+
+#if NETINET
+ if (inet_addr(addr) != INADDR_NONE)
+ {
+ if (tTd(16, 9))
+ sm_dprintf("addr_family(%s): INET\n", addr);
+ return AF_INET;
+ }
+#endif /* NETINET */
+#if NETINET6
+ if (anynet_pton(AF_INET6, addr, &clt_addr.sin6.sin6_addr) == 1)
+ {
+ if (tTd(16, 9))
+ sm_dprintf("addr_family(%s): INET6\n", addr);
+ return AF_INET6;
+ }
+#endif /* NETINET6 */
+#if _FFR_DAEMON_NETUNIX
+# if NETUNIX
+ if (*addr == '/')
+ {
+ if (tTd(16, 9))
+ sm_dprintf("addr_family(%s): LOCAL\n", addr);
+ return AF_UNIX;
+ }
+# endif /* NETUNIX */
+#endif /* _FFR_DAEMON_NETUNIX */
+ if (tTd(16, 9))
+ sm_dprintf("addr_family(%s): UNSPEC\n", addr);
+ return AF_UNSPEC;
+}
+
+/*
+** CHKCLIENTMODIFIERS -- check whether all clients have set a flag.
+**
+** Parameters:
+** flag -- the flag to test.
+**
+** Returns:
+** true iff all configured clients have set the flag.
+*/
+
+bool
+chkclientmodifiers(flag)
+ int flag;
+{
+ int i;
+ bool flagisset;
+
+ flagisset = false;
+ for (i = 0; i < AF_MAX; i++)
+ {
+ if (bitnset(D_ISSET, ClientSettings[i].d_flags))
+ {
+ if (!bitnset((char) flag, ClientSettings[i].d_flags))
+ return false;
+ flagisset = true;
+ }
+ }
+ return flagisset;
+}
+
+#if MILTER
+/*
+** SETUP_DAEMON_FILTERS -- Parse per-socket filters
+**
+** Parameters:
+** none
+**
+** Returns:
+** none
+*/
+
+void
+setup_daemon_milters()
+{
+ int idx;
+
+ if (OpMode == MD_SMTP)
+ {
+ /* no need to configure the daemons */
+ return;
+ }
+
+ for (idx = 0; idx < NDaemons; idx++)
+ {
+ if (Daemons[idx].d_inputfilterlist != NULL)
+ {
+ milter_config(Daemons[idx].d_inputfilterlist,
+ Daemons[idx].d_inputfilters,
+ MAXFILTERS);
+ }
+ }
+}
+#endif /* MILTER */
+/*
+** MAKECONNECTION -- make a connection to an SMTP socket on a machine.
+**
+** Parameters:
+** host -- the name of the host.
+** port -- the port number to connect to.
+** mci -- a pointer to the mail connection information
+** structure to be filled in.
+** e -- the current envelope.
+** enough -- time at which to stop further connection attempts.
+** (0 means no limit)
+**
+** Returns:
+** An exit code telling whether the connection could be
+** made and if not why not.
+**
+** Side Effects:
+** none.
+*/
+
+static jmp_buf CtxConnectTimeout;
+
+SOCKADDR CurHostAddr; /* address of current host */
+
+int
+makeconnection(host, port, mci, e, enough)
+ char *host;
+ volatile unsigned int port;
+ register MCI *mci;
+ ENVELOPE *e;
+ time_t enough;
+{
+ register volatile int addrno = 0;
+ volatile int s;
+ register struct hostent *volatile hp = (struct hostent *) NULL;
+ SOCKADDR addr;
+ SOCKADDR clt_addr;
+ int save_errno = 0;
+ volatile SOCKADDR_LEN_T addrlen;
+ volatile bool firstconnect;
+ SM_EVENT *volatile ev = NULL;
+#if NETINET6
+ volatile bool v6found = false;
+#endif /* NETINET6 */
+ volatile int family = InetMode;
+ SOCKADDR_LEN_T len;
+ volatile SOCKADDR_LEN_T socksize = 0;
+ volatile bool clt_bind;
+ BITMAP256 d_flags;
+ char *p;
+ extern ENVELOPE BlankEnvelope;
+
+ /* retranslate {daemon_flags} into bitmap */
+ clrbitmap(d_flags);
+ if ((p = macvalue(macid("{daemon_flags}"), e)) != NULL)
+ {
+ for (; *p != '\0'; p++)
+ {
+ if (!(isascii(*p) && isspace(*p)))
+ setbitn(bitidx(*p), d_flags);
+ }
+ }
+
+#if NETINET6
+ v4retry:
+#endif /* NETINET6 */
+ clt_bind = false;
+
+ /* Set up the address for outgoing connection. */
+ if (bitnset(D_BINDIF, d_flags) &&
+ (p = macvalue(macid("{if_addr}"), e)) != NULL &&
+ *p != '\0')
+ {
+#if NETINET6
+ char p6[INET6_ADDRSTRLEN];
+#endif /* NETINET6 */
+
+ memset(&clt_addr, '\0', sizeof clt_addr);
+
+ /* infer the address family from the address itself */
+ clt_addr.sa.sa_family = addr_family(p);
+ switch (clt_addr.sa.sa_family)
+ {
+#if NETINET
+ case AF_INET:
+ clt_addr.sin.sin_addr.s_addr = inet_addr(p);
+ if (clt_addr.sin.sin_addr.s_addr != INADDR_NONE &&
+ clt_addr.sin.sin_addr.s_addr != INADDR_LOOPBACK)
+ {
+ clt_bind = true;
+ socksize = sizeof (struct sockaddr_in);
+ }
+ break;
+#endif /* NETINET */
+
+#if NETINET6
+ case AF_INET6:
+ if (inet_addr(p) != INADDR_NONE)
+ (void) sm_snprintf(p6, sizeof p6,
+ "IPv6:::ffff:%s", p);
+ else
+ (void) sm_strlcpy(p6, p, sizeof p6);
+ if (anynet_pton(AF_INET6, p6,
+ &clt_addr.sin6.sin6_addr) == 1 &&
+ !IN6_IS_ADDR_LOOPBACK(&clt_addr.sin6.sin6_addr))
+ {
+ clt_bind = true;
+ socksize = sizeof (struct sockaddr_in6);
+ }
+ break;
+#endif /* NETINET6 */
+
+#if 0
+ default:
+ syserr("554 5.3.5 Address= option unsupported for family %d",
+ clt_addr.sa.sa_family);
+ break;
+#endif /* 0 */
+ }
+ if (clt_bind)
+ family = clt_addr.sa.sa_family;
+ }
+
+ /* D_BINDIF not set or not available, fallback to ClientPortOptions */
+ if (!clt_bind)
+ {
+ STRUCTCOPY(ClientSettings[family].d_addr, clt_addr);
+ switch (clt_addr.sa.sa_family)
+ {
+#if NETINET
+ case AF_INET:
+ if (clt_addr.sin.sin_addr.s_addr == 0)
+ clt_addr.sin.sin_addr.s_addr = INADDR_ANY;
+ else
+ clt_bind = true;
+ if (clt_addr.sin.sin_port != 0)
+ clt_bind = true;
+ socksize = sizeof (struct sockaddr_in);
+ break;
+#endif /* NETINET */
+#if NETINET6
+ case AF_INET6:
+ if (IN6_IS_ADDR_UNSPECIFIED(&clt_addr.sin6.sin6_addr))
+ clt_addr.sin6.sin6_addr = in6addr_any;
+ else
+ clt_bind = true;
+ socksize = sizeof (struct sockaddr_in6);
+ if (clt_addr.sin6.sin6_port != 0)
+ clt_bind = true;
+ break;
+#endif /* NETINET6 */
+#if NETISO
+ case AF_ISO:
+ socksize = sizeof clt_addr.siso;
+ clt_bind = true;
+ break;
+#endif /* NETISO */
+ default:
+ break;
+ }
+ }
+
+ /*
+ ** Set up the address for the mailer.
+ ** Accept "[a.b.c.d]" syntax for host name.
+ */
+
+ SM_SET_H_ERRNO(0);
+ errno = 0;
+ memset(&CurHostAddr, '\0', sizeof CurHostAddr);
+ memset(&addr, '\0', sizeof addr);
+ SmtpPhase = mci->mci_phase = "initial connection";
+ CurHostName = host;
+
+ if (host[0] == '[')
+ {
+ p = strchr(host, ']');
+ if (p != NULL)
+ {
+#if NETINET
+ unsigned long hid = INADDR_NONE;
+#endif /* NETINET */
+#if NETINET6
+ struct sockaddr_in6 hid6;
+#endif /* NETINET6 */
+
+ *p = '\0';
+#if NETINET6
+ memset(&hid6, '\0', sizeof hid6);
+#endif /* NETINET6 */
+#if NETINET
+ if (family == AF_INET &&
+ (hid = inet_addr(&host[1])) != INADDR_NONE)
+ {
+ addr.sin.sin_family = AF_INET;
+ addr.sin.sin_addr.s_addr = hid;
+ }
+ else
+#endif /* NETINET */
+#if NETINET6
+ if (family == AF_INET6 &&
+ anynet_pton(AF_INET6, &host[1],
+ &hid6.sin6_addr) == 1)
+ {
+ addr.sin6.sin6_family = AF_INET6;
+ addr.sin6.sin6_addr = hid6.sin6_addr;
+ }
+ else
+#endif /* NETINET6 */
+ {
+ /* try it as a host name (avoid MX lookup) */
+ hp = sm_gethostbyname(&host[1], family);
+ if (hp == NULL && p[-1] == '.')
+ {
+#if NAMED_BIND
+ int oldopts = _res.options;
+
+ _res.options &= ~(RES_DEFNAMES|RES_DNSRCH);
+#endif /* NAMED_BIND */
+ p[-1] = '\0';
+ hp = sm_gethostbyname(&host[1],
+ family);
+ p[-1] = '.';
+#if NAMED_BIND
+ _res.options = oldopts;
+#endif /* NAMED_BIND */
+ }
+ *p = ']';
+ goto gothostent;
+ }
+ *p = ']';
+ }
+ if (p == NULL)
+ {
+ extern char MsgBuf[];
+
+ usrerrenh("5.1.2",
+ "553 Invalid numeric domain spec \"%s\"",
+ host);
+ mci_setstat(mci, EX_NOHOST, "5.1.2", MsgBuf);
+ errno = EINVAL;
+ return EX_NOHOST;
+ }
+ }
+ else
+ {
+ /* contortion to get around SGI cc complaints */
+ {
+ p = &host[strlen(host) - 1];
+ hp = sm_gethostbyname(host, family);
+ if (hp == NULL && *p == '.')
+ {
+#if NAMED_BIND
+ int oldopts = _res.options;
+
+ _res.options &= ~(RES_DEFNAMES|RES_DNSRCH);
+#endif /* NAMED_BIND */
+ *p = '\0';
+ hp = sm_gethostbyname(host, family);
+ *p = '.';
+#if NAMED_BIND
+ _res.options = oldopts;
+#endif /* NAMED_BIND */
+ }
+ }
+gothostent:
+ if (hp == NULL)
+ {
+#if NAMED_BIND
+ /* check for name server timeouts */
+# if NETINET6
+ if (WorkAroundBrokenAAAA && family == AF_INET6 &&
+ errno == ETIMEDOUT)
+ {
+ /*
+ ** An attempt with family AF_INET may
+ ** succeed By skipping the next section
+ ** of code, we will try AF_INET before
+ ** failing.
+ */
+
+ if (tTd(16, 10))
+ sm_dprintf("makeconnection: WorkAroundBrokenAAAA: Trying AF_INET lookup (AF_INET6 failed)\n");
+ }
+ else
+# endif /* NETINET6 */
+ {
+ if (errno == ETIMEDOUT ||
+ h_errno == TRY_AGAIN ||
+ (errno == ECONNREFUSED && UseNameServer))
+ {
+ save_errno = errno;
+ mci_setstat(mci, EX_TEMPFAIL,
+ "4.4.3", NULL);
+ errno = save_errno;
+ return EX_TEMPFAIL;
+ }
+ }
+#endif /* NAMED_BIND */
+#if NETINET6
+ /*
+ ** Try v6 first, then fall back to v4.
+ ** If we found a v6 address, but no v4
+ ** addresses, then TEMPFAIL.
+ */
+
+ if (family == AF_INET6)
+ {
+ family = AF_INET;
+ goto v4retry;
+ }
+ if (v6found)
+ goto v6tempfail;
+#endif /* NETINET6 */
+ save_errno = errno;
+ mci_setstat(mci, EX_NOHOST, "5.1.2", NULL);
+ errno = save_errno;
+ return EX_NOHOST;
+ }
+ addr.sa.sa_family = hp->h_addrtype;
+ switch (hp->h_addrtype)
+ {
+#if NETINET
+ case AF_INET:
+ memmove(&addr.sin.sin_addr,
+ hp->h_addr,
+ INADDRSZ);
+ break;
+#endif /* NETINET */
+
+#if NETINET6
+ case AF_INET6:
+ memmove(&addr.sin6.sin6_addr,
+ hp->h_addr,
+ IN6ADDRSZ);
+ break;
+#endif /* NETINET6 */
+
+ default:
+ if (hp->h_length > sizeof addr.sa.sa_data)
+ {
+ syserr("makeconnection: long sa_data: family %d len %d",
+ hp->h_addrtype, hp->h_length);
+ mci_setstat(mci, EX_NOHOST, "5.1.2", NULL);
+ errno = EINVAL;
+ return EX_NOHOST;
+ }
+ memmove(addr.sa.sa_data, hp->h_addr, hp->h_length);
+ break;
+ }
+ addrno = 1;
+ }
+
+ /*
+ ** Determine the port number.
+ */
+
+ if (port == 0)
+ {
+#ifdef NO_GETSERVBYNAME
+ port = htons(25);
+#else /* NO_GETSERVBYNAME */
+ register struct servent *sp = getservbyname("smtp", "tcp");
+
+ if (sp == NULL)
+ {
+ if (LogLevel > 2)
+ sm_syslog(LOG_ERR, NOQID,
+ "makeconnection: service \"smtp\" unknown");
+ port = htons(25);
+ }
+ else
+ port = sp->s_port;
+#endif /* NO_GETSERVBYNAME */
+ }
+
+#if NETINET6
+ if (addr.sa.sa_family == AF_INET6 &&
+ IN6_IS_ADDR_V4MAPPED(&addr.sin6.sin6_addr) &&
+ ClientSettings[AF_INET].d_addr.sa.sa_family != 0)
+ {
+ /*
+ ** Ignore mapped IPv4 address since
+ ** there is a ClientPortOptions setting
+ ** for IPv4.
+ */
+
+ goto nextaddr;
+ }
+#endif /* NETINET6 */
+
+ switch (addr.sa.sa_family)
+ {
+#if NETINET
+ case AF_INET:
+ addr.sin.sin_port = port;
+ addrlen = sizeof (struct sockaddr_in);
+ break;
+#endif /* NETINET */
+
+#if NETINET6
+ case AF_INET6:
+ addr.sin6.sin6_port = port;
+ addrlen = sizeof (struct sockaddr_in6);
+ break;
+#endif /* NETINET6 */
+
+#if NETISO
+ case AF_ISO:
+ /* assume two byte transport selector */
+ memmove(TSEL((struct sockaddr_iso *) &addr), (char *) &port, 2);
+ addrlen = sizeof (struct sockaddr_iso);
+ break;
+#endif /* NETISO */
+
+ default:
+ syserr("Can't connect to address family %d", addr.sa.sa_family);
+ mci_setstat(mci, EX_NOHOST, "5.1.2", NULL);
+ errno = EINVAL;
+#if NETINET6
+ if (hp != NULL)
+ freehostent(hp);
+#endif /* NETINET6 */
+ return EX_NOHOST;
+ }
+
+ /*
+ ** Try to actually open the connection.
+ */
+
+#if XLA
+ /* if too many connections, don't bother trying */
+ if (!xla_noqueue_ok(host))
+ {
+# if NETINET6
+ if (hp != NULL)
+ freehostent(hp);
+# endif /* NETINET6 */
+ return EX_TEMPFAIL;
+ }
+#endif /* XLA */
+
+ firstconnect = true;
+ for (;;)
+ {
+ if (tTd(16, 1))
+ sm_dprintf("makeconnection (%s [%s].%d (%d))\n",
+ host, anynet_ntoa(&addr), ntohs(port),
+ (int) addr.sa.sa_family);
+
+ /* save for logging */
+ CurHostAddr = addr;
+
+#if HASRRESVPORT
+ if (bitnset(M_SECURE_PORT, mci->mci_mailer->m_flags))
+ {
+ int rport = IPPORT_RESERVED - 1;
+
+ s = rresvport(&rport);
+ }
+ else
+#endif /* HASRRESVPORT */
+ {
+ s = socket(addr.sa.sa_family, SOCK_STREAM, 0);
+ }
+ if (s < 0)
+ {
+ save_errno = errno;
+ syserr("makeconnection: cannot create socket");
+#if XLA
+ xla_host_end(host);
+#endif /* XLA */
+ mci_setstat(mci, EX_TEMPFAIL, "4.4.5", NULL);
+#if NETINET6
+ if (hp != NULL)
+ freehostent(hp);
+#endif /* NETINET6 */
+ errno = save_errno;
+ return EX_TEMPFAIL;
+ }
+
+#ifdef SO_SNDBUF
+ if (ClientSettings[family].d_tcpsndbufsize > 0)
+ {
+ if (setsockopt(s, SOL_SOCKET, SO_SNDBUF,
+ (char *) &ClientSettings[family].d_tcpsndbufsize,
+ sizeof(ClientSettings[family].d_tcpsndbufsize)) < 0)
+ syserr("makeconnection: setsockopt(SO_SNDBUF)");
+ }
+#endif /* SO_SNDBUF */
+#ifdef SO_RCVBUF
+ if (ClientSettings[family].d_tcprcvbufsize > 0)
+ {
+ if (setsockopt(s, SOL_SOCKET, SO_RCVBUF,
+ (char *) &ClientSettings[family].d_tcprcvbufsize,
+ sizeof(ClientSettings[family].d_tcprcvbufsize)) < 0)
+ syserr("makeconnection: setsockopt(SO_RCVBUF)");
+ }
+#endif /* SO_RCVBUF */
+
+ if (tTd(16, 1))
+ sm_dprintf("makeconnection: fd=%d\n", s);
+
+ /* turn on network debugging? */
+ if (tTd(16, 101))
+ {
+ int on = 1;
+
+ (void) setsockopt(s, SOL_SOCKET, SO_DEBUG,
+ (char *)&on, sizeof on);
+ }
+ if (e->e_xfp != NULL) /* for debugging */
+ (void) sm_io_flush(e->e_xfp, SM_TIME_DEFAULT);
+ errno = 0; /* for debugging */
+
+ if (clt_bind)
+ {
+ int on = 1;
+
+ switch (clt_addr.sa.sa_family)
+ {
+#if NETINET
+ case AF_INET:
+ if (clt_addr.sin.sin_port != 0)
+ (void) setsockopt(s, SOL_SOCKET,
+ SO_REUSEADDR,
+ (char *) &on,
+ sizeof on);
+ break;
+#endif /* NETINET */
+
+#if NETINET6
+ case AF_INET6:
+ if (clt_addr.sin6.sin6_port != 0)
+ (void) setsockopt(s, SOL_SOCKET,
+ SO_REUSEADDR,
+ (char *) &on,
+ sizeof on);
+ break;
+#endif /* NETINET6 */
+ }
+
+ if (bind(s, &clt_addr.sa, socksize) < 0)
+ {
+ save_errno = errno;
+ (void) close(s);
+ errno = save_errno;
+ syserr("makeconnection: cannot bind socket [%s]",
+ anynet_ntoa(&clt_addr));
+#if NETINET6
+ if (hp != NULL)
+ freehostent(hp);
+#endif /* NETINET6 */
+ errno = save_errno;
+ return EX_TEMPFAIL;
+ }
+ }
+
+ /*
+ ** Linux seems to hang in connect for 90 minutes (!!!).
+ ** Time out the connect to avoid this problem.
+ */
+
+ if (setjmp(CtxConnectTimeout) == 0)
+ {
+ int i;
+
+ if (e->e_ntries <= 0 && TimeOuts.to_iconnect != 0)
+ ev = sm_setevent(TimeOuts.to_iconnect,
+ connecttimeout, 0);
+ else if (TimeOuts.to_connect != 0)
+ ev = sm_setevent(TimeOuts.to_connect,
+ connecttimeout, 0);
+ else
+ ev = NULL;
+
+ switch (ConnectOnlyTo.sa.sa_family)
+ {
+#if NETINET
+ case AF_INET:
+ addr.sin.sin_addr.s_addr = ConnectOnlyTo.sin.sin_addr.s_addr;
+ break;
+#endif /* NETINET */
+
+#if NETINET6
+ case AF_INET6:
+ memmove(&addr.sin6.sin6_addr,
+ &ConnectOnlyTo.sin6.sin6_addr,
+ IN6ADDRSZ);
+ break;
+#endif /* NETINET6 */
+ }
+ if (tTd(16, 1))
+ sm_dprintf("Connecting to [%s]...\n", anynet_ntoa(&addr));
+ i = connect(s, (struct sockaddr *) &addr, addrlen);
+ save_errno = errno;
+ if (ev != NULL)
+ sm_clrevent(ev);
+ if (i >= 0)
+ break;
+ }
+ else
+ save_errno = errno;
+
+ /* couldn't connect.... figure out why */
+ (void) close(s);
+
+ /* if running demand-dialed connection, try again */
+ if (DialDelay > 0 && firstconnect &&
+ bitnset(M_DIALDELAY, mci->mci_mailer->m_flags))
+ {
+ if (tTd(16, 1))
+ sm_dprintf("Connect failed (%s); trying again...\n",
+ sm_errstring(save_errno));
+ firstconnect = false;
+ (void) sleep(DialDelay);
+ continue;
+ }
+
+ if (LogLevel > 13)
+ sm_syslog(LOG_INFO, e->e_id,
+ "makeconnection (%s [%s]) failed: %s",
+ host, anynet_ntoa(&addr),
+ sm_errstring(save_errno));
+
+#if NETINET6
+nextaddr:
+#endif /* NETINET6 */
+ if (hp != NULL && hp->h_addr_list[addrno] != NULL &&
+ (enough == 0 || curtime() < enough))
+ {
+ if (tTd(16, 1))
+ sm_dprintf("Connect failed (%s); trying new address....\n",
+ sm_errstring(save_errno));
+ switch (addr.sa.sa_family)
+ {
+#if NETINET
+ case AF_INET:
+ memmove(&addr.sin.sin_addr,
+ hp->h_addr_list[addrno++],
+ INADDRSZ);
+ break;
+#endif /* NETINET */
+
+#if NETINET6
+ case AF_INET6:
+ memmove(&addr.sin6.sin6_addr,
+ hp->h_addr_list[addrno++],
+ IN6ADDRSZ);
+ break;
+#endif /* NETINET6 */
+
+ default:
+ memmove(addr.sa.sa_data,
+ hp->h_addr_list[addrno++],
+ hp->h_length);
+ break;
+ }
+ continue;
+ }
+ errno = save_errno;
+
+#if NETINET6
+ if (family == AF_INET6)
+ {
+ if (tTd(16, 1))
+ sm_dprintf("Connect failed (%s); retrying with AF_INET....\n",
+ sm_errstring(save_errno));
+ v6found = true;
+ family = AF_INET;
+ if (hp != NULL)
+ {
+ freehostent(hp);
+ hp = NULL;
+ }
+ goto v4retry;
+ }
+ v6tempfail:
+#endif /* NETINET6 */
+ /* couldn't open connection */
+#if NETINET6
+ /* Don't clobber an already saved errno from v4retry */
+ if (errno > 0)
+#endif /* NETINET6 */
+ save_errno = errno;
+ if (tTd(16, 1))
+ sm_dprintf("Connect failed (%s)\n",
+ sm_errstring(save_errno));
+#if XLA
+ xla_host_end(host);
+#endif /* XLA */
+ mci_setstat(mci, EX_TEMPFAIL, "4.4.1", NULL);
+#if NETINET6
+ if (hp != NULL)
+ freehostent(hp);
+#endif /* NETINET6 */
+ errno = save_errno;
+ return EX_TEMPFAIL;
+ }
+
+#if NETINET6
+ if (hp != NULL)
+ {
+ freehostent(hp);
+ hp = NULL;
+ }
+#endif /* NETINET6 */
+
+ /* connection ok, put it into canonical form */
+ mci->mci_out = NULL;
+ if ((mci->mci_out = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT,
+ (void *) &s,
+ SM_IO_WRONLY_B, NULL)) == NULL ||
+ (s = dup(s)) < 0 ||
+ (mci->mci_in = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT,
+ (void *) &s,
+ SM_IO_RDONLY_B, NULL)) == NULL)
+ {
+ save_errno = errno;
+ syserr("cannot open SMTP client channel, fd=%d", s);
+ mci_setstat(mci, EX_TEMPFAIL, "4.4.5", NULL);
+ if (mci->mci_out != NULL)
+ (void) sm_io_close(mci->mci_out, SM_TIME_DEFAULT);
+ (void) close(s);
+ errno = save_errno;
+ return EX_TEMPFAIL;
+ }
+ sm_io_automode(mci->mci_out, mci->mci_in);
+
+ /* set {client_flags} */
+ if (ClientSettings[addr.sa.sa_family].d_mflags != NULL)
+ {
+ macdefine(&mci->mci_macro, A_PERM,
+ macid("{client_flags}"),
+ ClientSettings[addr.sa.sa_family].d_mflags);
+ }
+ else
+ macdefine(&mci->mci_macro, A_PERM,
+ macid("{client_flags}"), "");
+
+ /* "add" {client_flags} to bitmap */
+ if (bitnset(D_IFNHELO, ClientSettings[addr.sa.sa_family].d_flags))
+ {
+ /* look for just this one flag */
+ setbitn(D_IFNHELO, d_flags);
+ }
+
+ /* find out name for Interface through which we connect */
+ len = sizeof addr;
+ if (getsockname(s, &addr.sa, &len) == 0)
+ {
+ char *name;
+ char family[5];
+
+ macdefine(&BlankEnvelope.e_macro, A_TEMP,
+ macid("{if_addr_out}"), anynet_ntoa(&addr));
+ (void) sm_snprintf(family, sizeof(family), "%d",
+ addr.sa.sa_family);
+ macdefine(&BlankEnvelope.e_macro, A_TEMP,
+ macid("{if_family_out}"), family);
+
+ name = hostnamebyanyaddr(&addr);
+ macdefine(&BlankEnvelope.e_macro, A_TEMP,
+ macid("{if_name_out}"), name);
+ if (LogLevel > 11)
+ {
+ /* log connection information */
+ sm_syslog(LOG_INFO, e->e_id,
+ "SMTP outgoing connect on %.40s", name);
+ }
+ if (bitnset(D_IFNHELO, d_flags))
+ {
+ if (name[0] != '[' && strchr(name, '.') != NULL)
+ mci->mci_heloname = newstr(name);
+ }
+ }
+ else
+ {
+ macdefine(&BlankEnvelope.e_macro, A_PERM,
+ macid("{if_name_out}"), NULL);
+ macdefine(&BlankEnvelope.e_macro, A_PERM,
+ macid("{if_addr_out}"), NULL);
+ macdefine(&BlankEnvelope.e_macro, A_PERM,
+ macid("{if_family_out}"), NULL);
+ }
+
+#if _FFR_HELONAME
+ /* Use the configured HeloName as appropriate */
+ if (HeloName != NULL && HeloName[0] != '\0')
+ mci->mci_heloname = newstr(HeloName);
+#endif /* _FFR_HELONAME */
+
+ mci_setstat(mci, EX_OK, NULL, NULL);
+ return EX_OK;
+}
+
+static void
+connecttimeout(ignore)
+ int ignore;
+{
+ /*
+ ** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+ ** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+ ** DOING.
+ */
+
+ errno = ETIMEDOUT;
+ longjmp(CtxConnectTimeout, 1);
+}
+/*
+** MAKECONNECTION_DS -- make a connection to a domain socket.
+**
+** Parameters:
+** mux_path -- the path of the socket to connect to.
+** mci -- a pointer to the mail connection information
+** structure to be filled in.
+**
+** Returns:
+** An exit code telling whether the connection could be
+** made and if not why not.
+**
+** Side Effects:
+** none.
+*/
+
+#if NETUNIX
+int
+makeconnection_ds(mux_path, mci)
+ char *mux_path;
+ register MCI *mci;
+{
+ int sock;
+ int rval, save_errno;
+ long sff = SFF_SAFEDIRPATH|SFF_OPENASROOT|SFF_NOLINK|SFF_ROOTOK|SFF_EXECOK;
+ struct sockaddr_un unix_addr;
+
+ /* if not safe, don't connect */
+ rval = safefile(mux_path, RunAsUid, RunAsGid, RunAsUserName,
+ sff, S_IRUSR|S_IWUSR, NULL);
+
+ if (rval != 0)
+ {
+ syserr("makeconnection_ds: unsafe domain socket %s",
+ mux_path);
+ mci_setstat(mci, EX_TEMPFAIL, "4.3.5", NULL);
+ errno = rval;
+ return EX_TEMPFAIL;
+ }
+
+ /* prepare address structure */
+ memset(&unix_addr, '\0', sizeof unix_addr);
+ unix_addr.sun_family = AF_UNIX;
+
+ if (strlen(mux_path) >= sizeof unix_addr.sun_path)
+ {
+ syserr("makeconnection_ds: domain socket name %s too long",
+ mux_path);
+
+ /* XXX why TEMPFAIL but 5.x.y ? */
+ mci_setstat(mci, EX_TEMPFAIL, "5.3.5", NULL);
+ errno = ENAMETOOLONG;
+ return EX_UNAVAILABLE;
+ }
+ (void) sm_strlcpy(unix_addr.sun_path, mux_path,
+ sizeof unix_addr.sun_path);
+
+ /* initialize domain socket */
+ sock = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (sock == -1)
+ {
+ save_errno = errno;
+ syserr("makeconnection_ds: could not create domain socket %s",
+ mux_path);
+ mci_setstat(mci, EX_TEMPFAIL, "4.4.5", NULL);
+ errno = save_errno;
+ return EX_TEMPFAIL;
+ }
+
+ /* connect to server */
+ if (connect(sock, (struct sockaddr *) &unix_addr,
+ sizeof(unix_addr)) == -1)
+ {
+ save_errno = errno;
+ syserr("Could not connect to socket %s", mux_path);
+ mci_setstat(mci, EX_TEMPFAIL, "4.4.1", NULL);
+ (void) close(sock);
+ errno = save_errno;
+ return EX_TEMPFAIL;
+ }
+
+ /* connection ok, put it into canonical form */
+ mci->mci_out = NULL;
+ if ((mci->mci_out = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT,
+ (void *) &sock, SM_IO_WRONLY_B, NULL))
+ == NULL
+ || (sock = dup(sock)) < 0 ||
+ (mci->mci_in = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT,
+ (void *) &sock, SM_IO_RDONLY_B, NULL))
+ == NULL)
+ {
+ save_errno = errno;
+ syserr("cannot open SMTP client channel, fd=%d", sock);
+ mci_setstat(mci, EX_TEMPFAIL, "4.4.5", NULL);
+ if (mci->mci_out != NULL)
+ (void) sm_io_close(mci->mci_out, SM_TIME_DEFAULT);
+ (void) close(sock);
+ errno = save_errno;
+ return EX_TEMPFAIL;
+ }
+ sm_io_automode(mci->mci_out, mci->mci_in);
+
+ mci_setstat(mci, EX_OK, NULL, NULL);
+ errno = 0;
+ return EX_OK;
+}
+#endif /* NETUNIX */
+/*
+** SHUTDOWN_DAEMON -- Performs a clean shutdown of the daemon
+**
+** Parameters:
+** none.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** closes control socket, exits.
+*/
+
+void
+shutdown_daemon()
+{
+ int i;
+ char *reason;
+
+ sm_allsignals(true);
+
+ reason = ShutdownRequest;
+ ShutdownRequest = NULL;
+ PendingSignal = 0;
+
+ if (LogLevel > 9)
+ sm_syslog(LOG_INFO, CurEnv->e_id, "stopping daemon, reason=%s",
+ reason == NULL ? "implicit call" : reason);
+
+ FileName = NULL;
+ closecontrolsocket(true);
+#if XLA
+ xla_all_end();
+#endif /* XLA */
+
+ for (i = 0; i < NDaemons; i++)
+ {
+ if (Daemons[i].d_socket >= 0)
+ {
+ (void) close(Daemons[i].d_socket);
+ Daemons[i].d_socket = -1;
+
+#if _FFR_DAEMON_NETUNIX
+# if NETUNIX
+ /* Remove named sockets */
+ if (Daemons[i].d_addr.sa.sa_family == AF_UNIX)
+ {
+ int rval;
+ long sff = SFF_SAFEDIRPATH|SFF_OPENASROOT|SFF_NOLINK|SFF_MUSTOWN|SFF_EXECOK|SFF_CREAT;
+
+ /* if not safe, don't use it */
+ rval = safefile(Daemons[i].d_addr.sunix.sun_path,
+ RunAsUid, RunAsGid,
+ RunAsUserName, sff,
+ S_IRUSR|S_IWUSR, NULL);
+ if (rval == 0 &&
+ unlink(Daemons[i].d_addr.sunix.sun_path) < 0)
+ {
+ sm_syslog(LOG_WARNING, NOQID,
+ "Could not remove daemon %s socket: %s: %s",
+ Daemons[i].d_name,
+ Daemons[i].d_addr.sunix.sun_path,
+ sm_errstring(errno));
+ }
+ }
+# endif /* NETUNIX */
+#endif /* _FFR_DAEMON_NETUNIX */
+ }
+ }
+
+ finis(false, true, EX_OK);
+}
+/*
+** RESTART_DAEMON -- Performs a clean restart of the daemon
+**
+** Parameters:
+** none.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** restarts the daemon or exits if restart fails.
+*/
+
+/* Make a non-DFL/IGN signal a noop */
+#define SM_NOOP_SIGNAL(sig, old) \
+do \
+{ \
+ (old) = sm_signal((sig), sm_signal_noop); \
+ if ((old) == SIG_IGN || (old) == SIG_DFL) \
+ (void) sm_signal((sig), (old)); \
+} while (0)
+
+void
+restart_daemon()
+{
+ bool drop;
+ int save_errno;
+ char *reason;
+ sigfunc_t ignore, oalrm, ousr1;
+ extern int DtableSize;
+
+ /* clear the events to turn off SIGALRMs */
+ sm_clear_events();
+ sm_allsignals(true);
+
+ reason = RestartRequest;
+ RestartRequest = NULL;
+ PendingSignal = 0;
+
+ if (SaveArgv[0][0] != '/')
+ {
+ if (LogLevel > 3)
+ sm_syslog(LOG_INFO, NOQID,
+ "could not restart: need full path");
+ finis(false, true, EX_OSFILE);
+ /* NOTREACHED */
+ }
+ if (LogLevel > 3)
+ sm_syslog(LOG_INFO, NOQID, "restarting %s due to %s",
+ SaveArgv[0],
+ reason == NULL ? "implicit call" : reason);
+
+ closecontrolsocket(true);
+#if SM_CONF_SHM
+ cleanup_shm(DaemonPid == getpid());
+#endif /* SM_CONF_SHM */
+
+ /* close locked pid file */
+ close_sendmail_pid();
+
+ /*
+ ** Want to drop to the user who started the process in all cases
+ ** *but* when running as "smmsp" for the clientmqueue queue run
+ ** daemon. In that case, UseMSP will be true, RunAsUid should not
+ ** be root, and RealUid should be either 0 or RunAsUid.
+ */
+
+ drop = !(UseMSP && RunAsUid != 0 &&
+ (RealUid == 0 || RealUid == RunAsUid));
+
+ if (drop_privileges(drop) != EX_OK)
+ {
+ if (LogLevel > 0)
+ sm_syslog(LOG_ALERT, NOQID,
+ "could not drop privileges: %s",
+ sm_errstring(errno));
+ finis(false, true, EX_OSERR);
+ /* NOTREACHED */
+ }
+
+ sm_close_on_exec(STDERR_FILENO + 1, DtableSize);
+
+ /*
+ ** Need to allow signals before execve() to make them "harmless".
+ ** However, the default action can be "terminate", so it isn't
+ ** really harmless. Setting signals to IGN will cause them to be
+ ** ignored in the new process to, so that isn't a good alternative.
+ */
+
+ SM_NOOP_SIGNAL(SIGALRM, oalrm);
+ SM_NOOP_SIGNAL(SIGCHLD, ignore);
+ SM_NOOP_SIGNAL(SIGHUP, ignore);
+ SM_NOOP_SIGNAL(SIGINT, ignore);
+ SM_NOOP_SIGNAL(SIGPIPE, ignore);
+ SM_NOOP_SIGNAL(SIGTERM, ignore);
+#ifdef SIGUSR1
+ SM_NOOP_SIGNAL(SIGUSR1, ousr1);
+#endif /* SIGUSR1 */
+
+ /* Turn back on signals */
+ sm_allsignals(false);
+
+ (void) execve(SaveArgv[0], (ARGV_T) SaveArgv, (ARGV_T) ExternalEnviron);
+ save_errno = errno;
+
+ /* block signals again and restore needed signals */
+ sm_allsignals(true);
+
+ /* For finis() events */
+ (void) sm_signal(SIGALRM, oalrm);
+
+#ifdef SIGUSR1
+ /* For debugging finis() */
+ (void) sm_signal(SIGUSR1, ousr1);
+#endif /* SIGUSR1 */
+
+ errno = save_errno;
+ if (LogLevel > 0)
+ sm_syslog(LOG_ALERT, NOQID, "could not exec %s: %s",
+ SaveArgv[0], sm_errstring(errno));
+ finis(false, true, EX_OSFILE);
+ /* NOTREACHED */
+}
+/*
+** MYHOSTNAME -- return the name of this host.
+**
+** Parameters:
+** hostbuf -- a place to return the name of this host.
+** size -- the size of hostbuf.
+**
+** Returns:
+** A list of aliases for this host.
+**
+** Side Effects:
+** Adds numeric codes to $=w.
+*/
+
+struct hostent *
+myhostname(hostbuf, size)
+ char hostbuf[];
+ int size;
+{
+ register struct hostent *hp;
+
+ if (gethostname(hostbuf, size) < 0 || hostbuf[0] == '\0')
+ (void) sm_strlcpy(hostbuf, "localhost", size);
+ hp = sm_gethostbyname(hostbuf, InetMode);
+#if NETINET && NETINET6
+ if (hp == NULL && InetMode == AF_INET6)
+ {
+ /*
+ ** It's possible that this IPv6 enabled machine doesn't
+ ** actually have any IPv6 interfaces and, therefore, no
+ ** IPv6 addresses. Fall back to AF_INET.
+ */
+
+ hp = sm_gethostbyname(hostbuf, AF_INET);
+ }
+#endif /* NETINET && NETINET6 */
+ if (hp == NULL)
+ return NULL;
+ if (strchr(hp->h_name, '.') != NULL || strchr(hostbuf, '.') == NULL)
+ (void) cleanstrcpy(hostbuf, hp->h_name, size);
+
+#if NETINFO
+ if (strchr(hostbuf, '.') == NULL)
+ {
+ char *domainname;
+
+ domainname = ni_propval("/locations", NULL, "resolver",
+ "domain", '\0');
+ if (domainname != NULL &&
+ strlen(domainname) + strlen(hostbuf) + 1 < size)
+ (void) sm_strlcat2(hostbuf, ".", domainname, size);
+ }
+#endif /* NETINFO */
+
+ /*
+ ** If there is still no dot in the name, try looking for a
+ ** dotted alias.
+ */
+
+ if (strchr(hostbuf, '.') == NULL)
+ {
+ char **ha;
+
+ for (ha = hp->h_aliases; ha != NULL && *ha != NULL; ha++)
+ {
+ if (strchr(*ha, '.') != NULL)
+ {
+ (void) cleanstrcpy(hostbuf, *ha, size - 1);
+ hostbuf[size - 1] = '\0';
+ break;
+ }
+ }
+ }
+
+ /*
+ ** If _still_ no dot, wait for a while and try again -- it is
+ ** possible that some service is starting up. This can result
+ ** in excessive delays if the system is badly configured, but
+ ** there really isn't a way around that, particularly given that
+ ** the config file hasn't been read at this point.
+ ** All in all, a bit of a mess.
+ */
+
+ if (strchr(hostbuf, '.') == NULL &&
+ !getcanonname(hostbuf, size, true, NULL))
+ {
+ sm_syslog(LOG_CRIT, NOQID,
+ "My unqualified host name (%s) unknown; sleeping for retry",
+ hostbuf);
+ message("My unqualified host name (%s) unknown; sleeping for retry",
+ hostbuf);
+ (void) sleep(60);
+ if (!getcanonname(hostbuf, size, true, NULL))
+ {
+ sm_syslog(LOG_ALERT, NOQID,
+ "unable to qualify my own domain name (%s) -- using short name",
+ hostbuf);
+ message("WARNING: unable to qualify my own domain name (%s) -- using short name",
+ hostbuf);
+ }
+ }
+ return hp;
+}
+/*
+** ADDRCMP -- compare two host addresses
+**
+** Parameters:
+** hp -- hostent structure for the first address
+** ha -- actual first address
+** sa -- second address
+**
+** Returns:
+** 0 -- if ha and sa match
+** else -- they don't match
+*/
+
+static int
+addrcmp(hp, ha, sa)
+ struct hostent *hp;
+ char *ha;
+ SOCKADDR *sa;
+{
+#if NETINET6
+ unsigned char *a;
+#endif /* NETINET6 */
+
+ switch (sa->sa.sa_family)
+ {
+#if NETINET
+ case AF_INET:
+ if (hp->h_addrtype == AF_INET)
+ return memcmp(ha, (char *) &sa->sin.sin_addr, INADDRSZ);
+ break;
+#endif /* NETINET */
+
+#if NETINET6
+ case AF_INET6:
+ a = (unsigned char *) &sa->sin6.sin6_addr;
+
+ /* Straight binary comparison */
+ if (hp->h_addrtype == AF_INET6)
+ return memcmp(ha, a, IN6ADDRSZ);
+
+ /* If IPv4-mapped IPv6 address, compare the IPv4 section */
+ if (hp->h_addrtype == AF_INET &&
+ IN6_IS_ADDR_V4MAPPED(&sa->sin6.sin6_addr))
+ return memcmp(a + IN6ADDRSZ - INADDRSZ, ha, INADDRSZ);
+ break;
+#endif /* NETINET6 */
+ }
+ return -1;
+}
+/*
+** GETAUTHINFO -- get the real host name associated with a file descriptor
+**
+** Uses RFC1413 protocol to try to get info from the other end.
+**
+** Parameters:
+** fd -- the descriptor
+** may_be_forged -- an outage that is set to true if the
+** forward lookup of RealHostName does not match
+** RealHostAddr; set to false if they do match.
+**
+** Returns:
+** The user@host information associated with this descriptor.
+*/
+
+static jmp_buf CtxAuthTimeout;
+
+static void
+authtimeout(ignore)
+ int ignore;
+{
+ /*
+ ** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+ ** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+ ** DOING.
+ */
+
+ errno = ETIMEDOUT;
+ longjmp(CtxAuthTimeout, 1);
+}
+
+char *
+getauthinfo(fd, may_be_forged)
+ int fd;
+ bool *may_be_forged;
+{
+ unsigned short SM_NONVOLATILE port = 0;
+ SOCKADDR_LEN_T falen;
+ register char *volatile p = NULL;
+ SOCKADDR la;
+ SOCKADDR_LEN_T lalen;
+#ifndef NO_GETSERVBYNAME
+ register struct servent *sp;
+# if NETINET
+ static unsigned short port4 = 0;
+# endif /* NETINET */
+# if NETINET6
+ static unsigned short port6 = 0;
+# endif /* NETINET6 */
+#endif /* ! NO_GETSERVBYNAME */
+ volatile int s;
+ int i = 0;
+ size_t len;
+ SM_EVENT *ev;
+ int nleft;
+ struct hostent *hp;
+ char *ostype = NULL;
+ char **ha;
+ char ibuf[MAXNAME + 1];
+ static char hbuf[MAXNAME + MAXAUTHINFO + 11];
+
+ *may_be_forged = false;
+ falen = sizeof RealHostAddr;
+ if (isatty(fd) || (i = getpeername(fd, &RealHostAddr.sa, &falen)) < 0 ||
+ falen <= 0 || RealHostAddr.sa.sa_family == 0)
+ {
+ if (i < 0)
+ {
+ /*
+ ** ENOTSOCK is OK: bail on anything else, but reset
+ ** errno in this case, so a mis-report doesn't
+ ** happen later.
+ */
+
+ if (errno != ENOTSOCK)
+ return NULL;
+ errno = 0;
+ }
+ (void) sm_strlcpyn(hbuf, sizeof hbuf, 2, RealUserName,
+ "@localhost");
+ if (tTd(9, 1))
+ sm_dprintf("getauthinfo: %s\n", hbuf);
+ return hbuf;
+ }
+
+ if (RealHostName == NULL)
+ {
+ /* translate that to a host name */
+ RealHostName = newstr(hostnamebyanyaddr(&RealHostAddr));
+ if (strlen(RealHostName) > MAXNAME)
+ RealHostName[MAXNAME] = '\0'; /* XXX - 1 ? */
+ }
+
+ /* cross check RealHostName with forward DNS lookup */
+ if (anynet_ntoa(&RealHostAddr)[0] != '[' &&
+ RealHostName[0] != '[')
+ {
+ int family;
+
+ family = RealHostAddr.sa.sa_family;
+#if NETINET6 && NEEDSGETIPNODE
+ /*
+ ** If RealHostAddr is an IPv6 connection with an
+ ** IPv4-mapped address, we need RealHostName's IPv4
+ ** address(es) for addrcmp() to compare against
+ ** RealHostAddr.
+ **
+ ** Actually, we only need to do this for systems
+ ** which NEEDSGETIPNODE since the real getipnodebyname()
+ ** already does V4MAPPED address via the AI_V4MAPPEDCFG
+ ** flag. A better fix to this problem is to add this
+ ** functionality to our stub getipnodebyname().
+ */
+
+ if (family == AF_INET6 &&
+ IN6_IS_ADDR_V4MAPPED(&RealHostAddr.sin6.sin6_addr))
+ family = AF_INET;
+#endif /* NETINET6 && NEEDSGETIPNODE */
+
+ /* try to match the reverse against the forward lookup */
+ hp = sm_gethostbyname(RealHostName, family);
+ if (hp == NULL)
+ {
+ /* XXX: Could be a temporary error on forward lookup */
+ *may_be_forged = true;
+ }
+ else
+ {
+ for (ha = hp->h_addr_list; *ha != NULL; ha++)
+ {
+ if (addrcmp(hp, *ha, &RealHostAddr) == 0)
+ break;
+ }
+ *may_be_forged = *ha == NULL;
+#if NETINET6
+ freehostent(hp);
+ hp = NULL;
+#endif /* NETINET6 */
+ }
+ }
+
+ if (TimeOuts.to_ident == 0)
+ goto noident;
+
+ lalen = sizeof la;
+ switch (RealHostAddr.sa.sa_family)
+ {
+#if NETINET
+ case AF_INET:
+ if (getsockname(fd, &la.sa, &lalen) < 0 ||
+ lalen <= 0 ||
+ la.sa.sa_family != AF_INET)
+ {
+ /* no ident info */
+ goto noident;
+ }
+ port = RealHostAddr.sin.sin_port;
+
+ /* create ident query */
+ (void) sm_snprintf(ibuf, sizeof ibuf, "%d,%d\r\n",
+ ntohs(RealHostAddr.sin.sin_port),
+ ntohs(la.sin.sin_port));
+
+ /* create local address */
+ la.sin.sin_port = 0;
+
+ /* create foreign address */
+# ifdef NO_GETSERVBYNAME
+ RealHostAddr.sin.sin_port = htons(113);
+# else /* NO_GETSERVBYNAME */
+
+ /*
+ ** getservbyname() consumes about 5% of the time
+ ** when receiving a small message (almost all of the time
+ ** spent in this routine).
+ ** Hence we store the port in a static variable
+ ** to save this time.
+ ** The portnumber shouldn't change very often...
+ ** This code makes the assumption that the port number
+ ** is not 0.
+ */
+
+ if (port4 == 0)
+ {
+ sp = getservbyname("auth", "tcp");
+ if (sp != NULL)
+ port4 = sp->s_port;
+ else
+ port4 = htons(113);
+ }
+ RealHostAddr.sin.sin_port = port4;
+ break;
+# endif /* NO_GETSERVBYNAME */
+#endif /* NETINET */
+
+#if NETINET6
+ case AF_INET6:
+ if (getsockname(fd, &la.sa, &lalen) < 0 ||
+ lalen <= 0 ||
+ la.sa.sa_family != AF_INET6)
+ {
+ /* no ident info */
+ goto noident;
+ }
+ port = RealHostAddr.sin6.sin6_port;
+
+ /* create ident query */
+ (void) sm_snprintf(ibuf, sizeof ibuf, "%d,%d\r\n",
+ ntohs(RealHostAddr.sin6.sin6_port),
+ ntohs(la.sin6.sin6_port));
+
+ /* create local address */
+ la.sin6.sin6_port = 0;
+
+ /* create foreign address */
+# ifdef NO_GETSERVBYNAME
+ RealHostAddr.sin6.sin6_port = htons(113);
+# else /* NO_GETSERVBYNAME */
+ if (port6 == 0)
+ {
+ sp = getservbyname("auth", "tcp");
+ if (sp != NULL)
+ port6 = sp->s_port;
+ else
+ port6 = htons(113);
+ }
+ RealHostAddr.sin6.sin6_port = port6;
+ break;
+# endif /* NO_GETSERVBYNAME */
+#endif /* NETINET6 */
+ default:
+ /* no ident info */
+ goto noident;
+ }
+
+ s = -1;
+ if (setjmp(CtxAuthTimeout) != 0)
+ {
+ if (s >= 0)
+ (void) close(s);
+ goto noident;
+ }
+
+ /* put a timeout around the whole thing */
+ ev = sm_setevent(TimeOuts.to_ident, authtimeout, 0);
+
+ /* connect to foreign IDENT server using same address as SMTP socket */
+ s = socket(la.sa.sa_family, SOCK_STREAM, 0);
+ if (s < 0)
+ {
+ sm_clrevent(ev);
+ goto noident;
+ }
+ if (bind(s, &la.sa, lalen) < 0 ||
+ connect(s, &RealHostAddr.sa, lalen) < 0)
+ goto closeident;
+
+ if (tTd(9, 10))
+ sm_dprintf("getauthinfo: sent %s", ibuf);
+
+ /* send query */
+ if (write(s, ibuf, strlen(ibuf)) < 0)
+ goto closeident;
+
+ /* get result */
+ p = &ibuf[0];
+ nleft = sizeof ibuf - 1;
+ while ((i = read(s, p, nleft)) > 0)
+ {
+ char *s;
+
+ p += i;
+ nleft -= i;
+ *p = '\0';
+ if ((s = strchr(ibuf, '\n')) != NULL)
+ {
+ if (p > s + 1)
+ {
+ p = s + 1;
+ *p = '\0';
+ }
+ break;
+ }
+ if (nleft <= 0)
+ break;
+ }
+ (void) close(s);
+ sm_clrevent(ev);
+ if (i < 0 || p == &ibuf[0])
+ goto noident;
+
+ if (p >= &ibuf[2] && *--p == '\n' && *--p == '\r')
+ p--;
+ *++p = '\0';
+
+ if (tTd(9, 3))
+ sm_dprintf("getauthinfo: got %s\n", ibuf);
+
+ /* parse result */
+ p = strchr(ibuf, ':');
+ if (p == NULL)
+ {
+ /* malformed response */
+ goto noident;
+ }
+ while (isascii(*++p) && isspace(*p))
+ continue;
+ if (sm_strncasecmp(p, "userid", 6) != 0)
+ {
+ /* presumably an error string */
+ goto noident;
+ }
+ p += 6;
+ while (isascii(*p) && isspace(*p))
+ p++;
+ if (*p++ != ':')
+ {
+ /* either useridxx or malformed response */
+ goto noident;
+ }
+
+ /* p now points to the OSTYPE field */
+ while (isascii(*p) && isspace(*p))
+ p++;
+ ostype = p;
+ p = strchr(p, ':');
+ if (p == NULL)
+ {
+ /* malformed response */
+ goto noident;
+ }
+ else
+ {
+ char *charset;
+
+ *p = '\0';
+ charset = strchr(ostype, ',');
+ if (charset != NULL)
+ *charset = '\0';
+ }
+
+ /* 1413 says don't do this -- but it's broken otherwise */
+ while (isascii(*++p) && isspace(*p))
+ continue;
+
+ /* p now points to the authenticated name -- copy carefully */
+ if (sm_strncasecmp(ostype, "other", 5) == 0 &&
+ (ostype[5] == ' ' || ostype[5] == '\0'))
+ {
+ (void) sm_strlcpy(hbuf, "IDENT:", sizeof hbuf);
+ cleanstrcpy(&hbuf[6], p, MAXAUTHINFO);
+ }
+ else
+ cleanstrcpy(hbuf, p, MAXAUTHINFO);
+ len = strlen(hbuf);
+ (void) sm_strlcpyn(&hbuf[len], sizeof hbuf - len, 2, "@",
+ RealHostName == NULL ? "localhost" : RealHostName);
+ goto postident;
+
+closeident:
+ (void) close(s);
+ sm_clrevent(ev);
+
+noident:
+ /* put back the original incoming port */
+ switch (RealHostAddr.sa.sa_family)
+ {
+#if NETINET
+ case AF_INET:
+ if (port > 0)
+ RealHostAddr.sin.sin_port = port;
+ break;
+#endif /* NETINET */
+
+#if NETINET6
+ case AF_INET6:
+ if (port > 0)
+ RealHostAddr.sin6.sin6_port = port;
+ break;
+#endif /* NETINET6 */
+ }
+
+ if (RealHostName == NULL)
+ {
+ if (tTd(9, 1))
+ sm_dprintf("getauthinfo: NULL\n");
+ return NULL;
+ }
+ (void) sm_strlcpy(hbuf, RealHostName, sizeof hbuf);
+
+postident:
+#if IP_SRCROUTE
+# ifndef GET_IPOPT_DST
+# define GET_IPOPT_DST(dst) (dst)
+# endif /* ! GET_IPOPT_DST */
+ /*
+ ** Extract IP source routing information.
+ **
+ ** Format of output for a connection from site a through b
+ ** through c to d:
+ ** loose: @site-c@site-b:site-a
+ ** strict: !@site-c@site-b:site-a
+ **
+ ** o - pointer within ipopt_list structure.
+ ** q - pointer within ls/ss rr route data
+ ** p - pointer to hbuf
+ */
+
+ if (RealHostAddr.sa.sa_family == AF_INET)
+ {
+ SOCKOPT_LEN_T ipoptlen;
+ int j;
+ unsigned char *q;
+ unsigned char *o;
+ int l;
+ struct IPOPTION ipopt;
+
+ ipoptlen = sizeof ipopt;
+ if (getsockopt(fd, IPPROTO_IP, IP_OPTIONS,
+ (char *) &ipopt, &ipoptlen) < 0)
+ goto noipsr;
+ if (ipoptlen == 0)
+ goto noipsr;
+ o = (unsigned char *) ipopt.IP_LIST;
+ while (o != NULL && o < (unsigned char *) &ipopt + ipoptlen)
+ {
+ switch (*o)
+ {
+ case IPOPT_EOL:
+ o = NULL;
+ break;
+
+ case IPOPT_NOP:
+ o++;
+ break;
+
+ case IPOPT_SSRR:
+ case IPOPT_LSRR:
+ /*
+ ** Source routing.
+ ** o[0] is the option type (loose/strict).
+ ** o[1] is the length of this option,
+ ** including option type and
+ ** length.
+ ** o[2] is the pointer into the route
+ ** data.
+ ** o[3] begins the route data.
+ */
+
+ p = &hbuf[strlen(hbuf)];
+ l = sizeof hbuf - (hbuf - p) - 6;
+ (void) sm_snprintf(p, SPACELEFT(hbuf, p),
+ " [%s@%.*s",
+ *o == IPOPT_SSRR ? "!" : "",
+ l > 240 ? 120 : l / 2,
+ inet_ntoa(GET_IPOPT_DST(ipopt.IP_DST)));
+ i = strlen(p);
+ p += i;
+ l -= strlen(p);
+
+ j = o[1] / sizeof(struct in_addr) - 1;
+
+ /* q skips length and router pointer to data */
+ q = &o[3];
+ for ( ; j >= 0; j--)
+ {
+ struct in_addr addr;
+
+ memcpy(&addr, q, sizeof(addr));
+ (void) sm_snprintf(p,
+ SPACELEFT(hbuf, p),
+ "%c%.*s",
+ j != 0 ? '@' : ':',
+ l > 240 ? 120 :
+ j == 0 ? l : l / 2,
+ inet_ntoa(addr));
+ i = strlen(p);
+ p += i;
+ l -= i + 1;
+ q += sizeof(struct in_addr);
+ }
+ o += o[1];
+ break;
+
+ default:
+ /* Skip over option */
+ o += o[1];
+ break;
+ }
+ }
+ (void) sm_snprintf(p, SPACELEFT(hbuf, p), "]");
+ goto postipsr;
+ }
+
+noipsr:
+#endif /* IP_SRCROUTE */
+ if (RealHostName != NULL && RealHostName[0] != '[')
+ {
+ p = &hbuf[strlen(hbuf)];
+ (void) sm_snprintf(p, SPACELEFT(hbuf, p), " [%.100s]",
+ anynet_ntoa(&RealHostAddr));
+ }
+ if (*may_be_forged)
+ {
+ p = &hbuf[strlen(hbuf)];
+ (void) sm_strlcpy(p, " (may be forged)", SPACELEFT(hbuf, p));
+ macdefine(&BlankEnvelope.e_macro, A_PERM,
+ macid("{client_resolve}"), "FORGED");
+ }
+
+#if IP_SRCROUTE
+postipsr:
+#endif /* IP_SRCROUTE */
+
+ /* put back the original incoming port */
+ switch (RealHostAddr.sa.sa_family)
+ {
+#if NETINET
+ case AF_INET:
+ if (port > 0)
+ RealHostAddr.sin.sin_port = port;
+ break;
+#endif /* NETINET */
+
+#if NETINET6
+ case AF_INET6:
+ if (port > 0)
+ RealHostAddr.sin6.sin6_port = port;
+ break;
+#endif /* NETINET6 */
+ }
+
+ if (tTd(9, 1))
+ sm_dprintf("getauthinfo: %s\n", hbuf);
+ return hbuf;
+}
+/*
+** HOST_MAP_LOOKUP -- turn a hostname into canonical form
+**
+** Parameters:
+** map -- a pointer to this map.
+** name -- the (presumably unqualified) hostname.
+** av -- unused -- for compatibility with other mapping
+** functions.
+** statp -- an exit status (out parameter) -- set to
+** EX_TEMPFAIL if the name server is unavailable.
+**
+** Returns:
+** The mapping, if found.
+** NULL if no mapping found.
+**
+** Side Effects:
+** Looks up the host specified in hbuf. If it is not
+** the canonical name for that host, return the canonical
+** name (unless MF_MATCHONLY is set, which will cause the
+** status only to be returned).
+*/
+
+char *
+host_map_lookup(map, name, av, statp)
+ MAP *map;
+ char *name;
+ char **av;
+ int *statp;
+{
+ register struct hostent *hp;
+#if NETINET
+ struct in_addr in_addr;
+#endif /* NETINET */
+#if NETINET6
+ struct in6_addr in6_addr;
+#endif /* NETINET6 */
+ char *cp, *ans = NULL;
+ register STAB *s;
+ time_t now;
+#if NAMED_BIND
+ time_t SM_NONVOLATILE retrans = 0;
+ int SM_NONVOLATILE retry = 0;
+#endif /* NAMED_BIND */
+ char hbuf[MAXNAME + 1];
+
+ /*
+ ** See if we have already looked up this name. If so, just
+ ** return it (unless expired).
+ */
+
+ now = curtime();
+ s = stab(name, ST_NAMECANON, ST_ENTER);
+ if (bitset(NCF_VALID, s->s_namecanon.nc_flags) &&
+ s->s_namecanon.nc_exp >= now)
+ {
+ if (tTd(9, 1))
+ sm_dprintf("host_map_lookup(%s) => CACHE %s\n",
+ name,
+ s->s_namecanon.nc_cname == NULL
+ ? "NULL"
+ : s->s_namecanon.nc_cname);
+ errno = s->s_namecanon.nc_errno;
+ SM_SET_H_ERRNO(s->s_namecanon.nc_herrno);
+ *statp = s->s_namecanon.nc_stat;
+ if (*statp == EX_TEMPFAIL)
+ {
+ CurEnv->e_status = "4.4.3";
+ message("851 %s: Name server timeout",
+ shortenstring(name, 33));
+ }
+ if (*statp != EX_OK)
+ return NULL;
+ if (s->s_namecanon.nc_cname == NULL)
+ {
+ syserr("host_map_lookup(%s): bogus NULL cache entry, errno=%d, h_errno=%d",
+ name,
+ s->s_namecanon.nc_errno,
+ s->s_namecanon.nc_herrno);
+ return NULL;
+ }
+ if (bitset(MF_MATCHONLY, map->map_mflags))
+ cp = map_rewrite(map, name, strlen(name), NULL);
+ else
+ cp = map_rewrite(map,
+ s->s_namecanon.nc_cname,
+ strlen(s->s_namecanon.nc_cname),
+ av);
+ return cp;
+ }
+
+ /*
+ ** If we are running without a regular network connection (usually
+ ** dial-on-demand) and we are just queueing, we want to avoid DNS
+ ** lookups because those could try to connect to a server.
+ */
+
+ if (CurEnv->e_sendmode == SM_DEFER &&
+ bitset(MF_DEFER, map->map_mflags))
+ {
+ if (tTd(9, 1))
+ sm_dprintf("host_map_lookup(%s) => DEFERRED\n", name);
+ *statp = EX_TEMPFAIL;
+ return NULL;
+ }
+
+ /*
+ ** If first character is a bracket, then it is an address
+ ** lookup. Address is copied into a temporary buffer to
+ ** strip the brackets and to preserve name if address is
+ ** unknown.
+ */
+
+ if (tTd(9, 1))
+ sm_dprintf("host_map_lookup(%s) => ", name);
+#if NAMED_BIND
+ if (map->map_timeout > 0)
+ {
+ retrans = _res.retrans;
+ _res.retrans = map->map_timeout;
+ }
+ if (map->map_retry > 0)
+ {
+ retry = _res.retry;
+ _res.retry = map->map_retry;
+ }
+#endif /* NAMED_BIND */
+
+ /* set default TTL */
+ s->s_namecanon.nc_exp = now + SM_DEFAULT_TTL;
+ if (*name != '[')
+ {
+ int ttl;
+
+ (void) sm_strlcpy(hbuf, name, sizeof hbuf);
+ if (getcanonname(hbuf, sizeof hbuf - 1, !HasWildcardMX, &ttl))
+ {
+ ans = hbuf;
+ if (ttl > 0)
+ s->s_namecanon.nc_exp = now + SM_MIN(ttl,
+ SM_DEFAULT_TTL);
+ }
+ }
+ else
+ {
+ if ((cp = strchr(name, ']')) == NULL)
+ {
+ if (tTd(9, 1))
+ sm_dprintf("FAILED\n");
+ return NULL;
+ }
+ *cp = '\0';
+
+ hp = NULL;
+#if NETINET
+ if ((in_addr.s_addr = inet_addr(&name[1])) != INADDR_NONE)
+ hp = sm_gethostbyaddr((char *)&in_addr,
+ INADDRSZ, AF_INET);
+#endif /* NETINET */
+#if NETINET6
+ if (hp == NULL &&
+ anynet_pton(AF_INET6, &name[1], &in6_addr) == 1)
+ hp = sm_gethostbyaddr((char *)&in6_addr,
+ IN6ADDRSZ, AF_INET6);
+#endif /* NETINET6 */
+ *cp = ']';
+
+ if (hp != NULL)
+ {
+ /* found a match -- copy out */
+ ans = denlstring((char *) hp->h_name, true, true);
+#if NETINET6
+ if (ans == hp->h_name)
+ {
+ static char n[MAXNAME + 1];
+
+ /* hp->h_name is about to disappear */
+ (void) sm_strlcpy(n, ans, sizeof n);
+ ans = n;
+ }
+ freehostent(hp);
+ hp = NULL;
+#endif /* NETINET6 */
+ }
+ }
+#if NAMED_BIND
+ if (map->map_timeout > 0)
+ _res.retrans = retrans;
+ if (map->map_retry > 0)
+ _res.retry = retry;
+#endif /* NAMED_BIND */
+
+ s->s_namecanon.nc_flags |= NCF_VALID; /* will be soon */
+
+ /* Found an answer */
+ if (ans != NULL)
+ {
+ s->s_namecanon.nc_stat = *statp = EX_OK;
+ if (s->s_namecanon.nc_cname != NULL)
+ sm_free(s->s_namecanon.nc_cname);
+ s->s_namecanon.nc_cname = sm_strdup_x(ans);
+ if (bitset(MF_MATCHONLY, map->map_mflags))
+ cp = map_rewrite(map, name, strlen(name), NULL);
+ else
+ cp = map_rewrite(map, ans, strlen(ans), av);
+ if (tTd(9, 1))
+ sm_dprintf("FOUND %s\n", ans);
+ return cp;
+ }
+
+
+ /* No match found */
+ s->s_namecanon.nc_errno = errno;
+#if NAMED_BIND
+ s->s_namecanon.nc_herrno = h_errno;
+ if (tTd(9, 1))
+ sm_dprintf("FAIL (%d)\n", h_errno);
+ switch (h_errno)
+ {
+ case TRY_AGAIN:
+ if (UseNameServer)
+ {
+ CurEnv->e_status = "4.4.3";
+ message("851 %s: Name server timeout",
+ shortenstring(name, 33));
+ }
+ *statp = EX_TEMPFAIL;
+ break;
+
+ case HOST_NOT_FOUND:
+ case NO_DATA:
+ *statp = EX_NOHOST;
+ break;
+
+ case NO_RECOVERY:
+ *statp = EX_SOFTWARE;
+ break;
+
+ default:
+ *statp = EX_UNAVAILABLE;
+ break;
+ }
+#else /* NAMED_BIND */
+ if (tTd(9, 1))
+ sm_dprintf("FAIL\n");
+ *statp = EX_NOHOST;
+#endif /* NAMED_BIND */
+ s->s_namecanon.nc_stat = *statp;
+ return NULL;
+}
+/*
+** HOST_MAP_INIT -- initialize host class structures
+**
+** Parameters:
+** map -- a pointer to this map.
+** args -- argument string.
+**
+** Returns:
+** true.
+*/
+
+bool
+host_map_init(map, args)
+ MAP *map;
+ char *args;
+{
+ register char *p = args;
+
+ for (;;)
+ {
+ while (isascii(*p) && isspace(*p))
+ p++;
+ if (*p != '-')
+ break;
+ switch (*++p)
+ {
+ case 'a':
+ map->map_app = ++p;
+ break;
+
+ case 'T':
+ map->map_tapp = ++p;
+ break;
+
+ case 'm':
+ map->map_mflags |= MF_MATCHONLY;
+ break;
+
+ case 't':
+ map->map_mflags |= MF_NODEFER;
+ break;
+
+ case 'S': /* only for consistency */
+ map->map_spacesub = *++p;
+ break;
+
+ case 'D':
+ map->map_mflags |= MF_DEFER;
+ break;
+
+ case 'd':
+ {
+ char *h;
+
+ while (isascii(*++p) && isspace(*p))
+ continue;
+ h = strchr(p, ' ');
+ if (h != NULL)
+ *h = '\0';
+ map->map_timeout = convtime(p, 's');
+ if (h != NULL)
+ *h = ' ';
+ }
+ break;
+
+ case 'r':
+ while (isascii(*++p) && isspace(*p))
+ continue;
+ map->map_retry = atoi(p);
+ break;
+ }
+ while (*p != '\0' && !(isascii(*p) && isspace(*p)))
+ p++;
+ if (*p != '\0')
+ *p++ = '\0';
+ }
+ if (map->map_app != NULL)
+ map->map_app = newstr(map->map_app);
+ if (map->map_tapp != NULL)
+ map->map_tapp = newstr(map->map_tapp);
+ return true;
+}
+
+#if NETINET6
+/*
+** ANYNET_NTOP -- convert an IPv6 network address to printable form.
+**
+** Parameters:
+** s6a -- a pointer to an in6_addr structure.
+** dst -- buffer to store result in
+** dst_len -- size of dst buffer
+**
+** Returns:
+** A printable version of that structure.
+*/
+
+char *
+anynet_ntop(s6a, dst, dst_len)
+ struct in6_addr *s6a;
+ char *dst;
+ size_t dst_len;
+{
+ register char *ap;
+
+ if (IN6_IS_ADDR_V4MAPPED(s6a))
+ ap = (char *) inet_ntop(AF_INET,
+ &s6a->s6_addr[IN6ADDRSZ - INADDRSZ],
+ dst, dst_len);
+ else
+ {
+ char *d;
+ size_t sz;
+
+ /* Save pointer to beginning of string */
+ d = dst;
+
+ /* Add IPv6: protocol tag */
+ sz = sm_strlcpy(dst, "IPv6:", dst_len);
+ if (sz >= dst_len)
+ return NULL;
+ dst += sz;
+ dst_len -= sz;
+ ap = (char *) inet_ntop(AF_INET6, s6a, dst, dst_len);
+
+ /* Restore pointer to beginning of string */
+ if (ap != NULL)
+ ap = d;
+ }
+ return ap;
+}
+
+/*
+** ANYNET_PTON -- convert printed form to network address.
+**
+** Wrapper for inet_pton() which handles IPv6: labels.
+**
+** Parameters:
+** family -- address family
+** src -- string
+** dst -- destination address structure
+**
+** Returns:
+** 1 if the address was valid
+** 0 if the address wasn't parseable
+** -1 if error
+*/
+
+int
+anynet_pton(family, src, dst)
+ int family;
+ const char *src;
+ void *dst;
+{
+ if (family == AF_INET6 && sm_strncasecmp(src, "IPv6:", 5) == 0)
+ src += 5;
+ return inet_pton(family, src, dst);
+}
+#endif /* NETINET6 */
+/*
+** ANYNET_NTOA -- convert a network address to printable form.
+**
+** Parameters:
+** sap -- a pointer to a sockaddr structure.
+**
+** Returns:
+** A printable version of that sockaddr.
+*/
+
+#ifdef USE_SOCK_STREAM
+
+# if NETLINK
+# include <net/if_dl.h>
+# endif /* NETLINK */
+
+char *
+anynet_ntoa(sap)
+ register SOCKADDR *sap;
+{
+ register char *bp;
+ register char *ap;
+ int l;
+ static char buf[100];
+
+ /* check for null/zero family */
+ if (sap == NULL)
+ return "NULLADDR";
+ if (sap->sa.sa_family == 0)
+ return "0";
+
+ switch (sap->sa.sa_family)
+ {
+# if NETUNIX
+ case AF_UNIX:
+ if (sap->sunix.sun_path[0] != '\0')
+ (void) sm_snprintf(buf, sizeof buf, "[UNIX: %.64s]",
+ sap->sunix.sun_path);
+ else
+ (void) sm_strlcpy(buf, "[UNIX: localhost]", sizeof buf);
+ return buf;
+# endif /* NETUNIX */
+
+# if NETINET
+ case AF_INET:
+ return (char *) inet_ntoa(sap->sin.sin_addr);
+# endif /* NETINET */
+
+# if NETINET6
+ case AF_INET6:
+ ap = anynet_ntop(&sap->sin6.sin6_addr, buf, sizeof buf);
+ if (ap != NULL)
+ return ap;
+ break;
+# endif /* NETINET6 */
+
+# if NETLINK
+ case AF_LINK:
+ (void) sm_snprintf(buf, sizeof buf, "[LINK: %s]",
+ link_ntoa((struct sockaddr_dl *) &sap->sa));
+ return buf;
+# endif /* NETLINK */
+ default:
+ /* this case is needed when nothing is #defined */
+ /* in order to keep the switch syntactically correct */
+ break;
+ }
+
+ /* unknown family -- just dump bytes */
+ (void) sm_snprintf(buf, sizeof buf, "Family %d: ", sap->sa.sa_family);
+ bp = &buf[strlen(buf)];
+ ap = sap->sa.sa_data;
+ for (l = sizeof sap->sa.sa_data; --l >= 0; )
+ {
+ (void) sm_snprintf(bp, SPACELEFT(buf, bp), "%02x:",
+ *ap++ & 0377);
+ bp += 3;
+ }
+ *--bp = '\0';
+ return buf;
+}
+/*
+** HOSTNAMEBYANYADDR -- return name of host based on address
+**
+** Parameters:
+** sap -- SOCKADDR pointer
+**
+** Returns:
+** text representation of host name.
+**
+** Side Effects:
+** none.
+*/
+
+char *
+hostnamebyanyaddr(sap)
+ register SOCKADDR *sap;
+{
+ register struct hostent *hp;
+# if NAMED_BIND
+ int saveretry;
+# endif /* NAMED_BIND */
+# if NETINET6
+ struct in6_addr in6_addr;
+# endif /* NETINET6 */
+
+# if NAMED_BIND
+ /* shorten name server timeout to avoid higher level timeouts */
+ saveretry = _res.retry;
+ if (_res.retry * _res.retrans > 20)
+ _res.retry = 20 / _res.retrans;
+# endif /* NAMED_BIND */
+
+ switch (sap->sa.sa_family)
+ {
+# if NETINET
+ case AF_INET:
+ hp = sm_gethostbyaddr((char *) &sap->sin.sin_addr,
+ INADDRSZ, AF_INET);
+ break;
+# endif /* NETINET */
+
+# if NETINET6
+ case AF_INET6:
+ hp = sm_gethostbyaddr((char *) &sap->sin6.sin6_addr,
+ IN6ADDRSZ, AF_INET6);
+ break;
+# endif /* NETINET6 */
+
+# if NETISO
+ case AF_ISO:
+ hp = sm_gethostbyaddr((char *) &sap->siso.siso_addr,
+ sizeof sap->siso.siso_addr, AF_ISO);
+ break;
+# endif /* NETISO */
+
+# if NETUNIX
+ case AF_UNIX:
+ hp = NULL;
+ break;
+# endif /* NETUNIX */
+
+ default:
+ hp = sm_gethostbyaddr(sap->sa.sa_data, sizeof sap->sa.sa_data,
+ sap->sa.sa_family);
+ break;
+ }
+
+# if NAMED_BIND
+ _res.retry = saveretry;
+# endif /* NAMED_BIND */
+
+# if NETINET || NETINET6
+ if (hp != NULL && hp->h_name[0] != '['
+# if NETINET6
+ && inet_pton(AF_INET6, hp->h_name, &in6_addr) != 1
+# endif /* NETINET6 */
+# if NETINET
+ && inet_addr(hp->h_name) == INADDR_NONE
+# endif /* NETINET */
+ )
+ {
+ char *name;
+
+ name = denlstring((char *) hp->h_name, true, true);
+# if NETINET6
+ if (name == hp->h_name)
+ {
+ static char n[MAXNAME + 1];
+
+ /* Copy the string, hp->h_name is about to disappear */
+ (void) sm_strlcpy(n, name, sizeof n);
+ name = n;
+ }
+ freehostent(hp);
+# endif /* NETINET6 */
+ return name;
+ }
+# endif /* NETINET || NETINET6 */
+
+# if NETINET6
+ if (hp != NULL)
+ {
+ freehostent(hp);
+ hp = NULL;
+ }
+# endif /* NETINET6 */
+
+# if NETUNIX
+ if (sap->sa.sa_family == AF_UNIX && sap->sunix.sun_path[0] == '\0')
+ return "localhost";
+# endif /* NETUNIX */
+ {
+ static char buf[203];
+
+ (void) sm_snprintf(buf, sizeof buf, "[%.200s]",
+ anynet_ntoa(sap));
+ return buf;
+ }
+}
+#endif /* USE_SOCK_STREAM */
diff --git a/usr/src/cmd/sendmail/src/deliver.c b/usr/src/cmd/sendmail/src/deliver.c
new file mode 100644
index 0000000000..b689972e80
--- /dev/null
+++ b/usr/src/cmd/sendmail/src/deliver.c
@@ -0,0 +1,6283 @@
+/*
+ * Copyright (c) 1998-2005 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sendmail.h>
+#include <sys/time.h>
+
+SM_RCSID("@(#)$Id: deliver.c,v 8.986 2005/03/05 02:28:50 ca Exp $")
+
+#if HASSETUSERCONTEXT
+# include <login_cap.h>
+#endif /* HASSETUSERCONTEXT */
+
+#if NETINET || NETINET6
+# include <arpa/inet.h>
+#endif /* NETINET || NETINET6 */
+
+#if STARTTLS || SASL
+# include "sfsasl.h"
+#endif /* STARTTLS || SASL */
+
+static int deliver __P((ENVELOPE *, ADDRESS *));
+static void dup_queue_file __P((ENVELOPE *, ENVELOPE *, int));
+static void mailfiletimeout __P((int));
+static void endwaittimeout __P((int));
+static int parse_hostsignature __P((char *, char **, MAILER *));
+static void sendenvelope __P((ENVELOPE *, int));
+extern MCI *mci_new __P((SM_RPOOL_T *));
+static int coloncmp __P((const char *, const char *));
+
+#if STARTTLS
+static int starttls __P((MAILER *, MCI *, ENVELOPE *));
+static int endtlsclt __P((MCI *));
+#endif /* STARTTLS */
+# if STARTTLS || SASL
+static bool iscltflgset __P((ENVELOPE *, int));
+# endif /* STARTTLS || SASL */
+
+/*
+** SENDALL -- actually send all the messages.
+**
+** Parameters:
+** e -- the envelope to send.
+** mode -- the delivery mode to use. If SM_DEFAULT, use
+** the current e->e_sendmode.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Scans the send lists and sends everything it finds.
+** Delivers any appropriate error messages.
+** If we are running in a non-interactive mode, takes the
+** appropriate action.
+*/
+
+void
+sendall(e, mode)
+ ENVELOPE *e;
+ int mode;
+{
+ register ADDRESS *q;
+ char *owner;
+ int otherowners;
+ int save_errno;
+ register ENVELOPE *ee;
+ ENVELOPE *splitenv = NULL;
+ int oldverbose = Verbose;
+ bool somedeliveries = false, expensive = false;
+ pid_t pid;
+
+ /*
+ ** If this message is to be discarded, don't bother sending
+ ** the message at all.
+ */
+
+ if (bitset(EF_DISCARD, e->e_flags))
+ {
+ if (tTd(13, 1))
+ sm_dprintf("sendall: discarding id %s\n", e->e_id);
+ e->e_flags |= EF_CLRQUEUE;
+ if (LogLevel > 9)
+ logundelrcpts(e, "discarded", 9, true);
+ else if (LogLevel > 4)
+ sm_syslog(LOG_INFO, e->e_id, "discarded");
+ markstats(e, NULL, STATS_REJECT);
+ return;
+ }
+
+ /*
+ ** If we have had global, fatal errors, don't bother sending
+ ** the message at all if we are in SMTP mode. Local errors
+ ** (e.g., a single address failing) will still cause the other
+ ** addresses to be sent.
+ */
+
+ if (bitset(EF_FATALERRS, e->e_flags) &&
+ (OpMode == MD_SMTP || OpMode == MD_DAEMON))
+ {
+ e->e_flags |= EF_CLRQUEUE;
+ return;
+ }
+
+ /* determine actual delivery mode */
+ if (mode == SM_DEFAULT)
+ {
+ mode = e->e_sendmode;
+ if (mode != SM_VERIFY && mode != SM_DEFER &&
+ shouldqueue(e->e_msgpriority, e->e_ctime))
+ mode = SM_QUEUE;
+ }
+
+ if (tTd(13, 1))
+ {
+ sm_dprintf("\n===== SENDALL: mode %c, id %s, e_from ",
+ mode, e->e_id);
+ printaddr(sm_debug_file(), &e->e_from, false);
+ sm_dprintf("\te_flags = ");
+ printenvflags(e);
+ sm_dprintf("sendqueue:\n");
+ printaddr(sm_debug_file(), e->e_sendqueue, true);
+ }
+
+ /*
+ ** Do any preprocessing necessary for the mode we are running.
+ ** Check to make sure the hop count is reasonable.
+ ** Delete sends to the sender in mailing lists.
+ */
+
+ CurEnv = e;
+ if (tTd(62, 1))
+ checkfds(NULL);
+
+ if (e->e_hopcount > MaxHopCount)
+ {
+ char *recip;
+
+ if (e->e_sendqueue != NULL &&
+ e->e_sendqueue->q_paddr != NULL)
+ recip = e->e_sendqueue->q_paddr;
+ else
+ recip = "(nobody)";
+
+ errno = 0;
+ queueup(e, WILL_BE_QUEUED(mode), false);
+ e->e_flags |= EF_FATALERRS|EF_PM_NOTIFY|EF_CLRQUEUE;
+ ExitStat = EX_UNAVAILABLE;
+ syserr("554 5.4.6 Too many hops %d (%d max): from %s via %s, to %s",
+ e->e_hopcount, MaxHopCount, e->e_from.q_paddr,
+ RealHostName == NULL ? "localhost" : RealHostName,
+ recip);
+ for (q = e->e_sendqueue; q != NULL; q = q->q_next)
+ {
+ if (QS_IS_DEAD(q->q_state))
+ continue;
+ q->q_state = QS_BADADDR;
+ q->q_status = "5.4.6";
+ q->q_rstatus = "554 5.4.6 Too many hops";
+ }
+ return;
+ }
+
+ /*
+ ** Do sender deletion.
+ **
+ ** If the sender should be queued up, skip this.
+ ** This can happen if the name server is hosed when you
+ ** are trying to send mail. The result is that the sender
+ ** is instantiated in the queue as a recipient.
+ */
+
+ if (!bitset(EF_METOO, e->e_flags) &&
+ !QS_IS_QUEUEUP(e->e_from.q_state))
+ {
+ if (tTd(13, 5))
+ {
+ sm_dprintf("sendall: QS_SENDER ");
+ printaddr(sm_debug_file(), &e->e_from, false);
+ }
+ e->e_from.q_state = QS_SENDER;
+ (void) recipient(&e->e_from, &e->e_sendqueue, 0, e);
+ }
+
+ /*
+ ** Handle alias owners.
+ **
+ ** We scan up the q_alias chain looking for owners.
+ ** We discard owners that are the same as the return path.
+ */
+
+ for (q = e->e_sendqueue; q != NULL; q = q->q_next)
+ {
+ register struct address *a;
+
+ for (a = q; a != NULL && a->q_owner == NULL; a = a->q_alias)
+ continue;
+ if (a != NULL)
+ q->q_owner = a->q_owner;
+
+ if (q->q_owner != NULL &&
+ !QS_IS_DEAD(q->q_state) &&
+ strcmp(q->q_owner, e->e_from.q_paddr) == 0)
+ q->q_owner = NULL;
+ }
+
+ if (tTd(13, 25))
+ {
+ sm_dprintf("\nAfter first owner pass, sendq =\n");
+ printaddr(sm_debug_file(), e->e_sendqueue, true);
+ }
+
+ owner = "";
+ otherowners = 1;
+ while (owner != NULL && otherowners > 0)
+ {
+ if (tTd(13, 28))
+ sm_dprintf("owner = \"%s\", otherowners = %d\n",
+ owner, otherowners);
+ owner = NULL;
+ otherowners = bitset(EF_SENDRECEIPT, e->e_flags) ? 1 : 0;
+
+ for (q = e->e_sendqueue; q != NULL; q = q->q_next)
+ {
+ if (tTd(13, 30))
+ {
+ sm_dprintf("Checking ");
+ printaddr(sm_debug_file(), q, false);
+ }
+ if (QS_IS_DEAD(q->q_state))
+ {
+ if (tTd(13, 30))
+ sm_dprintf(" ... QS_IS_DEAD\n");
+ continue;
+ }
+ if (tTd(13, 29) && !tTd(13, 30))
+ {
+ sm_dprintf("Checking ");
+ printaddr(sm_debug_file(), q, false);
+ }
+
+ if (q->q_owner != NULL)
+ {
+ if (owner == NULL)
+ {
+ if (tTd(13, 40))
+ sm_dprintf(" ... First owner = \"%s\"\n",
+ q->q_owner);
+ owner = q->q_owner;
+ }
+ else if (owner != q->q_owner)
+ {
+ if (strcmp(owner, q->q_owner) == 0)
+ {
+ if (tTd(13, 40))
+ sm_dprintf(" ... Same owner = \"%s\"\n",
+ owner);
+
+ /* make future comparisons cheap */
+ q->q_owner = owner;
+ }
+ else
+ {
+ if (tTd(13, 40))
+ sm_dprintf(" ... Another owner \"%s\"\n",
+ q->q_owner);
+ otherowners++;
+ }
+ owner = q->q_owner;
+ }
+ else if (tTd(13, 40))
+ sm_dprintf(" ... Same owner = \"%s\"\n",
+ owner);
+ }
+ else
+ {
+ if (tTd(13, 40))
+ sm_dprintf(" ... Null owner\n");
+ otherowners++;
+ }
+
+ if (QS_IS_BADADDR(q->q_state))
+ {
+ if (tTd(13, 30))
+ sm_dprintf(" ... QS_IS_BADADDR\n");
+ continue;
+ }
+
+ if (QS_IS_QUEUEUP(q->q_state))
+ {
+ MAILER *m = q->q_mailer;
+
+ /*
+ ** If we have temporary address failures
+ ** (e.g., dns failure) and a fallback MX is
+ ** set, send directly to the fallback MX host.
+ */
+
+ if (FallbackMX != NULL &&
+ !wordinclass(FallbackMX, 'w') &&
+ mode != SM_VERIFY &&
+ !bitnset(M_NOMX, m->m_flags) &&
+ strcmp(m->m_mailer, "[IPC]") == 0 &&
+ m->m_argv[0] != NULL &&
+ strcmp(m->m_argv[0], "TCP") == 0)
+ {
+ int len;
+ char *p;
+
+ if (tTd(13, 30))
+ sm_dprintf(" ... FallbackMX\n");
+
+ len = strlen(FallbackMX) + 1;
+ p = sm_rpool_malloc_x(e->e_rpool, len);
+ (void) sm_strlcpy(p, FallbackMX, len);
+ q->q_state = QS_OK;
+ q->q_host = p;
+ }
+ else
+ {
+ if (tTd(13, 30))
+ sm_dprintf(" ... QS_IS_QUEUEUP\n");
+ continue;
+ }
+ }
+
+ /*
+ ** If this mailer is expensive, and if we don't
+ ** want to make connections now, just mark these
+ ** addresses and return. This is useful if we
+ ** want to batch connections to reduce load. This
+ ** will cause the messages to be queued up, and a
+ ** daemon will come along to send the messages later.
+ */
+
+ if (NoConnect && !Verbose &&
+ bitnset(M_EXPENSIVE, q->q_mailer->m_flags))
+ {
+ if (tTd(13, 30))
+ sm_dprintf(" ... expensive\n");
+ q->q_state = QS_QUEUEUP;
+ expensive = true;
+ }
+ else if (bitnset(M_HOLD, q->q_mailer->m_flags) &&
+ QueueLimitId == NULL &&
+ QueueLimitSender == NULL &&
+ QueueLimitRecipient == NULL)
+ {
+ if (tTd(13, 30))
+ sm_dprintf(" ... hold\n");
+ q->q_state = QS_QUEUEUP;
+ expensive = true;
+ }
+ else if (QueueMode != QM_QUARANTINE &&
+ e->e_quarmsg != NULL)
+ {
+ if (tTd(13, 30))
+ sm_dprintf(" ... quarantine: %s\n",
+ e->e_quarmsg);
+ q->q_state = QS_QUEUEUP;
+ expensive = true;
+ }
+ else
+ {
+ if (tTd(13, 30))
+ sm_dprintf(" ... deliverable\n");
+ somedeliveries = true;
+ }
+ }
+
+ if (owner != NULL && otherowners > 0)
+ {
+ /*
+ ** Split this envelope into two.
+ */
+
+ ee = (ENVELOPE *) sm_rpool_malloc_x(e->e_rpool,
+ sizeof *ee);
+ STRUCTCOPY(*e, *ee);
+ ee->e_message = NULL;
+ ee->e_id = NULL;
+ assign_queueid(ee);
+
+ if (tTd(13, 1))
+ sm_dprintf("sendall: split %s into %s, owner = \"%s\", otherowners = %d\n",
+ e->e_id, ee->e_id, owner,
+ otherowners);
+
+ ee->e_header = copyheader(e->e_header, ee->e_rpool);
+ ee->e_sendqueue = copyqueue(e->e_sendqueue,
+ ee->e_rpool);
+ ee->e_errorqueue = copyqueue(e->e_errorqueue,
+ ee->e_rpool);
+ ee->e_flags = e->e_flags & ~(EF_INQUEUE|EF_CLRQUEUE|EF_FATALERRS|EF_SENDRECEIPT|EF_RET_PARAM);
+ ee->e_flags |= EF_NORECEIPT;
+ setsender(owner, ee, NULL, '\0', true);
+ if (tTd(13, 5))
+ {
+ sm_dprintf("sendall(split): QS_SENDER ");
+ printaddr(sm_debug_file(), &ee->e_from, false);
+ }
+ ee->e_from.q_state = QS_SENDER;
+ ee->e_dfp = NULL;
+ ee->e_lockfp = NULL;
+ ee->e_xfp = NULL;
+ ee->e_qgrp = e->e_qgrp;
+ ee->e_qdir = e->e_qdir;
+ ee->e_errormode = EM_MAIL;
+ ee->e_sibling = splitenv;
+ ee->e_statmsg = NULL;
+ if (e->e_quarmsg != NULL)
+ ee->e_quarmsg = sm_rpool_strdup_x(ee->e_rpool,
+ e->e_quarmsg);
+ splitenv = ee;
+
+ for (q = e->e_sendqueue; q != NULL; q = q->q_next)
+ {
+ if (q->q_owner == owner)
+ {
+ q->q_state = QS_CLONED;
+ if (tTd(13, 6))
+ sm_dprintf("\t... stripping %s from original envelope\n",
+ q->q_paddr);
+ }
+ }
+ for (q = ee->e_sendqueue; q != NULL; q = q->q_next)
+ {
+ if (q->q_owner != owner)
+ {
+ q->q_state = QS_CLONED;
+ if (tTd(13, 6))
+ sm_dprintf("\t... dropping %s from cloned envelope\n",
+ q->q_paddr);
+ }
+ else
+ {
+ /* clear DSN parameters */
+ q->q_flags &= ~(QHASNOTIFY|Q_PINGFLAGS);
+ q->q_flags |= DefaultNotify & ~QPINGONSUCCESS;
+ if (tTd(13, 6))
+ sm_dprintf("\t... moving %s to cloned envelope\n",
+ q->q_paddr);
+ }
+ }
+
+ if (mode != SM_VERIFY && bitset(EF_HAS_DF, e->e_flags))
+ dup_queue_file(e, ee, DATAFL_LETTER);
+
+ /*
+ ** Give the split envelope access to the parent
+ ** transcript file for errors obtained while
+ ** processing the recipients (done before the
+ ** envelope splitting).
+ */
+
+ if (e->e_xfp != NULL)
+ ee->e_xfp = sm_io_dup(e->e_xfp);
+
+ /* failed to dup e->e_xfp, start a new transcript */
+ if (ee->e_xfp == NULL)
+ openxscript(ee);
+
+ if (mode != SM_VERIFY && LogLevel > 4)
+ sm_syslog(LOG_INFO, e->e_id,
+ "%s: clone: owner=%s",
+ ee->e_id, owner);
+ }
+ }
+
+ if (owner != NULL)
+ {
+ setsender(owner, e, NULL, '\0', true);
+ if (tTd(13, 5))
+ {
+ sm_dprintf("sendall(owner): QS_SENDER ");
+ printaddr(sm_debug_file(), &e->e_from, false);
+ }
+ e->e_from.q_state = QS_SENDER;
+ e->e_errormode = EM_MAIL;
+ e->e_flags |= EF_NORECEIPT;
+ e->e_flags &= ~EF_FATALERRS;
+ }
+
+ /* if nothing to be delivered, just queue up everything */
+ if (!somedeliveries && !WILL_BE_QUEUED(mode) &&
+ mode != SM_VERIFY)
+ {
+ time_t now;
+
+ if (tTd(13, 29))
+ sm_dprintf("No deliveries: auto-queuing\n");
+ mode = SM_QUEUE;
+ now = curtime();
+
+ /* treat this as a delivery in terms of counting tries */
+ e->e_dtime = now;
+ if (!expensive)
+ e->e_ntries++;
+ for (ee = splitenv; ee != NULL; ee = ee->e_sibling)
+ {
+ ee->e_dtime = now;
+ if (!expensive)
+ ee->e_ntries++;
+ }
+ }
+
+ if ((WILL_BE_QUEUED(mode) || mode == SM_FORK ||
+ (mode != SM_VERIFY &&
+ (SuperSafe == SAFE_REALLY ||
+ SuperSafe == SAFE_REALLY_POSTMILTER))) &&
+ (!bitset(EF_INQUEUE, e->e_flags) || splitenv != NULL))
+ {
+ bool msync;
+
+ /*
+ ** Be sure everything is instantiated in the queue.
+ ** Split envelopes first in case the machine crashes.
+ ** If the original were done first, we may lose
+ ** recipients.
+ */
+
+#if !HASFLOCK
+ msync = false;
+#else /* !HASFLOCK */
+ msync = mode == SM_FORK;
+#endif /* !HASFLOCK */
+
+ for (ee = splitenv; ee != NULL; ee = ee->e_sibling)
+ queueup(ee, WILL_BE_QUEUED(mode), msync);
+ queueup(e, WILL_BE_QUEUED(mode), msync);
+ }
+
+ if (tTd(62, 10))
+ checkfds("after envelope splitting");
+
+ /*
+ ** If we belong in background, fork now.
+ */
+
+ if (tTd(13, 20))
+ {
+ sm_dprintf("sendall: final mode = %c\n", mode);
+ if (tTd(13, 21))
+ {
+ sm_dprintf("\n================ Final Send Queue(s) =====================\n");
+ sm_dprintf("\n *** Envelope %s, e_from=%s ***\n",
+ e->e_id, e->e_from.q_paddr);
+ printaddr(sm_debug_file(), e->e_sendqueue, true);
+ for (ee = splitenv; ee != NULL; ee = ee->e_sibling)
+ {
+ sm_dprintf("\n *** Envelope %s, e_from=%s ***\n",
+ ee->e_id, ee->e_from.q_paddr);
+ printaddr(sm_debug_file(), ee->e_sendqueue, true);
+ }
+ sm_dprintf("==========================================================\n\n");
+ }
+ }
+ switch (mode)
+ {
+ case SM_VERIFY:
+ Verbose = 2;
+ break;
+
+ case SM_QUEUE:
+ case SM_DEFER:
+#if HASFLOCK
+ queueonly:
+#endif /* HASFLOCK */
+ if (e->e_nrcpts > 0)
+ e->e_flags |= EF_INQUEUE;
+ dropenvelope(e, splitenv != NULL, true);
+ for (ee = splitenv; ee != NULL; ee = ee->e_sibling)
+ {
+ if (ee->e_nrcpts > 0)
+ ee->e_flags |= EF_INQUEUE;
+ dropenvelope(ee, false, true);
+ }
+ return;
+
+ case SM_FORK:
+ if (e->e_xfp != NULL)
+ (void) sm_io_flush(e->e_xfp, SM_TIME_DEFAULT);
+
+#if !HASFLOCK
+ /*
+ ** Since fcntl locking has the interesting semantic that
+ ** the lock is owned by a process, not by an open file
+ ** descriptor, we have to flush this to the queue, and
+ ** then restart from scratch in the child.
+ */
+
+ {
+ /* save id for future use */
+ char *qid = e->e_id;
+
+ /* now drop the envelope in the parent */
+ e->e_flags |= EF_INQUEUE;
+ dropenvelope(e, splitenv != NULL, false);
+
+ /* arrange to reacquire lock after fork */
+ e->e_id = qid;
+ }
+
+ for (ee = splitenv; ee != NULL; ee = ee->e_sibling)
+ {
+ /* save id for future use */
+ char *qid = ee->e_id;
+
+ /* drop envelope in parent */
+ ee->e_flags |= EF_INQUEUE;
+ dropenvelope(ee, false, false);
+
+ /* and save qid for reacquisition */
+ ee->e_id = qid;
+ }
+
+#endif /* !HASFLOCK */
+
+ /*
+ ** Since the delivery may happen in a child and the parent
+ ** does not wait, the parent may close the maps thereby
+ ** removing any shared memory used by the map. Therefore,
+ ** close the maps now so the child will dynamically open
+ ** them if necessary.
+ */
+
+ closemaps(false);
+
+ pid = fork();
+ if (pid < 0)
+ {
+ syserr("deliver: fork 1");
+#if HASFLOCK
+ goto queueonly;
+#else /* HASFLOCK */
+ e->e_id = NULL;
+ for (ee = splitenv; ee != NULL; ee = ee->e_sibling)
+ ee->e_id = NULL;
+ return;
+#endif /* HASFLOCK */
+ }
+ else if (pid > 0)
+ {
+#if HASFLOCK
+ /* be sure we leave the temp files to our child */
+ /* close any random open files in the envelope */
+ closexscript(e);
+ if (e->e_dfp != NULL)
+ (void) sm_io_close(e->e_dfp, SM_TIME_DEFAULT);
+ e->e_dfp = NULL;
+ e->e_flags &= ~EF_HAS_DF;
+
+ /* can't call unlockqueue to avoid unlink of xfp */
+ if (e->e_lockfp != NULL)
+ (void) sm_io_close(e->e_lockfp, SM_TIME_DEFAULT);
+ else
+ syserr("%s: sendall: null lockfp", e->e_id);
+ e->e_lockfp = NULL;
+#endif /* HASFLOCK */
+
+ /* make sure the parent doesn't own the envelope */
+ e->e_id = NULL;
+
+#if USE_DOUBLE_FORK
+ /* catch intermediate zombie */
+ (void) waitfor(pid);
+#endif /* USE_DOUBLE_FORK */
+ return;
+ }
+
+ /* Reset global flags */
+ RestartRequest = NULL;
+ RestartWorkGroup = false;
+ ShutdownRequest = NULL;
+ PendingSignal = 0;
+
+ /*
+ ** Initialize exception stack and default exception
+ ** handler for child process.
+ */
+
+ sm_exc_newthread(fatal_error);
+
+ /*
+ ** Since we have accepted responsbility for the message,
+ ** change the SIGTERM handler. intsig() (the old handler)
+ ** would remove the envelope if this was a command line
+ ** message submission.
+ */
+
+ (void) sm_signal(SIGTERM, SIG_DFL);
+
+#if USE_DOUBLE_FORK
+ /* double fork to avoid zombies */
+ pid = fork();
+ if (pid > 0)
+ exit(EX_OK);
+ save_errno = errno;
+#endif /* USE_DOUBLE_FORK */
+
+ CurrentPid = getpid();
+
+ /* be sure we are immune from the terminal */
+ disconnect(2, e);
+ clearstats();
+
+ /* prevent parent from waiting if there was an error */
+ if (pid < 0)
+ {
+ errno = save_errno;
+ syserr("deliver: fork 2");
+#if HASFLOCK
+ e->e_flags |= EF_INQUEUE;
+#else /* HASFLOCK */
+ e->e_id = NULL;
+#endif /* HASFLOCK */
+ finis(true, true, ExitStat);
+ }
+
+ /* be sure to give error messages in child */
+ QuickAbort = false;
+
+ /*
+ ** Close any cached connections.
+ **
+ ** We don't send the QUIT protocol because the parent
+ ** still knows about the connection.
+ **
+ ** This should only happen when delivering an error
+ ** message.
+ */
+
+ mci_flush(false, NULL);
+
+#if HASFLOCK
+ break;
+#else /* HASFLOCK */
+
+ /*
+ ** Now reacquire and run the various queue files.
+ */
+
+ for (ee = splitenv; ee != NULL; ee = ee->e_sibling)
+ {
+ ENVELOPE *sibling = ee->e_sibling;
+
+ (void) dowork(ee->e_qgrp, ee->e_qdir, ee->e_id,
+ false, false, ee);
+ ee->e_sibling = sibling;
+ }
+ (void) dowork(e->e_qgrp, e->e_qdir, e->e_id,
+ false, false, e);
+ finis(true, true, ExitStat);
+#endif /* HASFLOCK */
+ }
+
+ sendenvelope(e, mode);
+ dropenvelope(e, true, true);
+ for (ee = splitenv; ee != NULL; ee = ee->e_sibling)
+ {
+ CurEnv = ee;
+ if (mode != SM_VERIFY)
+ openxscript(ee);
+ sendenvelope(ee, mode);
+ dropenvelope(ee, true, true);
+ }
+ CurEnv = e;
+
+ Verbose = oldverbose;
+ if (mode == SM_FORK)
+ finis(true, true, ExitStat);
+}
+
+static void
+sendenvelope(e, mode)
+ register ENVELOPE *e;
+ int mode;
+{
+ register ADDRESS *q;
+ bool didany;
+
+ if (tTd(13, 10))
+ sm_dprintf("sendenvelope(%s) e_flags=0x%lx\n",
+ e->e_id == NULL ? "[NOQUEUE]" : e->e_id,
+ e->e_flags);
+ if (LogLevel > 80)
+ sm_syslog(LOG_DEBUG, e->e_id,
+ "sendenvelope, flags=0x%lx",
+ e->e_flags);
+
+ /*
+ ** If we have had global, fatal errors, don't bother sending
+ ** the message at all if we are in SMTP mode. Local errors
+ ** (e.g., a single address failing) will still cause the other
+ ** addresses to be sent.
+ */
+
+ if (bitset(EF_FATALERRS, e->e_flags) &&
+ (OpMode == MD_SMTP || OpMode == MD_DAEMON))
+ {
+ e->e_flags |= EF_CLRQUEUE;
+ return;
+ }
+
+ /*
+ ** Don't attempt deliveries if we want to bounce now
+ ** or if deliver-by time is exceeded.
+ */
+
+ if (!bitset(EF_RESPONSE, e->e_flags) &&
+ (TimeOuts.to_q_return[e->e_timeoutclass] == NOW ||
+ (IS_DLVR_RETURN(e) && e->e_deliver_by > 0 &&
+ curtime() > e->e_ctime + e->e_deliver_by)))
+ return;
+
+ /*
+ ** Run through the list and send everything.
+ **
+ ** Set EF_GLOBALERRS so that error messages during delivery
+ ** result in returned mail.
+ */
+
+ e->e_nsent = 0;
+ e->e_flags |= EF_GLOBALERRS;
+
+ macdefine(&e->e_macro, A_PERM, macid("{envid}"), e->e_envid);
+ macdefine(&e->e_macro, A_PERM, macid("{bodytype}"), e->e_bodytype);
+ didany = false;
+
+ if (!bitset(EF_SPLIT, e->e_flags))
+ {
+ ENVELOPE *oldsib;
+ ENVELOPE *ee;
+
+ /*
+ ** Save old sibling and set it to NULL to avoid
+ ** queueing up the same envelopes again.
+ ** This requires that envelopes in that list have
+ ** been take care of before (or at some other place).
+ */
+
+ oldsib = e->e_sibling;
+ e->e_sibling = NULL;
+ if (!split_by_recipient(e) &&
+ bitset(EF_FATALERRS, e->e_flags))
+ {
+ if (OpMode == MD_SMTP || OpMode == MD_DAEMON)
+ e->e_flags |= EF_CLRQUEUE;
+ return;
+ }
+ for (ee = e->e_sibling; ee != NULL; ee = ee->e_sibling)
+ queueup(ee, false, true);
+
+ /* clean up */
+ for (ee = e->e_sibling; ee != NULL; ee = ee->e_sibling)
+ {
+ /* now unlock the job */
+ closexscript(ee);
+ unlockqueue(ee);
+
+ /* this envelope is marked unused */
+ if (ee->e_dfp != NULL)
+ {
+ (void) sm_io_close(ee->e_dfp, SM_TIME_DEFAULT);
+ ee->e_dfp = NULL;
+ }
+ ee->e_id = NULL;
+ ee->e_flags &= ~EF_HAS_DF;
+ }
+ e->e_sibling = oldsib;
+ }
+
+ /* now run through the queue */
+ for (q = e->e_sendqueue; q != NULL; q = q->q_next)
+ {
+#if XDEBUG
+ char wbuf[MAXNAME + 20];
+
+ (void) sm_snprintf(wbuf, sizeof wbuf, "sendall(%.*s)",
+ MAXNAME, q->q_paddr);
+ checkfd012(wbuf);
+#endif /* XDEBUG */
+ if (mode == SM_VERIFY)
+ {
+ e->e_to = q->q_paddr;
+ if (QS_IS_SENDABLE(q->q_state))
+ {
+ if (q->q_host != NULL && q->q_host[0] != '\0')
+ message("deliverable: mailer %s, host %s, user %s",
+ q->q_mailer->m_name,
+ q->q_host,
+ q->q_user);
+ else
+ message("deliverable: mailer %s, user %s",
+ q->q_mailer->m_name,
+ q->q_user);
+ }
+ }
+ else if (QS_IS_OK(q->q_state))
+ {
+ /*
+ ** Checkpoint the send list every few addresses
+ */
+
+ if (CheckpointInterval > 0 &&
+ e->e_nsent >= CheckpointInterval)
+ {
+ queueup(e, false, false);
+ e->e_nsent = 0;
+ }
+ (void) deliver(e, q);
+ didany = true;
+ }
+ }
+ if (didany)
+ {
+ e->e_dtime = curtime();
+ e->e_ntries++;
+ }
+
+#if XDEBUG
+ checkfd012("end of sendenvelope");
+#endif /* XDEBUG */
+}
+
+#if REQUIRES_DIR_FSYNC
+/*
+** SYNC_DIR -- fsync a directory based on a filename
+**
+** Parameters:
+** filename -- path of file
+** panic -- panic?
+**
+** Returns:
+** none
+*/
+
+void
+sync_dir(filename, panic)
+ char *filename;
+ bool panic;
+{
+ int dirfd;
+ char *dirp;
+ char dir[MAXPATHLEN];
+
+ if (!RequiresDirfsync)
+ return;
+
+ /* filesystems which require the directory be synced */
+ dirp = strrchr(filename, '/');
+ if (dirp != NULL)
+ {
+ if (sm_strlcpy(dir, filename, sizeof dir) >= sizeof dir)
+ return;
+ dir[dirp - filename] = '\0';
+ dirp = dir;
+ }
+ else
+ dirp = ".";
+ dirfd = open(dirp, O_RDONLY, 0700);
+ if (tTd(40,32))
+ sm_syslog(LOG_INFO, NOQID, "sync_dir: %s: fsync(%d)",
+ dirp, dirfd);
+ if (dirfd >= 0)
+ {
+ if (fsync(dirfd) < 0)
+ {
+ if (panic)
+ syserr("!sync_dir: cannot fsync directory %s",
+ dirp);
+ else if (LogLevel > 1)
+ sm_syslog(LOG_ERR, NOQID,
+ "sync_dir: cannot fsync directory %s: %s",
+ dirp, sm_errstring(errno));
+ }
+ (void) close(dirfd);
+ }
+}
+#endif /* REQUIRES_DIR_FSYNC */
+/*
+** DUP_QUEUE_FILE -- duplicate a queue file into a split queue
+**
+** Parameters:
+** e -- the existing envelope
+** ee -- the new envelope
+** type -- the queue file type (e.g., DATAFL_LETTER)
+**
+** Returns:
+** none
+*/
+
+static void
+dup_queue_file(e, ee, type)
+ ENVELOPE *e, *ee;
+ int type;
+{
+ char f1buf[MAXPATHLEN], f2buf[MAXPATHLEN];
+
+ ee->e_dfp = NULL;
+ ee->e_xfp = NULL;
+
+ /*
+ ** Make sure both are in the same directory.
+ */
+
+ (void) sm_strlcpy(f1buf, queuename(e, type), sizeof f1buf);
+ (void) sm_strlcpy(f2buf, queuename(ee, type), sizeof f2buf);
+
+ /* Force the df to disk if it's not there yet */
+ if (type == DATAFL_LETTER && e->e_dfp != NULL &&
+ sm_io_setinfo(e->e_dfp, SM_BF_COMMIT, NULL) < 0 &&
+ errno != EINVAL)
+ {
+ syserr("!dup_queue_file: can't commit %s", f1buf);
+ /* NOTREACHED */
+ }
+
+ if (link(f1buf, f2buf) < 0)
+ {
+ int save_errno = errno;
+
+ syserr("sendall: link(%s, %s)", f1buf, f2buf);
+ if (save_errno == EEXIST)
+ {
+ if (unlink(f2buf) < 0)
+ {
+ syserr("!sendall: unlink(%s): permanent",
+ f2buf);
+ /* NOTREACHED */
+ }
+ if (link(f1buf, f2buf) < 0)
+ {
+ syserr("!sendall: link(%s, %s): permanent",
+ f1buf, f2buf);
+ /* NOTREACHED */
+ }
+ }
+ }
+ SYNC_DIR(f2buf, true);
+}
+/*
+** DOFORK -- do a fork, retrying a couple of times on failure.
+**
+** This MUST be a macro, since after a vfork we are running
+** two processes on the same stack!!!
+**
+** Parameters:
+** none.
+**
+** Returns:
+** From a macro??? You've got to be kidding!
+**
+** Side Effects:
+** Modifies the ==> LOCAL <== variable 'pid', leaving:
+** pid of child in parent, zero in child.
+** -1 on unrecoverable error.
+**
+** Notes:
+** I'm awfully sorry this looks so awful. That's
+** vfork for you.....
+*/
+
+#define NFORKTRIES 5
+
+#ifndef FORK
+# define FORK fork
+#endif /* ! FORK */
+
+#define DOFORK(fORKfN) \
+{\
+ register int i;\
+\
+ for (i = NFORKTRIES; --i >= 0; )\
+ {\
+ pid = fORKfN();\
+ if (pid >= 0)\
+ break;\
+ if (i > 0)\
+ (void) sleep((unsigned) NFORKTRIES - i);\
+ }\
+}
+/*
+** DOFORK -- simple fork interface to DOFORK.
+**
+** Parameters:
+** none.
+**
+** Returns:
+** pid of child in parent.
+** zero in child.
+** -1 on error.
+**
+** Side Effects:
+** returns twice, once in parent and once in child.
+*/
+
+pid_t
+dofork()
+{
+ register pid_t pid = -1;
+
+ DOFORK(fork);
+ return pid;
+}
+
+/*
+** COLONCMP -- compare host-signatures up to first ':' or EOS
+**
+** This takes two strings which happen to be host-signatures and
+** compares them. If the lowest preference portions of the MX-RR's
+** match (up to ':' or EOS, whichever is first), then we have
+** match. This is used for coattail-piggybacking messages during
+** message delivery.
+** If the signatures are the same up to the first ':' the remainder of
+** the signatures are then compared with a normal strcmp(). This saves
+** re-examining the first part of the signatures.
+**
+** Parameters:
+** a - first host-signature
+** b - second host-signature
+**
+** Returns:
+** HS_MATCH_NO -- no "match".
+** HS_MATCH_FIRST -- "match" for the first MX preference
+** (up to the first colon (':')).
+** HS_MATCH_FULL -- match for the entire MX record.
+**
+** Side Effects:
+** none.
+*/
+
+#define HS_MATCH_NO 0
+#define HS_MATCH_FIRST 1
+#define HS_MATCH_FULL 2
+
+static int
+coloncmp(a, b)
+ register const char *a;
+ register const char *b;
+{
+ int ret = HS_MATCH_NO;
+ int braclev = 0;
+
+ while (*a == *b++)
+ {
+ /* Need to account for IPv6 bracketed addresses */
+ if (*a == '[')
+ braclev++;
+ else if (*a == ']' && braclev > 0)
+ braclev--;
+ else if (*a == ':' && braclev <= 0)
+ {
+ ret = HS_MATCH_FIRST;
+ a++;
+ break;
+ }
+ else if (*a == '\0')
+ return HS_MATCH_FULL; /* a full match */
+ a++;
+ }
+ if (ret == HS_MATCH_NO &&
+ braclev <= 0 &&
+ ((*a == '\0' && *(b - 1) == ':') ||
+ (*a == ':' && *(b - 1) == '\0')))
+ return HS_MATCH_FIRST;
+ if (ret == HS_MATCH_FIRST && strcmp(a, b) == 0)
+ return HS_MATCH_FULL;
+
+ return ret;
+}
+
+/*
+** SHOULD_TRY_FBSH -- Should try FallbackSmartHost?
+**
+** Parameters:
+** e -- envelope
+** tried_fallbacksmarthost -- has been tried already? (in/out)
+** hostbuf -- buffer for hostname (expand FallbackSmartHost) (out)
+** hbsz -- size of hostbuf
+** status -- current delivery status
+**
+** Returns:
+** true iff FallbackSmartHost should be tried.
+*/
+
+static bool
+should_try_fbsh(e, tried_fallbacksmarthost, hostbuf, hbsz, status)
+ ENVELOPE *e;
+ bool *tried_fallbacksmarthost;
+ char *hostbuf;
+ size_t hbsz;
+ int status;
+{
+ /*
+ ** If the host was not found and a FallbackSmartHost is defined
+ ** (and we have not yet tried it), then make one last try with
+ ** it as the host.
+ */
+
+ if (status == EX_NOHOST && FallbackSmartHost != NULL &&
+ !*tried_fallbacksmarthost)
+ {
+ *tried_fallbacksmarthost = true;
+ expand(FallbackSmartHost, hostbuf, hbsz, e);
+ if (!wordinclass(hostbuf, 'w'))
+ {
+ if (tTd(11, 1))
+ sm_dprintf("one last try with FallbackSmartHost %s\n",
+ hostbuf);
+ return true;
+ }
+ }
+ return false;
+}
+/*
+** DELIVER -- Deliver a message to a list of addresses.
+**
+** This routine delivers to everyone on the same host as the
+** user on the head of the list. It is clever about mailers
+** that don't handle multiple users. It is NOT guaranteed
+** that it will deliver to all these addresses however -- so
+** deliver should be called once for each address on the
+** list.
+** Deliver tries to be as opportunistic as possible about piggybacking
+** messages. Some definitions to make understanding easier follow below.
+** Piggybacking occurs when an existing connection to a mail host can
+** be used to send the same message to more than one recipient at the
+** same time. So "no piggybacking" means one message for one recipient
+** per connection. "Intentional piggybacking" happens when the
+** recipients' host address (not the mail host address) is used to
+** attempt piggybacking. Recipients with the same host address
+** have the same mail host. "Coincidental piggybacking" relies on
+** piggybacking based on all the mail host addresses in the MX-RR. This
+** is "coincidental" in the fact it could not be predicted until the
+** MX Resource Records for the hosts were obtained and examined. For
+** example (preference order and equivalence is important, not values):
+** domain1 IN MX 10 mxhost-A
+** IN MX 20 mxhost-B
+** domain2 IN MX 4 mxhost-A
+** IN MX 8 mxhost-B
+** Domain1 and domain2 can piggyback the same message to mxhost-A or
+** mxhost-B (if mxhost-A cannot be reached).
+** "Coattail piggybacking" relaxes the strictness of "coincidental
+** piggybacking" in the hope that most significant (lowest value)
+** MX preference host(s) can create more piggybacking. For example
+** (again, preference order and equivalence is important, not values):
+** domain3 IN MX 100 mxhost-C
+** IN MX 100 mxhost-D
+** IN MX 200 mxhost-E
+** domain4 IN MX 50 mxhost-C
+** IN MX 50 mxhost-D
+** IN MX 80 mxhost-F
+** A message for domain3 and domain4 can piggyback to mxhost-C if mxhost-C
+** is available. Same with mxhost-D because in both RR's the preference
+** value is the same as mxhost-C, respectively.
+** So deliver attempts coattail piggybacking when possible. If the
+** first MX preference level hosts cannot be used then the piggybacking
+** reverts to coincidental piggybacking. Using the above example you
+** cannot deliver to mxhost-F for domain3 regardless of preference value.
+** ("Coattail" from "riding on the coattails of your predecessor" meaning
+** gaining benefit from a predecessor effort with no or little addition
+** effort. The predecessor here being the preceding MX RR).
+**
+** Parameters:
+** e -- the envelope to deliver.
+** firstto -- head of the address list to deliver to.
+**
+** Returns:
+** zero -- successfully delivered.
+** else -- some failure, see ExitStat for more info.
+**
+** Side Effects:
+** The standard input is passed off to someone.
+*/
+
+static int
+deliver(e, firstto)
+ register ENVELOPE *e;
+ ADDRESS *firstto;
+{
+ char *host; /* host being sent to */
+ char *user; /* user being sent to */
+ char **pvp;
+ register char **mvp;
+ register char *p;
+ register MAILER *m; /* mailer for this recipient */
+ ADDRESS *volatile ctladdr;
+#if HASSETUSERCONTEXT
+ ADDRESS *volatile contextaddr = NULL;
+#endif /* HASSETUSERCONTEXT */
+ register MCI *volatile mci;
+ register ADDRESS *SM_NONVOLATILE to = firstto;
+ volatile bool clever = false; /* running user smtp to this mailer */
+ ADDRESS *volatile tochain = NULL; /* users chain in this mailer call */
+ int rcode; /* response code */
+ SM_NONVOLATILE int lmtp_rcode = EX_OK;
+ SM_NONVOLATILE int nummxhosts = 0; /* number of MX hosts available */
+ SM_NONVOLATILE int hostnum = 0; /* current MX host index */
+ char *firstsig; /* signature of firstto */
+ volatile pid_t pid = -1;
+ char *volatile curhost;
+ SM_NONVOLATILE unsigned short port = 0;
+ SM_NONVOLATILE time_t enough = 0;
+#if NETUNIX
+ char *SM_NONVOLATILE mux_path = NULL; /* path to UNIX domain socket */
+#endif /* NETUNIX */
+ time_t xstart;
+ bool suidwarn;
+ bool anyok; /* at least one address was OK */
+ SM_NONVOLATILE bool goodmxfound = false; /* at least one MX was OK */
+ bool ovr;
+ bool quarantine;
+ int strsize;
+ int rcptcount;
+ int ret;
+ static int tobufsize = 0;
+ static char *tobuf = NULL;
+ char *rpath; /* translated return path */
+ int mpvect[2];
+ int rpvect[2];
+ char *mxhosts[MAXMXHOSTS + 1];
+ char *pv[MAXPV + 1];
+ char buf[MAXNAME + 1];
+ char cbuf[MAXPATHLEN];
+
+ errno = 0;
+ if (!QS_IS_OK(to->q_state))
+ return 0;
+
+ suidwarn = geteuid() == 0;
+
+ m = to->q_mailer;
+ host = to->q_host;
+ CurEnv = e; /* just in case */
+ e->e_statmsg = NULL;
+ SmtpError[0] = '\0';
+ xstart = curtime();
+
+ if (tTd(10, 1))
+ sm_dprintf("\n--deliver, id=%s, mailer=%s, host=`%s', first user=`%s'\n",
+ e->e_id, m->m_name, host, to->q_user);
+ if (tTd(10, 100))
+ printopenfds(false);
+
+ /*
+ ** Clear {client_*} macros if this is a bounce message to
+ ** prevent rejection by check_compat ruleset.
+ */
+
+ if (bitset(EF_RESPONSE, e->e_flags))
+ {
+ macdefine(&e->e_macro, A_PERM, macid("{client_name}"), "");
+ macdefine(&e->e_macro, A_PERM, macid("{client_ptr}"), "");
+ macdefine(&e->e_macro, A_PERM, macid("{client_addr}"), "");
+ macdefine(&e->e_macro, A_PERM, macid("{client_port}"), "");
+ macdefine(&e->e_macro, A_PERM, macid("{client_resolve}"), "");
+ }
+
+ SM_TRY
+ {
+ ADDRESS *skip_back = NULL;
+
+ /*
+ ** Do initial argv setup.
+ ** Insert the mailer name. Notice that $x expansion is
+ ** NOT done on the mailer name. Then, if the mailer has
+ ** a picky -f flag, we insert it as appropriate. This
+ ** code does not check for 'pv' overflow; this places a
+ ** manifest lower limit of 4 for MAXPV.
+ ** The from address rewrite is expected to make
+ ** the address relative to the other end.
+ */
+
+ /* rewrite from address, using rewriting rules */
+ rcode = EX_OK;
+ if (bitnset(M_UDBENVELOPE, e->e_from.q_mailer->m_flags))
+ p = e->e_sender;
+ else
+ p = e->e_from.q_paddr;
+ rpath = remotename(p, m, RF_SENDERADDR|RF_CANONICAL, &rcode, e);
+ if (strlen(rpath) > MAXSHORTSTR)
+ {
+ rpath = shortenstring(rpath, MAXSHORTSTR);
+
+ /* avoid bogus errno */
+ errno = 0;
+ syserr("remotename: huge return path %s", rpath);
+ }
+ rpath = sm_rpool_strdup_x(e->e_rpool, rpath);
+ macdefine(&e->e_macro, A_PERM, 'g', rpath);
+ macdefine(&e->e_macro, A_PERM, 'h', host);
+ Errors = 0;
+ pvp = pv;
+ *pvp++ = m->m_argv[0];
+
+ /* ignore long term host status information if mailer flag W is set */
+ if (bitnset(M_NOHOSTSTAT, m->m_flags))
+ IgnoreHostStatus = true;
+
+ /* insert -f or -r flag as appropriate */
+ if (FromFlag &&
+ (bitnset(M_FOPT, m->m_flags) ||
+ bitnset(M_ROPT, m->m_flags)))
+ {
+ if (bitnset(M_FOPT, m->m_flags))
+ *pvp++ = "-f";
+ else
+ *pvp++ = "-r";
+ *pvp++ = rpath;
+ }
+
+ /*
+ ** Append the other fixed parts of the argv. These run
+ ** up to the first entry containing "$u". There can only
+ ** be one of these, and there are only a few more slots
+ ** in the pv after it.
+ */
+
+ for (mvp = m->m_argv; (p = *++mvp) != NULL; )
+ {
+ /* can't use strchr here because of sign extension problems */
+ while (*p != '\0')
+ {
+ if ((*p++ & 0377) == MACROEXPAND)
+ {
+ if (*p == 'u')
+ break;
+ }
+ }
+
+ if (*p != '\0')
+ break;
+
+ /* this entry is safe -- go ahead and process it */
+ expand(*mvp, buf, sizeof buf, e);
+ *pvp++ = sm_rpool_strdup_x(e->e_rpool, buf);
+ if (pvp >= &pv[MAXPV - 3])
+ {
+ syserr("554 5.3.5 Too many parameters to %s before $u",
+ pv[0]);
+ rcode = -1;
+ goto cleanup;
+ }
+ }
+
+ /*
+ ** If we have no substitution for the user name in the argument
+ ** list, we know that we must supply the names otherwise -- and
+ ** SMTP is the answer!!
+ */
+
+ if (*mvp == NULL)
+ {
+ /* running LMTP or SMTP */
+ clever = true;
+ *pvp = NULL;
+ }
+ else if (bitnset(M_LMTP, m->m_flags))
+ {
+ /* not running LMTP */
+ sm_syslog(LOG_ERR, NULL,
+ "Warning: mailer %s: LMTP flag (F=z) turned off",
+ m->m_name);
+ clrbitn(M_LMTP, m->m_flags);
+ }
+
+ /*
+ ** At this point *mvp points to the argument with $u. We
+ ** run through our address list and append all the addresses
+ ** we can. If we run out of space, do not fret! We can
+ ** always send another copy later.
+ */
+
+ e->e_to = NULL;
+ strsize = 2;
+ rcptcount = 0;
+ ctladdr = NULL;
+ if (firstto->q_signature == NULL)
+ firstto->q_signature = hostsignature(firstto->q_mailer,
+ firstto->q_host);
+ firstsig = firstto->q_signature;
+
+ for (; to != NULL; to = to->q_next)
+ {
+ /* avoid sending multiple recipients to dumb mailers */
+ if (tochain != NULL && !bitnset(M_MUSER, m->m_flags))
+ break;
+
+ /* if already sent or not for this host, don't send */
+ if (!QS_IS_OK(to->q_state)) /* already sent; look at next */
+ continue;
+
+ /*
+ ** Must be same mailer to keep grouping rcpts.
+ ** If mailers don't match: continue; sendqueue is not
+ ** sorted by mailers, so don't break;
+ */
+
+ if (to->q_mailer != firstto->q_mailer)
+ continue;
+
+ if (to->q_signature == NULL) /* for safety */
+ to->q_signature = hostsignature(to->q_mailer,
+ to->q_host);
+
+ /*
+ ** This is for coincidental and tailcoat piggybacking messages
+ ** to the same mail host. While the signatures are identical
+ ** (that's the MX-RR's are identical) we can do coincidental
+ ** piggybacking. We try hard for coattail piggybacking
+ ** with the same mail host when the next recipient has the
+ ** same host at lowest preference. It may be that this
+ ** won't work out, so 'skip_back' is maintained if a backup
+ ** to coincidental piggybacking or full signature must happen.
+ */
+
+ ret = firstto == to ? HS_MATCH_FULL :
+ coloncmp(to->q_signature, firstsig);
+ if (ret == HS_MATCH_FULL)
+ skip_back = to;
+ else if (ret == HS_MATCH_NO)
+ break;
+
+ if (!clever)
+ {
+ /* avoid overflowing tobuf */
+ strsize += strlen(to->q_paddr) + 1;
+ if (strsize > TOBUFSIZE)
+ break;
+ }
+
+ if (++rcptcount > to->q_mailer->m_maxrcpt)
+ break;
+
+ if (tTd(10, 1))
+ {
+ sm_dprintf("\nsend to ");
+ printaddr(sm_debug_file(), to, false);
+ }
+
+ /* compute effective uid/gid when sending */
+ if (bitnset(M_RUNASRCPT, to->q_mailer->m_flags))
+# if HASSETUSERCONTEXT
+ contextaddr = ctladdr = getctladdr(to);
+# else /* HASSETUSERCONTEXT */
+ ctladdr = getctladdr(to);
+# endif /* HASSETUSERCONTEXT */
+
+ if (tTd(10, 2))
+ {
+ sm_dprintf("ctladdr=");
+ printaddr(sm_debug_file(), ctladdr, false);
+ }
+
+ user = to->q_user;
+ e->e_to = to->q_paddr;
+
+ /*
+ ** Check to see that these people are allowed to
+ ** talk to each other.
+ ** Check also for overflow of e_msgsize.
+ */
+
+ if (m->m_maxsize != 0 &&
+ (e->e_msgsize > m->m_maxsize || e->e_msgsize < 0))
+ {
+ e->e_flags |= EF_NO_BODY_RETN;
+ if (bitnset(M_LOCALMAILER, to->q_mailer->m_flags))
+ to->q_status = "5.2.3";
+ else
+ to->q_status = "5.3.4";
+
+ /* set to->q_rstatus = NULL; or to the following? */
+ usrerrenh(to->q_status,
+ "552 Message is too large; %ld bytes max",
+ m->m_maxsize);
+ markfailure(e, to, NULL, EX_UNAVAILABLE, false);
+ giveresponse(EX_UNAVAILABLE, to->q_status, m,
+ NULL, ctladdr, xstart, e, to);
+ continue;
+ }
+ SM_SET_H_ERRNO(0);
+ ovr = true;
+
+ /* do config file checking of compatibility */
+ quarantine = (e->e_quarmsg != NULL);
+ rcode = rscheck("check_compat", e->e_from.q_paddr, to->q_paddr,
+ e, RSF_RMCOMM|RSF_COUNT, 3, NULL,
+ e->e_id);
+ if (rcode == EX_OK)
+ {
+ /* do in-code checking if not discarding */
+ if (!bitset(EF_DISCARD, e->e_flags))
+ {
+ rcode = checkcompat(to, e);
+ ovr = false;
+ }
+ }
+ if (rcode != EX_OK)
+ {
+ markfailure(e, to, NULL, rcode, ovr);
+ giveresponse(rcode, to->q_status, m,
+ NULL, ctladdr, xstart, e, to);
+ continue;
+ }
+ if (!quarantine && e->e_quarmsg != NULL)
+ {
+ /*
+ ** check_compat or checkcompat() has tried
+ ** to quarantine but that isn't supported.
+ ** Revert the attempt.
+ */
+
+ e->e_quarmsg = NULL;
+ macdefine(&e->e_macro, A_PERM,
+ macid("{quarantine}"), "");
+ }
+ if (bitset(EF_DISCARD, e->e_flags))
+ {
+ if (tTd(10, 5))
+ {
+ sm_dprintf("deliver: discarding recipient ");
+ printaddr(sm_debug_file(), to, false);
+ }
+
+ /* pretend the message was sent */
+ /* XXX should we log something here? */
+ to->q_state = QS_DISCARDED;
+
+ /*
+ ** Remove discard bit to prevent discard of
+ ** future recipients. This is safe because the
+ ** true "global discard" has been handled before
+ ** we get here.
+ */
+
+ e->e_flags &= ~EF_DISCARD;
+ continue;
+ }
+
+ /*
+ ** Strip quote bits from names if the mailer is dumb
+ ** about them.
+ */
+
+ if (bitnset(M_STRIPQ, m->m_flags))
+ {
+ stripquotes(user);
+ stripquotes(host);
+ }
+
+ /*
+ ** Strip all leading backslashes if requested and the
+ ** next character is alphanumerical (the latter can
+ ** probably relaxed a bit, see RFC2821).
+ */
+
+ if (bitnset(M_STRIPBACKSL, m->m_flags) && user[0] == '\\')
+ stripbackslash(user);
+
+ /* hack attack -- delivermail compatibility */
+ if (m == ProgMailer && *user == '|')
+ user++;
+
+ /*
+ ** If an error message has already been given, don't
+ ** bother to send to this address.
+ **
+ ** >>>>>>>>>> This clause assumes that the local mailer
+ ** >> NOTE >> cannot do any further aliasing; that
+ ** >>>>>>>>>> function is subsumed by sendmail.
+ */
+
+ if (!QS_IS_OK(to->q_state))
+ continue;
+
+ /*
+ ** See if this user name is "special".
+ ** If the user name has a slash in it, assume that this
+ ** is a file -- send it off without further ado. Note
+ ** that this type of addresses is not processed along
+ ** with the others, so we fudge on the To person.
+ */
+
+ if (strcmp(m->m_mailer, "[FILE]") == 0)
+ {
+ macdefine(&e->e_macro, A_PERM, 'u', user);
+ p = to->q_home;
+ if (p == NULL && ctladdr != NULL)
+ p = ctladdr->q_home;
+ macdefine(&e->e_macro, A_PERM, 'z', p);
+ expand(m->m_argv[1], buf, sizeof buf, e);
+ if (strlen(buf) > 0)
+ rcode = mailfile(buf, m, ctladdr, SFF_CREAT, e);
+ else
+ {
+ syserr("empty filename specification for mailer %s",
+ m->m_name);
+ rcode = EX_CONFIG;
+ }
+ giveresponse(rcode, to->q_status, m, NULL,
+ ctladdr, xstart, e, to);
+ markfailure(e, to, NULL, rcode, true);
+ e->e_nsent++;
+ if (rcode == EX_OK)
+ {
+ to->q_state = QS_SENT;
+ if (bitnset(M_LOCALMAILER, m->m_flags) &&
+ bitset(QPINGONSUCCESS, to->q_flags))
+ {
+ to->q_flags |= QDELIVERED;
+ to->q_status = "2.1.5";
+ (void) sm_io_fprintf(e->e_xfp,
+ SM_TIME_DEFAULT,
+ "%s... Successfully delivered\n",
+ to->q_paddr);
+ }
+ }
+ to->q_statdate = curtime();
+ markstats(e, to, STATS_NORMAL);
+ continue;
+ }
+
+ /*
+ ** Address is verified -- add this user to mailer
+ ** argv, and add it to the print list of recipients.
+ */
+
+ /* link together the chain of recipients */
+ to->q_tchain = tochain;
+ tochain = to;
+ e->e_to = "[CHAIN]";
+
+ macdefine(&e->e_macro, A_PERM, 'u', user); /* to user */
+ p = to->q_home;
+ if (p == NULL && ctladdr != NULL)
+ p = ctladdr->q_home;
+ macdefine(&e->e_macro, A_PERM, 'z', p); /* user's home */
+
+ /* set the ${dsn_notify} macro if applicable */
+ if (bitset(QHASNOTIFY, to->q_flags))
+ {
+ char notify[MAXLINE];
+
+ notify[0] = '\0';
+ if (bitset(QPINGONSUCCESS, to->q_flags))
+ (void) sm_strlcat(notify, "SUCCESS,",
+ sizeof notify);
+ if (bitset(QPINGONFAILURE, to->q_flags))
+ (void) sm_strlcat(notify, "FAILURE,",
+ sizeof notify);
+ if (bitset(QPINGONDELAY, to->q_flags))
+ (void) sm_strlcat(notify, "DELAY,",
+ sizeof notify);
+
+ /* Set to NEVER or drop trailing comma */
+ if (notify[0] == '\0')
+ (void) sm_strlcat(notify, "NEVER",
+ sizeof notify);
+ else
+ notify[strlen(notify) - 1] = '\0';
+
+ macdefine(&e->e_macro, A_TEMP,
+ macid("{dsn_notify}"), notify);
+ }
+ else
+ macdefine(&e->e_macro, A_PERM,
+ macid("{dsn_notify}"), NULL);
+
+ /*
+ ** Expand out this user into argument list.
+ */
+
+ if (!clever)
+ {
+ expand(*mvp, buf, sizeof buf, e);
+ *pvp++ = sm_rpool_strdup_x(e->e_rpool, buf);
+ if (pvp >= &pv[MAXPV - 2])
+ {
+ /* allow some space for trailing parms */
+ break;
+ }
+ }
+ }
+
+ /* see if any addresses still exist */
+ if (tochain == NULL)
+ {
+ rcode = 0;
+ goto cleanup;
+ }
+
+ /* print out messages as full list */
+ strsize = 1;
+ for (to = tochain; to != NULL; to = to->q_tchain)
+ strsize += strlen(to->q_paddr) + 1;
+ if (strsize < TOBUFSIZE)
+ strsize = TOBUFSIZE;
+ if (strsize > tobufsize)
+ {
+ SM_FREE_CLR(tobuf);
+ tobuf = sm_pmalloc_x(strsize);
+ tobufsize = strsize;
+ }
+ p = tobuf;
+ *p = '\0';
+ for (to = tochain; to != NULL; to = to->q_tchain)
+ {
+ (void) sm_strlcpyn(p, tobufsize - (p - tobuf), 2,
+ ",", to->q_paddr);
+ p += strlen(p);
+ }
+ e->e_to = tobuf + 1;
+
+ /*
+ ** Fill out any parameters after the $u parameter.
+ */
+
+ if (!clever)
+ {
+ while (*++mvp != NULL)
+ {
+ expand(*mvp, buf, sizeof buf, e);
+ *pvp++ = sm_rpool_strdup_x(e->e_rpool, buf);
+ if (pvp >= &pv[MAXPV])
+ syserr("554 5.3.0 deliver: pv overflow after $u for %s",
+ pv[0]);
+ }
+ }
+ *pvp++ = NULL;
+
+ /*
+ ** Call the mailer.
+ ** The argument vector gets built, pipes
+ ** are created as necessary, and we fork & exec as
+ ** appropriate.
+ ** If we are running SMTP, we just need to clean up.
+ */
+
+ /* XXX this seems a bit wierd */
+ if (ctladdr == NULL && m != ProgMailer && m != FileMailer &&
+ bitset(QGOODUID, e->e_from.q_flags))
+ ctladdr = &e->e_from;
+
+#if NAMED_BIND
+ if (ConfigLevel < 2)
+ _res.options &= ~(RES_DEFNAMES | RES_DNSRCH); /* XXX */
+#endif /* NAMED_BIND */
+
+ if (tTd(11, 1))
+ {
+ sm_dprintf("openmailer:");
+ printav(sm_debug_file(), pv);
+ }
+ errno = 0;
+ SM_SET_H_ERRNO(0);
+ CurHostName = NULL;
+
+ /*
+ ** Deal with the special case of mail handled through an IPC
+ ** connection.
+ ** In this case we don't actually fork. We must be
+ ** running SMTP for this to work. We will return a
+ ** zero pid to indicate that we are running IPC.
+ ** We also handle a debug version that just talks to stdin/out.
+ */
+
+ curhost = NULL;
+ SmtpPhase = NULL;
+ mci = NULL;
+
+#if XDEBUG
+ {
+ char wbuf[MAXLINE];
+
+ /* make absolutely certain 0, 1, and 2 are in use */
+ (void) sm_snprintf(wbuf, sizeof wbuf, "%s... openmailer(%s)",
+ shortenstring(e->e_to, MAXSHORTSTR),
+ m->m_name);
+ checkfd012(wbuf);
+ }
+#endif /* XDEBUG */
+
+ /* check for 8-bit available */
+ if (bitset(EF_HAS8BIT, e->e_flags) &&
+ bitnset(M_7BITS, m->m_flags) &&
+ (bitset(EF_DONT_MIME, e->e_flags) ||
+ !(bitset(MM_MIME8BIT, MimeMode) ||
+ (bitset(EF_IS_MIME, e->e_flags) &&
+ bitset(MM_CVTMIME, MimeMode)))))
+ {
+ e->e_status = "5.6.3";
+ usrerrenh(e->e_status,
+ "554 Cannot send 8-bit data to 7-bit destination");
+ rcode = EX_DATAERR;
+ goto give_up;
+ }
+
+ if (tTd(62, 8))
+ checkfds("before delivery");
+
+ /* check for Local Person Communication -- not for mortals!!! */
+ if (strcmp(m->m_mailer, "[LPC]") == 0)
+ {
+ if (clever)
+ {
+ /* flush any expired connections */
+ (void) mci_scan(NULL);
+
+ /* try to get a cached connection or just a slot */
+ mci = mci_get(m->m_name, m);
+ if (mci->mci_host == NULL)
+ mci->mci_host = m->m_name;
+ CurHostName = mci->mci_host;
+ if (mci->mci_state != MCIS_CLOSED)
+ {
+ message("Using cached SMTP/LPC connection for %s...",
+ m->m_name);
+ mci->mci_deliveries++;
+ goto do_transfer;
+ }
+ }
+ else
+ {
+ mci = mci_new(e->e_rpool);
+ }
+ mci->mci_in = smioin;
+ mci->mci_out = smioout;
+ mci->mci_mailer = m;
+ mci->mci_host = m->m_name;
+ if (clever)
+ {
+ mci->mci_state = MCIS_OPENING;
+ mci_cache(mci);
+ }
+ else
+ mci->mci_state = MCIS_OPEN;
+ }
+ else if (strcmp(m->m_mailer, "[IPC]") == 0)
+ {
+ register int i;
+
+ if (pv[0] == NULL || pv[1] == NULL || pv[1][0] == '\0')
+ {
+ syserr("null destination for %s mailer", m->m_mailer);
+ rcode = EX_CONFIG;
+ goto give_up;
+ }
+
+# if NETUNIX
+ if (strcmp(pv[0], "FILE") == 0)
+ {
+ curhost = CurHostName = "localhost";
+ mux_path = pv[1];
+ }
+ else
+# endif /* NETUNIX */
+ {
+ CurHostName = pv[1];
+ curhost = hostsignature(m, pv[1]);
+ }
+
+ if (curhost == NULL || curhost[0] == '\0')
+ {
+ syserr("null host signature for %s", pv[1]);
+ rcode = EX_CONFIG;
+ goto give_up;
+ }
+
+ if (!clever)
+ {
+ syserr("554 5.3.5 non-clever IPC");
+ rcode = EX_CONFIG;
+ goto give_up;
+ }
+ if (pv[2] != NULL
+# if NETUNIX
+ && mux_path == NULL
+# endif /* NETUNIX */
+ )
+ {
+ port = htons((unsigned short) atoi(pv[2]));
+ if (port == 0)
+ {
+# ifdef NO_GETSERVBYNAME
+ syserr("Invalid port number: %s", pv[2]);
+# else /* NO_GETSERVBYNAME */
+ struct servent *sp = getservbyname(pv[2], "tcp");
+
+ if (sp == NULL)
+ syserr("Service %s unknown", pv[2]);
+ else
+ port = sp->s_port;
+# endif /* NO_GETSERVBYNAME */
+ }
+ }
+
+ nummxhosts = parse_hostsignature(curhost, mxhosts, m);
+ if (TimeOuts.to_aconnect > 0)
+ enough = curtime() + TimeOuts.to_aconnect;
+tryhost:
+ while (hostnum < nummxhosts)
+ {
+ char sep = ':';
+ char *endp;
+ static char hostbuf[MAXNAME + 1];
+ bool tried_fallbacksmarthost = false;
+
+# if NETINET6
+ if (*mxhosts[hostnum] == '[')
+ {
+ endp = strchr(mxhosts[hostnum] + 1, ']');
+ if (endp != NULL)
+ endp = strpbrk(endp + 1, ":,");
+ }
+ else
+ endp = strpbrk(mxhosts[hostnum], ":,");
+# else /* NETINET6 */
+ endp = strpbrk(mxhosts[hostnum], ":,");
+# endif /* NETINET6 */
+ if (endp != NULL)
+ {
+ sep = *endp;
+ *endp = '\0';
+ }
+
+ if (hostnum == 1 && skip_back != NULL)
+ {
+ /*
+ ** Coattail piggybacking is no longer an
+ ** option with the mail host next to be tried
+ ** no longer the lowest MX preference
+ ** (hostnum == 1 meaning we're on the second
+ ** preference). We do not try to coattail
+ ** piggyback more than the first MX preference.
+ ** Revert 'tochain' to last location for
+ ** coincidental piggybacking. This works this
+ ** easily because the q_tchain kept getting
+ ** added to the top of the linked list.
+ */
+
+ tochain = skip_back;
+ }
+
+ if (*mxhosts[hostnum] == '\0')
+ {
+ syserr("deliver: null host name in signature");
+ hostnum++;
+ if (endp != NULL)
+ *endp = sep;
+ continue;
+ }
+ (void) sm_strlcpy(hostbuf, mxhosts[hostnum],
+ sizeof hostbuf);
+ hostnum++;
+ if (endp != NULL)
+ *endp = sep;
+
+ one_last_try:
+ /* see if we already know that this host is fried */
+ CurHostName = hostbuf;
+ mci = mci_get(hostbuf, m);
+ if (mci->mci_state != MCIS_CLOSED)
+ {
+ char *type;
+
+ if (tTd(11, 1))
+ {
+ sm_dprintf("openmailer: ");
+ mci_dump(sm_debug_file(), mci, false);
+ }
+ CurHostName = mci->mci_host;
+ if (bitnset(M_LMTP, m->m_flags))
+ type = "L";
+ else if (bitset(MCIF_ESMTP, mci->mci_flags))
+ type = "ES";
+ else
+ type = "S";
+ message("Using cached %sMTP connection to %s via %s...",
+ type, hostbuf, m->m_name);
+ mci->mci_deliveries++;
+ break;
+ }
+ mci->mci_mailer = m;
+ if (mci->mci_exitstat != EX_OK)
+ {
+ if (mci->mci_exitstat == EX_TEMPFAIL)
+ goodmxfound = true;
+
+ /* Try FallbackSmartHost? */
+ if (should_try_fbsh(e, &tried_fallbacksmarthost,
+ hostbuf, sizeof hostbuf,
+ mci->mci_exitstat))
+ goto one_last_try;
+
+ continue;
+ }
+
+ if (mci_lock_host(mci) != EX_OK)
+ {
+ mci_setstat(mci, EX_TEMPFAIL, "4.4.5", NULL);
+ goodmxfound = true;
+ continue;
+ }
+
+ /* try the connection */
+ sm_setproctitle(true, e, "%s %s: %s",
+ qid_printname(e),
+ hostbuf, "user open");
+# if NETUNIX
+ if (mux_path != NULL)
+ {
+ message("Connecting to %s via %s...",
+ mux_path, m->m_name);
+ i = makeconnection_ds((char *) mux_path, mci);
+ }
+ else
+# endif /* NETUNIX */
+ {
+ if (port == 0)
+ message("Connecting to %s via %s...",
+ hostbuf, m->m_name);
+ else
+ message("Connecting to %s port %d via %s...",
+ hostbuf, ntohs(port),
+ m->m_name);
+ i = makeconnection(hostbuf, port, mci, e,
+ enough);
+ }
+ mci->mci_errno = errno;
+ mci->mci_lastuse = curtime();
+ mci->mci_deliveries = 0;
+ mci->mci_exitstat = i;
+# if NAMED_BIND
+ mci->mci_herrno = h_errno;
+# endif /* NAMED_BIND */
+
+ /*
+ ** Have we tried long enough to get a connection?
+ ** If yes, skip to the fallback MX hosts
+ ** (if existent).
+ */
+
+ if (enough > 0 && mci->mci_lastuse >= enough)
+ {
+ int h;
+# if NAMED_BIND
+ extern int NumFallbackMXHosts;
+# else /* NAMED_BIND */
+ const int NumFallbackMXHosts = 0;
+# endif /* NAMED_BIND */
+
+ if (hostnum < nummxhosts && LogLevel > 9)
+ sm_syslog(LOG_INFO, e->e_id,
+ "Timeout.to_aconnect occurred before exhausting all addresses");
+
+ /* turn off timeout if fallback available */
+ if (NumFallbackMXHosts > 0)
+ enough = 0;
+
+ /* skip to a fallback MX host */
+ h = nummxhosts - NumFallbackMXHosts;
+ if (hostnum < h)
+ hostnum = h;
+ }
+ if (i == EX_OK)
+ {
+ goodmxfound = true;
+ markstats(e, firstto, STATS_CONNECT);
+ mci->mci_state = MCIS_OPENING;
+ mci_cache(mci);
+ if (TrafficLogFile != NULL)
+ (void) sm_io_fprintf(TrafficLogFile,
+ SM_TIME_DEFAULT,
+ "%05d === CONNECT %s\n",
+ (int) CurrentPid,
+ hostbuf);
+ break;
+ }
+ else
+ {
+ /* Try FallbackSmartHost? */
+ if (should_try_fbsh(e, &tried_fallbacksmarthost,
+ hostbuf, sizeof hostbuf, i))
+ goto one_last_try;
+
+ if (tTd(11, 1))
+ sm_dprintf("openmailer: makeconnection => stat=%d, errno=%d\n",
+ i, errno);
+ if (i == EX_TEMPFAIL)
+ goodmxfound = true;
+ mci_unlock_host(mci);
+ }
+
+ /* enter status of this host */
+ setstat(i);
+
+ /* should print some message here for -v mode */
+ }
+ if (mci == NULL)
+ {
+ syserr("deliver: no host name");
+ rcode = EX_SOFTWARE;
+ goto give_up;
+ }
+ mci->mci_pid = 0;
+ }
+ else
+ {
+ /* flush any expired connections */
+ (void) mci_scan(NULL);
+ mci = NULL;
+
+ if (bitnset(M_LMTP, m->m_flags))
+ {
+ /* try to get a cached connection */
+ mci = mci_get(m->m_name, m);
+ if (mci->mci_host == NULL)
+ mci->mci_host = m->m_name;
+ CurHostName = mci->mci_host;
+ if (mci->mci_state != MCIS_CLOSED)
+ {
+ message("Using cached LMTP connection for %s...",
+ m->m_name);
+ mci->mci_deliveries++;
+ goto do_transfer;
+ }
+ }
+
+ /* announce the connection to verbose listeners */
+ if (host == NULL || host[0] == '\0')
+ message("Connecting to %s...", m->m_name);
+ else
+ message("Connecting to %s via %s...", host, m->m_name);
+ if (TrafficLogFile != NULL)
+ {
+ char **av;
+
+ (void) sm_io_fprintf(TrafficLogFile, SM_TIME_DEFAULT,
+ "%05d === EXEC", (int) CurrentPid);
+ for (av = pv; *av != NULL; av++)
+ (void) sm_io_fprintf(TrafficLogFile,
+ SM_TIME_DEFAULT, " %s",
+ *av);
+ (void) sm_io_fprintf(TrafficLogFile, SM_TIME_DEFAULT,
+ "\n");
+ }
+
+#if XDEBUG
+ checkfd012("before creating mail pipe");
+#endif /* XDEBUG */
+
+ /* create a pipe to shove the mail through */
+ if (pipe(mpvect) < 0)
+ {
+ syserr("%s... openmailer(%s): pipe (to mailer)",
+ shortenstring(e->e_to, MAXSHORTSTR), m->m_name);
+ if (tTd(11, 1))
+ sm_dprintf("openmailer: NULL\n");
+ rcode = EX_OSERR;
+ goto give_up;
+ }
+
+#if XDEBUG
+ /* make sure we didn't get one of the standard I/O files */
+ if (mpvect[0] < 3 || mpvect[1] < 3)
+ {
+ syserr("%s... openmailer(%s): bogus mpvect %d %d",
+ shortenstring(e->e_to, MAXSHORTSTR), m->m_name,
+ mpvect[0], mpvect[1]);
+ printopenfds(true);
+ if (tTd(11, 1))
+ sm_dprintf("openmailer: NULL\n");
+ rcode = EX_OSERR;
+ goto give_up;
+ }
+
+ /* make sure system call isn't dead meat */
+ checkfdopen(mpvect[0], "mpvect[0]");
+ checkfdopen(mpvect[1], "mpvect[1]");
+ if (mpvect[0] == mpvect[1] ||
+ (e->e_lockfp != NULL &&
+ (mpvect[0] == sm_io_getinfo(e->e_lockfp, SM_IO_WHAT_FD,
+ NULL) ||
+ mpvect[1] == sm_io_getinfo(e->e_lockfp, SM_IO_WHAT_FD,
+ NULL))))
+ {
+ if (e->e_lockfp == NULL)
+ syserr("%s... openmailer(%s): overlapping mpvect %d %d",
+ shortenstring(e->e_to, MAXSHORTSTR),
+ m->m_name, mpvect[0], mpvect[1]);
+ else
+ syserr("%s... openmailer(%s): overlapping mpvect %d %d, lockfp = %d",
+ shortenstring(e->e_to, MAXSHORTSTR),
+ m->m_name, mpvect[0], mpvect[1],
+ sm_io_getinfo(e->e_lockfp,
+ SM_IO_WHAT_FD, NULL));
+ }
+#endif /* XDEBUG */
+
+ /* create a return pipe */
+ if (pipe(rpvect) < 0)
+ {
+ syserr("%s... openmailer(%s): pipe (from mailer)",
+ shortenstring(e->e_to, MAXSHORTSTR),
+ m->m_name);
+ (void) close(mpvect[0]);
+ (void) close(mpvect[1]);
+ if (tTd(11, 1))
+ sm_dprintf("openmailer: NULL\n");
+ rcode = EX_OSERR;
+ goto give_up;
+ }
+#if XDEBUG
+ checkfdopen(rpvect[0], "rpvect[0]");
+ checkfdopen(rpvect[1], "rpvect[1]");
+#endif /* XDEBUG */
+
+ /*
+ ** Actually fork the mailer process.
+ ** DOFORK is clever about retrying.
+ **
+ ** Dispose of SIGCHLD signal catchers that may be laying
+ ** around so that endmailer will get it.
+ */
+
+ if (e->e_xfp != NULL) /* for debugging */
+ (void) sm_io_flush(e->e_xfp, SM_TIME_DEFAULT);
+ (void) sm_io_flush(smioout, SM_TIME_DEFAULT);
+ (void) sm_signal(SIGCHLD, SIG_DFL);
+
+
+ DOFORK(FORK);
+ /* pid is set by DOFORK */
+
+ if (pid < 0)
+ {
+ /* failure */
+ syserr("%s... openmailer(%s): cannot fork",
+ shortenstring(e->e_to, MAXSHORTSTR), m->m_name);
+ (void) close(mpvect[0]);
+ (void) close(mpvect[1]);
+ (void) close(rpvect[0]);
+ (void) close(rpvect[1]);
+ if (tTd(11, 1))
+ sm_dprintf("openmailer: NULL\n");
+ rcode = EX_OSERR;
+ goto give_up;
+ }
+ else if (pid == 0)
+ {
+ int save_errno;
+ int sff;
+ int new_euid = NO_UID;
+ int new_ruid = NO_UID;
+ int new_gid = NO_GID;
+ char *user = NULL;
+ struct stat stb;
+ extern int DtableSize;
+
+ CurrentPid = getpid();
+
+ /* clear the events to turn off SIGALRMs */
+ sm_clear_events();
+
+ /* Reset global flags */
+ RestartRequest = NULL;
+ RestartWorkGroup = false;
+ ShutdownRequest = NULL;
+ PendingSignal = 0;
+
+ if (e->e_lockfp != NULL)
+ (void) close(sm_io_getinfo(e->e_lockfp,
+ SM_IO_WHAT_FD,
+ NULL));
+
+ /* child -- set up input & exec mailer */
+ (void) sm_signal(SIGALRM, sm_signal_noop);
+ (void) sm_signal(SIGCHLD, SIG_DFL);
+ (void) sm_signal(SIGHUP, SIG_IGN);
+ (void) sm_signal(SIGINT, SIG_IGN);
+ (void) sm_signal(SIGTERM, SIG_DFL);
+# ifdef SIGUSR1
+ (void) sm_signal(SIGUSR1, sm_signal_noop);
+# endif /* SIGUSR1 */
+
+ if (m != FileMailer || stat(tochain->q_user, &stb) < 0)
+ stb.st_mode = 0;
+
+# if HASSETUSERCONTEXT
+ /*
+ ** Set user resources.
+ */
+
+ if (contextaddr != NULL)
+ {
+ int sucflags;
+ struct passwd *pwd;
+
+ if (contextaddr->q_ruser != NULL)
+ pwd = sm_getpwnam(contextaddr->q_ruser);
+ else
+ pwd = sm_getpwnam(contextaddr->q_user);
+ sucflags = LOGIN_SETRESOURCES|LOGIN_SETPRIORITY;
+#ifdef LOGIN_SETMAC
+ sucflags |= LOGIN_SETMAC;
+#endif /* LOGIN_SETMAC */
+ if (pwd != NULL &&
+ setusercontext(NULL, pwd, pwd->pw_uid,
+ sucflags) == -1 &&
+ suidwarn)
+ {
+ syserr("openmailer: setusercontext() failed");
+ exit(EX_TEMPFAIL);
+ }
+ }
+# endif /* HASSETUSERCONTEXT */
+
+#if HASNICE
+ /* tweak niceness */
+ if (m->m_nice != 0)
+ (void) nice(m->m_nice);
+#endif /* HASNICE */
+
+ /* reset group id */
+ if (bitnset(M_SPECIFIC_UID, m->m_flags))
+ {
+ if (m->m_gid == NO_GID)
+ new_gid = RunAsGid;
+ else
+ new_gid = m->m_gid;
+ }
+ else if (bitset(S_ISGID, stb.st_mode))
+ new_gid = stb.st_gid;
+ else if (ctladdr != NULL && ctladdr->q_gid != 0)
+ {
+ if (!DontInitGroups)
+ {
+ user = ctladdr->q_ruser;
+ if (user == NULL)
+ user = ctladdr->q_user;
+
+ if (initgroups(user,
+ ctladdr->q_gid) == -1
+ && suidwarn)
+ {
+ syserr("openmailer: initgroups(%s, %d) failed",
+ user, ctladdr->q_gid);
+ exit(EX_TEMPFAIL);
+ }
+ }
+ else
+ {
+ GIDSET_T gidset[1];
+
+ gidset[0] = ctladdr->q_gid;
+ if (setgroups(1, gidset) == -1
+ && suidwarn)
+ {
+ syserr("openmailer: setgroups() failed");
+ exit(EX_TEMPFAIL);
+ }
+ }
+ new_gid = ctladdr->q_gid;
+ }
+ else
+ {
+ if (!DontInitGroups)
+ {
+ user = DefUser;
+ if (initgroups(DefUser, DefGid) == -1 &&
+ suidwarn)
+ {
+ syserr("openmailer: initgroups(%s, %d) failed",
+ DefUser, DefGid);
+ exit(EX_TEMPFAIL);
+ }
+ }
+ else
+ {
+ GIDSET_T gidset[1];
+
+ gidset[0] = DefGid;
+ if (setgroups(1, gidset) == -1
+ && suidwarn)
+ {
+ syserr("openmailer: setgroups() failed");
+ exit(EX_TEMPFAIL);
+ }
+ }
+ if (m->m_gid == NO_GID)
+ new_gid = DefGid;
+ else
+ new_gid = m->m_gid;
+ }
+ if (new_gid != NO_GID)
+ {
+ if (RunAsUid != 0 &&
+ bitnset(M_SPECIFIC_UID, m->m_flags) &&
+ new_gid != getgid() &&
+ new_gid != getegid())
+ {
+ /* Only root can change the gid */
+ syserr("openmailer: insufficient privileges to change gid, RunAsUid=%d, new_gid=%d, gid=%d, egid=%d",
+ (int) RunAsUid, (int) new_gid,
+ (int) getgid(), (int) getegid());
+ exit(EX_TEMPFAIL);
+ }
+
+ if (setgid(new_gid) < 0 && suidwarn)
+ {
+ syserr("openmailer: setgid(%ld) failed",
+ (long) new_gid);
+ exit(EX_TEMPFAIL);
+ }
+ }
+
+ /* change root to some "safe" directory */
+ if (m->m_rootdir != NULL)
+ {
+ expand(m->m_rootdir, cbuf, sizeof cbuf, e);
+ if (tTd(11, 20))
+ sm_dprintf("openmailer: chroot %s\n",
+ cbuf);
+ if (chroot(cbuf) < 0)
+ {
+ syserr("openmailer: Cannot chroot(%s)",
+ cbuf);
+ exit(EX_TEMPFAIL);
+ }
+ if (chdir("/") < 0)
+ {
+ syserr("openmailer: cannot chdir(/)");
+ exit(EX_TEMPFAIL);
+ }
+ }
+
+ /* reset user id */
+ endpwent();
+ sm_mbdb_terminate();
+ if (bitnset(M_SPECIFIC_UID, m->m_flags))
+ {
+ if (m->m_uid == NO_UID)
+ new_euid = RunAsUid;
+ else
+ new_euid = m->m_uid;
+
+ /*
+ ** Undo the effects of the uid change in main
+ ** for signal handling. The real uid may
+ ** be used by mailer in adding a "From "
+ ** line.
+ */
+
+ if (RealUid != 0 && RealUid != getuid())
+ {
+# if MAILER_SETUID_METHOD == USE_SETEUID
+# if HASSETREUID
+ if (setreuid(RealUid, geteuid()) < 0)
+ {
+ syserr("openmailer: setreuid(%d, %d) failed",
+ (int) RealUid, (int) geteuid());
+ exit(EX_OSERR);
+ }
+# endif /* HASSETREUID */
+# endif /* MAILER_SETUID_METHOD == USE_SETEUID */
+# if MAILER_SETUID_METHOD == USE_SETREUID
+ new_ruid = RealUid;
+# endif /* MAILER_SETUID_METHOD == USE_SETREUID */
+ }
+ }
+ else if (bitset(S_ISUID, stb.st_mode))
+ new_ruid = stb.st_uid;
+ else if (ctladdr != NULL && ctladdr->q_uid != 0)
+ new_ruid = ctladdr->q_uid;
+ else if (m->m_uid != NO_UID)
+ new_ruid = m->m_uid;
+ else
+ new_ruid = DefUid;
+
+# if _FFR_USE_SETLOGIN
+ /* run disconnected from terminal and set login name */
+ if (setsid() >= 0 &&
+ ctladdr != NULL && ctladdr->q_uid != 0 &&
+ new_euid == ctladdr->q_uid)
+ {
+ struct passwd *pwd;
+
+ pwd = sm_getpwuid(ctladdr->q_uid);
+ if (pwd != NULL && suidwarn)
+ (void) setlogin(pwd->pw_name);
+ endpwent();
+ }
+# endif /* _FFR_USE_SETLOGIN */
+
+ if (new_euid != NO_UID)
+ {
+ if (RunAsUid != 0 && new_euid != RunAsUid)
+ {
+ /* Only root can change the uid */
+ syserr("openmailer: insufficient privileges to change uid, new_euid=%d, RunAsUid=%d",
+ (int) new_euid, (int) RunAsUid);
+ exit(EX_TEMPFAIL);
+ }
+
+ vendor_set_uid(new_euid);
+# if MAILER_SETUID_METHOD == USE_SETEUID
+ if (seteuid(new_euid) < 0 && suidwarn)
+ {
+ syserr("openmailer: seteuid(%ld) failed",
+ (long) new_euid);
+ exit(EX_TEMPFAIL);
+ }
+# endif /* MAILER_SETUID_METHOD == USE_SETEUID */
+# if MAILER_SETUID_METHOD == USE_SETREUID
+ if (setreuid(new_ruid, new_euid) < 0 && suidwarn)
+ {
+ syserr("openmailer: setreuid(%ld, %ld) failed",
+ (long) new_ruid, (long) new_euid);
+ exit(EX_TEMPFAIL);
+ }
+# endif /* MAILER_SETUID_METHOD == USE_SETREUID */
+# if MAILER_SETUID_METHOD == USE_SETUID
+ if (new_euid != geteuid() && setuid(new_euid) < 0 && suidwarn)
+ {
+ syserr("openmailer: setuid(%ld) failed",
+ (long) new_euid);
+ exit(EX_TEMPFAIL);
+ }
+# endif /* MAILER_SETUID_METHOD == USE_SETUID */
+ }
+ else if (new_ruid != NO_UID)
+ {
+ vendor_set_uid(new_ruid);
+ if (setuid(new_ruid) < 0 && suidwarn)
+ {
+ syserr("openmailer: setuid(%ld) failed",
+ (long) new_ruid);
+ exit(EX_TEMPFAIL);
+ }
+ }
+
+ if (tTd(11, 2))
+ sm_dprintf("openmailer: running as r/euid=%d/%d, r/egid=%d/%d\n",
+ (int) getuid(), (int) geteuid(),
+ (int) getgid(), (int) getegid());
+
+ /* move into some "safe" directory */
+ if (m->m_execdir != NULL)
+ {
+ char *q;
+
+ for (p = m->m_execdir; p != NULL; p = q)
+ {
+ q = strchr(p, ':');
+ if (q != NULL)
+ *q = '\0';
+ expand(p, cbuf, sizeof cbuf, e);
+ if (q != NULL)
+ *q++ = ':';
+ if (tTd(11, 20))
+ sm_dprintf("openmailer: trydir %s\n",
+ cbuf);
+ if (cbuf[0] != '\0' &&
+ chdir(cbuf) >= 0)
+ break;
+ }
+ }
+
+ /* Check safety of program to be run */
+ sff = SFF_ROOTOK|SFF_EXECOK;
+ if (!bitnset(DBS_RUNWRITABLEPROGRAM,
+ DontBlameSendmail))
+ sff |= SFF_NOGWFILES|SFF_NOWWFILES;
+ if (bitnset(DBS_RUNPROGRAMINUNSAFEDIRPATH,
+ DontBlameSendmail))
+ sff |= SFF_NOPATHCHECK;
+ else
+ sff |= SFF_SAFEDIRPATH;
+ ret = safefile(m->m_mailer, getuid(), getgid(),
+ user, sff, 0, NULL);
+ if (ret != 0)
+ sm_syslog(LOG_INFO, e->e_id,
+ "Warning: program %s unsafe: %s",
+ m->m_mailer, sm_errstring(ret));
+
+ /* arrange to filter std & diag output of command */
+ (void) close(rpvect[0]);
+ if (dup2(rpvect[1], STDOUT_FILENO) < 0)
+ {
+ syserr("%s... openmailer(%s): cannot dup pipe %d for stdout",
+ shortenstring(e->e_to, MAXSHORTSTR),
+ m->m_name, rpvect[1]);
+ _exit(EX_OSERR);
+ }
+ (void) close(rpvect[1]);
+
+ if (dup2(STDOUT_FILENO, STDERR_FILENO) < 0)
+ {
+ syserr("%s... openmailer(%s): cannot dup stdout for stderr",
+ shortenstring(e->e_to, MAXSHORTSTR),
+ m->m_name);
+ _exit(EX_OSERR);
+ }
+
+ /* arrange to get standard input */
+ (void) close(mpvect[1]);
+ if (dup2(mpvect[0], STDIN_FILENO) < 0)
+ {
+ syserr("%s... openmailer(%s): cannot dup pipe %d for stdin",
+ shortenstring(e->e_to, MAXSHORTSTR),
+ m->m_name, mpvect[0]);
+ _exit(EX_OSERR);
+ }
+ (void) close(mpvect[0]);
+
+ /* arrange for all the files to be closed */
+ sm_close_on_exec(STDERR_FILENO + 1, DtableSize);
+
+# if !_FFR_USE_SETLOGIN
+ /* run disconnected from terminal */
+ (void) setsid();
+# endif /* !_FFR_USE_SETLOGIN */
+
+ /* try to execute the mailer */
+ (void) execve(m->m_mailer, (ARGV_T) pv,
+ (ARGV_T) UserEnviron);
+ save_errno = errno;
+ syserr("Cannot exec %s", m->m_mailer);
+ if (bitnset(M_LOCALMAILER, m->m_flags) ||
+ transienterror(save_errno))
+ _exit(EX_OSERR);
+ _exit(EX_UNAVAILABLE);
+ }
+
+ /*
+ ** Set up return value.
+ */
+
+ if (mci == NULL)
+ {
+ if (clever)
+ {
+ /*
+ ** Allocate from general heap, not
+ ** envelope rpool, because this mci
+ ** is going to be cached.
+ */
+
+ mci = mci_new(NULL);
+ }
+ else
+ {
+ /*
+ ** Prevent a storage leak by allocating
+ ** this from the envelope rpool.
+ */
+
+ mci = mci_new(e->e_rpool);
+ }
+ }
+ mci->mci_mailer = m;
+ if (clever)
+ {
+ mci->mci_state = MCIS_OPENING;
+ mci_cache(mci);
+ }
+ else
+ {
+ mci->mci_state = MCIS_OPEN;
+ }
+ mci->mci_pid = pid;
+ (void) close(mpvect[0]);
+ mci->mci_out = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT,
+ (void *) &(mpvect[1]), SM_IO_WRONLY_B,
+ NULL);
+ if (mci->mci_out == NULL)
+ {
+ syserr("deliver: cannot create mailer output channel, fd=%d",
+ mpvect[1]);
+ (void) close(mpvect[1]);
+ (void) close(rpvect[0]);
+ (void) close(rpvect[1]);
+ rcode = EX_OSERR;
+ goto give_up;
+ }
+
+ (void) close(rpvect[1]);
+ mci->mci_in = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT,
+ (void *) &(rpvect[0]), SM_IO_RDONLY_B,
+ NULL);
+ if (mci->mci_in == NULL)
+ {
+ syserr("deliver: cannot create mailer input channel, fd=%d",
+ mpvect[1]);
+ (void) close(rpvect[0]);
+ (void) sm_io_close(mci->mci_out, SM_TIME_DEFAULT);
+ mci->mci_out = NULL;
+ rcode = EX_OSERR;
+ goto give_up;
+ }
+ }
+
+ /*
+ ** If we are in SMTP opening state, send initial protocol.
+ */
+
+ if (bitnset(M_7BITS, m->m_flags) &&
+ (!clever || mci->mci_state == MCIS_OPENING))
+ mci->mci_flags |= MCIF_7BIT;
+ if (clever && mci->mci_state != MCIS_CLOSED)
+ {
+# if STARTTLS || SASL
+ int dotpos;
+ char *srvname;
+ extern SOCKADDR CurHostAddr;
+# endif /* STARTTLS || SASL */
+
+# if SASL
+# define DONE_AUTH(f) bitset(MCIF_AUTHACT, f)
+# endif /* SASL */
+# if STARTTLS
+# define DONE_STARTTLS(f) bitset(MCIF_TLSACT, f)
+# endif /* STARTTLS */
+# define ONLY_HELO(f) bitset(MCIF_ONLY_EHLO, f)
+# define SET_HELO(f) f |= MCIF_ONLY_EHLO
+# define CLR_HELO(f) f &= ~MCIF_ONLY_EHLO
+
+# if STARTTLS || SASL
+ /* don't use CurHostName, it is changed in many places */
+ if (mci->mci_host != NULL)
+ {
+ srvname = mci->mci_host;
+ dotpos = strlen(srvname) - 1;
+ if (dotpos >= 0)
+ {
+ if (srvname[dotpos] == '.')
+ srvname[dotpos] = '\0';
+ else
+ dotpos = -1;
+ }
+ }
+ else if (mci->mci_mailer != NULL)
+ {
+ srvname = mci->mci_mailer->m_name;
+ dotpos = -1;
+ }
+ else
+ {
+ srvname = "local";
+ dotpos = -1;
+ }
+
+ /* don't set {server_name} to NULL or "": see getauth() */
+ macdefine(&mci->mci_macro, A_TEMP, macid("{server_name}"),
+ srvname);
+
+ /* CurHostAddr is set by makeconnection() and mci_get() */
+ if (CurHostAddr.sa.sa_family != 0)
+ {
+ macdefine(&mci->mci_macro, A_TEMP,
+ macid("{server_addr}"),
+ anynet_ntoa(&CurHostAddr));
+ }
+ else if (mci->mci_mailer != NULL)
+ {
+ /* mailer name is unique, use it as address */
+ macdefine(&mci->mci_macro, A_PERM,
+ macid("{server_addr}"),
+ mci->mci_mailer->m_name);
+ }
+ else
+ {
+ /* don't set it to NULL or "": see getauth() */
+ macdefine(&mci->mci_macro, A_PERM,
+ macid("{server_addr}"), "0");
+ }
+
+ /* undo change of srvname (mci->mci_host) */
+ if (dotpos >= 0)
+ srvname[dotpos] = '.';
+
+reconnect: /* after switching to an encrypted connection */
+# endif /* STARTTLS || SASL */
+
+ /* set the current connection information */
+ e->e_mci = mci;
+# if SASL
+ mci->mci_saslcap = NULL;
+# endif /* SASL */
+ smtpinit(m, mci, e, ONLY_HELO(mci->mci_flags));
+ CLR_HELO(mci->mci_flags);
+
+ if (IS_DLVR_RETURN(e))
+ {
+ /*
+ ** Check whether other side can deliver e-mail
+ ** fast enough
+ */
+
+ if (!bitset(MCIF_DLVR_BY, mci->mci_flags))
+ {
+ e->e_status = "5.4.7";
+ usrerrenh(e->e_status,
+ "554 Server does not support Deliver By");
+ rcode = EX_UNAVAILABLE;
+ goto give_up;
+ }
+ if (e->e_deliver_by > 0 &&
+ e->e_deliver_by - (curtime() - e->e_ctime) <
+ mci->mci_min_by)
+ {
+ e->e_status = "5.4.7";
+ usrerrenh(e->e_status,
+ "554 Message can't be delivered in time; %ld < %ld",
+ e->e_deliver_by - (curtime() - e->e_ctime),
+ mci->mci_min_by);
+ rcode = EX_UNAVAILABLE;
+ goto give_up;
+ }
+ }
+
+# if STARTTLS
+ /* first TLS then AUTH to provide a security layer */
+ if (mci->mci_state != MCIS_CLOSED &&
+ !DONE_STARTTLS(mci->mci_flags))
+ {
+ int olderrors;
+ bool usetls;
+ bool saveQuickAbort = QuickAbort;
+ bool saveSuprErrs = SuprErrs;
+ char *host = NULL;
+
+ rcode = EX_OK;
+ usetls = bitset(MCIF_TLS, mci->mci_flags);
+ if (usetls)
+ usetls = !iscltflgset(e, D_NOTLS);
+
+ if (usetls)
+ {
+ host = macvalue(macid("{server_name}"), e);
+ olderrors = Errors;
+ QuickAbort = false;
+ SuprErrs = true;
+ if (rscheck("try_tls", host, NULL, e,
+ RSF_RMCOMM, 7, host, NOQID) != EX_OK
+ || Errors > olderrors)
+ usetls = false;
+ SuprErrs = saveSuprErrs;
+ QuickAbort = saveQuickAbort;
+ }
+
+ if (usetls)
+ {
+ if ((rcode = starttls(m, mci, e)) == EX_OK)
+ {
+ /* start again without STARTTLS */
+ mci->mci_flags |= MCIF_TLSACT;
+ }
+ else
+ {
+ char *s;
+
+ /*
+ ** TLS negotation failed, what to do?
+ ** fall back to unencrypted connection
+ ** or abort? How to decide?
+ ** set a macro and call a ruleset.
+ */
+
+ mci->mci_flags &= ~MCIF_TLS;
+ switch (rcode)
+ {
+ case EX_TEMPFAIL:
+ s = "TEMP";
+ break;
+ case EX_USAGE:
+ s = "USAGE";
+ break;
+ case EX_PROTOCOL:
+ s = "PROTOCOL";
+ break;
+ case EX_SOFTWARE:
+ s = "SOFTWARE";
+ break;
+
+ /* everything else is a failure */
+ default:
+ s = "FAILURE";
+ rcode = EX_TEMPFAIL;
+ }
+ macdefine(&e->e_macro, A_PERM,
+ macid("{verify}"), s);
+ }
+ }
+ else
+ macdefine(&e->e_macro, A_PERM,
+ macid("{verify}"), "NONE");
+ olderrors = Errors;
+ QuickAbort = false;
+ SuprErrs = true;
+
+ /*
+ ** rcode == EX_SOFTWARE is special:
+ ** the TLS negotation failed
+ ** we have to drop the connection no matter what
+ ** However, we call tls_server to give it the chance
+ ** to log the problem and return an appropriate
+ ** error code.
+ */
+
+ if (rscheck("tls_server",
+ macvalue(macid("{verify}"), e),
+ NULL, e, RSF_RMCOMM|RSF_COUNT, 5,
+ host, NOQID) != EX_OK ||
+ Errors > olderrors ||
+ rcode == EX_SOFTWARE)
+ {
+ char enhsc[ENHSCLEN];
+ extern char MsgBuf[];
+
+ if (ISSMTPCODE(MsgBuf) &&
+ extenhsc(MsgBuf + 4, ' ', enhsc) > 0)
+ {
+ p = sm_rpool_strdup_x(e->e_rpool,
+ MsgBuf);
+ }
+ else
+ {
+ p = "403 4.7.0 server not authenticated.";
+ (void) sm_strlcpy(enhsc, "4.7.0",
+ sizeof enhsc);
+ }
+ SuprErrs = saveSuprErrs;
+ QuickAbort = saveQuickAbort;
+
+ if (rcode == EX_SOFTWARE)
+ {
+ /* drop the connection */
+ mci->mci_state = MCIS_QUITING;
+ if (mci->mci_in != NULL)
+ {
+ (void) sm_io_close(mci->mci_in,
+ SM_TIME_DEFAULT);
+ mci->mci_in = NULL;
+ }
+ mci->mci_flags &= ~MCIF_TLSACT;
+ (void) endmailer(mci, e, pv);
+ }
+ else
+ {
+ /* abort transfer */
+ smtpquit(m, mci, e);
+ }
+
+ /* avoid bogus error msg */
+ mci->mci_errno = 0;
+
+ /* temp or permanent failure? */
+ rcode = (*p == '4') ? EX_TEMPFAIL
+ : EX_UNAVAILABLE;
+ mci_setstat(mci, rcode, enhsc, p);
+
+ /*
+ ** hack to get the error message into
+ ** the envelope (done in giveresponse())
+ */
+
+ (void) sm_strlcpy(SmtpError, p,
+ sizeof SmtpError);
+ }
+ QuickAbort = saveQuickAbort;
+ SuprErrs = saveSuprErrs;
+ if (DONE_STARTTLS(mci->mci_flags) &&
+ mci->mci_state != MCIS_CLOSED)
+ {
+ SET_HELO(mci->mci_flags);
+ mci->mci_flags &= ~MCIF_EXTENS;
+ goto reconnect;
+ }
+ }
+# endif /* STARTTLS */
+# if SASL
+ /* if other server supports authentication let's authenticate */
+ if (mci->mci_state != MCIS_CLOSED &&
+ mci->mci_saslcap != NULL &&
+ !DONE_AUTH(mci->mci_flags) && !iscltflgset(e, D_NOAUTH))
+ {
+ /* Should we require some minimum authentication? */
+ if ((ret = smtpauth(m, mci, e)) == EX_OK)
+ {
+ int result;
+ sasl_ssf_t *ssf = NULL;
+
+ /* Get security strength (features) */
+ result = sasl_getprop(mci->mci_conn, SASL_SSF,
+# if SASL >= 20000
+ (const void **) &ssf);
+# else /* SASL >= 20000 */
+ (void **) &ssf);
+# endif /* SASL >= 20000 */
+
+ /* XXX authid? */
+ if (LogLevel > 9)
+ sm_syslog(LOG_INFO, NOQID,
+ "AUTH=client, relay=%.100s, mech=%.16s, bits=%d",
+ mci->mci_host,
+ macvalue(macid("{auth_type}"), e),
+ result == SASL_OK ? *ssf : 0);
+
+ /*
+ ** Only switch to encrypted connection
+ ** if a security layer has been negotiated
+ */
+
+ if (result == SASL_OK && *ssf > 0)
+ {
+ /*
+ ** Convert I/O layer to use SASL.
+ ** If the call fails, the connection
+ ** is aborted.
+ */
+
+ if (sfdcsasl(&mci->mci_in,
+ &mci->mci_out,
+ mci->mci_conn) == 0)
+ {
+ mci->mci_flags &= ~MCIF_EXTENS;
+ mci->mci_flags |= MCIF_AUTHACT|
+ MCIF_ONLY_EHLO;
+ goto reconnect;
+ }
+ syserr("AUTH TLS switch failed in client");
+ }
+ /* else? XXX */
+ mci->mci_flags |= MCIF_AUTHACT;
+
+ }
+ else if (ret == EX_TEMPFAIL)
+ {
+ if (LogLevel > 8)
+ sm_syslog(LOG_ERR, NOQID,
+ "AUTH=client, relay=%.100s, temporary failure, connection abort",
+ mci->mci_host);
+ smtpquit(m, mci, e);
+
+ /* avoid bogus error msg */
+ mci->mci_errno = 0;
+ rcode = EX_TEMPFAIL;
+ mci_setstat(mci, rcode, "4.3.0", p);
+
+ /*
+ ** hack to get the error message into
+ ** the envelope (done in giveresponse())
+ */
+
+ (void) sm_strlcpy(SmtpError,
+ "Temporary AUTH failure",
+ sizeof SmtpError);
+ }
+ }
+# endif /* SASL */
+ }
+
+
+do_transfer:
+ /* clear out per-message flags from connection structure */
+ mci->mci_flags &= ~(MCIF_CVT7TO8|MCIF_CVT8TO7);
+
+ if (bitset(EF_HAS8BIT, e->e_flags) &&
+ !bitset(EF_DONT_MIME, e->e_flags) &&
+ bitnset(M_7BITS, m->m_flags))
+ mci->mci_flags |= MCIF_CVT8TO7;
+
+#if MIME7TO8
+ if (bitnset(M_MAKE8BIT, m->m_flags) &&
+ !bitset(MCIF_7BIT, mci->mci_flags) &&
+ (p = hvalue("Content-Transfer-Encoding", e->e_header)) != NULL &&
+ (sm_strcasecmp(p, "quoted-printable") == 0 ||
+ sm_strcasecmp(p, "base64") == 0) &&
+ (p = hvalue("Content-Type", e->e_header)) != NULL)
+ {
+ /* may want to convert 7 -> 8 */
+ /* XXX should really parse it here -- and use a class XXX */
+ if (sm_strncasecmp(p, "text/plain", 10) == 0 &&
+ (p[10] == '\0' || p[10] == ' ' || p[10] == ';'))
+ mci->mci_flags |= MCIF_CVT7TO8;
+ }
+#endif /* MIME7TO8 */
+
+ if (tTd(11, 1))
+ {
+ sm_dprintf("openmailer: ");
+ mci_dump(sm_debug_file(), mci, false);
+ }
+
+#if _FFR_CLIENT_SIZE
+ /*
+ ** See if we know the maximum size and
+ ** abort if the message is too big.
+ **
+ ** NOTE: _FFR_CLIENT_SIZE is untested.
+ */
+
+ if (bitset(MCIF_SIZE, mci->mci_flags) &&
+ mci->mci_maxsize > 0 &&
+ e->e_msgsize > mci->mci_maxsize)
+ {
+ e->e_flags |= EF_NO_BODY_RETN;
+ if (bitnset(M_LOCALMAILER, m->m_flags))
+ e->e_status = "5.2.3";
+ else
+ e->e_status = "5.3.4";
+
+ usrerrenh(e->e_status,
+ "552 Message is too large; %ld bytes max",
+ mci->mci_maxsize);
+ rcode = EX_DATAERR;
+
+ /* Need an e_message for error */
+ (void) sm_snprintf(SmtpError, sizeof SmtpError,
+ "Message is too large; %ld bytes max",
+ mci->mci_maxsize);
+ goto give_up;
+ }
+#endif /* _FFR_CLIENT_SIZE */
+
+ if (mci->mci_state != MCIS_OPEN)
+ {
+ /* couldn't open the mailer */
+ rcode = mci->mci_exitstat;
+ errno = mci->mci_errno;
+ SM_SET_H_ERRNO(mci->mci_herrno);
+ if (rcode == EX_OK)
+ {
+ /* shouldn't happen */
+ syserr("554 5.3.5 deliver: mci=%lx rcode=%d errno=%d state=%d sig=%s",
+ (unsigned long) mci, rcode, errno,
+ mci->mci_state, firstsig);
+ mci_dump_all(smioout, true);
+ rcode = EX_SOFTWARE;
+ }
+ else if (nummxhosts > hostnum)
+ {
+ /* try next MX site */
+ goto tryhost;
+ }
+ }
+ else if (!clever)
+ {
+ /*
+ ** Format and send message.
+ */
+
+ putfromline(mci, e);
+ (*e->e_puthdr)(mci, e->e_header, e, M87F_OUTER);
+ (*e->e_putbody)(mci, e, NULL);
+
+ /* get the exit status */
+ rcode = endmailer(mci, e, pv);
+ if (rcode == EX_TEMPFAIL && SmtpError[0] == '\0')
+ {
+ /*
+ ** Need an e_message for mailq display.
+ ** We set SmtpError as
+ */
+
+ (void) sm_snprintf(SmtpError, sizeof SmtpError,
+ "%s mailer (%s) exited with EX_TEMPFAIL",
+ m->m_name, m->m_mailer);
+ }
+ }
+ else
+ {
+ /*
+ ** Send the MAIL FROM: protocol
+ */
+
+ /* XXX this isn't pipelined... */
+ rcode = smtpmailfrom(m, mci, e);
+ if (rcode == EX_OK)
+ {
+ register int i;
+# if PIPELINING
+ ADDRESS *volatile pchain;
+# endif /* PIPELINING */
+
+ /* send the recipient list */
+ tobuf[0] = '\0';
+ mci->mci_retryrcpt = false;
+ mci->mci_tolist = tobuf;
+# if PIPELINING
+ pchain = NULL;
+ mci->mci_nextaddr = NULL;
+# endif /* PIPELINING */
+
+ for (to = tochain; to != NULL; to = to->q_tchain)
+ {
+ if (!QS_IS_UNMARKED(to->q_state))
+ continue;
+
+ /* mark recipient state as "ok so far" */
+ to->q_state = QS_OK;
+ e->e_to = to->q_paddr;
+# if STARTTLS
+ i = rscheck("tls_rcpt", to->q_user, NULL, e,
+ RSF_RMCOMM|RSF_COUNT, 3,
+ mci->mci_host, e->e_id);
+ if (i != EX_OK)
+ {
+ markfailure(e, to, mci, i, false);
+ giveresponse(i, to->q_status, m, mci,
+ ctladdr, xstart, e, to);
+ if (i == EX_TEMPFAIL)
+ {
+ mci->mci_retryrcpt = true;
+ to->q_state = QS_RETRY;
+ }
+ continue;
+ }
+# endif /* STARTTLS */
+
+ i = smtprcpt(to, m, mci, e, ctladdr, xstart);
+# if PIPELINING
+ if (i == EX_OK &&
+ bitset(MCIF_PIPELINED, mci->mci_flags))
+ {
+ /*
+ ** Add new element to list of
+ ** recipients for pipelining.
+ */
+
+ to->q_pchain = NULL;
+ if (mci->mci_nextaddr == NULL)
+ mci->mci_nextaddr = to;
+ if (pchain == NULL)
+ pchain = to;
+ else
+ {
+ pchain->q_pchain = to;
+ pchain = pchain->q_pchain;
+ }
+ }
+# endif /* PIPELINING */
+ if (i != EX_OK)
+ {
+ markfailure(e, to, mci, i, false);
+ giveresponse(i, to->q_status, m, mci,
+ ctladdr, xstart, e, to);
+ if (i == EX_TEMPFAIL)
+ to->q_state = QS_RETRY;
+ }
+ }
+
+ /* No recipients in list and no missing responses? */
+ if (tobuf[0] == '\0'
+# if PIPELINING
+ && mci->mci_nextaddr == NULL
+# endif /* PIPELINING */
+ )
+ {
+ rcode = EX_OK;
+ e->e_to = NULL;
+ if (bitset(MCIF_CACHED, mci->mci_flags))
+ smtprset(m, mci, e);
+ }
+ else
+ {
+ e->e_to = tobuf + 1;
+ rcode = smtpdata(m, mci, e, ctladdr, xstart);
+ }
+ }
+ if (rcode == EX_TEMPFAIL && nummxhosts > hostnum)
+ {
+ /* try next MX site */
+ goto tryhost;
+ }
+ }
+#if NAMED_BIND
+ if (ConfigLevel < 2)
+ _res.options |= RES_DEFNAMES | RES_DNSRCH; /* XXX */
+#endif /* NAMED_BIND */
+
+ if (tTd(62, 1))
+ checkfds("after delivery");
+
+ /*
+ ** Do final status disposal.
+ ** We check for something in tobuf for the SMTP case.
+ ** If we got a temporary failure, arrange to queue the
+ ** addressees.
+ */
+
+ give_up:
+ if (bitnset(M_LMTP, m->m_flags))
+ {
+ lmtp_rcode = rcode;
+ tobuf[0] = '\0';
+ anyok = false;
+ strsize = 0;
+ }
+ else
+ anyok = rcode == EX_OK;
+
+ for (to = tochain; to != NULL; to = to->q_tchain)
+ {
+ /* see if address already marked */
+ if (!QS_IS_OK(to->q_state))
+ continue;
+
+ /* if running LMTP, get the status for each address */
+ if (bitnset(M_LMTP, m->m_flags))
+ {
+ if (lmtp_rcode == EX_OK)
+ rcode = smtpgetstat(m, mci, e);
+ if (rcode == EX_OK)
+ {
+ strsize += sm_strlcat2(tobuf + strsize, ",",
+ to->q_paddr,
+ tobufsize - strsize);
+ SM_ASSERT(strsize < tobufsize);
+ anyok = true;
+ }
+ else
+ {
+ e->e_to = to->q_paddr;
+ markfailure(e, to, mci, rcode, true);
+ giveresponse(rcode, to->q_status, m, mci,
+ ctladdr, xstart, e, to);
+ e->e_to = tobuf + 1;
+ continue;
+ }
+ }
+ else
+ {
+ /* mark bad addresses */
+ if (rcode != EX_OK)
+ {
+ if (goodmxfound && rcode == EX_NOHOST)
+ rcode = EX_TEMPFAIL;
+ markfailure(e, to, mci, rcode, true);
+ continue;
+ }
+ }
+
+ /* successful delivery */
+ to->q_state = QS_SENT;
+ to->q_statdate = curtime();
+ e->e_nsent++;
+
+ /*
+ ** Checkpoint the send list every few addresses
+ */
+
+ if (CheckpointInterval > 0 && e->e_nsent >= CheckpointInterval)
+ {
+ queueup(e, false, false);
+ e->e_nsent = 0;
+ }
+
+ if (bitnset(M_LOCALMAILER, m->m_flags) &&
+ bitset(QPINGONSUCCESS, to->q_flags))
+ {
+ to->q_flags |= QDELIVERED;
+ to->q_status = "2.1.5";
+ (void) sm_io_fprintf(e->e_xfp, SM_TIME_DEFAULT,
+ "%s... Successfully delivered\n",
+ to->q_paddr);
+ }
+ else if (bitset(QPINGONSUCCESS, to->q_flags) &&
+ bitset(QPRIMARY, to->q_flags) &&
+ !bitset(MCIF_DSN, mci->mci_flags))
+ {
+ to->q_flags |= QRELAYED;
+ (void) sm_io_fprintf(e->e_xfp, SM_TIME_DEFAULT,
+ "%s... relayed; expect no further notifications\n",
+ to->q_paddr);
+ }
+ else if (IS_DLVR_NOTIFY(e) &&
+ !bitset(MCIF_DLVR_BY, mci->mci_flags) &&
+ bitset(QPRIMARY, to->q_flags) &&
+ (!bitset(QHASNOTIFY, to->q_flags) ||
+ bitset(QPINGONSUCCESS, to->q_flags) ||
+ bitset(QPINGONFAILURE, to->q_flags) ||
+ bitset(QPINGONDELAY, to->q_flags)))
+ {
+ /* RFC 2852, 4.1.4.2: no NOTIFY, or not NEVER */
+ to->q_flags |= QBYNRELAY;
+ (void) sm_io_fprintf(e->e_xfp, SM_TIME_DEFAULT,
+ "%s... Deliver-by notify: relayed\n",
+ to->q_paddr);
+ }
+ else if (IS_DLVR_TRACE(e) &&
+ (!bitset(QHASNOTIFY, to->q_flags) ||
+ bitset(QPINGONSUCCESS, to->q_flags) ||
+ bitset(QPINGONFAILURE, to->q_flags) ||
+ bitset(QPINGONDELAY, to->q_flags)) &&
+ bitset(QPRIMARY, to->q_flags))
+ {
+ /* RFC 2852, 4.1.4: no NOTIFY, or not NEVER */
+ to->q_flags |= QBYTRACE;
+ (void) sm_io_fprintf(e->e_xfp, SM_TIME_DEFAULT,
+ "%s... Deliver-By trace: relayed\n",
+ to->q_paddr);
+ }
+ }
+
+ if (bitnset(M_LMTP, m->m_flags))
+ {
+ /*
+ ** Global information applies to the last recipient only;
+ ** clear it out to avoid bogus errors.
+ */
+
+ rcode = EX_OK;
+ e->e_statmsg = NULL;
+
+ /* reset the mci state for the next transaction */
+ if (mci != NULL &&
+ (mci->mci_state == MCIS_MAIL ||
+ mci->mci_state == MCIS_RCPT ||
+ mci->mci_state == MCIS_DATA))
+ {
+ mci->mci_state = MCIS_OPEN;
+ SmtpPhase = mci->mci_phase = "idle";
+ sm_setproctitle(true, e, "%s: %s", CurHostName,
+ mci->mci_phase);
+ }
+ }
+
+ if (tobuf[0] != '\0')
+ {
+ giveresponse(rcode, NULL, m, mci, ctladdr, xstart, e, tochain);
+#if 0
+ /*
+ ** This code is disabled for now because I am not
+ ** sure that copying status from the first recipient
+ ** to all non-status'ed recipients is a good idea.
+ */
+
+ if (tochain->q_message != NULL &&
+ !bitnset(M_LMTP, m->m_flags) && rcode != EX_OK)
+ {
+ for (to = tochain->q_tchain; to != NULL;
+ to = to->q_tchain)
+ {
+ /* see if address already marked */
+ if (QS_IS_QUEUEUP(to->q_state) &&
+ to->q_message == NULL)
+ to->q_message = sm_rpool_strdup_x(e->e_rpool,
+ tochain->q_message);
+ }
+ }
+#endif /* 0 */
+ }
+ if (anyok)
+ markstats(e, tochain, STATS_NORMAL);
+ mci_store_persistent(mci);
+
+ /* Some recipients were tempfailed, try them on the next host */
+ if (mci != NULL && mci->mci_retryrcpt && nummxhosts > hostnum)
+ {
+ /* try next MX site */
+ goto tryhost;
+ }
+
+ /* now close the connection */
+ if (clever && mci != NULL && mci->mci_state != MCIS_CLOSED &&
+ !bitset(MCIF_CACHED, mci->mci_flags))
+ smtpquit(m, mci, e);
+
+cleanup: ;
+ }
+ SM_FINALLY
+ {
+ /*
+ ** Restore state and return.
+ */
+#if XDEBUG
+ char wbuf[MAXLINE];
+
+ /* make absolutely certain 0, 1, and 2 are in use */
+ (void) sm_snprintf(wbuf, sizeof wbuf,
+ "%s... end of deliver(%s)",
+ e->e_to == NULL ? "NO-TO-LIST"
+ : shortenstring(e->e_to,
+ MAXSHORTSTR),
+ m->m_name);
+ checkfd012(wbuf);
+#endif /* XDEBUG */
+
+ errno = 0;
+
+ /*
+ ** It was originally necessary to set macro 'g' to NULL
+ ** because it previously pointed to an auto buffer.
+ ** We don't do this any more, so this may be unnecessary.
+ */
+
+ macdefine(&e->e_macro, A_PERM, 'g', (char *) NULL);
+ e->e_to = NULL;
+ }
+ SM_END_TRY
+ return rcode;
+}
+
+/*
+** MARKFAILURE -- mark a failure on a specific address.
+**
+** Parameters:
+** e -- the envelope we are sending.
+** q -- the address to mark.
+** mci -- mailer connection information.
+** rcode -- the code signifying the particular failure.
+** ovr -- override an existing code?
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** marks the address (and possibly the envelope) with the
+** failure so that an error will be returned or
+** the message will be queued, as appropriate.
+*/
+
+void
+markfailure(e, q, mci, rcode, ovr)
+ register ENVELOPE *e;
+ register ADDRESS *q;
+ register MCI *mci;
+ int rcode;
+ bool ovr;
+{
+ int save_errno = errno;
+ char *status = NULL;
+ char *rstatus = NULL;
+
+ switch (rcode)
+ {
+ case EX_OK:
+ break;
+
+ case EX_TEMPFAIL:
+ case EX_IOERR:
+ case EX_OSERR:
+ q->q_state = QS_QUEUEUP;
+ break;
+
+ default:
+ q->q_state = QS_BADADDR;
+ break;
+ }
+
+ /* find most specific error code possible */
+ if (mci != NULL && mci->mci_status != NULL)
+ {
+ status = sm_rpool_strdup_x(e->e_rpool, mci->mci_status);
+ if (mci->mci_rstatus != NULL)
+ rstatus = sm_rpool_strdup_x(e->e_rpool,
+ mci->mci_rstatus);
+ else
+ rstatus = NULL;
+ }
+ else if (e->e_status != NULL)
+ {
+ status = e->e_status;
+ rstatus = NULL;
+ }
+ else
+ {
+ switch (rcode)
+ {
+ case EX_USAGE:
+ status = "5.5.4";
+ break;
+
+ case EX_DATAERR:
+ status = "5.5.2";
+ break;
+
+ case EX_NOUSER:
+ status = "5.1.1";
+ break;
+
+ case EX_NOHOST:
+ status = "5.1.2";
+ break;
+
+ case EX_NOINPUT:
+ case EX_CANTCREAT:
+ case EX_NOPERM:
+ status = "5.3.0";
+ break;
+
+ case EX_UNAVAILABLE:
+ case EX_SOFTWARE:
+ case EX_OSFILE:
+ case EX_PROTOCOL:
+ case EX_CONFIG:
+ status = "5.5.0";
+ break;
+
+ case EX_OSERR:
+ case EX_IOERR:
+ status = "4.5.0";
+ break;
+
+ case EX_TEMPFAIL:
+ status = "4.2.0";
+ break;
+ }
+ }
+
+ /* new status? */
+ if (status != NULL && *status != '\0' && (ovr || q->q_status == NULL ||
+ *q->q_status == '\0' || *q->q_status < *status))
+ {
+ q->q_status = status;
+ q->q_rstatus = rstatus;
+ }
+ if (rcode != EX_OK && q->q_rstatus == NULL &&
+ q->q_mailer != NULL && q->q_mailer->m_diagtype != NULL &&
+ sm_strcasecmp(q->q_mailer->m_diagtype, "X-UNIX") == 0)
+ {
+ char buf[16];
+
+ (void) sm_snprintf(buf, sizeof buf, "%d", rcode);
+ q->q_rstatus = sm_rpool_strdup_x(e->e_rpool, buf);
+ }
+
+ q->q_statdate = curtime();
+ if (CurHostName != NULL && CurHostName[0] != '\0' &&
+ mci != NULL && !bitset(M_LOCALMAILER, mci->mci_flags))
+ q->q_statmta = sm_rpool_strdup_x(e->e_rpool, CurHostName);
+
+ /* restore errno */
+ errno = save_errno;
+}
+/*
+** ENDMAILER -- Wait for mailer to terminate.
+**
+** We should never get fatal errors (e.g., segmentation
+** violation), so we report those specially. For other
+** errors, we choose a status message (into statmsg),
+** and if it represents an error, we print it.
+**
+** Parameters:
+** mci -- the mailer connection info.
+** e -- the current envelope.
+** pv -- the parameter vector that invoked the mailer
+** (for error messages).
+**
+** Returns:
+** exit code of mailer.
+**
+** Side Effects:
+** none.
+*/
+
+static jmp_buf EndWaitTimeout;
+
+static void
+endwaittimeout(ignore)
+ int ignore;
+{
+ /*
+ ** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+ ** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+ ** DOING.
+ */
+
+ errno = ETIMEDOUT;
+ longjmp(EndWaitTimeout, 1);
+}
+
+int
+endmailer(mci, e, pv)
+ register MCI *mci;
+ register ENVELOPE *e;
+ char **pv;
+{
+ int st;
+ int save_errno = errno;
+ char buf[MAXLINE];
+ SM_EVENT *ev = NULL;
+
+
+ mci_unlock_host(mci);
+
+ /* close output to mailer */
+ if (mci->mci_out != NULL)
+ {
+ (void) sm_io_close(mci->mci_out, SM_TIME_DEFAULT);
+ mci->mci_out = NULL;
+ }
+
+ /* copy any remaining input to transcript */
+ if (mci->mci_in != NULL && mci->mci_state != MCIS_ERROR &&
+ e->e_xfp != NULL)
+ {
+ while (sfgets(buf, sizeof buf, mci->mci_in,
+ TimeOuts.to_quit, "Draining Input") != NULL)
+ (void) sm_io_fputs(e->e_xfp, SM_TIME_DEFAULT, buf);
+ }
+
+#if SASL
+ /* close SASL connection */
+ if (bitset(MCIF_AUTHACT, mci->mci_flags))
+ {
+ sasl_dispose(&mci->mci_conn);
+ mci->mci_flags &= ~MCIF_AUTHACT;
+ }
+#endif /* SASL */
+
+#if STARTTLS
+ /* shutdown TLS */
+ (void) endtlsclt(mci);
+#endif /* STARTTLS */
+
+ /* now close the input */
+ if (mci->mci_in != NULL)
+ {
+ (void) sm_io_close(mci->mci_in, SM_TIME_DEFAULT);
+ mci->mci_in = NULL;
+ }
+ mci->mci_state = MCIS_CLOSED;
+
+ errno = save_errno;
+
+ /* in the IPC case there is nothing to wait for */
+ if (mci->mci_pid == 0)
+ return EX_OK;
+
+ /* put a timeout around the wait */
+ if (mci->mci_mailer->m_wait > 0)
+ {
+ if (setjmp(EndWaitTimeout) == 0)
+ ev = sm_setevent(mci->mci_mailer->m_wait,
+ endwaittimeout, 0);
+ else
+ {
+ syserr("endmailer %s: wait timeout (%ld)",
+ mci->mci_mailer->m_name,
+ (long) mci->mci_mailer->m_wait);
+ return EX_TEMPFAIL;
+ }
+ }
+
+ /* wait for the mailer process, collect status */
+ st = waitfor(mci->mci_pid);
+ save_errno = errno;
+ if (ev != NULL)
+ sm_clrevent(ev);
+ errno = save_errno;
+
+ if (st == -1)
+ {
+ syserr("endmailer %s: wait", mci->mci_mailer->m_name);
+ return EX_SOFTWARE;
+ }
+
+ if (WIFEXITED(st))
+ {
+ /* normal death -- return status */
+ return (WEXITSTATUS(st));
+ }
+
+ /* it died a horrid death */
+ syserr("451 4.3.0 mailer %s died with signal %d%s",
+ mci->mci_mailer->m_name, WTERMSIG(st),
+ WCOREDUMP(st) ? " (core dumped)" :
+ (WIFSTOPPED(st) ? " (stopped)" : ""));
+
+ /* log the arguments */
+ if (pv != NULL && e->e_xfp != NULL)
+ {
+ register char **av;
+
+ (void) sm_io_fprintf(e->e_xfp, SM_TIME_DEFAULT, "Arguments:");
+ for (av = pv; *av != NULL; av++)
+ (void) sm_io_fprintf(e->e_xfp, SM_TIME_DEFAULT, " %s",
+ *av);
+ (void) sm_io_fprintf(e->e_xfp, SM_TIME_DEFAULT, "\n");
+ }
+
+ ExitStat = EX_TEMPFAIL;
+ return EX_TEMPFAIL;
+}
+/*
+** GIVERESPONSE -- Interpret an error response from a mailer
+**
+** Parameters:
+** status -- the status code from the mailer (high byte
+** only; core dumps must have been taken care of
+** already).
+** dsn -- the DSN associated with the address, if any.
+** m -- the mailer info for this mailer.
+** mci -- the mailer connection info -- can be NULL if the
+** response is given before the connection is made.
+** ctladdr -- the controlling address for the recipient
+** address(es).
+** xstart -- the transaction start time, for computing
+** transaction delays.
+** e -- the current envelope.
+** to -- the current recipient (NULL if none).
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Errors may be incremented.
+** ExitStat may be set.
+*/
+
+void
+giveresponse(status, dsn, m, mci, ctladdr, xstart, e, to)
+ int status;
+ char *dsn;
+ register MAILER *m;
+ register MCI *mci;
+ ADDRESS *ctladdr;
+ time_t xstart;
+ ENVELOPE *e;
+ ADDRESS *to;
+{
+ register const char *statmsg;
+ int errnum = errno;
+ int off = 4;
+ bool usestat = false;
+ char dsnbuf[ENHSCLEN];
+ char buf[MAXLINE];
+ char *exmsg;
+
+ if (e == NULL)
+ syserr("giveresponse: null envelope");
+
+ /*
+ ** Compute status message from code.
+ */
+
+ exmsg = sm_sysexmsg(status);
+ if (status == 0)
+ {
+ statmsg = "250 2.0.0 Sent";
+ if (e->e_statmsg != NULL)
+ {
+ (void) sm_snprintf(buf, sizeof buf, "%s (%s)",
+ statmsg,
+ shortenstring(e->e_statmsg, 403));
+ statmsg = buf;
+ }
+ }
+ else if (exmsg == NULL)
+ {
+ (void) sm_snprintf(buf, sizeof buf,
+ "554 5.3.0 unknown mailer error %d",
+ status);
+ status = EX_UNAVAILABLE;
+ statmsg = buf;
+ usestat = true;
+ }
+ else if (status == EX_TEMPFAIL)
+ {
+ char *bp = buf;
+
+ (void) sm_strlcpy(bp, exmsg + 1, SPACELEFT(buf, bp));
+ bp += strlen(bp);
+#if NAMED_BIND
+ if (h_errno == TRY_AGAIN)
+ statmsg = sm_errstring(h_errno + E_DNSBASE);
+ else
+#endif /* NAMED_BIND */
+ {
+ if (errnum != 0)
+ statmsg = sm_errstring(errnum);
+ else
+ statmsg = SmtpError;
+ }
+ if (statmsg != NULL && statmsg[0] != '\0')
+ {
+ switch (errnum)
+ {
+#ifdef ENETDOWN
+ case ENETDOWN: /* Network is down */
+#endif /* ENETDOWN */
+#ifdef ENETUNREACH
+ case ENETUNREACH: /* Network is unreachable */
+#endif /* ENETUNREACH */
+#ifdef ENETRESET
+ case ENETRESET: /* Network dropped connection on reset */
+#endif /* ENETRESET */
+#ifdef ECONNABORTED
+ case ECONNABORTED: /* Software caused connection abort */
+#endif /* ECONNABORTED */
+#ifdef EHOSTDOWN
+ case EHOSTDOWN: /* Host is down */
+#endif /* EHOSTDOWN */
+#ifdef EHOSTUNREACH
+ case EHOSTUNREACH: /* No route to host */
+#endif /* EHOSTUNREACH */
+ if (mci != NULL && mci->mci_host != NULL)
+ {
+ (void) sm_strlcpyn(bp,
+ SPACELEFT(buf, bp),
+ 2, ": ",
+ mci->mci_host);
+ bp += strlen(bp);
+ }
+ break;
+ }
+ (void) sm_strlcpyn(bp, SPACELEFT(buf, bp), 2, ": ",
+ statmsg);
+ usestat = true;
+ }
+ statmsg = buf;
+ }
+#if NAMED_BIND
+ else if (status == EX_NOHOST && h_errno != 0)
+ {
+ statmsg = sm_errstring(h_errno + E_DNSBASE);
+ (void) sm_snprintf(buf, sizeof buf, "%s (%s)", exmsg + 1,
+ statmsg);
+ statmsg = buf;
+ usestat = true;
+ }
+#endif /* NAMED_BIND */
+ else
+ {
+ statmsg = exmsg;
+ if (*statmsg++ == ':' && errnum != 0)
+ {
+ (void) sm_snprintf(buf, sizeof buf, "%s: %s", statmsg,
+ sm_errstring(errnum));
+ statmsg = buf;
+ usestat = true;
+ }
+ else if (bitnset(M_LMTP, m->m_flags) && e->e_statmsg != NULL)
+ {
+ (void) sm_snprintf(buf, sizeof buf, "%s (%s)", statmsg,
+ shortenstring(e->e_statmsg, 403));
+ statmsg = buf;
+ usestat = true;
+ }
+ }
+
+ /*
+ ** Print the message as appropriate
+ */
+
+ if (status == EX_OK || status == EX_TEMPFAIL)
+ {
+ extern char MsgBuf[];
+
+ if ((off = isenhsc(statmsg + 4, ' ')) > 0)
+ {
+ if (dsn == NULL)
+ {
+ (void) sm_snprintf(dsnbuf, sizeof dsnbuf,
+ "%.*s", off, statmsg + 4);
+ dsn = dsnbuf;
+ }
+ off += 5;
+ }
+ else
+ {
+ off = 4;
+ }
+ message("%s", statmsg + off);
+ if (status == EX_TEMPFAIL && e->e_xfp != NULL)
+ (void) sm_io_fprintf(e->e_xfp, SM_TIME_DEFAULT, "%s\n",
+ &MsgBuf[4]);
+ }
+ else
+ {
+ char mbuf[ENHSCLEN + 4];
+
+ Errors++;
+ if ((off = isenhsc(statmsg + 4, ' ')) > 0 &&
+ off < sizeof mbuf - 4)
+ {
+ if (dsn == NULL)
+ {
+ (void) sm_snprintf(dsnbuf, sizeof dsnbuf,
+ "%.*s", off, statmsg + 4);
+ dsn = dsnbuf;
+ }
+ off += 5;
+
+ /* copy only part of statmsg to mbuf */
+ (void) sm_strlcpy(mbuf, statmsg, off);
+ (void) sm_strlcat(mbuf, " %s", sizeof mbuf);
+ }
+ else
+ {
+ dsnbuf[0] = '\0';
+ (void) sm_snprintf(mbuf, sizeof mbuf, "%.3s %%s",
+ statmsg);
+ off = 4;
+ }
+ usrerr(mbuf, &statmsg[off]);
+ }
+
+ /*
+ ** Final cleanup.
+ ** Log a record of the transaction. Compute the new
+ ** ExitStat -- if we already had an error, stick with
+ ** that.
+ */
+
+ if (OpMode != MD_VERIFY && !bitset(EF_VRFYONLY, e->e_flags) &&
+ LogLevel > ((status == EX_TEMPFAIL) ? 8 : (status == EX_OK) ? 7 : 6))
+ logdelivery(m, mci, dsn, statmsg + off, ctladdr, xstart, e);
+
+ if (tTd(11, 2))
+ sm_dprintf("giveresponse: status=%d, dsn=%s, e->e_message=%s, errnum=%d\n",
+ status,
+ dsn == NULL ? "<NULL>" : dsn,
+ e->e_message == NULL ? "<NULL>" : e->e_message,
+ errnum);
+
+ if (status != EX_TEMPFAIL)
+ setstat(status);
+ if (status != EX_OK && (status != EX_TEMPFAIL || e->e_message == NULL))
+ e->e_message = sm_rpool_strdup_x(e->e_rpool, statmsg + off);
+ if (status != EX_OK && to != NULL && to->q_message == NULL)
+ {
+ if (!usestat && e->e_message != NULL)
+ to->q_message = sm_rpool_strdup_x(e->e_rpool,
+ e->e_message);
+ else
+ to->q_message = sm_rpool_strdup_x(e->e_rpool,
+ statmsg + off);
+ }
+ errno = 0;
+ SM_SET_H_ERRNO(0);
+}
+/*
+** LOGDELIVERY -- log the delivery in the system log
+**
+** Care is taken to avoid logging lines that are too long, because
+** some versions of syslog have an unfortunate proclivity for core
+** dumping. This is a hack, to be sure, that is at best empirical.
+**
+** Parameters:
+** m -- the mailer info. Can be NULL for initial queue.
+** mci -- the mailer connection info -- can be NULL if the
+** log is occurring when no connection is active.
+** dsn -- the DSN attached to the status.
+** status -- the message to print for the status.
+** ctladdr -- the controlling address for the to list.
+** xstart -- the transaction start time, used for
+** computing transaction delay.
+** e -- the current envelope.
+**
+** Returns:
+** none
+**
+** Side Effects:
+** none
+*/
+
+void
+logdelivery(m, mci, dsn, status, ctladdr, xstart, e)
+ MAILER *m;
+ register MCI *mci;
+ char *dsn;
+ const char *status;
+ ADDRESS *ctladdr;
+ time_t xstart;
+ register ENVELOPE *e;
+{
+ register char *bp;
+ register char *p;
+ int l;
+ time_t now = curtime();
+ char buf[1024];
+
+#if (SYSLOG_BUFSIZE) >= 256
+ /* ctladdr: max 106 bytes */
+ bp = buf;
+ if (ctladdr != NULL)
+ {
+ (void) sm_strlcpyn(bp, SPACELEFT(buf, bp), 2, ", ctladdr=",
+ shortenstring(ctladdr->q_paddr, 83));
+ bp += strlen(bp);
+ if (bitset(QGOODUID, ctladdr->q_flags))
+ {
+ (void) sm_snprintf(bp, SPACELEFT(buf, bp), " (%d/%d)",
+ (int) ctladdr->q_uid,
+ (int) ctladdr->q_gid);
+ bp += strlen(bp);
+ }
+ }
+
+ /* delay & xdelay: max 41 bytes */
+ (void) sm_strlcpyn(bp, SPACELEFT(buf, bp), 2, ", delay=",
+ pintvl(now - e->e_ctime, true));
+ bp += strlen(bp);
+
+ if (xstart != (time_t) 0)
+ {
+ (void) sm_strlcpyn(bp, SPACELEFT(buf, bp), 2, ", xdelay=",
+ pintvl(now - xstart, true));
+ bp += strlen(bp);
+ }
+
+ /* mailer: assume about 19 bytes (max 10 byte mailer name) */
+ if (m != NULL)
+ {
+ (void) sm_strlcpyn(bp, SPACELEFT(buf, bp), 2, ", mailer=",
+ m->m_name);
+ bp += strlen(bp);
+ }
+
+ /* pri: changes with each delivery attempt */
+ (void) sm_snprintf(bp, SPACELEFT(buf, bp), ", pri=%ld",
+ e->e_msgpriority);
+ bp += strlen(bp);
+
+ /* relay: max 66 bytes for IPv4 addresses */
+ if (mci != NULL && mci->mci_host != NULL)
+ {
+ extern SOCKADDR CurHostAddr;
+
+ (void) sm_strlcpyn(bp, SPACELEFT(buf, bp), 2, ", relay=",
+ shortenstring(mci->mci_host, 40));
+ bp += strlen(bp);
+
+ if (CurHostAddr.sa.sa_family != 0)
+ {
+ (void) sm_snprintf(bp, SPACELEFT(buf, bp), " [%s]",
+ anynet_ntoa(&CurHostAddr));
+ }
+ }
+ else if (strcmp(status, "quarantined") == 0)
+ {
+ if (e->e_quarmsg != NULL)
+ (void) sm_snprintf(bp, SPACELEFT(buf, bp),
+ ", quarantine=%s",
+ shortenstring(e->e_quarmsg, 40));
+ }
+ else if (strcmp(status, "queued") != 0)
+ {
+ p = macvalue('h', e);
+ if (p != NULL && p[0] != '\0')
+ {
+ (void) sm_snprintf(bp, SPACELEFT(buf, bp),
+ ", relay=%s", shortenstring(p, 40));
+ }
+ }
+ bp += strlen(bp);
+
+ /* dsn */
+ if (dsn != NULL && *dsn != '\0')
+ {
+ (void) sm_strlcpyn(bp, SPACELEFT(buf, bp), 2, ", dsn=",
+ shortenstring(dsn, ENHSCLEN));
+ bp += strlen(bp);
+ }
+
+#if _FFR_LOG_NTRIES
+ /* ntries */
+ if (e->e_ntries >= 0)
+ {
+ (void) sm_snprintf(bp, SPACELEFT(buf, bp),
+ ", ntries=%d", e->e_ntries + 1);
+ bp += strlen(bp);
+ }
+#endif /* _FFR_LOG_NTRIES */
+
+# define STATLEN (((SYSLOG_BUFSIZE) - 100) / 4)
+# if (STATLEN) < 63
+# undef STATLEN
+# define STATLEN 63
+# endif /* (STATLEN) < 63 */
+# if (STATLEN) > 203
+# undef STATLEN
+# define STATLEN 203
+# endif /* (STATLEN) > 203 */
+
+ /* stat: max 210 bytes */
+ if ((bp - buf) > (sizeof buf - ((STATLEN) + 20)))
+ {
+ /* desperation move -- truncate data */
+ bp = buf + sizeof buf - ((STATLEN) + 17);
+ (void) sm_strlcpy(bp, "...", SPACELEFT(buf, bp));
+ bp += 3;
+ }
+
+ (void) sm_strlcpy(bp, ", stat=", SPACELEFT(buf, bp));
+ bp += strlen(bp);
+
+ (void) sm_strlcpy(bp, shortenstring(status, STATLEN),
+ SPACELEFT(buf, bp));
+
+ /* id, to: max 13 + TOBUFSIZE bytes */
+ l = SYSLOG_BUFSIZE - 100 - strlen(buf);
+ if (l < 0)
+ l = 0;
+ p = e->e_to == NULL ? "NO-TO-LIST" : e->e_to;
+ while (strlen(p) >= l)
+ {
+ register char *q;
+
+ for (q = p + l; q > p; q--)
+ {
+ if (*q == ',')
+ break;
+ }
+ if (p == q)
+ break;
+ sm_syslog(LOG_INFO, e->e_id, "to=%.*s [more]%s",
+ (int) (++q - p), p, buf);
+ p = q;
+ }
+ sm_syslog(LOG_INFO, e->e_id, "to=%.*s%s", l, p, buf);
+
+#else /* (SYSLOG_BUFSIZE) >= 256 */
+
+ l = SYSLOG_BUFSIZE - 85;
+ if (l < 0)
+ l = 0;
+ p = e->e_to == NULL ? "NO-TO-LIST" : e->e_to;
+ while (strlen(p) >= l)
+ {
+ register char *q;
+
+ for (q = p + l; q > p; q--)
+ {
+ if (*q == ',')
+ break;
+ }
+ if (p == q)
+ break;
+
+ sm_syslog(LOG_INFO, e->e_id, "to=%.*s [more]",
+ (int) (++q - p), p);
+ p = q;
+ }
+ sm_syslog(LOG_INFO, e->e_id, "to=%.*s", l, p);
+
+ if (ctladdr != NULL)
+ {
+ bp = buf;
+ (void) sm_strlcpyn(bp, SPACELEFT(buf, bp), 2, "ctladdr=",
+ shortenstring(ctladdr->q_paddr, 83));
+ bp += strlen(bp);
+ if (bitset(QGOODUID, ctladdr->q_flags))
+ {
+ (void) sm_snprintf(bp, SPACELEFT(buf, bp), " (%d/%d)",
+ ctladdr->q_uid, ctladdr->q_gid);
+ bp += strlen(bp);
+ }
+ sm_syslog(LOG_INFO, e->e_id, "%s", buf);
+ }
+ bp = buf;
+ (void) sm_strlcpyn(bp, SPACELEFT(buf, bp), 2, "delay=",
+ pintvl(now - e->e_ctime, true));
+ bp += strlen(bp);
+ if (xstart != (time_t) 0)
+ {
+ (void) sm_strlcpyn(bp, SPACELEFT(buf, bp), 2, ", xdelay=",
+ pintvl(now - xstart, true));
+ bp += strlen(bp);
+ }
+
+ if (m != NULL)
+ {
+ (void) sm_strlcpyn(bp, SPACELEFT(buf, bp), 2, ", mailer=",
+ m->m_name);
+ bp += strlen(bp);
+ }
+ sm_syslog(LOG_INFO, e->e_id, "%.1000s", buf);
+
+ buf[0] = '\0';
+ bp = buf;
+ if (mci != NULL && mci->mci_host != NULL)
+ {
+ extern SOCKADDR CurHostAddr;
+
+ (void) sm_snprintf(bp, SPACELEFT(buf, bp), "relay=%.100s",
+ mci->mci_host);
+ bp += strlen(bp);
+
+ if (CurHostAddr.sa.sa_family != 0)
+ (void) sm_snprintf(bp, SPACELEFT(buf, bp),
+ " [%.100s]",
+ anynet_ntoa(&CurHostAddr));
+ }
+ else if (strcmp(status, "quarantined") == 0)
+ {
+ if (e->e_quarmsg != NULL)
+ (void) sm_snprintf(bp, SPACELEFT(buf, bp),
+ ", quarantine=%.100s",
+ e->e_quarmsg);
+ }
+ else if (strcmp(status, "queued") != 0)
+ {
+ p = macvalue('h', e);
+ if (p != NULL && p[0] != '\0')
+ (void) sm_snprintf(buf, sizeof buf, "relay=%.100s", p);
+ }
+ if (buf[0] != '\0')
+ sm_syslog(LOG_INFO, e->e_id, "%.1000s", buf);
+
+ sm_syslog(LOG_INFO, e->e_id, "stat=%s", shortenstring(status, 63));
+#endif /* (SYSLOG_BUFSIZE) >= 256 */
+}
+/*
+** PUTFROMLINE -- output a UNIX-style from line (or whatever)
+**
+** This can be made an arbitrary message separator by changing $l
+**
+** One of the ugliest hacks seen by human eyes is contained herein:
+** UUCP wants those stupid "remote from <host>" lines. Why oh why
+** does a well-meaning programmer such as myself have to deal with
+** this kind of antique garbage????
+**
+** Parameters:
+** mci -- the connection information.
+** e -- the envelope.
+**
+** Returns:
+** none
+**
+** Side Effects:
+** outputs some text to fp.
+*/
+
+void
+putfromline(mci, e)
+ register MCI *mci;
+ ENVELOPE *e;
+{
+ char *template = UnixFromLine;
+ char buf[MAXLINE];
+ char xbuf[MAXLINE];
+
+ if (bitnset(M_NHDR, mci->mci_mailer->m_flags))
+ return;
+
+ mci->mci_flags |= MCIF_INHEADER;
+
+ if (bitnset(M_UGLYUUCP, mci->mci_mailer->m_flags))
+ {
+ char *bang;
+
+ expand("\201g", buf, sizeof buf, e);
+ bang = strchr(buf, '!');
+ if (bang == NULL)
+ {
+ char *at;
+ char hname[MAXNAME];
+
+ /*
+ ** If we can construct a UUCP path, do so
+ */
+
+ at = strrchr(buf, '@');
+ if (at == NULL)
+ {
+ expand("\201k", hname, sizeof hname, e);
+ at = hname;
+ }
+ else
+ *at++ = '\0';
+ (void) sm_snprintf(xbuf, sizeof xbuf,
+ "From %.800s \201d remote from %.100s\n",
+ buf, at);
+ }
+ else
+ {
+ *bang++ = '\0';
+ (void) sm_snprintf(xbuf, sizeof xbuf,
+ "From %.800s \201d remote from %.100s\n",
+ bang, buf);
+ template = xbuf;
+ }
+ }
+ expand(template, buf, sizeof buf, e);
+ putxline(buf, strlen(buf), mci, PXLF_HEADER);
+}
+/*
+** PUTBODY -- put the body of a message.
+**
+** Parameters:
+** mci -- the connection information.
+** e -- the envelope to put out.
+** separator -- if non-NULL, a message separator that must
+** not be permitted in the resulting message.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** The message is written onto fp.
+*/
+
+/* values for output state variable */
+#define OS_HEAD 0 /* at beginning of line */
+#define OS_CR 1 /* read a carriage return */
+#define OS_INLINE 2 /* putting rest of line */
+
+void
+putbody(mci, e, separator)
+ register MCI *mci;
+ register ENVELOPE *e;
+ char *separator;
+{
+ bool dead = false;
+ char buf[MAXLINE];
+#if MIME8TO7
+ char *boundaries[MAXMIMENESTING + 1];
+#endif /* MIME8TO7 */
+
+ /*
+ ** Output the body of the message
+ */
+
+ if (e->e_dfp == NULL && bitset(EF_HAS_DF, e->e_flags))
+ {
+ char *df = queuename(e, DATAFL_LETTER);
+
+ e->e_dfp = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, df,
+ SM_IO_RDONLY_B, NULL);
+ if (e->e_dfp == NULL)
+ {
+ char *msg = "!putbody: Cannot open %s for %s from %s";
+
+ if (errno == ENOENT)
+ msg++;
+ syserr(msg, df, e->e_to, e->e_from.q_paddr);
+ }
+
+ }
+ if (e->e_dfp == NULL)
+ {
+ if (bitset(MCIF_INHEADER, mci->mci_flags))
+ {
+ putline("", mci);
+ mci->mci_flags &= ~MCIF_INHEADER;
+ }
+ putline("<<< No Message Collected >>>", mci);
+ goto endofmessage;
+ }
+
+ if (e->e_dfino == (ino_t) 0)
+ {
+ struct stat stbuf;
+
+ if (fstat(sm_io_getinfo(e->e_dfp, SM_IO_WHAT_FD, NULL), &stbuf)
+ < 0)
+ e->e_dfino = -1;
+ else
+ {
+ e->e_dfdev = stbuf.st_dev;
+ e->e_dfino = stbuf.st_ino;
+ }
+ }
+
+ /* paranoia: the data file should always be in a rewound state */
+ (void) bfrewind(e->e_dfp);
+
+#if MIME8TO7
+ if (bitset(MCIF_CVT8TO7, mci->mci_flags))
+ {
+ /*
+ ** Do 8 to 7 bit MIME conversion.
+ */
+
+ /* make sure it looks like a MIME message */
+ if (hvalue("MIME-Version", e->e_header) == NULL)
+ putline("MIME-Version: 1.0", mci);
+
+ if (hvalue("Content-Type", e->e_header) == NULL)
+ {
+ (void) sm_snprintf(buf, sizeof buf,
+ "Content-Type: text/plain; charset=%s",
+ defcharset(e));
+ putline(buf, mci);
+ }
+
+ /* now do the hard work */
+ boundaries[0] = NULL;
+ mci->mci_flags |= MCIF_INHEADER;
+ (void) mime8to7(mci, e->e_header, e, boundaries, M87F_OUTER);
+ }
+# if MIME7TO8
+ else if (bitset(MCIF_CVT7TO8, mci->mci_flags))
+ {
+ (void) mime7to8(mci, e->e_header, e);
+ }
+# endif /* MIME7TO8 */
+ else if (MaxMimeHeaderLength > 0 || MaxMimeFieldLength > 0)
+ {
+ bool oldsuprerrs = SuprErrs;
+
+ /* Use mime8to7 to check multipart for MIME header overflows */
+ boundaries[0] = NULL;
+ mci->mci_flags |= MCIF_INHEADER;
+
+ /*
+ ** If EF_DONT_MIME is set, we have a broken MIME message
+ ** and don't want to generate a new bounce message whose
+ ** body propagates the broken MIME. We can't just not call
+ ** mime8to7() as is done above since we need the security
+ ** checks. The best we can do is suppress the errors.
+ */
+
+ if (bitset(EF_DONT_MIME, e->e_flags))
+ SuprErrs = true;
+
+ (void) mime8to7(mci, e->e_header, e, boundaries,
+ M87F_OUTER|M87F_NO8TO7);
+
+ /* restore SuprErrs */
+ SuprErrs = oldsuprerrs;
+ }
+ else
+#endif /* MIME8TO7 */
+ {
+ int ostate;
+ register char *bp;
+ register char *pbp;
+ register int c;
+ register char *xp;
+ int padc;
+ char *buflim;
+ int pos = 0;
+ char peekbuf[12];
+
+ if (bitset(MCIF_INHEADER, mci->mci_flags))
+ {
+ putline("", mci);
+ mci->mci_flags &= ~MCIF_INHEADER;
+ }
+
+ /* determine end of buffer; allow for short mailer lines */
+ buflim = &buf[sizeof buf - 1];
+ if (mci->mci_mailer->m_linelimit > 0 &&
+ mci->mci_mailer->m_linelimit < sizeof buf - 1)
+ buflim = &buf[mci->mci_mailer->m_linelimit - 1];
+
+ /* copy temp file to output with mapping */
+ ostate = OS_HEAD;
+ bp = buf;
+ pbp = peekbuf;
+ while (!sm_io_error(mci->mci_out) && !dead)
+ {
+ if (pbp > peekbuf)
+ c = *--pbp;
+ else if ((c = sm_io_getc(e->e_dfp, SM_TIME_DEFAULT))
+ == SM_IO_EOF)
+ break;
+ if (bitset(MCIF_7BIT, mci->mci_flags))
+ c &= 0x7f;
+ switch (ostate)
+ {
+ case OS_HEAD:
+ if (c == '\0' &&
+ bitnset(M_NONULLS,
+ mci->mci_mailer->m_flags))
+ break;
+ if (c != '\r' && c != '\n' && bp < buflim)
+ {
+ *bp++ = c;
+ break;
+ }
+
+ /* check beginning of line for special cases */
+ *bp = '\0';
+ pos = 0;
+ padc = SM_IO_EOF;
+ if (buf[0] == 'F' &&
+ bitnset(M_ESCFROM, mci->mci_mailer->m_flags)
+ && strncmp(buf, "From ", 5) == 0)
+ {
+ padc = '>';
+ }
+ if (buf[0] == '-' && buf[1] == '-' &&
+ separator != NULL)
+ {
+ /* possible separator */
+ int sl = strlen(separator);
+
+ if (strncmp(&buf[2], separator, sl)
+ == 0)
+ padc = ' ';
+ }
+ if (buf[0] == '.' &&
+ bitnset(M_XDOT, mci->mci_mailer->m_flags))
+ {
+ padc = '.';
+ }
+
+ /* now copy out saved line */
+ if (TrafficLogFile != NULL)
+ {
+ (void) sm_io_fprintf(TrafficLogFile,
+ SM_TIME_DEFAULT,
+ "%05d >>> ",
+ (int) CurrentPid);
+ if (padc != SM_IO_EOF)
+ (void) sm_io_putc(TrafficLogFile,
+ SM_TIME_DEFAULT,
+ padc);
+ for (xp = buf; xp < bp; xp++)
+ (void) sm_io_putc(TrafficLogFile,
+ SM_TIME_DEFAULT,
+ (unsigned char) *xp);
+ if (c == '\n')
+ (void) sm_io_fputs(TrafficLogFile,
+ SM_TIME_DEFAULT,
+ mci->mci_mailer->m_eol);
+ }
+ if (padc != SM_IO_EOF)
+ {
+ if (sm_io_putc(mci->mci_out,
+ SM_TIME_DEFAULT, padc)
+ == SM_IO_EOF)
+ {
+ dead = true;
+ continue;
+ }
+ else
+ {
+ /* record progress for DATA timeout */
+ DataProgress = true;
+ }
+ pos++;
+ }
+ for (xp = buf; xp < bp; xp++)
+ {
+ if (sm_io_putc(mci->mci_out,
+ SM_TIME_DEFAULT,
+ (unsigned char) *xp)
+ == SM_IO_EOF)
+ {
+ dead = true;
+ break;
+ }
+ else
+ {
+ /* record progress for DATA timeout */
+ DataProgress = true;
+ }
+ }
+ if (dead)
+ continue;
+ if (c == '\n')
+ {
+ if (sm_io_fputs(mci->mci_out,
+ SM_TIME_DEFAULT,
+ mci->mci_mailer->m_eol)
+ == SM_IO_EOF)
+ break;
+ else
+ {
+ /* record progress for DATA timeout */
+ DataProgress = true;
+ }
+ pos = 0;
+ }
+ else
+ {
+ pos += bp - buf;
+ if (c != '\r')
+ {
+ SM_ASSERT(pbp < peekbuf +
+ sizeof(peekbuf));
+ *pbp++ = c;
+ }
+ }
+
+ bp = buf;
+
+ /* determine next state */
+ if (c == '\n')
+ ostate = OS_HEAD;
+ else if (c == '\r')
+ ostate = OS_CR;
+ else
+ ostate = OS_INLINE;
+ continue;
+
+ case OS_CR:
+ if (c == '\n')
+ {
+ /* got CRLF */
+ if (sm_io_fputs(mci->mci_out,
+ SM_TIME_DEFAULT,
+ mci->mci_mailer->m_eol)
+ == SM_IO_EOF)
+ continue;
+ else
+ {
+ /* record progress for DATA timeout */
+ DataProgress = true;
+ }
+
+ if (TrafficLogFile != NULL)
+ {
+ (void) sm_io_fputs(TrafficLogFile,
+ SM_TIME_DEFAULT,
+ mci->mci_mailer->m_eol);
+ }
+ ostate = OS_HEAD;
+ continue;
+ }
+
+ /* had a naked carriage return */
+ SM_ASSERT(pbp < peekbuf + sizeof(peekbuf));
+ *pbp++ = c;
+ c = '\r';
+ ostate = OS_INLINE;
+ goto putch;
+
+ case OS_INLINE:
+ if (c == '\r')
+ {
+ ostate = OS_CR;
+ continue;
+ }
+ if (c == '\0' &&
+ bitnset(M_NONULLS,
+ mci->mci_mailer->m_flags))
+ break;
+putch:
+ if (mci->mci_mailer->m_linelimit > 0 &&
+ pos >= mci->mci_mailer->m_linelimit - 1 &&
+ c != '\n')
+ {
+ int d;
+
+ /* check next character for EOL */
+ if (pbp > peekbuf)
+ d = *(pbp - 1);
+ else if ((d = sm_io_getc(e->e_dfp,
+ SM_TIME_DEFAULT))
+ != SM_IO_EOF)
+ {
+ SM_ASSERT(pbp < peekbuf +
+ sizeof(peekbuf));
+ *pbp++ = d;
+ }
+
+ if (d == '\n' || d == SM_IO_EOF)
+ {
+ if (TrafficLogFile != NULL)
+ (void) sm_io_putc(TrafficLogFile,
+ SM_TIME_DEFAULT,
+ (unsigned char) c);
+ if (sm_io_putc(mci->mci_out,
+ SM_TIME_DEFAULT,
+ (unsigned char) c)
+ == SM_IO_EOF)
+ {
+ dead = true;
+ continue;
+ }
+ else
+ {
+ /* record progress for DATA timeout */
+ DataProgress = true;
+ }
+ pos++;
+ continue;
+ }
+
+ if (sm_io_putc(mci->mci_out,
+ SM_TIME_DEFAULT, '!')
+ == SM_IO_EOF ||
+ sm_io_fputs(mci->mci_out,
+ SM_TIME_DEFAULT,
+ mci->mci_mailer->m_eol)
+ == SM_IO_EOF)
+ {
+ dead = true;
+ continue;
+ }
+ else
+ {
+ /* record progress for DATA timeout */
+ DataProgress = true;
+ }
+
+ if (TrafficLogFile != NULL)
+ {
+ (void) sm_io_fprintf(TrafficLogFile,
+ SM_TIME_DEFAULT,
+ "!%s",
+ mci->mci_mailer->m_eol);
+ }
+ ostate = OS_HEAD;
+ SM_ASSERT(pbp < peekbuf +
+ sizeof(peekbuf));
+ *pbp++ = c;
+ continue;
+ }
+ if (c == '\n')
+ {
+ if (TrafficLogFile != NULL)
+ (void) sm_io_fputs(TrafficLogFile,
+ SM_TIME_DEFAULT,
+ mci->mci_mailer->m_eol);
+ if (sm_io_fputs(mci->mci_out,
+ SM_TIME_DEFAULT,
+ mci->mci_mailer->m_eol)
+ == SM_IO_EOF)
+ continue;
+ else
+ {
+ /* record progress for DATA timeout */
+ DataProgress = true;
+ }
+ pos = 0;
+ ostate = OS_HEAD;
+ }
+ else
+ {
+ if (TrafficLogFile != NULL)
+ (void) sm_io_putc(TrafficLogFile,
+ SM_TIME_DEFAULT,
+ (unsigned char) c);
+ if (sm_io_putc(mci->mci_out,
+ SM_TIME_DEFAULT,
+ (unsigned char) c)
+ == SM_IO_EOF)
+ {
+ dead = true;
+ continue;
+ }
+ else
+ {
+ /* record progress for DATA timeout */
+ DataProgress = true;
+ }
+ pos++;
+ ostate = OS_INLINE;
+ }
+ break;
+ }
+ }
+
+ /* make sure we are at the beginning of a line */
+ if (bp > buf)
+ {
+ if (TrafficLogFile != NULL)
+ {
+ for (xp = buf; xp < bp; xp++)
+ (void) sm_io_putc(TrafficLogFile,
+ SM_TIME_DEFAULT,
+ (unsigned char) *xp);
+ }
+ for (xp = buf; xp < bp; xp++)
+ {
+ if (sm_io_putc(mci->mci_out, SM_TIME_DEFAULT,
+ (unsigned char) *xp)
+ == SM_IO_EOF)
+ {
+ dead = true;
+ break;
+ }
+ else
+ {
+ /* record progress for DATA timeout */
+ DataProgress = true;
+ }
+ }
+ pos += bp - buf;
+ }
+ if (!dead && pos > 0)
+ {
+ if (TrafficLogFile != NULL)
+ (void) sm_io_fputs(TrafficLogFile,
+ SM_TIME_DEFAULT,
+ mci->mci_mailer->m_eol);
+ (void) sm_io_fputs(mci->mci_out, SM_TIME_DEFAULT,
+ mci->mci_mailer->m_eol);
+
+ /* record progress for DATA timeout */
+ DataProgress = true;
+ }
+ }
+
+ if (sm_io_error(e->e_dfp))
+ {
+ syserr("putbody: %s/%cf%s: read error",
+ qid_printqueue(e->e_dfqgrp, e->e_dfqdir),
+ DATAFL_LETTER, e->e_id);
+ ExitStat = EX_IOERR;
+ }
+
+endofmessage:
+ /*
+ ** Since mailfile() uses e_dfp in a child process,
+ ** the file offset in the stdio library for the
+ ** parent process will not agree with the in-kernel
+ ** file offset since the file descriptor is shared
+ ** between the processes. Therefore, it is vital
+ ** that the file always be rewound. This forces the
+ ** kernel offset (lseek) and stdio library (ftell)
+ ** offset to match.
+ */
+
+ if (e->e_dfp != NULL)
+ (void) bfrewind(e->e_dfp);
+
+ /* some mailers want extra blank line at end of message */
+ if (!dead && bitnset(M_BLANKEND, mci->mci_mailer->m_flags) &&
+ buf[0] != '\0' && buf[0] != '\n')
+ putline("", mci);
+
+ (void) sm_io_flush(mci->mci_out, SM_TIME_DEFAULT);
+ if (sm_io_error(mci->mci_out) && errno != EPIPE)
+ {
+ syserr("putbody: write error");
+ ExitStat = EX_IOERR;
+ }
+
+ errno = 0;
+}
+/*
+** MAILFILE -- Send a message to a file.
+**
+** If the file has the set-user-ID/set-group-ID bits set, but NO
+** execute bits, sendmail will try to become the owner of that file
+** rather than the real user. Obviously, this only works if
+** sendmail runs as root.
+**
+** This could be done as a subordinate mailer, except that it
+** is used implicitly to save messages in ~/dead.letter. We
+** view this as being sufficiently important as to include it
+** here. For example, if the system is dying, we shouldn't have
+** to create another process plus some pipes to save the message.
+**
+** Parameters:
+** filename -- the name of the file to send to.
+** mailer -- mailer definition for recipient -- if NULL,
+** use FileMailer.
+** ctladdr -- the controlling address header -- includes
+** the userid/groupid to be when sending.
+** sfflags -- flags for opening.
+** e -- the current envelope.
+**
+** Returns:
+** The exit code associated with the operation.
+**
+** Side Effects:
+** none.
+*/
+
+# define RETURN(st) exit(st);
+
+static jmp_buf CtxMailfileTimeout;
+
+int
+mailfile(filename, mailer, ctladdr, sfflags, e)
+ char *volatile filename;
+ MAILER *volatile mailer;
+ ADDRESS *ctladdr;
+ volatile long sfflags;
+ register ENVELOPE *e;
+{
+ register SM_FILE_T *f;
+ register pid_t pid = -1;
+ volatile int mode;
+ int len;
+ off_t curoff;
+ bool suidwarn = geteuid() == 0;
+ char *p;
+ char *volatile realfile;
+ SM_EVENT *ev;
+ char buf[MAXPATHLEN];
+ char targetfile[MAXPATHLEN];
+
+ if (tTd(11, 1))
+ {
+ sm_dprintf("mailfile %s\n ctladdr=", filename);
+ printaddr(sm_debug_file(), ctladdr, false);
+ }
+
+ if (mailer == NULL)
+ mailer = FileMailer;
+
+ if (e->e_xfp != NULL)
+ (void) sm_io_flush(e->e_xfp, SM_TIME_DEFAULT);
+
+ /*
+ ** Special case /dev/null. This allows us to restrict file
+ ** delivery to regular files only.
+ */
+
+ if (sm_path_isdevnull(filename))
+ return EX_OK;
+
+ /* check for 8-bit available */
+ if (bitset(EF_HAS8BIT, e->e_flags) &&
+ bitnset(M_7BITS, mailer->m_flags) &&
+ (bitset(EF_DONT_MIME, e->e_flags) ||
+ !(bitset(MM_MIME8BIT, MimeMode) ||
+ (bitset(EF_IS_MIME, e->e_flags) &&
+ bitset(MM_CVTMIME, MimeMode)))))
+ {
+ e->e_status = "5.6.3";
+ usrerrenh(e->e_status,
+ "554 Cannot send 8-bit data to 7-bit destination");
+ errno = 0;
+ return EX_DATAERR;
+ }
+
+ /* Find the actual file */
+ if (SafeFileEnv != NULL && SafeFileEnv[0] != '\0')
+ {
+ len = strlen(SafeFileEnv);
+
+ if (strncmp(SafeFileEnv, filename, len) == 0)
+ filename += len;
+
+ if (len + strlen(filename) + 1 >= sizeof targetfile)
+ {
+ syserr("mailfile: filename too long (%s/%s)",
+ SafeFileEnv, filename);
+ return EX_CANTCREAT;
+ }
+ (void) sm_strlcpy(targetfile, SafeFileEnv, sizeof targetfile);
+ realfile = targetfile + len;
+ if (*filename == '/')
+ filename++;
+ if (*filename != '\0')
+ {
+ /* paranoia: trailing / should be removed in readcf */
+ if (targetfile[len - 1] != '/')
+ (void) sm_strlcat(targetfile,
+ "/", sizeof targetfile);
+ (void) sm_strlcat(targetfile, filename,
+ sizeof targetfile);
+ }
+ }
+ else if (mailer->m_rootdir != NULL)
+ {
+ expand(mailer->m_rootdir, targetfile, sizeof targetfile, e);
+ len = strlen(targetfile);
+
+ if (strncmp(targetfile, filename, len) == 0)
+ filename += len;
+
+ if (len + strlen(filename) + 1 >= sizeof targetfile)
+ {
+ syserr("mailfile: filename too long (%s/%s)",
+ targetfile, filename);
+ return EX_CANTCREAT;
+ }
+ realfile = targetfile + len;
+ if (targetfile[len - 1] != '/')
+ (void) sm_strlcat(targetfile, "/", sizeof targetfile);
+ if (*filename == '/')
+ (void) sm_strlcat(targetfile, filename + 1,
+ sizeof targetfile);
+ else
+ (void) sm_strlcat(targetfile, filename,
+ sizeof targetfile);
+ }
+ else
+ {
+ if (sm_strlcpy(targetfile, filename, sizeof targetfile) >=
+ sizeof targetfile)
+ {
+ syserr("mailfile: filename too long (%s)", filename);
+ return EX_CANTCREAT;
+ }
+ realfile = targetfile;
+ }
+
+ /*
+ ** Fork so we can change permissions here.
+ ** Note that we MUST use fork, not vfork, because of
+ ** the complications of calling subroutines, etc.
+ */
+
+
+ /*
+ ** Dispose of SIGCHLD signal catchers that may be laying
+ ** around so that the waitfor() below will get it.
+ */
+
+ (void) sm_signal(SIGCHLD, SIG_DFL);
+
+ DOFORK(fork);
+
+ if (pid < 0)
+ return EX_OSERR;
+ else if (pid == 0)
+ {
+ /* child -- actually write to file */
+ struct stat stb;
+ MCI mcibuf;
+ int err;
+ volatile int oflags = O_WRONLY|O_APPEND;
+
+ /* Reset global flags */
+ RestartRequest = NULL;
+ RestartWorkGroup = false;
+ ShutdownRequest = NULL;
+ PendingSignal = 0;
+ CurrentPid = getpid();
+
+ if (e->e_lockfp != NULL)
+ (void) close(sm_io_getinfo(e->e_lockfp, SM_IO_WHAT_FD,
+ NULL));
+
+ (void) sm_signal(SIGINT, SIG_DFL);
+ (void) sm_signal(SIGHUP, SIG_DFL);
+ (void) sm_signal(SIGTERM, SIG_DFL);
+ (void) umask(OldUmask);
+ e->e_to = filename;
+ ExitStat = EX_OK;
+
+ if (setjmp(CtxMailfileTimeout) != 0)
+ {
+ RETURN(EX_TEMPFAIL);
+ }
+
+ if (TimeOuts.to_fileopen > 0)
+ ev = sm_setevent(TimeOuts.to_fileopen, mailfiletimeout,
+ 0);
+ else
+ ev = NULL;
+
+ /* check file mode to see if set-user-ID */
+ if (stat(targetfile, &stb) < 0)
+ mode = FileMode;
+ else
+ mode = stb.st_mode;
+
+ /* limit the errors to those actually caused in the child */
+ errno = 0;
+ ExitStat = EX_OK;
+
+ /* Allow alias expansions to use the S_IS{U,G}ID bits */
+ if ((ctladdr != NULL && !bitset(QALIAS, ctladdr->q_flags)) ||
+ bitset(SFF_RUNASREALUID, sfflags))
+ {
+ /* ignore set-user-ID and set-group-ID bits */
+ mode &= ~(S_ISGID|S_ISUID);
+ if (tTd(11, 20))
+ sm_dprintf("mailfile: ignoring set-user-ID/set-group-ID bits\n");
+ }
+
+ /* we have to open the data file BEFORE setuid() */
+ if (e->e_dfp == NULL && bitset(EF_HAS_DF, e->e_flags))
+ {
+ char *df = queuename(e, DATAFL_LETTER);
+
+ e->e_dfp = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, df,
+ SM_IO_RDONLY_B, NULL);
+ if (e->e_dfp == NULL)
+ {
+ syserr("mailfile: Cannot open %s for %s from %s",
+ df, e->e_to, e->e_from.q_paddr);
+ }
+ }
+
+ /* select a new user to run as */
+ if (!bitset(SFF_RUNASREALUID, sfflags))
+ {
+ if (bitnset(M_SPECIFIC_UID, mailer->m_flags))
+ {
+ RealUserName = NULL;
+ if (mailer->m_uid == NO_UID)
+ RealUid = RunAsUid;
+ else
+ RealUid = mailer->m_uid;
+ if (RunAsUid != 0 && RealUid != RunAsUid)
+ {
+ /* Only root can change the uid */
+ syserr("mailfile: insufficient privileges to change uid, RunAsUid=%d, RealUid=%d",
+ (int) RunAsUid, (int) RealUid);
+ RETURN(EX_TEMPFAIL);
+ }
+ }
+ else if (bitset(S_ISUID, mode))
+ {
+ RealUserName = NULL;
+ RealUid = stb.st_uid;
+ }
+ else if (ctladdr != NULL && ctladdr->q_uid != 0)
+ {
+ if (ctladdr->q_ruser != NULL)
+ RealUserName = ctladdr->q_ruser;
+ else
+ RealUserName = ctladdr->q_user;
+ RealUid = ctladdr->q_uid;
+ }
+ else if (mailer != NULL && mailer->m_uid != NO_UID)
+ {
+ RealUserName = DefUser;
+ RealUid = mailer->m_uid;
+ }
+ else
+ {
+ RealUserName = DefUser;
+ RealUid = DefUid;
+ }
+
+ /* select a new group to run as */
+ if (bitnset(M_SPECIFIC_UID, mailer->m_flags))
+ {
+ if (mailer->m_gid == NO_GID)
+ RealGid = RunAsGid;
+ else
+ RealGid = mailer->m_gid;
+ if (RunAsUid != 0 &&
+ (RealGid != getgid() ||
+ RealGid != getegid()))
+ {
+ /* Only root can change the gid */
+ syserr("mailfile: insufficient privileges to change gid, RealGid=%d, RunAsUid=%d, gid=%d, egid=%d",
+ (int) RealGid, (int) RunAsUid,
+ (int) getgid(), (int) getegid());
+ RETURN(EX_TEMPFAIL);
+ }
+ }
+ else if (bitset(S_ISGID, mode))
+ RealGid = stb.st_gid;
+ else if (ctladdr != NULL &&
+ ctladdr->q_uid == DefUid &&
+ ctladdr->q_gid == 0)
+ {
+ /*
+ ** Special case: This means it is an
+ ** alias and we should act as DefaultUser.
+ ** See alias()'s comments.
+ */
+
+ RealGid = DefGid;
+ RealUserName = DefUser;
+ }
+ else if (ctladdr != NULL && ctladdr->q_uid != 0)
+ RealGid = ctladdr->q_gid;
+ else if (mailer != NULL && mailer->m_gid != NO_GID)
+ RealGid = mailer->m_gid;
+ else
+ RealGid = DefGid;
+ }
+
+ /* last ditch */
+ if (!bitset(SFF_ROOTOK, sfflags))
+ {
+ if (RealUid == 0)
+ RealUid = DefUid;
+ if (RealGid == 0)
+ RealGid = DefGid;
+ }
+
+ /* set group id list (needs /etc/group access) */
+ if (RealUserName != NULL && !DontInitGroups)
+ {
+ if (initgroups(RealUserName, RealGid) == -1 && suidwarn)
+ {
+ syserr("mailfile: initgroups(%s, %d) failed",
+ RealUserName, RealGid);
+ RETURN(EX_TEMPFAIL);
+ }
+ }
+ else
+ {
+ GIDSET_T gidset[1];
+
+ gidset[0] = RealGid;
+ if (setgroups(1, gidset) == -1 && suidwarn)
+ {
+ syserr("mailfile: setgroups() failed");
+ RETURN(EX_TEMPFAIL);
+ }
+ }
+
+ /*
+ ** If you have a safe environment, go into it.
+ */
+
+ if (realfile != targetfile)
+ {
+ char save;
+
+ save = *realfile;
+ *realfile = '\0';
+ if (tTd(11, 20))
+ sm_dprintf("mailfile: chroot %s\n", targetfile);
+ if (chroot(targetfile) < 0)
+ {
+ syserr("mailfile: Cannot chroot(%s)",
+ targetfile);
+ RETURN(EX_CANTCREAT);
+ }
+ *realfile = save;
+ }
+
+ if (tTd(11, 40))
+ sm_dprintf("mailfile: deliver to %s\n", realfile);
+
+ if (chdir("/") < 0)
+ {
+ syserr("mailfile: cannot chdir(/)");
+ RETURN(EX_CANTCREAT);
+ }
+
+ /* now reset the group and user ids */
+ endpwent();
+ sm_mbdb_terminate();
+ if (setgid(RealGid) < 0 && suidwarn)
+ {
+ syserr("mailfile: setgid(%ld) failed", (long) RealGid);
+ RETURN(EX_TEMPFAIL);
+ }
+ vendor_set_uid(RealUid);
+ if (setuid(RealUid) < 0 && suidwarn)
+ {
+ syserr("mailfile: setuid(%ld) failed", (long) RealUid);
+ RETURN(EX_TEMPFAIL);
+ }
+
+ if (tTd(11, 2))
+ sm_dprintf("mailfile: running as r/euid=%d/%d, r/egid=%d/%d\n",
+ (int) getuid(), (int) geteuid(),
+ (int) getgid(), (int) getegid());
+
+
+ /* move into some "safe" directory */
+ if (mailer->m_execdir != NULL)
+ {
+ char *q;
+
+ for (p = mailer->m_execdir; p != NULL; p = q)
+ {
+ q = strchr(p, ':');
+ if (q != NULL)
+ *q = '\0';
+ expand(p, buf, sizeof buf, e);
+ if (q != NULL)
+ *q++ = ':';
+ if (tTd(11, 20))
+ sm_dprintf("mailfile: trydir %s\n",
+ buf);
+ if (buf[0] != '\0' && chdir(buf) >= 0)
+ break;
+ }
+ }
+
+ /*
+ ** Recheck the file after we have assumed the ID of the
+ ** delivery user to make sure we can deliver to it as
+ ** that user. This is necessary if sendmail is running
+ ** as root and the file is on an NFS mount which treats
+ ** root as nobody.
+ */
+
+#if HASLSTAT
+ if (bitnset(DBS_FILEDELIVERYTOSYMLINK, DontBlameSendmail))
+ err = stat(realfile, &stb);
+ else
+ err = lstat(realfile, &stb);
+#else /* HASLSTAT */
+ err = stat(realfile, &stb);
+#endif /* HASLSTAT */
+
+ if (err < 0)
+ {
+ stb.st_mode = ST_MODE_NOFILE;
+ mode = FileMode;
+ oflags |= O_CREAT|O_EXCL;
+ }
+ else if (bitset(S_IXUSR|S_IXGRP|S_IXOTH, mode) ||
+ (!bitnset(DBS_FILEDELIVERYTOHARDLINK,
+ DontBlameSendmail) &&
+ stb.st_nlink != 1) ||
+ (realfile != targetfile && !S_ISREG(mode)))
+ exit(EX_CANTCREAT);
+ else
+ mode = stb.st_mode;
+
+ if (!bitnset(DBS_FILEDELIVERYTOSYMLINK, DontBlameSendmail))
+ sfflags |= SFF_NOSLINK;
+ if (!bitnset(DBS_FILEDELIVERYTOHARDLINK, DontBlameSendmail))
+ sfflags |= SFF_NOHLINK;
+ sfflags &= ~SFF_OPENASROOT;
+ f = safefopen(realfile, oflags, mode, sfflags);
+ if (f == NULL)
+ {
+ if (transienterror(errno))
+ {
+ usrerr("454 4.3.0 cannot open %s: %s",
+ shortenstring(realfile, MAXSHORTSTR),
+ sm_errstring(errno));
+ RETURN(EX_TEMPFAIL);
+ }
+ else
+ {
+ usrerr("554 5.3.0 cannot open %s: %s",
+ shortenstring(realfile, MAXSHORTSTR),
+ sm_errstring(errno));
+ RETURN(EX_CANTCREAT);
+ }
+ }
+ if (filechanged(realfile, sm_io_getinfo(f, SM_IO_WHAT_FD, NULL),
+ &stb))
+ {
+ syserr("554 5.3.0 file changed after open");
+ RETURN(EX_CANTCREAT);
+ }
+ if (fstat(sm_io_getinfo(f, SM_IO_WHAT_FD, NULL), &stb) < 0)
+ {
+ syserr("554 5.3.0 cannot fstat %s",
+ sm_errstring(errno));
+ RETURN(EX_CANTCREAT);
+ }
+
+ curoff = stb.st_size;
+
+ if (ev != NULL)
+ sm_clrevent(ev);
+
+ memset(&mcibuf, '\0', sizeof mcibuf);
+ mcibuf.mci_mailer = mailer;
+ mcibuf.mci_out = f;
+ if (bitnset(M_7BITS, mailer->m_flags))
+ mcibuf.mci_flags |= MCIF_7BIT;
+
+ /* clear out per-message flags from connection structure */
+ mcibuf.mci_flags &= ~(MCIF_CVT7TO8|MCIF_CVT8TO7);
+
+ if (bitset(EF_HAS8BIT, e->e_flags) &&
+ !bitset(EF_DONT_MIME, e->e_flags) &&
+ bitnset(M_7BITS, mailer->m_flags))
+ mcibuf.mci_flags |= MCIF_CVT8TO7;
+
+#if MIME7TO8
+ if (bitnset(M_MAKE8BIT, mailer->m_flags) &&
+ !bitset(MCIF_7BIT, mcibuf.mci_flags) &&
+ (p = hvalue("Content-Transfer-Encoding", e->e_header)) != NULL &&
+ (sm_strcasecmp(p, "quoted-printable") == 0 ||
+ sm_strcasecmp(p, "base64") == 0) &&
+ (p = hvalue("Content-Type", e->e_header)) != NULL)
+ {
+ /* may want to convert 7 -> 8 */
+ /* XXX should really parse it here -- and use a class XXX */
+ if (sm_strncasecmp(p, "text/plain", 10) == 0 &&
+ (p[10] == '\0' || p[10] == ' ' || p[10] == ';'))
+ mcibuf.mci_flags |= MCIF_CVT7TO8;
+ }
+#endif /* MIME7TO8 */
+
+ putfromline(&mcibuf, e);
+ (*e->e_puthdr)(&mcibuf, e->e_header, e, M87F_OUTER);
+ (*e->e_putbody)(&mcibuf, e, NULL);
+ putline("\n", &mcibuf);
+ if (sm_io_flush(f, SM_TIME_DEFAULT) != 0 ||
+ (SuperSafe != SAFE_NO &&
+ fsync(sm_io_getinfo(f, SM_IO_WHAT_FD, NULL)) < 0) ||
+ sm_io_error(f))
+ {
+ setstat(EX_IOERR);
+#if !NOFTRUNCATE
+ (void) ftruncate(sm_io_getinfo(f, SM_IO_WHAT_FD, NULL),
+ curoff);
+#endif /* !NOFTRUNCATE */
+ }
+
+ /* reset ISUID & ISGID bits for paranoid systems */
+#if HASFCHMOD
+ (void) fchmod(sm_io_getinfo(f, SM_IO_WHAT_FD, NULL),
+ (MODE_T) mode);
+#else /* HASFCHMOD */
+ (void) chmod(filename, (MODE_T) mode);
+#endif /* HASFCHMOD */
+ if (sm_io_close(f, SM_TIME_DEFAULT) < 0)
+ setstat(EX_IOERR);
+ (void) sm_io_flush(smioout, SM_TIME_DEFAULT);
+ (void) setuid(RealUid);
+ exit(ExitStat);
+ /* NOTREACHED */
+ }
+ else
+ {
+ /* parent -- wait for exit status */
+ int st;
+
+ st = waitfor(pid);
+ if (st == -1)
+ {
+ syserr("mailfile: %s: wait", mailer->m_name);
+ return EX_SOFTWARE;
+ }
+ if (WIFEXITED(st))
+ {
+ errno = 0;
+ return (WEXITSTATUS(st));
+ }
+ else
+ {
+ syserr("mailfile: %s: child died on signal %d",
+ mailer->m_name, st);
+ return EX_UNAVAILABLE;
+ }
+ /* NOTREACHED */
+ }
+ return EX_UNAVAILABLE; /* avoid compiler warning on IRIX */
+}
+
+static void
+mailfiletimeout(ignore)
+ int ignore;
+{
+ /*
+ ** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+ ** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+ ** DOING.
+ */
+
+ errno = ETIMEDOUT;
+ longjmp(CtxMailfileTimeout, 1);
+}
+/*
+** HOSTSIGNATURE -- return the "signature" for a host.
+**
+** The signature describes how we are going to send this -- it
+** can be just the hostname (for non-Internet hosts) or can be
+** an ordered list of MX hosts.
+**
+** Parameters:
+** m -- the mailer describing this host.
+** host -- the host name.
+**
+** Returns:
+** The signature for this host.
+**
+** Side Effects:
+** Can tweak the symbol table.
+*/
+
+#define MAXHOSTSIGNATURE 8192 /* max len of hostsignature */
+
+char *
+hostsignature(m, host)
+ register MAILER *m;
+ char *host;
+{
+ register char *p;
+ register STAB *s;
+ time_t now;
+#if NAMED_BIND
+ char sep = ':';
+ char prevsep = ':';
+ int i;
+ int len;
+ int nmx;
+ int hl;
+ char *hp;
+ char *endp;
+ int oldoptions = _res.options;
+ char *mxhosts[MAXMXHOSTS + 1];
+ unsigned short mxprefs[MAXMXHOSTS + 1];
+#endif /* NAMED_BIND */
+
+ if (tTd(17, 3))
+ sm_dprintf("hostsignature(%s)\n", host);
+
+ /*
+ ** If local delivery (and not remote), just return a constant.
+ */
+
+ if (bitnset(M_LOCALMAILER, m->m_flags) &&
+ strcmp(m->m_mailer, "[IPC]") != 0 &&
+ !(m->m_argv[0] != NULL && strcmp(m->m_argv[0], "TCP") == 0))
+ return "localhost";
+
+ /* an empty host does not have MX records */
+ if (*host == '\0')
+ return "_empty_";
+
+ /*
+ ** Check to see if this uses IPC -- if not, it can't have MX records.
+ */
+
+ if (strcmp(m->m_mailer, "[IPC]") != 0 ||
+ CurEnv->e_sendmode == SM_DEFER)
+ {
+ /* just an ordinary mailer or deferred mode */
+ return host;
+ }
+#if NETUNIX
+ else if (m->m_argv[0] != NULL &&
+ strcmp(m->m_argv[0], "FILE") == 0)
+ {
+ /* rendezvous in the file system, no MX records */
+ return host;
+ }
+#endif /* NETUNIX */
+
+ /*
+ ** Look it up in the symbol table.
+ */
+
+ now = curtime();
+ s = stab(host, ST_HOSTSIG, ST_ENTER);
+ if (s->s_hostsig.hs_sig != NULL)
+ {
+ if (s->s_hostsig.hs_exp >= now)
+ {
+ if (tTd(17, 3))
+ sm_dprintf("hostsignature(): stab(%s) found %s\n", host,
+ s->s_hostsig.hs_sig);
+ return s->s_hostsig.hs_sig;
+ }
+
+ /* signature is expired: clear it */
+ sm_free(s->s_hostsig.hs_sig);
+ s->s_hostsig.hs_sig = NULL;
+ }
+
+ /* set default TTL */
+ s->s_hostsig.hs_exp = now + SM_DEFAULT_TTL;
+
+ /*
+ ** Not already there or expired -- create a signature.
+ */
+
+#if NAMED_BIND
+ if (ConfigLevel < 2)
+ _res.options &= ~(RES_DEFNAMES | RES_DNSRCH); /* XXX */
+
+ for (hp = host; hp != NULL; hp = endp)
+ {
+#if NETINET6
+ if (*hp == '[')
+ {
+ endp = strchr(hp + 1, ']');
+ if (endp != NULL)
+ endp = strpbrk(endp + 1, ":,");
+ }
+ else
+ endp = strpbrk(hp, ":,");
+#else /* NETINET6 */
+ endp = strpbrk(hp, ":,");
+#endif /* NETINET6 */
+ if (endp != NULL)
+ {
+ sep = *endp;
+ *endp = '\0';
+ }
+
+ if (bitnset(M_NOMX, m->m_flags))
+ {
+ /* skip MX lookups */
+ nmx = 1;
+ mxhosts[0] = hp;
+ }
+ else
+ {
+ auto int rcode;
+ int ttl;
+
+ nmx = getmxrr(hp, mxhosts, mxprefs, true, &rcode, true,
+ &ttl);
+ if (nmx <= 0)
+ {
+ int save_errno;
+ register MCI *mci;
+
+ /* update the connection info for this host */
+ save_errno = errno;
+ mci = mci_get(hp, m);
+ mci->mci_errno = save_errno;
+ mci->mci_herrno = h_errno;
+ mci->mci_lastuse = now;
+ if (rcode == EX_NOHOST)
+ mci_setstat(mci, rcode, "5.1.2",
+ "550 Host unknown");
+ else
+ mci_setstat(mci, rcode, NULL, NULL);
+
+ /* use the original host name as signature */
+ nmx = 1;
+ mxhosts[0] = hp;
+ }
+ if (tTd(17, 3))
+ sm_dprintf("hostsignature(): getmxrr() returned %d, mxhosts[0]=%s\n",
+ nmx, mxhosts[0]);
+
+ /*
+ ** Set new TTL: we use only one!
+ ** We could try to use the minimum instead.
+ */
+
+ s->s_hostsig.hs_exp = now + SM_MIN(ttl, SM_DEFAULT_TTL);
+ }
+
+ len = 0;
+ for (i = 0; i < nmx; i++)
+ len += strlen(mxhosts[i]) + 1;
+ if (s->s_hostsig.hs_sig != NULL)
+ len += strlen(s->s_hostsig.hs_sig) + 1;
+ if (len < 0 || len >= MAXHOSTSIGNATURE)
+ {
+ sm_syslog(LOG_WARNING, NOQID, "hostsignature for host '%s' exceeds maxlen (%d): %d",
+ host, MAXHOSTSIGNATURE, len);
+ len = MAXHOSTSIGNATURE;
+ }
+ p = sm_pmalloc_x(len);
+ if (s->s_hostsig.hs_sig != NULL)
+ {
+ (void) sm_strlcpy(p, s->s_hostsig.hs_sig, len);
+ sm_free(s->s_hostsig.hs_sig); /* XXX */
+ s->s_hostsig.hs_sig = p;
+ hl = strlen(p);
+ p += hl;
+ *p++ = prevsep;
+ len -= hl + 1;
+ }
+ else
+ s->s_hostsig.hs_sig = p;
+ for (i = 0; i < nmx; i++)
+ {
+ hl = strlen(mxhosts[i]);
+ if (len - 1 < hl || len <= 1)
+ {
+ /* force to drop out of outer loop */
+ len = -1;
+ break;
+ }
+ if (i != 0)
+ {
+ if (mxprefs[i] == mxprefs[i - 1])
+ *p++ = ',';
+ else
+ *p++ = ':';
+ len--;
+ }
+ (void) sm_strlcpy(p, mxhosts[i], len);
+ p += hl;
+ len -= hl;
+ }
+
+ /*
+ ** break out of loop if len exceeded MAXHOSTSIGNATURE
+ ** because we won't have more space for further hosts
+ ** anyway (separated by : in the .cf file).
+ */
+
+ if (len < 0)
+ break;
+ if (endp != NULL)
+ *endp++ = sep;
+ prevsep = sep;
+ }
+ makelower(s->s_hostsig.hs_sig);
+ if (ConfigLevel < 2)
+ _res.options = oldoptions;
+#else /* NAMED_BIND */
+ /* not using BIND -- the signature is just the host name */
+ /*
+ ** 'host' points to storage that will be freed after we are
+ ** done processing the current envelope, so we copy it.
+ */
+ s->s_hostsig.hs_sig = sm_pstrdup_x(host);
+#endif /* NAMED_BIND */
+ if (tTd(17, 1))
+ sm_dprintf("hostsignature(%s) = %s\n", host, s->s_hostsig.hs_sig);
+ return s->s_hostsig.hs_sig;
+}
+/*
+** PARSE_HOSTSIGNATURE -- parse the "signature" and return MX host array.
+**
+** The signature describes how we are going to send this -- it
+** can be just the hostname (for non-Internet hosts) or can be
+** an ordered list of MX hosts which must be randomized for equal
+** MX preference values.
+**
+** Parameters:
+** sig -- the host signature.
+** mxhosts -- array to populate.
+** mailer -- mailer.
+**
+** Returns:
+** The number of hosts inserted into mxhosts array.
+**
+** Side Effects:
+** Randomizes equal MX preference hosts in mxhosts.
+*/
+
+static int
+parse_hostsignature(sig, mxhosts, mailer)
+ char *sig;
+ char **mxhosts;
+ MAILER *mailer;
+{
+ unsigned short curpref = 0;
+ int nmx = 0, i, j; /* NOTE: i, j, and nmx must have same type */
+ char *hp, *endp;
+ unsigned short prefer[MAXMXHOSTS];
+ long rndm[MAXMXHOSTS];
+
+ for (hp = sig; hp != NULL; hp = endp)
+ {
+ char sep = ':';
+
+#if NETINET6
+ if (*hp == '[')
+ {
+ endp = strchr(hp + 1, ']');
+ if (endp != NULL)
+ endp = strpbrk(endp + 1, ":,");
+ }
+ else
+ endp = strpbrk(hp, ":,");
+#else /* NETINET6 */
+ endp = strpbrk(hp, ":,");
+#endif /* NETINET6 */
+ if (endp != NULL)
+ {
+ sep = *endp;
+ *endp = '\0';
+ }
+
+ mxhosts[nmx] = hp;
+ prefer[nmx] = curpref;
+ if (mci_match(hp, mailer))
+ rndm[nmx] = 0;
+ else
+ rndm[nmx] = get_random();
+
+ if (endp != NULL)
+ {
+ /*
+ ** Since we don't have the original MX prefs,
+ ** make our own. If the separator is a ':', that
+ ** means the preference for the next host will be
+ ** higher than this one, so simply increment curpref.
+ */
+
+ if (sep == ':')
+ curpref++;
+
+ *endp++ = sep;
+ }
+ if (++nmx >= MAXMXHOSTS)
+ break;
+ }
+
+ /* sort the records using the random factor for equal preferences */
+ for (i = 0; i < nmx; i++)
+ {
+ for (j = i + 1; j < nmx; j++)
+ {
+ /*
+ ** List is already sorted by MX preference, only
+ ** need to look for equal preference MX records
+ */
+
+ if (prefer[i] < prefer[j])
+ break;
+
+ if (prefer[i] > prefer[j] ||
+ (prefer[i] == prefer[j] && rndm[i] > rndm[j]))
+ {
+ register unsigned short tempp;
+ register long tempr;
+ register char *temp1;
+
+ tempp = prefer[i];
+ prefer[i] = prefer[j];
+ prefer[j] = tempp;
+ temp1 = mxhosts[i];
+ mxhosts[i] = mxhosts[j];
+ mxhosts[j] = temp1;
+ tempr = rndm[i];
+ rndm[i] = rndm[j];
+ rndm[j] = tempr;
+ }
+ }
+ }
+ return nmx;
+}
+
+# if STARTTLS
+static SSL_CTX *clt_ctx = NULL;
+static bool tls_ok_clt = true;
+
+/*
+** SETCLTTLS -- client side TLS: allow/disallow.
+**
+** Parameters:
+** tls_ok -- should tls be done?
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** sets tls_ok_clt (static variable in this module)
+*/
+
+void
+setclttls(tls_ok)
+ bool tls_ok;
+{
+ tls_ok_clt = tls_ok;
+ return;
+}
+/*
+** INITCLTTLS -- initialize client side TLS
+**
+** Parameters:
+** tls_ok -- should tls initialization be done?
+**
+** Returns:
+** succeeded?
+**
+** Side Effects:
+** sets tls_ok_clt (static variable in this module)
+*/
+
+bool
+initclttls(tls_ok)
+ bool tls_ok;
+{
+ if (!tls_ok_clt)
+ return false;
+ tls_ok_clt = tls_ok;
+ if (!tls_ok_clt)
+ return false;
+ if (clt_ctx != NULL)
+ return true; /* already done */
+ tls_ok_clt = inittls(&clt_ctx, TLS_I_CLT, false, CltCertFile,
+ CltKeyFile, CACertPath, CACertFile, DHParams);
+ return tls_ok_clt;
+}
+
+/*
+** STARTTLS -- try to start secure connection (client side)
+**
+** Parameters:
+** m -- the mailer.
+** mci -- the mailer connection info.
+** e -- the envelope.
+**
+** Returns:
+** success?
+** (maybe this should be some other code than EX_
+** that denotes which stage failed.)
+*/
+
+static int
+starttls(m, mci, e)
+ MAILER *m;
+ MCI *mci;
+ ENVELOPE *e;
+{
+ int smtpresult;
+ int result = 0;
+ int rfd, wfd;
+ SSL *clt_ssl = NULL;
+ time_t tlsstart;
+
+ if (clt_ctx == NULL && !initclttls(true))
+ return EX_TEMPFAIL;
+ smtpmessage("STARTTLS", m, mci);
+
+ /* get the reply */
+ smtpresult = reply(m, mci, e, TimeOuts.to_starttls, NULL, NULL,
+ XS_STARTTLS);
+
+ /* check return code from server */
+ if (smtpresult == 454)
+ return EX_TEMPFAIL;
+ if (smtpresult == 501)
+ return EX_USAGE;
+ if (smtpresult == -1)
+ return smtpresult;
+ if (smtpresult != 220)
+ return EX_PROTOCOL;
+
+ if (LogLevel > 13)
+ sm_syslog(LOG_INFO, NOQID, "STARTTLS=client, start=ok");
+
+ /* start connection */
+ if ((clt_ssl = SSL_new(clt_ctx)) == NULL)
+ {
+ if (LogLevel > 5)
+ {
+ sm_syslog(LOG_ERR, NOQID,
+ "STARTTLS=client, error: SSL_new failed");
+ if (LogLevel > 9)
+ tlslogerr("client");
+ }
+ return EX_SOFTWARE;
+ }
+
+ rfd = sm_io_getinfo(mci->mci_in, SM_IO_WHAT_FD, NULL);
+ wfd = sm_io_getinfo(mci->mci_out, SM_IO_WHAT_FD, NULL);
+
+ /* SSL_clear(clt_ssl); ? */
+ if (rfd < 0 || wfd < 0 ||
+ (result = SSL_set_rfd(clt_ssl, rfd)) != 1 ||
+ (result = SSL_set_wfd(clt_ssl, wfd)) != 1)
+ {
+ if (LogLevel > 5)
+ {
+ sm_syslog(LOG_ERR, NOQID,
+ "STARTTLS=client, error: SSL_set_xfd failed=%d",
+ result);
+ if (LogLevel > 9)
+ tlslogerr("client");
+ }
+ return EX_SOFTWARE;
+ }
+ SSL_set_connect_state(clt_ssl);
+ tlsstart = curtime();
+
+ssl_retry:
+ if ((result = SSL_connect(clt_ssl)) <= 0)
+ {
+ int i;
+ bool timedout;
+ time_t left;
+ time_t now = curtime();
+ struct timeval tv;
+
+ /* what to do in this case? */
+ i = SSL_get_error(clt_ssl, result);
+
+ /*
+ ** For SSL_ERROR_WANT_{READ,WRITE}:
+ ** There is not a complete SSL record available yet
+ ** or there is only a partial SSL record removed from
+ ** the network (socket) buffer into the SSL buffer.
+ ** The SSL_connect will only succeed when a full
+ ** SSL record is available (assuming a "real" error
+ ** doesn't happen). To handle when a "real" error
+ ** does happen the select is set for exceptions too.
+ ** The connection may be re-negotiated during this time
+ ** so both read and write "want errors" need to be handled.
+ ** A select() exception loops back so that a proper SSL
+ ** error message can be gotten.
+ */
+
+ left = TimeOuts.to_starttls - (now - tlsstart);
+ timedout = left <= 0;
+ if (!timedout)
+ {
+ tv.tv_sec = left;
+ tv.tv_usec = 0;
+ }
+
+ if (!timedout && FD_SETSIZE > 0 &&
+ (rfd >= FD_SETSIZE ||
+ (i == SSL_ERROR_WANT_WRITE && wfd >= FD_SETSIZE)))
+ {
+ if (LogLevel > 5)
+ {
+ sm_syslog(LOG_ERR, e->e_id,
+ "STARTTLS=client, error: fd %d/%d too large",
+ rfd, wfd);
+ if (LogLevel > 8)
+ tlslogerr("client");
+ }
+ errno = EINVAL;
+ goto tlsfail;
+ }
+ if (!timedout && i == SSL_ERROR_WANT_READ)
+ {
+ fd_set ssl_maskr, ssl_maskx;
+
+ FD_ZERO(&ssl_maskr);
+ FD_SET(rfd, &ssl_maskr);
+ FD_ZERO(&ssl_maskx);
+ FD_SET(rfd, &ssl_maskx);
+ if (select(rfd + 1, &ssl_maskr, NULL, &ssl_maskx, &tv)
+ > 0)
+ goto ssl_retry;
+ }
+ if (!timedout && i == SSL_ERROR_WANT_WRITE)
+ {
+ fd_set ssl_maskw, ssl_maskx;
+
+ FD_ZERO(&ssl_maskw);
+ FD_SET(wfd, &ssl_maskw);
+ FD_ZERO(&ssl_maskx);
+ FD_SET(rfd, &ssl_maskx);
+ if (select(wfd + 1, NULL, &ssl_maskw, &ssl_maskx, &tv)
+ > 0)
+ goto ssl_retry;
+ }
+ if (LogLevel > 5)
+ {
+ sm_syslog(LOG_ERR, e->e_id,
+ "STARTTLS=client, error: connect failed=%d, SSL_error=%d, timedout=%d, errno=%d",
+ result, i, (int) timedout, errno);
+ if (LogLevel > 8)
+ tlslogerr("client");
+ }
+tlsfail:
+ SSL_free(clt_ssl);
+ clt_ssl = NULL;
+ return EX_SOFTWARE;
+ }
+ mci->mci_ssl = clt_ssl;
+ result = tls_get_info(mci->mci_ssl, false, mci->mci_host,
+ &mci->mci_macro, true);
+
+ /* switch to use TLS... */
+ if (sfdctls(&mci->mci_in, &mci->mci_out, mci->mci_ssl) == 0)
+ return EX_OK;
+
+ /* failure */
+ SSL_free(clt_ssl);
+ clt_ssl = NULL;
+ return EX_SOFTWARE;
+}
+/*
+** ENDTLSCLT -- shutdown secure connection (client side)
+**
+** Parameters:
+** mci -- the mailer connection info.
+**
+** Returns:
+** success?
+*/
+
+static int
+endtlsclt(mci)
+ MCI *mci;
+{
+ int r;
+
+ if (!bitset(MCIF_TLSACT, mci->mci_flags))
+ return EX_OK;
+ r = endtls(mci->mci_ssl, "client");
+ mci->mci_flags &= ~MCIF_TLSACT;
+ return r;
+}
+# endif /* STARTTLS */
+# if STARTTLS || SASL
+/*
+** ISCLTFLGSET -- check whether client flag is set.
+**
+** Parameters:
+** e -- envelope.
+** flag -- flag to check in {client_flags}
+**
+** Returns:
+** true iff flag is set.
+*/
+
+static bool
+iscltflgset(e, flag)
+ ENVELOPE *e;
+ int flag;
+{
+ char *p;
+
+ p = macvalue(macid("{client_flags}"), e);
+ if (p == NULL)
+ return false;
+ for (; *p != '\0'; p++)
+ {
+ /* look for just this one flag */
+ if (*p == (char) flag)
+ return true;
+ }
+ return false;
+}
+# endif /* STARTTLS || SASL */
diff --git a/usr/src/cmd/sendmail/src/domain.c b/usr/src/cmd/sendmail/src/domain.c
new file mode 100644
index 0000000000..67dbdc6359
--- /dev/null
+++ b/usr/src/cmd/sendmail/src/domain.c
@@ -0,0 +1,1171 @@
+/*
+ * Copyright (c) 1998-2004 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1986, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sendmail.h>
+
+#if NAMED_BIND
+SM_RCSID("@(#)$Id: domain.c,v 8.197 2005/03/04 00:54:42 ca Exp $ (with name server)")
+#else /* NAMED_BIND */
+SM_RCSID("@(#)$Id: domain.c,v 8.197 2005/03/04 00:54:42 ca Exp $ (without name server)")
+#endif /* NAMED_BIND */
+
+#if NAMED_BIND
+
+# include <arpa/inet.h>
+
+
+/*
+** The standard udp packet size PACKETSZ (512) is not sufficient for some
+** nameserver answers containing very many resource records. The resolver
+** may switch to tcp and retry if it detects udp packet overflow.
+** Also note that the resolver routines res_query and res_search return
+** the size of the *un*truncated answer in case the supplied answer buffer
+** it not big enough to accommodate the entire answer.
+*/
+
+# ifndef MAXPACKET
+# define MAXPACKET 8192 /* max packet size used internally by BIND */
+# endif /* ! MAXPACKET */
+
+typedef union
+{
+ HEADER qb1;
+ unsigned char qb2[MAXPACKET];
+} querybuf;
+
+# ifndef MXHOSTBUFSIZE
+# define MXHOSTBUFSIZE (128 * MAXMXHOSTS)
+# endif /* ! MXHOSTBUFSIZE */
+
+static char MXHostBuf[MXHOSTBUFSIZE];
+#if (MXHOSTBUFSIZE < 2) || (MXHOSTBUFSIZE >= INT_MAX/2)
+ ERROR: _MXHOSTBUFSIZE is out of range
+#endif /* (MXHOSTBUFSIZE < 2) || (MXHOSTBUFSIZE >= INT_MAX/2) */
+
+# ifndef MAXDNSRCH
+# define MAXDNSRCH 6 /* number of possible domains to search */
+# endif /* ! MAXDNSRCH */
+
+# ifndef RES_DNSRCH_VARIABLE
+# define RES_DNSRCH_VARIABLE _res.dnsrch
+# endif /* ! RES_DNSRCH_VARIABLE */
+
+# ifndef NO_DATA
+# define NO_DATA NO_ADDRESS
+# endif /* ! NO_DATA */
+
+# ifndef HFIXEDSZ
+# define HFIXEDSZ 12 /* sizeof(HEADER) */
+# endif /* ! HFIXEDSZ */
+
+# define MAXCNAMEDEPTH 10 /* maximum depth of CNAME recursion */
+
+# if defined(__RES) && (__RES >= 19940415)
+# define RES_UNC_T char *
+# else /* defined(__RES) && (__RES >= 19940415) */
+# define RES_UNC_T unsigned char *
+# endif /* defined(__RES) && (__RES >= 19940415) */
+
+static int mxrand __P((char *));
+static int fallbackmxrr __P((int, unsigned short *, char **));
+
+/*
+** GETFALLBACKMXRR -- get MX resource records for fallback MX host.
+**
+** We have to initialize this once before doing anything else.
+** Moreover, we have to repeat this from time to time to avoid
+** stale data, e.g., in persistent queue runners.
+** This should be done in a parent process so the child
+** processes have the right data.
+**
+** Parameters:
+** host -- the name of the fallback MX host.
+**
+** Returns:
+** number of MX records.
+**
+** Side Effects:
+** Populates NumFallbackMXHosts and fbhosts.
+** Sets renewal time (based on TTL).
+*/
+
+int NumFallbackMXHosts = 0; /* Number of fallback MX hosts (after MX expansion) */
+static char *fbhosts[MAXMXHOSTS + 1];
+
+int
+getfallbackmxrr(host)
+ char *host;
+{
+ int i, rcode;
+ int ttl;
+ static time_t renew = 0;
+
+#if 0
+ /* This is currently done before this function is called. */
+ if (host == NULL || *host == '\0')
+ return 0;
+#endif /* 0 */
+ if (NumFallbackMXHosts > 0 && renew > curtime())
+ return NumFallbackMXHosts;
+ if (host[0] == '[')
+ {
+ fbhosts[0] = host;
+ NumFallbackMXHosts = 1;
+ }
+ else
+ {
+ /* free old data */
+ for (i = 0; i < NumFallbackMXHosts; i++)
+ sm_free(fbhosts[i]);
+
+ /* get new data */
+ NumFallbackMXHosts = getmxrr(host, fbhosts, NULL, false,
+ &rcode, false, &ttl);
+ renew = curtime() + ttl;
+ for (i = 0; i < NumFallbackMXHosts; i++)
+ fbhosts[i] = newstr(fbhosts[i]);
+ }
+ return NumFallbackMXHosts;
+}
+
+/*
+** FALLBACKMXRR -- add MX resource records for fallback MX host to list.
+**
+** Parameters:
+** nmx -- current number of MX records.
+** prefs -- array of preferences.
+** mxhosts -- array of MX hosts (maximum size: MAXMXHOSTS)
+**
+** Returns:
+** new number of MX records.
+**
+** Side Effects:
+** If FallbackMX was set, it appends the MX records for
+** that host to mxhosts (and modifies prefs accordingly).
+*/
+
+static int
+fallbackmxrr(nmx, prefs, mxhosts)
+ int nmx;
+ unsigned short *prefs;
+ char **mxhosts;
+{
+ int i;
+
+ for (i = 0; i < NumFallbackMXHosts && nmx < MAXMXHOSTS; i++)
+ {
+ if (nmx > 0)
+ prefs[nmx] = prefs[nmx - 1] + 1;
+ else
+ prefs[nmx] = 0;
+ mxhosts[nmx++] = fbhosts[i];
+ }
+ return nmx;
+}
+
+/*
+** GETMXRR -- get MX resource records for a domain
+**
+** Parameters:
+** host -- the name of the host to MX.
+** mxhosts -- a pointer to a return buffer of MX records.
+** mxprefs -- a pointer to a return buffer of MX preferences.
+** If NULL, don't try to populate.
+** droplocalhost -- If true, all MX records less preferred
+** than the local host (as determined by $=w) will
+** be discarded.
+** rcode -- a pointer to an EX_ status code.
+** tryfallback -- add also fallback MX host?
+** pttl -- pointer to return TTL (can be NULL).
+**
+** Returns:
+** The number of MX records found.
+** -1 if there is an internal failure.
+** If no MX records are found, mxhosts[0] is set to host
+** and 1 is returned.
+**
+** Side Effects:
+** The entries made for mxhosts point to a static array
+** MXHostBuf[MXHOSTBUFSIZE], so the data needs to be copied,
+** if it must be preserved across calls to this function.
+*/
+
+int
+getmxrr(host, mxhosts, mxprefs, droplocalhost, rcode, tryfallback, pttl)
+ char *host;
+ char **mxhosts;
+ unsigned short *mxprefs;
+ bool droplocalhost;
+ int *rcode;
+ bool tryfallback;
+ int *pttl;
+{
+ register unsigned char *eom, *cp;
+ register int i, j, n;
+ int nmx = 0;
+ register char *bp;
+ HEADER *hp;
+ querybuf answer;
+ int ancount, qdcount, buflen;
+ bool seenlocal = false;
+ unsigned short pref, type;
+ unsigned short localpref = 256;
+ char *fallbackMX = FallbackMX;
+ bool trycanon = false;
+ unsigned short *prefs;
+ int (*resfunc) __P((const char *, int, int, u_char *, int));
+ unsigned short prefer[MAXMXHOSTS];
+ int weight[MAXMXHOSTS];
+ int ttl = 0;
+ extern int res_query(), res_search();
+
+ if (tTd(8, 2))
+ sm_dprintf("getmxrr(%s, droplocalhost=%d)\n",
+ host, droplocalhost);
+ *rcode = EX_OK;
+ if (pttl != NULL)
+ *pttl = SM_DEFAULT_TTL;
+ if (*host == '\0')
+ return 0;
+
+ if ((fallbackMX != NULL && droplocalhost &&
+ wordinclass(fallbackMX, 'w')) || !tryfallback)
+ {
+ /* don't use fallback for this pass */
+ fallbackMX = NULL;
+ }
+
+ if (mxprefs != NULL)
+ prefs = mxprefs;
+ else
+ prefs = prefer;
+
+ /* efficiency hack -- numeric or non-MX lookups */
+ if (host[0] == '[')
+ goto punt;
+
+ /*
+ ** If we don't have MX records in our host switch, don't
+ ** try for MX records. Note that this really isn't "right",
+ ** since we might be set up to try NIS first and then DNS;
+ ** if the host is found in NIS we really shouldn't be doing
+ ** MX lookups. However, that should be a degenerate case.
+ */
+
+ if (!UseNameServer)
+ goto punt;
+ if (HasWildcardMX && ConfigLevel >= 6)
+ resfunc = res_query;
+ else
+ resfunc = res_search;
+
+ errno = 0;
+ n = (*resfunc)(host, C_IN, T_MX, (unsigned char *) &answer,
+ sizeof(answer));
+ if (n < 0)
+ {
+ if (tTd(8, 1))
+ sm_dprintf("getmxrr: res_search(%s) failed (errno=%d, h_errno=%d)\n",
+ host == NULL ? "<NULL>" : host, errno, h_errno);
+ switch (h_errno)
+ {
+ case NO_DATA:
+ trycanon = true;
+ /* FALLTHROUGH */
+
+ case NO_RECOVERY:
+ /* no MX data on this host */
+ goto punt;
+
+ case HOST_NOT_FOUND:
+# if BROKEN_RES_SEARCH
+ case 0: /* Ultrix resolver retns failure w/ h_errno=0 */
+# endif /* BROKEN_RES_SEARCH */
+ /* host doesn't exist in DNS; might be in /etc/hosts */
+ trycanon = true;
+ *rcode = EX_NOHOST;
+ goto punt;
+
+ case TRY_AGAIN:
+ case -1:
+ /* couldn't connect to the name server */
+ if (fallbackMX != NULL)
+ {
+ /* name server is hosed -- push to fallback */
+ return fallbackmxrr(nmx, prefs, mxhosts);
+ }
+ /* it might come up later; better queue it up */
+ *rcode = EX_TEMPFAIL;
+ break;
+
+ default:
+ syserr("getmxrr: res_search (%s) failed with impossible h_errno (%d)",
+ host, h_errno);
+ *rcode = EX_OSERR;
+ break;
+ }
+
+ /* irreconcilable differences */
+ return -1;
+ }
+
+ /* avoid problems after truncation in tcp packets */
+ if (n > sizeof(answer))
+ n = sizeof(answer);
+
+ /* find first satisfactory answer */
+ hp = (HEADER *)&answer;
+ cp = (unsigned char *)&answer + HFIXEDSZ;
+ eom = (unsigned char *)&answer + n;
+ for (qdcount = ntohs((unsigned short) hp->qdcount);
+ qdcount--;
+ cp += n + QFIXEDSZ)
+ {
+ if ((n = dn_skipname(cp, eom)) < 0)
+ goto punt;
+ }
+
+ /* NOTE: see definition of MXHostBuf! */
+ buflen = sizeof(MXHostBuf) - 1;
+ SM_ASSERT(buflen > 0);
+ bp = MXHostBuf;
+ ancount = ntohs((unsigned short) hp->ancount);
+
+ /* See RFC 1035 for layout of RRs. */
+ /* XXX leave room for FallbackMX ? */
+ while (--ancount >= 0 && cp < eom && nmx < MAXMXHOSTS - 1)
+ {
+ if ((n = dn_expand((unsigned char *)&answer, eom, cp,
+ (RES_UNC_T) bp, buflen)) < 0)
+ break;
+ cp += n;
+ GETSHORT(type, cp);
+ cp += INT16SZ; /* skip over class */
+ GETLONG(ttl, cp);
+ GETSHORT(n, cp); /* rdlength */
+ if (type != T_MX)
+ {
+ if (tTd(8, 8) || _res.options & RES_DEBUG)
+ sm_dprintf("unexpected answer type %d, size %d\n",
+ type, n);
+ cp += n;
+ continue;
+ }
+ GETSHORT(pref, cp);
+ if ((n = dn_expand((unsigned char *)&answer, eom, cp,
+ (RES_UNC_T) bp, buflen)) < 0)
+ break;
+ cp += n;
+ n = strlen(bp);
+# if 0
+ /* Can this happen? */
+ if (n == 0)
+ {
+ if (LogLevel > 4)
+ sm_syslog(LOG_ERR, NOQID,
+ "MX records for %s contain empty string",
+ host);
+ continue;
+ }
+# endif /* 0 */
+ if (wordinclass(bp, 'w'))
+ {
+ if (tTd(8, 3))
+ sm_dprintf("found localhost (%s) in MX list, pref=%d\n",
+ bp, pref);
+ if (droplocalhost)
+ {
+ if (!seenlocal || pref < localpref)
+ localpref = pref;
+ seenlocal = true;
+ continue;
+ }
+ weight[nmx] = 0;
+ }
+ else
+ weight[nmx] = mxrand(bp);
+ prefs[nmx] = pref;
+ mxhosts[nmx++] = bp;
+ bp += n;
+ if (bp[-1] != '.')
+ {
+ *bp++ = '.';
+ n++;
+ }
+ *bp++ = '\0';
+ if (buflen < n + 1)
+ {
+ /* don't want to wrap buflen */
+ break;
+ }
+ buflen -= n + 1;
+ }
+
+ /* return only one TTL entry, that should be sufficient */
+ if (ttl > 0 && pttl != NULL)
+ *pttl = ttl;
+
+ /* sort the records */
+ for (i = 0; i < nmx; i++)
+ {
+ for (j = i + 1; j < nmx; j++)
+ {
+ if (prefs[i] > prefs[j] ||
+ (prefs[i] == prefs[j] && weight[i] > weight[j]))
+ {
+ register int temp;
+ register char *temp1;
+
+ temp = prefs[i];
+ prefs[i] = prefs[j];
+ prefs[j] = temp;
+ temp1 = mxhosts[i];
+ mxhosts[i] = mxhosts[j];
+ mxhosts[j] = temp1;
+ temp = weight[i];
+ weight[i] = weight[j];
+ weight[j] = temp;
+ }
+ }
+ if (seenlocal && prefs[i] >= localpref)
+ {
+ /* truncate higher preference part of list */
+ nmx = i;
+ }
+ }
+
+ /* delete duplicates from list (yes, some bozos have duplicates) */
+ for (i = 0; i < nmx - 1; )
+ {
+ if (sm_strcasecmp(mxhosts[i], mxhosts[i + 1]) != 0)
+ i++;
+ else
+ {
+ /* compress out duplicate */
+ for (j = i + 1; j < nmx; j++)
+ {
+ mxhosts[j] = mxhosts[j + 1];
+ prefs[j] = prefs[j + 1];
+ }
+ nmx--;
+ }
+ }
+
+ if (nmx == 0)
+ {
+punt:
+ if (seenlocal)
+ {
+ struct hostent *h = NULL;
+
+ /*
+ ** If we have deleted all MX entries, this is
+ ** an error -- we should NEVER send to a host that
+ ** has an MX, and this should have been caught
+ ** earlier in the config file.
+ **
+ ** Some sites prefer to go ahead and try the
+ ** A record anyway; that case is handled by
+ ** setting TryNullMXList. I believe this is a
+ ** bad idea, but it's up to you....
+ */
+
+ if (TryNullMXList)
+ {
+ SM_SET_H_ERRNO(0);
+ errno = 0;
+ h = sm_gethostbyname(host, AF_INET);
+ if (h == NULL)
+ {
+ if (errno == ETIMEDOUT ||
+ h_errno == TRY_AGAIN ||
+ (errno == ECONNREFUSED &&
+ UseNameServer))
+ {
+ *rcode = EX_TEMPFAIL;
+ return -1;
+ }
+# if NETINET6
+ SM_SET_H_ERRNO(0);
+ errno = 0;
+ h = sm_gethostbyname(host, AF_INET6);
+ if (h == NULL &&
+ (errno == ETIMEDOUT ||
+ h_errno == TRY_AGAIN ||
+ (errno == ECONNREFUSED &&
+ UseNameServer)))
+ {
+ *rcode = EX_TEMPFAIL;
+ return -1;
+ }
+# endif /* NETINET6 */
+ }
+ }
+
+ if (h == NULL)
+ {
+ *rcode = EX_CONFIG;
+ syserr("MX list for %s points back to %s",
+ host, MyHostName);
+ return -1;
+ }
+# if NETINET6
+ freehostent(h);
+ hp = NULL;
+# endif /* NETINET6 */
+ }
+ if (strlen(host) >= sizeof MXHostBuf)
+ {
+ *rcode = EX_CONFIG;
+ syserr("Host name %s too long",
+ shortenstring(host, MAXSHORTSTR));
+ return -1;
+ }
+ (void) sm_strlcpy(MXHostBuf, host, sizeof MXHostBuf);
+ mxhosts[0] = MXHostBuf;
+ prefs[0] = 0;
+ if (host[0] == '[')
+ {
+ register char *p;
+# if NETINET6
+ struct sockaddr_in6 tmp6;
+# endif /* NETINET6 */
+
+ /* this may be an MX suppression-style address */
+ p = strchr(MXHostBuf, ']');
+ if (p != NULL)
+ {
+ *p = '\0';
+
+ if (inet_addr(&MXHostBuf[1]) != INADDR_NONE)
+ {
+ nmx++;
+ *p = ']';
+ }
+# if NETINET6
+ else if (anynet_pton(AF_INET6, &MXHostBuf[1],
+ &tmp6.sin6_addr) == 1)
+ {
+ nmx++;
+ *p = ']';
+ }
+# endif /* NETINET6 */
+ else
+ {
+ trycanon = true;
+ mxhosts[0]++;
+ }
+ }
+ }
+ if (trycanon &&
+ getcanonname(mxhosts[0], sizeof MXHostBuf - 2, false, pttl))
+ {
+ /* XXX MXHostBuf == "" ? is that possible? */
+ bp = &MXHostBuf[strlen(MXHostBuf)];
+ if (bp[-1] != '.')
+ {
+ *bp++ = '.';
+ *bp = '\0';
+ }
+ nmx = 1;
+ }
+ }
+
+ /* if we have a default lowest preference, include that */
+ if (fallbackMX != NULL && !seenlocal)
+ {
+ nmx = fallbackmxrr(nmx, prefs, mxhosts);
+ }
+ return nmx;
+}
+/*
+** MXRAND -- create a randomizer for equal MX preferences
+**
+** If two MX hosts have equal preferences we want to randomize
+** the selection. But in order for signatures to be the same,
+** we need to randomize the same way each time. This function
+** computes a pseudo-random hash function from the host name.
+**
+** Parameters:
+** host -- the name of the host.
+**
+** Returns:
+** A random but repeatable value based on the host name.
+*/
+
+static int
+mxrand(host)
+ register char *host;
+{
+ int hfunc;
+ static unsigned int seed;
+
+ if (seed == 0)
+ {
+ seed = (int) curtime() & 0xffff;
+ if (seed == 0)
+ seed++;
+ }
+
+ if (tTd(17, 9))
+ sm_dprintf("mxrand(%s)", host);
+
+ hfunc = seed;
+ while (*host != '\0')
+ {
+ int c = *host++;
+
+ if (isascii(c) && isupper(c))
+ c = tolower(c);
+ hfunc = ((hfunc << 1) ^ c) % 2003;
+ }
+
+ hfunc &= 0xff;
+ hfunc++;
+
+ if (tTd(17, 9))
+ sm_dprintf(" = %d\n", hfunc);
+ return hfunc;
+}
+/*
+** BESTMX -- find the best MX for a name
+**
+** This is really a hack, but I don't see any obvious way
+** to generalize it at the moment.
+*/
+
+/* ARGSUSED3 */
+char *
+bestmx_map_lookup(map, name, av, statp)
+ MAP *map;
+ char *name;
+ char **av;
+ int *statp;
+{
+ int nmx;
+ int saveopts = _res.options;
+ int i;
+ ssize_t len = 0;
+ char *result;
+ char *mxhosts[MAXMXHOSTS + 1];
+#if _FFR_BESTMX_BETTER_TRUNCATION
+ char *buf;
+#else /* _FFR_BESTMX_BETTER_TRUNCATION */
+ char *p;
+ char buf[PSBUFSIZE / 2];
+#endif /* _FFR_BESTMX_BETTER_TRUNCATION */
+
+ _res.options &= ~(RES_DNSRCH|RES_DEFNAMES);
+ nmx = getmxrr(name, mxhosts, NULL, false, statp, false, NULL);
+ _res.options = saveopts;
+ if (nmx <= 0)
+ return NULL;
+ if (bitset(MF_MATCHONLY, map->map_mflags))
+ return map_rewrite(map, name, strlen(name), NULL);
+ if ((map->map_coldelim == '\0') || (nmx == 1))
+ return map_rewrite(map, mxhosts[0], strlen(mxhosts[0]), av);
+
+ /*
+ ** We were given a -z flag (return all MXs) and there are multiple
+ ** ones. We need to build them all into a list.
+ */
+
+#if _FFR_BESTMX_BETTER_TRUNCATION
+ for (i = 0; i < nmx; i++)
+ {
+ if (strchr(mxhosts[i], map->map_coldelim) != NULL)
+ {
+ syserr("bestmx_map_lookup: MX host %.64s includes map delimiter character 0x%02X",
+ mxhosts[i], map->map_coldelim);
+ return NULL;
+ }
+ len += strlen(mxhosts[i]) + 1;
+ if (len < 0)
+ {
+ len -= strlen(mxhosts[i]) + 1;
+ break;
+ }
+ }
+ buf = (char *) sm_malloc(len);
+ if (buf == NULL)
+ {
+ *statp = EX_UNAVAILABLE;
+ return NULL;
+ }
+ *buf = '\0';
+ for (i = 0; i < nmx; i++)
+ {
+ int end;
+
+ end = sm_strlcat(buf, mxhosts[i], len);
+ if (i != nmx && end + 1 < len)
+ {
+ buf[end] = map->map_coldelim;
+ buf[end + 1] = '\0';
+ }
+ }
+
+ /* Cleanly truncate for rulesets */
+ truncate_at_delim(buf, PSBUFSIZE / 2, map->map_coldelim);
+#else /* _FFR_BESTMX_BETTER_TRUNCATION */
+ p = buf;
+ for (i = 0; i < nmx; i++)
+ {
+ size_t slen;
+
+ if (strchr(mxhosts[i], map->map_coldelim) != NULL)
+ {
+ syserr("bestmx_map_lookup: MX host %.64s includes map delimiter character 0x%02X",
+ mxhosts[i], map->map_coldelim);
+ return NULL;
+ }
+ slen = strlen(mxhosts[i]);
+ if (len + slen + 2 > sizeof buf)
+ break;
+ if (i > 0)
+ {
+ *p++ = map->map_coldelim;
+ len++;
+ }
+ (void) sm_strlcpy(p, mxhosts[i], sizeof buf - len);
+ p += slen;
+ len += slen;
+ }
+#endif /* _FFR_BESTMX_BETTER_TRUNCATION */
+
+ result = map_rewrite(map, buf, len, av);
+#if _FFR_BESTMX_BETTER_TRUNCATION
+ sm_free(buf);
+#endif /* _FFR_BESTMX_BETTER_TRUNCATION */
+ return result;
+}
+/*
+** DNS_GETCANONNAME -- get the canonical name for named host using DNS
+**
+** This algorithm tries to be smart about wildcard MX records.
+** This is hard to do because DNS doesn't tell is if we matched
+** against a wildcard or a specific MX.
+**
+** We always prefer A & CNAME records, since these are presumed
+** to be specific.
+**
+** If we match an MX in one pass and lose it in the next, we use
+** the old one. For example, consider an MX matching *.FOO.BAR.COM.
+** A hostname bletch.foo.bar.com will match against this MX, but
+** will stop matching when we try bletch.bar.com -- so we know
+** that bletch.foo.bar.com must have been right. This fails if
+** there was also an MX record matching *.BAR.COM, but there are
+** some things that just can't be fixed.
+**
+** Parameters:
+** host -- a buffer containing the name of the host.
+** This is a value-result parameter.
+** hbsize -- the size of the host buffer.
+** trymx -- if set, try MX records as well as A and CNAME.
+** statp -- pointer to place to store status.
+** pttl -- pointer to return TTL (can be NULL).
+**
+** Returns:
+** true -- if the host matched.
+** false -- otherwise.
+*/
+
+bool
+dns_getcanonname(host, hbsize, trymx, statp, pttl)
+ char *host;
+ int hbsize;
+ bool trymx;
+ int *statp;
+ int *pttl;
+{
+ register unsigned char *eom, *ap;
+ register char *cp;
+ register int n;
+ HEADER *hp;
+ querybuf answer;
+ int ancount, qdcount;
+ int ret;
+ char **domain;
+ int type;
+ int ttl = 0;
+ char **dp;
+ char *mxmatch;
+ bool amatch;
+ bool gotmx = false;
+ int qtype;
+ int initial;
+ int loopcnt;
+ char nbuf[SM_MAX(MAXPACKET, MAXDNAME*2+2)];
+ char *searchlist[MAXDNSRCH + 2];
+
+ if (tTd(8, 2))
+ sm_dprintf("dns_getcanonname(%s, trymx=%d)\n", host, trymx);
+
+ if ((_res.options & RES_INIT) == 0 && res_init() == -1)
+ {
+ *statp = EX_UNAVAILABLE;
+ return false;
+ }
+
+ *statp = EX_OK;
+
+ /*
+ ** Initialize domain search list. If there is at least one
+ ** dot in the name, search the unmodified name first so we
+ ** find "vse.CS" in Czechoslovakia instead of in the local
+ ** domain (e.g., vse.CS.Berkeley.EDU). Note that there is no
+ ** longer a country named Czechoslovakia but this type of problem
+ ** is still present.
+ **
+ ** Older versions of the resolver could create this
+ ** list by tearing apart the host name.
+ */
+
+ loopcnt = 0;
+cnameloop:
+ /* Check for dots in the name */
+ for (cp = host, n = 0; *cp != '\0'; cp++)
+ if (*cp == '.')
+ n++;
+
+ /*
+ ** Build the search list.
+ ** If there is at least one dot in name, start with a null
+ ** domain to search the unmodified name first.
+ ** If name does not end with a dot and search up local domain
+ ** tree desired, append each local domain component to the
+ ** search list; if name contains no dots and default domain
+ ** name is desired, append default domain name to search list;
+ ** else if name ends in a dot, remove that dot.
+ */
+
+ dp = searchlist;
+ if (n > 0)
+ *dp++ = "";
+ if (n >= 0 && *--cp != '.' && bitset(RES_DNSRCH, _res.options))
+ {
+ /* make sure there are less than MAXDNSRCH domains */
+ for (domain = RES_DNSRCH_VARIABLE, ret = 0;
+ *domain != NULL && ret < MAXDNSRCH;
+ ret++)
+ *dp++ = *domain++;
+ }
+ else if (n == 0 && bitset(RES_DEFNAMES, _res.options))
+ {
+ *dp++ = _res.defdname;
+ }
+ else if (*cp == '.')
+ {
+ *cp = '\0';
+ }
+ *dp = NULL;
+
+ /*
+ ** Now loop through the search list, appending each domain in turn
+ ** name and searching for a match.
+ */
+
+ mxmatch = NULL;
+ initial = T_A;
+# if NETINET6
+ if (InetMode == AF_INET6)
+ initial = T_AAAA;
+# endif /* NETINET6 */
+ qtype = initial;
+
+ for (dp = searchlist; *dp != NULL; )
+ {
+ if (qtype == initial)
+ gotmx = false;
+ if (tTd(8, 5))
+ sm_dprintf("dns_getcanonname: trying %s.%s (%s)\n",
+ host, *dp,
+# if NETINET6
+ qtype == T_AAAA ? "AAAA" :
+# endif /* NETINET6 */
+ qtype == T_A ? "A" :
+ qtype == T_MX ? "MX" :
+ "???");
+ errno = 0;
+ ret = res_querydomain(host, *dp, C_IN, qtype,
+ answer.qb2, sizeof(answer.qb2));
+ if (ret <= 0)
+ {
+ int save_errno = errno;
+
+ if (tTd(8, 7))
+ sm_dprintf("\tNO: errno=%d, h_errno=%d\n",
+ save_errno, h_errno);
+
+ if (save_errno == ECONNREFUSED || h_errno == TRY_AGAIN)
+ {
+ /*
+ ** the name server seems to be down or broken.
+ */
+
+ SM_SET_H_ERRNO(TRY_AGAIN);
+ if (**dp == '\0')
+ {
+ if (*statp == EX_OK)
+ *statp = EX_TEMPFAIL;
+ goto nexttype;
+ }
+ *statp = EX_TEMPFAIL;
+
+ if (WorkAroundBrokenAAAA)
+ {
+ /*
+ ** Only return if not TRY_AGAIN as an
+ ** attempt with a different qtype may
+ ** succeed (res_querydomain() calls
+ ** res_query() calls res_send() which
+ ** sets errno to ETIMEDOUT if the
+ ** nameservers could be contacted but
+ ** didn't give an answer).
+ */
+
+ if (save_errno != ETIMEDOUT)
+ return false;
+ }
+ else
+ return false;
+ }
+
+nexttype:
+ if (h_errno != HOST_NOT_FOUND)
+ {
+ /* might have another type of interest */
+# if NETINET6
+ if (qtype == T_AAAA)
+ {
+ qtype = T_A;
+ continue;
+ }
+ else
+# endif /* NETINET6 */
+ if (qtype == T_A && !gotmx &&
+ (trymx || **dp == '\0'))
+ {
+ qtype = T_MX;
+ continue;
+ }
+ }
+
+ /* definite no -- try the next domain */
+ dp++;
+ qtype = initial;
+ continue;
+ }
+ else if (tTd(8, 7))
+ sm_dprintf("\tYES\n");
+
+ /* avoid problems after truncation in tcp packets */
+ if (ret > sizeof(answer))
+ ret = sizeof(answer);
+ if (ret < 0)
+ {
+ *statp = EX_SOFTWARE;
+ return false;
+ }
+
+ /*
+ ** Appear to have a match. Confirm it by searching for A or
+ ** CNAME records. If we don't have a local domain
+ ** wild card MX record, we will accept MX as well.
+ */
+
+ hp = (HEADER *) &answer;
+ ap = (unsigned char *) &answer + HFIXEDSZ;
+ eom = (unsigned char *) &answer + ret;
+
+ /* skip question part of response -- we know what we asked */
+ for (qdcount = ntohs((unsigned short) hp->qdcount);
+ qdcount--;
+ ap += ret + QFIXEDSZ)
+ {
+ if ((ret = dn_skipname(ap, eom)) < 0)
+ {
+ if (tTd(8, 20))
+ sm_dprintf("qdcount failure (%d)\n",
+ ntohs((unsigned short) hp->qdcount));
+ *statp = EX_SOFTWARE;
+ return false; /* ???XXX??? */
+ }
+ }
+
+ amatch = false;
+ for (ancount = ntohs((unsigned short) hp->ancount);
+ --ancount >= 0 && ap < eom;
+ ap += n)
+ {
+ n = dn_expand((unsigned char *) &answer, eom, ap,
+ (RES_UNC_T) nbuf, sizeof nbuf);
+ if (n < 0)
+ break;
+ ap += n;
+ GETSHORT(type, ap);
+ ap += INT16SZ; /* skip over class */
+ GETLONG(ttl, ap);
+ GETSHORT(n, ap); /* rdlength */
+ switch (type)
+ {
+ case T_MX:
+ gotmx = true;
+ if (**dp != '\0' && HasWildcardMX)
+ {
+ /*
+ ** If we are using MX matches and have
+ ** not yet gotten one, save this one
+ ** but keep searching for an A or
+ ** CNAME match.
+ */
+
+ if (trymx && mxmatch == NULL)
+ mxmatch = *dp;
+ continue;
+ }
+
+ /*
+ ** If we did not append a domain name, this
+ ** must have been a canonical name to start
+ ** with. Even if we did append a domain name,
+ ** in the absence of a wildcard MX this must
+ ** still be a real MX match.
+ ** Such MX matches are as good as an A match,
+ ** fall through.
+ */
+ /* FALLTHROUGH */
+
+# if NETINET6
+ case T_AAAA:
+# endif /* NETINET6 */
+ case T_A:
+ /* Flag that a good match was found */
+ amatch = true;
+
+ /* continue in case a CNAME also exists */
+ continue;
+
+ case T_CNAME:
+ if (DontExpandCnames)
+ {
+ /* got CNAME -- guaranteed canonical */
+ amatch = true;
+ break;
+ }
+
+ if (loopcnt++ > MAXCNAMEDEPTH)
+ {
+ /*XXX should notify postmaster XXX*/
+ message("DNS failure: CNAME loop for %s",
+ host);
+ if (CurEnv->e_message == NULL)
+ {
+ char ebuf[MAXLINE];
+
+ (void) sm_snprintf(ebuf,
+ sizeof ebuf,
+ "Deferred: DNS failure: CNAME loop for %.100s",
+ host);
+ CurEnv->e_message =
+ sm_rpool_strdup_x(
+ CurEnv->e_rpool, ebuf);
+ }
+ SM_SET_H_ERRNO(NO_RECOVERY);
+ *statp = EX_CONFIG;
+ return false;
+ }
+
+ /* value points at name */
+ if ((ret = dn_expand((unsigned char *)&answer,
+ eom, ap, (RES_UNC_T) nbuf,
+ sizeof(nbuf))) < 0)
+ break;
+ (void) sm_strlcpy(host, nbuf, hbsize);
+
+ /*
+ ** RFC 1034 section 3.6 specifies that CNAME
+ ** should point at the canonical name -- but
+ ** urges software to try again anyway.
+ */
+
+ goto cnameloop;
+
+ default:
+ /* not a record of interest */
+ continue;
+ }
+ }
+
+ if (amatch)
+ {
+ /*
+ ** Got a good match -- either an A, CNAME, or an
+ ** exact MX record. Save it and get out of here.
+ */
+
+ mxmatch = *dp;
+ break;
+ }
+
+ /*
+ ** Nothing definitive yet.
+ ** If this was a T_A query and we haven't yet found a MX
+ ** match, try T_MX if allowed to do so.
+ ** Otherwise, try the next domain.
+ */
+
+# if NETINET6
+ if (qtype == T_AAAA)
+ qtype = T_A;
+ else
+# endif /* NETINET6 */
+ if (qtype == T_A && !gotmx && (trymx || **dp == '\0'))
+ qtype = T_MX;
+ else
+ {
+ qtype = initial;
+ dp++;
+ }
+ }
+
+ /* if nothing was found, we are done */
+ if (mxmatch == NULL)
+ {
+ if (*statp == EX_OK)
+ *statp = EX_NOHOST;
+ return false;
+ }
+
+ /*
+ ** Create canonical name and return.
+ ** If saved domain name is null, name was already canonical.
+ ** Otherwise append the saved domain name.
+ */
+
+ (void) sm_snprintf(nbuf, sizeof nbuf, "%.*s%s%.*s", MAXDNAME, host,
+ *mxmatch == '\0' ? "" : ".",
+ MAXDNAME, mxmatch);
+ (void) sm_strlcpy(host, nbuf, hbsize);
+ if (tTd(8, 5))
+ sm_dprintf("dns_getcanonname: %s\n", host);
+ *statp = EX_OK;
+
+ /* return only one TTL entry, that should be sufficient */
+ if (ttl > 0 && pttl != NULL)
+ *pttl = ttl;
+ return true;
+}
+#endif /* NAMED_BIND */
diff --git a/usr/src/cmd/sendmail/src/envelope.c b/usr/src/cmd/sendmail/src/envelope.c
new file mode 100644
index 0000000000..925e0937c5
--- /dev/null
+++ b/usr/src/cmd/sendmail/src/envelope.c
@@ -0,0 +1,1259 @@
+/*
+ * Copyright (c) 1998-2003 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sendmail.h>
+
+SM_RCSID("@(#)$Id: envelope.c,v 8.294 2005/02/16 23:38:51 ca Exp $")
+
+/*
+** CLRSESSENVELOPE -- clear session oriented data in an envelope
+**
+** Parameters:
+** e -- the envelope to clear.
+**
+** Returns:
+** none.
+*/
+
+void
+clrsessenvelope(e)
+ ENVELOPE *e;
+{
+#if SASL
+ macdefine(&e->e_macro, A_PERM, macid("{auth_type}"), "");
+ macdefine(&e->e_macro, A_PERM, macid("{auth_authen}"), "");
+ macdefine(&e->e_macro, A_PERM, macid("{auth_author}"), "");
+ macdefine(&e->e_macro, A_PERM, macid("{auth_ssf}"), "");
+#endif /* SASL */
+#if STARTTLS
+ macdefine(&e->e_macro, A_PERM, macid("{cert_issuer}"), "");
+ macdefine(&e->e_macro, A_PERM, macid("{cert_subject}"), "");
+ macdefine(&e->e_macro, A_PERM, macid("{cipher_bits}"), "");
+ macdefine(&e->e_macro, A_PERM, macid("{cipher}"), "");
+ macdefine(&e->e_macro, A_PERM, macid("{tls_version}"), "");
+ macdefine(&e->e_macro, A_PERM, macid("{verify}"), "");
+# if _FFR_TLS_1
+ macdefine(&e->e_macro, A_PERM, macid("{alg_bits}"), "");
+ macdefine(&e->e_macro, A_PERM, macid("{cn_issuer}"), "");
+ macdefine(&e->e_macro, A_PERM, macid("{cn_subject}"), "");
+# endif /* _FFR_TLS_1 */
+#endif /* STARTTLS */
+}
+
+/*
+** NEWENVELOPE -- fill in a new envelope
+**
+** Supports inheritance.
+**
+** Parameters:
+** e -- the new envelope to fill in.
+** parent -- the envelope to be the parent of e.
+** rpool -- either NULL, or a pointer to a resource pool
+** from which envelope memory is allocated, and
+** to which envelope resources are attached.
+**
+** Returns:
+** e.
+**
+** Side Effects:
+** none.
+*/
+
+ENVELOPE *
+newenvelope(e, parent, rpool)
+ register ENVELOPE *e;
+ register ENVELOPE *parent;
+ SM_RPOOL_T *rpool;
+{
+ /*
+ ** This code used to read:
+ ** if (e == parent && e->e_parent != NULL)
+ ** parent = e->e_parent;
+ ** So if e == parent && e->e_parent == NULL then we would
+ ** set e->e_parent = e, which creates a loop in the e_parent chain.
+ ** This meant macvalue() could go into an infinite loop.
+ */
+
+ if (e == parent)
+ parent = e->e_parent;
+ clearenvelope(e, true, rpool);
+ if (e == CurEnv)
+ memmove((char *) &e->e_from,
+ (char *) &NullAddress,
+ sizeof e->e_from);
+ else
+ memmove((char *) &e->e_from,
+ (char *) &CurEnv->e_from,
+ sizeof e->e_from);
+ e->e_parent = parent;
+ assign_queueid(e);
+ e->e_ctime = curtime();
+ if (parent != NULL)
+ {
+ e->e_msgpriority = parent->e_msgsize;
+ if (parent->e_quarmsg == NULL)
+ {
+ e->e_quarmsg = NULL;
+ macdefine(&e->e_macro, A_PERM,
+ macid("{quarantine}"), "");
+ }
+ else
+ {
+ e->e_quarmsg = sm_rpool_strdup_x(rpool,
+ parent->e_quarmsg);
+ macdefine(&e->e_macro, A_PERM,
+ macid("{quarantine}"), e->e_quarmsg);
+ }
+ }
+ e->e_puthdr = putheader;
+ e->e_putbody = putbody;
+ if (CurEnv->e_xfp != NULL)
+ (void) sm_io_flush(CurEnv->e_xfp, SM_TIME_DEFAULT);
+
+ return e;
+}
+
+/* values for msg_timeout, see also IS_* below for usage (bit layout) */
+#define MSG_T_O 0x01 /* normal timeout */
+#define MSG_T_O_NOW 0x02 /* NOW timeout */
+#define MSG_NOT_BY 0x04 /* Deliver-By time exceeded, mode R */
+#define MSG_WARN 0x10 /* normal queue warning */
+#define MSG_WARN_BY 0x20 /* Deliver-By time exceeded, mode N */
+
+#define IS_MSG_ERR(x) (((x) & 0x0f) != 0) /* return an error */
+
+/* immediate return */
+#define IS_IMM_RET(x) (((x) & (MSG_T_O_NOW|MSG_NOT_BY)) != 0)
+#define IS_MSG_WARN(x) (((x) & 0xf0) != 0) /* return a warning */
+
+/*
+** DROPENVELOPE -- deallocate an envelope.
+**
+** Parameters:
+** e -- the envelope to deallocate.
+** fulldrop -- if set, do return receipts.
+** split -- if true, split by recipient if message is queued up
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** housekeeping necessary to dispose of an envelope.
+** Unlocks this queue file.
+*/
+
+void
+dropenvelope(e, fulldrop, split)
+ register ENVELOPE *e;
+ bool fulldrop;
+ bool split;
+{
+ bool panic = false;
+ bool queueit = false;
+ int msg_timeout = 0;
+ bool failure_return = false;
+ bool delay_return = false;
+ bool success_return = false;
+ bool pmnotify = bitset(EF_PM_NOTIFY, e->e_flags);
+ bool done = false;
+ register ADDRESS *q;
+ char *id = e->e_id;
+ time_t now;
+ char buf[MAXLINE];
+
+ if (tTd(50, 1))
+ {
+ sm_dprintf("dropenvelope %p: id=", e);
+ xputs(sm_debug_file(), e->e_id);
+ sm_dprintf(", flags=");
+ printenvflags(e);
+ if (tTd(50, 10))
+ {
+ sm_dprintf("sendq=");
+ printaddr(sm_debug_file(), e->e_sendqueue, true);
+ }
+ }
+
+ if (LogLevel > 84)
+ sm_syslog(LOG_DEBUG, id,
+ "dropenvelope, e_flags=0x%lx, OpMode=%c, pid=%d",
+ e->e_flags, OpMode, (int) CurrentPid);
+
+ /* we must have an id to remove disk files */
+ if (id == NULL)
+ return;
+
+ /* if verify-only mode, we can skip most of this */
+ if (OpMode == MD_VERIFY)
+ goto simpledrop;
+
+ if (LogLevel > 4 && bitset(EF_LOGSENDER, e->e_flags))
+ logsender(e, NULL);
+ e->e_flags &= ~EF_LOGSENDER;
+
+ /* post statistics */
+ poststats(StatFile);
+
+ /*
+ ** Extract state information from dregs of send list.
+ */
+
+ now = curtime();
+ if (now >= e->e_ctime + TimeOuts.to_q_return[e->e_timeoutclass])
+ msg_timeout = MSG_T_O;
+ if (IS_DLVR_RETURN(e) && e->e_deliver_by > 0 &&
+ now >= e->e_ctime + e->e_deliver_by &&
+ !bitset(EF_RESPONSE, e->e_flags))
+ {
+ msg_timeout = MSG_NOT_BY;
+ e->e_flags |= EF_FATALERRS|EF_CLRQUEUE;
+ }
+ else if (TimeOuts.to_q_return[e->e_timeoutclass] == NOW &&
+ !bitset(EF_RESPONSE, e->e_flags))
+ {
+ msg_timeout = MSG_T_O_NOW;
+ e->e_flags |= EF_FATALERRS|EF_CLRQUEUE;
+ }
+
+ e->e_flags &= ~EF_QUEUERUN;
+ for (q = e->e_sendqueue; q != NULL; q = q->q_next)
+ {
+ if (QS_IS_UNDELIVERED(q->q_state))
+ queueit = true;
+
+ /* see if a notification is needed */
+ if (bitset(QPINGONFAILURE, q->q_flags) &&
+ ((IS_MSG_ERR(msg_timeout) &&
+ QS_IS_UNDELIVERED(q->q_state)) ||
+ QS_IS_BADADDR(q->q_state) ||
+ IS_IMM_RET(msg_timeout)))
+ {
+ failure_return = true;
+ if (!done && q->q_owner == NULL &&
+ !emptyaddr(&e->e_from))
+ {
+ (void) sendtolist(e->e_from.q_paddr, NULLADDR,
+ &e->e_errorqueue, 0, e);
+ done = true;
+ }
+ }
+ else if ((bitset(QPINGONSUCCESS, q->q_flags) &&
+ ((QS_IS_SENT(q->q_state) &&
+ bitnset(M_LOCALMAILER, q->q_mailer->m_flags)) ||
+ bitset(QRELAYED|QEXPANDED|QDELIVERED, q->q_flags))) ||
+ bitset(QBYTRACE, q->q_flags) ||
+ bitset(QBYNRELAY, q->q_flags))
+ {
+ success_return = true;
+ }
+ }
+
+ if (e->e_class < 0)
+ e->e_flags |= EF_NO_BODY_RETN;
+
+ /*
+ ** See if the message timed out.
+ */
+
+ if (!queueit)
+ /* EMPTY */
+ /* nothing to do */ ;
+ else if (IS_MSG_ERR(msg_timeout))
+ {
+ if (failure_return)
+ {
+ if (msg_timeout == MSG_NOT_BY)
+ {
+ (void) sm_snprintf(buf, sizeof buf,
+ "delivery time expired %lds",
+ e->e_deliver_by);
+ }
+ else
+ {
+ (void) sm_snprintf(buf, sizeof buf,
+ "Cannot send message for %s",
+ pintvl(TimeOuts.to_q_return[e->e_timeoutclass],
+ false));
+ }
+
+ /* don't free, allocated from e_rpool */
+ e->e_message = sm_rpool_strdup_x(e->e_rpool, buf);
+ message(buf);
+ e->e_flags |= EF_CLRQUEUE;
+ }
+ if (msg_timeout == MSG_NOT_BY)
+ {
+ (void) sm_io_fprintf(e->e_xfp, SM_TIME_DEFAULT,
+ "Delivery time (%lds) expired\n",
+ e->e_deliver_by);
+ }
+ else
+ (void) sm_io_fprintf(e->e_xfp, SM_TIME_DEFAULT,
+ "Message could not be delivered for %s\n",
+ pintvl(TimeOuts.to_q_return[e->e_timeoutclass],
+ false));
+ (void) sm_io_fprintf(e->e_xfp, SM_TIME_DEFAULT,
+ "Message will be deleted from queue\n");
+ for (q = e->e_sendqueue; q != NULL; q = q->q_next)
+ {
+ if (QS_IS_UNDELIVERED(q->q_state))
+ {
+ q->q_state = QS_BADADDR;
+ if (msg_timeout == MSG_NOT_BY)
+ q->q_status = "5.4.7";
+ else
+ q->q_status = "4.4.7";
+ }
+ }
+ }
+ else
+ {
+ if (TimeOuts.to_q_warning[e->e_timeoutclass] > 0 &&
+ now >= e->e_ctime +
+ TimeOuts.to_q_warning[e->e_timeoutclass])
+ msg_timeout = MSG_WARN;
+ else if (IS_DLVR_NOTIFY(e) &&
+ e->e_deliver_by > 0 &&
+ now >= e->e_ctime + e->e_deliver_by)
+ msg_timeout = MSG_WARN_BY;
+
+ if (IS_MSG_WARN(msg_timeout))
+ {
+ if (!bitset(EF_WARNING|EF_RESPONSE, e->e_flags) &&
+ e->e_class >= 0 &&
+ e->e_from.q_paddr != NULL &&
+ strcmp(e->e_from.q_paddr, "<>") != 0 &&
+ sm_strncasecmp(e->e_from.q_paddr, "owner-", 6) != 0 &&
+ (strlen(e->e_from.q_paddr) <= 8 ||
+ sm_strcasecmp(&e->e_from.q_paddr[strlen(e->e_from.q_paddr) - 8],
+ "-request") != 0))
+ {
+ for (q = e->e_sendqueue; q != NULL;
+ q = q->q_next)
+ {
+ if (QS_IS_UNDELIVERED(q->q_state)
+#if _FFR_NODELAYDSN_ON_HOLD
+ && !bitnset(M_HOLD,
+ q->q_mailer->m_flags)
+#endif /* _FFR_NODELAYDSN_ON_HOLD */
+ )
+ {
+ if (msg_timeout ==
+ MSG_WARN_BY &&
+ (bitset(QPINGONDELAY,
+ q->q_flags) ||
+ !bitset(QHASNOTIFY,
+ q->q_flags))
+ )
+ {
+ q->q_flags |= QBYNDELAY;
+ delay_return = true;
+ }
+ if (bitset(QPINGONDELAY,
+ q->q_flags))
+ {
+ q->q_flags |= QDELAYED;
+ delay_return = true;
+ }
+ }
+ }
+ }
+ if (delay_return)
+ {
+ if (msg_timeout == MSG_WARN_BY)
+ {
+ (void) sm_snprintf(buf, sizeof buf,
+ "Warning: Delivery time (%lds) exceeded",
+ e->e_deliver_by);
+ }
+ else
+ (void) sm_snprintf(buf, sizeof buf,
+ "Warning: could not send message for past %s",
+ pintvl(TimeOuts.to_q_warning[e->e_timeoutclass],
+ false));
+
+ /* don't free, allocated from e_rpool */
+ e->e_message = sm_rpool_strdup_x(e->e_rpool,
+ buf);
+ message(buf);
+ e->e_flags |= EF_WARNING;
+ }
+ if (msg_timeout == MSG_WARN_BY)
+ {
+ (void) sm_io_fprintf(e->e_xfp, SM_TIME_DEFAULT,
+ "Warning: Delivery time (%lds) exceeded\n",
+ e->e_deliver_by);
+ }
+ else
+ (void) sm_io_fprintf(e->e_xfp, SM_TIME_DEFAULT,
+ "Warning: message still undelivered after %s\n",
+ pintvl(TimeOuts.to_q_warning[e->e_timeoutclass],
+ false));
+ (void) sm_io_fprintf(e->e_xfp, SM_TIME_DEFAULT,
+ "Will keep trying until message is %s old\n",
+ pintvl(TimeOuts.to_q_return[e->e_timeoutclass],
+ false));
+ }
+ }
+
+ if (tTd(50, 2))
+ sm_dprintf("failure_return=%d delay_return=%d success_return=%d queueit=%d\n",
+ failure_return, delay_return, success_return, queueit);
+
+ /*
+ ** If we had some fatal error, but no addresses are marked as
+ ** bad, mark them _all_ as bad.
+ */
+
+ if (bitset(EF_FATALERRS, e->e_flags) && !failure_return)
+ {
+ for (q = e->e_sendqueue; q != NULL; q = q->q_next)
+ {
+ if ((QS_IS_OK(q->q_state) ||
+ QS_IS_VERIFIED(q->q_state)) &&
+ bitset(QPINGONFAILURE, q->q_flags))
+ {
+ failure_return = true;
+ q->q_state = QS_BADADDR;
+ }
+ }
+ }
+
+ /*
+ ** Send back return receipts as requested.
+ */
+
+ if (success_return && !failure_return && !delay_return && fulldrop &&
+ !bitset(PRIV_NORECEIPTS, PrivacyFlags) &&
+ strcmp(e->e_from.q_paddr, "<>") != 0)
+ {
+ auto ADDRESS *rlist = NULL;
+
+ if (tTd(50, 8))
+ sm_dprintf("dropenvelope(%s): sending return receipt\n",
+ id);
+ e->e_flags |= EF_SENDRECEIPT;
+ (void) sendtolist(e->e_from.q_paddr, NULLADDR, &rlist, 0, e);
+ (void) returntosender("Return receipt", rlist, RTSF_NO_BODY, e);
+ }
+ e->e_flags &= ~EF_SENDRECEIPT;
+
+ /*
+ ** Arrange to send error messages if there are fatal errors.
+ */
+
+ if ((failure_return || delay_return) && e->e_errormode != EM_QUIET)
+ {
+ if (tTd(50, 8))
+ sm_dprintf("dropenvelope(%s): saving mail\n", id);
+ panic = savemail(e, !bitset(EF_NO_BODY_RETN, e->e_flags));
+ }
+
+ /*
+ ** Arrange to send warning messages to postmaster as requested.
+ */
+
+ if ((failure_return || pmnotify) &&
+ PostMasterCopy != NULL &&
+ !bitset(EF_RESPONSE, e->e_flags) &&
+ e->e_class >= 0)
+ {
+ auto ADDRESS *rlist = NULL;
+ char pcopy[MAXNAME];
+
+ if (failure_return)
+ {
+ expand(PostMasterCopy, pcopy, sizeof pcopy, e);
+
+ if (tTd(50, 8))
+ sm_dprintf("dropenvelope(%s): sending postmaster copy to %s\n",
+ id, pcopy);
+ (void) sendtolist(pcopy, NULLADDR, &rlist, 0, e);
+ }
+ if (pmnotify)
+ (void) sendtolist("postmaster", NULLADDR,
+ &rlist, 0, e);
+ (void) returntosender(e->e_message, rlist,
+ RTSF_PM_BOUNCE|RTSF_NO_BODY, e);
+ }
+
+ /*
+ ** Instantiate or deinstantiate the queue.
+ */
+
+simpledrop:
+ if (tTd(50, 8))
+ sm_dprintf("dropenvelope(%s): at simpledrop, queueit=%d\n",
+ id, queueit);
+ if (!queueit || bitset(EF_CLRQUEUE, e->e_flags))
+ {
+ if (tTd(50, 1))
+ {
+ sm_dprintf("\n===== Dropping queue files for %s... queueit=%d, e_flags=",
+ e->e_id, queueit);
+ printenvflags(e);
+ }
+ if (!panic)
+ (void) xunlink(queuename(e, DATAFL_LETTER));
+ if (panic && QueueMode == QM_LOST)
+ {
+ /*
+ ** leave the Qf file behind as
+ ** the delivery attempt failed.
+ */
+
+ /* EMPTY */
+ }
+ else
+ if (xunlink(queuename(e, ANYQFL_LETTER)) == 0)
+ {
+ /* add to available space in filesystem */
+ updfs(e, -1, panic ? 0 : -1, "dropenvelope");
+ }
+
+ if (e->e_ntries > 0 && LogLevel > 9)
+ sm_syslog(LOG_INFO, id, "done; delay=%s, ntries=%d",
+ pintvl(curtime() - e->e_ctime, true),
+ e->e_ntries);
+ }
+ else if (queueit || !bitset(EF_INQUEUE, e->e_flags))
+ {
+ if (!split)
+ queueup(e, false, true);
+ else
+ {
+ ENVELOPE *oldsib;
+ ENVELOPE *ee;
+
+ /*
+ ** Save old sibling and set it to NULL to avoid
+ ** queueing up the same envelopes again.
+ ** This requires that envelopes in that list have
+ ** been take care of before (or at some other place).
+ */
+
+ oldsib = e->e_sibling;
+ e->e_sibling = NULL;
+ if (!split_by_recipient(e) &&
+ bitset(EF_FATALERRS, e->e_flags))
+ {
+ syserr("!dropenvelope(%s): cannot commit data file %s, uid=%d",
+ e->e_id, queuename(e, DATAFL_LETTER),
+ (int) geteuid());
+ }
+ for (ee = e->e_sibling; ee != NULL; ee = ee->e_sibling)
+ queueup(ee, false, true);
+ queueup(e, false, true);
+
+ /* clean up */
+ for (ee = e->e_sibling; ee != NULL; ee = ee->e_sibling)
+ {
+ /* now unlock the job */
+ if (tTd(50, 8))
+ sm_dprintf("dropenvelope(%s): unlocking job\n",
+ ee->e_id);
+ closexscript(ee);
+ unlockqueue(ee);
+
+ /* this envelope is marked unused */
+ if (ee->e_dfp != NULL)
+ {
+ (void) sm_io_close(ee->e_dfp,
+ SM_TIME_DEFAULT);
+ ee->e_dfp = NULL;
+ }
+ ee->e_id = NULL;
+ ee->e_flags &= ~EF_HAS_DF;
+ }
+ e->e_sibling = oldsib;
+ }
+ }
+
+ /* now unlock the job */
+ if (tTd(50, 8))
+ sm_dprintf("dropenvelope(%s): unlocking job\n", id);
+ closexscript(e);
+ unlockqueue(e);
+
+ /* make sure that this envelope is marked unused */
+ if (e->e_dfp != NULL)
+ {
+ (void) sm_io_close(e->e_dfp, SM_TIME_DEFAULT);
+ e->e_dfp = NULL;
+ }
+ e->e_id = NULL;
+ e->e_flags &= ~EF_HAS_DF;
+}
+/*
+** CLEARENVELOPE -- clear an envelope without unlocking
+**
+** This is normally used by a child process to get a clean
+** envelope without disturbing the parent.
+**
+** Parameters:
+** e -- the envelope to clear.
+** fullclear - if set, the current envelope is total
+** garbage and should be ignored; otherwise,
+** release any resources it may indicate.
+** rpool -- either NULL, or a pointer to a resource pool
+** from which envelope memory is allocated, and
+** to which envelope resources are attached.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Closes files associated with the envelope.
+** Marks the envelope as unallocated.
+*/
+
+void
+clearenvelope(e, fullclear, rpool)
+ register ENVELOPE *e;
+ bool fullclear;
+ SM_RPOOL_T *rpool;
+{
+ register HDR *bh;
+ register HDR **nhp;
+ extern ENVELOPE BlankEnvelope;
+ char **p;
+
+ if (!fullclear)
+ {
+ /* clear out any file information */
+ if (e->e_xfp != NULL)
+ (void) sm_io_close(e->e_xfp, SM_TIME_DEFAULT);
+ if (e->e_dfp != NULL)
+ (void) sm_io_close(e->e_dfp, SM_TIME_DEFAULT);
+ e->e_xfp = e->e_dfp = NULL;
+ }
+
+ /*
+ ** Copy BlankEnvelope into *e.
+ ** It is not safe to simply copy pointers to strings;
+ ** the strings themselves must be copied (or set to NULL).
+ ** The problem is that when we assign a new string value to
+ ** a member of BlankEnvelope, we free the old string.
+ ** We did not need to do this copying in sendmail 8.11 :-(
+ ** and it is a potential performance hit. Reference counted
+ ** strings are one way out.
+ */
+
+ *e = BlankEnvelope;
+ e->e_message = NULL;
+ e->e_qfletter = '\0';
+ e->e_quarmsg = NULL;
+ macdefine(&e->e_macro, A_PERM, macid("{quarantine}"), "");
+
+ /*
+ ** Copy the macro table.
+ ** We might be able to avoid this by zeroing the macro table
+ ** and always searching BlankEnvelope.e_macro after e->e_macro
+ ** in macvalue().
+ */
+
+ for (p = &e->e_macro.mac_table[0];
+ p <= &e->e_macro.mac_table[MAXMACROID];
+ ++p)
+ {
+ if (*p != NULL)
+ *p = sm_rpool_strdup_x(rpool, *p);
+ }
+
+ /*
+ ** XXX There are many strings in the envelope structure
+ ** XXX that we are not attempting to copy here.
+ ** XXX Investigate this further.
+ */
+
+ e->e_rpool = rpool;
+ e->e_macro.mac_rpool = rpool;
+ if (Verbose)
+ set_delivery_mode(SM_DELIVER, e);
+ bh = BlankEnvelope.e_header;
+ nhp = &e->e_header;
+ while (bh != NULL)
+ {
+ *nhp = (HDR *) sm_rpool_malloc_x(rpool, sizeof *bh);
+ memmove((char *) *nhp, (char *) bh, sizeof *bh);
+ bh = bh->h_link;
+ nhp = &(*nhp)->h_link;
+ }
+}
+/*
+** INITSYS -- initialize instantiation of system
+**
+** In Daemon mode, this is done in the child.
+**
+** Parameters:
+** e -- the envelope to use.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Initializes the system macros, some global variables,
+** etc. In particular, the current time in various
+** forms is set.
+*/
+
+void
+initsys(e)
+ register ENVELOPE *e;
+{
+ char buf[10];
+#ifdef TTYNAME
+ static char ybuf[60]; /* holds tty id */
+ register char *p;
+ extern char *ttyname();
+#endif /* TTYNAME */
+
+ /*
+ ** Give this envelope a reality.
+ ** I.e., an id, a transcript, and a creation time.
+ ** We don't select the queue until all of the recipients are known.
+ */
+
+ openxscript(e);
+ e->e_ctime = curtime();
+ e->e_qfletter = '\0';
+
+ /*
+ ** Set OutChannel to something useful if stdout isn't it.
+ ** This arranges that any extra stuff the mailer produces
+ ** gets sent back to the user on error (because it is
+ ** tucked away in the transcript).
+ */
+
+ if (OpMode == MD_DAEMON && bitset(EF_QUEUERUN, e->e_flags) &&
+ e->e_xfp != NULL)
+ OutChannel = e->e_xfp;
+
+ /*
+ ** Set up some basic system macros.
+ */
+
+ /* process id */
+ (void) sm_snprintf(buf, sizeof buf, "%d", (int) CurrentPid);
+ macdefine(&e->e_macro, A_TEMP, 'p', buf);
+
+ /* hop count */
+ (void) sm_snprintf(buf, sizeof buf, "%d", e->e_hopcount);
+ macdefine(&e->e_macro, A_TEMP, 'c', buf);
+
+ /* time as integer, unix time, arpa time */
+ settime(e);
+
+ /* Load average */
+ sm_getla();
+
+#ifdef TTYNAME
+ /* tty name */
+ if (macvalue('y', e) == NULL)
+ {
+ p = ttyname(2);
+ if (p != NULL)
+ {
+ if (strrchr(p, '/') != NULL)
+ p = strrchr(p, '/') + 1;
+ (void) sm_strlcpy(ybuf, sizeof ybuf, p);
+ macdefine(&e->e_macro, A_PERM, 'y', ybuf);
+ }
+ }
+#endif /* TTYNAME */
+}
+/*
+** SETTIME -- set the current time.
+**
+** Parameters:
+** e -- the envelope in which the macros should be set.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Sets the various time macros -- $a, $b, $d, $t.
+*/
+
+void
+settime(e)
+ register ENVELOPE *e;
+{
+ register char *p;
+ auto time_t now;
+ char buf[30];
+ register struct tm *tm;
+
+ now = curtime();
+ (void) sm_snprintf(buf, sizeof buf, "%ld", (long) now);
+ macdefine(&e->e_macro, A_TEMP, macid("{time}"), buf);
+ tm = gmtime(&now);
+ (void) sm_snprintf(buf, sizeof buf, "%04d%02d%02d%02d%02d",
+ tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
+ tm->tm_hour, tm->tm_min);
+ macdefine(&e->e_macro, A_TEMP, 't', buf);
+ (void) sm_strlcpy(buf, ctime(&now), sizeof buf);
+ p = strchr(buf, '\n');
+ if (p != NULL)
+ *p = '\0';
+ macdefine(&e->e_macro, A_TEMP, 'd', buf);
+ macdefine(&e->e_macro, A_TEMP, 'b', arpadate(buf));
+ if (macvalue('a', e) == NULL)
+ macdefine(&e->e_macro, A_PERM, 'a', macvalue('b', e));
+}
+/*
+** OPENXSCRIPT -- Open transcript file
+**
+** Creates a transcript file for possible eventual mailing or
+** sending back.
+**
+** Parameters:
+** e -- the envelope to create the transcript in/for.
+**
+** Returns:
+** none
+**
+** Side Effects:
+** Creates the transcript file.
+*/
+
+#ifndef O_APPEND
+# define O_APPEND 0
+#endif /* ! O_APPEND */
+
+void
+openxscript(e)
+ register ENVELOPE *e;
+{
+ register char *p;
+
+ if (e->e_xfp != NULL)
+ return;
+
+#if 0
+ if (e->e_lockfp == NULL && bitset(EF_INQUEUE, e->e_flags))
+ syserr("openxscript: job not locked");
+#endif /* 0 */
+
+ p = queuename(e, XSCRPT_LETTER);
+ e->e_xfp = bfopen(p, FileMode, XscriptFileBufferSize,
+ SFF_NOTEXCL|SFF_OPENASROOT);
+
+ if (e->e_xfp == NULL)
+ {
+ syserr("Can't create transcript file %s", p);
+ e->e_xfp = sm_io_open(SmFtStdio, SM_TIME_DEFAULT,
+ SM_PATH_DEVNULL, SM_IO_RDWR, NULL);
+ if (e->e_xfp == NULL)
+ syserr("!Can't open %s", SM_PATH_DEVNULL);
+ }
+ (void) sm_io_setvbuf(e->e_xfp, SM_TIME_DEFAULT, NULL, SM_IO_LBF, 0);
+ if (tTd(46, 9))
+ {
+ sm_dprintf("openxscript(%s):\n ", p);
+ dumpfd(sm_io_getinfo(e->e_xfp, SM_IO_WHAT_FD, NULL), true,
+ false);
+ }
+}
+/*
+** CLOSEXSCRIPT -- close the transcript file.
+**
+** Parameters:
+** e -- the envelope containing the transcript to close.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** none.
+*/
+
+void
+closexscript(e)
+ register ENVELOPE *e;
+{
+ if (e->e_xfp == NULL)
+ return;
+#if 0
+ if (e->e_lockfp == NULL)
+ syserr("closexscript: job not locked");
+#endif /* 0 */
+ (void) sm_io_close(e->e_xfp, SM_TIME_DEFAULT);
+ e->e_xfp = NULL;
+}
+/*
+** SETSENDER -- set the person who this message is from
+**
+** Under certain circumstances allow the user to say who
+** s/he is (using -f or -r). These are:
+** 1. The user's uid is zero (root).
+** 2. The user's login name is in an approved list (typically
+** from a network server).
+** 3. The address the user is trying to claim has a
+** "!" character in it (since #2 doesn't do it for
+** us if we are dialing out for UUCP).
+** A better check to replace #3 would be if the
+** effective uid is "UUCP" -- this would require me
+** to rewrite getpwent to "grab" uucp as it went by,
+** make getname more nasty, do another passwd file
+** scan, or compile the UID of "UUCP" into the code,
+** all of which are reprehensible.
+**
+** Assuming all of these fail, we figure out something
+** ourselves.
+**
+** Parameters:
+** from -- the person we would like to believe this message
+** is from, as specified on the command line.
+** e -- the envelope in which we would like the sender set.
+** delimptr -- if non-NULL, set to the location of the
+** trailing delimiter.
+** delimchar -- the character that will delimit the sender
+** address.
+** internal -- set if this address is coming from an internal
+** source such as an owner alias.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** sets sendmail's notion of who the from person is.
+*/
+
+void
+setsender(from, e, delimptr, delimchar, internal)
+ char *from;
+ register ENVELOPE *e;
+ char **delimptr;
+ int delimchar;
+ bool internal;
+{
+ register char **pvp;
+ char *realname = NULL;
+ char *bp;
+ char buf[MAXNAME + 2];
+ char pvpbuf[PSBUFSIZE];
+ extern char *FullName;
+
+ if (tTd(45, 1))
+ sm_dprintf("setsender(%s)\n", from == NULL ? "" : from);
+
+ /* may be set from earlier calls */
+ macdefine(&e->e_macro, A_PERM, 'x', "");
+
+ /*
+ ** Figure out the real user executing us.
+ ** Username can return errno != 0 on non-errors.
+ */
+
+ if (bitset(EF_QUEUERUN, e->e_flags) || OpMode == MD_SMTP ||
+ OpMode == MD_ARPAFTP || OpMode == MD_DAEMON)
+ realname = from;
+ if (realname == NULL || realname[0] == '\0')
+ realname = username();
+
+ if (ConfigLevel < 2)
+ SuprErrs = true;
+
+ macdefine(&e->e_macro, A_PERM, macid("{addr_type}"), "e s");
+
+ /* preset state for then clause in case from == NULL */
+ e->e_from.q_state = QS_BADADDR;
+ e->e_from.q_flags = 0;
+ if (from == NULL ||
+ parseaddr(from, &e->e_from, RF_COPYALL|RF_SENDERADDR,
+ delimchar, delimptr, e, false) == NULL ||
+ QS_IS_BADADDR(e->e_from.q_state) ||
+ e->e_from.q_mailer == ProgMailer ||
+ e->e_from.q_mailer == FileMailer ||
+ e->e_from.q_mailer == InclMailer)
+ {
+ /* log garbage addresses for traceback */
+ if (from != NULL && LogLevel > 2)
+ {
+ char *p;
+ char ebuf[MAXNAME * 2 + 2];
+
+ p = macvalue('_', e);
+ if (p == NULL)
+ {
+ char *host = RealHostName;
+
+ if (host == NULL)
+ host = MyHostName;
+ (void) sm_snprintf(ebuf, sizeof ebuf,
+ "%.*s@%.*s", MAXNAME,
+ realname, MAXNAME, host);
+ p = ebuf;
+ }
+ sm_syslog(LOG_NOTICE, e->e_id,
+ "setsender: %s: invalid or unparsable, received from %s",
+ shortenstring(from, 83), p);
+ }
+ if (from != NULL)
+ {
+ if (!QS_IS_BADADDR(e->e_from.q_state))
+ {
+ /* it was a bogus mailer in the from addr */
+ e->e_status = "5.1.7";
+ usrerrenh(e->e_status,
+ "553 Invalid sender address");
+ }
+ SuprErrs = true;
+ }
+ if (from == realname ||
+ parseaddr(from = realname,
+ &e->e_from, RF_COPYALL|RF_SENDERADDR, ' ',
+ NULL, e, false) == NULL)
+ {
+ char nbuf[100];
+
+ SuprErrs = true;
+ expand("\201n", nbuf, sizeof nbuf, e);
+ from = sm_rpool_strdup_x(e->e_rpool, nbuf);
+ if (parseaddr(from, &e->e_from, RF_COPYALL, ' ',
+ NULL, e, false) == NULL &&
+ parseaddr(from = "postmaster", &e->e_from,
+ RF_COPYALL, ' ', NULL, e, false) == NULL)
+ syserr("553 5.3.0 setsender: can't even parse postmaster!");
+ }
+ }
+ else
+ FromFlag = true;
+ e->e_from.q_state = QS_SENDER;
+ if (tTd(45, 5))
+ {
+ sm_dprintf("setsender: QS_SENDER ");
+ printaddr(sm_debug_file(), &e->e_from, false);
+ }
+ SuprErrs = false;
+
+#if USERDB
+ if (bitnset(M_CHECKUDB, e->e_from.q_mailer->m_flags))
+ {
+ register char *p;
+
+ p = udbsender(e->e_from.q_user, e->e_rpool);
+ if (p != NULL)
+ from = p;
+ }
+#endif /* USERDB */
+
+ if (bitnset(M_HASPWENT, e->e_from.q_mailer->m_flags))
+ {
+ SM_MBDB_T user;
+
+ if (!internal)
+ {
+ /* if the user already given fullname don't redefine */
+ if (FullName == NULL)
+ FullName = macvalue('x', e);
+ if (FullName != NULL)
+ {
+ if (FullName[0] == '\0')
+ FullName = NULL;
+ else
+ FullName = newstr(FullName);
+ }
+ }
+
+ if (e->e_from.q_user[0] != '\0' &&
+ sm_mbdb_lookup(e->e_from.q_user, &user) == EX_OK)
+ {
+ /*
+ ** Process passwd file entry.
+ */
+
+ /* extract home directory */
+ if (*user.mbdb_homedir == '\0')
+ e->e_from.q_home = NULL;
+ else if (strcmp(user.mbdb_homedir, "/") == 0)
+ e->e_from.q_home = "";
+ else
+ e->e_from.q_home = sm_rpool_strdup_x(e->e_rpool,
+ user.mbdb_homedir);
+ macdefine(&e->e_macro, A_PERM, 'z', e->e_from.q_home);
+
+ /* extract user and group id */
+ if (user.mbdb_uid != SM_NO_UID)
+ {
+ e->e_from.q_uid = user.mbdb_uid;
+ e->e_from.q_gid = user.mbdb_gid;
+ e->e_from.q_flags |= QGOODUID;
+ }
+
+ /* extract full name from passwd file */
+ if (FullName == NULL && !internal &&
+ user.mbdb_fullname[0] != '\0' &&
+ strcmp(user.mbdb_name, e->e_from.q_user) == 0)
+ {
+ FullName = newstr(user.mbdb_fullname);
+ }
+ }
+ else
+ {
+ e->e_from.q_home = NULL;
+ }
+ if (FullName != NULL && !internal)
+ macdefine(&e->e_macro, A_TEMP, 'x', FullName);
+ }
+ else if (!internal && OpMode != MD_DAEMON && OpMode != MD_SMTP)
+ {
+ if (e->e_from.q_home == NULL)
+ {
+ e->e_from.q_home = getenv("HOME");
+ if (e->e_from.q_home != NULL)
+ {
+ if (*e->e_from.q_home == '\0')
+ e->e_from.q_home = NULL;
+ else if (strcmp(e->e_from.q_home, "/") == 0)
+ e->e_from.q_home++;
+ }
+ }
+ e->e_from.q_uid = RealUid;
+ e->e_from.q_gid = RealGid;
+ e->e_from.q_flags |= QGOODUID;
+ }
+
+ /*
+ ** Rewrite the from person to dispose of possible implicit
+ ** links in the net.
+ */
+
+ pvp = prescan(from, delimchar, pvpbuf, sizeof pvpbuf, NULL, NULL, false);
+ if (pvp == NULL)
+ {
+ /* don't need to give error -- prescan did that already */
+ if (LogLevel > 2)
+ sm_syslog(LOG_NOTICE, e->e_id,
+ "cannot prescan from (%s)",
+ shortenstring(from, MAXSHORTSTR));
+ finis(true, true, ExitStat);
+ }
+ (void) REWRITE(pvp, 3, e);
+ (void) REWRITE(pvp, 1, e);
+ (void) REWRITE(pvp, 4, e);
+ macdefine(&e->e_macro, A_PERM, macid("{addr_type}"), NULL);
+ bp = buf + 1;
+ cataddr(pvp, NULL, bp, sizeof buf - 2, '\0');
+ if (*bp == '@' && !bitnset(M_NOBRACKET, e->e_from.q_mailer->m_flags))
+ {
+ /* heuristic: route-addr: add angle brackets */
+ (void) sm_strlcat(bp, ">", sizeof buf - 1);
+ *--bp = '<';
+ }
+ e->e_sender = sm_rpool_strdup_x(e->e_rpool, bp);
+ macdefine(&e->e_macro, A_PERM, 'f', e->e_sender);
+
+ /* save the domain spec if this mailer wants it */
+ if (e->e_from.q_mailer != NULL &&
+ bitnset(M_CANONICAL, e->e_from.q_mailer->m_flags))
+ {
+ char **lastat;
+
+ /* get rid of any pesky angle brackets */
+ macdefine(&e->e_macro, A_PERM, macid("{addr_type}"), "e s");
+ (void) REWRITE(pvp, 3, e);
+ (void) REWRITE(pvp, 1, e);
+ (void) REWRITE(pvp, 4, e);
+ macdefine(&e->e_macro, A_PERM, macid("{addr_type}"), NULL);
+
+ /* strip off to the last "@" sign */
+ for (lastat = NULL; *pvp != NULL; pvp++)
+ {
+ if (strcmp(*pvp, "@") == 0)
+ lastat = pvp;
+ }
+ if (lastat != NULL)
+ {
+ e->e_fromdomain = copyplist(lastat, true, e->e_rpool);
+ if (tTd(45, 3))
+ {
+ sm_dprintf("Saving from domain: ");
+ printav(sm_debug_file(), e->e_fromdomain);
+ }
+ }
+ }
+}
+/*
+** PRINTENVFLAGS -- print envelope flags for debugging
+**
+** Parameters:
+** e -- the envelope with the flags to be printed.
+**
+** Returns:
+** none.
+*/
+
+struct eflags
+{
+ char *ef_name;
+ unsigned long ef_bit;
+};
+
+static struct eflags EnvelopeFlags[] =
+{
+ { "OLDSTYLE", EF_OLDSTYLE },
+ { "INQUEUE", EF_INQUEUE },
+ { "NO_BODY_RETN", EF_NO_BODY_RETN },
+ { "CLRQUEUE", EF_CLRQUEUE },
+ { "SENDRECEIPT", EF_SENDRECEIPT },
+ { "FATALERRS", EF_FATALERRS },
+ { "DELETE_BCC", EF_DELETE_BCC },
+ { "RESPONSE", EF_RESPONSE },
+ { "RESENT", EF_RESENT },
+ { "VRFYONLY", EF_VRFYONLY },
+ { "WARNING", EF_WARNING },
+ { "QUEUERUN", EF_QUEUERUN },
+ { "GLOBALERRS", EF_GLOBALERRS },
+ { "PM_NOTIFY", EF_PM_NOTIFY },
+ { "METOO", EF_METOO },
+ { "LOGSENDER", EF_LOGSENDER },
+ { "NORECEIPT", EF_NORECEIPT },
+ { "HAS8BIT", EF_HAS8BIT },
+ { "NL_NOT_EOL", EF_NL_NOT_EOL },
+ { "CRLF_NOT_EOL", EF_CRLF_NOT_EOL },
+ { "RET_PARAM", EF_RET_PARAM },
+ { "HAS_DF", EF_HAS_DF },
+ { "IS_MIME", EF_IS_MIME },
+ { "DONT_MIME", EF_DONT_MIME },
+ { "DISCARD", EF_DISCARD },
+ { "TOOBIG", EF_TOOBIG },
+ { "SPLIT", EF_SPLIT },
+ { "UNSAFE", EF_UNSAFE },
+ { NULL, 0 }
+};
+
+void
+printenvflags(e)
+ register ENVELOPE *e;
+{
+ register struct eflags *ef;
+ bool first = true;
+
+ sm_dprintf("%lx", e->e_flags);
+ for (ef = EnvelopeFlags; ef->ef_name != NULL; ef++)
+ {
+ if (!bitset(ef->ef_bit, e->e_flags))
+ continue;
+ if (first)
+ sm_dprintf("<%s", ef->ef_name);
+ else
+ sm_dprintf(",%s", ef->ef_name);
+ first = false;
+ }
+ if (!first)
+ sm_dprintf(">\n");
+}
diff --git a/usr/src/cmd/sendmail/src/err.c b/usr/src/cmd/sendmail/src/err.c
new file mode 100644
index 0000000000..aecaab7b2a
--- /dev/null
+++ b/usr/src/cmd/sendmail/src/err.c
@@ -0,0 +1,1163 @@
+/*
+ * Copyright (c) 1998-2003 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sendmail.h>
+
+SM_RCSID("@(#)$Id: err.c,v 8.191 2003/01/10 02:16:46 ca Exp $")
+
+#if LDAPMAP
+# include <lber.h>
+# include <ldap.h> /* for LDAP error codes */
+#endif /* LDAPMAP */
+
+static void putoutmsg __P((char *, bool, bool));
+static void puterrmsg __P((char *));
+static char *fmtmsg __P((char *, const char *, const char *, const char *,
+ int, const char *, va_list));
+
+/*
+** FATAL_ERROR -- handle a fatal exception
+**
+** This function is installed as the default exception handler
+** in the main sendmail process, and in all child processes
+** that we create. Its job is to handle exceptions that are not
+** handled at a lower level.
+**
+** The theory is that unhandled exceptions will be 'fatal' class
+** exceptions (with an "F:" prefix), such as the out-of-memory
+** exception "F:sm.heap". As such, they are handled by exiting
+** the process in exactly the same way that xalloc() in Sendmail 8.10
+** exits the process when it fails due to lack of memory:
+** we call syserr with a message beginning with "!".
+**
+** Parameters:
+** exc -- exception which is terminating this process
+**
+** Returns:
+** none
+*/
+
+void
+fatal_error(exc)
+ SM_EXC_T *exc;
+{
+ static char buf[256];
+ SM_FILE_T f;
+
+ /*
+ ** This function may be called when the heap is exhausted.
+ ** The following code writes the message for 'exc' into our
+ ** static buffer without allocating memory or raising exceptions.
+ */
+
+ sm_strio_init(&f, buf, sizeof(buf));
+ sm_exc_write(exc, &f);
+ (void) sm_io_flush(&f, SM_TIME_DEFAULT);
+
+ /*
+ ** Terminate the process after logging an error and cleaning up.
+ ** Problems:
+ ** - syserr decides what class of error this is by looking at errno.
+ ** That's no good; we should look at the exc structure.
+ ** - The cleanup code should be moved out of syserr
+ ** and into individual exception handlers
+ ** that are part of the module they clean up after.
+ */
+
+ errno = ENOMEM;
+ syserr("!%s", buf);
+}
+
+/*
+** SYSERR -- Print error message.
+**
+** Prints an error message via sm_io_printf to the diagnostic output.
+**
+** If the first character of the syserr message is `!' it will
+** log this as an ALERT message and exit immediately. This can
+** leave queue files in an indeterminate state, so it should not
+** be used lightly.
+**
+** If the first character of the syserr message is '!' or '@'
+** then syserr knows that the process is about to be terminated,
+** so the SMTP reply code defaults to 421. Otherwise, the
+** reply code defaults to 451 or 554, depending on errno.
+**
+** Parameters:
+** fmt -- the format string. An optional '!' or '@',
+** followed by an optional three-digit SMTP
+** reply code, followed by message text.
+** (others) -- parameters
+**
+** Returns:
+** none
+** Raises E:mta.quickabort if QuickAbort is set.
+**
+** Side Effects:
+** increments Errors.
+** sets ExitStat.
+*/
+
+char MsgBuf[BUFSIZ*2]; /* text of most recent message */
+static char HeldMessageBuf[sizeof MsgBuf]; /* for held messages */
+
+#if NAMED_BIND && !defined(NO_DATA)
+# define NO_DATA NO_ADDRESS
+#endif /* NAMED_BIND && !defined(NO_DATA) */
+
+void
+/*VARARGS1*/
+#ifdef __STDC__
+syserr(const char *fmt, ...)
+#else /* __STDC__ */
+syserr(fmt, va_alist)
+ const char *fmt;
+ va_dcl
+#endif /* __STDC__ */
+{
+ register char *p;
+ int save_errno = errno;
+ bool panic;
+ bool exiting;
+ char *user;
+ char *enhsc;
+ char *errtxt;
+ struct passwd *pw;
+ char ubuf[80];
+ SM_VA_LOCAL_DECL
+
+ switch (*fmt)
+ {
+ case '!':
+ ++fmt;
+ panic = true;
+ exiting = true;
+ break;
+ case '@':
+ ++fmt;
+ panic = false;
+ exiting = true;
+ break;
+ default:
+ panic = false;
+ exiting = false;
+ break;
+ }
+
+ /* format and output the error message */
+ if (exiting)
+ {
+ /*
+ ** Since we are terminating the process,
+ ** we are aborting the entire SMTP session,
+ ** rather than just the current transaction.
+ */
+
+ p = "421";
+ enhsc = "4.0.0";
+ }
+ else if (save_errno == 0)
+ {
+ p = "554";
+ enhsc = "5.0.0";
+ }
+ else
+ {
+ p = "451";
+ enhsc = "4.0.0";
+ }
+ SM_VA_START(ap, fmt);
+ errtxt = fmtmsg(MsgBuf, (char *) NULL, p, enhsc, save_errno, fmt, ap);
+ SM_VA_END(ap);
+ puterrmsg(MsgBuf);
+
+ /* save this message for mailq printing */
+ if (!panic && CurEnv != NULL)
+ {
+ char *nmsg = sm_rpool_strdup_x(CurEnv->e_rpool, errtxt);
+
+ if (CurEnv->e_rpool == NULL && CurEnv->e_message != NULL)
+ sm_free(CurEnv->e_message);
+ CurEnv->e_message = nmsg;
+ }
+
+ /* determine exit status if not already set */
+ if (ExitStat == EX_OK)
+ {
+ if (save_errno == 0)
+ ExitStat = EX_SOFTWARE;
+ else
+ ExitStat = EX_OSERR;
+ if (tTd(54, 1))
+ sm_dprintf("syserr: ExitStat = %d\n", ExitStat);
+ }
+
+ pw = sm_getpwuid(RealUid);
+ if (pw != NULL)
+ user = pw->pw_name;
+ else
+ {
+ user = ubuf;
+ (void) sm_snprintf(ubuf, sizeof ubuf, "UID%d", (int) RealUid);
+ }
+
+ if (LogLevel > 0)
+ sm_syslog(panic ? LOG_ALERT : LOG_CRIT,
+ CurEnv == NULL ? NOQID : CurEnv->e_id,
+ "SYSERR(%s): %.900s",
+ user, errtxt);
+ switch (save_errno)
+ {
+ case EBADF:
+ case ENFILE:
+ case EMFILE:
+ case ENOTTY:
+#ifdef EFBIG
+ case EFBIG:
+#endif /* EFBIG */
+#ifdef ESPIPE
+ case ESPIPE:
+#endif /* ESPIPE */
+#ifdef EPIPE
+ case EPIPE:
+#endif /* EPIPE */
+#ifdef ENOBUFS
+ case ENOBUFS:
+#endif /* ENOBUFS */
+#ifdef ESTALE
+ case ESTALE:
+#endif /* ESTALE */
+ printopenfds(true);
+ mci_dump_all(smioout, true);
+ break;
+ }
+ if (panic)
+ {
+#if XLA
+ xla_all_end();
+#endif /* XLA */
+ sync_queue_time();
+ if (tTd(0, 1))
+ abort();
+ exit(EX_OSERR);
+ }
+ errno = 0;
+ if (QuickAbort)
+ sm_exc_raisenew_x(&EtypeQuickAbort, 2);
+}
+/*
+** USRERR -- Signal user error.
+**
+** This is much like syserr except it is for user errors.
+**
+** Parameters:
+** fmt -- the format string. If it does not begin with
+** a three-digit SMTP reply code, 550 is assumed.
+** (others) -- sm_io_printf strings
+**
+** Returns:
+** none
+** Raises E:mta.quickabort if QuickAbort is set.
+**
+** Side Effects:
+** increments Errors.
+*/
+
+/*VARARGS1*/
+void
+#ifdef __STDC__
+usrerr(const char *fmt, ...)
+#else /* __STDC__ */
+usrerr(fmt, va_alist)
+ const char *fmt;
+ va_dcl
+#endif /* __STDC__ */
+{
+ char *enhsc;
+ char *errtxt;
+ SM_VA_LOCAL_DECL
+
+ if (fmt[0] == '5' || fmt[0] == '6')
+ enhsc = "5.0.0";
+ else if (fmt[0] == '4' || fmt[0] == '8')
+ enhsc = "4.0.0";
+ else if (fmt[0] == '2')
+ enhsc = "2.0.0";
+ else
+ enhsc = NULL;
+ SM_VA_START(ap, fmt);
+ errtxt = fmtmsg(MsgBuf, CurEnv->e_to, "550", enhsc, 0, fmt, ap);
+ SM_VA_END(ap);
+
+ if (SuprErrs)
+ return;
+
+ /* save this message for mailq printing */
+ switch (MsgBuf[0])
+ {
+ case '4':
+ case '8':
+ if (CurEnv->e_message != NULL)
+ break;
+
+ /* FALLTHROUGH */
+
+ case '5':
+ case '6':
+ if (CurEnv->e_rpool == NULL && CurEnv->e_message != NULL)
+ sm_free(CurEnv->e_message);
+ if (MsgBuf[0] == '6')
+ {
+ char buf[MAXLINE];
+
+ (void) sm_snprintf(buf, sizeof buf,
+ "Postmaster warning: %.*s",
+ (int) sizeof buf - 22, errtxt);
+ CurEnv->e_message =
+ sm_rpool_strdup_x(CurEnv->e_rpool, buf);
+ }
+ else
+ {
+ CurEnv->e_message =
+ sm_rpool_strdup_x(CurEnv->e_rpool, errtxt);
+ }
+ break;
+ }
+
+ puterrmsg(MsgBuf);
+ if (LogLevel > 3 && LogUsrErrs)
+ sm_syslog(LOG_NOTICE, CurEnv->e_id, "%.900s", errtxt);
+ if (QuickAbort)
+ sm_exc_raisenew_x(&EtypeQuickAbort, 1);
+}
+/*
+** USRERRENH -- Signal user error.
+**
+** Same as usrerr but with enhanced status code.
+**
+** Parameters:
+** enhsc -- the enhanced status code.
+** fmt -- the format string. If it does not begin with
+** a three-digit SMTP reply code, 550 is assumed.
+** (others) -- sm_io_printf strings
+**
+** Returns:
+** none
+** Raises E:mta.quickabort if QuickAbort is set.
+**
+** Side Effects:
+** increments Errors.
+*/
+
+/*VARARGS1*/
+void
+#ifdef __STDC__
+usrerrenh(char *enhsc, const char *fmt, ...)
+#else /* __STDC__ */
+usrerrenh(enhsc, fmt, va_alist)
+ char *enhsc;
+ const char *fmt;
+ va_dcl
+#endif /* __STDC__ */
+{
+ char *errtxt;
+ SM_VA_LOCAL_DECL
+
+ if (enhsc == NULL || *enhsc == '\0')
+ {
+ if (fmt[0] == '5' || fmt[0] == '6')
+ enhsc = "5.0.0";
+ else if (fmt[0] == '4' || fmt[0] == '8')
+ enhsc = "4.0.0";
+ else if (fmt[0] == '2')
+ enhsc = "2.0.0";
+ }
+ SM_VA_START(ap, fmt);
+ errtxt = fmtmsg(MsgBuf, CurEnv->e_to, "550", enhsc, 0, fmt, ap);
+ SM_VA_END(ap);
+
+ if (SuprErrs)
+ return;
+
+ /* save this message for mailq printing */
+ switch (MsgBuf[0])
+ {
+ case '4':
+ case '8':
+ if (CurEnv->e_message != NULL)
+ break;
+
+ /* FALLTHROUGH */
+
+ case '5':
+ case '6':
+ if (CurEnv->e_rpool == NULL && CurEnv->e_message != NULL)
+ sm_free(CurEnv->e_message);
+ if (MsgBuf[0] == '6')
+ {
+ char buf[MAXLINE];
+
+ (void) sm_snprintf(buf, sizeof buf,
+ "Postmaster warning: %.*s",
+ (int) sizeof buf - 22, errtxt);
+ CurEnv->e_message =
+ sm_rpool_strdup_x(CurEnv->e_rpool, buf);
+ }
+ else
+ {
+ CurEnv->e_message =
+ sm_rpool_strdup_x(CurEnv->e_rpool, errtxt);
+ }
+ break;
+ }
+
+ puterrmsg(MsgBuf);
+ if (LogLevel > 3 && LogUsrErrs)
+ sm_syslog(LOG_NOTICE, CurEnv->e_id, "%.900s", errtxt);
+ if (QuickAbort)
+ sm_exc_raisenew_x(&EtypeQuickAbort, 1);
+}
+/*
+** MESSAGE -- print message (not necessarily an error)
+**
+** Parameters:
+** msg -- the message (sm_io_printf fmt) -- it can begin with
+** an SMTP reply code. If not, 050 is assumed.
+** (others) -- sm_io_printf arguments
+**
+** Returns:
+** none
+**
+** Side Effects:
+** none.
+*/
+
+/*VARARGS1*/
+void
+#ifdef __STDC__
+message(const char *msg, ...)
+#else /* __STDC__ */
+message(msg, va_alist)
+ const char *msg;
+ va_dcl
+#endif /* __STDC__ */
+{
+ char *errtxt;
+ SM_VA_LOCAL_DECL
+
+ errno = 0;
+ SM_VA_START(ap, msg);
+ errtxt = fmtmsg(MsgBuf, CurEnv->e_to, "050", (char *) NULL, 0, msg, ap);
+ SM_VA_END(ap);
+ putoutmsg(MsgBuf, false, false);
+
+ /* save this message for mailq printing */
+ switch (MsgBuf[0])
+ {
+ case '4':
+ case '8':
+ if (CurEnv->e_message != NULL)
+ break;
+ /* FALLTHROUGH */
+
+ case '5':
+ if (CurEnv->e_rpool == NULL && CurEnv->e_message != NULL)
+ sm_free(CurEnv->e_message);
+ CurEnv->e_message =
+ sm_rpool_strdup_x(CurEnv->e_rpool, errtxt);
+ break;
+ }
+}
+/*
+** NMESSAGE -- print message (not necessarily an error)
+**
+** Just like "message" except it never puts the to... tag on.
+**
+** Parameters:
+** msg -- the message (sm_io_printf fmt) -- if it begins
+** with a three digit SMTP reply code, that is used,
+** otherwise 050 is assumed.
+** (others) -- sm_io_printf arguments
+**
+** Returns:
+** none
+**
+** Side Effects:
+** none.
+*/
+
+/*VARARGS1*/
+void
+#ifdef __STDC__
+nmessage(const char *msg, ...)
+#else /* __STDC__ */
+nmessage(msg, va_alist)
+ const char *msg;
+ va_dcl
+#endif /* __STDC__ */
+{
+ char *errtxt;
+ SM_VA_LOCAL_DECL
+
+ errno = 0;
+ SM_VA_START(ap, msg);
+ errtxt = fmtmsg(MsgBuf, (char *) NULL, "050",
+ (char *) NULL, 0, msg, ap);
+ SM_VA_END(ap);
+ putoutmsg(MsgBuf, false, false);
+
+ /* save this message for mailq printing */
+ switch (MsgBuf[0])
+ {
+ case '4':
+ case '8':
+ if (CurEnv->e_message != NULL)
+ break;
+ /* FALLTHROUGH */
+
+ case '5':
+ if (CurEnv->e_rpool == NULL && CurEnv->e_message != NULL)
+ sm_free(CurEnv->e_message);
+ CurEnv->e_message =
+ sm_rpool_strdup_x(CurEnv->e_rpool, errtxt);
+ break;
+ }
+}
+/*
+** PUTOUTMSG -- output error message to transcript and channel
+**
+** Parameters:
+** msg -- message to output (in SMTP format).
+** holdmsg -- if true, don't output a copy of the message to
+** our output channel.
+** heldmsg -- if true, this is a previously held message;
+** don't log it to the transcript file.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Outputs msg to the transcript.
+** If appropriate, outputs it to the channel.
+** Deletes SMTP reply code number as appropriate.
+*/
+
+static void
+putoutmsg(msg, holdmsg, heldmsg)
+ char *msg;
+ bool holdmsg;
+ bool heldmsg;
+{
+ char *errtxt = msg;
+ char msgcode = msg[0];
+
+ /* display for debugging */
+ if (tTd(54, 8))
+ sm_dprintf("--- %s%s%s\n", msg, holdmsg ? " (hold)" : "",
+ heldmsg ? " (held)" : "");
+
+ /* map warnings to something SMTP can handle */
+ if (msgcode == '6')
+ msg[0] = '5';
+ else if (msgcode == '8')
+ msg[0] = '4';
+
+ /* output to transcript if serious */
+ if (!heldmsg && CurEnv != NULL && CurEnv->e_xfp != NULL &&
+ strchr("45", msg[0]) != NULL)
+ (void) sm_io_fprintf(CurEnv->e_xfp, SM_TIME_DEFAULT, "%s\n",
+ msg);
+
+ if (LogLevel > 14 && (OpMode == MD_SMTP || OpMode == MD_DAEMON))
+ sm_syslog(LOG_INFO, CurEnv->e_id,
+ "--- %s%s%s", msg, holdmsg ? " (hold)" : "",
+ heldmsg ? " (held)" : "");
+
+ if (msgcode == '8')
+ msg[0] = '0';
+
+ /* output to channel if appropriate */
+ if (!Verbose && msg[0] == '0')
+ return;
+ if (holdmsg)
+ {
+ /* save for possible future display */
+ msg[0] = msgcode;
+ if (HeldMessageBuf[0] == '5' && msgcode == '4')
+ return;
+ (void) sm_strlcpy(HeldMessageBuf, msg, sizeof HeldMessageBuf);
+ return;
+ }
+
+ (void) sm_io_flush(smioout, SM_TIME_DEFAULT);
+
+ if (OutChannel == NULL)
+ return;
+
+ /* find actual text of error (after SMTP status codes) */
+ if (ISSMTPREPLY(errtxt))
+ {
+ int l;
+
+ errtxt += 4;
+ l = isenhsc(errtxt, ' ');
+ if (l <= 0)
+ l = isenhsc(errtxt, '\0');
+ if (l > 0)
+ errtxt += l + 1;
+ }
+
+ /* if DisConnected, OutChannel now points to the transcript */
+ if (!DisConnected &&
+ (OpMode == MD_SMTP || OpMode == MD_DAEMON || OpMode == MD_ARPAFTP))
+ (void) sm_io_fprintf(OutChannel, SM_TIME_DEFAULT, "%s\r\n",
+ msg);
+ else
+ (void) sm_io_fprintf(OutChannel, SM_TIME_DEFAULT, "%s\n",
+ errtxt);
+ if (TrafficLogFile != NULL)
+ (void) sm_io_fprintf(TrafficLogFile, SM_TIME_DEFAULT,
+ "%05d >>> %s\n", (int) CurrentPid,
+ (OpMode == MD_SMTP || OpMode == MD_DAEMON)
+ ? msg : errtxt);
+#if !PIPELINING
+ /* XXX can't flush here for SMTP pipelining */
+ if (msg[3] == ' ')
+ (void) sm_io_flush(OutChannel, SM_TIME_DEFAULT);
+ if (!sm_io_error(OutChannel) || DisConnected)
+ return;
+
+ /*
+ ** Error on output -- if reporting lost channel, just ignore it.
+ ** Also, ignore errors from QUIT response (221 message) -- some
+ ** rude servers don't read result.
+ */
+
+ if (InChannel == NULL || sm_io_eof(InChannel) ||
+ sm_io_error(InChannel) || strncmp(msg, "221", 3) == 0)
+ return;
+
+ /* can't call syserr, 'cause we are using MsgBuf */
+ HoldErrs = true;
+ if (LogLevel > 0)
+ sm_syslog(LOG_CRIT, CurEnv->e_id,
+ "SYSERR: putoutmsg (%s): error on output channel sending \"%s\": %s",
+ CURHOSTNAME,
+ shortenstring(msg, MAXSHORTSTR), sm_errstring(errno));
+#endif /* !PIPELINING */
+}
+/*
+** PUTERRMSG -- like putoutmsg, but does special processing for error messages
+**
+** Parameters:
+** msg -- the message to output.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Sets the fatal error bit in the envelope as appropriate.
+*/
+
+static void
+puterrmsg(msg)
+ char *msg;
+{
+ char msgcode = msg[0];
+
+ /* output the message as usual */
+ putoutmsg(msg, HoldErrs, false);
+
+ /* be careful about multiple error messages */
+ if (OnlyOneError)
+ HoldErrs = true;
+
+ /* signal the error */
+ Errors++;
+
+ if (CurEnv == NULL)
+ return;
+
+ if (msgcode == '6')
+ {
+ /* notify the postmaster */
+ CurEnv->e_flags |= EF_PM_NOTIFY;
+ }
+ else if (msgcode == '5' && bitset(EF_GLOBALERRS, CurEnv->e_flags))
+ {
+ /* mark long-term fatal errors */
+ CurEnv->e_flags |= EF_FATALERRS;
+ }
+}
+/*
+** ISENHSC -- check whether a string contains an enhanced status code
+**
+** Parameters:
+** s -- string with possible enhanced status code.
+** delim -- delim for enhanced status code.
+**
+** Returns:
+** 0 -- no enhanced status code.
+** >4 -- length of enhanced status code.
+**
+** Side Effects:
+** none.
+*/
+int
+isenhsc(s, delim)
+ const char *s;
+ int delim;
+{
+ int l, h;
+
+ if (s == NULL)
+ return 0;
+ if (!((*s == '2' || *s == '4' || *s == '5') && s[1] == '.'))
+ return 0;
+ h = 0;
+ l = 2;
+ while (h < 3 && isascii(s[l + h]) && isdigit(s[l + h]))
+ ++h;
+ if (h == 0 || s[l + h] != '.')
+ return 0;
+ l += h + 1;
+ h = 0;
+ while (h < 3 && isascii(s[l + h]) && isdigit(s[l + h]))
+ ++h;
+ if (h == 0 || s[l + h] != delim)
+ return 0;
+ return l + h;
+}
+/*
+** EXTENHSC -- check and extract an enhanced status code
+**
+** Parameters:
+** s -- string with possible enhanced status code.
+** delim -- delim for enhanced status code.
+** e -- pointer to storage for enhanced status code.
+** must be != NULL and have space for at least
+** 10 characters ([245].[0-9]{1,3}.[0-9]{1,3})
+**
+** Returns:
+** 0 -- no enhanced status code.
+** >4 -- length of enhanced status code.
+**
+** Side Effects:
+** fills e with enhanced status code.
+*/
+
+int
+extenhsc(s, delim, e)
+ const char *s;
+ int delim;
+ char *e;
+{
+ int l, h;
+
+ if (s == NULL)
+ return 0;
+ if (!((*s == '2' || *s == '4' || *s == '5') && s[1] == '.'))
+ return 0;
+ h = 0;
+ l = 2;
+ e[0] = s[0];
+ e[1] = '.';
+ while (h < 3 && isascii(s[l + h]) && isdigit(s[l + h]))
+ {
+ e[l + h] = s[l + h];
+ ++h;
+ }
+ if (h == 0 || s[l + h] != '.')
+ return 0;
+ e[l + h] = '.';
+ l += h + 1;
+ h = 0;
+ while (h < 3 && isascii(s[l + h]) && isdigit(s[l + h]))
+ {
+ e[l + h] = s[l + h];
+ ++h;
+ }
+ if (h == 0 || s[l + h] != delim)
+ return 0;
+ e[l + h] = '\0';
+ return l + h;
+}
+/*
+** FMTMSG -- format a message into buffer.
+**
+** Parameters:
+** eb -- error buffer to get result -- MUST BE MsgBuf.
+** to -- the recipient tag for this message.
+** num -- default three digit SMTP reply code.
+** enhsc -- enhanced status code.
+** en -- the error number to display.
+** fmt -- format of string.
+** ap -- arguments for fmt.
+**
+** Returns:
+** pointer to error text beyond status codes.
+**
+** Side Effects:
+** none.
+*/
+
+static char *
+fmtmsg(eb, to, num, enhsc, eno, fmt, ap)
+ register char *eb;
+ const char *to;
+ const char *num;
+ const char *enhsc;
+ int eno;
+ const char *fmt;
+ SM_VA_LOCAL_DECL
+{
+ char del;
+ int l;
+ int spaceleft = sizeof MsgBuf;
+ char *errtxt;
+
+ /* output the reply code */
+ if (ISSMTPCODE(fmt))
+ {
+ num = fmt;
+ fmt += 4;
+ }
+ if (num[3] == '-')
+ del = '-';
+ else
+ del = ' ';
+#if _FFR_SOFT_BOUNCE
+ if (SoftBounce && num[0] == '5')
+ {
+ /* replace 5 by 4 */
+ (void) sm_snprintf(eb, spaceleft, "4%2.2s%c", num + 1, del);
+ }
+ else
+#endif /* _FFR_SOFT_BOUNCE */
+ (void) sm_snprintf(eb, spaceleft, "%3.3s%c", num, del);
+ eb += 4;
+ spaceleft -= 4;
+
+ if ((l = isenhsc(fmt, ' ' )) > 0 && l < spaceleft - 4)
+ {
+ /* copy enh.status code including trailing blank */
+ l++;
+ (void) sm_strlcpy(eb, fmt, l + 1);
+ eb += l;
+ spaceleft -= l;
+ fmt += l;
+ }
+ else if ((l = isenhsc(enhsc, '\0')) > 0 && l < spaceleft - 4)
+ {
+ /* copy enh.status code */
+ (void) sm_strlcpy(eb, enhsc, l + 1);
+ eb[l] = ' ';
+ eb[++l] = '\0';
+ eb += l;
+ spaceleft -= l;
+ }
+#if _FFR_SOFT_BOUNCE
+ if (SoftBounce && eb[-l] == '5')
+ {
+ /* replace 5 by 4 */
+ eb[-l] = '4';
+ }
+#endif /* _FFR_SOFT_BOUNCE */
+ errtxt = eb;
+
+ /* output the file name and line number */
+ if (FileName != NULL)
+ {
+ (void) sm_snprintf(eb, spaceleft, "%s: line %d: ",
+ shortenstring(FileName, 83), LineNumber);
+ eb += (l = strlen(eb));
+ spaceleft -= l;
+ }
+
+ /*
+ ** output the "to" address only if it is defined and one of the
+ ** following codes is used:
+ ** 050 internal notices, e.g., alias expansion
+ ** 250 Ok
+ ** 252 Cannot VRFY user, but will accept message and attempt delivery
+ ** 450 Requested mail action not taken: mailbox unavailable
+ ** 550 Requested action not taken: mailbox unavailable
+ ** 553 Requested action not taken: mailbox name not allowed
+ **
+ ** Notice: this still isn't "the right thing", this code shouldn't
+ ** (indirectly) depend on CurEnv->e_to.
+ */
+
+ if (to != NULL && to[0] != '\0' &&
+ (strncmp(num, "050", 3) == 0 ||
+ strncmp(num, "250", 3) == 0 ||
+ strncmp(num, "252", 3) == 0 ||
+ strncmp(num, "450", 3) == 0 ||
+ strncmp(num, "550", 3) == 0 ||
+ strncmp(num, "553", 3) == 0))
+ {
+ (void) sm_strlcpyn(eb, spaceleft, 2,
+ shortenstring(to, MAXSHORTSTR), "... ");
+ spaceleft -= strlen(eb);
+ while (*eb != '\0')
+ *eb++ &= 0177;
+ }
+
+ /* output the message */
+ (void) sm_vsnprintf(eb, spaceleft, fmt, ap);
+ spaceleft -= strlen(eb);
+ while (*eb != '\0')
+ *eb++ &= 0177;
+
+ /* output the error code, if any */
+ if (eno != 0)
+ (void) sm_strlcpyn(eb, spaceleft, 2, ": ", sm_errstring(eno));
+
+ return errtxt;
+}
+/*
+** BUFFER_ERRORS -- arrange to buffer future error messages
+**
+** Parameters:
+** none
+**
+** Returns:
+** none.
+*/
+
+void
+buffer_errors()
+{
+ HeldMessageBuf[0] = '\0';
+ HoldErrs = true;
+}
+/*
+** FLUSH_ERRORS -- flush the held error message buffer
+**
+** Parameters:
+** print -- if set, print the message, otherwise just
+** delete it.
+**
+** Returns:
+** none.
+*/
+
+void
+flush_errors(print)
+ bool print;
+{
+ if (print && HeldMessageBuf[0] != '\0')
+ putoutmsg(HeldMessageBuf, false, true);
+ HeldMessageBuf[0] = '\0';
+ HoldErrs = false;
+}
+/*
+** SM_ERRSTRING -- return string description of error code
+**
+** Parameters:
+** errnum -- the error number to translate
+**
+** Returns:
+** A string description of errnum.
+**
+** Side Effects:
+** none.
+*/
+
+const char *
+sm_errstring(errnum)
+ int errnum;
+{
+ char *dnsmsg;
+ char *bp;
+ static char buf[MAXLINE];
+#if HASSTRERROR
+ char *err;
+ char errbuf[30];
+#endif /* HASSTRERROR */
+#if !HASSTRERROR && !defined(ERRLIST_PREDEFINED)
+ extern char *sys_errlist[];
+ extern int sys_nerr;
+#endif /* !HASSTRERROR && !defined(ERRLIST_PREDEFINED) */
+
+ /*
+ ** Handle special network error codes.
+ **
+ ** These are 4.2/4.3bsd specific; they should be in daemon.c.
+ */
+
+ dnsmsg = NULL;
+ switch (errnum)
+ {
+ case ETIMEDOUT:
+ case ECONNRESET:
+ bp = buf;
+#if HASSTRERROR
+ err = strerror(errnum);
+ if (err == NULL)
+ {
+ (void) sm_snprintf(errbuf, sizeof errbuf,
+ "Error %d", errnum);
+ err = errbuf;
+ }
+ (void) sm_strlcpy(bp, err, SPACELEFT(buf, bp));
+#else /* HASSTRERROR */
+ if (errnum >= 0 && errnum < sys_nerr)
+ (void) sm_strlcpy(bp, sys_errlist[errnum],
+ SPACELEFT(buf, bp));
+ else
+ (void) sm_snprintf(bp, SPACELEFT(buf, bp),
+ "Error %d", errnum);
+#endif /* HASSTRERROR */
+ bp += strlen(bp);
+ if (CurHostName != NULL)
+ {
+ if (errnum == ETIMEDOUT)
+ {
+ (void) sm_snprintf(bp, SPACELEFT(buf, bp),
+ " with ");
+ bp += strlen(bp);
+ }
+ else
+ {
+ bp = buf;
+ (void) sm_snprintf(bp, SPACELEFT(buf, bp),
+ "Connection reset by ");
+ bp += strlen(bp);
+ }
+ (void) sm_strlcpy(bp,
+ shortenstring(CurHostName, MAXSHORTSTR),
+ SPACELEFT(buf, bp));
+ bp += strlen(buf);
+ }
+ if (SmtpPhase != NULL)
+ {
+ (void) sm_snprintf(bp, SPACELEFT(buf, bp),
+ " during %s", SmtpPhase);
+ }
+ return buf;
+
+ case EHOSTDOWN:
+ if (CurHostName == NULL)
+ break;
+ (void) sm_snprintf(buf, sizeof buf, "Host %s is down",
+ shortenstring(CurHostName, MAXSHORTSTR));
+ return buf;
+
+ case ECONNREFUSED:
+ if (CurHostName == NULL)
+ break;
+ (void) sm_strlcpyn(buf, sizeof buf, 2, "Connection refused by ",
+ shortenstring(CurHostName, MAXSHORTSTR));
+ return buf;
+
+#if NAMED_BIND
+ case HOST_NOT_FOUND + E_DNSBASE:
+ dnsmsg = "host not found";
+ break;
+
+ case TRY_AGAIN + E_DNSBASE:
+ dnsmsg = "host name lookup failure";
+ break;
+
+ case NO_RECOVERY + E_DNSBASE:
+ dnsmsg = "non-recoverable error";
+ break;
+
+ case NO_DATA + E_DNSBASE:
+ dnsmsg = "no data known";
+ break;
+#endif /* NAMED_BIND */
+
+ case EPERM:
+ /* SunOS gives "Not owner" -- this is the POSIX message */
+ return "Operation not permitted";
+
+ /*
+ ** Error messages used internally in sendmail.
+ */
+
+ case E_SM_OPENTIMEOUT:
+ return "Timeout on file open";
+
+ case E_SM_NOSLINK:
+ return "Symbolic links not allowed";
+
+ case E_SM_NOHLINK:
+ return "Hard links not allowed";
+
+ case E_SM_REGONLY:
+ return "Regular files only";
+
+ case E_SM_ISEXEC:
+ return "Executable files not allowed";
+
+ case E_SM_WWDIR:
+ return "World writable directory";
+
+ case E_SM_GWDIR:
+ return "Group writable directory";
+
+ case E_SM_FILECHANGE:
+ return "File changed after open";
+
+ case E_SM_WWFILE:
+ return "World writable file";
+
+ case E_SM_GWFILE:
+ return "Group writable file";
+
+ case E_SM_GRFILE:
+ return "Group readable file";
+
+ case E_SM_WRFILE:
+ return "World readable file";
+ }
+
+ if (dnsmsg != NULL)
+ {
+ bp = buf;
+ bp += sm_strlcpy(bp, "Name server: ", sizeof buf);
+ if (CurHostName != NULL)
+ {
+ (void) sm_strlcpyn(bp, SPACELEFT(buf, bp), 2,
+ shortenstring(CurHostName, MAXSHORTSTR), ": ");
+ bp += strlen(bp);
+ }
+ (void) sm_strlcpy(bp, dnsmsg, SPACELEFT(buf, bp));
+ return buf;
+ }
+
+#if LDAPMAP
+ if (errnum >= E_LDAPBASE)
+ return ldap_err2string(errnum - E_LDAPBASE);
+#endif /* LDAPMAP */
+
+#if HASSTRERROR
+ err = strerror(errnum);
+ if (err == NULL)
+ {
+ (void) sm_snprintf(buf, sizeof buf, "Error %d", errnum);
+ return buf;
+ }
+ return err;
+#else /* HASSTRERROR */
+ if (errnum > 0 && errnum < sys_nerr)
+ return sys_errlist[errnum];
+
+ (void) sm_snprintf(buf, sizeof buf, "Error %d", errnum);
+ return buf;
+#endif /* HASSTRERROR */
+}
diff --git a/usr/src/cmd/sendmail/src/headers.c b/usr/src/cmd/sendmail/src/headers.c
new file mode 100644
index 0000000000..2b3d1517f4
--- /dev/null
+++ b/usr/src/cmd/sendmail/src/headers.c
@@ -0,0 +1,2166 @@
+/*
+ * Copyright (c) 1998-2004 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sendmail.h>
+
+SM_RCSID("@(#)$Id: headers.c,v 8.287 2004/12/03 18:29:51 ca Exp $")
+
+static HDR *allocheader __P((char *, char *, int, SM_RPOOL_T *));
+static size_t fix_mime_header __P((HDR *, ENVELOPE *));
+static int priencode __P((char *));
+static void put_vanilla_header __P((HDR *, char *, MCI *));
+
+/*
+** SETUPHEADERS -- initialize headers in symbol table
+**
+** Parameters:
+** none
+**
+** Returns:
+** none
+*/
+
+void
+setupheaders()
+{
+ struct hdrinfo *hi;
+ STAB *s;
+
+ for (hi = HdrInfo; hi->hi_field != NULL; hi++)
+ {
+ s = stab(hi->hi_field, ST_HEADER, ST_ENTER);
+ s->s_header.hi_flags = hi->hi_flags;
+ s->s_header.hi_ruleset = NULL;
+ }
+}
+/*
+** CHOMPHEADER -- process and save a header line.
+**
+** Called by collect, readcf, and readqf to deal with header lines.
+**
+** Parameters:
+** line -- header as a text line.
+** pflag -- flags for chompheader() (from sendmail.h)
+** hdrp -- a pointer to the place to save the header.
+** e -- the envelope including this header.
+**
+** Returns:
+** flags for this header.
+**
+** Side Effects:
+** The header is saved on the header list.
+** Contents of 'line' are destroyed.
+*/
+
+static struct hdrinfo NormalHeader = { NULL, 0, NULL };
+
+unsigned long
+chompheader(line, pflag, hdrp, e)
+ char *line;
+ int pflag;
+ HDR **hdrp;
+ register ENVELOPE *e;
+{
+ unsigned char mid = '\0';
+ register char *p;
+ register HDR *h;
+ HDR **hp;
+ char *fname;
+ char *fvalue;
+ bool cond = false;
+ bool dropfrom;
+ bool headeronly;
+ STAB *s;
+ struct hdrinfo *hi;
+ bool nullheader = false;
+ BITMAP256 mopts;
+
+ if (tTd(31, 6))
+ {
+ sm_dprintf("chompheader: ");
+ xputs(sm_debug_file(), line);
+ sm_dprintf("\n");
+ }
+
+ headeronly = hdrp != NULL;
+ if (!headeronly)
+ hdrp = &e->e_header;
+
+ /* strip off options */
+ clrbitmap(mopts);
+ p = line;
+ if (!bitset(pflag, CHHDR_USER) && *p == '?')
+ {
+ int c;
+ register char *q;
+
+ q = strchr(++p, '?');
+ if (q == NULL)
+ goto hse;
+
+ *q = '\0';
+ c = *p & 0377;
+
+ /* possibly macro conditional */
+ if (c == MACROEXPAND)
+ {
+ /* catch ?$? */
+ if (*++p == '\0')
+ {
+ *q = '?';
+ goto hse;
+ }
+
+ mid = (unsigned char) *p++;
+
+ /* catch ?$abc? */
+ if (*p != '\0')
+ {
+ *q = '?';
+ goto hse;
+ }
+ }
+ else if (*p == '$')
+ {
+ /* catch ?$? */
+ if (*++p == '\0')
+ {
+ *q = '?';
+ goto hse;
+ }
+
+ mid = (unsigned char) macid(p);
+ if (bitset(0200, mid))
+ {
+ p += strlen(macname(mid)) + 2;
+ SM_ASSERT(p <= q);
+ }
+ else
+ p++;
+
+ /* catch ?$abc? */
+ if (*p != '\0')
+ {
+ *q = '?';
+ goto hse;
+ }
+ }
+ else
+ {
+ while (*p != '\0')
+ {
+ if (!isascii(*p))
+ {
+ *q = '?';
+ goto hse;
+ }
+
+ setbitn(bitidx(*p), mopts);
+ cond = true;
+ p++;
+ }
+ }
+ p = q + 1;
+ }
+
+ /* find canonical name */
+ fname = p;
+ while (isascii(*p) && isgraph(*p) && *p != ':')
+ p++;
+ fvalue = p;
+ while (isascii(*p) && isspace(*p))
+ p++;
+ if (*p++ != ':' || fname == fvalue)
+ {
+hse:
+ syserr("553 5.3.0 header syntax error, line \"%s\"", line);
+ return 0;
+ }
+ *fvalue = '\0';
+
+ /* strip field value on front */
+ if (*p == ' ')
+ p++;
+ fvalue = p;
+
+ /* if the field is null, go ahead and use the default */
+ while (isascii(*p) && isspace(*p))
+ p++;
+ if (*p == '\0')
+ nullheader = true;
+
+ /* security scan: long field names are end-of-header */
+ if (strlen(fname) > 100)
+ return H_EOH;
+
+ /* check to see if it represents a ruleset call */
+ if (bitset(pflag, CHHDR_DEF))
+ {
+ char hbuf[50];
+
+ (void) expand(fvalue, hbuf, sizeof hbuf, e);
+ for (p = hbuf; isascii(*p) && isspace(*p); )
+ p++;
+ if ((*p++ & 0377) == CALLSUBR)
+ {
+ auto char *endp;
+ bool strc;
+
+ strc = *p == '+'; /* strip comments? */
+ if (strc)
+ ++p;
+ if (strtorwset(p, &endp, ST_ENTER) > 0)
+ {
+ *endp = '\0';
+ s = stab(fname, ST_HEADER, ST_ENTER);
+ if (LogLevel > 9 &&
+ s->s_header.hi_ruleset != NULL)
+ sm_syslog(LOG_WARNING, NOQID,
+ "Warning: redefined ruleset for header=%s, old=%s, new=%s",
+ fname,
+ s->s_header.hi_ruleset, p);
+ s->s_header.hi_ruleset = newstr(p);
+ if (!strc)
+ s->s_header.hi_flags |= H_STRIPCOMM;
+ }
+ return 0;
+ }
+ }
+
+ /* see if it is a known type */
+ s = stab(fname, ST_HEADER, ST_FIND);
+ if (s != NULL)
+ hi = &s->s_header;
+ else
+ hi = &NormalHeader;
+
+ if (tTd(31, 9))
+ {
+ if (s == NULL)
+ sm_dprintf("no header flags match\n");
+ else
+ sm_dprintf("header match, flags=%lx, ruleset=%s\n",
+ hi->hi_flags,
+ hi->hi_ruleset == NULL ? "<NULL>"
+ : hi->hi_ruleset);
+ }
+
+ /* see if this is a resent message */
+ if (!bitset(pflag, CHHDR_DEF) && !headeronly &&
+ bitset(H_RESENT, hi->hi_flags))
+ e->e_flags |= EF_RESENT;
+
+ /* if this is an Errors-To: header keep track of it now */
+ if (UseErrorsTo && !bitset(pflag, CHHDR_DEF) && !headeronly &&
+ bitset(H_ERRORSTO, hi->hi_flags))
+ (void) sendtolist(fvalue, NULLADDR, &e->e_errorqueue, 0, e);
+
+ /* if this means "end of header" quit now */
+ if (!headeronly && bitset(H_EOH, hi->hi_flags))
+ return hi->hi_flags;
+
+ /*
+ ** Horrible hack to work around problem with Lotus Notes SMTP
+ ** mail gateway, which generates From: headers with newlines in
+ ** them and the <address> on the second line. Although this is
+ ** legal RFC 822, many MUAs don't handle this properly and thus
+ ** never find the actual address.
+ */
+
+ if (bitset(H_FROM, hi->hi_flags) && SingleLineFromHeader)
+ {
+ while ((p = strchr(fvalue, '\n')) != NULL)
+ *p = ' ';
+ }
+
+ /*
+ ** If there is a check ruleset, verify it against the header.
+ */
+
+ if (bitset(pflag, CHHDR_CHECK))
+ {
+ int rscheckflags;
+ char *rs;
+
+ rscheckflags = RSF_COUNT;
+ if (!bitset(hi->hi_flags, H_FROM|H_RCPT))
+ rscheckflags |= RSF_UNSTRUCTURED;
+
+ /* no ruleset? look for default */
+ rs = hi->hi_ruleset;
+ if (rs == NULL)
+ {
+ s = stab("*", ST_HEADER, ST_FIND);
+ if (s != NULL)
+ {
+ rs = (&s->s_header)->hi_ruleset;
+ if (bitset((&s->s_header)->hi_flags,
+ H_STRIPCOMM))
+ rscheckflags |= RSF_RMCOMM;
+ }
+ }
+ else if (bitset(hi->hi_flags, H_STRIPCOMM))
+ rscheckflags |= RSF_RMCOMM;
+ if (rs != NULL)
+ {
+ int l, k;
+ char qval[MAXNAME];
+
+ l = 0;
+ qval[l++] = '"';
+
+ /* - 3 to avoid problems with " at the end */
+ /* should be sizeof(qval), not MAXNAME */
+ for (k = 0; fvalue[k] != '\0' && l < MAXNAME - 3; k++)
+ {
+ switch (fvalue[k])
+ {
+ /* XXX other control chars? */
+ case '\011': /* ht */
+ case '\012': /* nl */
+ case '\013': /* vt */
+ case '\014': /* np */
+ case '\015': /* cr */
+ qval[l++] = ' ';
+ break;
+ case '"':
+ qval[l++] = '\\';
+ /* FALLTHROUGH */
+ default:
+ qval[l++] = fvalue[k];
+ break;
+ }
+ }
+ qval[l++] = '"';
+ qval[l] = '\0';
+ k += strlen(fvalue + k);
+ if (k >= MAXNAME)
+ {
+ if (LogLevel > 9)
+ sm_syslog(LOG_WARNING, e->e_id,
+ "Warning: truncated header '%s' before check with '%s' len=%d max=%d",
+ fname, rs, k, MAXNAME - 1);
+ }
+ macdefine(&e->e_macro, A_TEMP,
+ macid("{currHeader}"), qval);
+ macdefine(&e->e_macro, A_TEMP,
+ macid("{hdr_name}"), fname);
+
+ (void) sm_snprintf(qval, sizeof qval, "%d", k);
+ macdefine(&e->e_macro, A_TEMP, macid("{hdrlen}"), qval);
+#if _FFR_HDR_TYPE
+ if (bitset(H_FROM, hi->hi_flags))
+ macdefine(&e->e_macro, A_PERM,
+ macid("{addr_type}"), "h s");
+ else if (bitset(H_RCPT, hi->hi_flags))
+ macdefine(&e->e_macro, A_PERM,
+ macid("{addr_type}"), "h r");
+ else
+#endif /* _FFR_HDR_TYPE */
+ macdefine(&e->e_macro, A_PERM,
+ macid("{addr_type}"), "h");
+ (void) rscheck(rs, fvalue, NULL, e, rscheckflags, 3,
+ NULL, e->e_id);
+ }
+ }
+
+ /*
+ ** Drop explicit From: if same as what we would generate.
+ ** This is to make MH (which doesn't always give a full name)
+ ** insert the full name information in all circumstances.
+ */
+
+ dropfrom = false;
+ p = "resent-from";
+ if (!bitset(EF_RESENT, e->e_flags))
+ p += 7;
+ if (!bitset(pflag, CHHDR_DEF) && !headeronly &&
+ !bitset(EF_QUEUERUN, e->e_flags) && sm_strcasecmp(fname, p) == 0)
+ {
+ if (tTd(31, 2))
+ {
+ sm_dprintf("comparing header from (%s) against default (%s or %s)\n",
+ fvalue, e->e_from.q_paddr, e->e_from.q_user);
+ }
+ if (e->e_from.q_paddr != NULL &&
+ e->e_from.q_mailer != NULL &&
+ bitnset(M_LOCALMAILER, e->e_from.q_mailer->m_flags) &&
+ (strcmp(fvalue, e->e_from.q_paddr) == 0 ||
+ strcmp(fvalue, e->e_from.q_user) == 0))
+ dropfrom = true;
+ }
+
+ /* delete default value for this header */
+ for (hp = hdrp; (h = *hp) != NULL; hp = &h->h_link)
+ {
+ if (sm_strcasecmp(fname, h->h_field) == 0 &&
+ !bitset(H_USER, h->h_flags) &&
+ !bitset(H_FORCE, h->h_flags))
+ {
+ if (nullheader)
+ {
+ /* user-supplied value was null */
+ return 0;
+ }
+ if (dropfrom)
+ {
+ /* make this look like the user entered it */
+ h->h_flags |= H_USER;
+ return hi->hi_flags;
+ }
+ h->h_value = NULL;
+ if (!cond)
+ {
+ /* copy conditions from default case */
+ memmove((char *) mopts, (char *) h->h_mflags,
+ sizeof mopts);
+ }
+ h->h_macro = mid;
+ }
+ }
+
+ /* create a new node */
+ h = (HDR *) sm_rpool_malloc_x(e->e_rpool, sizeof *h);
+ h->h_field = sm_rpool_strdup_x(e->e_rpool, fname);
+ h->h_value = sm_rpool_strdup_x(e->e_rpool, fvalue);
+ h->h_link = NULL;
+ memmove((char *) h->h_mflags, (char *) mopts, sizeof mopts);
+ h->h_macro = mid;
+ *hp = h;
+ h->h_flags = hi->hi_flags;
+ if (bitset(pflag, CHHDR_USER) || bitset(pflag, CHHDR_QUEUE))
+ h->h_flags |= H_USER;
+
+ /* strip EOH flag if parsing MIME headers */
+ if (headeronly)
+ h->h_flags &= ~H_EOH;
+ if (bitset(pflag, CHHDR_DEF))
+ h->h_flags |= H_DEFAULT;
+ if (cond || mid != '\0')
+ h->h_flags |= H_CHECK;
+
+ /* hack to see if this is a new format message */
+ if (!bitset(pflag, CHHDR_DEF) && !headeronly &&
+ bitset(H_RCPT|H_FROM, h->h_flags) &&
+ (strchr(fvalue, ',') != NULL || strchr(fvalue, '(') != NULL ||
+ strchr(fvalue, '<') != NULL || strchr(fvalue, ';') != NULL))
+ {
+ e->e_flags &= ~EF_OLDSTYLE;
+ }
+
+ return h->h_flags;
+}
+/*
+** ALLOCHEADER -- allocate a header entry
+**
+** Parameters:
+** field -- the name of the header field.
+** value -- the value of the field.
+** flags -- flags to add to h_flags.
+** rp -- resource pool for allocations
+**
+** Returns:
+** Pointer to a newly allocated and populated HDR.
+*/
+
+static HDR *
+allocheader(field, value, flags, rp)
+ char *field;
+ char *value;
+ int flags;
+ SM_RPOOL_T *rp;
+{
+ HDR *h;
+ STAB *s;
+
+ /* find info struct */
+ s = stab(field, ST_HEADER, ST_FIND);
+
+ /* allocate space for new header */
+ h = (HDR *) sm_rpool_malloc_x(rp, sizeof *h);
+ h->h_field = field;
+ h->h_value = sm_rpool_strdup_x(rp, value);
+ h->h_flags = flags;
+ if (s != NULL)
+ h->h_flags |= s->s_header.hi_flags;
+ clrbitmap(h->h_mflags);
+ h->h_macro = '\0';
+
+ return h;
+}
+/*
+** ADDHEADER -- add a header entry to the end of the queue.
+**
+** This bypasses the special checking of chompheader.
+**
+** Parameters:
+** field -- the name of the header field.
+** value -- the value of the field.
+** flags -- flags to add to h_flags.
+** e -- envelope.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** adds the field on the list of headers for this envelope.
+*/
+
+void
+addheader(field, value, flags, e)
+ char *field;
+ char *value;
+ int flags;
+ ENVELOPE *e;
+{
+ register HDR *h;
+ HDR **hp;
+ HDR **hdrlist = &e->e_header;
+
+ /* find current place in list -- keep back pointer? */
+ for (hp = hdrlist; (h = *hp) != NULL; hp = &h->h_link)
+ {
+ if (sm_strcasecmp(field, h->h_field) == 0)
+ break;
+ }
+
+ /* allocate space for new header */
+ h = allocheader(field, value, flags, e->e_rpool);
+ h->h_link = *hp;
+ *hp = h;
+}
+/*
+** INSHEADER -- insert a header entry at the specified index
+**
+** This bypasses the special checking of chompheader.
+**
+** Parameters:
+** idx -- index into the header list at which to insert
+** field -- the name of the header field.
+** value -- the value of the field.
+** flags -- flags to add to h_flags.
+** e -- envelope.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** inserts the field on the list of headers for this envelope.
+*/
+
+void
+insheader(idx, field, value, flags, e)
+ int idx;
+ char *field;
+ char *value;
+ int flags;
+ ENVELOPE *e;
+{
+ HDR *h, *srch, *last = NULL;
+
+ /* allocate space for new header */
+ h = allocheader(field, value, flags, e->e_rpool);
+
+ /* find insertion position */
+ for (srch = e->e_header; srch != NULL && idx > 0;
+ srch = srch->h_link, idx--)
+ last = srch;
+
+ if (e->e_header == NULL)
+ {
+ e->e_header = h;
+ h->h_link = NULL;
+ }
+ else if (srch == NULL)
+ {
+ SM_ASSERT(last != NULL);
+ last->h_link = h;
+ h->h_link = NULL;
+ }
+ else
+ {
+ h->h_link = srch->h_link;
+ srch->h_link = h;
+ }
+}
+/*
+** HVALUE -- return value of a header.
+**
+** Only "real" fields (i.e., ones that have not been supplied
+** as a default) are used.
+**
+** Parameters:
+** field -- the field name.
+** header -- the header list.
+**
+** Returns:
+** pointer to the value part.
+** NULL if not found.
+**
+** Side Effects:
+** none.
+*/
+
+char *
+hvalue(field, header)
+ char *field;
+ HDR *header;
+{
+ register HDR *h;
+
+ for (h = header; h != NULL; h = h->h_link)
+ {
+ if (!bitset(H_DEFAULT, h->h_flags) &&
+ sm_strcasecmp(h->h_field, field) == 0)
+ return h->h_value;
+ }
+ return NULL;
+}
+/*
+** ISHEADER -- predicate telling if argument is a header.
+**
+** A line is a header if it has a single word followed by
+** optional white space followed by a colon.
+**
+** Header fields beginning with two dashes, although technically
+** permitted by RFC822, are automatically rejected in order
+** to make MIME work out. Without this we could have a technically
+** legal header such as ``--"foo:bar"'' that would also be a legal
+** MIME separator.
+**
+** Parameters:
+** h -- string to check for possible headerness.
+**
+** Returns:
+** true if h is a header.
+** false otherwise.
+**
+** Side Effects:
+** none.
+*/
+
+bool
+isheader(h)
+ char *h;
+{
+ register char *s = h;
+
+ if (s[0] == '-' && s[1] == '-')
+ return false;
+
+ while (*s > ' ' && *s != ':' && *s != '\0')
+ s++;
+
+ if (h == s)
+ return false;
+
+ /* following technically violates RFC822 */
+ while (isascii(*s) && isspace(*s))
+ s++;
+
+ return (*s == ':');
+}
+/*
+** EATHEADER -- run through the stored header and extract info.
+**
+** Parameters:
+** e -- the envelope to process.
+** full -- if set, do full processing (e.g., compute
+** message priority). This should not be set
+** when reading a queue file because some info
+** needed to compute the priority is wrong.
+** log -- call logsender()?
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Sets a bunch of global variables from information
+** in the collected header.
+*/
+
+void
+eatheader(e, full, log)
+ register ENVELOPE *e;
+ bool full;
+ bool log;
+{
+ register HDR *h;
+ register char *p;
+ int hopcnt = 0;
+ char buf[MAXLINE];
+
+ /*
+ ** Set up macros for possible expansion in headers.
+ */
+
+ macdefine(&e->e_macro, A_PERM, 'f', e->e_sender);
+ macdefine(&e->e_macro, A_PERM, 'g', e->e_sender);
+ if (e->e_origrcpt != NULL && *e->e_origrcpt != '\0')
+ macdefine(&e->e_macro, A_PERM, 'u', e->e_origrcpt);
+ else
+ macdefine(&e->e_macro, A_PERM, 'u', NULL);
+
+ /* full name of from person */
+ p = hvalue("full-name", e->e_header);
+ if (p != NULL)
+ {
+ if (!rfc822_string(p))
+ {
+ /*
+ ** Quote a full name with special characters
+ ** as a comment so crackaddr() doesn't destroy
+ ** the name portion of the address.
+ */
+
+ p = addquotes(p, e->e_rpool);
+ }
+ macdefine(&e->e_macro, A_PERM, 'x', p);
+ }
+
+ if (tTd(32, 1))
+ sm_dprintf("----- collected header -----\n");
+ e->e_msgid = NULL;
+ for (h = e->e_header; h != NULL; h = h->h_link)
+ {
+ if (tTd(32, 1))
+ sm_dprintf("%s: ", h->h_field);
+ if (h->h_value == NULL)
+ {
+ if (tTd(32, 1))
+ sm_dprintf("<NULL>\n");
+ continue;
+ }
+
+ /* do early binding */
+ if (bitset(H_DEFAULT, h->h_flags) &&
+ !bitset(H_BINDLATE, h->h_flags))
+ {
+ if (tTd(32, 1))
+ {
+ sm_dprintf("(");
+ xputs(sm_debug_file(), h->h_value);
+ sm_dprintf(") ");
+ }
+ expand(h->h_value, buf, sizeof buf, e);
+ if (buf[0] != '\0')
+ {
+ if (bitset(H_FROM, h->h_flags))
+ expand(crackaddr(buf, e),
+ buf, sizeof buf, e);
+ h->h_value = sm_rpool_strdup_x(e->e_rpool, buf);
+ h->h_flags &= ~H_DEFAULT;
+ }
+ }
+ if (tTd(32, 1))
+ {
+ xputs(sm_debug_file(), h->h_value);
+ sm_dprintf("\n");
+ }
+
+ /* count the number of times it has been processed */
+ if (bitset(H_TRACE, h->h_flags))
+ hopcnt++;
+
+ /* send to this person if we so desire */
+ if (GrabTo && bitset(H_RCPT, h->h_flags) &&
+ !bitset(H_DEFAULT, h->h_flags) &&
+ (!bitset(EF_RESENT, e->e_flags) ||
+ bitset(H_RESENT, h->h_flags)))
+ {
+#if 0
+ int saveflags = e->e_flags;
+#endif /* 0 */
+
+ (void) sendtolist(denlstring(h->h_value, true, false),
+ NULLADDR, &e->e_sendqueue, 0, e);
+
+#if 0
+ /*
+ ** Change functionality so a fatal error on an
+ ** address doesn't affect the entire envelope.
+ */
+
+ /* delete fatal errors generated by this address */
+ if (!bitset(EF_FATALERRS, saveflags))
+ e->e_flags &= ~EF_FATALERRS;
+#endif /* 0 */
+ }
+
+ /* save the message-id for logging */
+ p = "resent-message-id";
+ if (!bitset(EF_RESENT, e->e_flags))
+ p += 7;
+ if (sm_strcasecmp(h->h_field, p) == 0)
+ {
+ e->e_msgid = h->h_value;
+ while (isascii(*e->e_msgid) && isspace(*e->e_msgid))
+ e->e_msgid++;
+ macdefine(&e->e_macro, A_PERM, macid("{msg_id}"),
+ e->e_msgid);
+ }
+ }
+ if (tTd(32, 1))
+ sm_dprintf("----------------------------\n");
+
+ /* if we are just verifying (that is, sendmail -t -bv), drop out now */
+ if (OpMode == MD_VERIFY)
+ return;
+
+ /* store hop count */
+ if (hopcnt > e->e_hopcount)
+ {
+ e->e_hopcount = hopcnt;
+ (void) sm_snprintf(buf, sizeof buf, "%d", e->e_hopcount);
+ macdefine(&e->e_macro, A_TEMP, 'c', buf);
+ }
+
+ /* message priority */
+ p = hvalue("precedence", e->e_header);
+ if (p != NULL)
+ e->e_class = priencode(p);
+ if (e->e_class < 0)
+ e->e_timeoutclass = TOC_NONURGENT;
+ else if (e->e_class > 0)
+ e->e_timeoutclass = TOC_URGENT;
+ if (full)
+ {
+ e->e_msgpriority = e->e_msgsize
+ - e->e_class * WkClassFact
+ + e->e_nrcpts * WkRecipFact;
+ }
+
+ /* check for DSN to properly set e_timeoutclass */
+ p = hvalue("content-type", e->e_header);
+ if (p != NULL)
+ {
+ bool oldsupr;
+ char **pvp;
+ char pvpbuf[MAXLINE];
+ extern unsigned char MimeTokenTab[256];
+
+ /* tokenize header */
+ oldsupr = SuprErrs;
+ SuprErrs = true;
+ pvp = prescan(p, '\0', pvpbuf, sizeof pvpbuf, NULL,
+ MimeTokenTab, false);
+ SuprErrs = oldsupr;
+
+ /* Check if multipart/report */
+ if (pvp != NULL && pvp[0] != NULL &&
+ pvp[1] != NULL && pvp[2] != NULL &&
+ sm_strcasecmp(*pvp++, "multipart") == 0 &&
+ strcmp(*pvp++, "/") == 0 &&
+ sm_strcasecmp(*pvp++, "report") == 0)
+ {
+ /* Look for report-type=delivery-status */
+ while (*pvp != NULL)
+ {
+ /* skip to semicolon separator */
+ while (*pvp != NULL && strcmp(*pvp, ";") != 0)
+ pvp++;
+
+ /* skip semicolon */
+ if (*pvp++ == NULL || *pvp == NULL)
+ break;
+
+ /* look for report-type */
+ if (sm_strcasecmp(*pvp++, "report-type") != 0)
+ continue;
+
+ /* skip equal */
+ if (*pvp == NULL || strcmp(*pvp, "=") != 0)
+ continue;
+
+ /* check value */
+ if (*++pvp != NULL &&
+ sm_strcasecmp(*pvp,
+ "delivery-status") == 0)
+ e->e_timeoutclass = TOC_DSN;
+
+ /* found report-type, no need to continue */
+ break;
+ }
+ }
+ }
+
+ /* message timeout priority */
+ p = hvalue("priority", e->e_header);
+ if (p != NULL)
+ {
+ /* (this should be in the configuration file) */
+ if (sm_strcasecmp(p, "urgent") == 0)
+ e->e_timeoutclass = TOC_URGENT;
+ else if (sm_strcasecmp(p, "normal") == 0)
+ e->e_timeoutclass = TOC_NORMAL;
+ else if (sm_strcasecmp(p, "non-urgent") == 0)
+ e->e_timeoutclass = TOC_NONURGENT;
+ else if (bitset(EF_RESPONSE, e->e_flags))
+ e->e_timeoutclass = TOC_DSN;
+ }
+ else if (bitset(EF_RESPONSE, e->e_flags))
+ e->e_timeoutclass = TOC_DSN;
+
+ /* date message originated */
+ p = hvalue("posted-date", e->e_header);
+ if (p == NULL)
+ p = hvalue("date", e->e_header);
+ if (p != NULL)
+ macdefine(&e->e_macro, A_PERM, 'a', p);
+
+ /* check to see if this is a MIME message */
+ if ((e->e_bodytype != NULL &&
+ sm_strcasecmp(e->e_bodytype, "8BITMIME") == 0) ||
+ hvalue("MIME-Version", e->e_header) != NULL)
+ {
+ e->e_flags |= EF_IS_MIME;
+ if (HasEightBits)
+ e->e_bodytype = "8BITMIME";
+ }
+ else if ((p = hvalue("Content-Type", e->e_header)) != NULL)
+ {
+ /* this may be an RFC 1049 message */
+ p = strpbrk(p, ";/");
+ if (p == NULL || *p == ';')
+ {
+ /* yep, it is */
+ e->e_flags |= EF_DONT_MIME;
+ }
+ }
+
+ /*
+ ** From person in antiquated ARPANET mode
+ ** required by UK Grey Book e-mail gateways (sigh)
+ */
+
+ if (OpMode == MD_ARPAFTP)
+ {
+ register struct hdrinfo *hi;
+
+ for (hi = HdrInfo; hi->hi_field != NULL; hi++)
+ {
+ if (bitset(H_FROM, hi->hi_flags) &&
+ (!bitset(H_RESENT, hi->hi_flags) ||
+ bitset(EF_RESENT, e->e_flags)) &&
+ (p = hvalue(hi->hi_field, e->e_header)) != NULL)
+ break;
+ }
+ if (hi->hi_field != NULL)
+ {
+ if (tTd(32, 2))
+ sm_dprintf("eatheader: setsender(*%s == %s)\n",
+ hi->hi_field, p);
+ setsender(p, e, NULL, '\0', true);
+ }
+ }
+
+ /*
+ ** Log collection information.
+ */
+
+ if (log && bitset(EF_LOGSENDER, e->e_flags) && LogLevel > 4)
+ {
+ logsender(e, e->e_msgid);
+ e->e_flags &= ~EF_LOGSENDER;
+ }
+}
+/*
+** LOGSENDER -- log sender information
+**
+** Parameters:
+** e -- the envelope to log
+** msgid -- the message id
+**
+** Returns:
+** none
+*/
+
+void
+logsender(e, msgid)
+ register ENVELOPE *e;
+ char *msgid;
+{
+ char *name;
+ register char *sbp;
+ register char *p;
+ int l;
+ char hbuf[MAXNAME + 1];
+ char sbuf[MAXLINE + 1];
+ char mbuf[MAXNAME + 1];
+
+ /* don't allow newlines in the message-id */
+ /* XXX do we still need this? sm_syslog() replaces control chars */
+ if (msgid != NULL)
+ {
+ l = strlen(msgid);
+ if (l > sizeof mbuf - 1)
+ l = sizeof mbuf - 1;
+ memmove(mbuf, msgid, l);
+ mbuf[l] = '\0';
+ p = mbuf;
+ while ((p = strchr(p, '\n')) != NULL)
+ *p++ = ' ';
+ }
+
+ if (bitset(EF_RESPONSE, e->e_flags))
+ name = "[RESPONSE]";
+ else if ((name = macvalue('_', e)) != NULL)
+ /* EMPTY */
+ ;
+ else if (RealHostName == NULL)
+ name = "localhost";
+ else if (RealHostName[0] == '[')
+ name = RealHostName;
+ else
+ {
+ name = hbuf;
+ (void) sm_snprintf(hbuf, sizeof hbuf, "%.80s", RealHostName);
+ if (RealHostAddr.sa.sa_family != 0)
+ {
+ p = &hbuf[strlen(hbuf)];
+ (void) sm_snprintf(p, SPACELEFT(hbuf, p),
+ " (%.100s)",
+ anynet_ntoa(&RealHostAddr));
+ }
+ }
+
+ /* some versions of syslog only take 5 printf args */
+#if (SYSLOG_BUFSIZE) >= 256
+ sbp = sbuf;
+ (void) sm_snprintf(sbp, SPACELEFT(sbuf, sbp),
+ "from=%.200s, size=%ld, class=%d, nrcpts=%d",
+ e->e_from.q_paddr == NULL ? "<NONE>" : e->e_from.q_paddr,
+ e->e_msgsize, e->e_class, e->e_nrcpts);
+ sbp += strlen(sbp);
+ if (msgid != NULL)
+ {
+ (void) sm_snprintf(sbp, SPACELEFT(sbuf, sbp),
+ ", msgid=%.100s", mbuf);
+ sbp += strlen(sbp);
+ }
+ if (e->e_bodytype != NULL)
+ {
+ (void) sm_snprintf(sbp, SPACELEFT(sbuf, sbp),
+ ", bodytype=%.20s", e->e_bodytype);
+ sbp += strlen(sbp);
+ }
+ p = macvalue('r', e);
+ if (p != NULL)
+ {
+ (void) sm_snprintf(sbp, SPACELEFT(sbuf, sbp),
+ ", proto=%.20s", p);
+ sbp += strlen(sbp);
+ }
+ p = macvalue(macid("{daemon_name}"), e);
+ if (p != NULL)
+ {
+ (void) sm_snprintf(sbp, SPACELEFT(sbuf, sbp),
+ ", daemon=%.20s", p);
+ sbp += strlen(sbp);
+ }
+ sm_syslog(LOG_INFO, e->e_id, "%.850s, relay=%s", sbuf, name);
+
+#else /* (SYSLOG_BUFSIZE) >= 256 */
+
+ sm_syslog(LOG_INFO, e->e_id,
+ "from=%s",
+ e->e_from.q_paddr == NULL ? "<NONE>"
+ : shortenstring(e->e_from.q_paddr,
+ 83));
+ sm_syslog(LOG_INFO, e->e_id,
+ "size=%ld, class=%ld, nrcpts=%d",
+ e->e_msgsize, e->e_class, e->e_nrcpts);
+ if (msgid != NULL)
+ sm_syslog(LOG_INFO, e->e_id,
+ "msgid=%s",
+ shortenstring(mbuf, 83));
+ sbp = sbuf;
+ *sbp = '\0';
+ if (e->e_bodytype != NULL)
+ {
+ (void) sm_snprintf(sbp, SPACELEFT(sbuf, sbp),
+ "bodytype=%.20s, ", e->e_bodytype);
+ sbp += strlen(sbp);
+ }
+ p = macvalue('r', e);
+ if (p != NULL)
+ {
+ (void) sm_snprintf(sbp, SPACELEFT(sbuf, sbp),
+ "proto=%.20s, ", p);
+ sbp += strlen(sbp);
+ }
+ sm_syslog(LOG_INFO, e->e_id,
+ "%.400srelay=%s", sbuf, name);
+#endif /* (SYSLOG_BUFSIZE) >= 256 */
+}
+/*
+** PRIENCODE -- encode external priority names into internal values.
+**
+** Parameters:
+** p -- priority in ascii.
+**
+** Returns:
+** priority as a numeric level.
+**
+** Side Effects:
+** none.
+*/
+
+static int
+priencode(p)
+ char *p;
+{
+ register int i;
+
+ for (i = 0; i < NumPriorities; i++)
+ {
+ if (sm_strcasecmp(p, Priorities[i].pri_name) == 0)
+ return Priorities[i].pri_val;
+ }
+
+ /* unknown priority */
+ return 0;
+}
+/*
+** CRACKADDR -- parse an address and turn it into a macro
+**
+** This doesn't actually parse the address -- it just extracts
+** it and replaces it with "$g". The parse is totally ad hoc
+** and isn't even guaranteed to leave something syntactically
+** identical to what it started with. However, it does leave
+** something semantically identical if possible, else at least
+** syntactically correct.
+**
+** For example, it changes "Real Name <real@example.com> (Comment)"
+** to "Real Name <$g> (Comment)".
+**
+** This algorithm has been cleaned up to handle a wider range
+** of cases -- notably quoted and backslash escaped strings.
+** This modification makes it substantially better at preserving
+** the original syntax.
+**
+** Parameters:
+** addr -- the address to be cracked.
+** e -- the current envelope.
+**
+** Returns:
+** a pointer to the new version.
+**
+** Side Effects:
+** none.
+**
+** Warning:
+** The return value is saved in local storage and should
+** be copied if it is to be reused.
+*/
+
+#define SM_HAVE_ROOM ((bp < buflim) && (buflim <= bufend))
+
+/*
+** Append a character to bp if we have room.
+** If not, punt and return $g.
+*/
+
+#define SM_APPEND_CHAR(c) \
+ do \
+ { \
+ if (SM_HAVE_ROOM) \
+ *bp++ = (c); \
+ else \
+ goto returng; \
+ } while (0)
+
+#if MAXNAME < 10
+ERROR MAXNAME must be at least 10
+#endif /* MAXNAME < 10 */
+
+char *
+crackaddr(addr, e)
+ register char *addr;
+ ENVELOPE *e;
+{
+ register char *p;
+ register char c;
+ int cmtlev; /* comment level in input string */
+ int realcmtlev; /* comment level in output string */
+ int anglelev; /* angle level in input string */
+ int copylev; /* 0 == in address, >0 copying */
+ int bracklev; /* bracket level for IPv6 addr check */
+ bool addangle; /* put closing angle in output */
+ bool qmode; /* quoting in original string? */
+ bool realqmode; /* quoting in output string? */
+ bool putgmac = false; /* already wrote $g */
+ bool quoteit = false; /* need to quote next character */
+ bool gotangle = false; /* found first '<' */
+ bool gotcolon = false; /* found a ':' */
+ register char *bp;
+ char *buflim;
+ char *bufhead;
+ char *addrhead;
+ char *bufend;
+ static char buf[MAXNAME + 1];
+
+ if (tTd(33, 1))
+ sm_dprintf("crackaddr(%s)\n", addr);
+
+ /* strip leading spaces */
+ while (*addr != '\0' && isascii(*addr) && isspace(*addr))
+ addr++;
+
+ /*
+ ** Start by assuming we have no angle brackets. This will be
+ ** adjusted later if we find them.
+ */
+
+ buflim = bufend = &buf[sizeof(buf) - 1];
+ bp = bufhead = buf;
+ p = addrhead = addr;
+ copylev = anglelev = cmtlev = realcmtlev = 0;
+ bracklev = 0;
+ qmode = realqmode = addangle = false;
+
+ while ((c = *p++) != '\0')
+ {
+ /*
+ ** Try to keep legal syntax using spare buffer space
+ ** (maintained by buflim).
+ */
+
+ if (copylev > 0)
+ SM_APPEND_CHAR(c);
+
+ /* check for backslash escapes */
+ if (c == '\\')
+ {
+ /* arrange to quote the address */
+ if (cmtlev <= 0 && !qmode)
+ quoteit = true;
+
+ if ((c = *p++) == '\0')
+ {
+ /* too far */
+ p--;
+ goto putg;
+ }
+ if (copylev > 0)
+ SM_APPEND_CHAR(c);
+ goto putg;
+ }
+
+ /* check for quoted strings */
+ if (c == '"' && cmtlev <= 0)
+ {
+ qmode = !qmode;
+ if (copylev > 0 && SM_HAVE_ROOM)
+ {
+ if (realqmode)
+ buflim--;
+ else
+ buflim++;
+ realqmode = !realqmode;
+ }
+ continue;
+ }
+ if (qmode)
+ goto putg;
+
+ /* check for comments */
+ if (c == '(')
+ {
+ cmtlev++;
+
+ /* allow space for closing paren */
+ if (SM_HAVE_ROOM)
+ {
+ buflim--;
+ realcmtlev++;
+ if (copylev++ <= 0)
+ {
+ if (bp != bufhead)
+ SM_APPEND_CHAR(' ');
+ SM_APPEND_CHAR(c);
+ }
+ }
+ }
+ if (cmtlev > 0)
+ {
+ if (c == ')')
+ {
+ cmtlev--;
+ copylev--;
+ if (SM_HAVE_ROOM)
+ {
+ realcmtlev--;
+ buflim++;
+ }
+ }
+ continue;
+ }
+ else if (c == ')')
+ {
+ /* syntax error: unmatched ) */
+ if (copylev > 0 && SM_HAVE_ROOM && bp > bufhead)
+ bp--;
+ }
+
+ /* count nesting on [ ... ] (for IPv6 domain literals) */
+ if (c == '[')
+ bracklev++;
+ else if (c == ']')
+ bracklev--;
+
+ /* check for group: list; syntax */
+ if (c == ':' && anglelev <= 0 && bracklev <= 0 &&
+ !gotcolon && !ColonOkInAddr)
+ {
+ register char *q;
+
+ /*
+ ** Check for DECnet phase IV ``::'' (host::user)
+ ** or DECnet phase V ``:.'' syntaxes. The latter
+ ** covers ``user@DEC:.tay.myhost'' and
+ ** ``DEC:.tay.myhost::user'' syntaxes (bletch).
+ */
+
+ if (*p == ':' || *p == '.')
+ {
+ if (cmtlev <= 0 && !qmode)
+ quoteit = true;
+ if (copylev > 0)
+ {
+ SM_APPEND_CHAR(c);
+ SM_APPEND_CHAR(*p);
+ }
+ p++;
+ goto putg;
+ }
+
+ gotcolon = true;
+
+ bp = bufhead;
+ if (quoteit)
+ {
+ SM_APPEND_CHAR('"');
+
+ /* back up over the ':' and any spaces */
+ --p;
+ while (p > addr &&
+ isascii(*--p) && isspace(*p))
+ continue;
+ p++;
+ }
+ for (q = addrhead; q < p; )
+ {
+ c = *q++;
+ if (quoteit && c == '"')
+ SM_APPEND_CHAR('\\');
+ SM_APPEND_CHAR(c);
+ }
+ if (quoteit)
+ {
+ if (bp == &bufhead[1])
+ bp--;
+ else
+ SM_APPEND_CHAR('"');
+ while ((c = *p++) != ':')
+ SM_APPEND_CHAR(c);
+ SM_APPEND_CHAR(c);
+ }
+
+ /* any trailing white space is part of group: */
+ while (isascii(*p) && isspace(*p))
+ {
+ SM_APPEND_CHAR(*p);
+ p++;
+ }
+ copylev = 0;
+ putgmac = quoteit = false;
+ bufhead = bp;
+ addrhead = p;
+ continue;
+ }
+
+ if (c == ';' && copylev <= 0 && !ColonOkInAddr)
+ SM_APPEND_CHAR(c);
+
+ /* check for characters that may have to be quoted */
+ if (strchr(MustQuoteChars, c) != NULL)
+ {
+ /*
+ ** If these occur as the phrase part of a <>
+ ** construct, but are not inside of () or already
+ ** quoted, they will have to be quoted. Note that
+ ** now (but don't actually do the quoting).
+ */
+
+ if (cmtlev <= 0 && !qmode)
+ quoteit = true;
+ }
+
+ /* check for angle brackets */
+ if (c == '<')
+ {
+ register char *q;
+
+ /* assume first of two angles is bogus */
+ if (gotangle)
+ quoteit = true;
+ gotangle = true;
+
+ /* oops -- have to change our mind */
+ anglelev = 1;
+ if (SM_HAVE_ROOM)
+ {
+ if (!addangle)
+ buflim--;
+ addangle = true;
+ }
+
+ bp = bufhead;
+ if (quoteit)
+ {
+ SM_APPEND_CHAR('"');
+
+ /* back up over the '<' and any spaces */
+ --p;
+ while (p > addr &&
+ isascii(*--p) && isspace(*p))
+ continue;
+ p++;
+ }
+ for (q = addrhead; q < p; )
+ {
+ c = *q++;
+ if (quoteit && c == '"')
+ {
+ SM_APPEND_CHAR('\\');
+ SM_APPEND_CHAR(c);
+ }
+ else
+ SM_APPEND_CHAR(c);
+ }
+ if (quoteit)
+ {
+ if (bp == &buf[1])
+ bp--;
+ else
+ SM_APPEND_CHAR('"');
+ while ((c = *p++) != '<')
+ SM_APPEND_CHAR(c);
+ SM_APPEND_CHAR(c);
+ }
+ copylev = 0;
+ putgmac = quoteit = false;
+ continue;
+ }
+
+ if (c == '>')
+ {
+ if (anglelev > 0)
+ {
+ anglelev--;
+ if (SM_HAVE_ROOM)
+ {
+ if (addangle)
+ buflim++;
+ addangle = false;
+ }
+ }
+ else if (SM_HAVE_ROOM)
+ {
+ /* syntax error: unmatched > */
+ if (copylev > 0 && bp > bufhead)
+ bp--;
+ quoteit = true;
+ continue;
+ }
+ if (copylev++ <= 0)
+ SM_APPEND_CHAR(c);
+ continue;
+ }
+
+ /* must be a real address character */
+ putg:
+ if (copylev <= 0 && !putgmac)
+ {
+ if (bp > buf && bp[-1] == ')')
+ SM_APPEND_CHAR(' ');
+ SM_APPEND_CHAR(MACROEXPAND);
+ SM_APPEND_CHAR('g');
+ putgmac = true;
+ }
+ }
+
+ /* repair any syntactic damage */
+ if (realqmode && bp < bufend)
+ *bp++ = '"';
+ while (realcmtlev-- > 0 && bp < bufend)
+ *bp++ = ')';
+ if (addangle && bp < bufend)
+ *bp++ = '>';
+ *bp = '\0';
+ if (bp < bufend)
+ goto success;
+
+ returng:
+ /* String too long, punt */
+ buf[0] = '<';
+ buf[1] = MACROEXPAND;
+ buf[2]= 'g';
+ buf[3] = '>';
+ buf[4]= '\0';
+ sm_syslog(LOG_ALERT, e->e_id,
+ "Dropped invalid comments from header address");
+
+ success:
+ if (tTd(33, 1))
+ {
+ sm_dprintf("crackaddr=>`");
+ xputs(sm_debug_file(), buf);
+ sm_dprintf("'\n");
+ }
+ return buf;
+}
+/*
+** PUTHEADER -- put the header part of a message from the in-core copy
+**
+** Parameters:
+** mci -- the connection information.
+** hdr -- the header to put.
+** e -- envelope to use.
+** flags -- MIME conversion flags.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** none.
+*/
+
+void
+putheader(mci, hdr, e, flags)
+ register MCI *mci;
+ HDR *hdr;
+ register ENVELOPE *e;
+ int flags;
+{
+ register HDR *h;
+ char buf[SM_MAX(MAXLINE,BUFSIZ)];
+ char obuf[MAXLINE];
+
+ if (tTd(34, 1))
+ sm_dprintf("--- putheader, mailer = %s ---\n",
+ mci->mci_mailer->m_name);
+
+ /*
+ ** If we're in MIME mode, we're not really in the header of the
+ ** message, just the header of one of the parts of the body of
+ ** the message. Therefore MCIF_INHEADER should not be turned on.
+ */
+
+ if (!bitset(MCIF_INMIME, mci->mci_flags))
+ mci->mci_flags |= MCIF_INHEADER;
+
+ for (h = hdr; h != NULL; h = h->h_link)
+ {
+ register char *p = h->h_value;
+ char *q;
+
+ if (tTd(34, 11))
+ {
+ sm_dprintf(" %s: ", h->h_field);
+ xputs(sm_debug_file(), p);
+ }
+
+ /* Skip empty headers */
+ if (h->h_value == NULL)
+ continue;
+
+ /* heuristic shortening of MIME fields to avoid MUA overflows */
+ if (MaxMimeFieldLength > 0 &&
+ wordinclass(h->h_field,
+ macid("{checkMIMEFieldHeaders}")))
+ {
+ size_t len;
+
+ len = fix_mime_header(h, e);
+ if (len > 0)
+ {
+ sm_syslog(LOG_ALERT, e->e_id,
+ "Truncated MIME %s header due to field size (length = %ld) (possible attack)",
+ h->h_field, (unsigned long) len);
+ if (tTd(34, 11))
+ sm_dprintf(" truncated MIME %s header due to field size (length = %ld) (possible attack)\n",
+ h->h_field,
+ (unsigned long) len);
+ }
+ }
+
+ if (MaxMimeHeaderLength > 0 &&
+ wordinclass(h->h_field,
+ macid("{checkMIMETextHeaders}")))
+ {
+ size_t len;
+
+ len = strlen(h->h_value);
+ if (len > (size_t) MaxMimeHeaderLength)
+ {
+ h->h_value[MaxMimeHeaderLength - 1] = '\0';
+ sm_syslog(LOG_ALERT, e->e_id,
+ "Truncated long MIME %s header (length = %ld) (possible attack)",
+ h->h_field, (unsigned long) len);
+ if (tTd(34, 11))
+ sm_dprintf(" truncated long MIME %s header (length = %ld) (possible attack)\n",
+ h->h_field,
+ (unsigned long) len);
+ }
+ }
+
+ if (MaxMimeHeaderLength > 0 &&
+ wordinclass(h->h_field,
+ macid("{checkMIMEHeaders}")))
+ {
+ size_t len;
+
+ len = strlen(h->h_value);
+ if (shorten_rfc822_string(h->h_value,
+ MaxMimeHeaderLength))
+ {
+ if (len < MaxMimeHeaderLength)
+ {
+ /* we only rebalanced a bogus header */
+ sm_syslog(LOG_ALERT, e->e_id,
+ "Fixed MIME %s header (possible attack)",
+ h->h_field);
+ if (tTd(34, 11))
+ sm_dprintf(" fixed MIME %s header (possible attack)\n",
+ h->h_field);
+ }
+ else
+ {
+ /* we actually shortened header */
+ sm_syslog(LOG_ALERT, e->e_id,
+ "Truncated long MIME %s header (length = %ld) (possible attack)",
+ h->h_field,
+ (unsigned long) len);
+ if (tTd(34, 11))
+ sm_dprintf(" truncated long MIME %s header (length = %ld) (possible attack)\n",
+ h->h_field,
+ (unsigned long) len);
+ }
+ }
+ }
+
+ /*
+ ** Suppress Content-Transfer-Encoding: if we are MIMEing
+ ** and we are potentially converting from 8 bit to 7 bit
+ ** MIME. If converting, add a new CTE header in
+ ** mime8to7().
+ */
+
+ if (bitset(H_CTE, h->h_flags) &&
+ bitset(MCIF_CVT8TO7|MCIF_CVT7TO8|MCIF_INMIME,
+ mci->mci_flags) &&
+ !bitset(M87F_NO8TO7, flags))
+ {
+ if (tTd(34, 11))
+ sm_dprintf(" (skipped (content-transfer-encoding))\n");
+ continue;
+ }
+
+ if (bitset(MCIF_INMIME, mci->mci_flags))
+ {
+ if (tTd(34, 11))
+ sm_dprintf("\n");
+ put_vanilla_header(h, p, mci);
+ continue;
+ }
+
+ if (bitset(H_CHECK|H_ACHECK, h->h_flags) &&
+ !bitintersect(h->h_mflags, mci->mci_mailer->m_flags) &&
+ (h->h_macro == '\0' ||
+ (q = macvalue(bitidx(h->h_macro), e)) == NULL ||
+ *q == '\0'))
+ {
+ if (tTd(34, 11))
+ sm_dprintf(" (skipped)\n");
+ continue;
+ }
+
+ /* handle Resent-... headers specially */
+ if (bitset(H_RESENT, h->h_flags) && !bitset(EF_RESENT, e->e_flags))
+ {
+ if (tTd(34, 11))
+ sm_dprintf(" (skipped (resent))\n");
+ continue;
+ }
+
+ /* suppress return receipts if requested */
+ if (bitset(H_RECEIPTTO, h->h_flags) &&
+ (RrtImpliesDsn || bitset(EF_NORECEIPT, e->e_flags)))
+ {
+ if (tTd(34, 11))
+ sm_dprintf(" (skipped (receipt))\n");
+ continue;
+ }
+
+ /* macro expand value if generated internally */
+ if (bitset(H_DEFAULT, h->h_flags) ||
+ bitset(H_BINDLATE, h->h_flags))
+ {
+ expand(p, buf, sizeof buf, e);
+ p = buf;
+ if (*p == '\0')
+ {
+ if (tTd(34, 11))
+ sm_dprintf(" (skipped -- null value)\n");
+ continue;
+ }
+ }
+
+ if (bitset(H_BCC, h->h_flags))
+ {
+ /* Bcc: field -- either truncate or delete */
+ if (bitset(EF_DELETE_BCC, e->e_flags))
+ {
+ if (tTd(34, 11))
+ sm_dprintf(" (skipped -- bcc)\n");
+ }
+ else
+ {
+ /* no other recipient headers: truncate value */
+ (void) sm_strlcpyn(obuf, sizeof obuf, 2,
+ h->h_field, ":");
+ putline(obuf, mci);
+ }
+ continue;
+ }
+
+ if (tTd(34, 11))
+ sm_dprintf("\n");
+
+ if (bitset(H_FROM|H_RCPT, h->h_flags))
+ {
+ /* address field */
+ bool oldstyle = bitset(EF_OLDSTYLE, e->e_flags);
+
+ if (bitset(H_FROM, h->h_flags))
+ oldstyle = false;
+ commaize(h, p, oldstyle, mci, e);
+ }
+ else
+ {
+ put_vanilla_header(h, p, mci);
+ }
+ }
+
+ /*
+ ** If we are converting this to a MIME message, add the
+ ** MIME headers (but not in MIME mode!).
+ */
+
+#if MIME8TO7
+ if (bitset(MM_MIME8BIT, MimeMode) &&
+ bitset(EF_HAS8BIT, e->e_flags) &&
+ !bitset(EF_DONT_MIME, e->e_flags) &&
+ !bitnset(M_8BITS, mci->mci_mailer->m_flags) &&
+ !bitset(MCIF_CVT8TO7|MCIF_CVT7TO8|MCIF_INMIME, mci->mci_flags) &&
+ hvalue("MIME-Version", e->e_header) == NULL)
+ {
+ putline("MIME-Version: 1.0", mci);
+ if (hvalue("Content-Type", e->e_header) == NULL)
+ {
+ (void) sm_snprintf(obuf, sizeof obuf,
+ "Content-Type: text/plain; charset=%s",
+ defcharset(e));
+ putline(obuf, mci);
+ }
+ if (hvalue("Content-Transfer-Encoding", e->e_header) == NULL)
+ putline("Content-Transfer-Encoding: 8bit", mci);
+ }
+#endif /* MIME8TO7 */
+}
+/*
+** PUT_VANILLA_HEADER -- output a fairly ordinary header
+**
+** Parameters:
+** h -- the structure describing this header
+** v -- the value of this header
+** mci -- the connection info for output
+**
+** Returns:
+** none.
+*/
+
+static void
+put_vanilla_header(h, v, mci)
+ HDR *h;
+ char *v;
+ MCI *mci;
+{
+ register char *nlp;
+ register char *obp;
+ int putflags;
+ char obuf[MAXLINE + 256]; /* additional length for h_field */
+
+ putflags = PXLF_HEADER;
+ if (bitnset(M_7BITHDRS, mci->mci_mailer->m_flags))
+ putflags |= PXLF_STRIP8BIT;
+ (void) sm_snprintf(obuf, sizeof obuf, "%.200s: ", h->h_field);
+ obp = obuf + strlen(obuf);
+ while ((nlp = strchr(v, '\n')) != NULL)
+ {
+ int l;
+
+ l = nlp - v;
+
+ /*
+ ** XXX This is broken for SPACELEFT()==0
+ ** However, SPACELEFT() is always > 0 unless MAXLINE==1.
+ */
+
+ if (SPACELEFT(obuf, obp) - 1 < (size_t) l)
+ l = SPACELEFT(obuf, obp) - 1;
+
+ (void) sm_snprintf(obp, SPACELEFT(obuf, obp), "%.*s", l, v);
+ putxline(obuf, strlen(obuf), mci, putflags);
+ v += l + 1;
+ obp = obuf;
+ if (*v != ' ' && *v != '\t')
+ *obp++ = ' ';
+ }
+
+ /* XXX This is broken for SPACELEFT()==0 */
+ (void) sm_snprintf(obp, SPACELEFT(obuf, obp), "%.*s",
+ (int) (SPACELEFT(obuf, obp) - 1), v);
+ putxline(obuf, strlen(obuf), mci, putflags);
+}
+/*
+** COMMAIZE -- output a header field, making a comma-translated list.
+**
+** Parameters:
+** h -- the header field to output.
+** p -- the value to put in it.
+** oldstyle -- true if this is an old style header.
+** mci -- the connection information.
+** e -- the envelope containing the message.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** outputs "p" to file "fp".
+*/
+
+void
+commaize(h, p, oldstyle, mci, e)
+ register HDR *h;
+ register char *p;
+ bool oldstyle;
+ register MCI *mci;
+ register ENVELOPE *e;
+{
+ register char *obp;
+ int opos;
+ int omax;
+ bool firstone = true;
+ int putflags = PXLF_HEADER;
+ char **res;
+ char obuf[MAXLINE + 3];
+
+ /*
+ ** Output the address list translated by the
+ ** mailer and with commas.
+ */
+
+ if (tTd(14, 2))
+ sm_dprintf("commaize(%s: %s)\n", h->h_field, p);
+
+ if (bitnset(M_7BITHDRS, mci->mci_mailer->m_flags))
+ putflags |= PXLF_STRIP8BIT;
+
+ obp = obuf;
+ (void) sm_snprintf(obp, SPACELEFT(obuf, obp), "%.200s: ",
+ h->h_field);
+
+ /* opos = strlen(obp); */
+ opos = strlen(h->h_field) + 2;
+ if (opos > 202)
+ opos = 202;
+ obp += opos;
+ omax = mci->mci_mailer->m_linelimit - 2;
+ if (omax < 0 || omax > 78)
+ omax = 78;
+
+ /*
+ ** Run through the list of values.
+ */
+
+ while (*p != '\0')
+ {
+ register char *name;
+ register int c;
+ char savechar;
+ int flags;
+ auto int status;
+
+ /*
+ ** Find the end of the name. New style names
+ ** end with a comma, old style names end with
+ ** a space character. However, spaces do not
+ ** necessarily delimit an old-style name -- at
+ ** signs mean keep going.
+ */
+
+ /* find end of name */
+ while ((isascii(*p) && isspace(*p)) || *p == ',')
+ p++;
+ name = p;
+ res = NULL;
+ for (;;)
+ {
+ auto char *oldp;
+ char pvpbuf[PSBUFSIZE];
+
+ res = prescan(p, oldstyle ? ' ' : ',', pvpbuf,
+ sizeof pvpbuf, &oldp, NULL, false);
+ p = oldp;
+#if _FFR_IGNORE_BOGUS_ADDR
+ /* ignore addresses that can't be parsed */
+ if (res == NULL)
+ {
+ name = p;
+ continue;
+ }
+#endif /* _FFR_IGNORE_BOGUS_ADDR */
+
+ /* look to see if we have an at sign */
+ while (*p != '\0' && isascii(*p) && isspace(*p))
+ p++;
+
+ if (*p != '@')
+ {
+ p = oldp;
+ break;
+ }
+ ++p;
+ while (*p != '\0' && isascii(*p) && isspace(*p))
+ p++;
+ }
+ /* at the end of one complete name */
+
+ /* strip off trailing white space */
+ while (p >= name &&
+ ((isascii(*p) && isspace(*p)) || *p == ',' || *p == '\0'))
+ p--;
+ if (++p == name)
+ continue;
+
+ /*
+ ** if prescan() failed go a bit backwards; this is a hack,
+ ** there should be some better error recovery.
+ */
+
+ if (res == NULL && p > name &&
+ !((isascii(*p) && isspace(*p)) || *p == ',' || *p == '\0'))
+ --p;
+ savechar = *p;
+ *p = '\0';
+
+ /* translate the name to be relative */
+ flags = RF_HEADERADDR|RF_ADDDOMAIN;
+ if (bitset(H_FROM, h->h_flags))
+ flags |= RF_SENDERADDR;
+#if USERDB
+ else if (e->e_from.q_mailer != NULL &&
+ bitnset(M_UDBRECIPIENT, e->e_from.q_mailer->m_flags))
+ {
+ char *q;
+
+ q = udbsender(name, e->e_rpool);
+ if (q != NULL)
+ name = q;
+ }
+#endif /* USERDB */
+ status = EX_OK;
+ name = remotename(name, mci->mci_mailer, flags, &status, e);
+ if (*name == '\0')
+ {
+ *p = savechar;
+ continue;
+ }
+ name = denlstring(name, false, true);
+
+ /*
+ ** record data progress so DNS timeouts
+ ** don't cause DATA timeouts
+ */
+
+ DataProgress = true;
+
+ /* output the name with nice formatting */
+ opos += strlen(name);
+ if (!firstone)
+ opos += 2;
+ if (opos > omax && !firstone)
+ {
+ (void) sm_strlcpy(obp, ",\n", SPACELEFT(obuf, obp));
+ putxline(obuf, strlen(obuf), mci, putflags);
+ obp = obuf;
+ (void) sm_strlcpy(obp, " ", sizeof obuf);
+ opos = strlen(obp);
+ obp += opos;
+ opos += strlen(name);
+ }
+ else if (!firstone)
+ {
+ (void) sm_strlcpy(obp, ", ", SPACELEFT(obuf, obp));
+ obp += 2;
+ }
+
+ while ((c = *name++) != '\0' && obp < &obuf[MAXLINE])
+ *obp++ = c;
+ firstone = false;
+ *p = savechar;
+ }
+ if (obp < &obuf[sizeof obuf])
+ *obp = '\0';
+ else
+ obuf[sizeof obuf - 1] = '\0';
+ putxline(obuf, strlen(obuf), mci, putflags);
+}
+/*
+** COPYHEADER -- copy header list
+**
+** This routine is the equivalent of newstr for header lists
+**
+** Parameters:
+** header -- list of header structures to copy.
+** rpool -- resource pool, or NULL
+**
+** Returns:
+** a copy of 'header'.
+**
+** Side Effects:
+** none.
+*/
+
+HDR *
+copyheader(header, rpool)
+ register HDR *header;
+ SM_RPOOL_T *rpool;
+{
+ register HDR *newhdr;
+ HDR *ret;
+ register HDR **tail = &ret;
+
+ while (header != NULL)
+ {
+ newhdr = (HDR *) sm_rpool_malloc_x(rpool, sizeof *newhdr);
+ STRUCTCOPY(*header, *newhdr);
+ *tail = newhdr;
+ tail = &newhdr->h_link;
+ header = header->h_link;
+ }
+ *tail = NULL;
+
+ return ret;
+}
+/*
+** FIX_MIME_HEADER -- possibly truncate/rebalance parameters in a MIME header
+**
+** Run through all of the parameters of a MIME header and
+** possibly truncate and rebalance the parameter according
+** to MaxMimeFieldLength.
+**
+** Parameters:
+** h -- the header to truncate/rebalance
+** e -- the current envelope
+**
+** Returns:
+** length of last offending field, 0 if all ok.
+**
+** Side Effects:
+** string modified in place
+*/
+
+static size_t
+fix_mime_header(h, e)
+ HDR *h;
+ ENVELOPE *e;
+{
+ char *begin = h->h_value;
+ char *end;
+ size_t len = 0;
+ size_t retlen = 0;
+
+ if (begin == NULL || *begin == '\0')
+ return 0;
+
+ /* Split on each ';' */
+ /* find_character() never returns NULL */
+ while ((end = find_character(begin, ';')) != NULL)
+ {
+ char save = *end;
+ char *bp;
+
+ *end = '\0';
+
+ len = strlen(begin);
+
+ /* Shorten individual parameter */
+ if (shorten_rfc822_string(begin, MaxMimeFieldLength))
+ {
+ if (len < MaxMimeFieldLength)
+ {
+ /* we only rebalanced a bogus field */
+ sm_syslog(LOG_ALERT, e->e_id,
+ "Fixed MIME %s header field (possible attack)",
+ h->h_field);
+ if (tTd(34, 11))
+ sm_dprintf(" fixed MIME %s header field (possible attack)\n",
+ h->h_field);
+ }
+ else
+ {
+ /* we actually shortened the header */
+ retlen = len;
+ }
+ }
+
+ /* Collapse the possibly shortened string with rest */
+ bp = begin + strlen(begin);
+ if (bp != end)
+ {
+ char *ep = end;
+
+ *end = save;
+ end = bp;
+
+ /* copy character by character due to overlap */
+ while (*ep != '\0')
+ *bp++ = *ep++;
+ *bp = '\0';
+ }
+ else
+ *end = save;
+ if (*end == '\0')
+ break;
+
+ /* Move past ';' */
+ begin = end + 1;
+ }
+ return retlen;
+}
diff --git a/usr/src/cmd/sendmail/src/macro.c b/usr/src/cmd/sendmail/src/macro.c
new file mode 100644
index 0000000000..a4ee54174b
--- /dev/null
+++ b/usr/src/cmd/sendmail/src/macro.c
@@ -0,0 +1,600 @@
+/*
+ * Copyright (c) 1998-2001, 2003 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sendmail.h>
+
+SM_RCSID("@(#)$Id: macro.c,v 8.88 2003/09/05 23:11:18 ca Exp $")
+
+#if MAXMACROID != (BITMAPBITS - 1)
+ ERROR Read the comment in conf.h
+#endif /* MAXMACROID != (BITMAPBITS - 1) */
+
+static char *MacroName[MAXMACROID + 1]; /* macro id to name table */
+int NextMacroId = 0240; /* codes for long named macros */
+
+/*
+** INITMACROS -- initialize the macro system
+**
+** This just involves defining some macros that are actually
+** used internally as metasymbols to be themselves.
+**
+** Parameters:
+** none.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** initializes several macros to be themselves.
+*/
+
+struct metamac MetaMacros[] =
+{
+ /* LHS pattern matching characters */
+ { '*', MATCHZANY }, { '+', MATCHANY }, { '-', MATCHONE },
+ { '=', MATCHCLASS }, { '~', MATCHNCLASS },
+
+ /* these are RHS metasymbols */
+ { '#', CANONNET }, { '@', CANONHOST }, { ':', CANONUSER },
+ { '>', CALLSUBR },
+
+ /* the conditional operations */
+ { '?', CONDIF }, { '|', CONDELSE }, { '.', CONDFI },
+
+ /* the hostname lookup characters */
+ { '[', HOSTBEGIN }, { ']', HOSTEND },
+ { '(', LOOKUPBEGIN }, { ')', LOOKUPEND },
+
+ /* miscellaneous control characters */
+ { '&', MACRODEXPAND },
+
+ { '\0', '\0' }
+};
+
+#define MACBINDING(name, mid) \
+ stab(name, ST_MACRO, ST_ENTER)->s_macro = mid; \
+ MacroName[mid] = name;
+
+void
+initmacros(e)
+ register ENVELOPE *e;
+{
+ register struct metamac *m;
+ register int c;
+ char buf[5];
+
+ for (m = MetaMacros; m->metaname != '\0'; m++)
+ {
+ buf[0] = m->metaval;
+ buf[1] = '\0';
+ macdefine(&e->e_macro, A_TEMP, m->metaname, buf);
+ }
+ buf[0] = MATCHREPL;
+ buf[2] = '\0';
+ for (c = '0'; c <= '9'; c++)
+ {
+ buf[1] = c;
+ macdefine(&e->e_macro, A_TEMP, c, buf);
+ }
+
+ /* set defaults for some macros sendmail will use later */
+ macdefine(&e->e_macro, A_PERM, 'n', "MAILER-DAEMON");
+
+ /* set up external names for some internal macros */
+ MACBINDING("opMode", MID_OPMODE);
+ /*XXX should probably add equivalents for all short macros here XXX*/
+}
+/*
+** EXPAND -- macro expand a string using $x escapes.
+**
+** Parameters:
+** s -- the string to expand.
+** buf -- the place to put the expansion.
+** bufsize -- the size of the buffer.
+** e -- envelope in which to work.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** none.
+*/
+
+void
+expand(s, buf, bufsize, e)
+ register char *s;
+ register char *buf;
+ size_t bufsize;
+ register ENVELOPE *e;
+{
+ register char *xp;
+ register char *q;
+ bool skipping; /* set if conditionally skipping output */
+ bool recurse; /* set if recursion required */
+ size_t i;
+ int skiplev; /* skipping nesting level */
+ int iflev; /* if nesting level */
+ char xbuf[MACBUFSIZE];
+ static int explevel = 0;
+
+ if (tTd(35, 24))
+ {
+ sm_dprintf("expand(");
+ xputs(sm_debug_file(), s);
+ sm_dprintf(")\n");
+ }
+
+ recurse = false;
+ skipping = false;
+ skiplev = 0;
+ iflev = 0;
+ if (s == NULL)
+ s = "";
+ for (xp = xbuf; *s != '\0'; s++)
+ {
+ int c;
+
+ /*
+ ** Check for non-ordinary (special?) character.
+ ** 'q' will be the interpolated quantity.
+ */
+
+ q = NULL;
+ c = *s;
+ switch (c & 0377)
+ {
+ case CONDIF: /* see if var set */
+ iflev++;
+ c = *++s;
+ if (skipping)
+ skiplev++;
+ else
+ {
+ char *mv;
+
+ mv = macvalue(c, e);
+ skipping = (mv == NULL || *mv == '\0');
+ }
+ continue;
+
+ case CONDELSE: /* change state of skipping */
+ if (iflev == 0)
+ break; /* XXX: error */
+ if (skiplev == 0)
+ skipping = !skipping;
+ continue;
+
+ case CONDFI: /* stop skipping */
+ if (iflev == 0)
+ break; /* XXX: error */
+ iflev--;
+ if (skiplev == 0)
+ skipping = false;
+ if (skipping)
+ skiplev--;
+ continue;
+
+ case MACROEXPAND: /* macro interpolation */
+ c = bitidx(*++s);
+ if (c != '\0')
+ q = macvalue(c, e);
+ else
+ {
+ s--;
+ q = NULL;
+ }
+ if (q == NULL)
+ continue;
+ break;
+ }
+
+ /*
+ ** Interpolate q or output one character
+ */
+
+ if (skipping || xp >= &xbuf[sizeof xbuf - 1])
+ continue;
+ if (q == NULL)
+ *xp++ = c;
+ else
+ {
+ /* copy to end of q or max space remaining in buf */
+ while ((c = *q++) != '\0' && xp < &xbuf[sizeof xbuf - 1])
+ {
+ /* check for any sendmail metacharacters */
+ if ((c & 0340) == 0200)
+ recurse = true;
+ *xp++ = c;
+ }
+ }
+ }
+ *xp = '\0';
+
+ if (tTd(35, 24))
+ {
+ sm_dprintf("expand ==> ");
+ xputs(sm_debug_file(), xbuf);
+ sm_dprintf("\n");
+ }
+
+ /* recurse as appropriate */
+ if (recurse)
+ {
+ if (explevel < MaxMacroRecursion)
+ {
+ explevel++;
+ expand(xbuf, buf, bufsize, e);
+ explevel--;
+ return;
+ }
+ syserr("expand: recursion too deep (%d max)",
+ MaxMacroRecursion);
+ }
+
+ /* copy results out */
+ i = xp - xbuf;
+ if (i >= bufsize)
+ i = bufsize - 1;
+ memmove(buf, xbuf, i);
+ buf[i] = '\0';
+}
+
+/*
+** MACDEFINE -- bind a macro name to a value
+**
+** Set a macro to a value, with fancy storage management.
+** macdefine will make a copy of the value, if required,
+** and will ensure that the storage for the previous value
+** is not leaked.
+**
+** Parameters:
+** mac -- Macro table.
+** vclass -- storage class of 'value', ignored if value==NULL.
+** A_HEAP means that the value was allocated by
+** malloc, and that macdefine owns the storage.
+** A_TEMP means that value points to temporary storage,
+** and thus macdefine needs to make a copy.
+** A_PERM means that value points to storage that
+** will remain allocated and unchanged for
+** at least the lifetime of mac. Use A_PERM if:
+** -- value == NULL,
+** -- value points to a string literal,
+** -- value was allocated from mac->mac_rpool
+** or (in the case of an envelope macro)
+** from e->e_rpool,
+** -- in the case of an envelope macro,
+** value is a string member of the envelope
+** such as e->e_sender.
+** id -- Macro id. This is a single character macro name
+** such as 'g', or a value returned by macid().
+** value -- Macro value: either NULL, or a string.
+*/
+
+void
+#if SM_HEAP_CHECK
+macdefine_tagged(mac, vclass, id, value, file, line, grp)
+#else /* SM_HEAP_CHECK */
+macdefine(mac, vclass, id, value)
+#endif /* SM_HEAP_CHECK */
+ MACROS_T *mac;
+ ARGCLASS_T vclass;
+ int id;
+ char *value;
+#if SM_HEAP_CHECK
+ char *file;
+ int line;
+ int grp;
+#endif /* SM_HEAP_CHECK */
+{
+ char *newvalue;
+
+ if (id < 0 || id > MAXMACROID)
+ return;
+
+ if (tTd(35, 9))
+ {
+ sm_dprintf("%sdefine(%s as ",
+ mac->mac_table[id] == NULL ? "" : "re", macname(id));
+ xputs(sm_debug_file(), value);
+ sm_dprintf(")\n");
+ }
+
+ if (mac->mac_rpool == NULL)
+ {
+ char *freeit = NULL;
+
+ if (mac->mac_table[id] != NULL &&
+ bitnset(id, mac->mac_allocated))
+ freeit = mac->mac_table[id];
+
+ if (value == NULL || vclass == A_HEAP)
+ {
+ sm_heap_checkptr_tagged(value, file, line);
+ newvalue = value;
+ clrbitn(id, mac->mac_allocated);
+ }
+ else
+ {
+#if SM_HEAP_CHECK
+ newvalue = sm_strdup_tagged_x(value, file, line, 0);
+#else /* SM_HEAP_CHECK */
+ newvalue = sm_strdup_x(value);
+#endif /* SM_HEAP_CHECK */
+ setbitn(id, mac->mac_allocated);
+ }
+ mac->mac_table[id] = newvalue;
+ if (freeit != NULL)
+ sm_free(freeit);
+ }
+ else
+ {
+ if (value == NULL || vclass == A_PERM)
+ newvalue = value;
+ else
+ newvalue = sm_rpool_strdup_x(mac->mac_rpool, value);
+ mac->mac_table[id] = newvalue;
+ if (vclass == A_HEAP)
+ sm_free(value);
+ }
+
+#if _FFR_RESET_MACRO_GLOBALS
+ switch (id)
+ {
+ case 'j':
+ PSTRSET(MyHostName, value);
+ break;
+ }
+#endif /* _FFR_RESET_MACRO_GLOBALS */
+}
+
+/*
+** MACSET -- set a named macro to a value (low level)
+**
+** No fancy storage management; the caller takes full responsibility.
+** Often used with macget; see also macdefine.
+**
+** Parameters:
+** mac -- Macro table.
+** i -- Macro name, specified as an integer offset.
+** value -- Macro value: either NULL, or a string.
+*/
+
+void
+macset(mac, i, value)
+ MACROS_T *mac;
+ int i;
+ char *value;
+{
+ if (i < 0 || i > MAXMACROID)
+ return;
+
+ if (tTd(35, 9))
+ {
+ sm_dprintf("macset(%s as ", macname(i));
+ xputs(sm_debug_file(), value);
+ sm_dprintf(")\n");
+ }
+ mac->mac_table[i] = value;
+}
+
+/*
+** MACVALUE -- return uninterpreted value of a macro.
+**
+** Does fancy path searching.
+** The low level counterpart is macget.
+**
+** Parameters:
+** n -- the name of the macro.
+** e -- envelope in which to start looking for the macro.
+**
+** Returns:
+** The value of n.
+**
+** Side Effects:
+** none.
+*/
+
+char *
+macvalue(n, e)
+ int n;
+ register ENVELOPE *e;
+{
+ n = bitidx(n);
+ if (e != NULL && e->e_mci != NULL)
+ {
+ register char *p = e->e_mci->mci_macro.mac_table[n];
+
+ if (p != NULL)
+ return p;
+ }
+ while (e != NULL)
+ {
+ register char *p = e->e_macro.mac_table[n];
+
+ if (p != NULL)
+ return p;
+ if (e == e->e_parent)
+ break;
+ e = e->e_parent;
+ }
+ return GlobalMacros.mac_table[n];
+}
+/*
+** MACNAME -- return the name of a macro given its internal id
+**
+** Parameter:
+** n -- the id of the macro
+**
+** Returns:
+** The name of n.
+**
+** Side Effects:
+** none.
+*/
+
+char *
+macname(n)
+ int n;
+{
+ static char mbuf[2];
+
+ n = bitidx(n);
+ if (bitset(0200, n))
+ {
+ char *p = MacroName[n];
+
+ if (p != NULL)
+ return p;
+ return "***UNDEFINED MACRO***";
+ }
+ mbuf[0] = n;
+ mbuf[1] = '\0';
+ return mbuf;
+}
+/*
+** MACID_PARSE -- return id of macro identified by its name
+**
+** Parameters:
+** p -- pointer to name string -- either a single
+** character or {name}.
+** ep -- filled in with the pointer to the byte
+** after the name.
+**
+** Returns:
+** 0 -- An error was detected.
+** 1..255 -- The internal id code for this macro.
+**
+** Side Effects:
+** If this is a new macro name, a new id is allocated.
+** On error, syserr is called.
+*/
+
+int
+macid_parse(p, ep)
+ register char *p;
+ char **ep;
+{
+ int mid;
+ register char *bp;
+ char mbuf[MAXMACNAMELEN + 1];
+
+ if (tTd(35, 14))
+ {
+ sm_dprintf("macid(");
+ xputs(sm_debug_file(), p);
+ sm_dprintf(") => ");
+ }
+
+ if (*p == '\0' || (p[0] == '{' && p[1] == '}'))
+ {
+ syserr("Name required for macro/class");
+ if (ep != NULL)
+ *ep = p;
+ if (tTd(35, 14))
+ sm_dprintf("NULL\n");
+ return 0;
+ }
+ if (*p != '{')
+ {
+ /* the macro is its own code */
+ if (ep != NULL)
+ *ep = p + 1;
+ if (tTd(35, 14))
+ sm_dprintf("%c\n", bitidx(*p));
+ return bitidx(*p);
+ }
+ bp = mbuf;
+ while (*++p != '\0' && *p != '}' && bp < &mbuf[sizeof mbuf - 1])
+ {
+ if (isascii(*p) && (isalnum(*p) || *p == '_'))
+ *bp++ = *p;
+ else
+ syserr("Invalid macro/class character %c", *p);
+ }
+ *bp = '\0';
+ mid = -1;
+ if (*p == '\0')
+ {
+ syserr("Unbalanced { on %s", mbuf); /* missing } */
+ }
+ else if (*p != '}')
+ {
+ syserr("Macro/class name ({%s}) too long (%d chars max)",
+ mbuf, (int) (sizeof mbuf - 1));
+ }
+ else if (mbuf[1] == '\0')
+ {
+ /* ${x} == $x */
+ mid = bitidx(mbuf[0]);
+ p++;
+ }
+ else
+ {
+ register STAB *s;
+
+ s = stab(mbuf, ST_MACRO, ST_ENTER);
+ if (s->s_macro != 0)
+ mid = s->s_macro;
+ else
+ {
+ if (NextMacroId > MAXMACROID)
+ {
+ syserr("Macro/class {%s}: too many long names",
+ mbuf);
+ s->s_macro = -1;
+ }
+ else
+ {
+ MacroName[NextMacroId] = s->s_name;
+ s->s_macro = mid = NextMacroId++;
+ }
+ }
+ p++;
+ }
+ if (ep != NULL)
+ *ep = p;
+ if (mid < 0 || mid > MAXMACROID)
+ {
+ syserr("Unable to assign macro/class ID (mid = 0x%x)", mid);
+ if (tTd(35, 14))
+ sm_dprintf("NULL\n");
+ return 0;
+ }
+ if (tTd(35, 14))
+ sm_dprintf("0x%x\n", mid);
+ return mid;
+}
+/*
+** WORDINCLASS -- tell if a word is in a specific class
+**
+** Parameters:
+** str -- the name of the word to look up.
+** cl -- the class name.
+**
+** Returns:
+** true if str can be found in cl.
+** false otherwise.
+*/
+
+bool
+wordinclass(str, cl)
+ char *str;
+ int cl;
+{
+ register STAB *s;
+
+ s = stab(str, ST_CLASS, ST_FIND);
+ return s != NULL && bitnset(bitidx(cl), s->s_class);
+}
diff --git a/usr/src/cmd/sendmail/src/main.c b/usr/src/cmd/sendmail/src/main.c
new file mode 100644
index 0000000000..576cec59d1
--- /dev/null
+++ b/usr/src/cmd/sendmail/src/main.c
@@ -0,0 +1,4400 @@
+/*
+ * Copyright (c) 1998-2004 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+/*
+ * Copyright 1996-2004 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#define _DEFINE
+#include <sendmail.h>
+#include <sm/xtrap.h>
+#include <sm/signal.h>
+
+#ifndef lint
+SM_UNUSED(static char copyright[]) =
+"@(#) Copyright (c) 1998-2003 Sendmail, Inc. and its suppliers.\n\
+@(#) All rights reserved.\n\
+@(#) Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.\n\
+@(#) Copyright (c) 1988, 1993\n\
+@(#) The Regents of the University of California. All rights reserved.\n\
+@(#) Copyright 1996-2004 Sun Microsystems, Inc. All rights reserved.\n\
+@(#) Use is subject to license terms.\n";
+#endif /* ! lint */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+SM_RCSID("@(#)$Id: main.c,v 8.939 2004/06/17 16:39:21 ca Exp $")
+SM_IDSTR(i2, "%W% (Sun) %G%")
+
+#if NETINET || NETINET6
+# include <arpa/inet.h>
+#endif /* NETINET || NETINET6 */
+
+/* for getcfname() */
+#include <sendmail/pathnames.h>
+
+static SM_DEBUG_T
+DebugNoPRestart = SM_DEBUG_INITIALIZER("no_persistent_restart",
+ "@(#)$Debug: no_persistent_restart - don't restart, log only $");
+
+static void dump_class __P((STAB *, int));
+static void obsolete __P((char **));
+static void testmodeline __P((char *, ENVELOPE *));
+static char *getextenv __P((const char *));
+static void sm_printoptions __P((char **));
+static SIGFUNC_DECL intindebug __P((int));
+static SIGFUNC_DECL sighup __P((int));
+static SIGFUNC_DECL sigpipe __P((int));
+static SIGFUNC_DECL sigterm __P((int));
+#ifdef SIGUSR1
+static SIGFUNC_DECL sigusr1 __P((int));
+#endif /* SIGUSR1 */
+
+/*
+** SENDMAIL -- Post mail to a set of destinations.
+**
+** This is the basic mail router. All user mail programs should
+** call this routine to actually deliver mail. Sendmail in
+** turn calls a bunch of mail servers that do the real work of
+** delivering the mail.
+**
+** Sendmail is driven by settings read in from /etc/mail/sendmail.cf
+** (read by readcf.c).
+**
+** Usage:
+** /usr/lib/sendmail [flags] addr ...
+**
+** See the associated documentation for details.
+**
+** Authors:
+** Eric Allman, UCB/INGRES (until 10/81).
+** Britton-Lee, Inc., purveyors of fine
+** database computers (11/81 - 10/88).
+** International Computer Science Institute
+** (11/88 - 9/89).
+** UCB/Mammoth Project (10/89 - 7/95).
+** InReference, Inc. (8/95 - 1/97).
+** Sendmail, Inc. (1/98 - present).
+** The support of my employers is gratefully acknowledged.
+** Few of them (Britton-Lee in particular) have had
+** anything to gain from my involvement in this project.
+**
+** Gregory Neil Shapiro,
+** Worcester Polytechnic Institute (until 3/98).
+** Sendmail, Inc. (3/98 - present).
+**
+** Claus Assmann,
+** Sendmail, Inc. (12/98 - present).
+*/
+
+char *FullName; /* sender's full name */
+ENVELOPE BlankEnvelope; /* a "blank" envelope */
+static ENVELOPE MainEnvelope; /* the envelope around the basic letter */
+ADDRESS NullAddress = /* a null address */
+ { "", "", NULL, "" };
+char *CommandLineArgs; /* command line args for pid file */
+bool Warn_Q_option = false; /* warn about Q option use */
+static int MissingFds = 0; /* bit map of fds missing on startup */
+char *Mbdb = "pw"; /* mailbox database defaults to /etc/passwd */
+
+#ifdef NGROUPS_MAX
+GIDSET_T InitialGidSet[NGROUPS_MAX];
+#endif /* NGROUPS_MAX */
+
+#define MAXCONFIGLEVEL 10 /* highest config version level known */
+
+#if SASL
+static sasl_callback_t srvcallbacks[] =
+{
+ { SASL_CB_VERIFYFILE, &safesaslfile, NULL },
+ { SASL_CB_PROXY_POLICY, &proxy_policy, NULL },
+ { SASL_CB_LIST_END, NULL, NULL }
+};
+#endif /* SASL */
+
+unsigned int SubmitMode;
+int SyslogPrefixLen; /* estimated length of syslog prefix */
+#define PIDLEN 6 /* pid length for computing SyslogPrefixLen */
+#ifndef SL_FUDGE
+# define SL_FUDGE 10 /* fudge offset for SyslogPrefixLen */
+#endif /* ! SL_FUDGE */
+#define SLDLL 8 /* est. length of default syslog label */
+
+
+/* Some options are dangerous to allow users to use in non-submit mode */
+#define CHECK_AGAINST_OPMODE(cmd) \
+{ \
+ if (extraprivs && \
+ OpMode != MD_DELIVER && OpMode != MD_SMTP && \
+ OpMode != MD_ARPAFTP && \
+ OpMode != MD_VERIFY && OpMode != MD_TEST) \
+ { \
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, \
+ "WARNING: Ignoring submission mode -%c option (not in submission mode)\n", \
+ (cmd)); \
+ break; \
+ } \
+ if (extraprivs && queuerun) \
+ { \
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, \
+ "WARNING: Ignoring submission mode -%c option with -q\n", \
+ (cmd)); \
+ break; \
+ } \
+}
+
+int
+main(argc, argv, envp)
+ int argc;
+ char **argv;
+ char **envp;
+{
+ register char *p;
+ char **av;
+ extern char Version[];
+ char *ep, *from;
+ STAB *st;
+ register int i;
+ int j;
+ int dp;
+ int fill_errno;
+ int qgrp = NOQGRP; /* queue group to process */
+ bool safecf = true;
+ BITMAP256 *p_flags = NULL; /* daemon flags */
+ bool warn_C_flag = false;
+ bool auth = true; /* whether to set e_auth_param */
+ char warn_f_flag = '\0';
+ bool run_in_foreground = false; /* -bD mode */
+ bool queuerun = false, debug = false;
+ struct passwd *pw;
+ struct hostent *hp;
+ char *nullserver = NULL;
+ char *authinfo = NULL;
+ char *sysloglabel = NULL; /* label for syslog */
+ char *conffile = NULL; /* name of .cf file */
+ char *queuegroup = NULL; /* queue group to process */
+ char *quarantining = NULL; /* quarantine queue items? */
+ bool extraprivs;
+ bool forged, negate;
+ bool queuepersistent = false; /* queue runner process runs forever */
+ bool foregroundqueue = false; /* queue run in foreground */
+ bool save_val; /* to save some bool var. */
+ int cftype; /* which cf file to use? */
+ SM_FILE_T *smdebug;
+ static time_t starttime = 0; /* when was process started */
+ struct stat traf_st; /* for TrafficLog FIFO check */
+ char buf[MAXLINE];
+ char jbuf[MAXHOSTNAMELEN]; /* holds MyHostName */
+ static char rnamebuf[MAXNAME]; /* holds RealUserName */
+ char *emptyenviron[1];
+#if STARTTLS
+ bool tls_ok;
+#endif /* STARTTLS */
+ QUEUE_CHAR *new;
+ ENVELOPE *e;
+ extern int DtableSize;
+ extern int optind;
+ extern int opterr;
+ extern char *optarg;
+ extern char **environ;
+#if SASL
+ extern void sm_sasl_init __P((void));
+#endif /* SASL */
+
+#if USE_ENVIRON
+ envp = environ;
+#endif /* USE_ENVIRON */
+
+ /* turn off profiling */
+ SM_PROF(0);
+
+ /* install default exception handler */
+ sm_exc_newthread(fatal_error);
+
+ /* set the default in/out channel so errors reported to screen */
+ InChannel = smioin;
+ OutChannel = smioout;
+
+ /*
+ ** Check to see if we reentered.
+ ** This would normally happen if e_putheader or e_putbody
+ ** were NULL when invoked.
+ */
+
+ if (starttime != 0)
+ {
+ syserr("main: reentered!");
+ abort();
+ }
+ starttime = curtime();
+
+ /* avoid null pointer dereferences */
+ TermEscape.te_rv_on = TermEscape.te_rv_off = "";
+
+ RealUid = getuid();
+ RealGid = getgid();
+
+ /* Check if sendmail is running with extra privs */
+ extraprivs = (RealUid != 0 &&
+ (geteuid() != getuid() || getegid() != getgid()));
+
+ CurrentPid = getpid();
+
+ /* get whatever .cf file is right for the opmode */
+ cftype = SM_GET_RIGHT_CF;
+
+ /* in 4.4BSD, the table can be huge; impose a reasonable limit */
+ DtableSize = getdtsize();
+ if (DtableSize > 256)
+ DtableSize = 256;
+
+ /*
+ ** Be sure we have enough file descriptors.
+ ** But also be sure that 0, 1, & 2 are open.
+ */
+
+ /* reset errno and fill_errno; the latter is used way down below */
+ errno = fill_errno = 0;
+ fill_fd(STDIN_FILENO, NULL);
+ if (errno != 0)
+ fill_errno = errno;
+ fill_fd(STDOUT_FILENO, NULL);
+ if (errno != 0)
+ fill_errno = errno;
+ fill_fd(STDERR_FILENO, NULL);
+ if (errno != 0)
+ fill_errno = errno;
+
+ sm_closefrom(STDERR_FILENO + 1, DtableSize);
+ errno = 0;
+ smdebug = NULL;
+
+#if LOG
+# ifndef SM_LOG_STR
+# define SM_LOG_STR "sendmail"
+# endif /* ! SM_LOG_STR */
+# ifdef LOG_MAIL
+ openlog(SM_LOG_STR, LOG_PID, LOG_MAIL);
+# else /* LOG_MAIL */
+ openlog(SM_LOG_STR, LOG_PID);
+# endif /* LOG_MAIL */
+#endif /* LOG */
+
+ /*
+ ** Seed the random number generator.
+ ** Used for queue file names, picking a queue directory, and
+ ** MX randomization.
+ */
+
+ seed_random();
+
+ /* do machine-dependent initializations */
+ init_md(argc, argv);
+
+
+ SyslogPrefixLen = PIDLEN + (MAXQFNAME - 3) + SL_FUDGE + SLDLL;
+
+ /* reset status from syserr() calls for missing file descriptors */
+ Errors = 0;
+ ExitStat = EX_OK;
+
+ SubmitMode = SUBMIT_UNKNOWN;
+#if XDEBUG
+ checkfd012("after openlog");
+#endif /* XDEBUG */
+
+ tTsetup(tTdvect, sizeof tTdvect, "0-99.1,*_trace_*.1");
+
+#ifdef NGROUPS_MAX
+ /* save initial group set for future checks */
+ i = getgroups(NGROUPS_MAX, InitialGidSet);
+ if (i <= 0)
+ {
+ InitialGidSet[0] = (GID_T) -1;
+ i = 0;
+ }
+ while (i < NGROUPS_MAX)
+ InitialGidSet[i++] = InitialGidSet[0];
+#endif /* NGROUPS_MAX */
+
+ /* drop group id privileges (RunAsUser not yet set) */
+ dp = drop_privileges(false);
+ setstat(dp);
+
+#ifdef SIGUSR1
+ /* Only allow root (or non-set-*-ID binaries) to use SIGUSR1 */
+ if (!extraprivs)
+ {
+ /* arrange to dump state on user-1 signal */
+ (void) sm_signal(SIGUSR1, sigusr1);
+ }
+ else
+ {
+ /* ignore user-1 signal */
+ (void) sm_signal(SIGUSR1, SIG_IGN);
+ }
+#endif /* SIGUSR1 */
+
+ /* initialize for setproctitle */
+ initsetproctitle(argc, argv, envp);
+
+ /* Handle any non-getoptable constructions. */
+ obsolete(argv);
+
+ /*
+ ** Do a quick prescan of the argument list.
+ */
+
+
+ /* find initial opMode */
+ OpMode = MD_DELIVER;
+ av = argv;
+ p = strrchr(*av, '/');
+ if (p++ == NULL)
+ p = *av;
+ if (strcmp(p, "newaliases") == 0)
+ OpMode = MD_INITALIAS;
+ else if (strcmp(p, "mailq") == 0)
+ OpMode = MD_PRINT;
+ else if (strcmp(p, "smtpd") == 0)
+ OpMode = MD_DAEMON;
+ else if (strcmp(p, "hoststat") == 0)
+ OpMode = MD_HOSTSTAT;
+ else if (strcmp(p, "purgestat") == 0)
+ OpMode = MD_PURGESTAT;
+
+#if defined(__osf__) || defined(_AIX3)
+# define OPTIONS "A:B:b:C:cD:d:e:F:f:Gh:IiL:M:mN:nO:o:p:Q:q:R:r:sTtV:vX:x"
+#endif /* defined(__osf__) || defined(_AIX3) */
+#if defined(sony_news)
+# define OPTIONS "A:B:b:C:cD:d:E:e:F:f:Gh:IiJ:L:M:mN:nO:o:p:Q:q:R:r:sTtV:vX:"
+#endif /* defined(sony_news) */
+#ifndef OPTIONS
+# define OPTIONS "A:B:b:C:cD:d:e:F:f:Gh:IiL:M:mN:nO:o:p:Q:q:R:r:sTtV:vX:"
+#endif /* ! OPTIONS */
+
+ /* Set to 0 to allow -b; need to check optarg before using it! */
+ opterr = 0;
+ while ((j = getopt(argc, argv, OPTIONS)) != -1)
+ {
+ switch (j)
+ {
+ case 'b': /* operations mode */
+ j = (optarg == NULL) ? ' ' : *optarg;
+ switch (j)
+ {
+ case MD_DAEMON:
+ case MD_FGDAEMON:
+ case MD_SMTP:
+ case MD_INITALIAS:
+ case MD_DELIVER:
+ case MD_VERIFY:
+ case MD_TEST:
+ case MD_PRINT:
+ case MD_PRINTNQE:
+ case MD_HOSTSTAT:
+ case MD_PURGESTAT:
+ case MD_ARPAFTP:
+ OpMode = j;
+ break;
+
+ case MD_FREEZE:
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Frozen configurations unsupported\n");
+ return EX_USAGE;
+
+ default:
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Invalid operation mode %c\n",
+ j);
+ return EX_USAGE;
+ }
+ break;
+
+ case 'D':
+ if (debug)
+ {
+ errno = 0;
+ syserr("-D file must be before -d");
+ ExitStat = EX_USAGE;
+ break;
+ }
+ dp = drop_privileges(true);
+ setstat(dp);
+ smdebug = sm_io_open(SmFtStdio, SM_TIME_DEFAULT,
+ optarg, SM_IO_APPEND, NULL);
+ if (smdebug == NULL)
+ {
+ syserr("cannot open %s", optarg);
+ ExitStat = EX_CANTCREAT;
+ break;
+ }
+ sm_debug_setfile(smdebug);
+ break;
+
+ case 'd':
+ debug = true;
+ tTflag(optarg);
+ (void) sm_io_setvbuf(sm_debug_file(), SM_TIME_DEFAULT,
+ (char *) NULL, SM_IO_NBF,
+ SM_IO_BUFSIZ);
+ break;
+
+ case 'G': /* relay (gateway) submission */
+ SubmitMode = SUBMIT_MTA;
+ break;
+
+ case 'L':
+ if (optarg == NULL)
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "option requires an argument -- '%c'",
+ (char) j);
+ return EX_USAGE;
+ }
+ j = SM_MIN(strlen(optarg), 32) + 1;
+ sysloglabel = xalloc(j);
+ (void) sm_strlcpy(sysloglabel, optarg, j);
+ SyslogPrefixLen = PIDLEN + (MAXQFNAME - 3) +
+ SL_FUDGE + j;
+ break;
+
+ case 'Q':
+ case 'q':
+ /* just check if it is there */
+ queuerun = true;
+ break;
+ }
+ }
+ opterr = 1;
+
+ /* Don't leak queue information via debug flags */
+ if (extraprivs && queuerun && debug)
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "WARNING: Can not use -d with -q. Disabling debugging.\n");
+ sm_debug_close();
+ sm_debug_setfile(NULL);
+ (void) memset(tTdvect, '\0', sizeof tTdvect);
+ }
+
+#if LOG
+ if (sysloglabel != NULL)
+ {
+ /* Sanitize the string */
+ for (p = sysloglabel; *p != '\0'; p++)
+ {
+ if (!isascii(*p) || !isprint(*p) || *p == '%')
+ *p = '*';
+ }
+ closelog();
+# ifdef LOG_MAIL
+ openlog(sysloglabel, LOG_PID, LOG_MAIL);
+# else /* LOG_MAIL */
+ openlog(sysloglabel, LOG_PID);
+# endif /* LOG_MAIL */
+ }
+#endif /* LOG */
+
+ /* set up the blank envelope */
+ BlankEnvelope.e_puthdr = putheader;
+ BlankEnvelope.e_putbody = putbody;
+ BlankEnvelope.e_xfp = NULL;
+ STRUCTCOPY(NullAddress, BlankEnvelope.e_from);
+ CurEnv = &BlankEnvelope;
+ STRUCTCOPY(NullAddress, MainEnvelope.e_from);
+
+ /*
+ ** Set default values for variables.
+ ** These cannot be in initialized data space.
+ */
+
+ setdefaults(&BlankEnvelope);
+ initmacros(&BlankEnvelope);
+
+ /* reset macro */
+ set_op_mode(OpMode);
+
+ pw = sm_getpwuid(RealUid);
+ if (pw != NULL)
+ (void) sm_strlcpy(rnamebuf, pw->pw_name, sizeof rnamebuf);
+ else
+ (void) sm_snprintf(rnamebuf, sizeof rnamebuf, "Unknown UID %d",
+ (int) RealUid);
+
+ RealUserName = rnamebuf;
+
+ if (tTd(0, 101))
+ {
+ sm_dprintf("Version %s\n", Version);
+ finis(false, true, EX_OK);
+ /* NOTREACHED */
+ }
+
+ /*
+ ** if running non-set-user-ID binary as non-root, pretend
+ ** we are the RunAsUid
+ */
+
+ if (RealUid != 0 && geteuid() == RealUid)
+ {
+ if (tTd(47, 1))
+ sm_dprintf("Non-set-user-ID binary: RunAsUid = RealUid = %d\n",
+ (int) RealUid);
+ RunAsUid = RealUid;
+ }
+ else if (geteuid() != 0)
+ RunAsUid = geteuid();
+
+ EffGid = getegid();
+ if (RealUid != 0 && EffGid == RealGid)
+ RunAsGid = RealGid;
+
+ if (tTd(47, 5))
+ {
+ sm_dprintf("main: e/ruid = %d/%d e/rgid = %d/%d\n",
+ (int) geteuid(), (int) getuid(),
+ (int) getegid(), (int) getgid());
+ sm_dprintf("main: RunAsUser = %d:%d\n",
+ (int) RunAsUid, (int) RunAsGid);
+ }
+
+ /* save command line arguments */
+ j = 0;
+ for (av = argv; *av != NULL; )
+ j += strlen(*av++) + 1;
+ SaveArgv = (char **) xalloc(sizeof (char *) * (argc + 1));
+ CommandLineArgs = xalloc(j);
+ p = CommandLineArgs;
+ for (av = argv, i = 0; *av != NULL; )
+ {
+ int h;
+
+ SaveArgv[i++] = newstr(*av);
+ if (av != argv)
+ *p++ = ' ';
+ (void) sm_strlcpy(p, *av++, j);
+ h = strlen(p);
+ p += h;
+ j -= h + 1;
+ }
+ SaveArgv[i] = NULL;
+
+ if (tTd(0, 1))
+ {
+ extern char *CompileOptions[];
+
+ sm_dprintf("Version %s\n Compiled with:", Version);
+ sm_printoptions(CompileOptions);
+ }
+ if (tTd(0, 10))
+ {
+ extern char *OsCompileOptions[];
+
+ sm_dprintf(" OS Defines:");
+ sm_printoptions(OsCompileOptions);
+#ifdef _PATH_UNIX
+ sm_dprintf("Kernel symbols:\t%s\n", _PATH_UNIX);
+#endif /* _PATH_UNIX */
+
+ sm_dprintf(" Conf file:\t%s (default for MSP)\n",
+ getcfname(OpMode, SubmitMode, SM_GET_SUBMIT_CF,
+ conffile));
+ sm_dprintf(" Conf file:\t%s (default for MTA)\n",
+ getcfname(OpMode, SubmitMode, SM_GET_SENDMAIL_CF,
+ conffile));
+ sm_dprintf(" Pid file:\t%s (default)\n", PidFile);
+ }
+
+ if (tTd(0, 12))
+ {
+ extern char *SmCompileOptions[];
+
+ sm_dprintf(" libsm Defines:");
+ sm_printoptions(SmCompileOptions);
+ }
+
+ if (tTd(0, 13))
+ {
+ extern char *FFRCompileOptions[];
+
+ sm_dprintf(" FFR Defines:");
+ sm_printoptions(FFRCompileOptions);
+ }
+
+ /* clear sendmail's environment */
+ ExternalEnviron = environ;
+ emptyenviron[0] = NULL;
+ environ = emptyenviron;
+
+ /*
+ ** restore any original TZ setting until TimeZoneSpec has been
+ ** determined - or early log messages may get bogus time stamps
+ */
+
+ if ((p = getextenv("TZ")) != NULL)
+ {
+ char *tz;
+ int tzlen;
+
+ /* XXX check for reasonable length? */
+ tzlen = strlen(p) + 4;
+ tz = xalloc(tzlen);
+ (void) sm_strlcpyn(tz, tzlen, 2, "TZ=", p);
+
+ /* XXX check return code? */
+ (void) putenv(tz);
+ }
+
+ /* prime the child environment */
+ setuserenv("AGENT", "sendmail");
+
+ (void) sm_signal(SIGPIPE, SIG_IGN);
+ OldUmask = umask(022);
+ FullName = getextenv("NAME");
+ if (FullName != NULL)
+ FullName = newstr(FullName);
+
+ /*
+ ** Initialize name server if it is going to be used.
+ */
+
+#if NAMED_BIND
+ if (!bitset(RES_INIT, _res.options))
+ (void) res_init();
+ if (tTd(8, 8))
+ _res.options |= RES_DEBUG;
+ else
+ _res.options &= ~RES_DEBUG;
+# ifdef RES_NOALIASES
+ _res.options |= RES_NOALIASES;
+# endif /* RES_NOALIASES */
+ TimeOuts.res_retry[RES_TO_DEFAULT] = _res.retry;
+ TimeOuts.res_retry[RES_TO_FIRST] = _res.retry;
+ TimeOuts.res_retry[RES_TO_NORMAL] = _res.retry;
+ TimeOuts.res_retrans[RES_TO_DEFAULT] = _res.retrans;
+ TimeOuts.res_retrans[RES_TO_FIRST] = _res.retrans;
+ TimeOuts.res_retrans[RES_TO_NORMAL] = _res.retrans;
+#endif /* NAMED_BIND */
+
+ errno = 0;
+ from = NULL;
+
+ /* initialize some macros, etc. */
+ init_vendor_macros(&BlankEnvelope);
+
+ /* version */
+ macdefine(&BlankEnvelope.e_macro, A_PERM, 'v', Version);
+
+ /* hostname */
+ hp = myhostname(jbuf, sizeof jbuf);
+ if (jbuf[0] != '\0')
+ {
+ struct utsname utsname;
+
+ if (tTd(0, 4))
+ sm_dprintf("Canonical name: %s\n", jbuf);
+ macdefine(&BlankEnvelope.e_macro, A_TEMP, 'w', jbuf);
+ macdefine(&BlankEnvelope.e_macro, A_TEMP, 'j', jbuf);
+ setclass('w', jbuf);
+
+ p = strchr(jbuf, '.');
+ if (p != NULL && p[1] != '\0')
+ macdefine(&BlankEnvelope.e_macro, A_TEMP, 'm', &p[1]);
+
+ if (uname(&utsname) >= 0)
+ p = utsname.nodename;
+ else
+ {
+ if (tTd(0, 22))
+ sm_dprintf("uname failed (%s)\n",
+ sm_errstring(errno));
+ makelower(jbuf);
+ p = jbuf;
+ }
+ if (tTd(0, 4))
+ sm_dprintf(" UUCP nodename: %s\n", p);
+ macdefine(&BlankEnvelope.e_macro, A_TEMP, 'k', p);
+ setclass('k', p);
+ setclass('w', p);
+ }
+ if (hp != NULL)
+ {
+ for (av = hp->h_aliases; av != NULL && *av != NULL; av++)
+ {
+ if (tTd(0, 4))
+ sm_dprintf("\ta.k.a.: %s\n", *av);
+ setclass('w', *av);
+ }
+#if NETINET || NETINET6
+ for (i = 0; i >= 0 && hp->h_addr_list[i] != NULL; i++)
+ {
+# if NETINET6
+ char *addr;
+ char buf6[INET6_ADDRSTRLEN];
+ struct in6_addr ia6;
+# endif /* NETINET6 */
+# if NETINET
+ struct in_addr ia;
+# endif /* NETINET */
+ char ipbuf[103];
+
+ ipbuf[0] = '\0';
+ switch (hp->h_addrtype)
+ {
+# if NETINET
+ case AF_INET:
+ if (hp->h_length != INADDRSZ)
+ break;
+
+ memmove(&ia, hp->h_addr_list[i], INADDRSZ);
+ (void) sm_snprintf(ipbuf, sizeof ipbuf,
+ "[%.100s]", inet_ntoa(ia));
+ break;
+# endif /* NETINET */
+
+# if NETINET6
+ case AF_INET6:
+ if (hp->h_length != IN6ADDRSZ)
+ break;
+
+ memmove(&ia6, hp->h_addr_list[i], IN6ADDRSZ);
+ addr = anynet_ntop(&ia6, buf6, sizeof buf6);
+ if (addr != NULL)
+ (void) sm_snprintf(ipbuf, sizeof ipbuf,
+ "[%.100s]", addr);
+ break;
+# endif /* NETINET6 */
+ }
+ if (ipbuf[0] == '\0')
+ break;
+
+ if (tTd(0, 4))
+ sm_dprintf("\ta.k.a.: %s\n", ipbuf);
+ setclass('w', ipbuf);
+ }
+#endif /* NETINET || NETINET6 */
+#if NETINET6
+ freehostent(hp);
+ hp = NULL;
+#endif /* NETINET6 */
+ }
+
+ /* current time */
+ macdefine(&BlankEnvelope.e_macro, A_TEMP, 'b', arpadate((char *) NULL));
+
+ /* current load average */
+ sm_getla();
+
+ QueueLimitRecipient = (QUEUE_CHAR *) NULL;
+ QueueLimitSender = (QUEUE_CHAR *) NULL;
+ QueueLimitId = (QUEUE_CHAR *) NULL;
+ QueueLimitQuarantine = (QUEUE_CHAR *) NULL;
+
+ /*
+ ** Crack argv.
+ */
+
+ optind = 1;
+ while ((j = getopt(argc, argv, OPTIONS)) != -1)
+ {
+ switch (j)
+ {
+ case 'b': /* operations mode */
+ /* already done */
+ break;
+
+ case 'A': /* use Alternate sendmail/submit.cf */
+ cftype = optarg[0] == 'c' ? SM_GET_SUBMIT_CF
+ : SM_GET_SENDMAIL_CF;
+ break;
+
+ case 'B': /* body type */
+ CHECK_AGAINST_OPMODE(j);
+ BlankEnvelope.e_bodytype = newstr(optarg);
+ break;
+
+ case 'C': /* select configuration file (already done) */
+ if (RealUid != 0)
+ warn_C_flag = true;
+ conffile = newstr(optarg);
+ dp = drop_privileges(true);
+ setstat(dp);
+ safecf = false;
+ break;
+
+ case 'D':
+ case 'd': /* debugging */
+ /* already done */
+ break;
+
+ case 'f': /* from address */
+ case 'r': /* obsolete -f flag */
+ CHECK_AGAINST_OPMODE(j);
+ if (from != NULL)
+ {
+ usrerr("More than one \"from\" person");
+ ExitStat = EX_USAGE;
+ break;
+ }
+ if (optarg[0] == '\0')
+ from = newstr("<>");
+ else
+ from = newstr(denlstring(optarg, true, true));
+ if (strcmp(RealUserName, from) != 0)
+ warn_f_flag = j;
+ break;
+
+ case 'F': /* set full name */
+ CHECK_AGAINST_OPMODE(j);
+ FullName = newstr(optarg);
+ break;
+
+ case 'G': /* relay (gateway) submission */
+ /* already set */
+ CHECK_AGAINST_OPMODE(j);
+ break;
+
+ case 'h': /* hop count */
+ CHECK_AGAINST_OPMODE(j);
+ BlankEnvelope.e_hopcount = (short) strtol(optarg, &ep,
+ 10);
+ (void) sm_snprintf(buf, sizeof buf, "%d",
+ BlankEnvelope.e_hopcount);
+ macdefine(&BlankEnvelope.e_macro, A_TEMP, 'c', buf);
+
+ if (*ep)
+ {
+ usrerr("Bad hop count (%s)", optarg);
+ ExitStat = EX_USAGE;
+ }
+ break;
+
+ case 'L': /* program label */
+ /* already set */
+ break;
+
+ case 'n': /* don't alias */
+ CHECK_AGAINST_OPMODE(j);
+ NoAlias = true;
+ break;
+
+ case 'N': /* delivery status notifications */
+ CHECK_AGAINST_OPMODE(j);
+ DefaultNotify |= QHASNOTIFY;
+ macdefine(&BlankEnvelope.e_macro, A_TEMP,
+ macid("{dsn_notify}"), optarg);
+ if (sm_strcasecmp(optarg, "never") == 0)
+ break;
+ for (p = optarg; p != NULL; optarg = p)
+ {
+ p = strchr(p, ',');
+ if (p != NULL)
+ *p++ = '\0';
+ if (sm_strcasecmp(optarg, "success") == 0)
+ DefaultNotify |= QPINGONSUCCESS;
+ else if (sm_strcasecmp(optarg, "failure") == 0)
+ DefaultNotify |= QPINGONFAILURE;
+ else if (sm_strcasecmp(optarg, "delay") == 0)
+ DefaultNotify |= QPINGONDELAY;
+ else
+ {
+ usrerr("Invalid -N argument");
+ ExitStat = EX_USAGE;
+ }
+ }
+ break;
+
+ case 'o': /* set option */
+ setoption(*optarg, optarg + 1, false, true,
+ &BlankEnvelope);
+ break;
+
+ case 'O': /* set option (long form) */
+ setoption(' ', optarg, false, true, &BlankEnvelope);
+ break;
+
+ case 'p': /* set protocol */
+ CHECK_AGAINST_OPMODE(j);
+ p = strchr(optarg, ':');
+ if (p != NULL)
+ {
+ *p++ = '\0';
+ if (*p != '\0')
+ {
+ i = strlen(p) + 1;
+ ep = sm_malloc_x(i);
+ cleanstrcpy(ep, p, i);
+ macdefine(&BlankEnvelope.e_macro,
+ A_HEAP, 's', ep);
+ }
+ }
+ if (*optarg != '\0')
+ {
+ i = strlen(optarg) + 1;
+ ep = sm_malloc_x(i);
+ cleanstrcpy(ep, optarg, i);
+ macdefine(&BlankEnvelope.e_macro, A_HEAP,
+ 'r', ep);
+ }
+ break;
+
+ case 'Q': /* change quarantining on queued items */
+ /* sanity check */
+ if (OpMode != MD_DELIVER &&
+ OpMode != MD_QUEUERUN)
+ {
+ usrerr("Can not use -Q with -b%c", OpMode);
+ ExitStat = EX_USAGE;
+ break;
+ }
+
+ if (OpMode == MD_DELIVER)
+ set_op_mode(MD_QUEUERUN);
+
+ FullName = NULL;
+
+ quarantining = newstr(optarg);
+ break;
+
+ case 'q': /* run queue files at intervals */
+ /* sanity check */
+ if (OpMode != MD_DELIVER &&
+ OpMode != MD_DAEMON &&
+ OpMode != MD_FGDAEMON &&
+ OpMode != MD_PRINT &&
+ OpMode != MD_PRINTNQE &&
+ OpMode != MD_QUEUERUN)
+ {
+ usrerr("Can not use -q with -b%c", OpMode);
+ ExitStat = EX_USAGE;
+ break;
+ }
+
+ /* don't override -bd, -bD or -bp */
+ if (OpMode == MD_DELIVER)
+ set_op_mode(MD_QUEUERUN);
+
+ FullName = NULL;
+ negate = optarg[0] == '!';
+ if (negate)
+ {
+ /* negate meaning of pattern match */
+ optarg++; /* skip '!' for next switch */
+ }
+
+ switch (optarg[0])
+ {
+ case 'G': /* Limit by queue group name */
+ if (negate)
+ {
+ usrerr("Can not use -q!G");
+ ExitStat = EX_USAGE;
+ break;
+ }
+ if (queuegroup != NULL)
+ {
+ usrerr("Can not use multiple -qG options");
+ ExitStat = EX_USAGE;
+ break;
+ }
+ queuegroup = newstr(&optarg[1]);
+ break;
+
+ case 'I': /* Limit by ID */
+ new = (QUEUE_CHAR *) xalloc(sizeof *new);
+ new->queue_match = newstr(&optarg[1]);
+ new->queue_negate = negate;
+ new->queue_next = QueueLimitId;
+ QueueLimitId = new;
+ break;
+
+ case 'R': /* Limit by recipient */
+ new = (QUEUE_CHAR *) xalloc(sizeof *new);
+ new->queue_match = newstr(&optarg[1]);
+ new->queue_negate = negate;
+ new->queue_next = QueueLimitRecipient;
+ QueueLimitRecipient = new;
+ break;
+
+ case 'S': /* Limit by sender */
+ new = (QUEUE_CHAR *) xalloc(sizeof *new);
+ new->queue_match = newstr(&optarg[1]);
+ new->queue_negate = negate;
+ new->queue_next = QueueLimitSender;
+ QueueLimitSender = new;
+ break;
+
+ case 'f': /* foreground queue run */
+ foregroundqueue = true;
+ break;
+
+ case 'Q': /* Limit by quarantine message */
+ if (optarg[1] != '\0')
+ {
+ new = (QUEUE_CHAR *) xalloc(sizeof *new);
+ new->queue_match = newstr(&optarg[1]);
+ new->queue_negate = negate;
+ new->queue_next = QueueLimitQuarantine;
+ QueueLimitQuarantine = new;
+ }
+ QueueMode = QM_QUARANTINE;
+ break;
+
+ case 'L': /* act on lost items */
+ QueueMode = QM_LOST;
+ break;
+
+ case 'p': /* Persistent queue */
+ queuepersistent = true;
+ if (QueueIntvl == 0)
+ QueueIntvl = 1;
+ if (optarg[1] == '\0')
+ break;
+ ++optarg;
+ /* FALLTHROUGH */
+
+ default:
+ i = Errors;
+ QueueIntvl = convtime(optarg, 'm');
+ if (QueueIntvl < 0)
+ {
+ usrerr("Invalid -q value");
+ ExitStat = EX_USAGE;
+ }
+
+ /* check for bad conversion */
+ if (i < Errors)
+ ExitStat = EX_USAGE;
+ break;
+ }
+ break;
+
+ case 'R': /* DSN RET: what to return */
+ CHECK_AGAINST_OPMODE(j);
+ if (bitset(EF_RET_PARAM, BlankEnvelope.e_flags))
+ {
+ usrerr("Duplicate -R flag");
+ ExitStat = EX_USAGE;
+ break;
+ }
+ BlankEnvelope.e_flags |= EF_RET_PARAM;
+ if (sm_strcasecmp(optarg, "hdrs") == 0)
+ BlankEnvelope.e_flags |= EF_NO_BODY_RETN;
+ else if (sm_strcasecmp(optarg, "full") != 0)
+ {
+ usrerr("Invalid -R value");
+ ExitStat = EX_USAGE;
+ }
+ macdefine(&BlankEnvelope.e_macro, A_TEMP,
+ macid("{dsn_ret}"), optarg);
+ break;
+
+ case 't': /* read recipients from message */
+ CHECK_AGAINST_OPMODE(j);
+ GrabTo = true;
+ break;
+
+ case 'V': /* DSN ENVID: set "original" envelope id */
+ CHECK_AGAINST_OPMODE(j);
+ if (!xtextok(optarg))
+ {
+ usrerr("Invalid syntax in -V flag");
+ ExitStat = EX_USAGE;
+ }
+ else
+ {
+ BlankEnvelope.e_envid = newstr(optarg);
+ macdefine(&BlankEnvelope.e_macro, A_TEMP,
+ macid("{dsn_envid}"), optarg);
+ }
+ break;
+
+ case 'X': /* traffic log file */
+ dp = drop_privileges(true);
+ setstat(dp);
+ if (stat(optarg, &traf_st) == 0 &&
+ S_ISFIFO(traf_st.st_mode))
+ TrafficLogFile = sm_io_open(SmFtStdio,
+ SM_TIME_DEFAULT,
+ optarg,
+ SM_IO_WRONLY, NULL);
+ else
+ TrafficLogFile = sm_io_open(SmFtStdio,
+ SM_TIME_DEFAULT,
+ optarg,
+ SM_IO_APPEND, NULL);
+ if (TrafficLogFile == NULL)
+ {
+ syserr("cannot open %s", optarg);
+ ExitStat = EX_CANTCREAT;
+ break;
+ }
+ (void) sm_io_setvbuf(TrafficLogFile, SM_TIME_DEFAULT,
+ NULL, SM_IO_LBF, 0);
+ break;
+
+ /* compatibility flags */
+ case 'c': /* connect to non-local mailers */
+ case 'i': /* don't let dot stop me */
+ case 'm': /* send to me too */
+ case 'T': /* set timeout interval */
+ case 'v': /* give blow-by-blow description */
+ setoption(j, "T", false, true, &BlankEnvelope);
+ break;
+
+ case 'e': /* error message disposition */
+ case 'M': /* define macro */
+ setoption(j, optarg, false, true, &BlankEnvelope);
+ break;
+
+ case 's': /* save From lines in headers */
+ setoption('f', "T", false, true, &BlankEnvelope);
+ break;
+
+#ifdef DBM
+ case 'I': /* initialize alias DBM file */
+ set_op_mode(MD_INITALIAS);
+ break;
+#endif /* DBM */
+
+#if defined(__osf__) || defined(_AIX3)
+ case 'x': /* random flag that OSF/1 & AIX mailx passes */
+ break;
+#endif /* defined(__osf__) || defined(_AIX3) */
+#if defined(sony_news)
+ case 'E':
+ case 'J': /* ignore flags for Japanese code conversion
+ implemented on Sony NEWS */
+ break;
+#endif /* defined(sony_news) */
+
+ default:
+ finis(true, true, EX_USAGE);
+ /* NOTREACHED */
+ break;
+ }
+ }
+
+ /* if we've had errors so far, exit now */
+ if ((ExitStat != EX_OK && OpMode != MD_TEST) ||
+ ExitStat == EX_OSERR)
+ {
+ finis(false, true, ExitStat);
+ /* NOTREACHED */
+ }
+
+ if (bitset(SUBMIT_MTA, SubmitMode))
+ {
+ /* If set daemon_flags on command line, don't reset it */
+ if (macvalue(macid("{daemon_flags}"), &BlankEnvelope) == NULL)
+ macdefine(&BlankEnvelope.e_macro, A_PERM,
+ macid("{daemon_flags}"), "CC f");
+ }
+ else if (OpMode == MD_DELIVER || OpMode == MD_SMTP)
+ {
+ SubmitMode = SUBMIT_MSA;
+
+ /* If set daemon_flags on command line, don't reset it */
+ if (macvalue(macid("{daemon_flags}"), &BlankEnvelope) == NULL)
+ macdefine(&BlankEnvelope.e_macro, A_PERM,
+ macid("{daemon_flags}"), "c u");
+ }
+
+ /*
+ ** Do basic initialization.
+ ** Read system control file.
+ ** Extract special fields for local use.
+ */
+
+#if XDEBUG
+ checkfd012("before readcf");
+#endif /* XDEBUG */
+ vendor_pre_defaults(&BlankEnvelope);
+
+ readcf(getcfname(OpMode, SubmitMode, cftype, conffile),
+ safecf, &BlankEnvelope);
+#if !defined(_USE_SUN_NSSWITCH_) && !defined(_USE_DEC_SVC_CONF_)
+ ConfigFileRead = true;
+#endif /* !defined(_USE_SUN_NSSWITCH_) && !defined(_USE_DEC_SVC_CONF_) */
+ vendor_post_defaults(&BlankEnvelope);
+
+ /* now we can complain about missing fds */
+ if (MissingFds != 0 && LogLevel > 8)
+ {
+ char mbuf[MAXLINE];
+
+ mbuf[0] = '\0';
+ if (bitset(1 << STDIN_FILENO, MissingFds))
+ (void) sm_strlcat(mbuf, ", stdin", sizeof mbuf);
+ if (bitset(1 << STDOUT_FILENO, MissingFds))
+ (void) sm_strlcat(mbuf, ", stdout", sizeof mbuf);
+ if (bitset(1 << STDERR_FILENO, MissingFds))
+ (void) sm_strlcat(mbuf, ", stderr", sizeof mbuf);
+
+ /* Notice: fill_errno is from high above: fill_fd() */
+ sm_syslog(LOG_WARNING, NOQID,
+ "File descriptors missing on startup: %s; %s",
+ &mbuf[2], sm_errstring(fill_errno));
+ }
+
+ /* Remove the ability for a normal user to send signals */
+ if (RealUid != 0 && RealUid != geteuid())
+ {
+ uid_t new_uid = geteuid();
+
+#if HASSETREUID
+ /*
+ ** Since we can differentiate between uid and euid,
+ ** make the uid a different user so the real user
+ ** can't send signals. However, it doesn't need to be
+ ** root (euid has root).
+ */
+
+ if (new_uid == 0)
+ new_uid = DefUid;
+ if (tTd(47, 5))
+ sm_dprintf("Changing real uid to %d\n", (int) new_uid);
+ if (setreuid(new_uid, geteuid()) < 0)
+ {
+ syserr("main: setreuid(%d, %d) failed",
+ (int) new_uid, (int) geteuid());
+ finis(false, true, EX_OSERR);
+ /* NOTREACHED */
+ }
+ if (tTd(47, 10))
+ sm_dprintf("Now running as e/ruid %d:%d\n",
+ (int) geteuid(), (int) getuid());
+#else /* HASSETREUID */
+ /*
+ ** Have to change both effective and real so need to
+ ** change them both to effective to keep privs.
+ */
+
+ if (tTd(47, 5))
+ sm_dprintf("Changing uid to %d\n", (int) new_uid);
+ if (setuid(new_uid) < 0)
+ {
+ syserr("main: setuid(%d) failed", (int) new_uid);
+ finis(false, true, EX_OSERR);
+ /* NOTREACHED */
+ }
+ if (tTd(47, 10))
+ sm_dprintf("Now running as e/ruid %d:%d\n",
+ (int) geteuid(), (int) getuid());
+#endif /* HASSETREUID */
+ }
+
+#if NAMED_BIND
+ if (FallbackMX != NULL)
+ (void) getfallbackmxrr(FallbackMX);
+#endif /* NAMED_BIND */
+
+ if (SuperSafe == SAFE_INTERACTIVE && CurEnv->e_sendmode != SM_DELIVER)
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "WARNING: SuperSafe=interactive should only be used with\n DeliveryMode=interactive\n");
+ }
+
+ if (UseMSP && (OpMode == MD_DAEMON || OpMode == MD_FGDAEMON))
+ {
+ usrerr("Mail submission program cannot be used as daemon");
+ finis(false, true, EX_USAGE);
+ }
+
+ if (OpMode == MD_DELIVER || OpMode == MD_SMTP ||
+ OpMode == MD_QUEUERUN || OpMode == MD_ARPAFTP ||
+ OpMode == MD_DAEMON || OpMode == MD_FGDAEMON)
+ makeworkgroups();
+
+ /* set up the basic signal handlers */
+ if (sm_signal(SIGINT, SIG_IGN) != SIG_IGN)
+ (void) sm_signal(SIGINT, intsig);
+ (void) sm_signal(SIGTERM, intsig);
+
+ /* Enforce use of local time (null string overrides this) */
+ if (TimeZoneSpec == NULL)
+ unsetenv("TZ");
+ else if (TimeZoneSpec[0] != '\0')
+ setuserenv("TZ", TimeZoneSpec);
+ else
+ setuserenv("TZ", NULL);
+ tzset();
+
+ /* initialize mailbox database */
+ i = sm_mbdb_initialize(Mbdb);
+ if (i != EX_OK)
+ {
+ usrerr("Can't initialize mailbox database \"%s\": %s",
+ Mbdb, sm_strexit(i));
+ ExitStat = i;
+ }
+
+ /* avoid denial-of-service attacks */
+ resetlimits();
+
+ if (OpMode == MD_TEST)
+ {
+ /* can't be done after readcf if RunAs* is used */
+ dp = drop_privileges(true);
+ if (dp != EX_OK)
+ {
+ finis(false, true, dp);
+ /* NOTREACHED */
+ }
+ }
+ else if (OpMode != MD_DAEMON && OpMode != MD_FGDAEMON)
+ {
+ /* drop privileges -- daemon mode done after socket/bind */
+ dp = drop_privileges(false);
+ setstat(dp);
+ if (dp == EX_OK && UseMSP && (geteuid() == 0 || getuid() == 0))
+ {
+ usrerr("Mail submission program must have RunAsUser set to non root user");
+ finis(false, true, EX_CONFIG);
+ /* NOTREACHED */
+ }
+ }
+
+#if NAMED_BIND
+ _res.retry = TimeOuts.res_retry[RES_TO_DEFAULT];
+ _res.retrans = TimeOuts.res_retrans[RES_TO_DEFAULT];
+#endif /* NAMED_BIND */
+
+ /*
+ ** Find our real host name for future logging.
+ */
+
+ authinfo = getauthinfo(STDIN_FILENO, &forged);
+ macdefine(&BlankEnvelope.e_macro, A_TEMP, '_', authinfo);
+
+ /* suppress error printing if errors mailed back or whatever */
+ if (BlankEnvelope.e_errormode != EM_PRINT)
+ HoldErrs = true;
+
+ /* set up the $=m class now, after .cf has a chance to redefine $m */
+ expand("\201m", jbuf, sizeof jbuf, &BlankEnvelope);
+ if (jbuf[0] != '\0')
+ setclass('m', jbuf);
+
+ /* probe interfaces and locate any additional names */
+ if (DontProbeInterfaces != DPI_PROBENONE)
+ load_if_names();
+
+ if (tTd(0, 10))
+ {
+ char pidpath[MAXPATHLEN];
+
+ /* Now we know which .cf file we use */
+ sm_dprintf(" Conf file:\t%s (selected)\n",
+ getcfname(OpMode, SubmitMode, cftype, conffile));
+ expand(PidFile, pidpath, sizeof pidpath, &BlankEnvelope);
+ sm_dprintf(" Pid file:\t%s (selected)\n", pidpath);
+ }
+
+ if (tTd(0, 1))
+ {
+ sm_dprintf("\n============ SYSTEM IDENTITY (after readcf) ============");
+ sm_dprintf("\n (short domain name) $w = ");
+ xputs(sm_debug_file(), macvalue('w', &BlankEnvelope));
+ sm_dprintf("\n (canonical domain name) $j = ");
+ xputs(sm_debug_file(), macvalue('j', &BlankEnvelope));
+ sm_dprintf("\n (subdomain name) $m = ");
+ xputs(sm_debug_file(), macvalue('m', &BlankEnvelope));
+ sm_dprintf("\n (node name) $k = ");
+ xputs(sm_debug_file(), macvalue('k', &BlankEnvelope));
+ sm_dprintf("\n========================================================\n\n");
+ }
+
+ /*
+ ** Do more command line checking -- these are things that
+ ** have to modify the results of reading the config file.
+ */
+
+ /* process authorization warnings from command line */
+ if (warn_C_flag)
+ auth_warning(&BlankEnvelope, "Processed by %s with -C %s",
+ RealUserName, conffile);
+ if (Warn_Q_option && !wordinclass(RealUserName, 't'))
+ auth_warning(&BlankEnvelope, "Processed from queue %s",
+ QueueDir);
+ if (sysloglabel != NULL && !wordinclass(RealUserName, 't') &&
+ RealUid != 0 && RealUid != TrustedUid && LogLevel > 1)
+ sm_syslog(LOG_WARNING, NOQID, "user %d changed syslog label",
+ (int) RealUid);
+
+ /* check body type for legality */
+ i = check_bodytype(BlankEnvelope.e_bodytype);
+ if (i == BODYTYPE_ILLEGAL)
+ {
+ usrerr("Illegal body type %s", BlankEnvelope.e_bodytype);
+ BlankEnvelope.e_bodytype = NULL;
+ }
+ else if (i != BODYTYPE_NONE)
+ SevenBitInput = (i == BODYTYPE_7BIT);
+
+ /* tweak default DSN notifications */
+ if (DefaultNotify == 0)
+ DefaultNotify = QPINGONFAILURE|QPINGONDELAY;
+
+ /* check for sane configuration level */
+ if (ConfigLevel > MAXCONFIGLEVEL)
+ {
+ syserr("Warning: .cf version level (%d) exceeds sendmail version %s functionality (%d)",
+ ConfigLevel, Version, MAXCONFIGLEVEL);
+ }
+
+ /* need MCI cache to have persistence */
+ if (HostStatDir != NULL && MaxMciCache == 0)
+ {
+ HostStatDir = NULL;
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Warning: HostStatusDirectory disabled with ConnectionCacheSize = 0\n");
+ }
+
+ /* need HostStatusDir in order to have SingleThreadDelivery */
+ if (SingleThreadDelivery && HostStatDir == NULL)
+ {
+ SingleThreadDelivery = false;
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Warning: HostStatusDirectory required for SingleThreadDelivery\n");
+ }
+
+ /* check for permissions */
+ if (RealUid != 0 &&
+ RealUid != TrustedUid)
+ {
+ char *action = NULL;
+
+ switch (OpMode)
+ {
+ case MD_QUEUERUN:
+ if (quarantining != NULL)
+ action = "quarantine jobs";
+ else
+ {
+ /* Normal users can do a single queue run */
+ if (QueueIntvl == 0)
+ break;
+ }
+
+ /* but not persistent queue runners */
+ if (action == NULL)
+ action = "start a queue runner daemon";
+ /* FALLTHROUGH */
+
+ case MD_PURGESTAT:
+ if (action == NULL)
+ action = "purge host status";
+ /* FALLTHROUGH */
+
+ case MD_DAEMON:
+ case MD_FGDAEMON:
+ if (action == NULL)
+ action = "run daemon";
+
+ if (tTd(65, 1))
+ sm_dprintf("Deny user %d attempt to %s\n",
+ (int) RealUid, action);
+
+ if (LogLevel > 1)
+ sm_syslog(LOG_ALERT, NOQID,
+ "user %d attempted to %s",
+ (int) RealUid, action);
+ HoldErrs = false;
+ usrerr("Permission denied (real uid not trusted)");
+ finis(false, true, EX_USAGE);
+ /* NOTREACHED */
+ break;
+
+ case MD_VERIFY:
+ if (bitset(PRIV_RESTRICTEXPAND, PrivacyFlags))
+ {
+ /*
+ ** If -bv and RestrictExpand,
+ ** drop privs to prevent normal
+ ** users from reading private
+ ** aliases/forwards/:include:s
+ */
+
+ if (tTd(65, 1))
+ sm_dprintf("Drop privs for user %d attempt to expand (RestrictExpand)\n",
+ (int) RealUid);
+
+ dp = drop_privileges(true);
+
+ /* Fake address safety */
+ if (tTd(65, 1))
+ sm_dprintf("Faking DontBlameSendmail=NonRootSafeAddr\n");
+ setbitn(DBS_NONROOTSAFEADDR, DontBlameSendmail);
+
+ if (dp != EX_OK)
+ {
+ if (tTd(65, 1))
+ sm_dprintf("Failed to drop privs for user %d attempt to expand, exiting\n",
+ (int) RealUid);
+ CurEnv->e_id = NULL;
+ finis(true, true, dp);
+ /* NOTREACHED */
+ }
+ }
+ break;
+
+ case MD_TEST:
+ case MD_PRINT:
+ case MD_PRINTNQE:
+ case MD_FREEZE:
+ case MD_HOSTSTAT:
+ /* Nothing special to check */
+ break;
+
+ case MD_INITALIAS:
+ if (!wordinclass(RealUserName, 't'))
+ {
+ if (tTd(65, 1))
+ sm_dprintf("Deny user %d attempt to rebuild the alias map\n",
+ (int) RealUid);
+ if (LogLevel > 1)
+ sm_syslog(LOG_ALERT, NOQID,
+ "user %d attempted to rebuild the alias map",
+ (int) RealUid);
+ HoldErrs = false;
+ usrerr("Permission denied (real uid not trusted)");
+ finis(false, true, EX_USAGE);
+ /* NOTREACHED */
+ }
+ if (UseMSP)
+ {
+ HoldErrs = false;
+ usrerr("User %d cannot rebuild aliases in mail submission program",
+ (int) RealUid);
+ finis(false, true, EX_USAGE);
+ /* NOTREACHED */
+ }
+ /* FALLTHROUGH */
+
+ default:
+ if (bitset(PRIV_RESTRICTEXPAND, PrivacyFlags) &&
+ Verbose != 0)
+ {
+ /*
+ ** If -v and RestrictExpand, reset
+ ** Verbose to prevent normal users
+ ** from seeing the expansion of
+ ** aliases/forwards/:include:s
+ */
+
+ if (tTd(65, 1))
+ sm_dprintf("Dropping verbosity for user %d (RestrictExpand)\n",
+ (int) RealUid);
+ Verbose = 0;
+ }
+ break;
+ }
+ }
+
+ if (MeToo)
+ BlankEnvelope.e_flags |= EF_METOO;
+
+ switch (OpMode)
+ {
+ case MD_TEST:
+ /* don't have persistent host status in test mode */
+ HostStatDir = NULL;
+ if (Verbose == 0)
+ Verbose = 2;
+ BlankEnvelope.e_errormode = EM_PRINT;
+ HoldErrs = false;
+ break;
+
+ case MD_VERIFY:
+ BlankEnvelope.e_errormode = EM_PRINT;
+ HoldErrs = false;
+ /* arrange to exit cleanly on hangup signal */
+ if (sm_signal(SIGHUP, SIG_IGN) == (sigfunc_t) SIG_DFL)
+ (void) sm_signal(SIGHUP, intsig);
+ if (geteuid() != 0)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Notice: -bv may give misleading output for non-privileged user\n");
+ break;
+
+ case MD_FGDAEMON:
+ run_in_foreground = true;
+ set_op_mode(MD_DAEMON);
+ /* FALLTHROUGH */
+
+ case MD_DAEMON:
+ vendor_daemon_setup(&BlankEnvelope);
+
+ /* remove things that don't make sense in daemon mode */
+ FullName = NULL;
+ GrabTo = false;
+
+ /* arrange to restart on hangup signal */
+ if (SaveArgv[0] == NULL || SaveArgv[0][0] != '/')
+ sm_syslog(LOG_WARNING, NOQID,
+ "daemon invoked without full pathname; kill -1 won't work");
+ break;
+
+ case MD_INITALIAS:
+ Verbose = 2;
+ BlankEnvelope.e_errormode = EM_PRINT;
+ HoldErrs = false;
+ /* FALLTHROUGH */
+
+ default:
+ /* arrange to exit cleanly on hangup signal */
+ if (sm_signal(SIGHUP, SIG_IGN) == (sigfunc_t) SIG_DFL)
+ (void) sm_signal(SIGHUP, intsig);
+ break;
+ }
+
+ /* special considerations for FullName */
+ if (FullName != NULL)
+ {
+ char *full = NULL;
+
+ /* full names can't have newlines */
+ if (strchr(FullName, '\n') != NULL)
+ {
+ full = newstr(denlstring(FullName, true, true));
+ FullName = full;
+ }
+
+ /* check for characters that may have to be quoted */
+ if (!rfc822_string(FullName))
+ {
+ /*
+ ** Quote a full name with special characters
+ ** as a comment so crackaddr() doesn't destroy
+ ** the name portion of the address.
+ */
+
+ FullName = addquotes(FullName, NULL);
+ if (full != NULL)
+ sm_free(full); /* XXX */
+ }
+ }
+
+ /* do heuristic mode adjustment */
+ if (Verbose)
+ {
+ /* turn off noconnect option */
+ setoption('c', "F", true, false, &BlankEnvelope);
+
+ /* turn on interactive delivery */
+ setoption('d', "", true, false, &BlankEnvelope);
+ }
+
+#ifdef VENDOR_CODE
+ /* check for vendor mismatch */
+ if (VendorCode != VENDOR_CODE)
+ {
+ message("Warning: .cf file vendor code mismatch: sendmail expects vendor %s, .cf file vendor is %s",
+ getvendor(VENDOR_CODE), getvendor(VendorCode));
+ }
+#endif /* VENDOR_CODE */
+
+ /* check for out of date configuration level */
+ if (ConfigLevel < MAXCONFIGLEVEL)
+ {
+ message("Warning: .cf file is out of date: sendmail %s supports version %d, .cf file is version %d",
+ Version, MAXCONFIGLEVEL, ConfigLevel);
+ }
+
+ if (ConfigLevel < 3)
+ UseErrorsTo = true;
+
+ /* set options that were previous macros */
+ if (SmtpGreeting == NULL)
+ {
+ if (ConfigLevel < 7 &&
+ (p = macvalue('e', &BlankEnvelope)) != NULL)
+ SmtpGreeting = newstr(p);
+ else
+ SmtpGreeting = "\201j Sendmail \201v ready at \201b";
+ }
+ if (UnixFromLine == NULL)
+ {
+ if (ConfigLevel < 7 &&
+ (p = macvalue('l', &BlankEnvelope)) != NULL)
+ UnixFromLine = newstr(p);
+ else
+ UnixFromLine = "From \201g \201d";
+ }
+ SmtpError[0] = '\0';
+
+ /* our name for SMTP codes */
+ expand("\201j", jbuf, sizeof jbuf, &BlankEnvelope);
+ if (jbuf[0] == '\0')
+ PSTRSET(MyHostName, "localhost");
+ else
+ PSTRSET(MyHostName, jbuf);
+ if (strchr(MyHostName, '.') == NULL)
+ message("WARNING: local host name (%s) is not qualified; see cf/README: WHO AM I?",
+ MyHostName);
+
+ /* make certain that this name is part of the $=w class */
+ setclass('w', MyHostName);
+
+ /* fill in the structure of the *default* queue */
+ st = stab("mqueue", ST_QUEUE, ST_FIND);
+ if (st == NULL)
+ syserr("No default queue (mqueue) defined");
+ else
+ set_def_queueval(st->s_quegrp, true);
+
+ /* the indices of built-in mailers */
+ st = stab("local", ST_MAILER, ST_FIND);
+ if (st != NULL)
+ LocalMailer = st->s_mailer;
+ else if (OpMode != MD_TEST || !warn_C_flag)
+ syserr("No local mailer defined");
+
+ st = stab("prog", ST_MAILER, ST_FIND);
+ if (st == NULL)
+ syserr("No prog mailer defined");
+ else
+ {
+ ProgMailer = st->s_mailer;
+ clrbitn(M_MUSER, ProgMailer->m_flags);
+ }
+
+ st = stab("*file*", ST_MAILER, ST_FIND);
+ if (st == NULL)
+ syserr("No *file* mailer defined");
+ else
+ {
+ FileMailer = st->s_mailer;
+ clrbitn(M_MUSER, FileMailer->m_flags);
+ }
+
+ st = stab("*include*", ST_MAILER, ST_FIND);
+ if (st == NULL)
+ syserr("No *include* mailer defined");
+ else
+ InclMailer = st->s_mailer;
+
+ if (ConfigLevel < 6)
+ {
+ /* heuristic tweaking of local mailer for back compat */
+ if (LocalMailer != NULL)
+ {
+ setbitn(M_ALIASABLE, LocalMailer->m_flags);
+ setbitn(M_HASPWENT, LocalMailer->m_flags);
+ setbitn(M_TRYRULESET5, LocalMailer->m_flags);
+ setbitn(M_CHECKINCLUDE, LocalMailer->m_flags);
+ setbitn(M_CHECKPROG, LocalMailer->m_flags);
+ setbitn(M_CHECKFILE, LocalMailer->m_flags);
+ setbitn(M_CHECKUDB, LocalMailer->m_flags);
+ }
+ if (ProgMailer != NULL)
+ setbitn(M_RUNASRCPT, ProgMailer->m_flags);
+ if (FileMailer != NULL)
+ setbitn(M_RUNASRCPT, FileMailer->m_flags);
+ }
+ if (ConfigLevel < 7)
+ {
+ if (LocalMailer != NULL)
+ setbitn(M_VRFY250, LocalMailer->m_flags);
+ if (ProgMailer != NULL)
+ setbitn(M_VRFY250, ProgMailer->m_flags);
+ if (FileMailer != NULL)
+ setbitn(M_VRFY250, FileMailer->m_flags);
+ }
+
+ /* MIME Content-Types that cannot be transfer encoded */
+ setclass('n', "multipart/signed");
+
+ /* MIME message/xxx subtypes that can be treated as messages */
+ setclass('s', "rfc822");
+
+ /* MIME Content-Transfer-Encodings that can be encoded */
+ setclass('e', "7bit");
+ setclass('e', "8bit");
+ setclass('e', "binary");
+
+#ifdef USE_B_CLASS
+ /* MIME Content-Types that should be treated as binary */
+ setclass('b', "image");
+ setclass('b', "audio");
+ setclass('b', "video");
+ setclass('b', "application/octet-stream");
+#endif /* USE_B_CLASS */
+
+ /* MIME headers which have fields to check for overflow */
+ setclass(macid("{checkMIMEFieldHeaders}"), "content-disposition");
+ setclass(macid("{checkMIMEFieldHeaders}"), "content-type");
+
+ /* MIME headers to check for length overflow */
+ setclass(macid("{checkMIMETextHeaders}"), "content-description");
+
+ /* MIME headers to check for overflow and rebalance */
+ setclass(macid("{checkMIMEHeaders}"), "content-disposition");
+ setclass(macid("{checkMIMEHeaders}"), "content-id");
+ setclass(macid("{checkMIMEHeaders}"), "content-transfer-encoding");
+ setclass(macid("{checkMIMEHeaders}"), "content-type");
+ setclass(macid("{checkMIMEHeaders}"), "mime-version");
+
+ /* Macros to save in the queue file -- don't remove any */
+ setclass(macid("{persistentMacros}"), "r");
+ setclass(macid("{persistentMacros}"), "s");
+ setclass(macid("{persistentMacros}"), "_");
+ setclass(macid("{persistentMacros}"), "{if_addr}");
+ setclass(macid("{persistentMacros}"), "{daemon_flags}");
+
+ /* operate in queue directory */
+ if (QueueDir == NULL || *QueueDir == '\0')
+ {
+ if (OpMode != MD_TEST)
+ {
+ syserr("QueueDirectory (Q) option must be set");
+ ExitStat = EX_CONFIG;
+ }
+ }
+ else
+ {
+ if (OpMode != MD_TEST)
+ setup_queues(OpMode == MD_DAEMON);
+ }
+
+ /* check host status directory for validity */
+ if (HostStatDir != NULL && !path_is_dir(HostStatDir, false))
+ {
+ /* cannot use this value */
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Warning: Cannot use HostStatusDirectory = %s: %s\n",
+ HostStatDir, sm_errstring(errno));
+ HostStatDir = NULL;
+ }
+
+ if (OpMode == MD_QUEUERUN &&
+ RealUid != 0 && bitset(PRIV_RESTRICTQRUN, PrivacyFlags))
+ {
+ struct stat stbuf;
+
+ /* check to see if we own the queue directory */
+ if (stat(".", &stbuf) < 0)
+ syserr("main: cannot stat %s", QueueDir);
+ if (stbuf.st_uid != RealUid)
+ {
+ /* nope, really a botch */
+ HoldErrs = false;
+ usrerr("You do not have permission to process the queue");
+ finis(false, true, EX_NOPERM);
+ /* NOTREACHED */
+ }
+ }
+
+#if MILTER
+ /* sanity checks on milter filters */
+ if (OpMode == MD_DAEMON || OpMode == MD_SMTP)
+ {
+ milter_config(InputFilterList, InputFilters, MAXFILTERS);
+ setup_daemon_milters();
+ }
+#endif /* MILTER */
+
+ /* Convert queuegroup string to qgrp number */
+ if (queuegroup != NULL)
+ {
+ qgrp = name2qid(queuegroup);
+ if (qgrp == NOQGRP)
+ {
+ HoldErrs = false;
+ usrerr("Queue group %s unknown", queuegroup);
+ finis(false, true, ExitStat);
+ /* NOTREACHED */
+ }
+ }
+
+ /* if we've had errors so far, exit now */
+ if (ExitStat != EX_OK && OpMode != MD_TEST)
+ {
+ finis(false, true, ExitStat);
+ /* NOTREACHED */
+ }
+
+#if SASL
+ /* sendmail specific SASL initialization */
+ sm_sasl_init();
+#endif /* SASL */
+
+#if XDEBUG
+ checkfd012("before main() initmaps");
+#endif /* XDEBUG */
+
+ /*
+ ** Do operation-mode-dependent initialization.
+ */
+
+ switch (OpMode)
+ {
+ case MD_PRINT:
+ /* print the queue */
+ HoldErrs = false;
+ dropenvelope(&BlankEnvelope, true, false);
+ (void) sm_signal(SIGPIPE, sigpipe);
+ if (qgrp != NOQGRP)
+ {
+ int j;
+
+ /* Selecting a particular queue group to run */
+ for (j = 0; j < Queue[qgrp]->qg_numqueues; j++)
+ {
+ if (StopRequest)
+ stop_sendmail();
+ (void) print_single_queue(qgrp, j);
+ }
+ finis(false, true, EX_OK);
+ /* NOTREACHED */
+ }
+ printqueue();
+ finis(false, true, EX_OK);
+ /* NOTREACHED */
+ break;
+
+ case MD_PRINTNQE:
+ /* print number of entries in queue */
+ dropenvelope(&BlankEnvelope, true, false);
+ (void) sm_signal(SIGPIPE, sigpipe);
+ printnqe(smioout, NULL);
+ finis(false, true, EX_OK);
+ /* NOTREACHED */
+ break;
+
+ case MD_QUEUERUN:
+ /* only handle quarantining here */
+ if (quarantining == NULL)
+ break;
+
+ if (QueueMode != QM_QUARANTINE &&
+ QueueMode != QM_NORMAL)
+ {
+ HoldErrs = false;
+ usrerr("Can not use -Q with -q%c", QueueMode);
+ ExitStat = EX_USAGE;
+ finis(false, true, ExitStat);
+ /* NOTREACHED */
+ }
+ quarantine_queue(quarantining, qgrp);
+ finis(false, true, EX_OK);
+ break;
+
+ case MD_HOSTSTAT:
+ (void) sm_signal(SIGPIPE, sigpipe);
+ (void) mci_traverse_persistent(mci_print_persistent, NULL);
+ finis(false, true, EX_OK);
+ /* NOTREACHED */
+ break;
+
+ case MD_PURGESTAT:
+ (void) mci_traverse_persistent(mci_purge_persistent, NULL);
+ finis(false, true, EX_OK);
+ /* NOTREACHED */
+ break;
+
+ case MD_INITALIAS:
+ /* initialize maps */
+ initmaps();
+ finis(false, true, ExitStat);
+ /* NOTREACHED */
+ break;
+
+ case MD_SMTP:
+ case MD_DAEMON:
+ /* reset DSN parameters */
+ DefaultNotify = QPINGONFAILURE|QPINGONDELAY;
+ macdefine(&BlankEnvelope.e_macro, A_PERM,
+ macid("{dsn_notify}"), NULL);
+ BlankEnvelope.e_envid = NULL;
+ macdefine(&BlankEnvelope.e_macro, A_PERM,
+ macid("{dsn_envid}"), NULL);
+ BlankEnvelope.e_flags &= ~(EF_RET_PARAM|EF_NO_BODY_RETN);
+ macdefine(&BlankEnvelope.e_macro, A_PERM,
+ macid("{dsn_ret}"), NULL);
+
+ /* don't open maps for daemon -- done below in child */
+ break;
+ }
+
+ if (tTd(0, 15))
+ {
+ /* print configuration table (or at least part of it) */
+ if (tTd(0, 90))
+ printrules();
+ for (i = 0; i < MAXMAILERS; i++)
+ {
+ if (Mailer[i] != NULL)
+ printmailer(sm_debug_file(), Mailer[i]);
+ }
+ }
+
+ /*
+ ** Switch to the main envelope.
+ */
+
+ CurEnv = newenvelope(&MainEnvelope, &BlankEnvelope,
+ sm_rpool_new_x(NULL));
+ MainEnvelope.e_flags = BlankEnvelope.e_flags;
+
+ /*
+ ** If test mode, read addresses from stdin and process.
+ */
+
+ if (OpMode == MD_TEST)
+ {
+ if (isatty(sm_io_getinfo(smioin, SM_IO_WHAT_FD, NULL)))
+ Verbose = 2;
+
+ if (Verbose)
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "ADDRESS TEST MODE (ruleset 3 NOT automatically invoked)\n");
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Enter <ruleset> <address>\n");
+ }
+ macdefine(&(MainEnvelope.e_macro), A_PERM,
+ macid("{addr_type}"), "e r");
+ for (;;)
+ {
+ SM_TRY
+ {
+ (void) sm_signal(SIGINT, intindebug);
+ (void) sm_releasesignal(SIGINT);
+ if (Verbose == 2)
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT,
+ "> ");
+ (void) sm_io_flush(smioout, SM_TIME_DEFAULT);
+ if (sm_io_fgets(smioin, SM_TIME_DEFAULT, buf,
+ sizeof buf) == NULL)
+ testmodeline("/quit", &MainEnvelope);
+ p = strchr(buf, '\n');
+ if (p != NULL)
+ *p = '\0';
+ if (Verbose < 2)
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT,
+ "> %s\n", buf);
+ testmodeline(buf, &MainEnvelope);
+ }
+ SM_EXCEPT(exc, "[!F]*")
+ {
+ /*
+ ** 8.10 just prints \n on interrupt.
+ ** I'm printing the exception here in case
+ ** sendmail is extended to raise additional
+ ** exceptions in this context.
+ */
+
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "\n");
+ sm_exc_print(exc, smioout);
+ }
+ SM_END_TRY
+ }
+ }
+
+#if STARTTLS
+ tls_ok = true;
+ if (OpMode == MD_QUEUERUN || OpMode == MD_DELIVER)
+ {
+ /* check whether STARTTLS is turned off for the client */
+ if (chkclientmodifiers(D_NOTLS))
+ tls_ok = false;
+ }
+ else if (OpMode == MD_DAEMON || OpMode == MD_FGDAEMON ||
+ OpMode == MD_SMTP)
+ {
+ /* check whether STARTTLS is turned off for the server */
+ if (chkdaemonmodifiers(D_NOTLS))
+ tls_ok = false;
+ }
+ else /* other modes don't need STARTTLS */
+ tls_ok = false;
+
+ if (tls_ok)
+ {
+ /* basic TLS initialization */
+ tls_ok = init_tls_library();
+ }
+
+ if (!tls_ok && (OpMode == MD_QUEUERUN || OpMode == MD_DELIVER))
+ {
+ /* disable TLS for client */
+ setclttls(false);
+ }
+#endif /* STARTTLS */
+
+ /*
+ ** If collecting stuff from the queue, go start doing that.
+ */
+
+ if (OpMode == MD_QUEUERUN && QueueIntvl == 0)
+ {
+ pid_t pid = -1;
+
+#if STARTTLS
+ /* init TLS for client, ignore result for now */
+ (void) initclttls(tls_ok);
+#endif /* STARTTLS */
+
+ /*
+ ** The parent process of the caller of runqueue() needs
+ ** to stay around for a possible SIGTERM. The SIGTERM will
+ ** tell this process that all of the queue runners children
+ ** need to be sent SIGTERM as well. At the same time, we
+ ** want to return control to the command line. So we do an
+ ** extra fork().
+ */
+
+ if (Verbose || foregroundqueue || (pid = fork()) <= 0)
+ {
+ /*
+ ** If the fork() failed we should still try to do
+ ** the queue run. If it succeeded then the child
+ ** is going to start the run and wait for all
+ ** of the children to finish.
+ */
+
+ if (pid == 0)
+ {
+ /* Reset global flags */
+ RestartRequest = NULL;
+ ShutdownRequest = NULL;
+ PendingSignal = 0;
+
+ /* disconnect from terminal */
+ disconnect(2, CurEnv);
+ }
+
+ CurrentPid = getpid();
+ if (qgrp != NOQGRP)
+ {
+ int rwgflags = RWG_NONE;
+
+ /*
+ ** To run a specific queue group mark it to
+ ** be run, select the work group it's in and
+ ** increment the work counter.
+ */
+
+ for (i = 0; i < NumQueue && Queue[i] != NULL;
+ i++)
+ Queue[i]->qg_nextrun = (time_t) -1;
+ Queue[qgrp]->qg_nextrun = 0;
+ if (Verbose)
+ rwgflags |= RWG_VERBOSE;
+ if (queuepersistent)
+ rwgflags |= RWG_PERSISTENT;
+ rwgflags |= RWG_FORCE;
+ (void) run_work_group(Queue[qgrp]->qg_wgrp,
+ rwgflags);
+ }
+ else
+ (void) runqueue(false, Verbose,
+ queuepersistent, true);
+
+ /* set the title to make it easier to find */
+ sm_setproctitle(true, CurEnv, "Queue control");
+ (void) sm_signal(SIGCHLD, SIG_DFL);
+ while (CurChildren > 0)
+ {
+ int status;
+ pid_t ret;
+
+ errno = 0;
+ while ((ret = sm_wait(&status)) <= 0)
+ {
+ if (errno == ECHILD)
+ {
+ /*
+ ** Oops... something got messed
+ ** up really bad. Waiting for
+ ** non-existent children
+ ** shouldn't happen. Let's get
+ ** out of here.
+ */
+
+ CurChildren = 0;
+ break;
+ }
+ continue;
+ }
+
+ /* something is really really wrong */
+ if (errno == ECHILD)
+ {
+ sm_syslog(LOG_ERR, NOQID,
+ "queue control process: lost all children: wait returned ECHILD");
+ break;
+ }
+
+ /* Only drop when a child gives status */
+ if (WIFSTOPPED(status))
+ continue;
+
+ proc_list_drop(ret, status, NULL);
+ }
+ }
+ finis(true, true, ExitStat);
+ /* NOTREACHED */
+ }
+
+# if SASL
+ if (OpMode == MD_SMTP || OpMode == MD_DAEMON)
+ {
+ /* check whether AUTH is turned off for the server */
+ if (!chkdaemonmodifiers(D_NOAUTH) &&
+ (i = sasl_server_init(srvcallbacks, "Sendmail")) != SASL_OK)
+ syserr("!sasl_server_init failed! [%s]",
+ sasl_errstring(i, NULL, NULL));
+ }
+# endif /* SASL */
+
+ if (OpMode == MD_SMTP)
+ {
+ proc_list_add(CurrentPid, "Sendmail SMTP Agent",
+ PROC_DAEMON, 0, -1, NULL);
+
+ /* clean up background delivery children */
+ (void) sm_signal(SIGCHLD, reapchild);
+ }
+
+ /*
+ ** If a daemon, wait for a request.
+ ** getrequests will always return in a child.
+ ** If we should also be processing the queue, start
+ ** doing it in background.
+ ** We check for any errors that might have happened
+ ** during startup.
+ */
+
+ if (OpMode == MD_DAEMON || QueueIntvl > 0)
+ {
+ char dtype[200];
+
+ if (!run_in_foreground && !tTd(99, 100))
+ {
+ /* put us in background */
+ i = fork();
+ if (i < 0)
+ syserr("daemon: cannot fork");
+ if (i != 0)
+ {
+ finis(false, true, EX_OK);
+ /* NOTREACHED */
+ }
+
+ /*
+ ** Initialize exception stack and default exception
+ ** handler for child process.
+ */
+
+ /* Reset global flags */
+ RestartRequest = NULL;
+ RestartWorkGroup = false;
+ ShutdownRequest = NULL;
+ PendingSignal = 0;
+ CurrentPid = getpid();
+
+ sm_exc_newthread(fatal_error);
+
+ /* disconnect from our controlling tty */
+ disconnect(2, &MainEnvelope);
+ }
+
+ dtype[0] = '\0';
+ if (OpMode == MD_DAEMON)
+ {
+ (void) sm_strlcat(dtype, "+SMTP", sizeof dtype);
+ DaemonPid = CurrentPid;
+ }
+ if (QueueIntvl > 0)
+ {
+ (void) sm_strlcat2(dtype,
+ queuepersistent
+ ? "+persistent-queueing@"
+ : "+queueing@",
+ pintvl(QueueIntvl, true),
+ sizeof dtype);
+ }
+ if (tTd(0, 1))
+ (void) sm_strlcat(dtype, "+debugging", sizeof dtype);
+
+ sm_syslog(LOG_INFO, NOQID,
+ "starting daemon (%s): %s", Version, dtype + 1);
+#if XLA
+ xla_create_file();
+#endif /* XLA */
+
+ /* save daemon type in a macro for possible PidFile use */
+ macdefine(&BlankEnvelope.e_macro, A_TEMP,
+ macid("{daemon_info}"), dtype + 1);
+
+ /* save queue interval in a macro for possible PidFile use */
+ macdefine(&MainEnvelope.e_macro, A_TEMP,
+ macid("{queue_interval}"), pintvl(QueueIntvl, true));
+
+ /* workaround: can't seem to release the signal in the parent */
+ (void) sm_signal(SIGHUP, sighup);
+ (void) sm_releasesignal(SIGHUP);
+ (void) sm_signal(SIGTERM, sigterm);
+
+ if (QueueIntvl > 0)
+ {
+ (void) runqueue(true, false, queuepersistent, true);
+
+ /*
+ ** If queuepersistent but not in daemon mode then
+ ** we're going to do the queue runner monitoring here.
+ ** If in daemon mode then the monitoring will happen
+ ** elsewhere.
+ */
+
+ if (OpMode != MD_DAEMON && queuepersistent)
+ {
+ /*
+ ** Write the pid to file
+ ** XXX Overwrites sendmail.pid
+ */
+
+ log_sendmail_pid(&MainEnvelope);
+
+ /* set the title to make it easier to find */
+ sm_setproctitle(true, CurEnv, "Queue control");
+ (void) sm_signal(SIGCHLD, SIG_DFL);
+ while (CurChildren > 0)
+ {
+ int status;
+ pid_t ret;
+ int group;
+
+ CHECK_RESTART;
+ errno = 0;
+ while ((ret = sm_wait(&status)) <= 0)
+ {
+ /*
+ ** Waiting for non-existent
+ ** children shouldn't happen.
+ ** Let's get out of here if
+ ** it occurs.
+ */
+
+ if (errno == ECHILD)
+ {
+ CurChildren = 0;
+ break;
+ }
+ continue;
+ }
+
+ /* something is really really wrong */
+ if (errno == ECHILD)
+ {
+ sm_syslog(LOG_ERR, NOQID,
+ "persistent queue runner control process: lost all children: wait returned ECHILD");
+ break;
+ }
+
+ if (WIFSTOPPED(status))
+ continue;
+
+ /* Probe only on a child status */
+ proc_list_drop(ret, status, &group);
+
+ if (WIFSIGNALED(status))
+ {
+ if (WCOREDUMP(status))
+ {
+ sm_syslog(LOG_ERR, NOQID,
+ "persistent queue runner=%d core dumped, signal=%d",
+ group, WTERMSIG(status));
+
+ /* don't restart this */
+ mark_work_group_restart(
+ group, -1);
+ continue;
+ }
+
+ sm_syslog(LOG_ERR, NOQID,
+ "persistent queue runner=%d died, signal=%d",
+ group, WTERMSIG(status));
+ }
+
+ /*
+ ** When debugging active, don't
+ ** restart the persistent queues.
+ ** But do log this as info.
+ */
+
+ if (sm_debug_active(&DebugNoPRestart,
+ 1))
+ {
+ sm_syslog(LOG_DEBUG, NOQID,
+ "persistent queue runner=%d, exited",
+ group);
+ mark_work_group_restart(group,
+ -1);
+ }
+ }
+ finis(true, true, ExitStat);
+ /* NOTREACHED */
+ }
+
+ if (OpMode != MD_DAEMON)
+ {
+ char qtype[200];
+
+ /*
+ ** Write the pid to file
+ ** XXX Overwrites sendmail.pid
+ */
+
+ log_sendmail_pid(&MainEnvelope);
+
+ /* set the title to make it easier to find */
+ qtype[0] = '\0';
+ (void) sm_strlcpyn(qtype, sizeof qtype, 4,
+ "Queue runner@",
+ pintvl(QueueIntvl, true),
+ " for ",
+ QueueDir);
+ sm_setproctitle(true, CurEnv, qtype);
+ for (;;)
+ {
+ (void) pause();
+
+ CHECK_RESTART;
+
+ if (doqueuerun())
+ (void) runqueue(true, false,
+ false, false);
+ }
+ }
+ }
+ dropenvelope(&MainEnvelope, true, false);
+
+#if STARTTLS
+ /* init TLS for server, ignore result for now */
+ (void) initsrvtls(tls_ok);
+#endif /* STARTTLS */
+
+ nextreq:
+ p_flags = getrequests(&MainEnvelope);
+
+ /* drop privileges */
+ (void) drop_privileges(false);
+
+ /*
+ ** Get authentication data
+ ** Set _ macro in BlankEnvelope before calling newenvelope().
+ */
+
+ authinfo = getauthinfo(sm_io_getinfo(InChannel, SM_IO_WHAT_FD,
+ NULL), &forged);
+ macdefine(&BlankEnvelope.e_macro, A_TEMP, '_', authinfo);
+
+ /* at this point we are in a child: reset state */
+ sm_rpool_free(MainEnvelope.e_rpool);
+ (void) newenvelope(&MainEnvelope, &MainEnvelope,
+ sm_rpool_new_x(NULL));
+ }
+
+ if (LogLevel > 9)
+ {
+ /* log connection information */
+ sm_syslog(LOG_INFO, NULL, "connect from %s", authinfo);
+ }
+
+ /*
+ ** If running SMTP protocol, start collecting and executing
+ ** commands. This will never return.
+ */
+
+ if (OpMode == MD_SMTP || OpMode == MD_DAEMON)
+ {
+ char pbuf[20];
+
+ /*
+ ** Save some macros for check_* rulesets.
+ */
+
+ if (forged)
+ {
+ char ipbuf[103];
+
+ (void) sm_snprintf(ipbuf, sizeof ipbuf, "[%.100s]",
+ anynet_ntoa(&RealHostAddr));
+ macdefine(&BlankEnvelope.e_macro, A_TEMP,
+ macid("{client_name}"), ipbuf);
+ }
+ else
+ macdefine(&BlankEnvelope.e_macro, A_PERM,
+ macid("{client_name}"), RealHostName);
+ macdefine(&BlankEnvelope.e_macro, A_PERM,
+ macid("{client_ptr}"), RealHostName);
+ macdefine(&BlankEnvelope.e_macro, A_TEMP,
+ macid("{client_addr}"), anynet_ntoa(&RealHostAddr));
+ sm_getla();
+
+ switch (RealHostAddr.sa.sa_family)
+ {
+#if NETINET
+ case AF_INET:
+ (void) sm_snprintf(pbuf, sizeof pbuf, "%d",
+ RealHostAddr.sin.sin_port);
+ break;
+#endif /* NETINET */
+#if NETINET6
+ case AF_INET6:
+ (void) sm_snprintf(pbuf, sizeof pbuf, "%d",
+ RealHostAddr.sin6.sin6_port);
+ break;
+#endif /* NETINET6 */
+ default:
+ (void) sm_snprintf(pbuf, sizeof pbuf, "0");
+ break;
+ }
+ macdefine(&BlankEnvelope.e_macro, A_TEMP,
+ macid("{client_port}"), pbuf);
+
+ if (OpMode == MD_DAEMON)
+ {
+ /* validate the connection */
+ HoldErrs = true;
+ nullserver = validate_connection(&RealHostAddr,
+ macvalue(macid("{client_name}"),
+ &MainEnvelope),
+ &MainEnvelope);
+ HoldErrs = false;
+ }
+ else if (p_flags == NULL)
+ {
+ p_flags = (BITMAP256 *) xalloc(sizeof *p_flags);
+ clrbitmap(p_flags);
+ }
+#if STARTTLS
+ if (OpMode == MD_SMTP)
+ (void) initsrvtls(tls_ok);
+#endif /* STARTTLS */
+
+ /* turn off profiling */
+ SM_PROF(1);
+ smtp(nullserver, *p_flags, &MainEnvelope);
+
+ if (tTd(93, 100))
+ {
+ /* turn off profiling */
+ SM_PROF(0);
+ if (OpMode == MD_DAEMON)
+ goto nextreq;
+ }
+ }
+
+ sm_rpool_free(MainEnvelope.e_rpool);
+ clearenvelope(&MainEnvelope, false, sm_rpool_new_x(NULL));
+ if (OpMode == MD_VERIFY)
+ {
+ set_delivery_mode(SM_VERIFY, &MainEnvelope);
+ PostMasterCopy = NULL;
+ }
+ else
+ {
+ /* interactive -- all errors are global */
+ MainEnvelope.e_flags |= EF_GLOBALERRS|EF_LOGSENDER;
+ }
+
+ /*
+ ** Do basic system initialization and set the sender
+ */
+
+ initsys(&MainEnvelope);
+ macdefine(&MainEnvelope.e_macro, A_PERM, macid("{ntries}"), "0");
+ macdefine(&MainEnvelope.e_macro, A_PERM, macid("{nrcpts}"), "0");
+ setsender(from, &MainEnvelope, NULL, '\0', false);
+ if (warn_f_flag != '\0' && !wordinclass(RealUserName, 't') &&
+ (!bitnset(M_LOCALMAILER, MainEnvelope.e_from.q_mailer->m_flags) ||
+ strcmp(MainEnvelope.e_from.q_user, RealUserName) != 0))
+ {
+ auth_warning(&MainEnvelope, "%s set sender to %s using -%c",
+ RealUserName, from, warn_f_flag);
+#if SASL
+ auth = false;
+#endif /* SASL */
+ }
+ if (auth)
+ {
+ char *fv;
+
+ /* set the initial sender for AUTH= to $f@$j */
+ fv = macvalue('f', &MainEnvelope);
+ if (fv == NULL || *fv == '\0')
+ MainEnvelope.e_auth_param = NULL;
+ else
+ {
+ if (strchr(fv, '@') == NULL)
+ {
+ i = strlen(fv) + strlen(macvalue('j',
+ &MainEnvelope)) + 2;
+ p = sm_malloc_x(i);
+ (void) sm_strlcpyn(p, i, 3, fv, "@",
+ macvalue('j',
+ &MainEnvelope));
+ }
+ else
+ p = sm_strdup_x(fv);
+ MainEnvelope.e_auth_param = sm_rpool_strdup_x(MainEnvelope.e_rpool,
+ xtextify(p, "="));
+ sm_free(p); /* XXX */
+ }
+ }
+ if (macvalue('s', &MainEnvelope) == NULL)
+ macdefine(&MainEnvelope.e_macro, A_PERM, 's', RealHostName);
+
+ av = argv + optind;
+ if (*av == NULL && !GrabTo)
+ {
+ MainEnvelope.e_to = NULL;
+ MainEnvelope.e_flags |= EF_GLOBALERRS;
+ HoldErrs = false;
+ SuperSafe = SAFE_NO;
+ usrerr("Recipient names must be specified");
+
+ /* collect body for UUCP return */
+ if (OpMode != MD_VERIFY)
+ collect(InChannel, false, NULL, &MainEnvelope, true);
+ finis(true, true, EX_USAGE);
+ /* NOTREACHED */
+ }
+
+ /*
+ ** Scan argv and deliver the message to everyone.
+ */
+
+ save_val = LogUsrErrs;
+ LogUsrErrs = true;
+ sendtoargv(av, &MainEnvelope);
+ LogUsrErrs = save_val;
+
+ /* if we have had errors sofar, arrange a meaningful exit stat */
+ if (Errors > 0 && ExitStat == EX_OK)
+ ExitStat = EX_USAGE;
+
+#if _FFR_FIX_DASHT
+ /*
+ ** If using -t, force not sending to argv recipients, even
+ ** if they are mentioned in the headers.
+ */
+
+ if (GrabTo)
+ {
+ ADDRESS *q;
+
+ for (q = MainEnvelope.e_sendqueue; q != NULL; q = q->q_next)
+ q->q_state = QS_REMOVED;
+ }
+#endif /* _FFR_FIX_DASHT */
+
+ /*
+ ** Read the input mail.
+ */
+
+ MainEnvelope.e_to = NULL;
+ if (OpMode != MD_VERIFY || GrabTo)
+ {
+ int savederrors;
+ unsigned long savedflags;
+
+ /*
+ ** workaround for compiler warning on Irix:
+ ** do not initialize variable in the definition, but
+ ** later on:
+ ** warning(1548): transfer of control bypasses
+ ** initialization of:
+ ** variable "savederrors" (declared at line 2570)
+ ** variable "savedflags" (declared at line 2571)
+ ** goto giveup;
+ */
+
+ savederrors = Errors;
+ savedflags = MainEnvelope.e_flags & EF_FATALERRS;
+ MainEnvelope.e_flags |= EF_GLOBALERRS;
+ MainEnvelope.e_flags &= ~EF_FATALERRS;
+ Errors = 0;
+ buffer_errors();
+ collect(InChannel, false, NULL, &MainEnvelope, true);
+
+ /* header checks failed */
+ if (Errors > 0)
+ {
+ giveup:
+ if (!GrabTo)
+ {
+ /* Log who the mail would have gone to */
+ logundelrcpts(&MainEnvelope,
+ MainEnvelope.e_message,
+ 8, false);
+ }
+ flush_errors(true);
+ finis(true, true, ExitStat);
+ /* NOTREACHED */
+ return -1;
+ }
+
+ /* bail out if message too large */
+ if (bitset(EF_CLRQUEUE, MainEnvelope.e_flags))
+ {
+ finis(true, true, ExitStat != EX_OK ? ExitStat
+ : EX_DATAERR);
+ /* NOTREACHED */
+ return -1;
+ }
+
+ /* set message size */
+ (void) sm_snprintf(buf, sizeof buf, "%ld",
+ MainEnvelope.e_msgsize);
+ macdefine(&MainEnvelope.e_macro, A_TEMP,
+ macid("{msg_size}"), buf);
+
+ Errors = savederrors;
+ MainEnvelope.e_flags |= savedflags;
+ }
+ errno = 0;
+
+ if (tTd(1, 1))
+ sm_dprintf("From person = \"%s\"\n",
+ MainEnvelope.e_from.q_paddr);
+
+ /* Check if quarantining stats should be updated */
+ if (MainEnvelope.e_quarmsg != NULL)
+ markstats(&MainEnvelope, NULL, STATS_QUARANTINE);
+
+ /*
+ ** Actually send everything.
+ ** If verifying, just ack.
+ */
+
+ if (Errors == 0)
+ {
+ if (!split_by_recipient(&MainEnvelope) &&
+ bitset(EF_FATALERRS, MainEnvelope.e_flags))
+ goto giveup;
+ }
+
+ /* make sure we deliver at least the first envelope */
+ i = FastSplit > 0 ? 0 : -1;
+ for (e = &MainEnvelope; e != NULL; e = e->e_sibling, i++)
+ {
+ ENVELOPE *next;
+
+ e->e_from.q_state = QS_SENDER;
+ if (tTd(1, 5))
+ {
+ sm_dprintf("main[%d]: QS_SENDER ", i);
+ printaddr(sm_debug_file(), &e->e_from, false);
+ }
+ e->e_to = NULL;
+ sm_getla();
+ GrabTo = false;
+#if NAMED_BIND
+ _res.retry = TimeOuts.res_retry[RES_TO_FIRST];
+ _res.retrans = TimeOuts.res_retrans[RES_TO_FIRST];
+#endif /* NAMED_BIND */
+ next = e->e_sibling;
+ e->e_sibling = NULL;
+
+ /* after FastSplit envelopes: queue up */
+ sendall(e, i >= FastSplit ? SM_QUEUE : SM_DEFAULT);
+ e->e_sibling = next;
+ }
+
+ /*
+ ** All done.
+ ** Don't send return error message if in VERIFY mode.
+ */
+
+ finis(true, true, ExitStat);
+ /* NOTREACHED */
+ return ExitStat;
+}
+/*
+** STOP_SENDMAIL -- Stop the running program
+**
+** Parameters:
+** none.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** exits.
+*/
+
+void
+stop_sendmail()
+{
+ /* reset uid for process accounting */
+ endpwent();
+ (void) setuid(RealUid);
+ exit(EX_OK);
+}
+/*
+** FINIS -- Clean up and exit.
+**
+** Parameters:
+** drop -- whether or not to drop CurEnv envelope
+** cleanup -- call exit() or _exit()?
+** exitstat -- exit status to use for exit() call
+**
+** Returns:
+** never
+**
+** Side Effects:
+** exits sendmail
+*/
+
+void
+finis(drop, cleanup, exitstat)
+ bool drop;
+ bool cleanup;
+ volatile int exitstat;
+{
+ char pidpath[MAXPATHLEN];
+
+ /* Still want to process new timeouts added below */
+ sm_clear_events();
+ (void) sm_releasesignal(SIGALRM);
+
+ if (tTd(2, 1))
+ {
+ sm_dprintf("\n====finis: stat %d e_id=%s e_flags=",
+ exitstat,
+ CurEnv->e_id == NULL ? "NOQUEUE" : CurEnv->e_id);
+ printenvflags(CurEnv);
+ }
+ if (tTd(2, 9))
+ printopenfds(false);
+
+ SM_TRY
+ /*
+ ** Clean up. This might raise E:mta.quickabort
+ */
+
+ /* clean up temp files */
+ CurEnv->e_to = NULL;
+ if (drop)
+ {
+ if (CurEnv->e_id != NULL)
+ {
+ dropenvelope(CurEnv, true, false);
+ sm_rpool_free(CurEnv->e_rpool);
+ CurEnv->e_rpool = NULL;
+ }
+ else
+ poststats(StatFile);
+ }
+
+ /* flush any cached connections */
+ mci_flush(true, NULL);
+
+ /* close maps belonging to this pid */
+ closemaps(false);
+
+#if USERDB
+ /* close UserDatabase */
+ _udbx_close();
+#endif /* USERDB */
+
+#if SASL
+ stop_sasl_client();
+#endif /* SASL */
+
+#if XLA
+ /* clean up extended load average stuff */
+ xla_all_end();
+#endif /* XLA */
+
+ SM_FINALLY
+ /*
+ ** And exit.
+ */
+
+ if (LogLevel > 78)
+ sm_syslog(LOG_DEBUG, CurEnv->e_id, "finis, pid=%d",
+ (int) CurrentPid);
+ if (exitstat == EX_TEMPFAIL ||
+ CurEnv->e_errormode == EM_BERKNET)
+ exitstat = EX_OK;
+
+ /* XXX clean up queues and related data structures */
+ cleanup_queues();
+#if SM_CONF_SHM
+ cleanup_shm(DaemonPid == getpid());
+#endif /* SM_CONF_SHM */
+
+ /* close locked pid file */
+ close_sendmail_pid();
+
+ if (DaemonPid == getpid() || PidFilePid == getpid())
+ {
+ /* blow away the pid file */
+ expand(PidFile, pidpath, sizeof pidpath, CurEnv);
+ (void) unlink(pidpath);
+ }
+
+ /* reset uid for process accounting */
+ endpwent();
+ sm_mbdb_terminate();
+ (void) setuid(RealUid);
+#if SM_HEAP_CHECK
+ /* dump the heap, if we are checking for memory leaks */
+ if (sm_debug_active(&SmHeapCheck, 2))
+ sm_heap_report(smioout,
+ sm_debug_level(&SmHeapCheck) - 1);
+#endif /* SM_HEAP_CHECK */
+ if (sm_debug_active(&SmXtrapReport, 1))
+ sm_dprintf("xtrap count = %d\n", SmXtrapCount);
+ if (cleanup)
+ exit(exitstat);
+ else
+ _exit(exitstat);
+ SM_END_TRY
+}
+/*
+** INTINDEBUG -- signal handler for SIGINT in -bt mode
+**
+** Parameters:
+** sig -- incoming signal.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** longjmps back to test mode loop.
+**
+** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+** DOING.
+*/
+
+/* Type of an exception generated on SIGINT during address test mode. */
+static const SM_EXC_TYPE_T EtypeInterrupt =
+{
+ SmExcTypeMagic,
+ "S:mta.interrupt",
+ "",
+ sm_etype_printf,
+ "interrupt",
+};
+
+/* ARGSUSED */
+static SIGFUNC_DECL
+intindebug(sig)
+ int sig;
+{
+ int save_errno = errno;
+
+ FIX_SYSV_SIGNAL(sig, intindebug);
+ errno = save_errno;
+ CHECK_CRITICAL(sig);
+ errno = save_errno;
+ sm_exc_raisenew_x(&EtypeInterrupt);
+ errno = save_errno;
+ return SIGFUNC_RETURN;
+}
+/*
+** SIGTERM -- SIGTERM handler for the daemon
+**
+** Parameters:
+** sig -- signal number.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Sets ShutdownRequest which will hopefully trigger
+** the daemon to exit.
+**
+** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+** DOING.
+*/
+
+/* ARGSUSED */
+static SIGFUNC_DECL
+sigterm(sig)
+ int sig;
+{
+ int save_errno = errno;
+
+ FIX_SYSV_SIGNAL(sig, sigterm);
+ ShutdownRequest = "signal";
+ errno = save_errno;
+ return SIGFUNC_RETURN;
+}
+/*
+** SIGHUP -- handle a SIGHUP signal
+**
+** Parameters:
+** sig -- incoming signal.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Sets RestartRequest which should cause the daemon
+** to restart.
+**
+** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+** DOING.
+*/
+
+/* ARGSUSED */
+static SIGFUNC_DECL
+sighup(sig)
+ int sig;
+{
+ int save_errno = errno;
+
+ FIX_SYSV_SIGNAL(sig, sighup);
+ RestartRequest = "signal";
+ errno = save_errno;
+ return SIGFUNC_RETURN;
+}
+/*
+** SIGPIPE -- signal handler for SIGPIPE
+**
+** Parameters:
+** sig -- incoming signal.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Sets StopRequest which should cause the mailq/hoststatus
+** display to stop.
+**
+** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+** DOING.
+*/
+
+/* ARGSUSED */
+static SIGFUNC_DECL
+sigpipe(sig)
+ int sig;
+{
+ int save_errno = errno;
+
+ FIX_SYSV_SIGNAL(sig, sigpipe);
+ StopRequest = true;
+ errno = save_errno;
+ return SIGFUNC_RETURN;
+}
+/*
+** INTSIG -- clean up on interrupt
+**
+** This just arranges to exit. It pessimizes in that it
+** may resend a message.
+**
+** Parameters:
+** none.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Unlocks the current job.
+**
+** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+** DOING.
+**
+** XXX: More work is needed for this signal handler.
+*/
+
+/* ARGSUSED */
+SIGFUNC_DECL
+intsig(sig)
+ int sig;
+{
+ bool drop = false;
+ int save_errno = errno;
+
+ FIX_SYSV_SIGNAL(sig, intsig);
+ errno = save_errno;
+ CHECK_CRITICAL(sig);
+ sm_allsignals(true);
+
+ if (sig != 0 && LogLevel > 79)
+ sm_syslog(LOG_DEBUG, CurEnv->e_id, "interrupt");
+ FileName = NULL;
+
+ /* Clean-up on aborted stdin message submission */
+ if (CurEnv->e_id != NULL &&
+ (OpMode == MD_SMTP ||
+ OpMode == MD_DELIVER ||
+ OpMode == MD_ARPAFTP))
+ {
+ register ADDRESS *q;
+
+ /* don't return an error indication */
+ CurEnv->e_to = NULL;
+ CurEnv->e_flags &= ~EF_FATALERRS;
+ CurEnv->e_flags |= EF_CLRQUEUE;
+
+ /*
+ ** Spin through the addresses and
+ ** mark them dead to prevent bounces
+ */
+
+ for (q = CurEnv->e_sendqueue; q != NULL; q = q->q_next)
+ q->q_state = QS_DONTSEND;
+
+ drop = true;
+ }
+ else if (OpMode != MD_TEST)
+ {
+ unlockqueue(CurEnv);
+ }
+
+ finis(drop, false, EX_OK);
+ /* NOTREACHED */
+}
+/*
+** DISCONNECT -- remove our connection with any foreground process
+**
+** Parameters:
+** droplev -- how "deeply" we should drop the line.
+** 0 -- ignore signals, mail back errors, make sure
+** output goes to stdout.
+** 1 -- also, make stdout go to /dev/null.
+** 2 -- also, disconnect from controlling terminal
+** (only for daemon mode).
+** e -- the current envelope.
+**
+** Returns:
+** none
+**
+** Side Effects:
+** Trys to insure that we are immune to vagaries of
+** the controlling tty.
+*/
+
+void
+disconnect(droplev, e)
+ int droplev;
+ register ENVELOPE *e;
+{
+ int fd;
+
+ if (tTd(52, 1))
+ sm_dprintf("disconnect: In %d Out %d, e=%p\n",
+ sm_io_getinfo(InChannel, SM_IO_WHAT_FD, NULL),
+ sm_io_getinfo(OutChannel, SM_IO_WHAT_FD, NULL), e);
+ if (tTd(52, 100))
+ {
+ sm_dprintf("don't\n");
+ return;
+ }
+ if (LogLevel > 93)
+ sm_syslog(LOG_DEBUG, e->e_id,
+ "disconnect level %d",
+ droplev);
+
+ /* be sure we don't get nasty signals */
+ (void) sm_signal(SIGINT, SIG_IGN);
+ (void) sm_signal(SIGQUIT, SIG_IGN);
+
+ /* we can't communicate with our caller, so.... */
+ HoldErrs = true;
+ CurEnv->e_errormode = EM_MAIL;
+ Verbose = 0;
+ DisConnected = true;
+
+ /* all input from /dev/null */
+ if (InChannel != smioin)
+ {
+ (void) sm_io_close(InChannel, SM_TIME_DEFAULT);
+ InChannel = smioin;
+ }
+ if (sm_io_reopen(SmFtStdio, SM_TIME_DEFAULT, SM_PATH_DEVNULL,
+ SM_IO_RDONLY, NULL, smioin) == NULL)
+ sm_syslog(LOG_ERR, e->e_id,
+ "disconnect: sm_io_reopen(\"%s\") failed: %s",
+ SM_PATH_DEVNULL, sm_errstring(errno));
+
+ /*
+ ** output to the transcript
+ ** We also compare the fd numbers here since OutChannel
+ ** might be a layer on top of smioout due to encryption
+ ** (see sfsasl.c).
+ */
+
+ if (OutChannel != smioout &&
+ sm_io_getinfo(OutChannel, SM_IO_WHAT_FD, NULL) !=
+ sm_io_getinfo(smioout, SM_IO_WHAT_FD, NULL))
+ {
+ (void) sm_io_close(OutChannel, SM_TIME_DEFAULT);
+ OutChannel = smioout;
+
+#if 0
+ /*
+ ** Has smioout been closed? Reopen it.
+ ** This shouldn't happen anymore, the code is here
+ ** just as a reminder.
+ */
+
+ if (smioout->sm_magic == NULL &&
+ sm_io_reopen(SmFtStdio, SM_TIME_DEFAULT, SM_PATH_DEVNULL,
+ SM_IO_WRONLY, NULL, smioout) == NULL)
+ sm_syslog(LOG_ERR, e->e_id,
+ "disconnect: sm_io_reopen(\"%s\") failed: %s",
+ SM_PATH_DEVNULL, sm_errstring(errno));
+#endif /* 0 */
+ }
+ if (droplev > 0)
+ {
+ fd = open(SM_PATH_DEVNULL, O_WRONLY, 0666);
+ if (fd == -1)
+ sm_syslog(LOG_ERR, e->e_id,
+ "disconnect: open(\"%s\") failed: %s",
+ SM_PATH_DEVNULL, sm_errstring(errno));
+ (void) sm_io_flush(smioout, SM_TIME_DEFAULT);
+ (void) dup2(fd, STDOUT_FILENO);
+ (void) dup2(fd, STDERR_FILENO);
+ (void) close(fd);
+ }
+
+ /* drop our controlling TTY completely if possible */
+ if (droplev > 1)
+ {
+ (void) setsid();
+ errno = 0;
+ }
+
+#if XDEBUG
+ checkfd012("disconnect");
+#endif /* XDEBUG */
+
+ if (LogLevel > 71)
+ sm_syslog(LOG_DEBUG, e->e_id, "in background, pid=%d",
+ (int) CurrentPid);
+
+ errno = 0;
+}
+
+static void
+obsolete(argv)
+ char *argv[];
+{
+ register char *ap;
+ register char *op;
+
+ while ((ap = *++argv) != NULL)
+ {
+ /* Return if "--" or not an option of any form. */
+ if (ap[0] != '-' || ap[1] == '-')
+ return;
+
+ /* Don't allow users to use "-Q." or "-Q ." */
+ if ((ap[1] == 'Q' && ap[2] == '.') ||
+ (ap[1] == 'Q' && argv[1] != NULL &&
+ argv[1][0] == '.' && argv[1][1] == '\0'))
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Can not use -Q.\n");
+ exit(EX_USAGE);
+ }
+
+ /* skip over options that do have a value */
+ op = strchr(OPTIONS, ap[1]);
+ if (op != NULL && *++op == ':' && ap[2] == '\0' &&
+ ap[1] != 'd' &&
+#if defined(sony_news)
+ ap[1] != 'E' && ap[1] != 'J' &&
+#endif /* defined(sony_news) */
+ argv[1] != NULL && argv[1][0] != '-')
+ {
+ argv++;
+ continue;
+ }
+
+ /* If -C doesn't have an argument, use sendmail.cf. */
+#define __DEFPATH "sendmail.cf"
+ if (ap[1] == 'C' && ap[2] == '\0')
+ {
+ *argv = xalloc(sizeof(__DEFPATH) + 2);
+ (void) sm_strlcpyn(argv[0], sizeof(__DEFPATH) + 2, 2,
+ "-C", __DEFPATH);
+ }
+
+ /* If -q doesn't have an argument, run it once. */
+ if (ap[1] == 'q' && ap[2] == '\0')
+ *argv = "-q0";
+
+ /* If -Q doesn't have an argument, disable quarantining */
+ if (ap[1] == 'Q' && ap[2] == '\0')
+ *argv = "-Q.";
+
+ /* if -d doesn't have an argument, use 0-99.1 */
+ if (ap[1] == 'd' && ap[2] == '\0')
+ *argv = "-d0-99.1";
+
+#if defined(sony_news)
+ /* if -E doesn't have an argument, use -EC */
+ if (ap[1] == 'E' && ap[2] == '\0')
+ *argv = "-EC";
+
+ /* if -J doesn't have an argument, use -JJ */
+ if (ap[1] == 'J' && ap[2] == '\0')
+ *argv = "-JJ";
+#endif /* defined(sony_news) */
+ }
+}
+/*
+** AUTH_WARNING -- specify authorization warning
+**
+** Parameters:
+** e -- the current envelope.
+** msg -- the text of the message.
+** args -- arguments to the message.
+**
+** Returns:
+** none.
+*/
+
+void
+#ifdef __STDC__
+auth_warning(register ENVELOPE *e, const char *msg, ...)
+#else /* __STDC__ */
+auth_warning(e, msg, va_alist)
+ register ENVELOPE *e;
+ const char *msg;
+ va_dcl
+#endif /* __STDC__ */
+{
+ char buf[MAXLINE];
+ SM_VA_LOCAL_DECL
+
+ if (bitset(PRIV_AUTHWARNINGS, PrivacyFlags))
+ {
+ register char *p;
+ static char hostbuf[48];
+
+ if (hostbuf[0] == '\0')
+ {
+ struct hostent *hp;
+
+ hp = myhostname(hostbuf, sizeof hostbuf);
+#if NETINET6
+ if (hp != NULL)
+ {
+ freehostent(hp);
+ hp = NULL;
+ }
+#endif /* NETINET6 */
+ }
+
+ (void) sm_strlcpyn(buf, sizeof buf, 2, hostbuf, ": ");
+ p = &buf[strlen(buf)];
+ SM_VA_START(ap, msg);
+ (void) sm_vsnprintf(p, SPACELEFT(buf, p), msg, ap);
+ SM_VA_END(ap);
+ addheader("X-Authentication-Warning", buf, 0, e);
+ if (LogLevel > 3)
+ sm_syslog(LOG_INFO, e->e_id,
+ "Authentication-Warning: %.400s",
+ buf);
+ }
+}
+/*
+** GETEXTENV -- get from external environment
+**
+** Parameters:
+** envar -- the name of the variable to retrieve
+**
+** Returns:
+** The value, if any.
+*/
+
+static char *
+getextenv(envar)
+ const char *envar;
+{
+ char **envp;
+ int l;
+
+ l = strlen(envar);
+ for (envp = ExternalEnviron; envp != NULL && *envp != NULL; envp++)
+ {
+ if (strncmp(*envp, envar, l) == 0 && (*envp)[l] == '=')
+ return &(*envp)[l + 1];
+ }
+ return NULL;
+}
+/*
+** SETUSERENV -- set an environment in the propagated environment
+**
+** Parameters:
+** envar -- the name of the environment variable.
+** value -- the value to which it should be set. If
+** null, this is extracted from the incoming
+** environment. If that is not set, the call
+** to setuserenv is ignored.
+**
+** Returns:
+** none.
+*/
+
+void
+setuserenv(envar, value)
+ const char *envar;
+ const char *value;
+{
+ int i, l;
+ char **evp = UserEnviron;
+ char *p;
+
+ if (value == NULL)
+ {
+ value = getextenv(envar);
+ if (value == NULL)
+ return;
+ }
+
+ /* XXX enforce reasonable size? */
+ i = strlen(envar) + 1;
+ l = strlen(value) + i + 1;
+ p = (char *) xalloc(l);
+ (void) sm_strlcpyn(p, l, 3, envar, "=", value);
+
+ while (*evp != NULL && strncmp(*evp, p, i) != 0)
+ evp++;
+ if (*evp != NULL)
+ {
+ *evp++ = p;
+ }
+ else if (evp < &UserEnviron[MAXUSERENVIRON])
+ {
+ *evp++ = p;
+ *evp = NULL;
+ }
+
+ /* make sure it is in our environment as well */
+ if (putenv(p) < 0)
+ syserr("setuserenv: putenv(%s) failed", p);
+}
+/*
+** DUMPSTATE -- dump state
+**
+** For debugging.
+*/
+
+void
+dumpstate(when)
+ char *when;
+{
+ register char *j = macvalue('j', CurEnv);
+ int rs;
+ extern int NextMacroId;
+
+ sm_syslog(LOG_DEBUG, CurEnv->e_id,
+ "--- dumping state on %s: $j = %s ---",
+ when,
+ j == NULL ? "<NULL>" : j);
+ if (j != NULL)
+ {
+ if (!wordinclass(j, 'w'))
+ sm_syslog(LOG_DEBUG, CurEnv->e_id,
+ "*** $j not in $=w ***");
+ }
+ sm_syslog(LOG_DEBUG, CurEnv->e_id, "CurChildren = %d", CurChildren);
+ sm_syslog(LOG_DEBUG, CurEnv->e_id, "NextMacroId = %d (Max %d)",
+ NextMacroId, MAXMACROID);
+ sm_syslog(LOG_DEBUG, CurEnv->e_id, "--- open file descriptors: ---");
+ printopenfds(true);
+ sm_syslog(LOG_DEBUG, CurEnv->e_id, "--- connection cache: ---");
+ mci_dump_all(smioout, true);
+ rs = strtorwset("debug_dumpstate", NULL, ST_FIND);
+ if (rs > 0)
+ {
+ int status;
+ register char **pvp;
+ char *pv[MAXATOM + 1];
+
+ pv[0] = NULL;
+ status = REWRITE(pv, rs, CurEnv);
+ sm_syslog(LOG_DEBUG, CurEnv->e_id,
+ "--- ruleset debug_dumpstate returns stat %d, pv: ---",
+ status);
+ for (pvp = pv; *pvp != NULL; pvp++)
+ sm_syslog(LOG_DEBUG, CurEnv->e_id, "%s", *pvp);
+ }
+ sm_syslog(LOG_DEBUG, CurEnv->e_id, "--- end of state dump ---");
+}
+
+#ifdef SIGUSR1
+/*
+** SIGUSR1 -- Signal a request to dump state.
+**
+** Parameters:
+** sig -- calling signal.
+**
+** Returns:
+** none.
+**
+** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+** DOING.
+**
+** XXX: More work is needed for this signal handler.
+*/
+
+/* ARGSUSED */
+static SIGFUNC_DECL
+sigusr1(sig)
+ int sig;
+{
+ int save_errno = errno;
+# if SM_HEAP_CHECK
+ extern void dumpstab __P((void));
+# endif /* SM_HEAP_CHECK */
+
+ FIX_SYSV_SIGNAL(sig, sigusr1);
+ errno = save_errno;
+ CHECK_CRITICAL(sig);
+ dumpstate("user signal");
+# if SM_HEAP_CHECK
+ dumpstab();
+# endif /* SM_HEAP_CHECK */
+ errno = save_errno;
+ return SIGFUNC_RETURN;
+}
+#endif /* SIGUSR1 */
+
+/*
+** DROP_PRIVILEGES -- reduce privileges to those of the RunAsUser option
+**
+** Parameters:
+** to_real_uid -- if set, drop to the real uid instead
+** of the RunAsUser.
+**
+** Returns:
+** EX_OSERR if the setuid failed.
+** EX_OK otherwise.
+*/
+
+int
+drop_privileges(to_real_uid)
+ bool to_real_uid;
+{
+ int rval = EX_OK;
+ GIDSET_T emptygidset[1];
+
+ if (tTd(47, 1))
+ sm_dprintf("drop_privileges(%d): Real[UG]id=%d:%d, get[ug]id=%d:%d, gete[ug]id=%d:%d, RunAs[UG]id=%d:%d\n",
+ (int) to_real_uid,
+ (int) RealUid, (int) RealGid,
+ (int) getuid(), (int) getgid(),
+ (int) geteuid(), (int) getegid(),
+ (int) RunAsUid, (int) RunAsGid);
+
+ if (to_real_uid)
+ {
+ RunAsUserName = RealUserName;
+ RunAsUid = RealUid;
+ RunAsGid = RealGid;
+ EffGid = RunAsGid;
+ }
+
+ /* make sure no one can grab open descriptors for secret files */
+ endpwent();
+ sm_mbdb_terminate();
+
+ /* reset group permissions; these can be set later */
+ emptygidset[0] = (to_real_uid || RunAsGid != 0) ? RunAsGid : getegid();
+
+ /*
+ ** Notice: on some OS (Linux...) the setgroups() call causes
+ ** a logfile entry if sendmail is not run by root.
+ ** However, it is unclear (no POSIX standard) whether
+ ** setgroups() can only succeed if executed by root.
+ ** So for now we keep it as it is; if you want to change it, use
+ ** if (geteuid() == 0 && setgroups(1, emptygidset) == -1)
+ */
+
+ if (setgroups(1, emptygidset) == -1 && geteuid() == 0)
+ {
+ syserr("drop_privileges: setgroups(1, %d) failed",
+ (int) emptygidset[0]);
+ rval = EX_OSERR;
+ }
+
+ /* reset primary group id */
+ if (to_real_uid)
+ {
+ /*
+ ** Drop gid to real gid.
+ ** On some OS we must reset the effective[/real[/saved]] gid,
+ ** and then use setgid() to finally drop all group privileges.
+ ** Later on we check whether we can get back the
+ ** effective gid.
+ */
+
+#if HASSETEGID
+ if (setegid(RunAsGid) < 0)
+ {
+ syserr("drop_privileges: setegid(%d) failed",
+ (int) RunAsGid);
+ rval = EX_OSERR;
+ }
+#else /* HASSETEGID */
+# if HASSETREGID
+ if (setregid(RunAsGid, RunAsGid) < 0)
+ {
+ syserr("drop_privileges: setregid(%d, %d) failed",
+ (int) RunAsGid, (int) RunAsGid);
+ rval = EX_OSERR;
+ }
+# else /* HASSETREGID */
+# if HASSETRESGID
+ if (setresgid(RunAsGid, RunAsGid, RunAsGid) < 0)
+ {
+ syserr("drop_privileges: setresgid(%d, %d, %d) failed",
+ (int) RunAsGid, (int) RunAsGid, (int) RunAsGid);
+ rval = EX_OSERR;
+ }
+# endif /* HASSETRESGID */
+# endif /* HASSETREGID */
+#endif /* HASSETEGID */
+ }
+ if (rval == EX_OK && (to_real_uid || RunAsGid != 0))
+ {
+ if (setgid(RunAsGid) < 0 && (!UseMSP || getegid() != RunAsGid))
+ {
+ syserr("drop_privileges: setgid(%d) failed",
+ (int) RunAsGid);
+ rval = EX_OSERR;
+ }
+ errno = 0;
+ if (rval == EX_OK && getegid() != RunAsGid)
+ {
+ syserr("drop_privileges: Unable to set effective gid=%d to RunAsGid=%d",
+ (int) getegid(), (int) RunAsGid);
+ rval = EX_OSERR;
+ }
+ }
+
+ /* fiddle with uid */
+ if (to_real_uid || RunAsUid != 0)
+ {
+ uid_t euid;
+
+ /*
+ ** Try to setuid(RunAsUid).
+ ** euid must be RunAsUid,
+ ** ruid must be RunAsUid unless (e|r)uid wasn't 0
+ ** and we didn't have to drop privileges to the real uid.
+ */
+
+ if (setuid(RunAsUid) < 0 ||
+ geteuid() != RunAsUid ||
+ (getuid() != RunAsUid &&
+ (to_real_uid || geteuid() == 0 || getuid() == 0)))
+ {
+#if HASSETREUID
+ /*
+ ** if ruid != RunAsUid, euid == RunAsUid, then
+ ** try resetting just the real uid, then using
+ ** setuid() to drop the saved-uid as well.
+ */
+
+ if (geteuid() == RunAsUid)
+ {
+ if (setreuid(RunAsUid, -1) < 0)
+ {
+ syserr("drop_privileges: setreuid(%d, -1) failed",
+ (int) RunAsUid);
+ rval = EX_OSERR;
+ }
+ if (setuid(RunAsUid) < 0)
+ {
+ syserr("drop_privileges: second setuid(%d) attempt failed",
+ (int) RunAsUid);
+ rval = EX_OSERR;
+ }
+ }
+ else
+#endif /* HASSETREUID */
+ {
+ syserr("drop_privileges: setuid(%d) failed",
+ (int) RunAsUid);
+ rval = EX_OSERR;
+ }
+ }
+ euid = geteuid();
+ if (RunAsUid != 0 && setuid(0) == 0)
+ {
+ /*
+ ** Believe it or not, the Linux capability model
+ ** allows a non-root process to override setuid()
+ ** on a process running as root and prevent that
+ ** process from dropping privileges.
+ */
+
+ syserr("drop_privileges: setuid(0) succeeded (when it should not)");
+ rval = EX_OSERR;
+ }
+ else if (RunAsUid != euid && setuid(euid) == 0)
+ {
+ /*
+ ** Some operating systems will keep the saved-uid
+ ** if a non-root effective-uid calls setuid(real-uid)
+ ** making it possible to set it back again later.
+ */
+
+ syserr("drop_privileges: Unable to drop non-root set-user-ID privileges");
+ rval = EX_OSERR;
+ }
+ }
+
+ if ((to_real_uid || RunAsGid != 0) &&
+ rval == EX_OK && RunAsGid != EffGid &&
+ getuid() != 0 && geteuid() != 0)
+ {
+ errno = 0;
+ if (setgid(EffGid) == 0)
+ {
+ syserr("drop_privileges: setgid(%d) succeeded (when it should not)",
+ (int) EffGid);
+ rval = EX_OSERR;
+ }
+ }
+
+ if (tTd(47, 5))
+ {
+ sm_dprintf("drop_privileges: e/ruid = %d/%d e/rgid = %d/%d\n",
+ (int) geteuid(), (int) getuid(),
+ (int) getegid(), (int) getgid());
+ sm_dprintf("drop_privileges: RunAsUser = %d:%d\n",
+ (int) RunAsUid, (int) RunAsGid);
+ if (tTd(47, 10))
+ sm_dprintf("drop_privileges: rval = %d\n", rval);
+ }
+ return rval;
+}
+/*
+** FILL_FD -- make sure a file descriptor has been properly allocated
+**
+** Used to make sure that stdin/out/err are allocated on startup
+**
+** Parameters:
+** fd -- the file descriptor to be filled.
+** where -- a string used for logging. If NULL, this is
+** being called on startup, and logging should
+** not be done.
+**
+** Returns:
+** none
+**
+** Side Effects:
+** possibly changes MissingFds
+*/
+
+void
+fill_fd(fd, where)
+ int fd;
+ char *where;
+{
+ int i;
+ struct stat stbuf;
+
+ if (fstat(fd, &stbuf) >= 0 || errno != EBADF)
+ return;
+
+ if (where != NULL)
+ syserr("fill_fd: %s: fd %d not open", where, fd);
+ else
+ MissingFds |= 1 << fd;
+ i = open(SM_PATH_DEVNULL, fd == 0 ? O_RDONLY : O_WRONLY, 0666);
+ if (i < 0)
+ {
+ syserr("!fill_fd: %s: cannot open %s",
+ where == NULL ? "startup" : where, SM_PATH_DEVNULL);
+ }
+ if (fd != i)
+ {
+ (void) dup2(i, fd);
+ (void) close(i);
+ }
+}
+/*
+** SM_PRINTOPTIONS -- print options
+**
+** Parameters:
+** options -- array of options.
+**
+** Returns:
+** none.
+*/
+
+static void
+sm_printoptions(options)
+ char **options;
+{
+ int ll;
+ char **av;
+
+ av = options;
+ ll = 7;
+ while (*av != NULL)
+ {
+ if (ll + strlen(*av) > 63)
+ {
+ sm_dprintf("\n");
+ ll = 0;
+ }
+ if (ll == 0)
+ sm_dprintf("\t\t");
+ else
+ sm_dprintf(" ");
+ sm_dprintf("%s", *av);
+ ll += strlen(*av++) + 1;
+ }
+ sm_dprintf("\n");
+}
+/*
+** TESTMODELINE -- process a test mode input line
+**
+** Parameters:
+** line -- the input line.
+** e -- the current environment.
+** Syntax:
+** # a comment
+** .X process X as a configuration line
+** =X dump a configuration item (such as mailers)
+** $X dump a macro or class
+** /X try an activity
+** X normal process through rule set X
+*/
+
+static void
+testmodeline(line, e)
+ char *line;
+ ENVELOPE *e;
+{
+ register char *p;
+ char *q;
+ auto char *delimptr;
+ int mid;
+ int i, rs;
+ STAB *map;
+ char **s;
+ struct rewrite *rw;
+ ADDRESS a;
+ static int tryflags = RF_COPYNONE;
+ char exbuf[MAXLINE];
+ extern unsigned char TokTypeNoC[];
+
+ /* skip leading spaces */
+ while (*line == ' ')
+ line++;
+
+ switch (line[0])
+ {
+ case '#':
+ case '\0':
+ return;
+
+ case '?':
+ help("-bt", e);
+ return;
+
+ case '.': /* config-style settings */
+ switch (line[1])
+ {
+ case 'D':
+ mid = macid_parse(&line[2], &delimptr);
+ if (mid == 0)
+ return;
+ translate_dollars(delimptr);
+ macdefine(&e->e_macro, A_TEMP, mid, delimptr);
+ break;
+
+ case 'C':
+ if (line[2] == '\0') /* not to call syserr() */
+ return;
+
+ mid = macid_parse(&line[2], &delimptr);
+ if (mid == 0)
+ return;
+ translate_dollars(delimptr);
+ expand(delimptr, exbuf, sizeof exbuf, e);
+ p = exbuf;
+ while (*p != '\0')
+ {
+ register char *wd;
+ char delim;
+
+ while (*p != '\0' && isascii(*p) && isspace(*p))
+ p++;
+ wd = p;
+ while (*p != '\0' && !(isascii(*p) && isspace(*p)))
+ p++;
+ delim = *p;
+ *p = '\0';
+ if (wd[0] != '\0')
+ setclass(mid, wd);
+ *p = delim;
+ }
+ break;
+
+ case '\0':
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Usage: .[DC]macro value(s)\n");
+ break;
+
+ default:
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Unknown \".\" command %s\n", line);
+ break;
+ }
+ return;
+
+ case '=': /* config-style settings */
+ switch (line[1])
+ {
+ case 'S': /* dump rule set */
+ rs = strtorwset(&line[2], NULL, ST_FIND);
+ if (rs < 0)
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Undefined ruleset %s\n", &line[2]);
+ return;
+ }
+ rw = RewriteRules[rs];
+ if (rw == NULL)
+ return;
+ do
+ {
+ (void) sm_io_putc(smioout, SM_TIME_DEFAULT,
+ 'R');
+ s = rw->r_lhs;
+ while (*s != NULL)
+ {
+ xputs(smioout, *s++);
+ (void) sm_io_putc(smioout,
+ SM_TIME_DEFAULT, ' ');
+ }
+ (void) sm_io_putc(smioout, SM_TIME_DEFAULT,
+ '\t');
+ (void) sm_io_putc(smioout, SM_TIME_DEFAULT,
+ '\t');
+ s = rw->r_rhs;
+ while (*s != NULL)
+ {
+ xputs(smioout, *s++);
+ (void) sm_io_putc(smioout,
+ SM_TIME_DEFAULT, ' ');
+ }
+ (void) sm_io_putc(smioout, SM_TIME_DEFAULT,
+ '\n');
+ } while ((rw = rw->r_next) != NULL);
+ break;
+
+ case 'M':
+ for (i = 0; i < MAXMAILERS; i++)
+ {
+ if (Mailer[i] != NULL)
+ printmailer(smioout, Mailer[i]);
+ }
+ break;
+
+ case '\0':
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Usage: =Sruleset or =M\n");
+ break;
+
+ default:
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Unknown \"=\" command %s\n", line);
+ break;
+ }
+ return;
+
+ case '-': /* set command-line-like opts */
+ switch (line[1])
+ {
+ case 'd':
+ tTflag(&line[2]);
+ break;
+
+ case '\0':
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Usage: -d{debug arguments}\n");
+ break;
+
+ default:
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Unknown \"-\" command %s\n", line);
+ break;
+ }
+ return;
+
+ case '$':
+ if (line[1] == '=')
+ {
+ mid = macid(&line[2]);
+ if (mid != 0)
+ stabapply(dump_class, mid);
+ return;
+ }
+ mid = macid(&line[1]);
+ if (mid == 0)
+ return;
+ p = macvalue(mid, e);
+ if (p == NULL)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Undefined\n");
+ else
+ {
+ xputs(smioout, p);
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "\n");
+ }
+ return;
+
+ case '/': /* miscellaneous commands */
+ p = &line[strlen(line)];
+ while (--p >= line && isascii(*p) && isspace(*p))
+ *p = '\0';
+ p = strpbrk(line, " \t");
+ if (p != NULL)
+ {
+ while (isascii(*p) && isspace(*p))
+ *p++ = '\0';
+ }
+ else
+ p = "";
+ if (line[1] == '\0')
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Usage: /[canon|map|mx|parse|try|tryflags]\n");
+ return;
+ }
+ if (sm_strcasecmp(&line[1], "quit") == 0)
+ {
+ CurEnv->e_id = NULL;
+ finis(true, true, ExitStat);
+ /* NOTREACHED */
+ }
+ if (sm_strcasecmp(&line[1], "mx") == 0)
+ {
+#if NAMED_BIND
+ /* look up MX records */
+ int nmx;
+ auto int rcode;
+ char *mxhosts[MAXMXHOSTS + 1];
+
+ if (*p == '\0')
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Usage: /mx address\n");
+ return;
+ }
+ nmx = getmxrr(p, mxhosts, NULL, false, &rcode, true,
+ NULL);
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "getmxrr(%s) returns %d value(s):\n",
+ p, nmx);
+ for (i = 0; i < nmx; i++)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "\t%s\n", mxhosts[i]);
+#else /* NAMED_BIND */
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "No MX code compiled in\n");
+#endif /* NAMED_BIND */
+ }
+ else if (sm_strcasecmp(&line[1], "canon") == 0)
+ {
+ char host[MAXHOSTNAMELEN];
+
+ if (*p == '\0')
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Usage: /canon address\n");
+ return;
+ }
+ else if (sm_strlcpy(host, p, sizeof host) >= sizeof host)
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Name too long\n");
+ return;
+ }
+ (void) getcanonname(host, sizeof host, !HasWildcardMX,
+ NULL);
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "getcanonname(%s) returns %s\n",
+ p, host);
+ }
+ else if (sm_strcasecmp(&line[1], "map") == 0)
+ {
+ auto int rcode = EX_OK;
+ char *av[2];
+
+ if (*p == '\0')
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Usage: /map mapname key\n");
+ return;
+ }
+ for (q = p; *q != '\0' && !(isascii(*q) && isspace(*q)); q++)
+ continue;
+ if (*q == '\0')
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "No key specified\n");
+ return;
+ }
+ *q++ = '\0';
+ map = stab(p, ST_MAP, ST_FIND);
+ if (map == NULL)
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Map named \"%s\" not found\n", p);
+ return;
+ }
+ if (!bitset(MF_OPEN, map->s_map.map_mflags) &&
+ !openmap(&(map->s_map)))
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Map named \"%s\" not open\n", p);
+ return;
+ }
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "map_lookup: %s (%s) ", p, q);
+ av[0] = q;
+ av[1] = NULL;
+ p = (*map->s_map.map_class->map_lookup)
+ (&map->s_map, q, av, &rcode);
+ if (p == NULL)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "no match (%d)\n",
+ rcode);
+ else
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "returns %s (%d)\n", p,
+ rcode);
+ }
+ else if (sm_strcasecmp(&line[1], "try") == 0)
+ {
+ MAILER *m;
+ STAB *st;
+ auto int rcode = EX_OK;
+
+ q = strpbrk(p, " \t");
+ if (q != NULL)
+ {
+ while (isascii(*q) && isspace(*q))
+ *q++ = '\0';
+ }
+ if (q == NULL || *q == '\0')
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Usage: /try mailer address\n");
+ return;
+ }
+ st = stab(p, ST_MAILER, ST_FIND);
+ if (st == NULL)
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Unknown mailer %s\n", p);
+ return;
+ }
+ m = st->s_mailer;
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Trying %s %s address %s for mailer %s\n",
+ bitset(RF_HEADERADDR, tryflags) ? "header"
+ : "envelope",
+ bitset(RF_SENDERADDR, tryflags) ? "sender"
+ : "recipient", q, p);
+ p = remotename(q, m, tryflags, &rcode, CurEnv);
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Rcode = %d, addr = %s\n",
+ rcode, p == NULL ? "<NULL>" : p);
+ e->e_to = NULL;
+ }
+ else if (sm_strcasecmp(&line[1], "tryflags") == 0)
+ {
+ if (*p == '\0')
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Usage: /tryflags [Hh|Ee][Ss|Rr]\n");
+ return;
+ }
+ for (; *p != '\0'; p++)
+ {
+ switch (*p)
+ {
+ case 'H':
+ case 'h':
+ tryflags |= RF_HEADERADDR;
+ break;
+
+ case 'E':
+ case 'e':
+ tryflags &= ~RF_HEADERADDR;
+ break;
+
+ case 'S':
+ case 's':
+ tryflags |= RF_SENDERADDR;
+ break;
+
+ case 'R':
+ case 'r':
+ tryflags &= ~RF_SENDERADDR;
+ break;
+ }
+ }
+ exbuf[0] = bitset(RF_HEADERADDR, tryflags) ? 'h' : 'e';
+ exbuf[1] = ' ';
+ exbuf[2] = bitset(RF_SENDERADDR, tryflags) ? 's' : 'r';
+ exbuf[3] = '\0';
+ macdefine(&e->e_macro, A_TEMP,
+ macid("{addr_type}"), exbuf);
+ }
+ else if (sm_strcasecmp(&line[1], "parse") == 0)
+ {
+ if (*p == '\0')
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Usage: /parse address\n");
+ return;
+ }
+ q = crackaddr(p, e);
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Cracked address = ");
+ xputs(smioout, q);
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "\nParsing %s %s address\n",
+ bitset(RF_HEADERADDR, tryflags) ?
+ "header" : "envelope",
+ bitset(RF_SENDERADDR, tryflags) ?
+ "sender" : "recipient");
+ if (parseaddr(p, &a, tryflags, '\0', NULL, e, true)
+ == NULL)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Cannot parse\n");
+ else if (a.q_host != NULL && a.q_host[0] != '\0')
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "mailer %s, host %s, user %s\n",
+ a.q_mailer->m_name,
+ a.q_host,
+ a.q_user);
+ else
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "mailer %s, user %s\n",
+ a.q_mailer->m_name,
+ a.q_user);
+ e->e_to = NULL;
+ }
+ else
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Unknown \"/\" command %s\n",
+ line);
+ }
+ return;
+ }
+
+ for (p = line; isascii(*p) && isspace(*p); p++)
+ continue;
+ q = p;
+ while (*p != '\0' && !(isascii(*p) && isspace(*p)))
+ p++;
+ if (*p == '\0')
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "No address!\n");
+ return;
+ }
+ *p = '\0';
+ if (invalidaddr(p + 1, NULL, true))
+ return;
+ do
+ {
+ register char **pvp;
+ char pvpbuf[PSBUFSIZE];
+
+ pvp = prescan(++p, ',', pvpbuf, sizeof pvpbuf, &delimptr,
+ ConfigLevel >= 9 ? TokTypeNoC : NULL, false);
+ if (pvp == NULL)
+ continue;
+ p = q;
+ while (*p != '\0')
+ {
+ int status;
+
+ rs = strtorwset(p, NULL, ST_FIND);
+ if (rs < 0)
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Undefined ruleset %s\n",
+ p);
+ break;
+ }
+ status = REWRITE(pvp, rs, e);
+ if (status != EX_OK)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "== Ruleset %s (%d) status %d\n",
+ p, rs, status);
+ while (*p != '\0' && *p++ != ',')
+ continue;
+ }
+ } while (*(p = delimptr) != '\0');
+}
+
+static void
+dump_class(s, id)
+ register STAB *s;
+ int id;
+{
+ if (s->s_symtype != ST_CLASS)
+ return;
+ if (bitnset(bitidx(id), s->s_class))
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "%s\n", s->s_name);
+}
+
+/*
+** An exception type used to create QuickAbort exceptions.
+** This is my first cut at converting QuickAbort from longjmp to exceptions.
+** These exceptions have a single integer argument, which is the argument
+** to longjmp in the original code (either 1 or 2). I don't know the
+** significance of 1 vs 2: the calls to setjmp don't care.
+*/
+
+const SM_EXC_TYPE_T EtypeQuickAbort =
+{
+ SmExcTypeMagic,
+ "E:mta.quickabort",
+ "i",
+ sm_etype_printf,
+ "quick abort %0",
+};
diff --git a/usr/src/cmd/sendmail/src/map.c b/usr/src/cmd/sendmail/src/map.c
new file mode 100644
index 0000000000..7bdf15ec70
--- /dev/null
+++ b/usr/src/cmd/sendmail/src/map.c
@@ -0,0 +1,7781 @@
+/*
+ * Copyright (c) 1998-2005 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1992, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+/*
+ * Copyright 1996-2004 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sendmail.h>
+
+SM_RCSID("@(#)$Id: map.c,v 8.669 2005/02/09 01:46:35 ca Exp $")
+SM_IDSTR(i2, "%W% (Sun) %G%")
+
+#if LDAPMAP
+# include <sm/ldap.h>
+#endif /* LDAPMAP */
+
+#if NDBM
+# include <ndbm.h>
+# ifdef R_FIRST
+ ERROR README: You are running the Berkeley DB version of ndbm.h. See
+ ERROR README: the README file about tweaking Berkeley DB so it can
+ ERROR README: coexist with NDBM, or delete -DNDBM from the Makefile
+ ERROR README: and use -DNEWDB instead.
+# endif /* R_FIRST */
+#endif /* NDBM */
+#if NEWDB
+# include "sm/bdb.h"
+#endif /* NEWDB */
+#if NIS
+ struct dom_binding; /* forward reference needed on IRIX */
+# include <rpcsvc/ypclnt.h>
+# if NDBM
+# define NDBM_YP_COMPAT /* create YP-compatible NDBM files */
+# endif /* NDBM */
+#endif /* NIS */
+
+#if NEWDB
+# if DB_VERSION_MAJOR < 2
+static bool db_map_open __P((MAP *, int, char *, DBTYPE, const void *));
+# endif /* DB_VERSION_MAJOR < 2 */
+# if DB_VERSION_MAJOR == 2
+static bool db_map_open __P((MAP *, int, char *, DBTYPE, DB_INFO *));
+# endif /* DB_VERSION_MAJOR == 2 */
+# if DB_VERSION_MAJOR > 2
+static bool db_map_open __P((MAP *, int, char *, DBTYPE, void **));
+# endif /* DB_VERSION_MAJOR > 2 */
+#endif /* NEWDB */
+static bool extract_canonname __P((char *, char *, char *, char[], int));
+static void map_close __P((STAB *, int));
+static void map_init __P((STAB *, int));
+#ifdef LDAPMAP
+static STAB * ldapmap_findconn __P((SM_LDAP_STRUCT *));
+#endif /* LDAPMAP */
+#if NISPLUS
+static bool nisplus_getcanonname __P((char *, int, int *));
+#endif /* NISPLUS */
+#if NIS
+static bool nis_getcanonname __P((char *, int, int *));
+#endif /* NIS */
+#if NETINFO
+static bool ni_getcanonname __P((char *, int, int *));
+#endif /* NETINFO */
+static bool text_getcanonname __P((char *, int, int *));
+#if SOCKETMAP
+static STAB *socket_map_findconn __P((const char*));
+
+/* XXX arbitrary limit for sanity */
+# define SOCKETMAP_MAXL 1000000
+#endif /* SOCKETMAP */
+
+/* default error message for trying to open a map in write mode */
+#ifdef ENOSYS
+# define SM_EMAPCANTWRITE ENOSYS
+#else /* ENOSYS */
+# ifdef EFTYPE
+# define SM_EMAPCANTWRITE EFTYPE
+# else /* EFTYPE */
+# define SM_EMAPCANTWRITE ENXIO
+# endif /* EFTYPE */
+#endif /* ENOSYS */
+
+/*
+** MAP.C -- implementations for various map classes.
+**
+** Each map class implements a series of functions:
+**
+** bool map_parse(MAP *map, char *args)
+** Parse the arguments from the config file. Return true
+** if they were ok, false otherwise. Fill in map with the
+** values.
+**
+** char *map_lookup(MAP *map, char *key, char **args, int *pstat)
+** Look up the key in the given map. If found, do any
+** rewriting the map wants (including "args" if desired)
+** and return the value. Set *pstat to the appropriate status
+** on error and return NULL. Args will be NULL if called
+** from the alias routines, although this should probably
+** not be relied upon. It is suggested you call map_rewrite
+** to return the results -- it takes care of null termination
+** and uses a dynamically expanded buffer as needed.
+**
+** void map_store(MAP *map, char *key, char *value)
+** Store the key:value pair in the map.
+**
+** bool map_open(MAP *map, int mode)
+** Open the map for the indicated mode. Mode should
+** be either O_RDONLY or O_RDWR. Return true if it
+** was opened successfully, false otherwise. If the open
+** failed and the MF_OPTIONAL flag is not set, it should
+** also print an error. If the MF_ALIAS bit is set
+** and this map class understands the @:@ convention, it
+** should call aliaswait() before returning.
+**
+** void map_close(MAP *map)
+** Close the map.
+**
+** This file also includes the implementation for getcanonname.
+** It is currently implemented in a pretty ad-hoc manner; it ought
+** to be more properly integrated into the map structure.
+*/
+
+#if O_EXLOCK && HASFLOCK && !BOGUS_O_EXCL
+# define LOCK_ON_OPEN 1 /* we can open/create a locked file */
+#else /* O_EXLOCK && HASFLOCK && !BOGUS_O_EXCL */
+# define LOCK_ON_OPEN 0 /* no such luck -- bend over backwards */
+#endif /* O_EXLOCK && HASFLOCK && !BOGUS_O_EXCL */
+
+/*
+** MAP_PARSEARGS -- parse config line arguments for database lookup
+**
+** This is a generic version of the map_parse method.
+**
+** Parameters:
+** map -- the map being initialized.
+** ap -- a pointer to the args on the config line.
+**
+** Returns:
+** true -- if everything parsed OK.
+** false -- otherwise.
+**
+** Side Effects:
+** null terminates the filename; stores it in map
+*/
+
+bool
+map_parseargs(map, ap)
+ MAP *map;
+ char *ap;
+{
+ register char *p = ap;
+
+ /*
+ ** There is no check whether there is really an argument,
+ ** but that's not important enough to warrant extra code.
+ */
+
+ map->map_mflags |= MF_TRY0NULL|MF_TRY1NULL;
+ map->map_spacesub = SpaceSub; /* default value */
+ for (;;)
+ {
+ while (isascii(*p) && isspace(*p))
+ p++;
+ if (*p != '-')
+ break;
+ switch (*++p)
+ {
+ case 'N':
+ map->map_mflags |= MF_INCLNULL;
+ map->map_mflags &= ~MF_TRY0NULL;
+ break;
+
+ case 'O':
+ map->map_mflags &= ~MF_TRY1NULL;
+ break;
+
+ case 'o':
+ map->map_mflags |= MF_OPTIONAL;
+ break;
+
+ case 'f':
+ map->map_mflags |= MF_NOFOLDCASE;
+ break;
+
+ case 'm':
+ map->map_mflags |= MF_MATCHONLY;
+ break;
+
+ case 'A':
+ map->map_mflags |= MF_APPEND;
+ break;
+
+ case 'q':
+ map->map_mflags |= MF_KEEPQUOTES;
+ break;
+
+ case 'a':
+ map->map_app = ++p;
+ break;
+
+ case 'T':
+ map->map_tapp = ++p;
+ break;
+
+ case 'k':
+ while (isascii(*++p) && isspace(*p))
+ continue;
+ map->map_keycolnm = p;
+ break;
+
+ case 'v':
+ while (isascii(*++p) && isspace(*p))
+ continue;
+ map->map_valcolnm = p;
+ break;
+
+ case 'z':
+ if (*++p != '\\')
+ map->map_coldelim = *p;
+ else
+ {
+ switch (*++p)
+ {
+ case 'n':
+ map->map_coldelim = '\n';
+ break;
+
+ case 't':
+ map->map_coldelim = '\t';
+ break;
+
+ default:
+ map->map_coldelim = '\\';
+ }
+ }
+ break;
+
+ case 't':
+ map->map_mflags |= MF_NODEFER;
+ break;
+
+
+ case 'S':
+ map->map_spacesub = *++p;
+ break;
+
+ case 'D':
+ map->map_mflags |= MF_DEFER;
+ break;
+
+ default:
+ syserr("Illegal option %c map %s", *p, map->map_mname);
+ break;
+ }
+ while (*p != '\0' && !(isascii(*p) && isspace(*p)))
+ p++;
+ if (*p != '\0')
+ *p++ = '\0';
+ }
+ if (map->map_app != NULL)
+ map->map_app = newstr(map->map_app);
+ if (map->map_tapp != NULL)
+ map->map_tapp = newstr(map->map_tapp);
+ if (map->map_keycolnm != NULL)
+ map->map_keycolnm = newstr(map->map_keycolnm);
+ if (map->map_valcolnm != NULL)
+ map->map_valcolnm = newstr(map->map_valcolnm);
+
+ if (*p != '\0')
+ {
+ map->map_file = p;
+ while (*p != '\0' && !(isascii(*p) && isspace(*p)))
+ p++;
+ if (*p != '\0')
+ *p++ = '\0';
+ map->map_file = newstr(map->map_file);
+ }
+
+ while (*p != '\0' && isascii(*p) && isspace(*p))
+ p++;
+ if (*p != '\0')
+ map->map_rebuild = newstr(p);
+
+ if (map->map_file == NULL &&
+ !bitset(MCF_OPTFILE, map->map_class->map_cflags))
+ {
+ syserr("No file name for %s map %s",
+ map->map_class->map_cname, map->map_mname);
+ return false;
+ }
+ return true;
+}
+/*
+** MAP_REWRITE -- rewrite a database key, interpolating %n indications.
+**
+** It also adds the map_app string. It can be used as a utility
+** in the map_lookup method.
+**
+** Parameters:
+** map -- the map that causes this.
+** s -- the string to rewrite, NOT necessarily null terminated.
+** slen -- the length of s.
+** av -- arguments to interpolate into buf.
+**
+** Returns:
+** Pointer to rewritten result. This is static data that
+** should be copied if it is to be saved!
+*/
+
+char *
+map_rewrite(map, s, slen, av)
+ register MAP *map;
+ register const char *s;
+ size_t slen;
+ char **av;
+{
+ register char *bp;
+ register char c;
+ char **avp;
+ register char *ap;
+ size_t l;
+ size_t len;
+ static size_t buflen = 0;
+ static char *buf = NULL;
+
+ if (tTd(39, 1))
+ {
+ sm_dprintf("map_rewrite(%.*s), av =", (int) slen, s);
+ if (av == NULL)
+ sm_dprintf(" (nullv)");
+ else
+ {
+ for (avp = av; *avp != NULL; avp++)
+ sm_dprintf("\n\t%s", *avp);
+ }
+ sm_dprintf("\n");
+ }
+
+ /* count expected size of output (can safely overestimate) */
+ l = len = slen;
+ if (av != NULL)
+ {
+ const char *sp = s;
+
+ while (l-- > 0 && (c = *sp++) != '\0')
+ {
+ if (c != '%')
+ continue;
+ if (l-- <= 0)
+ break;
+ c = *sp++;
+ if (!(isascii(c) && isdigit(c)))
+ continue;
+ for (avp = av; --c >= '0' && *avp != NULL; avp++)
+ continue;
+ if (*avp == NULL)
+ continue;
+ len += strlen(*avp);
+ }
+ }
+ if (map->map_app != NULL)
+ len += strlen(map->map_app);
+ if (buflen < ++len)
+ {
+ /* need to malloc additional space */
+ buflen = len;
+ if (buf != NULL)
+ sm_free(buf);
+ buf = sm_pmalloc_x(buflen);
+ }
+
+ bp = buf;
+ if (av == NULL)
+ {
+ memmove(bp, s, slen);
+ bp += slen;
+
+ /* assert(len > slen); */
+ len -= slen;
+ }
+ else
+ {
+ while (slen-- > 0 && (c = *s++) != '\0')
+ {
+ if (c != '%')
+ {
+ pushc:
+ if (len-- <= 1)
+ break;
+ *bp++ = c;
+ continue;
+ }
+ if (slen-- <= 0 || (c = *s++) == '\0')
+ c = '%';
+ if (c == '%')
+ goto pushc;
+ if (!(isascii(c) && isdigit(c)))
+ {
+ if (len-- <= 1)
+ break;
+ *bp++ = '%';
+ goto pushc;
+ }
+ for (avp = av; --c >= '0' && *avp != NULL; avp++)
+ continue;
+ if (*avp == NULL)
+ continue;
+
+ /* transliterate argument into output string */
+ for (ap = *avp; (c = *ap++) != '\0' && len > 0; --len)
+ *bp++ = c;
+ }
+ }
+ if (map->map_app != NULL && len > 0)
+ (void) sm_strlcpy(bp, map->map_app, len);
+ else
+ *bp = '\0';
+ if (tTd(39, 1))
+ sm_dprintf("map_rewrite => %s\n", buf);
+ return buf;
+}
+/*
+** INITMAPS -- rebuild alias maps
+**
+** Parameters:
+** none.
+**
+** Returns:
+** none.
+*/
+
+void
+initmaps()
+{
+#if XDEBUG
+ checkfd012("entering initmaps");
+#endif /* XDEBUG */
+ stabapply(map_init, 0);
+#if XDEBUG
+ checkfd012("exiting initmaps");
+#endif /* XDEBUG */
+}
+/*
+** MAP_INIT -- rebuild a map
+**
+** Parameters:
+** s -- STAB entry: if map: try to rebuild
+** unused -- unused variable
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** will close already open rebuildable map.
+*/
+
+/* ARGSUSED1 */
+static void
+map_init(s, unused)
+ register STAB *s;
+ int unused;
+{
+ register MAP *map;
+
+ /* has to be a map */
+ if (s->s_symtype != ST_MAP)
+ return;
+
+ map = &s->s_map;
+ if (!bitset(MF_VALID, map->map_mflags))
+ return;
+
+ if (tTd(38, 2))
+ sm_dprintf("map_init(%s:%s, %s)\n",
+ map->map_class->map_cname == NULL ? "NULL" :
+ map->map_class->map_cname,
+ map->map_mname == NULL ? "NULL" : map->map_mname,
+ map->map_file == NULL ? "NULL" : map->map_file);
+
+ if (!bitset(MF_ALIAS, map->map_mflags) ||
+ !bitset(MCF_REBUILDABLE, map->map_class->map_cflags))
+ {
+ if (tTd(38, 3))
+ sm_dprintf("\tnot rebuildable\n");
+ return;
+ }
+
+ /* if already open, close it (for nested open) */
+ if (bitset(MF_OPEN, map->map_mflags))
+ {
+ map->map_mflags |= MF_CLOSING;
+ map->map_class->map_close(map);
+ map->map_mflags &= ~(MF_OPEN|MF_WRITABLE|MF_CLOSING);
+ }
+
+ (void) rebuildaliases(map, false);
+ return;
+}
+/*
+** OPENMAP -- open a map
+**
+** Parameters:
+** map -- map to open (it must not be open).
+**
+** Returns:
+** whether open succeeded.
+*/
+
+bool
+openmap(map)
+ MAP *map;
+{
+ bool restore = false;
+ bool savehold = HoldErrs;
+ bool savequick = QuickAbort;
+ int saveerrors = Errors;
+
+ if (!bitset(MF_VALID, map->map_mflags))
+ return false;
+
+ /* better safe than sorry... */
+ if (bitset(MF_OPEN, map->map_mflags))
+ return true;
+
+ /* Don't send a map open error out via SMTP */
+ if ((OnlyOneError || QuickAbort) &&
+ (OpMode == MD_SMTP || OpMode == MD_DAEMON))
+ {
+ restore = true;
+ HoldErrs = true;
+ QuickAbort = false;
+ }
+
+ errno = 0;
+ if (map->map_class->map_open(map, O_RDONLY))
+ {
+ if (tTd(38, 4))
+ sm_dprintf("openmap()\t%s:%s %s: valid\n",
+ map->map_class->map_cname == NULL ? "NULL" :
+ map->map_class->map_cname,
+ map->map_mname == NULL ? "NULL" :
+ map->map_mname,
+ map->map_file == NULL ? "NULL" :
+ map->map_file);
+ map->map_mflags |= MF_OPEN;
+ map->map_pid = CurrentPid;
+ }
+ else
+ {
+ if (tTd(38, 4))
+ sm_dprintf("openmap()\t%s:%s %s: invalid%s%s\n",
+ map->map_class->map_cname == NULL ? "NULL" :
+ map->map_class->map_cname,
+ map->map_mname == NULL ? "NULL" :
+ map->map_mname,
+ map->map_file == NULL ? "NULL" :
+ map->map_file,
+ errno == 0 ? "" : ": ",
+ errno == 0 ? "" : sm_errstring(errno));
+ if (!bitset(MF_OPTIONAL, map->map_mflags))
+ {
+ extern MAPCLASS BogusMapClass;
+
+ map->map_orgclass = map->map_class;
+ map->map_class = &BogusMapClass;
+ map->map_mflags |= MF_OPEN|MF_OPENBOGUS;
+ map->map_pid = CurrentPid;
+ }
+ else
+ {
+ /* don't try again */
+ map->map_mflags &= ~MF_VALID;
+ }
+ }
+
+ if (restore)
+ {
+ Errors = saveerrors;
+ HoldErrs = savehold;
+ QuickAbort = savequick;
+ }
+
+ return bitset(MF_OPEN, map->map_mflags);
+}
+/*
+** CLOSEMAPS -- close all open maps opened by the current pid.
+**
+** Parameters:
+** bogus -- only close bogus maps.
+**
+** Returns:
+** none.
+*/
+
+void
+closemaps(bogus)
+ bool bogus;
+{
+ stabapply(map_close, bogus);
+}
+/*
+** MAP_CLOSE -- close a map opened by the current pid.
+**
+** Parameters:
+** s -- STAB entry: if map: try to close
+** bogus -- only close bogus maps or MCF_NOTPERSIST maps.
+**
+** Returns:
+** none.
+*/
+
+/* ARGSUSED1 */
+static void
+map_close(s, bogus)
+ register STAB *s;
+ int bogus; /* int because of stabapply(), used as bool */
+{
+ MAP *map;
+ extern MAPCLASS BogusMapClass;
+
+ if (s->s_symtype != ST_MAP)
+ return;
+
+ map = &s->s_map;
+
+ /*
+ ** close the map iff:
+ ** it is valid and open and opened by this process
+ ** and (!bogus or it's a bogus map or it is not persistent)
+ ** negate this: return iff
+ ** it is not valid or it is not open or not opened by this process
+ ** or (bogus and it's not a bogus map and it's not not-persistent)
+ */
+
+ if (!bitset(MF_VALID, map->map_mflags) ||
+ !bitset(MF_OPEN, map->map_mflags) ||
+ bitset(MF_CLOSING, map->map_mflags) ||
+ map->map_pid != CurrentPid ||
+ (bogus && map->map_class != &BogusMapClass &&
+ !bitset(MCF_NOTPERSIST, map->map_class->map_cflags)))
+ return;
+
+ if (map->map_class == &BogusMapClass && map->map_orgclass != NULL &&
+ map->map_orgclass != &BogusMapClass)
+ map->map_class = map->map_orgclass;
+ if (tTd(38, 5))
+ sm_dprintf("closemaps: closing %s (%s)\n",
+ map->map_mname == NULL ? "NULL" : map->map_mname,
+ map->map_file == NULL ? "NULL" : map->map_file);
+
+ if (!bitset(MF_OPENBOGUS, map->map_mflags))
+ {
+ map->map_mflags |= MF_CLOSING;
+ map->map_class->map_close(map);
+ }
+ map->map_mflags &= ~(MF_OPEN|MF_WRITABLE|MF_OPENBOGUS|MF_CLOSING);
+}
+/*
+** GETCANONNAME -- look up name using service switch
+**
+** Parameters:
+** host -- the host name to look up.
+** hbsize -- the size of the host buffer.
+** trymx -- if set, try MX records.
+** pttl -- pointer to return TTL (can be NULL).
+**
+** Returns:
+** true -- if the host was found.
+** false -- otherwise.
+*/
+
+bool
+getcanonname(host, hbsize, trymx, pttl)
+ char *host;
+ int hbsize;
+ bool trymx;
+ int *pttl;
+{
+ int nmaps;
+ int mapno;
+ bool found = false;
+ bool got_tempfail = false;
+ auto int status;
+ char *maptype[MAXMAPSTACK];
+ short mapreturn[MAXMAPACTIONS];
+#if defined(SUN_EXTENSIONS) && defined(SUN_INIT_DOMAIN)
+ bool should_try_nis_domain = false;
+ static char *nis_domain = NULL;
+ extern char *sun_init_domain();
+#endif
+
+ nmaps = switch_map_find("hosts", maptype, mapreturn);
+ if (pttl != 0)
+ *pttl = SM_DEFAULT_TTL;
+ for (mapno = 0; mapno < nmaps; mapno++)
+ {
+ int i;
+
+ if (tTd(38, 20))
+ sm_dprintf("getcanonname(%s), trying %s\n",
+ host, maptype[mapno]);
+ if (strcmp("files", maptype[mapno]) == 0)
+ {
+ found = text_getcanonname(host, hbsize, &status);
+ }
+#if NIS
+ else if (strcmp("nis", maptype[mapno]) == 0)
+ {
+ found = nis_getcanonname(host, hbsize, &status);
+# if defined(SUN_EXTENSIONS) && defined(SUN_INIT_DOMAIN)
+ if (nis_domain == NULL)
+ nis_domain = sun_init_domain();
+# endif
+ }
+#endif /* NIS */
+#if NISPLUS
+ else if (strcmp("nisplus", maptype[mapno]) == 0)
+ {
+ found = nisplus_getcanonname(host, hbsize, &status);
+# if defined(SUN_EXTENSIONS) && defined(SUN_INIT_DOMAIN)
+ if (nis_domain == NULL)
+ nis_domain = sun_init_domain();
+# endif
+ }
+#endif /* NISPLUS */
+#if NAMED_BIND
+ else if (strcmp("dns", maptype[mapno]) == 0)
+ {
+ found = dns_getcanonname(host, hbsize, trymx, &status, pttl);
+ }
+#endif /* NAMED_BIND */
+#if NETINFO
+ else if (strcmp("netinfo", maptype[mapno]) == 0)
+ {
+ found = ni_getcanonname(host, hbsize, &status);
+ }
+#endif /* NETINFO */
+ else
+ {
+ found = false;
+ status = EX_UNAVAILABLE;
+ }
+
+ /*
+ ** Heuristic: if $m is not set, we are running during system
+ ** startup. In this case, when a name is apparently found
+ ** but has no dot, treat is as not found. This avoids
+ ** problems if /etc/hosts has no FQDN but is listed first
+ ** in the service switch.
+ */
+
+ if (found &&
+ (macvalue('m', CurEnv) != NULL || strchr(host, '.') != NULL))
+ break;
+
+#if defined(SUN_EXTENSIONS) && defined(SUN_INIT_DOMAIN)
+ if (found)
+ should_try_nis_domain = true;
+ /* but don't break, as we need to try all methods first */
+#endif
+
+ /* see if we should continue */
+ if (status == EX_TEMPFAIL)
+ {
+ i = MA_TRYAGAIN;
+ got_tempfail = true;
+ }
+ else if (status == EX_NOTFOUND)
+ i = MA_NOTFOUND;
+ else
+ i = MA_UNAVAIL;
+ if (bitset(1 << mapno, mapreturn[i]))
+ break;
+ }
+
+ if (found)
+ {
+ char *d;
+
+ if (tTd(38, 20))
+ sm_dprintf("getcanonname(%s), found\n", host);
+
+ /*
+ ** If returned name is still single token, compensate
+ ** by tagging on $m. This is because some sites set
+ ** up their DNS or NIS databases wrong.
+ */
+
+ if ((d = strchr(host, '.')) == NULL || d[1] == '\0')
+ {
+ d = macvalue('m', CurEnv);
+ if (d != NULL &&
+ hbsize > (int) (strlen(host) + strlen(d) + 1))
+ {
+ if (host[strlen(host) - 1] != '.')
+ (void) sm_strlcat2(host, ".", d,
+ hbsize);
+ else
+ (void) sm_strlcat(host, d, hbsize);
+ }
+ else
+ {
+#if defined(SUN_EXTENSIONS) && defined(SUN_INIT_DOMAIN)
+ if (VendorCode == VENDOR_SUN &&
+ should_try_nis_domain)
+ {
+ goto try_nis_domain;
+ }
+#endif
+ return false;
+ }
+ }
+ return true;
+ }
+
+#if defined(SUN_EXTENSIONS) && defined(SUN_INIT_DOMAIN)
+ if (VendorCode == VENDOR_SUN && should_try_nis_domain)
+ {
+try_nis_domain:
+ if (nis_domain != NULL &&
+ strlen(nis_domain) + strlen(host) + 1 < hbsize)
+ {
+ (void) sm_strlcat2(host, ".", nis_domain, hbsize);
+ return true;
+ }
+ }
+#endif
+
+ if (tTd(38, 20))
+ sm_dprintf("getcanonname(%s), failed, status=%d\n", host,
+ status);
+
+ if (got_tempfail)
+ SM_SET_H_ERRNO(TRY_AGAIN);
+ else
+ SM_SET_H_ERRNO(HOST_NOT_FOUND);
+
+ return false;
+}
+/*
+** EXTRACT_CANONNAME -- extract canonical name from /etc/hosts entry
+**
+** Parameters:
+** name -- the name against which to match.
+** dot -- where to reinsert '.' to get FQDN
+** line -- the /etc/hosts line.
+** cbuf -- the location to store the result.
+** cbuflen -- the size of cbuf.
+**
+** Returns:
+** true -- if the line matched the desired name.
+** false -- otherwise.
+*/
+
+static bool
+extract_canonname(name, dot, line, cbuf, cbuflen)
+ char *name;
+ char *dot;
+ char *line;
+ char cbuf[];
+ int cbuflen;
+{
+ int i;
+ char *p;
+ bool found = false;
+
+ cbuf[0] = '\0';
+ if (line[0] == '#')
+ return false;
+
+ for (i = 1; ; i++)
+ {
+ char nbuf[MAXNAME + 1];
+
+ p = get_column(line, i, '\0', nbuf, sizeof nbuf);
+ if (p == NULL)
+ break;
+ if (*p == '\0')
+ continue;
+ if (cbuf[0] == '\0' ||
+ (strchr(cbuf, '.') == NULL && strchr(p, '.') != NULL))
+ {
+ (void) sm_strlcpy(cbuf, p, cbuflen);
+ }
+ if (sm_strcasecmp(name, p) == 0)
+ found = true;
+ else if (dot != NULL)
+ {
+ /* try looking for the FQDN as well */
+ *dot = '.';
+ if (sm_strcasecmp(name, p) == 0)
+ found = true;
+ *dot = '\0';
+ }
+ }
+ if (found && strchr(cbuf, '.') == NULL)
+ {
+ /* try to add a domain on the end of the name */
+ char *domain = macvalue('m', CurEnv);
+
+ if (domain != NULL &&
+ strlen(domain) + (i = strlen(cbuf)) + 1 < (size_t) cbuflen)
+ {
+ p = &cbuf[i];
+ *p++ = '.';
+ (void) sm_strlcpy(p, domain, cbuflen - i - 1);
+ }
+ }
+ return found;
+}
+
+/*
+** DNS modules
+*/
+
+#if NAMED_BIND
+# if DNSMAP
+
+# include "sm_resolve.h"
+# if NETINET || NETINET6
+# include <arpa/inet.h>
+# endif /* NETINET || NETINET6 */
+
+/*
+** DNS_MAP_OPEN -- stub to check proper value for dns map type
+*/
+
+bool
+dns_map_open(map, mode)
+ MAP *map;
+ int mode;
+{
+ if (tTd(38,2))
+ sm_dprintf("dns_map_open(%s, %d)\n", map->map_mname, mode);
+
+ mode &= O_ACCMODE;
+ if (mode != O_RDONLY)
+ {
+ /* issue a pseudo-error message */
+ errno = SM_EMAPCANTWRITE;
+ return false;
+ }
+ return true;
+}
+
+/*
+** DNS_MAP_PARSEARGS -- parse dns map definition args.
+**
+** Parameters:
+** map -- pointer to MAP
+** args -- pointer to the args on the config line.
+**
+** Returns:
+** true -- if everything parsed OK.
+** false -- otherwise.
+*/
+
+# if _FFR_DNSMAP_MULTILIMIT
+# if !_FFR_DNSMAP_MULTI
+ ERROR README: You must define _FFR_DNSMAP_MULTI to use _FFR_DNSMAP_MULTILIMIT
+# endif /* ! _FFR_DNSMAP_MULTI */
+# endif /* _FFR_DNSMAP_MULTILIMIT */
+
+# if _FFR_DNSMAP_MULTI
+# if _FFR_DNSMAP_MULTILIMIT
+# define map_sizelimit map_lockfd /* overload field */
+# endif /* _FFR_DNSMAP_MULTILIMIT */
+# endif /* _FFR_DNSMAP_MULTI */
+
+struct dns_map
+{
+ int dns_m_type;
+};
+
+bool
+dns_map_parseargs(map,args)
+ MAP *map;
+ char *args;
+{
+ register char *p = args;
+ struct dns_map *map_p;
+
+ map_p = (struct dns_map *) xalloc(sizeof *map_p);
+ map_p->dns_m_type = -1;
+ map->map_mflags |= MF_TRY0NULL|MF_TRY1NULL;
+
+ for (;;)
+ {
+ while (isascii(*p) && isspace(*p))
+ p++;
+ if (*p != '-')
+ break;
+ switch (*++p)
+ {
+ case 'N':
+ map->map_mflags |= MF_INCLNULL;
+ map->map_mflags &= ~MF_TRY0NULL;
+ break;
+
+ case 'O':
+ map->map_mflags &= ~MF_TRY1NULL;
+ break;
+
+ case 'o':
+ map->map_mflags |= MF_OPTIONAL;
+ break;
+
+ case 'f':
+ map->map_mflags |= MF_NOFOLDCASE;
+ break;
+
+ case 'm':
+ map->map_mflags |= MF_MATCHONLY;
+ break;
+
+ case 'A':
+ map->map_mflags |= MF_APPEND;
+ break;
+
+ case 'q':
+ map->map_mflags |= MF_KEEPQUOTES;
+ break;
+
+ case 't':
+ map->map_mflags |= MF_NODEFER;
+ break;
+
+ case 'a':
+ map->map_app = ++p;
+ break;
+
+ case 'T':
+ map->map_tapp = ++p;
+ break;
+
+ case 'd':
+ {
+ char *h;
+
+ ++p;
+ h = strchr(p, ' ');
+ if (h != NULL)
+ *h = '\0';
+ map->map_timeout = convtime(p, 's');
+ if (h != NULL)
+ *h = ' ';
+ }
+ break;
+
+ case 'r':
+ while (isascii(*++p) && isspace(*p))
+ continue;
+ map->map_retry = atoi(p);
+ break;
+
+# if _FFR_DNSMAP_MULTI
+ case 'z':
+ if (*++p != '\\')
+ map->map_coldelim = *p;
+ else
+ {
+ switch (*++p)
+ {
+ case 'n':
+ map->map_coldelim = '\n';
+ break;
+
+ case 't':
+ map->map_coldelim = '\t';
+ break;
+
+ default:
+ map->map_coldelim = '\\';
+ }
+ }
+ break;
+
+# if _FFR_DNSMAP_MULTILIMIT
+ case 'Z':
+ while (isascii(*++p) && isspace(*p))
+ continue;
+ map->map_sizelimit = atoi(p);
+ break;
+# endif /* _FFR_DNSMAP_MULTILIMIT */
+# endif /* _FFR_DNSMAP_MULTI */
+
+ /* Start of dns_map specific args */
+ case 'R': /* search field */
+ {
+ char *h;
+
+ while (isascii(*++p) && isspace(*p))
+ continue;
+ h = strchr(p, ' ');
+ if (h != NULL)
+ *h = '\0';
+ map_p->dns_m_type = dns_string_to_type(p);
+ if (h != NULL)
+ *h = ' ';
+ if (map_p->dns_m_type < 0)
+ syserr("dns map %s: wrong type %s",
+ map->map_mname, p);
+ }
+ break;
+
+# if _FFR_DNSMAP_BASE
+ case 'B': /* base domain */
+ {
+ char *h;
+
+ while (isascii(*++p) && isspace(*p))
+ continue;
+ h = strchr(p, ' ');
+ if (h != NULL)
+ *h = '\0';
+
+ /*
+ ** slight abuse of map->map_file; it isn't
+ ** used otherwise in this map type.
+ */
+
+ map->map_file = newstr(p);
+ if (h != NULL)
+ *h = ' ';
+ }
+ break;
+# endif /* _FFR_DNSMAP_BASE */
+
+ }
+ while (*p != '\0' && !(isascii(*p) && isspace(*p)))
+ p++;
+ if (*p != '\0')
+ *p++ = '\0';
+ }
+ if (map_p->dns_m_type < 0)
+ syserr("dns map %s: missing -R type", map->map_mname);
+ if (map->map_app != NULL)
+ map->map_app = newstr(map->map_app);
+ if (map->map_tapp != NULL)
+ map->map_tapp = newstr(map->map_tapp);
+
+ /*
+ ** Assumption: assert(sizeof int <= sizeof(ARBPTR_T));
+ ** Even if this assumption is wrong, we use only one byte,
+ ** so it doesn't really matter.
+ */
+
+ map->map_db1 = (ARBPTR_T) map_p;
+ return true;
+}
+
+/*
+** DNS_MAP_LOOKUP -- perform dns map lookup.
+**
+** Parameters:
+** map -- pointer to MAP
+** name -- name to lookup
+** av -- arguments to interpolate into buf.
+** statp -- pointer to status (EX_)
+**
+** Returns:
+** result of lookup if succeeded.
+** NULL -- otherwise.
+*/
+
+char *
+dns_map_lookup(map, name, av, statp)
+ MAP *map;
+ char *name;
+ char **av;
+ int *statp;
+{
+# if _FFR_DNSMAP_MULTI
+# if _FFR_DNSMAP_MULTILIMIT
+ int resnum = 0;
+# endif /* _FFR_DNSMAP_MULTILIMIT */
+# endif /* _FFR_DNSMAP_MULTI */
+ char *vp = NULL, *result = NULL;
+ size_t vsize;
+ struct dns_map *map_p;
+ RESOURCE_RECORD_T *rr = NULL;
+ DNS_REPLY_T *r = NULL;
+# if NETINET6
+ static char buf6[INET6_ADDRSTRLEN];
+# endif /* NETINET6 */
+
+ if (tTd(38, 20))
+ sm_dprintf("dns_map_lookup(%s, %s)\n",
+ map->map_mname, name);
+
+ map_p = (struct dns_map *)(map->map_db1);
+# if _FFR_DNSMAP_BASE
+ if (map->map_file != NULL && *map->map_file != '\0')
+ {
+ size_t len;
+ char *appdomain;
+
+ len = strlen(map->map_file) + strlen(name) + 2;
+ appdomain = (char *) sm_malloc(len);
+ if (appdomain == NULL)
+ {
+ *statp = EX_UNAVAILABLE;
+ return NULL;
+ }
+ (void) sm_strlcpyn(appdomain, len, 3, name, ".", map->map_file);
+ r = dns_lookup_int(appdomain, C_IN, map_p->dns_m_type,
+ map->map_timeout, map->map_retry);
+ sm_free(appdomain);
+ }
+ else
+# endif /* _FFR_DNSMAP_BASE */
+ {
+ r = dns_lookup_int(name, C_IN, map_p->dns_m_type,
+ map->map_timeout, map->map_retry);
+ }
+
+ if (r == NULL)
+ {
+ result = NULL;
+ if (h_errno == TRY_AGAIN || transienterror(errno))
+ *statp = EX_TEMPFAIL;
+ else
+ *statp = EX_NOTFOUND;
+ goto cleanup;
+ }
+ *statp = EX_OK;
+ for (rr = r->dns_r_head; rr != NULL; rr = rr->rr_next)
+ {
+ char *type = NULL;
+ char *value = NULL;
+
+ switch (rr->rr_type)
+ {
+ case T_NS:
+ type = "T_NS";
+ value = rr->rr_u.rr_txt;
+ break;
+ case T_CNAME:
+ type = "T_CNAME";
+ value = rr->rr_u.rr_txt;
+ break;
+ case T_AFSDB:
+ type = "T_AFSDB";
+ value = rr->rr_u.rr_mx->mx_r_domain;
+ break;
+ case T_SRV:
+ type = "T_SRV";
+ value = rr->rr_u.rr_srv->srv_r_target;
+ break;
+ case T_PTR:
+ type = "T_PTR";
+ value = rr->rr_u.rr_txt;
+ break;
+ case T_TXT:
+ type = "T_TXT";
+ value = rr->rr_u.rr_txt;
+ break;
+ case T_MX:
+ type = "T_MX";
+ value = rr->rr_u.rr_mx->mx_r_domain;
+ break;
+# if NETINET
+ case T_A:
+ type = "T_A";
+ value = inet_ntoa(*(rr->rr_u.rr_a));
+ break;
+# endif /* NETINET */
+# if NETINET6
+ case T_AAAA:
+ type = "T_AAAA";
+ value = anynet_ntop(rr->rr_u.rr_aaaa, buf6,
+ sizeof buf6);
+ break;
+# endif /* NETINET6 */
+ }
+
+ (void) strreplnonprt(value, 'X');
+ if (map_p->dns_m_type != rr->rr_type)
+ {
+ if (tTd(38, 40))
+ sm_dprintf("\tskipping type %s (%d) value %s\n",
+ type != NULL ? type : "<UNKNOWN>",
+ rr->rr_type,
+ value != NULL ? value : "<NO VALUE>");
+ continue;
+ }
+
+# if NETINET6
+ if (rr->rr_type == T_AAAA && value == NULL)
+ {
+ result = NULL;
+ *statp = EX_DATAERR;
+ if (tTd(38, 40))
+ sm_dprintf("\tbad T_AAAA conversion\n");
+ goto cleanup;
+ }
+# endif /* NETINET6 */
+ if (tTd(38, 40))
+ sm_dprintf("\tfound type %s (%d) value %s\n",
+ type != NULL ? type : "<UNKNOWN>",
+ rr->rr_type,
+ value != NULL ? value : "<NO VALUE>");
+# if _FFR_DNSMAP_MULTI
+ if (value != NULL &&
+ (map->map_coldelim == '\0' ||
+# if _FFR_DNSMAP_MULTILIMIT
+ map->map_sizelimit == 1 ||
+# endif /* _FFR_DNSMAP_MULTILIMIT */
+ bitset(MF_MATCHONLY, map->map_mflags)))
+ {
+ /* Only care about the first match */
+ vp = newstr(value);
+ break;
+ }
+ else if (vp == NULL)
+ {
+ /* First result */
+ vp = newstr(value);
+ }
+ else
+ {
+ /* concatenate the results */
+ int sz;
+ char *new;
+
+ sz = strlen(vp) + strlen(value) + 2;
+ new = xalloc(sz);
+ (void) sm_snprintf(new, sz, "%s%c%s",
+ vp, map->map_coldelim, value);
+ sm_free(vp);
+ vp = new;
+# if _FFR_DNSMAP_MULTILIMIT
+ if (map->map_sizelimit > 0 &&
+ ++resnum >= map->map_sizelimit)
+ break;
+# endif /* _FFR_DNSMAP_MULTILIMIT */
+ }
+# else /* _FFR_DNSMAP_MULTI */
+ vp = value;
+ break;
+# endif /* _FFR_DNSMAP_MULTI */
+ }
+ if (vp == NULL)
+ {
+ result = NULL;
+ *statp = EX_NOTFOUND;
+ if (tTd(38, 40))
+ sm_dprintf("\tno match found\n");
+ goto cleanup;
+ }
+
+# if _FFR_DNSMAP_MULTI
+ /* Cleanly truncate for rulesets */
+ truncate_at_delim(vp, PSBUFSIZE / 2, map->map_coldelim);
+# endif /* _FFR_DNSMAP_MULTI */
+
+ vsize = strlen(vp);
+
+ if (LogLevel > 9)
+ sm_syslog(LOG_INFO, CurEnv->e_id, "dns %.100s => %s",
+ name, vp);
+ if (bitset(MF_MATCHONLY, map->map_mflags))
+ result = map_rewrite(map, name, strlen(name), NULL);
+ else
+ result = map_rewrite(map, vp, vsize, av);
+
+ cleanup:
+# if _FFR_DNSMAP_MULTI
+ if (vp != NULL)
+ sm_free(vp);
+# endif /* _FFR_DNSMAP_MULTI */
+ if (r != NULL)
+ dns_free_data(r);
+ return result;
+}
+# endif /* DNSMAP */
+#endif /* NAMED_BIND */
+
+/*
+** NDBM modules
+*/
+
+#if NDBM
+
+/*
+** NDBM_MAP_OPEN -- DBM-style map open
+*/
+
+bool
+ndbm_map_open(map, mode)
+ MAP *map;
+ int mode;
+{
+ register DBM *dbm;
+ int save_errno;
+ int dfd;
+ int pfd;
+ long sff;
+ int ret;
+ int smode = S_IREAD;
+ char dirfile[MAXPATHLEN];
+ char pagfile[MAXPATHLEN];
+ struct stat st;
+ struct stat std, stp;
+
+ if (tTd(38, 2))
+ sm_dprintf("ndbm_map_open(%s, %s, %d)\n",
+ map->map_mname, map->map_file, mode);
+ map->map_lockfd = -1;
+ mode &= O_ACCMODE;
+
+ /* do initial file and directory checks */
+ if (sm_strlcpyn(dirfile, sizeof dirfile, 2,
+ map->map_file, ".dir") >= sizeof dirfile ||
+ sm_strlcpyn(pagfile, sizeof pagfile, 2,
+ map->map_file, ".pag") >= sizeof pagfile)
+ {
+ errno = 0;
+ if (!bitset(MF_OPTIONAL, map->map_mflags))
+ syserr("dbm map \"%s\": map file %s name too long",
+ map->map_mname, map->map_file);
+ return false;
+ }
+ sff = SFF_ROOTOK|SFF_REGONLY;
+ if (mode == O_RDWR)
+ {
+ sff |= SFF_CREAT;
+ if (!bitnset(DBS_WRITEMAPTOSYMLINK, DontBlameSendmail))
+ sff |= SFF_NOSLINK;
+ if (!bitnset(DBS_WRITEMAPTOHARDLINK, DontBlameSendmail))
+ sff |= SFF_NOHLINK;
+ smode = S_IWRITE;
+ }
+ else
+ {
+ if (!bitnset(DBS_LINKEDMAPINWRITABLEDIR, DontBlameSendmail))
+ sff |= SFF_NOWLINK;
+ }
+ if (!bitnset(DBS_MAPINUNSAFEDIRPATH, DontBlameSendmail))
+ sff |= SFF_SAFEDIRPATH;
+ ret = safefile(dirfile, RunAsUid, RunAsGid, RunAsUserName,
+ sff, smode, &std);
+ if (ret == 0)
+ ret = safefile(pagfile, RunAsUid, RunAsGid, RunAsUserName,
+ sff, smode, &stp);
+
+ if (ret != 0)
+ {
+ char *prob = "unsafe";
+
+ /* cannot open this map */
+ if (ret == ENOENT)
+ prob = "missing";
+ if (tTd(38, 2))
+ sm_dprintf("\t%s map file: %d\n", prob, ret);
+ if (!bitset(MF_OPTIONAL, map->map_mflags))
+ syserr("dbm map \"%s\": %s map file %s",
+ map->map_mname, prob, map->map_file);
+ return false;
+ }
+ if (std.st_mode == ST_MODE_NOFILE)
+ mode |= O_CREAT|O_EXCL;
+
+# if LOCK_ON_OPEN
+ if (mode == O_RDONLY)
+ mode |= O_SHLOCK;
+ else
+ mode |= O_TRUNC|O_EXLOCK;
+# else /* LOCK_ON_OPEN */
+ if ((mode & O_ACCMODE) == O_RDWR)
+ {
+# if NOFTRUNCATE
+ /*
+ ** Warning: race condition. Try to lock the file as
+ ** quickly as possible after opening it.
+ ** This may also have security problems on some systems,
+ ** but there isn't anything we can do about it.
+ */
+
+ mode |= O_TRUNC;
+# else /* NOFTRUNCATE */
+ /*
+ ** This ugly code opens the map without truncating it,
+ ** locks the file, then truncates it. Necessary to
+ ** avoid race conditions.
+ */
+
+ int dirfd;
+ int pagfd;
+ long sff = SFF_CREAT|SFF_OPENASROOT;
+
+ if (!bitnset(DBS_WRITEMAPTOSYMLINK, DontBlameSendmail))
+ sff |= SFF_NOSLINK;
+ if (!bitnset(DBS_WRITEMAPTOHARDLINK, DontBlameSendmail))
+ sff |= SFF_NOHLINK;
+
+ dirfd = safeopen(dirfile, mode, DBMMODE, sff);
+ pagfd = safeopen(pagfile, mode, DBMMODE, sff);
+
+ if (dirfd < 0 || pagfd < 0)
+ {
+ save_errno = errno;
+ if (dirfd >= 0)
+ (void) close(dirfd);
+ if (pagfd >= 0)
+ (void) close(pagfd);
+ errno = save_errno;
+ syserr("ndbm_map_open: cannot create database %s",
+ map->map_file);
+ return false;
+ }
+ if (ftruncate(dirfd, (off_t) 0) < 0 ||
+ ftruncate(pagfd, (off_t) 0) < 0)
+ {
+ save_errno = errno;
+ (void) close(dirfd);
+ (void) close(pagfd);
+ errno = save_errno;
+ syserr("ndbm_map_open: cannot truncate %s.{dir,pag}",
+ map->map_file);
+ return false;
+ }
+
+ /* if new file, get "before" bits for later filechanged check */
+ if (std.st_mode == ST_MODE_NOFILE &&
+ (fstat(dirfd, &std) < 0 || fstat(pagfd, &stp) < 0))
+ {
+ save_errno = errno;
+ (void) close(dirfd);
+ (void) close(pagfd);
+ errno = save_errno;
+ syserr("ndbm_map_open(%s.{dir,pag}): cannot fstat pre-opened file",
+ map->map_file);
+ return false;
+ }
+
+ /* have to save the lock for the duration (bletch) */
+ map->map_lockfd = dirfd;
+ (void) close(pagfd);
+
+ /* twiddle bits for dbm_open */
+ mode &= ~(O_CREAT|O_EXCL);
+# endif /* NOFTRUNCATE */
+ }
+# endif /* LOCK_ON_OPEN */
+
+ /* open the database */
+ dbm = dbm_open(map->map_file, mode, DBMMODE);
+ if (dbm == NULL)
+ {
+ save_errno = errno;
+ if (bitset(MF_ALIAS, map->map_mflags) &&
+ aliaswait(map, ".pag", false))
+ return true;
+# if !LOCK_ON_OPEN && !NOFTRUNCATE
+ if (map->map_lockfd >= 0)
+ (void) close(map->map_lockfd);
+# endif /* !LOCK_ON_OPEN && !NOFTRUNCATE */
+ errno = save_errno;
+ if (!bitset(MF_OPTIONAL, map->map_mflags))
+ syserr("Cannot open DBM database %s", map->map_file);
+ return false;
+ }
+ dfd = dbm_dirfno(dbm);
+ pfd = dbm_pagfno(dbm);
+ if (dfd == pfd)
+ {
+ /* heuristic: if files are linked, this is actually gdbm */
+ dbm_close(dbm);
+# if !LOCK_ON_OPEN && !NOFTRUNCATE
+ if (map->map_lockfd >= 0)
+ (void) close(map->map_lockfd);
+# endif /* !LOCK_ON_OPEN && !NOFTRUNCATE */
+ errno = 0;
+ syserr("dbm map \"%s\": cannot support GDBM",
+ map->map_mname);
+ return false;
+ }
+
+ if (filechanged(dirfile, dfd, &std) ||
+ filechanged(pagfile, pfd, &stp))
+ {
+ save_errno = errno;
+ dbm_close(dbm);
+# if !LOCK_ON_OPEN && !NOFTRUNCATE
+ if (map->map_lockfd >= 0)
+ (void) close(map->map_lockfd);
+# endif /* !LOCK_ON_OPEN && !NOFTRUNCATE */
+ errno = save_errno;
+ syserr("ndbm_map_open(%s): file changed after open",
+ map->map_file);
+ return false;
+ }
+
+ map->map_db1 = (ARBPTR_T) dbm;
+
+ /*
+ ** Need to set map_mtime before the call to aliaswait()
+ ** as aliaswait() will call map_lookup() which requires
+ ** map_mtime to be set
+ */
+
+ if (fstat(pfd, &st) >= 0)
+ map->map_mtime = st.st_mtime;
+
+ if (mode == O_RDONLY)
+ {
+# if LOCK_ON_OPEN
+ if (dfd >= 0)
+ (void) lockfile(dfd, map->map_file, ".dir", LOCK_UN);
+ if (pfd >= 0)
+ (void) lockfile(pfd, map->map_file, ".pag", LOCK_UN);
+# endif /* LOCK_ON_OPEN */
+ if (bitset(MF_ALIAS, map->map_mflags) &&
+ !aliaswait(map, ".pag", true))
+ return false;
+ }
+ else
+ {
+ map->map_mflags |= MF_LOCKED;
+ if (geteuid() == 0 && TrustedUid != 0)
+ {
+# if HASFCHOWN
+ if (fchown(dfd, TrustedUid, -1) < 0 ||
+ fchown(pfd, TrustedUid, -1) < 0)
+ {
+ int err = errno;
+
+ sm_syslog(LOG_ALERT, NOQID,
+ "ownership change on %s failed: %s",
+ map->map_file, sm_errstring(err));
+ message("050 ownership change on %s failed: %s",
+ map->map_file, sm_errstring(err));
+ }
+# else /* HASFCHOWN */
+ sm_syslog(LOG_ALERT, NOQID,
+ "no fchown(): cannot change ownership on %s",
+ map->map_file);
+ message("050 no fchown(): cannot change ownership on %s",
+ map->map_file);
+# endif /* HASFCHOWN */
+ }
+ }
+ return true;
+}
+
+
+/*
+** NDBM_MAP_LOOKUP -- look up a datum in a DBM-type map
+*/
+
+char *
+ndbm_map_lookup(map, name, av, statp)
+ MAP *map;
+ char *name;
+ char **av;
+ int *statp;
+{
+ datum key, val;
+ int dfd, pfd;
+ char keybuf[MAXNAME + 1];
+ struct stat stbuf;
+
+ if (tTd(38, 20))
+ sm_dprintf("ndbm_map_lookup(%s, %s)\n",
+ map->map_mname, name);
+
+ key.dptr = name;
+ key.dsize = strlen(name);
+ if (!bitset(MF_NOFOLDCASE, map->map_mflags))
+ {
+ if (key.dsize > sizeof keybuf - 1)
+ key.dsize = sizeof keybuf - 1;
+ memmove(keybuf, key.dptr, key.dsize);
+ keybuf[key.dsize] = '\0';
+ makelower(keybuf);
+ key.dptr = keybuf;
+ }
+lockdbm:
+ dfd = dbm_dirfno((DBM *) map->map_db1);
+ if (dfd >= 0 && !bitset(MF_LOCKED, map->map_mflags))
+ (void) lockfile(dfd, map->map_file, ".dir", LOCK_SH);
+ pfd = dbm_pagfno((DBM *) map->map_db1);
+ if (pfd < 0 || fstat(pfd, &stbuf) < 0 ||
+ stbuf.st_mtime > map->map_mtime)
+ {
+ /* Reopen the database to sync the cache */
+ int omode = bitset(map->map_mflags, MF_WRITABLE) ? O_RDWR
+ : O_RDONLY;
+
+ if (dfd >= 0 && !bitset(MF_LOCKED, map->map_mflags))
+ (void) lockfile(dfd, map->map_file, ".dir", LOCK_UN);
+ map->map_mflags |= MF_CLOSING;
+ map->map_class->map_close(map);
+ map->map_mflags &= ~(MF_OPEN|MF_WRITABLE|MF_CLOSING);
+ if (map->map_class->map_open(map, omode))
+ {
+ map->map_mflags |= MF_OPEN;
+ map->map_pid = CurrentPid;
+ if ((omode && O_ACCMODE) == O_RDWR)
+ map->map_mflags |= MF_WRITABLE;
+ goto lockdbm;
+ }
+ else
+ {
+ if (!bitset(MF_OPTIONAL, map->map_mflags))
+ {
+ extern MAPCLASS BogusMapClass;
+
+ *statp = EX_TEMPFAIL;
+ map->map_orgclass = map->map_class;
+ map->map_class = &BogusMapClass;
+ map->map_mflags |= MF_OPEN;
+ map->map_pid = CurrentPid;
+ syserr("Cannot reopen NDBM database %s",
+ map->map_file);
+ }
+ return NULL;
+ }
+ }
+ val.dptr = NULL;
+ if (bitset(MF_TRY0NULL, map->map_mflags))
+ {
+ val = dbm_fetch((DBM *) map->map_db1, key);
+ if (val.dptr != NULL)
+ map->map_mflags &= ~MF_TRY1NULL;
+ }
+ if (val.dptr == NULL && bitset(MF_TRY1NULL, map->map_mflags))
+ {
+ key.dsize++;
+ val = dbm_fetch((DBM *) map->map_db1, key);
+ if (val.dptr != NULL)
+ map->map_mflags &= ~MF_TRY0NULL;
+ }
+ if (dfd >= 0 && !bitset(MF_LOCKED, map->map_mflags))
+ (void) lockfile(dfd, map->map_file, ".dir", LOCK_UN);
+ if (val.dptr == NULL)
+ return NULL;
+ if (bitset(MF_MATCHONLY, map->map_mflags))
+ return map_rewrite(map, name, strlen(name), NULL);
+ else
+ return map_rewrite(map, val.dptr, val.dsize, av);
+}
+
+
+/*
+** NDBM_MAP_STORE -- store a datum in the database
+*/
+
+void
+ndbm_map_store(map, lhs, rhs)
+ register MAP *map;
+ char *lhs;
+ char *rhs;
+{
+ datum key;
+ datum data;
+ int status;
+ char keybuf[MAXNAME + 1];
+
+ if (tTd(38, 12))
+ sm_dprintf("ndbm_map_store(%s, %s, %s)\n",
+ map->map_mname, lhs, rhs);
+
+ key.dsize = strlen(lhs);
+ key.dptr = lhs;
+ if (!bitset(MF_NOFOLDCASE, map->map_mflags))
+ {
+ if (key.dsize > sizeof keybuf - 1)
+ key.dsize = sizeof keybuf - 1;
+ memmove(keybuf, key.dptr, key.dsize);
+ keybuf[key.dsize] = '\0';
+ makelower(keybuf);
+ key.dptr = keybuf;
+ }
+
+ data.dsize = strlen(rhs);
+ data.dptr = rhs;
+
+ if (bitset(MF_INCLNULL, map->map_mflags))
+ {
+ key.dsize++;
+ data.dsize++;
+ }
+
+ status = dbm_store((DBM *) map->map_db1, key, data, DBM_INSERT);
+ if (status > 0)
+ {
+ if (!bitset(MF_APPEND, map->map_mflags))
+ message("050 Warning: duplicate alias name %s", lhs);
+ else
+ {
+ static char *buf = NULL;
+ static int bufsiz = 0;
+ auto int xstat;
+ datum old;
+
+ old.dptr = ndbm_map_lookup(map, key.dptr,
+ (char **) NULL, &xstat);
+ if (old.dptr != NULL && *(char *) old.dptr != '\0')
+ {
+ old.dsize = strlen(old.dptr);
+ if (data.dsize + old.dsize + 2 > bufsiz)
+ {
+ if (buf != NULL)
+ (void) sm_free(buf);
+ bufsiz = data.dsize + old.dsize + 2;
+ buf = sm_pmalloc_x(bufsiz);
+ }
+ (void) sm_strlcpyn(buf, bufsiz, 3,
+ data.dptr, ",", old.dptr);
+ data.dsize = data.dsize + old.dsize + 1;
+ data.dptr = buf;
+ if (tTd(38, 9))
+ sm_dprintf("ndbm_map_store append=%s\n",
+ data.dptr);
+ }
+ }
+ status = dbm_store((DBM *) map->map_db1,
+ key, data, DBM_REPLACE);
+ }
+ if (status != 0)
+ syserr("readaliases: dbm put (%s): %d", lhs, status);
+}
+
+
+/*
+** NDBM_MAP_CLOSE -- close the database
+*/
+
+void
+ndbm_map_close(map)
+ register MAP *map;
+{
+ if (tTd(38, 9))
+ sm_dprintf("ndbm_map_close(%s, %s, %lx)\n",
+ map->map_mname, map->map_file, map->map_mflags);
+
+ if (bitset(MF_WRITABLE, map->map_mflags))
+ {
+# ifdef NDBM_YP_COMPAT
+ bool inclnull;
+ char buf[MAXHOSTNAMELEN];
+
+ inclnull = bitset(MF_INCLNULL, map->map_mflags);
+ map->map_mflags &= ~MF_INCLNULL;
+
+ if (strstr(map->map_file, "/yp/") != NULL)
+ {
+ long save_mflags = map->map_mflags;
+
+ map->map_mflags |= MF_NOFOLDCASE;
+
+ (void) sm_snprintf(buf, sizeof buf, "%010ld", curtime());
+ ndbm_map_store(map, "YP_LAST_MODIFIED", buf);
+
+ (void) gethostname(buf, sizeof buf);
+ ndbm_map_store(map, "YP_MASTER_NAME", buf);
+
+ map->map_mflags = save_mflags;
+ }
+
+ if (inclnull)
+ map->map_mflags |= MF_INCLNULL;
+# endif /* NDBM_YP_COMPAT */
+
+ /* write out the distinguished alias */
+ ndbm_map_store(map, "@", "@");
+ }
+ dbm_close((DBM *) map->map_db1);
+
+ /* release lock (if needed) */
+# if !LOCK_ON_OPEN
+ if (map->map_lockfd >= 0)
+ (void) close(map->map_lockfd);
+# endif /* !LOCK_ON_OPEN */
+}
+
+#endif /* NDBM */
+/*
+** NEWDB (Hash and BTree) Modules
+*/
+
+#if NEWDB
+
+/*
+** BT_MAP_OPEN, HASH_MAP_OPEN -- database open primitives.
+**
+** These do rather bizarre locking. If you can lock on open,
+** do that to avoid the condition of opening a database that
+** is being rebuilt. If you don't, we'll try to fake it, but
+** there will be a race condition. If opening for read-only,
+** we immediately release the lock to avoid freezing things up.
+** We really ought to hold the lock, but guarantee that we won't
+** be pokey about it. That's hard to do.
+*/
+
+/* these should be K line arguments */
+# if DB_VERSION_MAJOR < 2
+# define db_cachesize cachesize
+# define h_nelem nelem
+# ifndef DB_CACHE_SIZE
+# define DB_CACHE_SIZE (1024 * 1024) /* database memory cache size */
+# endif /* ! DB_CACHE_SIZE */
+# ifndef DB_HASH_NELEM
+# define DB_HASH_NELEM 4096 /* (starting) size of hash table */
+# endif /* ! DB_HASH_NELEM */
+# endif /* DB_VERSION_MAJOR < 2 */
+
+bool
+bt_map_open(map, mode)
+ MAP *map;
+ int mode;
+{
+# if DB_VERSION_MAJOR < 2
+ BTREEINFO btinfo;
+# endif /* DB_VERSION_MAJOR < 2 */
+# if DB_VERSION_MAJOR == 2
+ DB_INFO btinfo;
+# endif /* DB_VERSION_MAJOR == 2 */
+# if DB_VERSION_MAJOR > 2
+ void *btinfo = NULL;
+# endif /* DB_VERSION_MAJOR > 2 */
+
+ if (tTd(38, 2))
+ sm_dprintf("bt_map_open(%s, %s, %d)\n",
+ map->map_mname, map->map_file, mode);
+
+# if DB_VERSION_MAJOR < 3
+ memset(&btinfo, '\0', sizeof btinfo);
+# ifdef DB_CACHE_SIZE
+ btinfo.db_cachesize = DB_CACHE_SIZE;
+# endif /* DB_CACHE_SIZE */
+# endif /* DB_VERSION_MAJOR < 3 */
+
+ return db_map_open(map, mode, "btree", DB_BTREE, &btinfo);
+}
+
+bool
+hash_map_open(map, mode)
+ MAP *map;
+ int mode;
+{
+# if DB_VERSION_MAJOR < 2
+ HASHINFO hinfo;
+# endif /* DB_VERSION_MAJOR < 2 */
+# if DB_VERSION_MAJOR == 2
+ DB_INFO hinfo;
+# endif /* DB_VERSION_MAJOR == 2 */
+# if DB_VERSION_MAJOR > 2
+ void *hinfo = NULL;
+# endif /* DB_VERSION_MAJOR > 2 */
+
+ if (tTd(38, 2))
+ sm_dprintf("hash_map_open(%s, %s, %d)\n",
+ map->map_mname, map->map_file, mode);
+
+# if DB_VERSION_MAJOR < 3
+ memset(&hinfo, '\0', sizeof hinfo);
+# ifdef DB_HASH_NELEM
+ hinfo.h_nelem = DB_HASH_NELEM;
+# endif /* DB_HASH_NELEM */
+# ifdef DB_CACHE_SIZE
+ hinfo.db_cachesize = DB_CACHE_SIZE;
+# endif /* DB_CACHE_SIZE */
+# endif /* DB_VERSION_MAJOR < 3 */
+
+ return db_map_open(map, mode, "hash", DB_HASH, &hinfo);
+}
+
+static bool
+db_map_open(map, mode, mapclassname, dbtype, openinfo)
+ MAP *map;
+ int mode;
+ char *mapclassname;
+ DBTYPE dbtype;
+# if DB_VERSION_MAJOR < 2
+ const void *openinfo;
+# endif /* DB_VERSION_MAJOR < 2 */
+# if DB_VERSION_MAJOR == 2
+ DB_INFO *openinfo;
+# endif /* DB_VERSION_MAJOR == 2 */
+# if DB_VERSION_MAJOR > 2
+ void **openinfo;
+# endif /* DB_VERSION_MAJOR > 2 */
+{
+ DB *db = NULL;
+ int i;
+ int omode;
+ int smode = S_IREAD;
+ int fd;
+ long sff;
+ int save_errno;
+ struct stat st;
+ char buf[MAXPATHLEN];
+
+ /* do initial file and directory checks */
+ if (sm_strlcpy(buf, map->map_file, sizeof buf) >= sizeof buf)
+ {
+ errno = 0;
+ if (!bitset(MF_OPTIONAL, map->map_mflags))
+ syserr("map \"%s\": map file %s name too long",
+ map->map_mname, map->map_file);
+ return false;
+ }
+ i = strlen(buf);
+ if (i < 3 || strcmp(&buf[i - 3], ".db") != 0)
+ {
+ if (sm_strlcat(buf, ".db", sizeof buf) >= sizeof buf)
+ {
+ errno = 0;
+ if (!bitset(MF_OPTIONAL, map->map_mflags))
+ syserr("map \"%s\": map file %s name too long",
+ map->map_mname, map->map_file);
+ return false;
+ }
+ }
+
+ mode &= O_ACCMODE;
+ omode = mode;
+
+ sff = SFF_ROOTOK|SFF_REGONLY;
+ if (mode == O_RDWR)
+ {
+ sff |= SFF_CREAT;
+ if (!bitnset(DBS_WRITEMAPTOSYMLINK, DontBlameSendmail))
+ sff |= SFF_NOSLINK;
+ if (!bitnset(DBS_WRITEMAPTOHARDLINK, DontBlameSendmail))
+ sff |= SFF_NOHLINK;
+ smode = S_IWRITE;
+ }
+ else
+ {
+ if (!bitnset(DBS_LINKEDMAPINWRITABLEDIR, DontBlameSendmail))
+ sff |= SFF_NOWLINK;
+ }
+ if (!bitnset(DBS_MAPINUNSAFEDIRPATH, DontBlameSendmail))
+ sff |= SFF_SAFEDIRPATH;
+ i = safefile(buf, RunAsUid, RunAsGid, RunAsUserName, sff, smode, &st);
+
+ if (i != 0)
+ {
+ char *prob = "unsafe";
+
+ /* cannot open this map */
+ if (i == ENOENT)
+ prob = "missing";
+ if (tTd(38, 2))
+ sm_dprintf("\t%s map file: %s\n", prob, sm_errstring(i));
+ errno = i;
+ if (!bitset(MF_OPTIONAL, map->map_mflags))
+ syserr("%s map \"%s\": %s map file %s",
+ mapclassname, map->map_mname, prob, buf);
+ return false;
+ }
+ if (st.st_mode == ST_MODE_NOFILE)
+ omode |= O_CREAT|O_EXCL;
+
+ map->map_lockfd = -1;
+
+# if LOCK_ON_OPEN
+ if (mode == O_RDWR)
+ omode |= O_TRUNC|O_EXLOCK;
+ else
+ omode |= O_SHLOCK;
+# else /* LOCK_ON_OPEN */
+ /*
+ ** Pre-lock the file to avoid race conditions. In particular,
+ ** since dbopen returns NULL if the file is zero length, we
+ ** must have a locked instance around the dbopen.
+ */
+
+ fd = open(buf, omode, DBMMODE);
+ if (fd < 0)
+ {
+ if (!bitset(MF_OPTIONAL, map->map_mflags))
+ syserr("db_map_open: cannot pre-open database %s", buf);
+ return false;
+ }
+
+ /* make sure no baddies slipped in just before the open... */
+ if (filechanged(buf, fd, &st))
+ {
+ save_errno = errno;
+ (void) close(fd);
+ errno = save_errno;
+ syserr("db_map_open(%s): file changed after pre-open", buf);
+ return false;
+ }
+
+ /* if new file, get the "before" bits for later filechanged check */
+ if (st.st_mode == ST_MODE_NOFILE && fstat(fd, &st) < 0)
+ {
+ save_errno = errno;
+ (void) close(fd);
+ errno = save_errno;
+ syserr("db_map_open(%s): cannot fstat pre-opened file",
+ buf);
+ return false;
+ }
+
+ /* actually lock the pre-opened file */
+ if (!lockfile(fd, buf, NULL, mode == O_RDONLY ? LOCK_SH : LOCK_EX))
+ syserr("db_map_open: cannot lock %s", buf);
+
+ /* set up mode bits for dbopen */
+ if (mode == O_RDWR)
+ omode |= O_TRUNC;
+ omode &= ~(O_EXCL|O_CREAT);
+# endif /* LOCK_ON_OPEN */
+
+# if DB_VERSION_MAJOR < 2
+ db = dbopen(buf, omode, DBMMODE, dbtype, openinfo);
+# else /* DB_VERSION_MAJOR < 2 */
+ {
+ int flags = 0;
+# if DB_VERSION_MAJOR > 2
+ int ret;
+# endif /* DB_VERSION_MAJOR > 2 */
+
+ if (mode == O_RDONLY)
+ flags |= DB_RDONLY;
+ if (bitset(O_CREAT, omode))
+ flags |= DB_CREATE;
+ if (bitset(O_TRUNC, omode))
+ flags |= DB_TRUNCATE;
+ SM_DB_FLAG_ADD(flags);
+
+# if DB_VERSION_MAJOR > 2
+ ret = db_create(&db, NULL, 0);
+# ifdef DB_CACHE_SIZE
+ if (ret == 0 && db != NULL)
+ {
+ ret = db->set_cachesize(db, 0, DB_CACHE_SIZE, 0);
+ if (ret != 0)
+ {
+ (void) db->close(db, 0);
+ db = NULL;
+ }
+ }
+# endif /* DB_CACHE_SIZE */
+# ifdef DB_HASH_NELEM
+ if (dbtype == DB_HASH && ret == 0 && db != NULL)
+ {
+ ret = db->set_h_nelem(db, DB_HASH_NELEM);
+ if (ret != 0)
+ {
+ (void) db->close(db, 0);
+ db = NULL;
+ }
+ }
+# endif /* DB_HASH_NELEM */
+ if (ret == 0 && db != NULL)
+ {
+ ret = db->open(db,
+ DBTXN /* transaction for DB 4.1 */
+ buf, NULL, dbtype, flags, DBMMODE);
+ if (ret != 0)
+ {
+#ifdef DB_OLD_VERSION
+ if (ret == DB_OLD_VERSION)
+ ret = EINVAL;
+#endif /* DB_OLD_VERSION */
+ (void) db->close(db, 0);
+ db = NULL;
+ }
+ }
+ errno = ret;
+# else /* DB_VERSION_MAJOR > 2 */
+ errno = db_open(buf, dbtype, flags, DBMMODE,
+ NULL, openinfo, &db);
+# endif /* DB_VERSION_MAJOR > 2 */
+ }
+# endif /* DB_VERSION_MAJOR < 2 */
+ save_errno = errno;
+
+# if !LOCK_ON_OPEN
+ if (mode == O_RDWR)
+ map->map_lockfd = fd;
+ else
+ (void) close(fd);
+# endif /* !LOCK_ON_OPEN */
+
+ if (db == NULL)
+ {
+ if (mode == O_RDONLY && bitset(MF_ALIAS, map->map_mflags) &&
+ aliaswait(map, ".db", false))
+ return true;
+# if !LOCK_ON_OPEN
+ if (map->map_lockfd >= 0)
+ (void) close(map->map_lockfd);
+# endif /* !LOCK_ON_OPEN */
+ errno = save_errno;
+ if (!bitset(MF_OPTIONAL, map->map_mflags))
+ syserr("Cannot open %s database %s",
+ mapclassname, buf);
+ return false;
+ }
+
+# if DB_VERSION_MAJOR < 2
+ fd = db->fd(db);
+# else /* DB_VERSION_MAJOR < 2 */
+ fd = -1;
+ errno = db->fd(db, &fd);
+# endif /* DB_VERSION_MAJOR < 2 */
+ if (filechanged(buf, fd, &st))
+ {
+ save_errno = errno;
+# if DB_VERSION_MAJOR < 2
+ (void) db->close(db);
+# else /* DB_VERSION_MAJOR < 2 */
+ errno = db->close(db, 0);
+# endif /* DB_VERSION_MAJOR < 2 */
+# if !LOCK_ON_OPEN
+ if (map->map_lockfd >= 0)
+ (void) close(map->map_lockfd);
+# endif /* !LOCK_ON_OPEN */
+ errno = save_errno;
+ syserr("db_map_open(%s): file changed after open", buf);
+ return false;
+ }
+
+ if (mode == O_RDWR)
+ map->map_mflags |= MF_LOCKED;
+# if LOCK_ON_OPEN
+ if (fd >= 0 && mode == O_RDONLY)
+ {
+ (void) lockfile(fd, buf, NULL, LOCK_UN);
+ }
+# endif /* LOCK_ON_OPEN */
+
+ /* try to make sure that at least the database header is on disk */
+ if (mode == O_RDWR)
+ {
+ (void) db->sync(db, 0);
+ if (geteuid() == 0 && TrustedUid != 0)
+ {
+# if HASFCHOWN
+ if (fchown(fd, TrustedUid, -1) < 0)
+ {
+ int err = errno;
+
+ sm_syslog(LOG_ALERT, NOQID,
+ "ownership change on %s failed: %s",
+ buf, sm_errstring(err));
+ message("050 ownership change on %s failed: %s",
+ buf, sm_errstring(err));
+ }
+# else /* HASFCHOWN */
+ sm_syslog(LOG_ALERT, NOQID,
+ "no fchown(): cannot change ownership on %s",
+ map->map_file);
+ message("050 no fchown(): cannot change ownership on %s",
+ map->map_file);
+# endif /* HASFCHOWN */
+ }
+ }
+
+ map->map_db2 = (ARBPTR_T) db;
+
+ /*
+ ** Need to set map_mtime before the call to aliaswait()
+ ** as aliaswait() will call map_lookup() which requires
+ ** map_mtime to be set
+ */
+
+ if (fd >= 0 && fstat(fd, &st) >= 0)
+ map->map_mtime = st.st_mtime;
+
+ if (mode == O_RDONLY && bitset(MF_ALIAS, map->map_mflags) &&
+ !aliaswait(map, ".db", true))
+ return false;
+ return true;
+}
+
+
+/*
+** DB_MAP_LOOKUP -- look up a datum in a BTREE- or HASH-type map
+*/
+
+char *
+db_map_lookup(map, name, av, statp)
+ MAP *map;
+ char *name;
+ char **av;
+ int *statp;
+{
+ DBT key, val;
+ register DB *db = (DB *) map->map_db2;
+ int i;
+ int st;
+ int save_errno;
+ int fd;
+ struct stat stbuf;
+ char keybuf[MAXNAME + 1];
+ char buf[MAXPATHLEN];
+
+ memset(&key, '\0', sizeof key);
+ memset(&val, '\0', sizeof val);
+
+ if (tTd(38, 20))
+ sm_dprintf("db_map_lookup(%s, %s)\n",
+ map->map_mname, name);
+
+ if (sm_strlcpy(buf, map->map_file, sizeof buf) >= sizeof buf)
+ {
+ errno = 0;
+ if (!bitset(MF_OPTIONAL, map->map_mflags))
+ syserr("map \"%s\": map file %s name too long",
+ map->map_mname, map->map_file);
+ return NULL;
+ }
+ i = strlen(buf);
+ if (i > 3 && strcmp(&buf[i - 3], ".db") == 0)
+ buf[i - 3] = '\0';
+
+ key.size = strlen(name);
+ if (key.size > sizeof keybuf - 1)
+ key.size = sizeof keybuf - 1;
+ key.data = keybuf;
+ memmove(keybuf, name, key.size);
+ keybuf[key.size] = '\0';
+ if (!bitset(MF_NOFOLDCASE, map->map_mflags))
+ makelower(keybuf);
+ lockdb:
+# if DB_VERSION_MAJOR < 2
+ fd = db->fd(db);
+# else /* DB_VERSION_MAJOR < 2 */
+ fd = -1;
+ errno = db->fd(db, &fd);
+# endif /* DB_VERSION_MAJOR < 2 */
+ if (fd >= 0 && !bitset(MF_LOCKED, map->map_mflags))
+ (void) lockfile(fd, buf, ".db", LOCK_SH);
+ if (fd < 0 || fstat(fd, &stbuf) < 0 || stbuf.st_mtime > map->map_mtime)
+ {
+ /* Reopen the database to sync the cache */
+ int omode = bitset(map->map_mflags, MF_WRITABLE) ? O_RDWR
+ : O_RDONLY;
+
+ if (fd >= 0 && !bitset(MF_LOCKED, map->map_mflags))
+ (void) lockfile(fd, buf, ".db", LOCK_UN);
+ map->map_mflags |= MF_CLOSING;
+ map->map_class->map_close(map);
+ map->map_mflags &= ~(MF_OPEN|MF_WRITABLE|MF_CLOSING);
+ if (map->map_class->map_open(map, omode))
+ {
+ map->map_mflags |= MF_OPEN;
+ map->map_pid = CurrentPid;
+ if ((omode && O_ACCMODE) == O_RDWR)
+ map->map_mflags |= MF_WRITABLE;
+ db = (DB *) map->map_db2;
+ goto lockdb;
+ }
+ else
+ {
+ if (!bitset(MF_OPTIONAL, map->map_mflags))
+ {
+ extern MAPCLASS BogusMapClass;
+
+ *statp = EX_TEMPFAIL;
+ map->map_orgclass = map->map_class;
+ map->map_class = &BogusMapClass;
+ map->map_mflags |= MF_OPEN;
+ map->map_pid = CurrentPid;
+ syserr("Cannot reopen DB database %s",
+ map->map_file);
+ }
+ return NULL;
+ }
+ }
+
+ st = 1;
+ if (bitset(MF_TRY0NULL, map->map_mflags))
+ {
+# if DB_VERSION_MAJOR < 2
+ st = db->get(db, &key, &val, 0);
+# else /* DB_VERSION_MAJOR < 2 */
+ errno = db->get(db, NULL, &key, &val, 0);
+ switch (errno)
+ {
+ case DB_NOTFOUND:
+ case DB_KEYEMPTY:
+ st = 1;
+ break;
+
+ case 0:
+ st = 0;
+ break;
+
+ default:
+ st = -1;
+ break;
+ }
+# endif /* DB_VERSION_MAJOR < 2 */
+ if (st == 0)
+ map->map_mflags &= ~MF_TRY1NULL;
+ }
+ if (st != 0 && bitset(MF_TRY1NULL, map->map_mflags))
+ {
+ key.size++;
+# if DB_VERSION_MAJOR < 2
+ st = db->get(db, &key, &val, 0);
+# else /* DB_VERSION_MAJOR < 2 */
+ errno = db->get(db, NULL, &key, &val, 0);
+ switch (errno)
+ {
+ case DB_NOTFOUND:
+ case DB_KEYEMPTY:
+ st = 1;
+ break;
+
+ case 0:
+ st = 0;
+ break;
+
+ default:
+ st = -1;
+ break;
+ }
+# endif /* DB_VERSION_MAJOR < 2 */
+ if (st == 0)
+ map->map_mflags &= ~MF_TRY0NULL;
+ }
+ save_errno = errno;
+ if (fd >= 0 && !bitset(MF_LOCKED, map->map_mflags))
+ (void) lockfile(fd, buf, ".db", LOCK_UN);
+ if (st != 0)
+ {
+ errno = save_errno;
+ if (st < 0)
+ syserr("db_map_lookup: get (%s)", name);
+ return NULL;
+ }
+ if (bitset(MF_MATCHONLY, map->map_mflags))
+ return map_rewrite(map, name, strlen(name), NULL);
+ else
+ return map_rewrite(map, val.data, val.size, av);
+}
+
+
+/*
+** DB_MAP_STORE -- store a datum in the NEWDB database
+*/
+
+void
+db_map_store(map, lhs, rhs)
+ register MAP *map;
+ char *lhs;
+ char *rhs;
+{
+ int status;
+ DBT key;
+ DBT data;
+ register DB *db = map->map_db2;
+ char keybuf[MAXNAME + 1];
+
+ memset(&key, '\0', sizeof key);
+ memset(&data, '\0', sizeof data);
+
+ if (tTd(38, 12))
+ sm_dprintf("db_map_store(%s, %s, %s)\n",
+ map->map_mname, lhs, rhs);
+
+ key.size = strlen(lhs);
+ key.data = lhs;
+ if (!bitset(MF_NOFOLDCASE, map->map_mflags))
+ {
+ if (key.size > sizeof keybuf - 1)
+ key.size = sizeof keybuf - 1;
+ memmove(keybuf, key.data, key.size);
+ keybuf[key.size] = '\0';
+ makelower(keybuf);
+ key.data = keybuf;
+ }
+
+ data.size = strlen(rhs);
+ data.data = rhs;
+
+ if (bitset(MF_INCLNULL, map->map_mflags))
+ {
+ key.size++;
+ data.size++;
+ }
+
+# if DB_VERSION_MAJOR < 2
+ status = db->put(db, &key, &data, R_NOOVERWRITE);
+# else /* DB_VERSION_MAJOR < 2 */
+ errno = db->put(db, NULL, &key, &data, DB_NOOVERWRITE);
+ switch (errno)
+ {
+ case DB_KEYEXIST:
+ status = 1;
+ break;
+
+ case 0:
+ status = 0;
+ break;
+
+ default:
+ status = -1;
+ break;
+ }
+# endif /* DB_VERSION_MAJOR < 2 */
+ if (status > 0)
+ {
+ if (!bitset(MF_APPEND, map->map_mflags))
+ message("050 Warning: duplicate alias name %s", lhs);
+ else
+ {
+ static char *buf = NULL;
+ static int bufsiz = 0;
+ DBT old;
+
+ memset(&old, '\0', sizeof old);
+
+ old.data = db_map_lookup(map, key.data,
+ (char **) NULL, &status);
+ if (old.data != NULL)
+ {
+ old.size = strlen(old.data);
+ if (data.size + old.size + 2 > (size_t) bufsiz)
+ {
+ if (buf != NULL)
+ sm_free(buf);
+ bufsiz = data.size + old.size + 2;
+ buf = sm_pmalloc_x(bufsiz);
+ }
+ (void) sm_strlcpyn(buf, bufsiz, 3,
+ (char *) data.data, ",",
+ (char *) old.data);
+ data.size = data.size + old.size + 1;
+ data.data = buf;
+ if (tTd(38, 9))
+ sm_dprintf("db_map_store append=%s\n",
+ (char *) data.data);
+ }
+ }
+# if DB_VERSION_MAJOR < 2
+ status = db->put(db, &key, &data, 0);
+# else /* DB_VERSION_MAJOR < 2 */
+ status = errno = db->put(db, NULL, &key, &data, 0);
+# endif /* DB_VERSION_MAJOR < 2 */
+ }
+ if (status != 0)
+ syserr("readaliases: db put (%s)", lhs);
+}
+
+
+/*
+** DB_MAP_CLOSE -- add distinguished entries and close the database
+*/
+
+void
+db_map_close(map)
+ MAP *map;
+{
+ register DB *db = map->map_db2;
+
+ if (tTd(38, 9))
+ sm_dprintf("db_map_close(%s, %s, %lx)\n",
+ map->map_mname, map->map_file, map->map_mflags);
+
+ if (bitset(MF_WRITABLE, map->map_mflags))
+ {
+ /* write out the distinguished alias */
+ db_map_store(map, "@", "@");
+ }
+
+ (void) db->sync(db, 0);
+
+# if !LOCK_ON_OPEN
+ if (map->map_lockfd >= 0)
+ (void) close(map->map_lockfd);
+# endif /* !LOCK_ON_OPEN */
+
+# if DB_VERSION_MAJOR < 2
+ if (db->close(db) != 0)
+# else /* DB_VERSION_MAJOR < 2 */
+ /*
+ ** Berkeley DB can use internal shared memory
+ ** locking for its memory pool. Closing a map
+ ** opened by another process will interfere
+ ** with the shared memory and locks of the parent
+ ** process leaving things in a bad state.
+ */
+
+ /*
+ ** If this map was not opened by the current
+ ** process, do not close the map but recover
+ ** the file descriptor.
+ */
+
+ if (map->map_pid != CurrentPid)
+ {
+ int fd = -1;
+
+ errno = db->fd(db, &fd);
+ if (fd >= 0)
+ (void) close(fd);
+ return;
+ }
+
+ if ((errno = db->close(db, 0)) != 0)
+# endif /* DB_VERSION_MAJOR < 2 */
+ syserr("db_map_close(%s, %s, %lx): db close failure",
+ map->map_mname, map->map_file, map->map_mflags);
+}
+#endif /* NEWDB */
+/*
+** NIS Modules
+*/
+
+#if NIS
+
+# ifndef YPERR_BUSY
+# define YPERR_BUSY 16
+# endif /* ! YPERR_BUSY */
+
+/*
+** NIS_MAP_OPEN -- open DBM map
+*/
+
+bool
+nis_map_open(map, mode)
+ MAP *map;
+ int mode;
+{
+ int yperr;
+ register char *p;
+ auto char *vp;
+ auto int vsize;
+
+ if (tTd(38, 2))
+ sm_dprintf("nis_map_open(%s, %s, %d)\n",
+ map->map_mname, map->map_file, mode);
+
+ mode &= O_ACCMODE;
+ if (mode != O_RDONLY)
+ {
+ /* issue a pseudo-error message */
+ errno = SM_EMAPCANTWRITE;
+ return false;
+ }
+
+ p = strchr(map->map_file, '@');
+ if (p != NULL)
+ {
+ *p++ = '\0';
+ if (*p != '\0')
+ map->map_domain = p;
+ }
+
+ if (*map->map_file == '\0')
+ map->map_file = "mail.aliases";
+
+ if (map->map_domain == NULL)
+ {
+ yperr = yp_get_default_domain(&map->map_domain);
+ if (yperr != 0)
+ {
+ if (!bitset(MF_OPTIONAL, map->map_mflags))
+ syserr("451 4.3.5 NIS map %s specified, but NIS not running",
+ map->map_file);
+ return false;
+ }
+ }
+
+ /* check to see if this map actually exists */
+ vp = NULL;
+ yperr = yp_match(map->map_domain, map->map_file, "@", 1,
+ &vp, &vsize);
+ if (tTd(38, 10))
+ sm_dprintf("nis_map_open: yp_match(@, %s, %s) => %s\n",
+ map->map_domain, map->map_file, yperr_string(yperr));
+ if (vp != NULL)
+ sm_free(vp);
+
+ if (yperr == 0 || yperr == YPERR_KEY || yperr == YPERR_BUSY)
+ {
+ /*
+ ** We ought to be calling aliaswait() here if this is an
+ ** alias file, but powerful HP-UX NIS servers apparently
+ ** don't insert the @:@ token into the alias map when it
+ ** is rebuilt, so aliaswait() just hangs. I hate HP-UX.
+ */
+
+# if 0
+ if (!bitset(MF_ALIAS, map->map_mflags) ||
+ aliaswait(map, NULL, true))
+# endif /* 0 */
+ return true;
+ }
+
+ if (!bitset(MF_OPTIONAL, map->map_mflags))
+ {
+ syserr("451 4.3.5 Cannot bind to map %s in domain %s: %s",
+ map->map_file, map->map_domain, yperr_string(yperr));
+ }
+
+ return false;
+}
+
+
+/*
+** NIS_MAP_LOOKUP -- look up a datum in a NIS map
+*/
+
+/* ARGSUSED3 */
+char *
+nis_map_lookup(map, name, av, statp)
+ MAP *map;
+ char *name;
+ char **av;
+ int *statp;
+{
+ char *vp;
+ auto int vsize;
+ int buflen;
+ int yperr;
+ char keybuf[MAXNAME + 1];
+ char *SM_NONVOLATILE result = NULL;
+
+ if (tTd(38, 20))
+ sm_dprintf("nis_map_lookup(%s, %s)\n",
+ map->map_mname, name);
+
+ buflen = strlen(name);
+ if (buflen > sizeof keybuf - 1)
+ buflen = sizeof keybuf - 1;
+ memmove(keybuf, name, buflen);
+ keybuf[buflen] = '\0';
+ if (!bitset(MF_NOFOLDCASE, map->map_mflags))
+ makelower(keybuf);
+ yperr = YPERR_KEY;
+ vp = NULL;
+ if (bitset(MF_TRY0NULL, map->map_mflags))
+ {
+ yperr = yp_match(map->map_domain, map->map_file, keybuf, buflen,
+ &vp, &vsize);
+ if (yperr == 0)
+ map->map_mflags &= ~MF_TRY1NULL;
+ }
+ if (yperr == YPERR_KEY && bitset(MF_TRY1NULL, map->map_mflags))
+ {
+ SM_FREE_CLR(vp);
+ buflen++;
+ yperr = yp_match(map->map_domain, map->map_file, keybuf, buflen,
+ &vp, &vsize);
+ if (yperr == 0)
+ map->map_mflags &= ~MF_TRY0NULL;
+ }
+ if (yperr != 0)
+ {
+ if (yperr != YPERR_KEY && yperr != YPERR_BUSY)
+ map->map_mflags &= ~(MF_VALID|MF_OPEN);
+ if (vp != NULL)
+ sm_free(vp);
+ return NULL;
+ }
+ SM_TRY
+ if (bitset(MF_MATCHONLY, map->map_mflags))
+ result = map_rewrite(map, name, strlen(name), NULL);
+ else
+ result = map_rewrite(map, vp, vsize, av);
+ SM_FINALLY
+ if (vp != NULL)
+ sm_free(vp);
+ SM_END_TRY
+ return result;
+}
+
+
+/*
+** NIS_GETCANONNAME -- look up canonical name in NIS
+*/
+
+static bool
+nis_getcanonname(name, hbsize, statp)
+ char *name;
+ int hbsize;
+ int *statp;
+{
+ char *vp;
+ auto int vsize;
+ int keylen;
+ int yperr;
+ static bool try0null = true;
+ static bool try1null = true;
+ static char *yp_domain = NULL;
+ char host_record[MAXLINE];
+ char cbuf[MAXNAME];
+ char nbuf[MAXNAME + 1];
+
+ if (tTd(38, 20))
+ sm_dprintf("nis_getcanonname(%s)\n", name);
+
+ if (sm_strlcpy(nbuf, name, sizeof nbuf) >= sizeof nbuf)
+ {
+ *statp = EX_UNAVAILABLE;
+ return false;
+ }
+ (void) shorten_hostname(nbuf);
+ keylen = strlen(nbuf);
+
+ if (yp_domain == NULL)
+ (void) yp_get_default_domain(&yp_domain);
+ makelower(nbuf);
+ yperr = YPERR_KEY;
+ vp = NULL;
+ if (try0null)
+ {
+ yperr = yp_match(yp_domain, "hosts.byname", nbuf, keylen,
+ &vp, &vsize);
+ if (yperr == 0)
+ try1null = false;
+ }
+ if (yperr == YPERR_KEY && try1null)
+ {
+ SM_FREE_CLR(vp);
+ keylen++;
+ yperr = yp_match(yp_domain, "hosts.byname", nbuf, keylen,
+ &vp, &vsize);
+ if (yperr == 0)
+ try0null = false;
+ }
+ if (yperr != 0)
+ {
+ if (yperr == YPERR_KEY)
+ *statp = EX_NOHOST;
+ else if (yperr == YPERR_BUSY)
+ *statp = EX_TEMPFAIL;
+ else
+ *statp = EX_UNAVAILABLE;
+ if (vp != NULL)
+ sm_free(vp);
+ return false;
+ }
+ (void) sm_strlcpy(host_record, vp, sizeof host_record);
+ sm_free(vp);
+ if (tTd(38, 44))
+ sm_dprintf("got record `%s'\n", host_record);
+ vp = strpbrk(host_record, "#\n");
+ if (vp != NULL)
+ *vp = '\0';
+ if (!extract_canonname(nbuf, NULL, host_record, cbuf, sizeof cbuf))
+ {
+ /* this should not happen, but.... */
+ *statp = EX_NOHOST;
+ return false;
+ }
+ if (sm_strlcpy(name, cbuf, hbsize) >= hbsize)
+ {
+ *statp = EX_UNAVAILABLE;
+ return false;
+ }
+ *statp = EX_OK;
+ return true;
+}
+
+#endif /* NIS */
+/*
+** NISPLUS Modules
+**
+** This code donated by Sun Microsystems.
+*/
+
+#if NISPLUS
+
+# undef NIS /* symbol conflict in nis.h */
+# undef T_UNSPEC /* symbol conflict in nis.h -> ... -> sys/tiuser.h */
+# include <rpcsvc/nis.h>
+# include <rpcsvc/nislib.h>
+
+# define EN_col(col) zo_data.objdata_u.en_data.en_cols.en_cols_val[(col)].ec_value.ec_value_val
+# define COL_NAME(res,i) ((res->objects.objects_val)->TA_data.ta_cols.ta_cols_val)[i].tc_name
+# define COL_MAX(res) ((res->objects.objects_val)->TA_data.ta_cols.ta_cols_len)
+# define PARTIAL_NAME(x) ((x)[strlen(x) - 1] != '.')
+
+/*
+** NISPLUS_MAP_OPEN -- open nisplus table
+*/
+
+bool
+nisplus_map_open(map, mode)
+ MAP *map;
+ int mode;
+{
+ nis_result *res = NULL;
+ int retry_cnt, max_col, i;
+ char qbuf[MAXLINE + NIS_MAXNAMELEN];
+
+ if (tTd(38, 2))
+ sm_dprintf("nisplus_map_open(%s, %s, %d)\n",
+ map->map_mname, map->map_file, mode);
+
+ mode &= O_ACCMODE;
+ if (mode != O_RDONLY)
+ {
+ errno = EPERM;
+ return false;
+ }
+
+ if (*map->map_file == '\0')
+ map->map_file = "mail_aliases.org_dir";
+
+ if (PARTIAL_NAME(map->map_file) && map->map_domain == NULL)
+ {
+ /* set default NISPLUS Domain to $m */
+ map->map_domain = newstr(nisplus_default_domain());
+ if (tTd(38, 2))
+ sm_dprintf("nisplus_map_open(%s): using domain %s\n",
+ map->map_file, map->map_domain);
+ }
+ if (!PARTIAL_NAME(map->map_file))
+ {
+ map->map_domain = newstr("");
+ (void) sm_strlcpy(qbuf, map->map_file, sizeof qbuf);
+ }
+ else
+ {
+ /* check to see if this map actually exists */
+ (void) sm_strlcpyn(qbuf, sizeof qbuf, 3,
+ map->map_file, ".", map->map_domain);
+ }
+
+ retry_cnt = 0;
+ while (res == NULL || res->status != NIS_SUCCESS)
+ {
+ res = nis_lookup(qbuf, FOLLOW_LINKS);
+ switch (res->status)
+ {
+ case NIS_SUCCESS:
+ break;
+
+ case NIS_TRYAGAIN:
+ case NIS_RPCERROR:
+ case NIS_NAMEUNREACHABLE:
+ if (retry_cnt++ > 4)
+ {
+ errno = EAGAIN;
+ return false;
+ }
+ /* try not to overwhelm hosed server */
+ sleep(2);
+ break;
+
+ default: /* all other nisplus errors */
+# if 0
+ if (!bitset(MF_OPTIONAL, map->map_mflags))
+ syserr("451 4.3.5 Cannot find table %s.%s: %s",
+ map->map_file, map->map_domain,
+ nis_sperrno(res->status));
+# endif /* 0 */
+ errno = EAGAIN;
+ return false;
+ }
+ }
+
+ if (NIS_RES_NUMOBJ(res) != 1 ||
+ (NIS_RES_OBJECT(res)->zo_data.zo_type != TABLE_OBJ))
+ {
+ if (tTd(38, 10))
+ sm_dprintf("nisplus_map_open: %s is not a table\n", qbuf);
+# if 0
+ if (!bitset(MF_OPTIONAL, map->map_mflags))
+ syserr("451 4.3.5 %s.%s: %s is not a table",
+ map->map_file, map->map_domain,
+ nis_sperrno(res->status));
+# endif /* 0 */
+ errno = EBADF;
+ return false;
+ }
+ /* default key column is column 0 */
+ if (map->map_keycolnm == NULL)
+ map->map_keycolnm = newstr(COL_NAME(res,0));
+
+ max_col = COL_MAX(res);
+
+ /* verify the key column exist */
+ for (i = 0; i < max_col; i++)
+ {
+ if (strcmp(map->map_keycolnm, COL_NAME(res,i)) == 0)
+ break;
+ }
+ if (i == max_col)
+ {
+ if (tTd(38, 2))
+ sm_dprintf("nisplus_map_open(%s): can not find key column %s\n",
+ map->map_file, map->map_keycolnm);
+ errno = ENOENT;
+ return false;
+ }
+
+ /* default value column is the last column */
+ if (map->map_valcolnm == NULL)
+ {
+ map->map_valcolno = max_col - 1;
+ return true;
+ }
+
+ for (i = 0; i< max_col; i++)
+ {
+ if (strcmp(map->map_valcolnm, COL_NAME(res,i)) == 0)
+ {
+ map->map_valcolno = i;
+ return true;
+ }
+ }
+
+ if (tTd(38, 2))
+ sm_dprintf("nisplus_map_open(%s): can not find column %s\n",
+ map->map_file, map->map_keycolnm);
+ errno = ENOENT;
+ return false;
+}
+
+
+/*
+** NISPLUS_MAP_LOOKUP -- look up a datum in a NISPLUS table
+*/
+
+char *
+nisplus_map_lookup(map, name, av, statp)
+ MAP *map;
+ char *name;
+ char **av;
+ int *statp;
+{
+ char *p;
+ auto int vsize;
+ char *skp;
+ int skleft;
+ char search_key[MAXNAME + 4];
+ char qbuf[MAXLINE + NIS_MAXNAMELEN];
+ nis_result *result;
+
+ if (tTd(38, 20))
+ sm_dprintf("nisplus_map_lookup(%s, %s)\n",
+ map->map_mname, name);
+
+ if (!bitset(MF_OPEN, map->map_mflags))
+ {
+ if (nisplus_map_open(map, O_RDONLY))
+ {
+ map->map_mflags |= MF_OPEN;
+ map->map_pid = CurrentPid;
+ }
+ else
+ {
+ *statp = EX_UNAVAILABLE;
+ return NULL;
+ }
+ }
+
+ /*
+ ** Copy the name to the key buffer, escaping double quote characters
+ ** by doubling them and quoting "]" and "," to avoid having the
+ ** NIS+ parser choke on them.
+ */
+
+ skleft = sizeof search_key - 4;
+ skp = search_key;
+ for (p = name; *p != '\0' && skleft > 0; p++)
+ {
+ switch (*p)
+ {
+ case ']':
+ case ',':
+ /* quote the character */
+ *skp++ = '"';
+ *skp++ = *p;
+ *skp++ = '"';
+ skleft -= 3;
+ break;
+
+ case '"':
+ /* double the quote */
+ *skp++ = '"';
+ skleft--;
+ /* FALLTHROUGH */
+
+ default:
+ *skp++ = *p;
+ skleft--;
+ break;
+ }
+ }
+ *skp = '\0';
+ if (!bitset(MF_NOFOLDCASE, map->map_mflags))
+ makelower(search_key);
+
+ /* construct the query */
+ if (PARTIAL_NAME(map->map_file))
+ (void) sm_snprintf(qbuf, sizeof qbuf, "[%s=%s],%s.%s",
+ map->map_keycolnm, search_key, map->map_file,
+ map->map_domain);
+ else
+ (void) sm_snprintf(qbuf, sizeof qbuf, "[%s=%s],%s",
+ map->map_keycolnm, search_key, map->map_file);
+
+ if (tTd(38, 20))
+ sm_dprintf("qbuf=%s\n", qbuf);
+ result = nis_list(qbuf, FOLLOW_LINKS | FOLLOW_PATH, NULL, NULL);
+ if (result->status == NIS_SUCCESS)
+ {
+ int count;
+ char *str;
+
+ if ((count = NIS_RES_NUMOBJ(result)) != 1)
+ {
+ if (LogLevel > 10)
+ sm_syslog(LOG_WARNING, CurEnv->e_id,
+ "%s: lookup error, expected 1 entry, got %d",
+ map->map_file, count);
+
+ /* ignore second entry */
+ if (tTd(38, 20))
+ sm_dprintf("nisplus_map_lookup(%s), got %d entries, additional entries ignored\n",
+ name, count);
+ }
+
+ p = ((NIS_RES_OBJECT(result))->EN_col(map->map_valcolno));
+ /* set the length of the result */
+ if (p == NULL)
+ p = "";
+ vsize = strlen(p);
+ if (tTd(38, 20))
+ sm_dprintf("nisplus_map_lookup(%s), found %s\n",
+ name, p);
+ if (bitset(MF_MATCHONLY, map->map_mflags))
+ str = map_rewrite(map, name, strlen(name), NULL);
+ else
+ str = map_rewrite(map, p, vsize, av);
+ nis_freeresult(result);
+ *statp = EX_OK;
+ return str;
+ }
+ else
+ {
+ if (result->status == NIS_NOTFOUND)
+ *statp = EX_NOTFOUND;
+ else if (result->status == NIS_TRYAGAIN)
+ *statp = EX_TEMPFAIL;
+ else
+ {
+ *statp = EX_UNAVAILABLE;
+ map->map_mflags &= ~(MF_VALID|MF_OPEN);
+ }
+ }
+ if (tTd(38, 20))
+ sm_dprintf("nisplus_map_lookup(%s), failed\n", name);
+ nis_freeresult(result);
+ return NULL;
+}
+
+
+
+/*
+** NISPLUS_GETCANONNAME -- look up canonical name in NIS+
+*/
+
+static bool
+nisplus_getcanonname(name, hbsize, statp)
+ char *name;
+ int hbsize;
+ int *statp;
+{
+ char *vp;
+ auto int vsize;
+ nis_result *result;
+ char *p;
+ char nbuf[MAXNAME + 1];
+ char qbuf[MAXLINE + NIS_MAXNAMELEN];
+
+ if (sm_strlcpy(nbuf, name, sizeof nbuf) >= sizeof nbuf)
+ {
+ *statp = EX_UNAVAILABLE;
+ return false;
+ }
+ (void) shorten_hostname(nbuf);
+
+ p = strchr(nbuf, '.');
+ if (p == NULL)
+ {
+ /* single token */
+ (void) sm_snprintf(qbuf, sizeof qbuf,
+ "[name=%s],hosts.org_dir", nbuf);
+ }
+ else if (p[1] != '\0')
+ {
+ /* multi token -- take only first token in nbuf */
+ *p = '\0';
+ (void) sm_snprintf(qbuf, sizeof qbuf,
+ "[name=%s],hosts.org_dir.%s", nbuf, &p[1]);
+ }
+ else
+ {
+ *statp = EX_NOHOST;
+ return false;
+ }
+
+ if (tTd(38, 20))
+ sm_dprintf("\nnisplus_getcanonname(%s), qbuf=%s\n",
+ name, qbuf);
+
+ result = nis_list(qbuf, EXPAND_NAME|FOLLOW_LINKS|FOLLOW_PATH,
+ NULL, NULL);
+
+ if (result->status == NIS_SUCCESS)
+ {
+ int count;
+ char *domain;
+
+ if ((count = NIS_RES_NUMOBJ(result)) != 1)
+ {
+ if (LogLevel > 10)
+ sm_syslog(LOG_WARNING, CurEnv->e_id,
+ "nisplus_getcanonname: lookup error, expected 1 entry, got %d",
+ count);
+
+ /* ignore second entry */
+ if (tTd(38, 20))
+ sm_dprintf("nisplus_getcanonname(%s), got %d entries, all but first ignored\n",
+ name, count);
+ }
+
+ if (tTd(38, 20))
+ sm_dprintf("nisplus_getcanonname(%s), found in directory \"%s\"\n",
+ name, (NIS_RES_OBJECT(result))->zo_domain);
+
+
+ vp = ((NIS_RES_OBJECT(result))->EN_col(0));
+ vsize = strlen(vp);
+ if (tTd(38, 20))
+ sm_dprintf("nisplus_getcanonname(%s), found %s\n",
+ name, vp);
+ if (strchr(vp, '.') != NULL)
+ {
+ domain = "";
+ }
+ else
+ {
+ domain = macvalue('m', CurEnv);
+ if (domain == NULL)
+ domain = "";
+ }
+ if (hbsize > vsize + (int) strlen(domain) + 1)
+ {
+ if (domain[0] == '\0')
+ (void) sm_strlcpy(name, vp, hbsize);
+ else
+ (void) sm_snprintf(name, hbsize,
+ "%s.%s", vp, domain);
+ *statp = EX_OK;
+ }
+ else
+ *statp = EX_NOHOST;
+ nis_freeresult(result);
+ return true;
+ }
+ else
+ {
+ if (result->status == NIS_NOTFOUND)
+ *statp = EX_NOHOST;
+ else if (result->status == NIS_TRYAGAIN)
+ *statp = EX_TEMPFAIL;
+ else
+ *statp = EX_UNAVAILABLE;
+ }
+ if (tTd(38, 20))
+ sm_dprintf("nisplus_getcanonname(%s), failed, status=%d, nsw_stat=%d\n",
+ name, result->status, *statp);
+ nis_freeresult(result);
+ return false;
+}
+
+char *
+nisplus_default_domain()
+{
+ static char default_domain[MAXNAME + 1] = "";
+ char *p;
+
+ if (default_domain[0] != '\0')
+ return default_domain;
+
+ p = nis_local_directory();
+ (void) sm_strlcpy(default_domain, p, sizeof default_domain);
+ return default_domain;
+}
+
+#endif /* NISPLUS */
+/*
+** LDAP Modules
+*/
+
+/*
+** LDAPMAP_DEQUOTE - helper routine for ldapmap_parseargs
+*/
+
+#if defined(LDAPMAP) || defined(PH_MAP)
+
+# if PH_MAP
+# define ph_map_dequote ldapmap_dequote
+# endif /* PH_MAP */
+
+static char *ldapmap_dequote __P((char *));
+
+static char *
+ldapmap_dequote(str)
+ char *str;
+{
+ char *p;
+ char *start;
+
+ if (str == NULL)
+ return NULL;
+
+ p = str;
+ if (*p == '"')
+ {
+ /* Should probably swallow initial whitespace here */
+ start = ++p;
+ }
+ else
+ return str;
+ while (*p != '"' && *p != '\0')
+ p++;
+ if (*p != '\0')
+ *p = '\0';
+ return start;
+}
+#endif /* defined(LDAPMAP) || defined(PH_MAP) */
+
+#if LDAPMAP
+
+static SM_LDAP_STRUCT *LDAPDefaults = NULL;
+
+/*
+** LDAPMAP_OPEN -- open LDAP map
+**
+** Connect to the LDAP server. Re-use existing connections since a
+** single server connection to a host (with the same host, port,
+** bind DN, and secret) can answer queries for multiple maps.
+*/
+
+bool
+ldapmap_open(map, mode)
+ MAP *map;
+ int mode;
+{
+ SM_LDAP_STRUCT *lmap;
+ STAB *s;
+ char *id;
+
+ if (tTd(38, 2))
+ sm_dprintf("ldapmap_open(%s, %d): ", map->map_mname, mode);
+
+#if defined(SUN_EXTENSIONS) && defined(SUN_SIMPLIFIED_LDAP) && \
+ defined(HASLDAPGETALIASBYNAME)
+ if (VendorCode == VENDOR_SUN &&
+ strcmp(map->map_mname, "aliases.ldap") == 0)
+ {
+ return true;
+ }
+#endif
+
+ mode &= O_ACCMODE;
+
+ /* sendmail doesn't have the ability to write to LDAP (yet) */
+ if (mode != O_RDONLY)
+ {
+ /* issue a pseudo-error message */
+ errno = SM_EMAPCANTWRITE;
+ return false;
+ }
+
+ lmap = (SM_LDAP_STRUCT *) map->map_db1;
+
+ s = ldapmap_findconn(lmap);
+ if (s->s_lmap != NULL)
+ {
+ /* Already have a connection open to this LDAP server */
+ lmap->ldap_ld = ((SM_LDAP_STRUCT *)s->s_lmap->map_db1)->ldap_ld;
+ lmap->ldap_pid = ((SM_LDAP_STRUCT *)s->s_lmap->map_db1)->ldap_pid;
+
+ /* Add this map as head of linked list */
+ lmap->ldap_next = s->s_lmap;
+ s->s_lmap = map;
+
+ if (tTd(38, 2))
+ sm_dprintf("using cached connection\n");
+ return true;
+ }
+
+ if (tTd(38, 2))
+ sm_dprintf("opening new connection\n");
+
+ if (lmap->ldap_host != NULL)
+ id = lmap->ldap_host;
+ else if (lmap->ldap_uri != NULL)
+ id = lmap->ldap_uri;
+ else
+ id = "localhost";
+
+ /* No connection yet, connect */
+ if (!sm_ldap_start(map->map_mname, lmap))
+ {
+ if (errno == ETIMEDOUT)
+ {
+ if (LogLevel > 1)
+ sm_syslog(LOG_NOTICE, CurEnv->e_id,
+ "timeout conning to LDAP server %.100s",
+ id);
+ }
+
+ if (!bitset(MF_OPTIONAL, map->map_mflags))
+ {
+ if (bitset(MF_NODEFER, map->map_mflags))
+ {
+ syserr("%s failed to %s in map %s",
+# if USE_LDAP_INIT
+ "ldap_init/ldap_bind",
+# else /* USE_LDAP_INIT */
+ "ldap_open",
+# endif /* USE_LDAP_INIT */
+ id, map->map_mname);
+ }
+ else
+ {
+ syserr("451 4.3.5 %s failed to %s in map %s",
+# if USE_LDAP_INIT
+ "ldap_init/ldap_bind",
+# else /* USE_LDAP_INIT */
+ "ldap_open",
+# endif /* USE_LDAP_INIT */
+ id, map->map_mname);
+ }
+ }
+ return false;
+ }
+
+ /* Save connection for reuse */
+ s->s_lmap = map;
+ return true;
+}
+
+/*
+** LDAPMAP_CLOSE -- close ldap map
+*/
+
+void
+ldapmap_close(map)
+ MAP *map;
+{
+ SM_LDAP_STRUCT *lmap;
+ STAB *s;
+
+ if (tTd(38, 2))
+ sm_dprintf("ldapmap_close(%s)\n", map->map_mname);
+
+ lmap = (SM_LDAP_STRUCT *) map->map_db1;
+
+ /* Check if already closed */
+ if (lmap->ldap_ld == NULL)
+ return;
+
+ /* Close the LDAP connection */
+ sm_ldap_close(lmap);
+
+ /* Mark all the maps that share the connection as closed */
+ s = ldapmap_findconn(lmap);
+
+ while (s->s_lmap != NULL)
+ {
+ MAP *smap = s->s_lmap;
+
+ if (tTd(38, 2) && smap != map)
+ sm_dprintf("ldapmap_close(%s): closed %s (shared LDAP connection)\n",
+ map->map_mname, smap->map_mname);
+ smap->map_mflags &= ~(MF_OPEN|MF_WRITABLE);
+ lmap = (SM_LDAP_STRUCT *) smap->map_db1;
+ lmap->ldap_ld = NULL;
+ s->s_lmap = lmap->ldap_next;
+ lmap->ldap_next = NULL;
+ }
+}
+
+# ifdef SUNET_ID
+/*
+** SUNET_ID_HASH -- Convert a string to its Sunet_id canonical form
+** This only makes sense at Stanford University.
+*/
+
+static char *
+sunet_id_hash(str)
+ char *str;
+{
+ char *p, *p_last;
+
+ p = str;
+ p_last = p;
+ while (*p != '\0')
+ {
+ if (islower(*p) || isdigit(*p))
+ {
+ *p_last = *p;
+ p_last++;
+ }
+ else if (isupper(*p))
+ {
+ *p_last = tolower(*p);
+ p_last++;
+ }
+ ++p;
+ }
+ if (*p_last != '\0')
+ *p_last = '\0';
+ return str;
+}
+# endif /* SUNET_ID */
+
+/*
+** LDAPMAP_LOOKUP -- look up a datum in a LDAP map
+*/
+
+char *
+ldapmap_lookup(map, name, av, statp)
+ MAP *map;
+ char *name;
+ char **av;
+ int *statp;
+{
+ int flags;
+ int plen = 0;
+ int psize = 0;
+ int msgid;
+ int save_errno;
+ char *vp, *p;
+ char *result = NULL;
+ SM_RPOOL_T *rpool;
+ SM_LDAP_STRUCT *lmap = NULL;
+ char keybuf[MAXNAME + 1];
+
+ if (tTd(38, 20))
+ sm_dprintf("ldapmap_lookup(%s, %s)\n", map->map_mname, name);
+
+#if defined(SUN_EXTENSIONS) && defined(SUN_SIMPLIFIED_LDAP) && \
+ defined(HASLDAPGETALIASBYNAME)
+ if (VendorCode == VENDOR_SUN &&
+ strcmp(map->map_mname, "aliases.ldap") == 0)
+ {
+ char answer[MAXNAME + 1];
+ int rc;
+
+ rc = __getldapaliasbyname(name, answer, sizeof(answer));
+ if (rc != 0)
+ {
+ if (tTd(38, 20))
+ sm_dprintf("getldapaliasbyname(%.100s) failed, errno=%d\n",
+ name, errno);
+ *statp = EX_NOTFOUND;
+ return NULL;
+ }
+ *statp = EX_OK;
+ if (tTd(38, 20))
+ sm_dprintf("getldapaliasbyname(%.100s) => %s\n", name,
+ answer);
+ if (bitset(MF_MATCHONLY, map->map_mflags))
+ result = map_rewrite(map, name, strlen(name), NULL);
+ else
+ result = map_rewrite(map, answer, strlen(answer), av);
+ return result;
+ }
+#endif
+
+ /* Get ldap struct pointer from map */
+ lmap = (SM_LDAP_STRUCT *) map->map_db1;
+ sm_ldap_setopts(lmap->ldap_ld, lmap);
+
+ (void) sm_strlcpy(keybuf, name, sizeof keybuf);
+
+ if (!bitset(MF_NOFOLDCASE, map->map_mflags))
+ {
+# ifdef SUNET_ID
+ sunet_id_hash(keybuf);
+# else /* SUNET_ID */
+ makelower(keybuf);
+# endif /* SUNET_ID */
+ }
+
+ msgid = sm_ldap_search(lmap, keybuf);
+ if (msgid == -1)
+ {
+ errno = sm_ldap_geterrno(lmap->ldap_ld) + E_LDAPBASE;
+ save_errno = errno;
+ if (!bitset(MF_OPTIONAL, map->map_mflags))
+ {
+ if (bitset(MF_NODEFER, map->map_mflags))
+ syserr("Error in ldap_search using %s in map %s",
+ keybuf, map->map_mname);
+ else
+ syserr("451 4.3.5 Error in ldap_search using %s in map %s",
+ keybuf, map->map_mname);
+ }
+ *statp = EX_TEMPFAIL;
+ switch (save_errno - E_LDAPBASE)
+ {
+# ifdef LDAP_SERVER_DOWN
+ case LDAP_SERVER_DOWN:
+# endif /* LDAP_SERVER_DOWN */
+ case LDAP_TIMEOUT:
+ case LDAP_UNAVAILABLE:
+ /* server disappeared, try reopen on next search */
+ ldapmap_close(map);
+ break;
+ }
+ errno = save_errno;
+ return NULL;
+ }
+
+ *statp = EX_NOTFOUND;
+ vp = NULL;
+
+ flags = 0;
+ if (bitset(MF_SINGLEMATCH, map->map_mflags))
+ flags |= SM_LDAP_SINGLEMATCH;
+ if (bitset(MF_MATCHONLY, map->map_mflags))
+ flags |= SM_LDAP_MATCHONLY;
+
+ /* Create an rpool for search related memory usage */
+ rpool = sm_rpool_new_x(NULL);
+
+ p = NULL;
+ *statp = sm_ldap_results(lmap, msgid, flags, map->map_coldelim,
+ rpool, &p, &plen, &psize, NULL);
+ save_errno = errno;
+
+ /* Copy result so rpool can be freed */
+ if (*statp == EX_OK && p != NULL)
+ vp = newstr(p);
+ sm_rpool_free(rpool);
+
+ /* need to restart LDAP connection? */
+ if (*statp == EX_RESTART)
+ {
+ *statp = EX_TEMPFAIL;
+ ldapmap_close(map);
+ }
+
+ errno = save_errno;
+ if (*statp != EX_OK && *statp != EX_NOTFOUND)
+ {
+ if (!bitset(MF_OPTIONAL, map->map_mflags))
+ {
+ if (bitset(MF_NODEFER, map->map_mflags))
+ syserr("Error getting LDAP results in map %s",
+ map->map_mname);
+ else
+ syserr("451 4.3.5 Error getting LDAP results in map %s",
+ map->map_mname);
+ }
+ errno = save_errno;
+ return NULL;
+ }
+
+ /* Did we match anything? */
+ if (vp == NULL && !bitset(MF_MATCHONLY, map->map_mflags))
+ return NULL;
+
+ if (*statp == EX_OK)
+ {
+ if (LogLevel > 9)
+ sm_syslog(LOG_INFO, CurEnv->e_id,
+ "ldap %.100s => %s", name,
+ vp == NULL ? "<NULL>" : vp);
+ if (bitset(MF_MATCHONLY, map->map_mflags))
+ result = map_rewrite(map, name, strlen(name), NULL);
+ else
+ {
+ /* vp != NULL according to test above */
+ result = map_rewrite(map, vp, strlen(vp), av);
+ }
+ if (vp != NULL)
+ sm_free(vp); /* XXX */
+ }
+ return result;
+}
+
+/*
+** LDAPMAP_FINDCONN -- find an LDAP connection to the server
+**
+** Cache LDAP connections based on the host, port, bind DN,
+** secret, and PID so we don't have multiple connections open to
+** the same server for different maps. Need a separate connection
+** per PID since a parent process may close the map before the
+** child is done with it.
+**
+** Parameters:
+** lmap -- LDAP map information
+**
+** Returns:
+** Symbol table entry for the LDAP connection.
+*/
+
+static STAB *
+ldapmap_findconn(lmap)
+ SM_LDAP_STRUCT *lmap;
+{
+ char *format;
+ char *nbuf;
+ char *id;
+ STAB *SM_NONVOLATILE s = NULL;
+
+ if (lmap->ldap_host != NULL)
+ id = lmap->ldap_host;
+ else if (lmap->ldap_uri != NULL)
+ id = lmap->ldap_uri;
+ else
+ id = "localhost";
+
+ format = "%s%c%d%c%d%c%s%c%s%d";
+ nbuf = sm_stringf_x(format,
+ id,
+ CONDELSE,
+ lmap->ldap_port,
+ CONDELSE,
+ lmap->ldap_version,
+ CONDELSE,
+ (lmap->ldap_binddn == NULL ? ""
+ : lmap->ldap_binddn),
+ CONDELSE,
+ (lmap->ldap_secret == NULL ? ""
+ : lmap->ldap_secret),
+ (int) CurrentPid);
+ SM_TRY
+ s = stab(nbuf, ST_LMAP, ST_ENTER);
+ SM_FINALLY
+ sm_free(nbuf);
+ SM_END_TRY
+ return s;
+}
+/*
+** LDAPMAP_PARSEARGS -- parse ldap map definition args.
+*/
+
+static struct lamvalues LDAPAuthMethods[] =
+{
+ { "none", LDAP_AUTH_NONE },
+ { "simple", LDAP_AUTH_SIMPLE },
+# ifdef LDAP_AUTH_KRBV4
+ { "krbv4", LDAP_AUTH_KRBV4 },
+# endif /* LDAP_AUTH_KRBV4 */
+ { NULL, 0 }
+};
+
+static struct ladvalues LDAPAliasDereference[] =
+{
+ { "never", LDAP_DEREF_NEVER },
+ { "always", LDAP_DEREF_ALWAYS },
+ { "search", LDAP_DEREF_SEARCHING },
+ { "find", LDAP_DEREF_FINDING },
+ { NULL, 0 }
+};
+
+static struct lssvalues LDAPSearchScope[] =
+{
+ { "base", LDAP_SCOPE_BASE },
+ { "one", LDAP_SCOPE_ONELEVEL },
+ { "sub", LDAP_SCOPE_SUBTREE },
+ { NULL, 0 }
+};
+
+bool
+ldapmap_parseargs(map, args)
+ MAP *map;
+ char *args;
+{
+ bool secretread = true;
+ bool attrssetup = false;
+ int i;
+ register char *p = args;
+ SM_LDAP_STRUCT *lmap;
+ struct lamvalues *lam;
+ struct ladvalues *lad;
+ struct lssvalues *lss;
+ char ldapfilt[MAXLINE];
+ char m_tmp[MAXPATHLEN + LDAPMAP_MAX_PASSWD];
+
+ /* Get ldap struct pointer from map */
+ lmap = (SM_LDAP_STRUCT *) map->map_db1;
+
+ /* Check if setting the initial LDAP defaults */
+ if (lmap == NULL || lmap != LDAPDefaults)
+ {
+ /* We need to alloc an SM_LDAP_STRUCT struct */
+ lmap = (SM_LDAP_STRUCT *) xalloc(sizeof *lmap);
+ if (LDAPDefaults == NULL)
+ sm_ldap_clear(lmap);
+ else
+ STRUCTCOPY(*LDAPDefaults, *lmap);
+ }
+
+ /* there is no check whether there is really an argument */
+ map->map_mflags |= MF_TRY0NULL|MF_TRY1NULL;
+ map->map_spacesub = SpaceSub; /* default value */
+
+ /* Check if setting up an alias or file class LDAP map */
+ if (bitset(MF_ALIAS, map->map_mflags))
+ {
+ /* Comma separate if used as an alias file */
+ map->map_coldelim = ',';
+ if (*args == '\0')
+ {
+ int n;
+ char *lc;
+ char jbuf[MAXHOSTNAMELEN];
+ char lcbuf[MAXLINE];
+
+ /* Get $j */
+ expand("\201j", jbuf, sizeof jbuf, &BlankEnvelope);
+ if (jbuf[0] == '\0')
+ {
+ (void) sm_strlcpy(jbuf, "localhost",
+ sizeof jbuf);
+ }
+
+ lc = macvalue(macid("{sendmailMTACluster}"), CurEnv);
+ if (lc == NULL)
+ lc = "";
+ else
+ {
+ expand(lc, lcbuf, sizeof lcbuf, CurEnv);
+ lc = lcbuf;
+ }
+
+ n = sm_snprintf(ldapfilt, sizeof ldapfilt,
+ "(&(objectClass=sendmailMTAAliasObject)(sendmailMTAAliasGrouping=aliases)(|(sendmailMTACluster=%s)(sendmailMTAHost=%s))(sendmailMTAKey=%%0))",
+ lc, jbuf);
+ if (n >= sizeof ldapfilt)
+ {
+ syserr("%s: Default LDAP string too long",
+ map->map_mname);
+ return false;
+ }
+
+ /* default args for an alias LDAP entry */
+ lmap->ldap_filter = ldapfilt;
+ lmap->ldap_attr[0] = "objectClass";
+ lmap->ldap_attr_type[0] = SM_LDAP_ATTR_OBJCLASS;
+ lmap->ldap_attr_needobjclass[0] = NULL;
+ lmap->ldap_attr[1] = "sendmailMTAAliasValue";
+ lmap->ldap_attr_type[1] = SM_LDAP_ATTR_NORMAL;
+ lmap->ldap_attr_needobjclass[1] = NULL;
+ lmap->ldap_attr[2] = "sendmailMTAAliasSearch";
+ lmap->ldap_attr_type[2] = SM_LDAP_ATTR_FILTER;
+ lmap->ldap_attr_needobjclass[2] = "sendmailMTAMapObject";
+ lmap->ldap_attr[3] = "sendmailMTAAliasURL";
+ lmap->ldap_attr_type[3] = SM_LDAP_ATTR_URL;
+ lmap->ldap_attr_needobjclass[3] = "sendmailMTAMapObject";
+ lmap->ldap_attr[4] = NULL;
+ lmap->ldap_attr_type[4] = SM_LDAP_ATTR_NONE;
+ lmap->ldap_attr_needobjclass[4] = NULL;
+ attrssetup = true;
+ }
+ }
+ else if (bitset(MF_FILECLASS, map->map_mflags))
+ {
+ /* Space separate if used as a file class file */
+ map->map_coldelim = ' ';
+ }
+
+ for (;;)
+ {
+ while (isascii(*p) && isspace(*p))
+ p++;
+ if (*p != '-')
+ break;
+ switch (*++p)
+ {
+ case 'N':
+ map->map_mflags |= MF_INCLNULL;
+ map->map_mflags &= ~MF_TRY0NULL;
+ break;
+
+ case 'O':
+ map->map_mflags &= ~MF_TRY1NULL;
+ break;
+
+ case 'o':
+ map->map_mflags |= MF_OPTIONAL;
+ break;
+
+ case 'f':
+ map->map_mflags |= MF_NOFOLDCASE;
+ break;
+
+ case 'm':
+ map->map_mflags |= MF_MATCHONLY;
+ break;
+
+ case 'A':
+ map->map_mflags |= MF_APPEND;
+ break;
+
+ case 'q':
+ map->map_mflags |= MF_KEEPQUOTES;
+ break;
+
+ case 'a':
+ map->map_app = ++p;
+ break;
+
+ case 'T':
+ map->map_tapp = ++p;
+ break;
+
+ case 't':
+ map->map_mflags |= MF_NODEFER;
+ break;
+
+ case 'S':
+ map->map_spacesub = *++p;
+ break;
+
+ case 'D':
+ map->map_mflags |= MF_DEFER;
+ break;
+
+ case 'z':
+ if (*++p != '\\')
+ map->map_coldelim = *p;
+ else
+ {
+ switch (*++p)
+ {
+ case 'n':
+ map->map_coldelim = '\n';
+ break;
+
+ case 't':
+ map->map_coldelim = '\t';
+ break;
+
+ default:
+ map->map_coldelim = '\\';
+ }
+ }
+ break;
+
+ /* Start of ldapmap specific args */
+ case 'V':
+ if (*++p != '\\')
+ lmap->ldap_attrsep = *p;
+ else
+ {
+ switch (*++p)
+ {
+ case 'n':
+ lmap->ldap_attrsep = '\n';
+ break;
+
+ case 't':
+ lmap->ldap_attrsep = '\t';
+ break;
+
+ default:
+ lmap->ldap_attrsep = '\\';
+ }
+ }
+ break;
+
+ case 'k': /* search field */
+ while (isascii(*++p) && isspace(*p))
+ continue;
+ lmap->ldap_filter = p;
+ break;
+
+ case 'v': /* attr to return */
+ while (isascii(*++p) && isspace(*p))
+ continue;
+ lmap->ldap_attr[0] = p;
+ lmap->ldap_attr[1] = NULL;
+ break;
+
+ case '1':
+ map->map_mflags |= MF_SINGLEMATCH;
+ break;
+
+ /* args stolen from ldapsearch.c */
+ case 'R': /* don't auto chase referrals */
+# ifdef LDAP_REFERRALS
+ lmap->ldap_options &= ~LDAP_OPT_REFERRALS;
+# else /* LDAP_REFERRALS */
+ syserr("compile with -DLDAP_REFERRALS for referral support");
+# endif /* LDAP_REFERRALS */
+ break;
+
+ case 'n': /* retrieve attribute names only */
+ lmap->ldap_attrsonly = LDAPMAP_TRUE;
+ break;
+
+ case 'r': /* alias dereferencing */
+ while (isascii(*++p) && isspace(*p))
+ continue;
+
+ if (sm_strncasecmp(p, "LDAP_DEREF_", 11) == 0)
+ p += 11;
+
+ for (lad = LDAPAliasDereference;
+ lad != NULL && lad->lad_name != NULL; lad++)
+ {
+ if (sm_strncasecmp(p, lad->lad_name,
+ strlen(lad->lad_name)) == 0)
+ break;
+ }
+ if (lad->lad_name != NULL)
+ lmap->ldap_deref = lad->lad_code;
+ else
+ {
+ /* bad config line */
+ if (!bitset(MCF_OPTFILE,
+ map->map_class->map_cflags))
+ {
+ char *ptr;
+
+ if ((ptr = strchr(p, ' ')) != NULL)
+ *ptr = '\0';
+ syserr("Deref must be [never|always|search|find] (not %s) in map %s",
+ p, map->map_mname);
+ if (ptr != NULL)
+ *ptr = ' ';
+ return false;
+ }
+ }
+ break;
+
+ case 's': /* search scope */
+ while (isascii(*++p) && isspace(*p))
+ continue;
+
+ if (sm_strncasecmp(p, "LDAP_SCOPE_", 11) == 0)
+ p += 11;
+
+ for (lss = LDAPSearchScope;
+ lss != NULL && lss->lss_name != NULL; lss++)
+ {
+ if (sm_strncasecmp(p, lss->lss_name,
+ strlen(lss->lss_name)) == 0)
+ break;
+ }
+ if (lss->lss_name != NULL)
+ lmap->ldap_scope = lss->lss_code;
+ else
+ {
+ /* bad config line */
+ if (!bitset(MCF_OPTFILE,
+ map->map_class->map_cflags))
+ {
+ char *ptr;
+
+ if ((ptr = strchr(p, ' ')) != NULL)
+ *ptr = '\0';
+ syserr("Scope must be [base|one|sub] (not %s) in map %s",
+ p, map->map_mname);
+ if (ptr != NULL)
+ *ptr = ' ';
+ return false;
+ }
+ }
+ break;
+
+ case 'h': /* ldap host */
+ while (isascii(*++p) && isspace(*p))
+ continue;
+ if (lmap->ldap_uri != NULL)
+ {
+ syserr("Can not specify both an LDAP host and an LDAP URI in map %s",
+ map->map_mname);
+ return false;
+ }
+ lmap->ldap_host = p;
+ break;
+
+ case 'b': /* search base */
+ while (isascii(*++p) && isspace(*p))
+ continue;
+ lmap->ldap_base = p;
+ break;
+
+ case 'p': /* ldap port */
+ while (isascii(*++p) && isspace(*p))
+ continue;
+ lmap->ldap_port = atoi(p);
+ break;
+
+ case 'l': /* time limit */
+ while (isascii(*++p) && isspace(*p))
+ continue;
+ lmap->ldap_timelimit = atoi(p);
+ lmap->ldap_timeout.tv_sec = lmap->ldap_timelimit;
+ break;
+
+ case 'Z':
+ while (isascii(*++p) && isspace(*p))
+ continue;
+ lmap->ldap_sizelimit = atoi(p);
+ break;
+
+ case 'd': /* Dn to bind to server as */
+ while (isascii(*++p) && isspace(*p))
+ continue;
+ lmap->ldap_binddn = p;
+ break;
+
+ case 'M': /* Method for binding */
+ while (isascii(*++p) && isspace(*p))
+ continue;
+
+ if (sm_strncasecmp(p, "LDAP_AUTH_", 10) == 0)
+ p += 10;
+
+ for (lam = LDAPAuthMethods;
+ lam != NULL && lam->lam_name != NULL; lam++)
+ {
+ if (sm_strncasecmp(p, lam->lam_name,
+ strlen(lam->lam_name)) == 0)
+ break;
+ }
+ if (lam->lam_name != NULL)
+ lmap->ldap_method = lam->lam_code;
+ else
+ {
+ /* bad config line */
+ if (!bitset(MCF_OPTFILE,
+ map->map_class->map_cflags))
+ {
+ char *ptr;
+
+ if ((ptr = strchr(p, ' ')) != NULL)
+ *ptr = '\0';
+ syserr("Method for binding must be [none|simple|krbv4] (not %s) in map %s",
+ p, map->map_mname);
+ if (ptr != NULL)
+ *ptr = ' ';
+ return false;
+ }
+ }
+
+ break;
+
+ /*
+ ** This is a string that is dependent on the
+ ** method used defined above.
+ */
+
+ case 'P': /* Secret password for binding */
+ while (isascii(*++p) && isspace(*p))
+ continue;
+ lmap->ldap_secret = p;
+ secretread = false;
+ break;
+
+ case 'H': /* Use LDAP URI */
+# if !USE_LDAP_INIT
+ syserr("Must compile with -DUSE_LDAP_INIT to use LDAP URIs (-H) in map %s",
+ map->map_mname);
+ return false;
+# else /* !USE_LDAP_INIT */
+ if (lmap->ldap_host != NULL)
+ {
+ syserr("Can not specify both an LDAP host and an LDAP URI in map %s",
+ map->map_mname);
+ return false;
+ }
+ while (isascii(*++p) && isspace(*p))
+ continue;
+ lmap->ldap_uri = p;
+ break;
+# endif /* !USE_LDAP_INIT */
+
+ case 'w':
+ /* -w should be for passwd, -P should be for version */
+ while (isascii(*++p) && isspace(*p))
+ continue;
+ lmap->ldap_version = atoi(p);
+# ifdef LDAP_VERSION_MAX
+ if (lmap->ldap_version > LDAP_VERSION_MAX)
+ {
+ syserr("LDAP version %d exceeds max of %d in map %s",
+ lmap->ldap_version, LDAP_VERSION_MAX,
+ map->map_mname);
+ return false;
+ }
+# endif /* LDAP_VERSION_MAX */
+# ifdef LDAP_VERSION_MIN
+ if (lmap->ldap_version < LDAP_VERSION_MIN)
+ {
+ syserr("LDAP version %d is lower than min of %d in map %s",
+ lmap->ldap_version, LDAP_VERSION_MIN,
+ map->map_mname);
+ return false;
+ }
+# endif /* LDAP_VERSION_MIN */
+ break;
+
+ default:
+ syserr("Illegal option %c map %s", *p, map->map_mname);
+ break;
+ }
+
+ /* need to account for quoted strings here */
+ while (*p != '\0' && !(isascii(*p) && isspace(*p)))
+ {
+ if (*p == '"')
+ {
+ while (*++p != '"' && *p != '\0')
+ continue;
+ if (*p != '\0')
+ p++;
+ }
+ else
+ p++;
+ }
+
+ if (*p != '\0')
+ *p++ = '\0';
+ }
+
+ if (map->map_app != NULL)
+ map->map_app = newstr(ldapmap_dequote(map->map_app));
+ if (map->map_tapp != NULL)
+ map->map_tapp = newstr(ldapmap_dequote(map->map_tapp));
+
+ /*
+ ** We need to swallow up all the stuff into a struct
+ ** and dump it into map->map_dbptr1
+ */
+
+ if (lmap->ldap_host != NULL &&
+ (LDAPDefaults == NULL ||
+ LDAPDefaults == lmap ||
+ LDAPDefaults->ldap_host != lmap->ldap_host))
+ lmap->ldap_host = newstr(ldapmap_dequote(lmap->ldap_host));
+ map->map_domain = lmap->ldap_host;
+
+ if (lmap->ldap_uri != NULL &&
+ (LDAPDefaults == NULL ||
+ LDAPDefaults == lmap ||
+ LDAPDefaults->ldap_uri != lmap->ldap_uri))
+ lmap->ldap_uri = newstr(ldapmap_dequote(lmap->ldap_uri));
+ map->map_domain = lmap->ldap_uri;
+
+ if (lmap->ldap_binddn != NULL &&
+ (LDAPDefaults == NULL ||
+ LDAPDefaults == lmap ||
+ LDAPDefaults->ldap_binddn != lmap->ldap_binddn))
+ lmap->ldap_binddn = newstr(ldapmap_dequote(lmap->ldap_binddn));
+
+ if (lmap->ldap_secret != NULL &&
+ (LDAPDefaults == NULL ||
+ LDAPDefaults == lmap ||
+ LDAPDefaults->ldap_secret != lmap->ldap_secret))
+ {
+ SM_FILE_T *sfd;
+ long sff = SFF_OPENASROOT|SFF_ROOTOK|SFF_NOWLINK|SFF_NOWWFILES|SFF_NOGWFILES;
+
+ if (DontLockReadFiles)
+ sff |= SFF_NOLOCK;
+
+ /* need to use method to map secret to passwd string */
+ switch (lmap->ldap_method)
+ {
+ case LDAP_AUTH_NONE:
+ /* Do nothing */
+ break;
+
+ case LDAP_AUTH_SIMPLE:
+
+ /*
+ ** Secret is the name of a file with
+ ** the first line as the password.
+ */
+
+ /* Already read in the secret? */
+ if (secretread)
+ break;
+
+ sfd = safefopen(ldapmap_dequote(lmap->ldap_secret),
+ O_RDONLY, 0, sff);
+ if (sfd == NULL)
+ {
+ syserr("LDAP map: cannot open secret %s",
+ ldapmap_dequote(lmap->ldap_secret));
+ return false;
+ }
+ lmap->ldap_secret = sfgets(m_tmp, sizeof m_tmp,
+ sfd, TimeOuts.to_fileopen,
+ "ldapmap_parseargs");
+ (void) sm_io_close(sfd, SM_TIME_DEFAULT);
+ if (strlen(m_tmp) > LDAPMAP_MAX_PASSWD)
+ {
+ syserr("LDAP map: secret in %s too long",
+ ldapmap_dequote(lmap->ldap_secret));
+ return false;
+ }
+ if (lmap->ldap_secret != NULL &&
+ strlen(m_tmp) > 0)
+ {
+ /* chomp newline */
+ if (m_tmp[strlen(m_tmp) - 1] == '\n')
+ m_tmp[strlen(m_tmp) - 1] = '\0';
+
+ lmap->ldap_secret = m_tmp;
+ }
+ break;
+
+# ifdef LDAP_AUTH_KRBV4
+ case LDAP_AUTH_KRBV4:
+
+ /*
+ ** Secret is where the ticket file is
+ ** stashed
+ */
+
+ (void) sm_snprintf(m_tmp, sizeof m_tmp,
+ "KRBTKFILE=%s",
+ ldapmap_dequote(lmap->ldap_secret));
+ lmap->ldap_secret = m_tmp;
+ break;
+# endif /* LDAP_AUTH_KRBV4 */
+
+ default: /* Should NEVER get here */
+ syserr("LDAP map: Illegal value in lmap method");
+ return false;
+ /* NOTREACHED */
+ break;
+ }
+ }
+
+ if (lmap->ldap_secret != NULL &&
+ (LDAPDefaults == NULL ||
+ LDAPDefaults == lmap ||
+ LDAPDefaults->ldap_secret != lmap->ldap_secret))
+ lmap->ldap_secret = newstr(ldapmap_dequote(lmap->ldap_secret));
+
+ if (lmap->ldap_base != NULL &&
+ (LDAPDefaults == NULL ||
+ LDAPDefaults == lmap ||
+ LDAPDefaults->ldap_base != lmap->ldap_base))
+ lmap->ldap_base = newstr(ldapmap_dequote(lmap->ldap_base));
+
+ /*
+ ** Save the server from extra work. If request is for a single
+ ** match, tell the server to only return enough records to
+ ** determine if there is a single match or not. This can not
+ ** be one since the server would only return one and we wouldn't
+ ** know if there were others available.
+ */
+
+ if (bitset(MF_SINGLEMATCH, map->map_mflags))
+ lmap->ldap_sizelimit = 2;
+
+ /* If setting defaults, don't process ldap_filter and ldap_attr */
+ if (lmap == LDAPDefaults)
+ return true;
+
+ if (lmap->ldap_filter != NULL)
+ lmap->ldap_filter = newstr(ldapmap_dequote(lmap->ldap_filter));
+ else
+ {
+ if (!bitset(MCF_OPTFILE, map->map_class->map_cflags))
+ {
+ syserr("No filter given in map %s", map->map_mname);
+ return false;
+ }
+ }
+
+ if (!attrssetup && lmap->ldap_attr[0] != NULL)
+ {
+ bool recurse = false;
+ bool normalseen = false;
+
+ i = 0;
+ p = ldapmap_dequote(lmap->ldap_attr[0]);
+ lmap->ldap_attr[0] = NULL;
+
+ /* Prime the attr list with the objectClass attribute */
+ lmap->ldap_attr[i] = "objectClass";
+ lmap->ldap_attr_type[i] = SM_LDAP_ATTR_OBJCLASS;
+ lmap->ldap_attr_needobjclass[i] = NULL;
+ i++;
+
+ while (p != NULL)
+ {
+ char *v;
+
+ while (isascii(*p) && isspace(*p))
+ p++;
+ if (*p == '\0')
+ break;
+ v = p;
+ p = strchr(v, ',');
+ if (p != NULL)
+ *p++ = '\0';
+
+ if (i >= LDAPMAP_MAX_ATTR)
+ {
+ syserr("Too many return attributes in %s (max %d)",
+ map->map_mname, LDAPMAP_MAX_ATTR);
+ return false;
+ }
+ if (*v != '\0')
+ {
+ int j;
+ int use;
+ char *type;
+ char *needobjclass;
+
+ type = strchr(v, ':');
+ if (type != NULL)
+ {
+ *type++ = '\0';
+ needobjclass = strchr(type, ':');
+ if (needobjclass != NULL)
+ *needobjclass++ = '\0';
+ }
+ else
+ {
+ needobjclass = NULL;
+ }
+
+ use = i;
+
+ /* allow override on "objectClass" type */
+ if (sm_strcasecmp(v, "objectClass") == 0 &&
+ lmap->ldap_attr_type[0] == SM_LDAP_ATTR_OBJCLASS)
+ {
+ use = 0;
+ }
+ else
+ {
+ /*
+ ** Don't add something to attribute
+ ** list twice.
+ */
+
+ for (j = 1; j < i; j++)
+ {
+ if (sm_strcasecmp(v, lmap->ldap_attr[j]) == 0)
+ {
+ syserr("Duplicate attribute (%s) in %s",
+ v, map->map_mname);
+ return false;
+ }
+ }
+
+ lmap->ldap_attr[use] = newstr(v);
+ if (needobjclass != NULL &&
+ *needobjclass != '\0' &&
+ *needobjclass != '*')
+ {
+ lmap->ldap_attr_needobjclass[use] = newstr(needobjclass);
+ }
+ else
+ {
+ lmap->ldap_attr_needobjclass[use] = NULL;
+ }
+
+ }
+
+ if (type != NULL && *type != '\0')
+ {
+ if (sm_strcasecmp(type, "dn") == 0)
+ {
+ recurse = true;
+ lmap->ldap_attr_type[use] = SM_LDAP_ATTR_DN;
+ }
+ else if (sm_strcasecmp(type, "filter") == 0)
+ {
+ recurse = true;
+ lmap->ldap_attr_type[use] = SM_LDAP_ATTR_FILTER;
+ }
+ else if (sm_strcasecmp(type, "url") == 0)
+ {
+ recurse = true;
+ lmap->ldap_attr_type[use] = SM_LDAP_ATTR_URL;
+ }
+ else if (sm_strcasecmp(type, "normal") == 0)
+ {
+ lmap->ldap_attr_type[use] = SM_LDAP_ATTR_NORMAL;
+ normalseen = true;
+ }
+ else
+ {
+ syserr("Unknown attribute type (%s) in %s",
+ type, map->map_mname);
+ return false;
+ }
+ }
+ else
+ {
+ lmap->ldap_attr_type[use] = SM_LDAP_ATTR_NORMAL;
+ normalseen = true;
+ }
+ i++;
+ }
+ }
+ lmap->ldap_attr[i] = NULL;
+
+ /* Set in case needed in future code */
+ attrssetup = true;
+
+ if (recurse && !normalseen)
+ {
+ syserr("LDAP recursion requested in %s but no returnable attribute given",
+ map->map_mname);
+ return false;
+ }
+ if (recurse && lmap->ldap_attrsonly == LDAPMAP_TRUE)
+ {
+ syserr("LDAP recursion requested in %s can not be used with -n",
+ map->map_mname);
+ return false;
+ }
+ }
+ map->map_db1 = (ARBPTR_T) lmap;
+ return true;
+}
+
+/*
+** LDAPMAP_SET_DEFAULTS -- Read default map spec from LDAPDefaults in .cf
+**
+** Parameters:
+** spec -- map argument string from LDAPDefaults option
+**
+** Returns:
+** None.
+*/
+
+void
+ldapmap_set_defaults(spec)
+ char *spec;
+{
+ STAB *class;
+ MAP map;
+
+ /* Allocate and set the default values */
+ if (LDAPDefaults == NULL)
+ LDAPDefaults = (SM_LDAP_STRUCT *) xalloc(sizeof *LDAPDefaults);
+ sm_ldap_clear(LDAPDefaults);
+
+ memset(&map, '\0', sizeof map);
+
+ /* look up the class */
+ class = stab("ldap", ST_MAPCLASS, ST_FIND);
+ if (class == NULL)
+ {
+ syserr("readcf: LDAPDefaultSpec: class ldap not available");
+ return;
+ }
+ map.map_class = &class->s_mapclass;
+ map.map_db1 = (ARBPTR_T) LDAPDefaults;
+ map.map_mname = "O LDAPDefaultSpec";
+
+ (void) ldapmap_parseargs(&map, spec);
+
+ /* These should never be set in LDAPDefaults */
+ if (map.map_mflags != (MF_TRY0NULL|MF_TRY1NULL) ||
+ map.map_spacesub != SpaceSub ||
+ map.map_app != NULL ||
+ map.map_tapp != NULL)
+ {
+ syserr("readcf: option LDAPDefaultSpec: Do not set non-LDAP specific flags");
+ SM_FREE_CLR(map.map_app);
+ SM_FREE_CLR(map.map_tapp);
+ }
+
+ if (LDAPDefaults->ldap_filter != NULL)
+ {
+ syserr("readcf: option LDAPDefaultSpec: Do not set the LDAP search filter");
+
+ /* don't free, it isn't malloc'ed in parseargs */
+ LDAPDefaults->ldap_filter = NULL;
+ }
+
+ if (LDAPDefaults->ldap_attr[0] != NULL)
+ {
+ syserr("readcf: option LDAPDefaultSpec: Do not set the requested LDAP attributes");
+ /* don't free, they aren't malloc'ed in parseargs */
+ LDAPDefaults->ldap_attr[0] = NULL;
+ }
+}
+#endif /* LDAPMAP */
+/*
+** PH map
+*/
+
+#if PH_MAP
+
+/*
+** Support for the CCSO Nameserver (ph/qi).
+** This code is intended to replace the so-called "ph mailer".
+** Contributed by Mark D. Roth <roth@uiuc.edu>. Contact him for support.
+*/
+
+/* what version of the ph map code we're running */
+static char phmap_id[128];
+
+/* sendmail version for phmap id string */
+extern const char Version[];
+
+/* assume we're using nph-1.2.x if not specified */
+# ifndef NPH_VERSION
+# define NPH_VERSION 10200
+# endif
+
+/* compatibility for versions older than nph-1.2.0 */
+# if NPH_VERSION < 10200
+# define PH_OPEN_ROUNDROBIN PH_ROUNDROBIN
+# define PH_OPEN_DONTID PH_DONTID
+# define PH_CLOSE_FAST PH_FASTCLOSE
+# define PH_ERR_DATAERR PH_DATAERR
+# define PH_ERR_NOMATCH PH_NOMATCH
+# endif /* NPH_VERSION < 10200 */
+
+/*
+** PH_MAP_PARSEARGS -- parse ph map definition args.
+*/
+
+bool
+ph_map_parseargs(map, args)
+ MAP *map;
+ char *args;
+{
+ register bool done;
+ register char *p = args;
+ PH_MAP_STRUCT *pmap = NULL;
+
+ /* initialize version string */
+ (void) sm_snprintf(phmap_id, sizeof phmap_id,
+ "sendmail-%s phmap-20010529 libphclient-%s",
+ Version, libphclient_version);
+
+ pmap = (PH_MAP_STRUCT *) xalloc(sizeof *pmap);
+
+ /* defaults */
+ pmap->ph_servers = NULL;
+ pmap->ph_field_list = NULL;
+ pmap->ph = NULL;
+ pmap->ph_timeout = 0;
+ pmap->ph_fastclose = 0;
+
+ map->map_mflags |= MF_TRY0NULL|MF_TRY1NULL;
+ for (;;)
+ {
+ while (isascii(*p) && isspace(*p))
+ p++;
+ if (*p != '-')
+ break;
+ switch (*++p)
+ {
+ case 'N':
+ map->map_mflags |= MF_INCLNULL;
+ map->map_mflags &= ~MF_TRY0NULL;
+ break;
+
+ case 'O':
+ map->map_mflags &= ~MF_TRY1NULL;
+ break;
+
+ case 'o':
+ map->map_mflags |= MF_OPTIONAL;
+ break;
+
+ case 'f':
+ map->map_mflags |= MF_NOFOLDCASE;
+ break;
+
+ case 'm':
+ map->map_mflags |= MF_MATCHONLY;
+ break;
+
+ case 'A':
+ map->map_mflags |= MF_APPEND;
+ break;
+
+ case 'q':
+ map->map_mflags |= MF_KEEPQUOTES;
+ break;
+
+ case 't':
+ map->map_mflags |= MF_NODEFER;
+ break;
+
+ case 'a':
+ map->map_app = ++p;
+ break;
+
+ case 'T':
+ map->map_tapp = ++p;
+ break;
+
+ case 'l':
+ while (isascii(*++p) && isspace(*p))
+ continue;
+ pmap->ph_timeout = atoi(p);
+ break;
+
+ case 'S':
+ map->map_spacesub = *++p;
+ break;
+
+ case 'D':
+ map->map_mflags |= MF_DEFER;
+ break;
+
+ case 'h': /* PH server list */
+ while (isascii(*++p) && isspace(*p))
+ continue;
+ pmap->ph_servers = p;
+ break;
+
+ case 'k': /* fields to search for */
+ while (isascii(*++p) && isspace(*p))
+ continue;
+ pmap->ph_field_list = p;
+ break;
+
+ default:
+ syserr("ph_map_parseargs: unknown option -%c", *p);
+ }
+
+ /* try to account for quoted strings */
+ done = isascii(*p) && isspace(*p);
+ while (*p != '\0' && !done)
+ {
+ if (*p == '"')
+ {
+ while (*++p != '"' && *p != '\0')
+ continue;
+ if (*p != '\0')
+ p++;
+ }
+ else
+ p++;
+ done = isascii(*p) && isspace(*p);
+ }
+
+ if (*p != '\0')
+ *p++ = '\0';
+ }
+
+ if (map->map_app != NULL)
+ map->map_app = newstr(ph_map_dequote(map->map_app));
+ if (map->map_tapp != NULL)
+ map->map_tapp = newstr(ph_map_dequote(map->map_tapp));
+
+ if (pmap->ph_field_list != NULL)
+ pmap->ph_field_list = newstr(ph_map_dequote(pmap->ph_field_list));
+
+ if (pmap->ph_servers != NULL)
+ pmap->ph_servers = newstr(ph_map_dequote(pmap->ph_servers));
+ else
+ {
+ syserr("ph_map_parseargs: -h flag is required");
+ return false;
+ }
+
+ map->map_db1 = (ARBPTR_T) pmap;
+ return true;
+}
+
+/*
+** PH_MAP_CLOSE -- close the connection to the ph server
+*/
+
+void
+ph_map_close(map)
+ MAP *map;
+{
+ PH_MAP_STRUCT *pmap;
+
+ pmap = (PH_MAP_STRUCT *)map->map_db1;
+ if (tTd(38, 9))
+ sm_dprintf("ph_map_close(%s): pmap->ph_fastclose=%d\n",
+ map->map_mname, pmap->ph_fastclose);
+
+
+ if (pmap->ph != NULL)
+ {
+ ph_set_sendhook(pmap->ph, NULL);
+ ph_set_recvhook(pmap->ph, NULL);
+ ph_close(pmap->ph, pmap->ph_fastclose);
+ }
+
+ map->map_mflags &= ~(MF_OPEN|MF_WRITABLE);
+}
+
+static jmp_buf PHTimeout;
+
+/* ARGSUSED */
+static void
+ph_timeout(unused)
+ int unused;
+{
+ /*
+ ** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+ ** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+ ** DOING.
+ */
+
+ errno = ETIMEDOUT;
+ longjmp(PHTimeout, 1);
+}
+
+static void
+#if NPH_VERSION >= 10200
+ph_map_send_debug(appdata, text)
+ void *appdata;
+#else
+ph_map_send_debug(text)
+#endif
+ char *text;
+{
+ if (LogLevel > 9)
+ sm_syslog(LOG_NOTICE, CurEnv->e_id,
+ "ph_map_send_debug: ==> %s", text);
+ if (tTd(38, 20))
+ sm_dprintf("ph_map_send_debug: ==> %s\n", text);
+}
+
+static void
+#if NPH_VERSION >= 10200
+ph_map_recv_debug(appdata, text)
+ void *appdata;
+#else
+ph_map_recv_debug(text)
+#endif
+ char *text;
+{
+ if (LogLevel > 10)
+ sm_syslog(LOG_NOTICE, CurEnv->e_id,
+ "ph_map_recv_debug: <== %s", text);
+ if (tTd(38, 21))
+ sm_dprintf("ph_map_recv_debug: <== %s\n", text);
+}
+
+/*
+** PH_MAP_OPEN -- sub for opening PH map
+*/
+bool
+ph_map_open(map, mode)
+ MAP *map;
+ int mode;
+{
+ PH_MAP_STRUCT *pmap;
+ register SM_EVENT *ev = NULL;
+ int save_errno = 0;
+ char *hostlist, *host;
+
+ if (tTd(38, 2))
+ sm_dprintf("ph_map_open(%s)\n", map->map_mname);
+
+ mode &= O_ACCMODE;
+ if (mode != O_RDONLY)
+ {
+ /* issue a pseudo-error message */
+ errno = SM_EMAPCANTWRITE;
+ return false;
+ }
+
+ if (CurEnv != NULL && CurEnv->e_sendmode == SM_DEFER &&
+ bitset(MF_DEFER, map->map_mflags))
+ {
+ if (tTd(9, 1))
+ sm_dprintf("ph_map_open(%s) => DEFERRED\n",
+ map->map_mname);
+
+ /*
+ ** Unset MF_DEFER here so that map_lookup() returns
+ ** a temporary failure using the bogus map and
+ ** map->map_tapp instead of the default permanent error.
+ */
+
+ map->map_mflags &= ~MF_DEFER;
+ return false;
+ }
+
+ pmap = (PH_MAP_STRUCT *)map->map_db1;
+ pmap->ph_fastclose = 0; /* refresh field for reopen */
+
+ /* try each host in the list */
+ hostlist = newstr(pmap->ph_servers);
+ for (host = strtok(hostlist, " ");
+ host != NULL;
+ host = strtok(NULL, " "))
+ {
+ /* set timeout */
+ if (pmap->ph_timeout != 0)
+ {
+ if (setjmp(PHTimeout) != 0)
+ {
+ ev = NULL;
+ if (LogLevel > 1)
+ sm_syslog(LOG_NOTICE, CurEnv->e_id,
+ "timeout connecting to PH server %.100s",
+ host);
+ errno = ETIMEDOUT;
+ goto ph_map_open_abort;
+ }
+ ev = sm_setevent(pmap->ph_timeout, ph_timeout, 0);
+ }
+
+ /* open connection to server */
+ if (ph_open(&(pmap->ph), host,
+ PH_OPEN_ROUNDROBIN|PH_OPEN_DONTID,
+ ph_map_send_debug, ph_map_recv_debug
+#if NPH_VERSION >= 10200
+ , NULL
+#endif
+ ) == 0
+ && ph_id(pmap->ph, phmap_id) == 0)
+ {
+ if (ev != NULL)
+ sm_clrevent(ev);
+ sm_free(hostlist); /* XXX */
+ return true;
+ }
+
+ ph_map_open_abort:
+ save_errno = errno;
+ if (ev != NULL)
+ sm_clrevent(ev);
+ pmap->ph_fastclose = PH_CLOSE_FAST;
+ ph_map_close(map);
+ errno = save_errno;
+ }
+
+ if (bitset(MF_NODEFER, map->map_mflags))
+ {
+ if (errno == 0)
+ errno = EAGAIN;
+ syserr("ph_map_open: %s: cannot connect to PH server",
+ map->map_mname);
+ }
+ else if (!bitset(MF_OPTIONAL, map->map_mflags) && LogLevel > 1)
+ sm_syslog(LOG_NOTICE, CurEnv->e_id,
+ "ph_map_open: %s: cannot connect to PH server",
+ map->map_mname);
+ sm_free(hostlist); /* XXX */
+ return false;
+}
+
+/*
+** PH_MAP_LOOKUP -- look up key from ph server
+*/
+
+char *
+ph_map_lookup(map, key, args, pstat)
+ MAP *map;
+ char *key;
+ char **args;
+ int *pstat;
+{
+ int i, save_errno = 0;
+ register SM_EVENT *ev = NULL;
+ PH_MAP_STRUCT *pmap;
+ char *value = NULL;
+
+ pmap = (PH_MAP_STRUCT *)map->map_db1;
+
+ *pstat = EX_OK;
+
+ /* set timeout */
+ if (pmap->ph_timeout != 0)
+ {
+ if (setjmp(PHTimeout) != 0)
+ {
+ ev = NULL;
+ if (LogLevel > 1)
+ sm_syslog(LOG_NOTICE, CurEnv->e_id,
+ "timeout during PH lookup of %.100s",
+ key);
+ errno = ETIMEDOUT;
+ *pstat = EX_TEMPFAIL;
+ goto ph_map_lookup_abort;
+ }
+ ev = sm_setevent(pmap->ph_timeout, ph_timeout, 0);
+ }
+
+ /* perform lookup */
+ i = ph_email_resolve(pmap->ph, key, pmap->ph_field_list, &value);
+ if (i == -1)
+ *pstat = EX_TEMPFAIL;
+ else if (i == PH_ERR_NOMATCH || i == PH_ERR_DATAERR)
+ *pstat = EX_UNAVAILABLE;
+
+ ph_map_lookup_abort:
+ if (ev != NULL)
+ sm_clrevent(ev);
+
+ /*
+ ** Close the connection if the timer popped
+ ** or we got a temporary PH error
+ */
+
+ if (*pstat == EX_TEMPFAIL)
+ {
+ save_errno = errno;
+ pmap->ph_fastclose = PH_CLOSE_FAST;
+ ph_map_close(map);
+ errno = save_errno;
+ }
+
+ if (*pstat == EX_OK)
+ {
+ if (tTd(38,20))
+ sm_dprintf("ph_map_lookup: %s => %s\n", key, value);
+
+ if (bitset(MF_MATCHONLY, map->map_mflags))
+ return map_rewrite(map, key, strlen(key), NULL);
+ else
+ return map_rewrite(map, value, strlen(value), args);
+ }
+
+ return NULL;
+}
+#endif /* PH_MAP */
+/*
+** syslog map
+*/
+
+#define map_prio map_lockfd /* overload field */
+
+/*
+** SYSLOG_MAP_PARSEARGS -- check for priority level to syslog messages.
+*/
+
+bool
+syslog_map_parseargs(map, args)
+ MAP *map;
+ char *args;
+{
+ char *p = args;
+ char *priority = NULL;
+
+ /* there is no check whether there is really an argument */
+ while (*p != '\0')
+ {
+ while (isascii(*p) && isspace(*p))
+ p++;
+ if (*p != '-')
+ break;
+ ++p;
+ if (*p == 'D')
+ {
+ map->map_mflags |= MF_DEFER;
+ ++p;
+ }
+ else if (*p == 'S')
+ {
+ map->map_spacesub = *++p;
+ if (*p != '\0')
+ p++;
+ }
+ else if (*p == 'L')
+ {
+ while (*++p != '\0' && isascii(*p) && isspace(*p))
+ continue;
+ if (*p == '\0')
+ break;
+ priority = p;
+ while (*p != '\0' && !(isascii(*p) && isspace(*p)))
+ p++;
+ if (*p != '\0')
+ *p++ = '\0';
+ }
+ else
+ {
+ syserr("Illegal option %c map syslog", *p);
+ ++p;
+ }
+ }
+
+ if (priority == NULL)
+ map->map_prio = LOG_INFO;
+ else
+ {
+ if (sm_strncasecmp("LOG_", priority, 4) == 0)
+ priority += 4;
+
+#ifdef LOG_EMERG
+ if (sm_strcasecmp("EMERG", priority) == 0)
+ map->map_prio = LOG_EMERG;
+ else
+#endif /* LOG_EMERG */
+#ifdef LOG_ALERT
+ if (sm_strcasecmp("ALERT", priority) == 0)
+ map->map_prio = LOG_ALERT;
+ else
+#endif /* LOG_ALERT */
+#ifdef LOG_CRIT
+ if (sm_strcasecmp("CRIT", priority) == 0)
+ map->map_prio = LOG_CRIT;
+ else
+#endif /* LOG_CRIT */
+#ifdef LOG_ERR
+ if (sm_strcasecmp("ERR", priority) == 0)
+ map->map_prio = LOG_ERR;
+ else
+#endif /* LOG_ERR */
+#ifdef LOG_WARNING
+ if (sm_strcasecmp("WARNING", priority) == 0)
+ map->map_prio = LOG_WARNING;
+ else
+#endif /* LOG_WARNING */
+#ifdef LOG_NOTICE
+ if (sm_strcasecmp("NOTICE", priority) == 0)
+ map->map_prio = LOG_NOTICE;
+ else
+#endif /* LOG_NOTICE */
+#ifdef LOG_INFO
+ if (sm_strcasecmp("INFO", priority) == 0)
+ map->map_prio = LOG_INFO;
+ else
+#endif /* LOG_INFO */
+#ifdef LOG_DEBUG
+ if (sm_strcasecmp("DEBUG", priority) == 0)
+ map->map_prio = LOG_DEBUG;
+ else
+#endif /* LOG_DEBUG */
+ {
+ syserr("syslog_map_parseargs: Unknown priority %s",
+ priority);
+ return false;
+ }
+ }
+ return true;
+}
+
+/*
+** SYSLOG_MAP_LOOKUP -- rewrite and syslog message. Always return empty string
+*/
+
+char *
+syslog_map_lookup(map, string, args, statp)
+ MAP *map;
+ char *string;
+ char **args;
+ int *statp;
+{
+ char *ptr = map_rewrite(map, string, strlen(string), args);
+
+ if (ptr != NULL)
+ {
+ if (tTd(38, 20))
+ sm_dprintf("syslog_map_lookup(%s (priority %d): %s\n",
+ map->map_mname, map->map_prio, ptr);
+
+ sm_syslog(map->map_prio, CurEnv->e_id, "%s", ptr);
+ }
+
+ *statp = EX_OK;
+ return "";
+}
+
+/*
+** HESIOD Modules
+*/
+
+#if HESIOD
+
+bool
+hes_map_open(map, mode)
+ MAP *map;
+ int mode;
+{
+ if (tTd(38, 2))
+ sm_dprintf("hes_map_open(%s, %s, %d)\n",
+ map->map_mname, map->map_file, mode);
+
+ if (mode != O_RDONLY)
+ {
+ /* issue a pseudo-error message */
+ errno = SM_EMAPCANTWRITE;
+ return false;
+ }
+
+# ifdef HESIOD_INIT
+ if (HesiodContext != NULL || hesiod_init(&HesiodContext) == 0)
+ return true;
+
+ if (!bitset(MF_OPTIONAL, map->map_mflags))
+ syserr("451 4.3.5 cannot initialize Hesiod map (%s)",
+ sm_errstring(errno));
+ return false;
+# else /* HESIOD_INIT */
+ if (hes_error() == HES_ER_UNINIT)
+ hes_init();
+ switch (hes_error())
+ {
+ case HES_ER_OK:
+ case HES_ER_NOTFOUND:
+ return true;
+ }
+
+ if (!bitset(MF_OPTIONAL, map->map_mflags))
+ syserr("451 4.3.5 cannot initialize Hesiod map (%d)", hes_error());
+
+ return false;
+# endif /* HESIOD_INIT */
+}
+
+char *
+hes_map_lookup(map, name, av, statp)
+ MAP *map;
+ char *name;
+ char **av;
+ int *statp;
+{
+ char **hp;
+
+ if (tTd(38, 20))
+ sm_dprintf("hes_map_lookup(%s, %s)\n", map->map_file, name);
+
+ if (name[0] == '\\')
+ {
+ char *np;
+ int nl;
+ int save_errno;
+ char nbuf[MAXNAME];
+
+ nl = strlen(name);
+ if (nl < sizeof nbuf - 1)
+ np = nbuf;
+ else
+ np = xalloc(strlen(name) + 2);
+ np[0] = '\\';
+ (void) sm_strlcpy(&np[1], name, (sizeof nbuf) - 1);
+# ifdef HESIOD_INIT
+ hp = hesiod_resolve(HesiodContext, np, map->map_file);
+# else /* HESIOD_INIT */
+ hp = hes_resolve(np, map->map_file);
+# endif /* HESIOD_INIT */
+ save_errno = errno;
+ if (np != nbuf)
+ sm_free(np); /* XXX */
+ errno = save_errno;
+ }
+ else
+ {
+# ifdef HESIOD_INIT
+ hp = hesiod_resolve(HesiodContext, name, map->map_file);
+# else /* HESIOD_INIT */
+ hp = hes_resolve(name, map->map_file);
+# endif /* HESIOD_INIT */
+ }
+# ifdef HESIOD_INIT
+ if (hp == NULL || *hp == NULL)
+ {
+ switch (errno)
+ {
+ case ENOENT:
+ *statp = EX_NOTFOUND;
+ break;
+ case ECONNREFUSED:
+ *statp = EX_TEMPFAIL;
+ break;
+ case EMSGSIZE:
+ case ENOMEM:
+ default:
+ *statp = EX_UNAVAILABLE;
+ break;
+ }
+ if (hp != NULL)
+ hesiod_free_list(HesiodContext, hp);
+ return NULL;
+ }
+# else /* HESIOD_INIT */
+ if (hp == NULL || hp[0] == NULL)
+ {
+ switch (hes_error())
+ {
+ case HES_ER_OK:
+ *statp = EX_OK;
+ break;
+
+ case HES_ER_NOTFOUND:
+ *statp = EX_NOTFOUND;
+ break;
+
+ case HES_ER_CONFIG:
+ *statp = EX_UNAVAILABLE;
+ break;
+
+ case HES_ER_NET:
+ *statp = EX_TEMPFAIL;
+ break;
+ }
+ return NULL;
+ }
+# endif /* HESIOD_INIT */
+
+ if (bitset(MF_MATCHONLY, map->map_mflags))
+ return map_rewrite(map, name, strlen(name), NULL);
+ else
+ return map_rewrite(map, hp[0], strlen(hp[0]), av);
+}
+
+/*
+** HES_MAP_CLOSE -- free the Hesiod context
+*/
+
+void
+hes_map_close(map)
+ MAP *map;
+{
+ if (tTd(38, 20))
+ sm_dprintf("hes_map_close(%s)\n", map->map_file);
+
+# ifdef HESIOD_INIT
+ /* Free the hesiod context */
+ if (HesiodContext != NULL)
+ {
+ hesiod_end(HesiodContext);
+ HesiodContext = NULL;
+ }
+# endif /* HESIOD_INIT */
+}
+
+#endif /* HESIOD */
+/*
+** NeXT NETINFO Modules
+*/
+
+#if NETINFO
+
+# define NETINFO_DEFAULT_DIR "/aliases"
+# define NETINFO_DEFAULT_PROPERTY "members"
+
+/*
+** NI_MAP_OPEN -- open NetInfo Aliases
+*/
+
+bool
+ni_map_open(map, mode)
+ MAP *map;
+ int mode;
+{
+ if (tTd(38, 2))
+ sm_dprintf("ni_map_open(%s, %s, %d)\n",
+ map->map_mname, map->map_file, mode);
+ mode &= O_ACCMODE;
+
+ if (*map->map_file == '\0')
+ map->map_file = NETINFO_DEFAULT_DIR;
+
+ if (map->map_valcolnm == NULL)
+ map->map_valcolnm = NETINFO_DEFAULT_PROPERTY;
+
+ if (map->map_coldelim == '\0')
+ {
+ if (bitset(MF_ALIAS, map->map_mflags))
+ map->map_coldelim = ',';
+ else if (bitset(MF_FILECLASS, map->map_mflags))
+ map->map_coldelim = ' ';
+ }
+ return true;
+}
+
+
+/*
+** NI_MAP_LOOKUP -- look up a datum in NetInfo
+*/
+
+char *
+ni_map_lookup(map, name, av, statp)
+ MAP *map;
+ char *name;
+ char **av;
+ int *statp;
+{
+ char *res;
+ char *propval;
+
+ if (tTd(38, 20))
+ sm_dprintf("ni_map_lookup(%s, %s)\n", map->map_mname, name);
+
+ propval = ni_propval(map->map_file, map->map_keycolnm, name,
+ map->map_valcolnm, map->map_coldelim);
+
+ if (propval == NULL)
+ return NULL;
+
+ SM_TRY
+ if (bitset(MF_MATCHONLY, map->map_mflags))
+ res = map_rewrite(map, name, strlen(name), NULL);
+ else
+ res = map_rewrite(map, propval, strlen(propval), av);
+ SM_FINALLY
+ sm_free(propval);
+ SM_END_TRY
+ return res;
+}
+
+
+static bool
+ni_getcanonname(name, hbsize, statp)
+ char *name;
+ int hbsize;
+ int *statp;
+{
+ char *vptr;
+ char *ptr;
+ char nbuf[MAXNAME + 1];
+
+ if (tTd(38, 20))
+ sm_dprintf("ni_getcanonname(%s)\n", name);
+
+ if (sm_strlcpy(nbuf, name, sizeof nbuf) >= sizeof nbuf)
+ {
+ *statp = EX_UNAVAILABLE;
+ return false;
+ }
+ (void) shorten_hostname(nbuf);
+
+ /* we only accept single token search key */
+ if (strchr(nbuf, '.'))
+ {
+ *statp = EX_NOHOST;
+ return false;
+ }
+
+ /* Do the search */
+ vptr = ni_propval("/machines", NULL, nbuf, "name", '\n');
+
+ if (vptr == NULL)
+ {
+ *statp = EX_NOHOST;
+ return false;
+ }
+
+ /* Only want the first machine name */
+ if ((ptr = strchr(vptr, '\n')) != NULL)
+ *ptr = '\0';
+
+ if (sm_strlcpy(name, vptr, hbsize) >= hbsize)
+ {
+ sm_free(vptr);
+ *statp = EX_UNAVAILABLE;
+ return true;
+ }
+ sm_free(vptr);
+ *statp = EX_OK;
+ return false;
+}
+#endif /* NETINFO */
+/*
+** TEXT (unindexed text file) Modules
+**
+** This code donated by Sun Microsystems.
+*/
+
+#define map_sff map_lockfd /* overload field */
+
+
+/*
+** TEXT_MAP_OPEN -- open text table
+*/
+
+bool
+text_map_open(map, mode)
+ MAP *map;
+ int mode;
+{
+ long sff;
+ int i;
+
+ if (tTd(38, 2))
+ sm_dprintf("text_map_open(%s, %s, %d)\n",
+ map->map_mname, map->map_file, mode);
+
+ mode &= O_ACCMODE;
+ if (mode != O_RDONLY)
+ {
+ errno = EPERM;
+ return false;
+ }
+
+ if (*map->map_file == '\0')
+ {
+ syserr("text map \"%s\": file name required",
+ map->map_mname);
+ return false;
+ }
+
+ if (map->map_file[0] != '/')
+ {
+ syserr("text map \"%s\": file name must be fully qualified",
+ map->map_mname);
+ return false;
+ }
+
+ sff = SFF_ROOTOK|SFF_REGONLY;
+ if (!bitnset(DBS_LINKEDMAPINWRITABLEDIR, DontBlameSendmail))
+ sff |= SFF_NOWLINK;
+ if (!bitnset(DBS_MAPINUNSAFEDIRPATH, DontBlameSendmail))
+ sff |= SFF_SAFEDIRPATH;
+ if ((i = safefile(map->map_file, RunAsUid, RunAsGid, RunAsUserName,
+ sff, S_IRUSR, NULL)) != 0)
+ {
+ int save_errno = errno;
+
+ /* cannot open this map */
+ if (tTd(38, 2))
+ sm_dprintf("\tunsafe map file: %d\n", i);
+ errno = save_errno;
+ if (!bitset(MF_OPTIONAL, map->map_mflags))
+ syserr("text map \"%s\": unsafe map file %s",
+ map->map_mname, map->map_file);
+ return false;
+ }
+
+ if (map->map_keycolnm == NULL)
+ map->map_keycolno = 0;
+ else
+ {
+ if (!(isascii(*map->map_keycolnm) && isdigit(*map->map_keycolnm)))
+ {
+ syserr("text map \"%s\", file %s: -k should specify a number, not %s",
+ map->map_mname, map->map_file,
+ map->map_keycolnm);
+ return false;
+ }
+ map->map_keycolno = atoi(map->map_keycolnm);
+ }
+
+ if (map->map_valcolnm == NULL)
+ map->map_valcolno = 0;
+ else
+ {
+ if (!(isascii(*map->map_valcolnm) && isdigit(*map->map_valcolnm)))
+ {
+ syserr("text map \"%s\", file %s: -v should specify a number, not %s",
+ map->map_mname, map->map_file,
+ map->map_valcolnm);
+ return false;
+ }
+ map->map_valcolno = atoi(map->map_valcolnm);
+ }
+
+ if (tTd(38, 2))
+ {
+ sm_dprintf("text_map_open(%s, %s): delimiter = ",
+ map->map_mname, map->map_file);
+ if (map->map_coldelim == '\0')
+ sm_dprintf("(white space)\n");
+ else
+ sm_dprintf("%c\n", map->map_coldelim);
+ }
+
+ map->map_sff = sff;
+ return true;
+}
+
+
+/*
+** TEXT_MAP_LOOKUP -- look up a datum in a TEXT table
+*/
+
+char *
+text_map_lookup(map, name, av, statp)
+ MAP *map;
+ char *name;
+ char **av;
+ int *statp;
+{
+ char *vp;
+ auto int vsize;
+ int buflen;
+ SM_FILE_T *f;
+ char delim;
+ int key_idx;
+ bool found_it;
+ long sff = map->map_sff;
+ char search_key[MAXNAME + 1];
+ char linebuf[MAXLINE];
+ char buf[MAXNAME + 1];
+
+ found_it = false;
+ if (tTd(38, 20))
+ sm_dprintf("text_map_lookup(%s, %s)\n", map->map_mname, name);
+
+ buflen = strlen(name);
+ if (buflen > sizeof search_key - 1)
+ buflen = sizeof search_key - 1; /* XXX just cut if off? */
+ memmove(search_key, name, buflen);
+ search_key[buflen] = '\0';
+ if (!bitset(MF_NOFOLDCASE, map->map_mflags))
+ makelower(search_key);
+
+ f = safefopen(map->map_file, O_RDONLY, FileMode, sff);
+ if (f == NULL)
+ {
+ map->map_mflags &= ~(MF_VALID|MF_OPEN);
+ *statp = EX_UNAVAILABLE;
+ return NULL;
+ }
+ key_idx = map->map_keycolno;
+ delim = map->map_coldelim;
+ while (sm_io_fgets(f, SM_TIME_DEFAULT,
+ linebuf, sizeof linebuf) != NULL)
+ {
+ char *p;
+
+ /* skip comment line */
+ if (linebuf[0] == '#')
+ continue;
+ p = strchr(linebuf, '\n');
+ if (p != NULL)
+ *p = '\0';
+ p = get_column(linebuf, key_idx, delim, buf, sizeof buf);
+ if (p != NULL && sm_strcasecmp(search_key, p) == 0)
+ {
+ found_it = true;
+ break;
+ }
+ }
+ (void) sm_io_close(f, SM_TIME_DEFAULT);
+ if (!found_it)
+ {
+ *statp = EX_NOTFOUND;
+ return NULL;
+ }
+ vp = get_column(linebuf, map->map_valcolno, delim, buf, sizeof buf);
+ if (vp == NULL)
+ {
+ *statp = EX_NOTFOUND;
+ return NULL;
+ }
+ vsize = strlen(vp);
+ *statp = EX_OK;
+ if (bitset(MF_MATCHONLY, map->map_mflags))
+ return map_rewrite(map, name, strlen(name), NULL);
+ else
+ return map_rewrite(map, vp, vsize, av);
+}
+
+/*
+** TEXT_GETCANONNAME -- look up canonical name in hosts file
+*/
+
+static bool
+text_getcanonname(name, hbsize, statp)
+ char *name;
+ int hbsize;
+ int *statp;
+{
+ bool found;
+ char *dot;
+ SM_FILE_T *f;
+ char linebuf[MAXLINE];
+ char cbuf[MAXNAME + 1];
+ char nbuf[MAXNAME + 1];
+
+ if (tTd(38, 20))
+ sm_dprintf("text_getcanonname(%s)\n", name);
+
+ if (sm_strlcpy(nbuf, name, sizeof nbuf) >= sizeof nbuf)
+ {
+ *statp = EX_UNAVAILABLE;
+ return false;
+ }
+ dot = shorten_hostname(nbuf);
+
+ f = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, HostsFile, SM_IO_RDONLY,
+ NULL);
+ if (f == NULL)
+ {
+ *statp = EX_UNAVAILABLE;
+ return false;
+ }
+ found = false;
+ while (!found &&
+ sm_io_fgets(f, SM_TIME_DEFAULT,
+ linebuf, sizeof linebuf) != NULL)
+ {
+ char *p = strpbrk(linebuf, "#\n");
+
+ if (p != NULL)
+ *p = '\0';
+ if (linebuf[0] != '\0')
+ found = extract_canonname(nbuf, dot, linebuf,
+ cbuf, sizeof cbuf);
+ }
+ (void) sm_io_close(f, SM_TIME_DEFAULT);
+ if (!found)
+ {
+ *statp = EX_NOHOST;
+ return false;
+ }
+
+ if (sm_strlcpy(name, cbuf, hbsize) >= hbsize)
+ {
+ *statp = EX_UNAVAILABLE;
+ return false;
+ }
+ *statp = EX_OK;
+ return true;
+}
+/*
+** STAB (Symbol Table) Modules
+*/
+
+
+/*
+** STAB_MAP_LOOKUP -- look up alias in symbol table
+*/
+
+/* ARGSUSED2 */
+char *
+stab_map_lookup(map, name, av, pstat)
+ register MAP *map;
+ char *name;
+ char **av;
+ int *pstat;
+{
+ register STAB *s;
+
+ if (tTd(38, 20))
+ sm_dprintf("stab_lookup(%s, %s)\n",
+ map->map_mname, name);
+
+ s = stab(name, ST_ALIAS, ST_FIND);
+ if (s == NULL)
+ return NULL;
+ if (bitset(MF_MATCHONLY, map->map_mflags))
+ return map_rewrite(map, name, strlen(name), NULL);
+ else
+ return map_rewrite(map, s->s_alias, strlen(s->s_alias), av);
+}
+
+/*
+** STAB_MAP_STORE -- store in symtab (actually using during init, not rebuild)
+*/
+
+void
+stab_map_store(map, lhs, rhs)
+ register MAP *map;
+ char *lhs;
+ char *rhs;
+{
+ register STAB *s;
+
+ s = stab(lhs, ST_ALIAS, ST_ENTER);
+ s->s_alias = newstr(rhs);
+}
+
+
+/*
+** STAB_MAP_OPEN -- initialize (reads data file)
+**
+** This is a wierd case -- it is only intended as a fallback for
+** aliases. For this reason, opens for write (only during a
+** "newaliases") always fails, and opens for read open the
+** actual underlying text file instead of the database.
+*/
+
+bool
+stab_map_open(map, mode)
+ register MAP *map;
+ int mode;
+{
+ SM_FILE_T *af;
+ long sff;
+ struct stat st;
+
+ if (tTd(38, 2))
+ sm_dprintf("stab_map_open(%s, %s, %d)\n",
+ map->map_mname, map->map_file, mode);
+
+ mode &= O_ACCMODE;
+ if (mode != O_RDONLY)
+ {
+ errno = EPERM;
+ return false;
+ }
+
+ sff = SFF_ROOTOK|SFF_REGONLY;
+ if (!bitnset(DBS_LINKEDMAPINWRITABLEDIR, DontBlameSendmail))
+ sff |= SFF_NOWLINK;
+ if (!bitnset(DBS_MAPINUNSAFEDIRPATH, DontBlameSendmail))
+ sff |= SFF_SAFEDIRPATH;
+ af = safefopen(map->map_file, O_RDONLY, 0444, sff);
+ if (af == NULL)
+ return false;
+ readaliases(map, af, false, false);
+
+ if (fstat(sm_io_getinfo(af, SM_IO_WHAT_FD, NULL), &st) >= 0)
+ map->map_mtime = st.st_mtime;
+ (void) sm_io_close(af, SM_TIME_DEFAULT);
+
+ return true;
+}
+/*
+** Implicit Modules
+**
+** Tries several types. For back compatibility of aliases.
+*/
+
+
+/*
+** IMPL_MAP_LOOKUP -- lookup in best open database
+*/
+
+char *
+impl_map_lookup(map, name, av, pstat)
+ MAP *map;
+ char *name;
+ char **av;
+ int *pstat;
+{
+ if (tTd(38, 20))
+ sm_dprintf("impl_map_lookup(%s, %s)\n",
+ map->map_mname, name);
+
+#if NEWDB
+ if (bitset(MF_IMPL_HASH, map->map_mflags))
+ return db_map_lookup(map, name, av, pstat);
+#endif /* NEWDB */
+#if NDBM
+ if (bitset(MF_IMPL_NDBM, map->map_mflags))
+ return ndbm_map_lookup(map, name, av, pstat);
+#endif /* NDBM */
+ return stab_map_lookup(map, name, av, pstat);
+}
+
+/*
+** IMPL_MAP_STORE -- store in open databases
+*/
+
+void
+impl_map_store(map, lhs, rhs)
+ MAP *map;
+ char *lhs;
+ char *rhs;
+{
+ if (tTd(38, 12))
+ sm_dprintf("impl_map_store(%s, %s, %s)\n",
+ map->map_mname, lhs, rhs);
+#if NEWDB
+ if (bitset(MF_IMPL_HASH, map->map_mflags))
+ db_map_store(map, lhs, rhs);
+#endif /* NEWDB */
+#if NDBM
+ if (bitset(MF_IMPL_NDBM, map->map_mflags))
+ ndbm_map_store(map, lhs, rhs);
+#endif /* NDBM */
+ stab_map_store(map, lhs, rhs);
+}
+
+/*
+** IMPL_MAP_OPEN -- implicit database open
+*/
+
+bool
+impl_map_open(map, mode)
+ MAP *map;
+ int mode;
+{
+ if (tTd(38, 2))
+ sm_dprintf("impl_map_open(%s, %s, %d)\n",
+ map->map_mname, map->map_file, mode);
+
+ mode &= O_ACCMODE;
+#if NEWDB
+ map->map_mflags |= MF_IMPL_HASH;
+ if (hash_map_open(map, mode))
+ {
+# ifdef NDBM_YP_COMPAT
+ if (mode == O_RDONLY || strstr(map->map_file, "/yp/") == NULL)
+# endif /* NDBM_YP_COMPAT */
+ return true;
+ }
+ else
+ map->map_mflags &= ~MF_IMPL_HASH;
+#endif /* NEWDB */
+#if NDBM
+ map->map_mflags |= MF_IMPL_NDBM;
+ if (ndbm_map_open(map, mode))
+ {
+ return true;
+ }
+ else
+ map->map_mflags &= ~MF_IMPL_NDBM;
+#endif /* NDBM */
+
+#if defined(NEWDB) || defined(NDBM)
+ if (Verbose)
+ message("WARNING: cannot open alias database %s%s",
+ map->map_file,
+ mode == O_RDONLY ? "; reading text version" : "");
+#else /* defined(NEWDB) || defined(NDBM) */
+ if (mode != O_RDONLY)
+ usrerr("Cannot rebuild aliases: no database format defined");
+#endif /* defined(NEWDB) || defined(NDBM) */
+
+ if (mode == O_RDONLY)
+ return stab_map_open(map, mode);
+ else
+ return false;
+}
+
+
+/*
+** IMPL_MAP_CLOSE -- close any open database(s)
+*/
+
+void
+impl_map_close(map)
+ MAP *map;
+{
+ if (tTd(38, 9))
+ sm_dprintf("impl_map_close(%s, %s, %lx)\n",
+ map->map_mname, map->map_file, map->map_mflags);
+#if NEWDB
+ if (bitset(MF_IMPL_HASH, map->map_mflags))
+ {
+ db_map_close(map);
+ map->map_mflags &= ~MF_IMPL_HASH;
+ }
+#endif /* NEWDB */
+
+#if NDBM
+ if (bitset(MF_IMPL_NDBM, map->map_mflags))
+ {
+ ndbm_map_close(map);
+ map->map_mflags &= ~MF_IMPL_NDBM;
+ }
+#endif /* NDBM */
+}
+/*
+** User map class.
+**
+** Provides access to the system password file.
+*/
+
+/*
+** USER_MAP_OPEN -- open user map
+**
+** Really just binds field names to field numbers.
+*/
+
+bool
+user_map_open(map, mode)
+ MAP *map;
+ int mode;
+{
+ if (tTd(38, 2))
+ sm_dprintf("user_map_open(%s, %d)\n",
+ map->map_mname, mode);
+
+ mode &= O_ACCMODE;
+ if (mode != O_RDONLY)
+ {
+ /* issue a pseudo-error message */
+ errno = SM_EMAPCANTWRITE;
+ return false;
+ }
+ if (map->map_valcolnm == NULL)
+ /* EMPTY */
+ /* nothing */ ;
+ else if (sm_strcasecmp(map->map_valcolnm, "name") == 0)
+ map->map_valcolno = 1;
+ else if (sm_strcasecmp(map->map_valcolnm, "passwd") == 0)
+ map->map_valcolno = 2;
+ else if (sm_strcasecmp(map->map_valcolnm, "uid") == 0)
+ map->map_valcolno = 3;
+ else if (sm_strcasecmp(map->map_valcolnm, "gid") == 0)
+ map->map_valcolno = 4;
+ else if (sm_strcasecmp(map->map_valcolnm, "gecos") == 0)
+ map->map_valcolno = 5;
+ else if (sm_strcasecmp(map->map_valcolnm, "dir") == 0)
+ map->map_valcolno = 6;
+ else if (sm_strcasecmp(map->map_valcolnm, "shell") == 0)
+ map->map_valcolno = 7;
+ else
+ {
+ syserr("User map %s: unknown column name %s",
+ map->map_mname, map->map_valcolnm);
+ return false;
+ }
+ return true;
+}
+
+
+/*
+** USER_MAP_LOOKUP -- look up a user in the passwd file.
+*/
+
+/* ARGSUSED3 */
+char *
+user_map_lookup(map, key, av, statp)
+ MAP *map;
+ char *key;
+ char **av;
+ int *statp;
+{
+ auto bool fuzzy;
+ SM_MBDB_T user;
+
+ if (tTd(38, 20))
+ sm_dprintf("user_map_lookup(%s, %s)\n",
+ map->map_mname, key);
+
+ *statp = finduser(key, &fuzzy, &user);
+ if (*statp != EX_OK)
+ return NULL;
+ if (bitset(MF_MATCHONLY, map->map_mflags))
+ return map_rewrite(map, key, strlen(key), NULL);
+ else
+ {
+ char *rwval = NULL;
+ char buf[30];
+
+ switch (map->map_valcolno)
+ {
+ case 0:
+ case 1:
+ rwval = user.mbdb_name;
+ break;
+
+ case 2:
+ rwval = "x"; /* passwd no longer supported */
+ break;
+
+ case 3:
+ (void) sm_snprintf(buf, sizeof buf, "%d",
+ (int) user.mbdb_uid);
+ rwval = buf;
+ break;
+
+ case 4:
+ (void) sm_snprintf(buf, sizeof buf, "%d",
+ (int) user.mbdb_gid);
+ rwval = buf;
+ break;
+
+ case 5:
+ rwval = user.mbdb_fullname;
+ break;
+
+ case 6:
+ rwval = user.mbdb_homedir;
+ break;
+
+ case 7:
+ rwval = user.mbdb_shell;
+ break;
+ }
+ return map_rewrite(map, rwval, strlen(rwval), av);
+ }
+}
+/*
+** Program map type.
+**
+** This provides access to arbitrary programs. It should be used
+** only very sparingly, since there is no way to bound the cost
+** of invoking an arbitrary program.
+*/
+
+char *
+prog_map_lookup(map, name, av, statp)
+ MAP *map;
+ char *name;
+ char **av;
+ int *statp;
+{
+ int i;
+ int save_errno;
+ int fd;
+ int status;
+ auto pid_t pid;
+ register char *p;
+ char *rval;
+ char *argv[MAXPV + 1];
+ char buf[MAXLINE];
+
+ if (tTd(38, 20))
+ sm_dprintf("prog_map_lookup(%s, %s) %s\n",
+ map->map_mname, name, map->map_file);
+
+ i = 0;
+ argv[i++] = map->map_file;
+ if (map->map_rebuild != NULL)
+ {
+ (void) sm_strlcpy(buf, map->map_rebuild, sizeof buf);
+ for (p = strtok(buf, " \t"); p != NULL; p = strtok(NULL, " \t"))
+ {
+ if (i >= MAXPV - 1)
+ break;
+ argv[i++] = p;
+ }
+ }
+ argv[i++] = name;
+ argv[i] = NULL;
+ if (tTd(38, 21))
+ {
+ sm_dprintf("prog_open:");
+ for (i = 0; argv[i] != NULL; i++)
+ sm_dprintf(" %s", argv[i]);
+ sm_dprintf("\n");
+ }
+ (void) sm_blocksignal(SIGCHLD);
+ pid = prog_open(argv, &fd, CurEnv);
+ if (pid < 0)
+ {
+ if (!bitset(MF_OPTIONAL, map->map_mflags))
+ syserr("prog_map_lookup(%s) failed (%s) -- closing",
+ map->map_mname, sm_errstring(errno));
+ else if (tTd(38, 9))
+ sm_dprintf("prog_map_lookup(%s) failed (%s) -- closing",
+ map->map_mname, sm_errstring(errno));
+ map->map_mflags &= ~(MF_VALID|MF_OPEN);
+ *statp = EX_OSFILE;
+ return NULL;
+ }
+ i = read(fd, buf, sizeof buf - 1);
+ if (i < 0)
+ {
+ syserr("prog_map_lookup(%s): read error %s",
+ map->map_mname, sm_errstring(errno));
+ rval = NULL;
+ }
+ else if (i == 0)
+ {
+ if (tTd(38, 20))
+ sm_dprintf("prog_map_lookup(%s): empty answer\n",
+ map->map_mname);
+ rval = NULL;
+ }
+ else
+ {
+ buf[i] = '\0';
+ p = strchr(buf, '\n');
+ if (p != NULL)
+ *p = '\0';
+
+ /* collect the return value */
+ if (bitset(MF_MATCHONLY, map->map_mflags))
+ rval = map_rewrite(map, name, strlen(name), NULL);
+ else
+ rval = map_rewrite(map, buf, strlen(buf), av);
+
+ /* now flush any additional output */
+ while ((i = read(fd, buf, sizeof buf)) > 0)
+ continue;
+ }
+
+ /* wait for the process to terminate */
+ (void) close(fd);
+ status = waitfor(pid);
+ save_errno = errno;
+ (void) sm_releasesignal(SIGCHLD);
+ errno = save_errno;
+
+ if (status == -1)
+ {
+ syserr("prog_map_lookup(%s): wait error %s",
+ map->map_mname, sm_errstring(errno));
+ *statp = EX_SOFTWARE;
+ rval = NULL;
+ }
+ else if (WIFEXITED(status))
+ {
+ if ((*statp = WEXITSTATUS(status)) != EX_OK)
+ rval = NULL;
+ }
+ else
+ {
+ syserr("prog_map_lookup(%s): child died on signal %d",
+ map->map_mname, status);
+ *statp = EX_UNAVAILABLE;
+ rval = NULL;
+ }
+ return rval;
+}
+/*
+** Sequenced map type.
+**
+** Tries each map in order until something matches, much like
+** implicit. Stores go to the first map in the list that can
+** support storing.
+**
+** This is slightly unusual in that there are two interfaces.
+** The "sequence" interface lets you stack maps arbitrarily.
+** The "switch" interface builds a sequence map by looking
+** at a system-dependent configuration file such as
+** /etc/nsswitch.conf on Solaris or /etc/svc.conf on Ultrix.
+**
+** We don't need an explicit open, since all maps are
+** opened on demand.
+*/
+
+/*
+** SEQ_MAP_PARSE -- Sequenced map parsing
+*/
+
+bool
+seq_map_parse(map, ap)
+ MAP *map;
+ char *ap;
+{
+ int maxmap;
+
+ if (tTd(38, 2))
+ sm_dprintf("seq_map_parse(%s, %s)\n", map->map_mname, ap);
+ maxmap = 0;
+ while (*ap != '\0')
+ {
+ register char *p;
+ STAB *s;
+
+ /* find beginning of map name */
+ while (isascii(*ap) && isspace(*ap))
+ ap++;
+ for (p = ap;
+ (isascii(*p) && isalnum(*p)) || *p == '_' || *p == '.';
+ p++)
+ continue;
+ if (*p != '\0')
+ *p++ = '\0';
+ while (*p != '\0' && (!isascii(*p) || !isalnum(*p)))
+ p++;
+ if (*ap == '\0')
+ {
+ ap = p;
+ continue;
+ }
+ s = stab(ap, ST_MAP, ST_FIND);
+ if (s == NULL)
+ {
+ syserr("Sequence map %s: unknown member map %s",
+ map->map_mname, ap);
+ }
+ else if (maxmap >= MAXMAPSTACK)
+ {
+ syserr("Sequence map %s: too many member maps (%d max)",
+ map->map_mname, MAXMAPSTACK);
+ maxmap++;
+ }
+ else if (maxmap < MAXMAPSTACK)
+ {
+ map->map_stack[maxmap++] = &s->s_map;
+ }
+ ap = p;
+ }
+ return true;
+}
+
+/*
+** SWITCH_MAP_OPEN -- open a switched map
+**
+** This looks at the system-dependent configuration and builds
+** a sequence map that does the same thing.
+**
+** Every system must define a switch_map_find routine in conf.c
+** that will return the list of service types associated with a
+** given service class.
+*/
+
+bool
+switch_map_open(map, mode)
+ MAP *map;
+ int mode;
+{
+ int mapno;
+ int nmaps;
+ char *maptype[MAXMAPSTACK];
+
+ if (tTd(38, 2))
+ sm_dprintf("switch_map_open(%s, %s, %d)\n",
+ map->map_mname, map->map_file, mode);
+
+ mode &= O_ACCMODE;
+ nmaps = switch_map_find(map->map_file, maptype, map->map_return);
+ if (tTd(38, 19))
+ {
+ sm_dprintf("\tswitch_map_find => %d\n", nmaps);
+ for (mapno = 0; mapno < nmaps; mapno++)
+ sm_dprintf("\t\t%s\n", maptype[mapno]);
+ }
+ if (nmaps <= 0 || nmaps > MAXMAPSTACK)
+ return false;
+
+ for (mapno = 0; mapno < nmaps; mapno++)
+ {
+ register STAB *s;
+ char nbuf[MAXNAME + 1];
+
+ if (maptype[mapno] == NULL)
+ continue;
+ (void) sm_strlcpyn(nbuf, sizeof nbuf, 3,
+ map->map_mname, ".", maptype[mapno]);
+ s = stab(nbuf, ST_MAP, ST_FIND);
+ if (s == NULL)
+ {
+ syserr("Switch map %s: unknown member map %s",
+ map->map_mname, nbuf);
+ }
+ else
+ {
+ map->map_stack[mapno] = &s->s_map;
+ if (tTd(38, 4))
+ sm_dprintf("\tmap_stack[%d] = %s:%s\n",
+ mapno,
+ s->s_map.map_class->map_cname,
+ nbuf);
+ }
+ }
+ return true;
+}
+
+#if 0
+/*
+** SEQ_MAP_CLOSE -- close all underlying maps
+*/
+
+void
+seq_map_close(map)
+ MAP *map;
+{
+ int mapno;
+
+ if (tTd(38, 9))
+ sm_dprintf("seq_map_close(%s)\n", map->map_mname);
+
+ for (mapno = 0; mapno < MAXMAPSTACK; mapno++)
+ {
+ MAP *mm = map->map_stack[mapno];
+
+ if (mm == NULL || !bitset(MF_OPEN, mm->map_mflags))
+ continue;
+ mm->map_mflags |= MF_CLOSING;
+ mm->map_class->map_close(mm);
+ mm->map_mflags &= ~(MF_OPEN|MF_WRITABLE|MF_CLOSING);
+ }
+}
+#endif /* 0 */
+
+/*
+** SEQ_MAP_LOOKUP -- sequenced map lookup
+*/
+
+char *
+seq_map_lookup(map, key, args, pstat)
+ MAP *map;
+ char *key;
+ char **args;
+ int *pstat;
+{
+ int mapno;
+ int mapbit = 0x01;
+ bool tempfail = false;
+
+ if (tTd(38, 20))
+ sm_dprintf("seq_map_lookup(%s, %s)\n", map->map_mname, key);
+
+ for (mapno = 0; mapno < MAXMAPSTACK; mapbit <<= 1, mapno++)
+ {
+ MAP *mm = map->map_stack[mapno];
+ char *rv;
+
+ if (mm == NULL)
+ continue;
+ if (!bitset(MF_OPEN, mm->map_mflags) &&
+ !openmap(mm))
+ {
+ if (bitset(mapbit, map->map_return[MA_UNAVAIL]))
+ {
+ *pstat = EX_UNAVAILABLE;
+ return NULL;
+ }
+ continue;
+ }
+ *pstat = EX_OK;
+ rv = mm->map_class->map_lookup(mm, key, args, pstat);
+ if (rv != NULL)
+ return rv;
+ if (*pstat == EX_TEMPFAIL)
+ {
+ if (bitset(mapbit, map->map_return[MA_TRYAGAIN]))
+ return NULL;
+ tempfail = true;
+ }
+ else if (bitset(mapbit, map->map_return[MA_NOTFOUND]))
+ break;
+ }
+ if (tempfail)
+ *pstat = EX_TEMPFAIL;
+ else if (*pstat == EX_OK)
+ *pstat = EX_NOTFOUND;
+ return NULL;
+}
+
+/*
+** SEQ_MAP_STORE -- sequenced map store
+*/
+
+void
+seq_map_store(map, key, val)
+ MAP *map;
+ char *key;
+ char *val;
+{
+ int mapno;
+
+ if (tTd(38, 12))
+ sm_dprintf("seq_map_store(%s, %s, %s)\n",
+ map->map_mname, key, val);
+
+ for (mapno = 0; mapno < MAXMAPSTACK; mapno++)
+ {
+ MAP *mm = map->map_stack[mapno];
+
+ if (mm == NULL || !bitset(MF_WRITABLE, mm->map_mflags))
+ continue;
+
+ mm->map_class->map_store(mm, key, val);
+ return;
+ }
+ syserr("seq_map_store(%s, %s, %s): no writable map",
+ map->map_mname, key, val);
+}
+/*
+** NULL stubs
+*/
+
+/* ARGSUSED */
+bool
+null_map_open(map, mode)
+ MAP *map;
+ int mode;
+{
+ return true;
+}
+
+/* ARGSUSED */
+void
+null_map_close(map)
+ MAP *map;
+{
+ return;
+}
+
+char *
+null_map_lookup(map, key, args, pstat)
+ MAP *map;
+ char *key;
+ char **args;
+ int *pstat;
+{
+ *pstat = EX_NOTFOUND;
+ return NULL;
+}
+
+/* ARGSUSED */
+void
+null_map_store(map, key, val)
+ MAP *map;
+ char *key;
+ char *val;
+{
+ return;
+}
+
+/*
+** BOGUS stubs
+*/
+
+char *
+bogus_map_lookup(map, key, args, pstat)
+ MAP *map;
+ char *key;
+ char **args;
+ int *pstat;
+{
+ *pstat = EX_TEMPFAIL;
+ return NULL;
+}
+
+MAPCLASS BogusMapClass =
+{
+ "bogus-map", NULL, 0,
+ NULL, bogus_map_lookup, null_map_store,
+ null_map_open, null_map_close,
+};
+/*
+** MACRO modules
+*/
+
+char *
+macro_map_lookup(map, name, av, statp)
+ MAP *map;
+ char *name;
+ char **av;
+ int *statp;
+{
+ int mid;
+
+ if (tTd(38, 20))
+ sm_dprintf("macro_map_lookup(%s, %s)\n", map->map_mname,
+ name == NULL ? "NULL" : name);
+
+ if (name == NULL ||
+ *name == '\0' ||
+ (mid = macid(name)) == 0)
+ {
+ *statp = EX_CONFIG;
+ return NULL;
+ }
+
+ if (av[1] == NULL)
+ macdefine(&CurEnv->e_macro, A_PERM, mid, NULL);
+ else
+ macdefine(&CurEnv->e_macro, A_TEMP, mid, av[1]);
+
+ *statp = EX_OK;
+ return "";
+}
+/*
+** REGEX modules
+*/
+
+#if MAP_REGEX
+
+# include <regex.h>
+
+# define DEFAULT_DELIM CONDELSE
+# define END_OF_FIELDS -1
+# define ERRBUF_SIZE 80
+# define MAX_MATCH 32
+
+# define xnalloc(s) memset(xalloc(s), '\0', s);
+
+struct regex_map
+{
+ regex_t *regex_pattern_buf; /* xalloc it */
+ int *regex_subfields; /* move to type MAP */
+ char *regex_delim; /* move to type MAP */
+};
+
+static int parse_fields __P((char *, int *, int, int));
+static char *regex_map_rewrite __P((MAP *, const char*, size_t, char **));
+
+static int
+parse_fields(s, ibuf, blen, nr_substrings)
+ char *s;
+ int *ibuf; /* array */
+ int blen; /* number of elements in ibuf */
+ int nr_substrings; /* number of substrings in the pattern */
+{
+ register char *cp;
+ int i = 0;
+ bool lastone = false;
+
+ blen--; /* for terminating END_OF_FIELDS */
+ cp = s;
+ do
+ {
+ for (;; cp++)
+ {
+ if (*cp == ',')
+ {
+ *cp = '\0';
+ break;
+ }
+ if (*cp == '\0')
+ {
+ lastone = true;
+ break;
+ }
+ }
+ if (i < blen)
+ {
+ int val = atoi(s);
+
+ if (val < 0 || val >= nr_substrings)
+ {
+ syserr("field (%d) out of range, only %d substrings in pattern",
+ val, nr_substrings);
+ return -1;
+ }
+ ibuf[i++] = val;
+ }
+ else
+ {
+ syserr("too many fields, %d max", blen);
+ return -1;
+ }
+ s = ++cp;
+ } while (!lastone);
+ ibuf[i] = END_OF_FIELDS;
+ return i;
+}
+
+bool
+regex_map_init(map, ap)
+ MAP *map;
+ char *ap;
+{
+ int regerr;
+ struct regex_map *map_p;
+ register char *p;
+ char *sub_param = NULL;
+ int pflags;
+ static char defdstr[] = { (char) DEFAULT_DELIM, '\0' };
+
+ if (tTd(38, 2))
+ sm_dprintf("regex_map_init: mapname '%s', args '%s'\n",
+ map->map_mname, ap);
+
+ pflags = REG_ICASE | REG_EXTENDED | REG_NOSUB;
+ p = ap;
+ map_p = (struct regex_map *) xnalloc(sizeof *map_p);
+ map_p->regex_pattern_buf = (regex_t *)xnalloc(sizeof(regex_t));
+
+ for (;;)
+ {
+ while (isascii(*p) && isspace(*p))
+ p++;
+ if (*p != '-')
+ break;
+ switch (*++p)
+ {
+ case 'n': /* not */
+ map->map_mflags |= MF_REGEX_NOT;
+ break;
+
+ case 'f': /* case sensitive */
+ map->map_mflags |= MF_NOFOLDCASE;
+ pflags &= ~REG_ICASE;
+ break;
+
+ case 'b': /* basic regular expressions */
+ pflags &= ~REG_EXTENDED;
+ break;
+
+ case 's': /* substring match () syntax */
+ sub_param = ++p;
+ pflags &= ~REG_NOSUB;
+ break;
+
+ case 'd': /* delimiter */
+ map_p->regex_delim = ++p;
+ break;
+
+ case 'a': /* map append */
+ map->map_app = ++p;
+ break;
+
+ case 'm': /* matchonly */
+ map->map_mflags |= MF_MATCHONLY;
+ break;
+
+ case 'q':
+ map->map_mflags |= MF_KEEPQUOTES;
+ break;
+
+ case 'S':
+ map->map_spacesub = *++p;
+ break;
+
+ case 'D':
+ map->map_mflags |= MF_DEFER;
+ break;
+
+ }
+ while (*p != '\0' && !(isascii(*p) && isspace(*p)))
+ p++;
+ if (*p != '\0')
+ *p++ = '\0';
+ }
+ if (tTd(38, 3))
+ sm_dprintf("regex_map_init: compile '%s' 0x%x\n", p, pflags);
+
+ if ((regerr = regcomp(map_p->regex_pattern_buf, p, pflags)) != 0)
+ {
+ /* Errorhandling */
+ char errbuf[ERRBUF_SIZE];
+
+ (void) regerror(regerr, map_p->regex_pattern_buf,
+ errbuf, sizeof errbuf);
+ syserr("pattern-compile-error: %s", errbuf);
+ sm_free(map_p->regex_pattern_buf); /* XXX */
+ sm_free(map_p); /* XXX */
+ return false;
+ }
+
+ if (map->map_app != NULL)
+ map->map_app = newstr(map->map_app);
+ if (map_p->regex_delim != NULL)
+ map_p->regex_delim = newstr(map_p->regex_delim);
+ else
+ map_p->regex_delim = defdstr;
+
+ if (!bitset(REG_NOSUB, pflags))
+ {
+ /* substring matching */
+ int substrings;
+ int *fields = (int *) xalloc(sizeof(int) * (MAX_MATCH + 1));
+
+ substrings = map_p->regex_pattern_buf->re_nsub + 1;
+
+ if (tTd(38, 3))
+ sm_dprintf("regex_map_init: nr of substrings %d\n",
+ substrings);
+
+ if (substrings >= MAX_MATCH)
+ {
+ syserr("too many substrings, %d max", MAX_MATCH);
+ sm_free(map_p->regex_pattern_buf); /* XXX */
+ sm_free(map_p); /* XXX */
+ return false;
+ }
+ if (sub_param != NULL && sub_param[0] != '\0')
+ {
+ /* optional parameter -sfields */
+ if (parse_fields(sub_param, fields,
+ MAX_MATCH + 1, substrings) == -1)
+ return false;
+ }
+ else
+ {
+ int i;
+
+ /* set default fields */
+ for (i = 0; i < substrings; i++)
+ fields[i] = i;
+ fields[i] = END_OF_FIELDS;
+ }
+ map_p->regex_subfields = fields;
+ if (tTd(38, 3))
+ {
+ int *ip;
+
+ sm_dprintf("regex_map_init: subfields");
+ for (ip = fields; *ip != END_OF_FIELDS; ip++)
+ sm_dprintf(" %d", *ip);
+ sm_dprintf("\n");
+ }
+ }
+ map->map_db1 = (ARBPTR_T) map_p; /* dirty hack */
+ return true;
+}
+
+static char *
+regex_map_rewrite(map, s, slen, av)
+ MAP *map;
+ const char *s;
+ size_t slen;
+ char **av;
+{
+ if (bitset(MF_MATCHONLY, map->map_mflags))
+ return map_rewrite(map, av[0], strlen(av[0]), NULL);
+ else
+ return map_rewrite(map, s, slen, av);
+}
+
+char *
+regex_map_lookup(map, name, av, statp)
+ MAP *map;
+ char *name;
+ char **av;
+ int *statp;
+{
+ int reg_res;
+ struct regex_map *map_p;
+ regmatch_t pmatch[MAX_MATCH];
+
+ if (tTd(38, 20))
+ {
+ char **cpp;
+
+ sm_dprintf("regex_map_lookup: key '%s'\n", name);
+ for (cpp = av; cpp != NULL && *cpp != NULL; cpp++)
+ sm_dprintf("regex_map_lookup: arg '%s'\n", *cpp);
+ }
+
+ map_p = (struct regex_map *)(map->map_db1);
+ reg_res = regexec(map_p->regex_pattern_buf,
+ name, MAX_MATCH, pmatch, 0);
+
+ if (bitset(MF_REGEX_NOT, map->map_mflags))
+ {
+ /* option -n */
+ if (reg_res == REG_NOMATCH)
+ return regex_map_rewrite(map, "", (size_t) 0, av);
+ else
+ return NULL;
+ }
+ if (reg_res == REG_NOMATCH)
+ return NULL;
+
+ if (map_p->regex_subfields != NULL)
+ {
+ /* option -s */
+ static char retbuf[MAXNAME];
+ int fields[MAX_MATCH + 1];
+ bool first = true;
+ int anglecnt = 0, cmntcnt = 0, spacecnt = 0;
+ bool quotemode = false, bslashmode = false;
+ register char *dp, *sp;
+ char *endp, *ldp;
+ int *ip;
+
+ dp = retbuf;
+ ldp = retbuf + sizeof(retbuf) - 1;
+
+ if (av[1] != NULL)
+ {
+ if (parse_fields(av[1], fields, MAX_MATCH + 1,
+ (int) map_p->regex_pattern_buf->re_nsub + 1) == -1)
+ {
+ *statp = EX_CONFIG;
+ return NULL;
+ }
+ ip = fields;
+ }
+ else
+ ip = map_p->regex_subfields;
+
+ for ( ; *ip != END_OF_FIELDS; ip++)
+ {
+ if (!first)
+ {
+ for (sp = map_p->regex_delim; *sp; sp++)
+ {
+ if (dp < ldp)
+ *dp++ = *sp;
+ }
+ }
+ else
+ first = false;
+
+ if (*ip >= MAX_MATCH ||
+ pmatch[*ip].rm_so < 0 || pmatch[*ip].rm_eo < 0)
+ continue;
+
+ sp = name + pmatch[*ip].rm_so;
+ endp = name + pmatch[*ip].rm_eo;
+ for (; endp > sp; sp++)
+ {
+ if (dp < ldp)
+ {
+ if (bslashmode)
+ {
+ *dp++ = *sp;
+ bslashmode = false;
+ }
+ else if (quotemode && *sp != '"' &&
+ *sp != '\\')
+ {
+ *dp++ = *sp;
+ }
+ else switch (*dp++ = *sp)
+ {
+ case '\\':
+ bslashmode = true;
+ break;
+
+ case '(':
+ cmntcnt++;
+ break;
+
+ case ')':
+ cmntcnt--;
+ break;
+
+ case '<':
+ anglecnt++;
+ break;
+
+ case '>':
+ anglecnt--;
+ break;
+
+ case ' ':
+ spacecnt++;
+ break;
+
+ case '"':
+ quotemode = !quotemode;
+ break;
+ }
+ }
+ }
+ }
+ if (anglecnt != 0 || cmntcnt != 0 || quotemode ||
+ bslashmode || spacecnt != 0)
+ {
+ sm_syslog(LOG_WARNING, NOQID,
+ "Warning: regex may cause prescan() failure map=%s lookup=%s",
+ map->map_mname, name);
+ return NULL;
+ }
+
+ *dp = '\0';
+
+ return regex_map_rewrite(map, retbuf, strlen(retbuf), av);
+ }
+ return regex_map_rewrite(map, "", (size_t)0, av);
+}
+#endif /* MAP_REGEX */
+/*
+** NSD modules
+*/
+#if MAP_NSD
+
+# include <ndbm.h>
+# define _DATUM_DEFINED
+# include <ns_api.h>
+
+typedef struct ns_map_list
+{
+ ns_map_t *map; /* XXX ns_ ? */
+ char *mapname;
+ struct ns_map_list *next;
+} ns_map_list_t;
+
+static ns_map_t *
+ns_map_t_find(mapname)
+ char *mapname;
+{
+ static ns_map_list_t *ns_maps = NULL;
+ ns_map_list_t *ns_map;
+
+ /* walk the list of maps looking for the correctly named map */
+ for (ns_map = ns_maps; ns_map != NULL; ns_map = ns_map->next)
+ {
+ if (strcmp(ns_map->mapname, mapname) == 0)
+ break;
+ }
+
+ /* if we are looking at a NULL ns_map_list_t, then create a new one */
+ if (ns_map == NULL)
+ {
+ ns_map = (ns_map_list_t *) xalloc(sizeof *ns_map);
+ ns_map->mapname = newstr(mapname);
+ ns_map->map = (ns_map_t *) xalloc(sizeof *ns_map->map);
+ memset(ns_map->map, '\0', sizeof *ns_map->map);
+ ns_map->next = ns_maps;
+ ns_maps = ns_map;
+ }
+ return ns_map->map;
+}
+
+char *
+nsd_map_lookup(map, name, av, statp)
+ MAP *map;
+ char *name;
+ char **av;
+ int *statp;
+{
+ int buflen, r;
+ char *p;
+ ns_map_t *ns_map;
+ char keybuf[MAXNAME + 1];
+ char buf[MAXLINE];
+
+ if (tTd(38, 20))
+ sm_dprintf("nsd_map_lookup(%s, %s)\n", map->map_mname, name);
+
+ buflen = strlen(name);
+ if (buflen > sizeof keybuf - 1)
+ buflen = sizeof keybuf - 1; /* XXX simply cut off? */
+ memmove(keybuf, name, buflen);
+ keybuf[buflen] = '\0';
+ if (!bitset(MF_NOFOLDCASE, map->map_mflags))
+ makelower(keybuf);
+
+ ns_map = ns_map_t_find(map->map_file);
+ if (ns_map == NULL)
+ {
+ if (tTd(38, 20))
+ sm_dprintf("nsd_map_t_find failed\n");
+ *statp = EX_UNAVAILABLE;
+ return NULL;
+ }
+ r = ns_lookup(ns_map, NULL, map->map_file, keybuf, NULL,
+ buf, sizeof buf);
+ if (r == NS_UNAVAIL || r == NS_TRYAGAIN)
+ {
+ *statp = EX_TEMPFAIL;
+ return NULL;
+ }
+ if (r == NS_BADREQ
+# ifdef NS_NOPERM
+ || r == NS_NOPERM
+# endif /* NS_NOPERM */
+ )
+ {
+ *statp = EX_CONFIG;
+ return NULL;
+ }
+ if (r != NS_SUCCESS)
+ {
+ *statp = EX_NOTFOUND;
+ return NULL;
+ }
+
+ *statp = EX_OK;
+
+ /* Null out trailing \n */
+ if ((p = strchr(buf, '\n')) != NULL)
+ *p = '\0';
+
+ return map_rewrite(map, buf, strlen(buf), av);
+}
+#endif /* MAP_NSD */
+
+char *
+arith_map_lookup(map, name, av, statp)
+ MAP *map;
+ char *name;
+ char **av;
+ int *statp;
+{
+ long r;
+ long v[2];
+ bool res = false;
+ bool boolres;
+ static char result[16];
+ char **cpp;
+
+ if (tTd(38, 2))
+ {
+ sm_dprintf("arith_map_lookup: key '%s'\n", name);
+ for (cpp = av; cpp != NULL && *cpp != NULL; cpp++)
+ sm_dprintf("arith_map_lookup: arg '%s'\n", *cpp);
+ }
+ r = 0;
+ boolres = false;
+ cpp = av;
+ *statp = EX_OK;
+
+ /*
+ ** read arguments for arith map
+ ** - no check is made whether they are really numbers
+ ** - just ignores args after the second
+ */
+
+ for (++cpp; cpp != NULL && *cpp != NULL && r < 2; cpp++)
+ v[r++] = strtol(*cpp, NULL, 0);
+
+ /* operator and (at least) two operands given? */
+ if (name != NULL && r == 2)
+ {
+ switch (*name)
+ {
+ case '|':
+ r = v[0] | v[1];
+ break;
+
+ case '&':
+ r = v[0] & v[1];
+ break;
+
+ case '%':
+ if (v[1] == 0)
+ return NULL;
+ r = v[0] % v[1];
+ break;
+ case '+':
+ r = v[0] + v[1];
+ break;
+
+ case '-':
+ r = v[0] - v[1];
+ break;
+
+ case '*':
+ r = v[0] * v[1];
+ break;
+
+ case '/':
+ if (v[1] == 0)
+ return NULL;
+ r = v[0] / v[1];
+ break;
+
+ case 'l':
+ res = v[0] < v[1];
+ boolres = true;
+ break;
+
+ case '=':
+ res = v[0] == v[1];
+ boolres = true;
+ break;
+
+ default:
+ /* XXX */
+ *statp = EX_CONFIG;
+ if (LogLevel > 10)
+ sm_syslog(LOG_WARNING, NOQID,
+ "arith_map: unknown operator %c",
+ isprint(*name) ? *name : '?');
+ return NULL;
+ }
+ if (boolres)
+ (void) sm_snprintf(result, sizeof result,
+ res ? "TRUE" : "FALSE");
+ else
+ (void) sm_snprintf(result, sizeof result, "%ld", r);
+ return result;
+ }
+ *statp = EX_CONFIG;
+ return NULL;
+}
+
+#if SOCKETMAP
+
+# if NETINET || NETINET6
+# include <arpa/inet.h>
+# endif /* NETINET || NETINET6 */
+
+# define socket_map_next map_stack[0]
+
+/*
+** SOCKET_MAP_OPEN -- open socket table
+*/
+
+bool
+socket_map_open(map, mode)
+ MAP *map;
+ int mode;
+{
+ STAB *s;
+ int sock = 0;
+ SOCKADDR_LEN_T addrlen = 0;
+ int addrno = 0;
+ int save_errno;
+ char *p;
+ char *colon;
+ char *at;
+ struct hostent *hp = NULL;
+ SOCKADDR addr;
+
+ if (tTd(38, 2))
+ sm_dprintf("socket_map_open(%s, %s, %d)\n",
+ map->map_mname, map->map_file, mode);
+
+ mode &= O_ACCMODE;
+
+ /* sendmail doesn't have the ability to write to SOCKET (yet) */
+ if (mode != O_RDONLY)
+ {
+ /* issue a pseudo-error message */
+ errno = SM_EMAPCANTWRITE;
+ return false;
+ }
+
+ if (*map->map_file == '\0')
+ {
+ syserr("socket map \"%s\": empty or missing socket information",
+ map->map_mname);
+ return false;
+ }
+
+ s = socket_map_findconn(map->map_file);
+ if (s->s_socketmap != NULL)
+ {
+ /* Copy open connection */
+ map->map_db1 = s->s_socketmap->map_db1;
+
+ /* Add this map as head of linked list */
+ map->socket_map_next = s->s_socketmap;
+ s->s_socketmap = map;
+
+ if (tTd(38, 2))
+ sm_dprintf("using cached connection\n");
+ return true;
+ }
+
+ if (tTd(38, 2))
+ sm_dprintf("opening new connection\n");
+
+ /* following code is ripped from milter.c */
+ /* XXX It should be put in a library... */
+
+ /* protocol:filename or protocol:port@host */
+ memset(&addr, '\0', sizeof addr);
+ p = map->map_file;
+ colon = strchr(p, ':');
+ if (colon != NULL)
+ {
+ *colon = '\0';
+
+ if (*p == '\0')
+ {
+# if NETUNIX
+ /* default to AF_UNIX */
+ addr.sa.sa_family = AF_UNIX;
+# else /* NETUNIX */
+# if NETINET
+ /* default to AF_INET */
+ addr.sa.sa_family = AF_INET;
+# else /* NETINET */
+# if NETINET6
+ /* default to AF_INET6 */
+ addr.sa.sa_family = AF_INET6;
+# else /* NETINET6 */
+ /* no protocols available */
+ syserr("socket map \"%s\": no valid socket protocols available",
+ map->map_mname);
+ return false;
+# endif /* NETINET6 */
+# endif /* NETINET */
+# endif /* NETUNIX */
+ }
+# if NETUNIX
+ else if (sm_strcasecmp(p, "unix") == 0 ||
+ sm_strcasecmp(p, "local") == 0)
+ addr.sa.sa_family = AF_UNIX;
+# endif /* NETUNIX */
+# if NETINET
+ else if (sm_strcasecmp(p, "inet") == 0)
+ addr.sa.sa_family = AF_INET;
+# endif /* NETINET */
+# if NETINET6
+ else if (sm_strcasecmp(p, "inet6") == 0)
+ addr.sa.sa_family = AF_INET6;
+# endif /* NETINET6 */
+ else
+ {
+# ifdef EPROTONOSUPPORT
+ errno = EPROTONOSUPPORT;
+# else /* EPROTONOSUPPORT */
+ errno = EINVAL;
+# endif /* EPROTONOSUPPORT */
+ syserr("socket map \"%s\": unknown socket type %s",
+ map->map_mname, p);
+ return false;
+ }
+ *colon++ = ':';
+ }
+ else
+ {
+ colon = p;
+#if NETUNIX
+ /* default to AF_UNIX */
+ addr.sa.sa_family = AF_UNIX;
+#else /* NETUNIX */
+# if NETINET
+ /* default to AF_INET */
+ addr.sa.sa_family = AF_INET;
+# else /* NETINET */
+# if NETINET6
+ /* default to AF_INET6 */
+ addr.sa.sa_family = AF_INET6;
+# else /* NETINET6 */
+ syserr("socket map \"%s\": unknown socket type %s",
+ map->map_mname, p);
+ return false;
+# endif /* NETINET6 */
+# endif /* NETINET */
+#endif /* NETUNIX */
+ }
+
+# if NETUNIX
+ if (addr.sa.sa_family == AF_UNIX)
+ {
+ long sff = SFF_SAFEDIRPATH|SFF_OPENASROOT|SFF_NOLINK|SFF_EXECOK;
+
+ at = colon;
+ if (strlen(colon) >= sizeof addr.sunix.sun_path)
+ {
+ syserr("socket map \"%s\": local socket name %s too long",
+ map->map_mname, colon);
+ return false;
+ }
+ errno = safefile(colon, RunAsUid, RunAsGid, RunAsUserName, sff,
+ S_IRUSR|S_IWUSR, NULL);
+
+ if (errno != 0)
+ {
+ /* if not safe, don't create */
+ syserr("socket map \"%s\": local socket name %s unsafe",
+ map->map_mname, colon);
+ return false;
+ }
+
+ (void) sm_strlcpy(addr.sunix.sun_path, colon,
+ sizeof addr.sunix.sun_path);
+ addrlen = sizeof (struct sockaddr_un);
+ }
+ else
+# endif /* NETUNIX */
+# if NETINET || NETINET6
+ if (false
+# if NETINET
+ || addr.sa.sa_family == AF_INET
+# endif /* NETINET */
+# if NETINET6
+ || addr.sa.sa_family == AF_INET6
+# endif /* NETINET6 */
+ )
+ {
+ unsigned short port;
+
+ /* Parse port@host */
+ at = strchr(colon, '@');
+ if (at == NULL)
+ {
+ syserr("socket map \"%s\": bad address %s (expected port@host)",
+ map->map_mname, colon);
+ return false;
+ }
+ *at = '\0';
+ if (isascii(*colon) && isdigit(*colon))
+ port = htons((unsigned short) atoi(colon));
+ else
+ {
+# ifdef NO_GETSERVBYNAME
+ syserr("socket map \"%s\": invalid port number %s",
+ map->map_mname, colon);
+ return false;
+# else /* NO_GETSERVBYNAME */
+ register struct servent *sp;
+
+ sp = getservbyname(colon, "tcp");
+ if (sp == NULL)
+ {
+ syserr("socket map \"%s\": unknown port name %s",
+ map->map_mname, colon);
+ return false;
+ }
+ port = sp->s_port;
+# endif /* NO_GETSERVBYNAME */
+ }
+ *at++ = '@';
+ if (*at == '[')
+ {
+ char *end;
+
+ end = strchr(at, ']');
+ if (end != NULL)
+ {
+ bool found = false;
+# if NETINET
+ unsigned long hid = INADDR_NONE;
+# endif /* NETINET */
+# if NETINET6
+ struct sockaddr_in6 hid6;
+# endif /* NETINET6 */
+
+ *end = '\0';
+# if NETINET
+ if (addr.sa.sa_family == AF_INET &&
+ (hid = inet_addr(&at[1])) != INADDR_NONE)
+ {
+ addr.sin.sin_addr.s_addr = hid;
+ addr.sin.sin_port = port;
+ found = true;
+ }
+# endif /* NETINET */
+# if NETINET6
+ (void) memset(&hid6, '\0', sizeof hid6);
+ if (addr.sa.sa_family == AF_INET6 &&
+ anynet_pton(AF_INET6, &at[1],
+ &hid6.sin6_addr) == 1)
+ {
+ addr.sin6.sin6_addr = hid6.sin6_addr;
+ addr.sin6.sin6_port = port;
+ found = true;
+ }
+# endif /* NETINET6 */
+ *end = ']';
+ if (!found)
+ {
+ syserr("socket map \"%s\": Invalid numeric domain spec \"%s\"",
+ map->map_mname, at);
+ return false;
+ }
+ }
+ else
+ {
+ syserr("socket map \"%s\": Invalid numeric domain spec \"%s\"",
+ map->map_mname, at);
+ return false;
+ }
+ }
+ else
+ {
+ hp = sm_gethostbyname(at, addr.sa.sa_family);
+ if (hp == NULL)
+ {
+ syserr("socket map \"%s\": Unknown host name %s",
+ map->map_mname, at);
+ return false;
+ }
+ addr.sa.sa_family = hp->h_addrtype;
+ switch (hp->h_addrtype)
+ {
+# if NETINET
+ case AF_INET:
+ memmove(&addr.sin.sin_addr,
+ hp->h_addr, INADDRSZ);
+ addr.sin.sin_port = port;
+ addrlen = sizeof (struct sockaddr_in);
+ addrno = 1;
+ break;
+# endif /* NETINET */
+
+# if NETINET6
+ case AF_INET6:
+ memmove(&addr.sin6.sin6_addr,
+ hp->h_addr, IN6ADDRSZ);
+ addr.sin6.sin6_port = port;
+ addrlen = sizeof (struct sockaddr_in6);
+ addrno = 1;
+ break;
+# endif /* NETINET6 */
+
+ default:
+ syserr("socket map \"%s\": Unknown protocol for %s (%d)",
+ map->map_mname, at, hp->h_addrtype);
+# if NETINET6
+ freehostent(hp);
+# endif /* NETINET6 */
+ return false;
+ }
+ }
+ }
+ else
+# endif /* NETINET || NETINET6 */
+ {
+ syserr("socket map \"%s\": unknown socket protocol",
+ map->map_mname);
+ return false;
+ }
+
+ /* nope, actually connecting */
+ for (;;)
+ {
+ sock = socket(addr.sa.sa_family, SOCK_STREAM, 0);
+ if (sock < 0)
+ {
+ save_errno = errno;
+ if (tTd(38, 5))
+ sm_dprintf("socket map \"%s\": error creating socket: %s\n",
+ map->map_mname,
+ sm_errstring(save_errno));
+# if NETINET6
+ if (hp != NULL)
+ freehostent(hp);
+# endif /* NETINET6 */
+ return false;
+ }
+
+ if (connect(sock, (struct sockaddr *) &addr, addrlen) >= 0)
+ break;
+
+ /* couldn't connect.... try next address */
+ save_errno = errno;
+ p = CurHostName;
+ CurHostName = at;
+ if (tTd(38, 5))
+ sm_dprintf("socket_open (%s): open %s failed: %s\n",
+ map->map_mname, at, sm_errstring(save_errno));
+ CurHostName = p;
+ (void) close(sock);
+
+ /* try next address */
+ if (hp != NULL && hp->h_addr_list[addrno] != NULL)
+ {
+ switch (addr.sa.sa_family)
+ {
+# if NETINET
+ case AF_INET:
+ memmove(&addr.sin.sin_addr,
+ hp->h_addr_list[addrno++],
+ INADDRSZ);
+ break;
+# endif /* NETINET */
+
+# if NETINET6
+ case AF_INET6:
+ memmove(&addr.sin6.sin6_addr,
+ hp->h_addr_list[addrno++],
+ IN6ADDRSZ);
+ break;
+# endif /* NETINET6 */
+
+ default:
+ if (tTd(38, 5))
+ sm_dprintf("socket map \"%s\": Unknown protocol for %s (%d)\n",
+ map->map_mname, at,
+ hp->h_addrtype);
+# if NETINET6
+ freehostent(hp);
+# endif /* NETINET6 */
+ return false;
+ }
+ continue;
+ }
+ p = CurHostName;
+ CurHostName = at;
+ if (tTd(38, 5))
+ sm_dprintf("socket map \"%s\": error connecting to socket map: %s\n",
+ map->map_mname, sm_errstring(save_errno));
+ CurHostName = p;
+# if NETINET6
+ if (hp != NULL)
+ freehostent(hp);
+# endif /* NETINET6 */
+ return false;
+ }
+# if NETINET6
+ if (hp != NULL)
+ {
+ freehostent(hp);
+ hp = NULL;
+ }
+# endif /* NETINET6 */
+ if ((map->map_db1 = (ARBPTR_T) sm_io_open(SmFtStdiofd,
+ SM_TIME_DEFAULT,
+ (void *) &sock,
+ SM_IO_RDWR,
+ NULL)) == NULL)
+ {
+ close(sock);
+ if (tTd(38, 2))
+ sm_dprintf("socket_open (%s): failed to create stream: %s\n",
+ map->map_mname, sm_errstring(errno));
+ return false;
+ }
+
+ /* Save connection for reuse */
+ s->s_socketmap = map;
+ return true;
+}
+
+/*
+** SOCKET_MAP_FINDCONN -- find a SOCKET connection to the server
+**
+** Cache SOCKET connections based on the connection specifier
+** and PID so we don't have multiple connections open to
+** the same server for different maps. Need a separate connection
+** per PID since a parent process may close the map before the
+** child is done with it.
+**
+** Parameters:
+** conn -- SOCKET map connection specifier
+**
+** Returns:
+** Symbol table entry for the SOCKET connection.
+*/
+
+static STAB *
+socket_map_findconn(conn)
+ const char *conn;
+{
+ char *nbuf;
+ STAB *SM_NONVOLATILE s = NULL;
+
+ nbuf = sm_stringf_x("%s%c%d", conn, CONDELSE, (int) CurrentPid);
+ SM_TRY
+ s = stab(nbuf, ST_SOCKETMAP, ST_ENTER);
+ SM_FINALLY
+ sm_free(nbuf);
+ SM_END_TRY
+ return s;
+}
+
+/*
+** SOCKET_MAP_CLOSE -- close the socket
+*/
+
+void
+socket_map_close(map)
+ MAP *map;
+{
+ STAB *s;
+ MAP *smap;
+
+ if (tTd(38, 20))
+ sm_dprintf("socket_map_close(%s), pid=%ld\n", map->map_file,
+ (long) CurrentPid);
+
+ /* Check if already closed */
+ if (map->map_db1 == NULL)
+ {
+ if (tTd(38, 20))
+ sm_dprintf("socket_map_close(%s) already closed\n",
+ map->map_file);
+ return;
+ }
+ sm_io_close((SM_FILE_T *)map->map_db1, SM_TIME_DEFAULT);
+
+ /* Mark all the maps that share the connection as closed */
+ s = socket_map_findconn(map->map_file);
+ smap = s->s_socketmap;
+ while (smap != NULL)
+ {
+ MAP *next;
+
+ if (tTd(38, 2) && smap != map)
+ sm_dprintf("socket_map_close(%s): closed %s (shared SOCKET connection)\n",
+ map->map_mname, smap->map_mname);
+
+ smap->map_mflags &= ~(MF_OPEN|MF_WRITABLE);
+ smap->map_db1 = NULL;
+ next = smap->socket_map_next;
+ smap->socket_map_next = NULL;
+ smap = next;
+ }
+ s->s_socketmap = NULL;
+}
+
+/*
+** SOCKET_MAP_LOOKUP -- look up a datum in a SOCKET table
+*/
+
+char *
+socket_map_lookup(map, name, av, statp)
+ MAP *map;
+ char *name;
+ char **av;
+ int *statp;
+{
+ unsigned int nettolen, replylen, recvlen;
+ char *replybuf, *rval, *value, *status, *key;
+ SM_FILE_T *f;
+ char keybuf[MAXNAME + 1];
+
+ replybuf = NULL;
+ rval = NULL;
+ f = (SM_FILE_T *)map->map_db1;
+ if (tTd(38, 20))
+ sm_dprintf("socket_map_lookup(%s, %s) %s\n",
+ map->map_mname, name, map->map_file);
+
+ if (!bitset(MF_NOFOLDCASE, map->map_mflags))
+ {
+ nettolen = strlen(name);
+ if (nettolen > sizeof keybuf - 1)
+ nettolen = sizeof keybuf - 1;
+ memmove(keybuf, name, nettolen);
+ keybuf[nettolen] = '\0';
+ makelower(keybuf);
+ key = keybuf;
+ }
+ else
+ key = name;
+
+ nettolen = strlen(map->map_mname) + 1 + strlen(key);
+ SM_ASSERT(nettolen > strlen(map->map_mname));
+ SM_ASSERT(nettolen > strlen(key));
+ if ((sm_io_fprintf(f, SM_TIME_DEFAULT, "%u:%s %s,",
+ nettolen, map->map_mname, key) == SM_IO_EOF) ||
+ (sm_io_flush(f, SM_TIME_DEFAULT) != 0) ||
+ (sm_io_error(f)))
+ {
+ syserr("451 4.3.0 socket_map_lookup(%s): failed to send lookup request",
+ map->map_mname);
+ *statp = EX_TEMPFAIL;
+ goto errcl;
+ }
+
+ if (sm_io_fscanf(f, SM_TIME_DEFAULT, "%9u", &replylen) != 1)
+ {
+ syserr("451 4.3.0 socket_map_lookup(%s): failed to read length parameter of reply",
+ map->map_mname);
+ *statp = EX_TEMPFAIL;
+ goto errcl;
+ }
+ if (replylen > SOCKETMAP_MAXL)
+ {
+ syserr("451 4.3.0 socket_map_lookup(%s): reply too long: %u",
+ map->map_mname, replylen);
+ *statp = EX_TEMPFAIL;
+ goto errcl;
+ }
+ if (sm_io_getc(f, SM_TIME_DEFAULT) != ':')
+ {
+ syserr("451 4.3.0 socket_map_lookup(%s): missing ':' in reply",
+ map->map_mname);
+ *statp = EX_TEMPFAIL;
+ goto error;
+ }
+
+ replybuf = (char *) sm_malloc(replylen + 1);
+ if (replybuf == NULL)
+ {
+ syserr("451 4.3.0 socket_map_lookup(%s): can't allocate %u bytes",
+ map->map_mname, replylen + 1);
+ *statp = EX_OSERR;
+ goto error;
+ }
+
+ recvlen = sm_io_read(f, SM_TIME_DEFAULT, replybuf, replylen);
+ if (recvlen < replylen)
+ {
+ syserr("451 4.3.0 socket_map_lookup(%s): received only %u of %u reply characters",
+ map->map_mname, recvlen, replylen);
+ *statp = EX_TEMPFAIL;
+ goto errcl;
+ }
+ if (sm_io_getc(f, SM_TIME_DEFAULT) != ',')
+ {
+ syserr("451 4.3.0 socket_map_lookup(%s): missing ',' in reply",
+ map->map_mname);
+ *statp = EX_TEMPFAIL;
+ goto errcl;
+ }
+ status = replybuf;
+ replybuf[recvlen] = '\0';
+ value = strchr(replybuf, ' ');
+ if (value != NULL)
+ {
+ *value = '\0';
+ value++;
+ }
+ if (strcmp(status, "OK") == 0)
+ {
+ *statp = EX_OK;
+
+ /* collect the return value */
+ if (bitset(MF_MATCHONLY, map->map_mflags))
+ rval = map_rewrite(map, key, strlen(key), NULL);
+ else
+ rval = map_rewrite(map, value, strlen(value), av);
+ }
+ else if (strcmp(status, "NOTFOUND") == 0)
+ {
+ *statp = EX_NOTFOUND;
+ if (tTd(38, 20))
+ sm_dprintf("socket_map_lookup(%s): %s not found\n",
+ map->map_mname, key);
+ }
+ else
+ {
+ if (tTd(38, 5))
+ sm_dprintf("socket_map_lookup(%s, %s): server returned error: type=%s, reason=%s\n",
+ map->map_mname, key, status,
+ value ? value : "");
+ if ((strcmp(status, "TEMP") == 0) ||
+ (strcmp(status, "TIMEOUT") == 0))
+ *statp = EX_TEMPFAIL;
+ else if(strcmp(status, "PERM") == 0)
+ *statp = EX_UNAVAILABLE;
+ else
+ *statp = EX_PROTOCOL;
+ }
+
+ if (replybuf != NULL)
+ sm_free(replybuf);
+ return rval;
+
+ errcl:
+ socket_map_close(map);
+ error:
+ if (replybuf != NULL)
+ sm_free(replybuf);
+ return rval;
+}
+#endif /* SOCKETMAP */
diff --git a/usr/src/cmd/sendmail/src/mci.c b/usr/src/cmd/sendmail/src/mci.c
new file mode 100644
index 0000000000..0f7d5c0fac
--- /dev/null
+++ b/usr/src/cmd/sendmail/src/mci.c
@@ -0,0 +1,1554 @@
+/*
+ * Copyright (c) 1998-2005 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sendmail.h>
+
+SM_RCSID("@(#)$Id: mci.c,v 8.214 2005/02/04 22:01:45 ca Exp $")
+
+#if NETINET || NETINET6
+# include <arpa/inet.h>
+#endif /* NETINET || NETINET6 */
+
+#include <dirent.h>
+
+static int mci_generate_persistent_path __P((const char *, char *,
+ int, bool));
+static bool mci_load_persistent __P((MCI *));
+static void mci_uncache __P((MCI **, bool));
+static int mci_lock_host_statfile __P((MCI *));
+static int mci_read_persistent __P((SM_FILE_T *, MCI *));
+
+/*
+** Mail Connection Information (MCI) Caching Module.
+**
+** There are actually two separate things cached. The first is
+** the set of all open connections -- these are stored in a
+** (small) list. The second is stored in the symbol table; it
+** has the overall status for all hosts, whether or not there
+** is a connection open currently.
+**
+** There should never be too many connections open (since this
+** could flood the socket table), nor should a connection be
+** allowed to sit idly for too long.
+**
+** MaxMciCache is the maximum number of open connections that
+** will be supported.
+**
+** MciCacheTimeout is the time (in seconds) that a connection
+** is permitted to survive without activity.
+**
+** We actually try any cached connections by sending a NOOP
+** before we use them; if the NOOP fails we close down the
+** connection and reopen it. Note that this means that a
+** server SMTP that doesn't support NOOP will hose the
+** algorithm -- but that doesn't seem too likely.
+**
+** The persistent MCI code is donated by Mark Lovell and Paul
+** Vixie. It is based on the long term host status code in KJS
+** written by Paul but has been adapted by Mark to fit into the
+** MCI structure.
+*/
+
+static MCI **MciCache; /* the open connection cache */
+
+/*
+** MCI_CACHE -- enter a connection structure into the open connection cache
+**
+** This may cause something else to be flushed.
+**
+** Parameters:
+** mci -- the connection to cache.
+**
+** Returns:
+** none.
+*/
+
+void
+mci_cache(mci)
+ register MCI *mci;
+{
+ register MCI **mcislot;
+
+ /*
+ ** Find the best slot. This may cause expired connections
+ ** to be closed.
+ */
+
+ mcislot = mci_scan(mci);
+ if (mcislot == NULL)
+ {
+ /* we don't support caching */
+ return;
+ }
+
+ if (mci->mci_host == NULL)
+ return;
+
+ /* if this is already cached, we are done */
+ if (bitset(MCIF_CACHED, mci->mci_flags))
+ return;
+
+ /* otherwise we may have to clear the slot */
+ if (*mcislot != NULL)
+ mci_uncache(mcislot, true);
+
+ if (tTd(42, 5))
+ sm_dprintf("mci_cache: caching %p (%s) in slot %d\n",
+ mci, mci->mci_host, (int) (mcislot - MciCache));
+ if (tTd(91, 100))
+ sm_syslog(LOG_DEBUG, CurEnv->e_id,
+ "mci_cache: caching %lx (%.100s) in slot %d",
+ (unsigned long) mci, mci->mci_host,
+ (int) (mcislot - MciCache));
+
+ *mcislot = mci;
+ mci->mci_flags |= MCIF_CACHED;
+}
+/*
+** MCI_SCAN -- scan the cache, flush junk, and return best slot
+**
+** Parameters:
+** savemci -- never flush this one. Can be null.
+**
+** Returns:
+** The LRU (or empty) slot.
+*/
+
+MCI **
+mci_scan(savemci)
+ MCI *savemci;
+{
+ time_t now;
+ register MCI **bestmci;
+ register MCI *mci;
+ register int i;
+
+ if (MaxMciCache <= 0)
+ {
+ /* we don't support caching */
+ return NULL;
+ }
+
+ if (MciCache == NULL)
+ {
+ /* first call */
+ MciCache = (MCI **) sm_pmalloc_x(MaxMciCache * sizeof *MciCache);
+ memset((char *) MciCache, '\0', MaxMciCache * sizeof *MciCache);
+ return &MciCache[0];
+ }
+
+ now = curtime();
+ bestmci = &MciCache[0];
+ for (i = 0; i < MaxMciCache; i++)
+ {
+ mci = MciCache[i];
+ if (mci == NULL || mci->mci_state == MCIS_CLOSED)
+ {
+ bestmci = &MciCache[i];
+ continue;
+ }
+ if ((mci->mci_lastuse + MciCacheTimeout <= now ||
+ (mci->mci_mailer != NULL &&
+ mci->mci_mailer->m_maxdeliveries > 0 &&
+ mci->mci_deliveries + 1 >= mci->mci_mailer->m_maxdeliveries))&&
+ mci != savemci)
+ {
+ /* connection idle too long or too many deliveries */
+ bestmci = &MciCache[i];
+
+ /* close it */
+ mci_uncache(bestmci, true);
+ continue;
+ }
+ if (*bestmci == NULL)
+ continue;
+ if (mci->mci_lastuse < (*bestmci)->mci_lastuse)
+ bestmci = &MciCache[i];
+ }
+ return bestmci;
+}
+/*
+** MCI_UNCACHE -- remove a connection from a slot.
+**
+** May close a connection.
+**
+** Parameters:
+** mcislot -- the slot to empty.
+** doquit -- if true, send QUIT protocol on this connection.
+** if false, we are assumed to be in a forked child;
+** all we want to do is close the file(s).
+**
+** Returns:
+** none.
+*/
+
+static void
+mci_uncache(mcislot, doquit)
+ register MCI **mcislot;
+ bool doquit;
+{
+ register MCI *mci;
+ extern ENVELOPE BlankEnvelope;
+
+ mci = *mcislot;
+ if (mci == NULL)
+ return;
+ *mcislot = NULL;
+ if (mci->mci_host == NULL)
+ return;
+
+ mci_unlock_host(mci);
+
+ if (tTd(42, 5))
+ sm_dprintf("mci_uncache: uncaching %p (%s) from slot %d (%d)\n",
+ mci, mci->mci_host, (int) (mcislot - MciCache),
+ doquit);
+ if (tTd(91, 100))
+ sm_syslog(LOG_DEBUG, CurEnv->e_id,
+ "mci_uncache: uncaching %lx (%.100s) from slot %d (%d)",
+ (unsigned long) mci, mci->mci_host,
+ (int) (mcislot - MciCache), doquit);
+
+ mci->mci_deliveries = 0;
+ if (doquit)
+ {
+ message("Closing connection to %s", mci->mci_host);
+
+ mci->mci_flags &= ~MCIF_CACHED;
+
+ /* only uses the envelope to flush the transcript file */
+ if (mci->mci_state != MCIS_CLOSED)
+ smtpquit(mci->mci_mailer, mci, &BlankEnvelope);
+#if XLA
+ xla_host_end(mci->mci_host);
+#endif /* XLA */
+ }
+ else
+ {
+ if (mci->mci_in != NULL)
+ (void) sm_io_close(mci->mci_in, SM_TIME_DEFAULT);
+ if (mci->mci_out != NULL)
+ (void) sm_io_close(mci->mci_out, SM_TIME_DEFAULT);
+ mci->mci_in = mci->mci_out = NULL;
+ mci->mci_state = MCIS_CLOSED;
+ mci->mci_exitstat = EX_OK;
+ mci->mci_errno = 0;
+ mci->mci_flags = 0;
+
+ mci->mci_retryrcpt = false;
+ mci->mci_tolist = NULL;
+#if PIPELINING
+ mci->mci_okrcpts = 0;
+#endif /* PIPELINING */
+ }
+
+ SM_FREE_CLR(mci->mci_status);
+ SM_FREE_CLR(mci->mci_rstatus);
+ SM_FREE_CLR(mci->mci_heloname);
+ if (mci->mci_rpool != NULL)
+ {
+ sm_rpool_free(mci->mci_rpool);
+ mci->mci_macro.mac_rpool = NULL;
+ mci->mci_rpool = NULL;
+ }
+}
+/*
+** MCI_FLUSH -- flush the entire cache
+**
+** Parameters:
+** doquit -- if true, send QUIT protocol.
+** if false, just close the connection.
+** allbut -- but leave this one open.
+**
+** Returns:
+** none.
+*/
+
+void
+mci_flush(doquit, allbut)
+ bool doquit;
+ MCI *allbut;
+{
+ register int i;
+
+ if (MciCache == NULL)
+ return;
+
+ for (i = 0; i < MaxMciCache; i++)
+ {
+ if (allbut != MciCache[i])
+ mci_uncache(&MciCache[i], doquit);
+ }
+}
+/*
+** MCI_GET -- get information about a particular host
+**
+** Parameters:
+** host -- host to look for.
+** m -- mailer.
+**
+** Returns:
+** mci for this host (might be new).
+*/
+
+MCI *
+mci_get(host, m)
+ char *host;
+ MAILER *m;
+{
+ register MCI *mci;
+ register STAB *s;
+ extern SOCKADDR CurHostAddr;
+
+ /* clear CurHostAddr so we don't get a bogus address with this name */
+ memset(&CurHostAddr, '\0', sizeof CurHostAddr);
+
+ /* clear out any expired connections */
+ (void) mci_scan(NULL);
+
+ if (m->m_mno < 0)
+ syserr("!negative mno %d (%s)", m->m_mno, m->m_name);
+
+ s = stab(host, ST_MCI + m->m_mno, ST_ENTER);
+ mci = &s->s_mci;
+
+ /* initialize per-message data */
+ mci->mci_retryrcpt = false;
+ mci->mci_tolist = NULL;
+#if PIPELINING
+ mci->mci_okrcpts = 0;
+#endif /* PIPELINING */
+
+ if (mci->mci_rpool == NULL)
+ mci->mci_rpool = sm_rpool_new_x(NULL);
+
+ if (mci->mci_macro.mac_rpool == NULL)
+ mci->mci_macro.mac_rpool = mci->mci_rpool;
+
+ /*
+ ** We don't need to load the persistent data if we have data
+ ** already loaded in the cache.
+ */
+
+ if (mci->mci_host == NULL &&
+ (mci->mci_host = s->s_name) != NULL &&
+ !mci_load_persistent(mci))
+ {
+ if (tTd(42, 2))
+ sm_dprintf("mci_get(%s %s): lock failed\n",
+ host, m->m_name);
+ mci->mci_exitstat = EX_TEMPFAIL;
+ mci->mci_state = MCIS_CLOSED;
+ mci->mci_statfile = NULL;
+ return mci;
+ }
+
+ if (tTd(42, 2))
+ {
+ sm_dprintf("mci_get(%s %s): mci_state=%d, _flags=%lx, _exitstat=%d, _errno=%d\n",
+ host, m->m_name, mci->mci_state, mci->mci_flags,
+ mci->mci_exitstat, mci->mci_errno);
+ }
+
+ if (mci->mci_state == MCIS_OPEN)
+ {
+ /* poke the connection to see if it's still alive */
+ (void) smtpprobe(mci);
+
+ /* reset the stored state in the event of a timeout */
+ if (mci->mci_state != MCIS_OPEN)
+ {
+ mci->mci_errno = 0;
+ mci->mci_exitstat = EX_OK;
+ mci->mci_state = MCIS_CLOSED;
+ }
+ else
+ {
+ /* get peer host address */
+ /* (this should really be in the mci struct) */
+ SOCKADDR_LEN_T socklen = sizeof CurHostAddr;
+
+ (void) getpeername(sm_io_getinfo(mci->mci_in,
+ SM_IO_WHAT_FD, NULL),
+ (struct sockaddr *) &CurHostAddr, &socklen);
+ }
+ }
+ if (mci->mci_state == MCIS_CLOSED)
+ {
+ time_t now = curtime();
+
+ /* if this info is stale, ignore it */
+ if (mci->mci_lastuse + MciInfoTimeout <= now)
+ {
+ mci->mci_lastuse = now;
+ mci->mci_errno = 0;
+ mci->mci_exitstat = EX_OK;
+ }
+ }
+
+ return mci;
+}
+
+/*
+** MCI_CLOSE -- (forcefully) close files used for a connection.
+** Note: this is a last resort, usually smtpquit() or endmailer()
+** should be used to close a connection.
+**
+** Parameters:
+** mci -- the connection to close.
+** where -- where has this been called?
+**
+** Returns:
+** none.
+*/
+
+void
+mci_close(mci, where)
+ MCI *mci;
+ char *where;
+{
+ bool dumped;
+
+ if (mci == NULL)
+ return;
+ dumped = false;
+ if (mci->mci_out != NULL)
+ {
+ if (tTd(56, 1))
+ {
+ sm_dprintf("mci_close: mci_out!=NULL, where=%s\n",
+ where);
+ mci_dump(sm_debug_file(), mci, false);
+ dumped = true;
+ }
+ (void) sm_io_close(mci->mci_out, SM_TIME_DEFAULT);
+ mci->mci_out = NULL;
+ }
+ if (mci->mci_in != NULL)
+ {
+ if (tTd(56, 1))
+ {
+ sm_dprintf("mci_close: mci_in!=NULL, where=%s\n",
+ where);
+ if (!dumped)
+ mci_dump(sm_debug_file(), mci, false);
+ }
+ (void) sm_io_close(mci->mci_in, SM_TIME_DEFAULT);
+ mci->mci_in = NULL;
+ }
+ mci->mci_state = MCIS_CLOSED;
+}
+
+/*
+** MCI_NEW -- allocate new MCI structure
+**
+** Parameters:
+** rpool -- if non-NULL: allocate from that rpool.
+**
+** Returns:
+** mci (new).
+*/
+
+MCI *
+mci_new(rpool)
+ SM_RPOOL_T *rpool;
+{
+ register MCI *mci;
+
+ if (rpool == NULL)
+ mci = (MCI *) sm_malloc_x(sizeof *mci);
+ else
+ mci = (MCI *) sm_rpool_malloc_x(rpool, sizeof *mci);
+ memset((char *) mci, '\0', sizeof *mci);
+ mci->mci_rpool = sm_rpool_new_x(NULL);
+ mci->mci_macro.mac_rpool = mci->mci_rpool;
+ return mci;
+}
+/*
+** MCI_MATCH -- check connection cache for a particular host
+**
+** Parameters:
+** host -- host to look for.
+** m -- mailer.
+**
+** Returns:
+** true iff open connection exists.
+*/
+
+bool
+mci_match(host, m)
+ char *host;
+ MAILER *m;
+{
+ register MCI *mci;
+ register STAB *s;
+
+ if (m->m_mno < 0 || m->m_mno > MAXMAILERS)
+ return false;
+ s = stab(host, ST_MCI + m->m_mno, ST_FIND);
+ if (s == NULL)
+ return false;
+
+ mci = &s->s_mci;
+ return mci->mci_state == MCIS_OPEN;
+}
+/*
+** MCI_SETSTAT -- set status codes in MCI structure.
+**
+** Parameters:
+** mci -- the MCI structure to set.
+** xstat -- the exit status code.
+** dstat -- the DSN status code.
+** rstat -- the SMTP status code.
+**
+** Returns:
+** none.
+*/
+
+void
+mci_setstat(mci, xstat, dstat, rstat)
+ MCI *mci;
+ int xstat;
+ char *dstat;
+ char *rstat;
+{
+ /* protocol errors should never be interpreted as sticky */
+ if (xstat != EX_NOTSTICKY && xstat != EX_PROTOCOL)
+ mci->mci_exitstat = xstat;
+
+ SM_FREE_CLR(mci->mci_status);
+ if (dstat != NULL)
+ mci->mci_status = sm_strdup_x(dstat);
+
+ SM_FREE_CLR(mci->mci_rstatus);
+ if (rstat != NULL)
+ mci->mci_rstatus = sm_strdup_x(rstat);
+}
+/*
+** MCI_DUMP -- dump the contents of an MCI structure.
+**
+** Parameters:
+** fp -- output file pointer
+** mci -- the MCI structure to dump.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** none.
+*/
+
+struct mcifbits
+{
+ int mcif_bit; /* flag bit */
+ char *mcif_name; /* flag name */
+};
+static struct mcifbits MciFlags[] =
+{
+ { MCIF_VALID, "VALID" },
+ { MCIF_CACHED, "CACHED" },
+ { MCIF_ESMTP, "ESMTP" },
+ { MCIF_EXPN, "EXPN" },
+ { MCIF_SIZE, "SIZE" },
+ { MCIF_8BITMIME, "8BITMIME" },
+ { MCIF_7BIT, "7BIT" },
+ { MCIF_INHEADER, "INHEADER" },
+ { MCIF_CVT8TO7, "CVT8TO7" },
+ { MCIF_DSN, "DSN" },
+ { MCIF_8BITOK, "8BITOK" },
+ { MCIF_CVT7TO8, "CVT7TO8" },
+ { MCIF_INMIME, "INMIME" },
+ { MCIF_AUTH, "AUTH" },
+ { MCIF_AUTHACT, "AUTHACT" },
+ { MCIF_ENHSTAT, "ENHSTAT" },
+ { MCIF_PIPELINED, "PIPELINED" },
+#if STARTTLS
+ { MCIF_TLS, "TLS" },
+ { MCIF_TLSACT, "TLSACT" },
+#endif /* STARTTLS */
+ { MCIF_DLVR_BY, "DLVR_BY" },
+ { 0, NULL }
+};
+
+void
+mci_dump(fp, mci, logit)
+ SM_FILE_T *fp;
+ register MCI *mci;
+ bool logit;
+{
+ register char *p;
+ char *sep;
+ char buf[4000];
+
+ sep = logit ? " " : "\n\t";
+ p = buf;
+ (void) sm_snprintf(p, SPACELEFT(buf, p), "MCI@%p: ", mci);
+ p += strlen(p);
+ if (mci == NULL)
+ {
+ (void) sm_snprintf(p, SPACELEFT(buf, p), "NULL");
+ goto printit;
+ }
+ (void) sm_snprintf(p, SPACELEFT(buf, p), "flags=%lx", mci->mci_flags);
+ p += strlen(p);
+
+ /*
+ ** The following check is just for paranoia. It protects the
+ ** assignment in the if() clause. If there's not some minimum
+ ** amount of space we can stop right now. The check will not
+ ** trigger as long as sizeof(buf)=4000.
+ */
+
+ if (p >= buf + sizeof(buf) - 4)
+ goto printit;
+ if (mci->mci_flags != 0)
+ {
+ struct mcifbits *f;
+
+ *p++ = '<'; /* protected above */
+ for (f = MciFlags; f->mcif_bit != 0; f++)
+ {
+ if (!bitset(f->mcif_bit, mci->mci_flags))
+ continue;
+ (void) sm_strlcpyn(p, SPACELEFT(buf, p), 2,
+ f->mcif_name, ",");
+ p += strlen(p);
+ }
+ p[-1] = '>';
+ }
+
+ /* Note: sm_snprintf() takes care of NULL arguments for %s */
+ (void) sm_snprintf(p, SPACELEFT(buf, p),
+ ",%serrno=%d, herrno=%d, exitstat=%d, state=%d, pid=%d,%s",
+ sep, mci->mci_errno, mci->mci_herrno,
+ mci->mci_exitstat, mci->mci_state, (int) mci->mci_pid, sep);
+ p += strlen(p);
+ (void) sm_snprintf(p, SPACELEFT(buf, p),
+ "maxsize=%ld, phase=%s, mailer=%s,%s",
+ mci->mci_maxsize, mci->mci_phase,
+ mci->mci_mailer == NULL ? "NULL" : mci->mci_mailer->m_name,
+ sep);
+ p += strlen(p);
+ (void) sm_snprintf(p, SPACELEFT(buf, p),
+ "status=%s, rstatus=%s,%s",
+ mci->mci_status, mci->mci_rstatus, sep);
+ p += strlen(p);
+ (void) sm_snprintf(p, SPACELEFT(buf, p),
+ "host=%s, lastuse=%s",
+ mci->mci_host, ctime(&mci->mci_lastuse));
+printit:
+ if (logit)
+ sm_syslog(LOG_DEBUG, CurEnv->e_id, "%.1000s", buf);
+ else
+ (void) sm_io_fprintf(fp, SM_TIME_DEFAULT, "%s\n", buf);
+}
+/*
+** MCI_DUMP_ALL -- print the entire MCI cache
+**
+** Parameters:
+** fp -- output file pointer
+** logit -- if set, log the result instead of printing
+** to stdout.
+**
+** Returns:
+** none.
+*/
+
+void
+mci_dump_all(fp, logit)
+ SM_FILE_T *fp;
+ bool logit;
+{
+ register int i;
+
+ if (MciCache == NULL)
+ return;
+
+ for (i = 0; i < MaxMciCache; i++)
+ mci_dump(fp, MciCache[i], logit);
+}
+/*
+** MCI_LOCK_HOST -- Lock host while sending.
+**
+** If we are contacting a host, we'll need to
+** update the status information in the host status
+** file, and if we want to do that, we ought to have
+** locked it. This has the (according to some)
+** desirable effect of serializing connectivity with
+** remote hosts -- i.e.: one connection to a given
+** host at a time.
+**
+** Parameters:
+** mci -- containing the host we want to lock.
+**
+** Returns:
+** EX_OK -- got the lock.
+** EX_TEMPFAIL -- didn't get the lock.
+*/
+
+int
+mci_lock_host(mci)
+ MCI *mci;
+{
+ if (mci == NULL)
+ {
+ if (tTd(56, 1))
+ sm_dprintf("mci_lock_host: NULL mci\n");
+ return EX_OK;
+ }
+
+ if (!SingleThreadDelivery)
+ return EX_OK;
+
+ return mci_lock_host_statfile(mci);
+}
+
+static int
+mci_lock_host_statfile(mci)
+ MCI *mci;
+{
+ int save_errno = errno;
+ int retVal = EX_OK;
+ char fname[MAXPATHLEN];
+
+ if (HostStatDir == NULL || mci->mci_host == NULL)
+ return EX_OK;
+
+ if (tTd(56, 2))
+ sm_dprintf("mci_lock_host: attempting to lock %s\n",
+ mci->mci_host);
+
+ if (mci_generate_persistent_path(mci->mci_host, fname, sizeof fname,
+ true) < 0)
+ {
+ /* of course this should never happen */
+ if (tTd(56, 2))
+ sm_dprintf("mci_lock_host: Failed to generate host path for %s\n",
+ mci->mci_host);
+
+ retVal = EX_TEMPFAIL;
+ goto cleanup;
+ }
+
+ mci->mci_statfile = safefopen(fname, O_RDWR, FileMode,
+ SFF_NOLOCK|SFF_NOLINK|SFF_OPENASROOT|SFF_REGONLY|SFF_SAFEDIRPATH|SFF_CREAT);
+
+ if (mci->mci_statfile == NULL)
+ {
+ syserr("mci_lock_host: cannot create host lock file %s", fname);
+ goto cleanup;
+ }
+
+ if (!lockfile(sm_io_getinfo(mci->mci_statfile, SM_IO_WHAT_FD, NULL),
+ fname, "", LOCK_EX|LOCK_NB))
+ {
+ if (tTd(56, 2))
+ sm_dprintf("mci_lock_host: couldn't get lock on %s\n",
+ fname);
+ (void) sm_io_close(mci->mci_statfile, SM_TIME_DEFAULT);
+ mci->mci_statfile = NULL;
+ retVal = EX_TEMPFAIL;
+ goto cleanup;
+ }
+
+ if (tTd(56, 12) && mci->mci_statfile != NULL)
+ sm_dprintf("mci_lock_host: Sanity check -- lock is good\n");
+
+cleanup:
+ errno = save_errno;
+ return retVal;
+}
+/*
+** MCI_UNLOCK_HOST -- unlock host
+**
+** Clean up the lock on a host, close the file, let
+** someone else use it.
+**
+** Parameters:
+** mci -- us.
+**
+** Returns:
+** nothing.
+*/
+
+void
+mci_unlock_host(mci)
+ MCI *mci;
+{
+ int save_errno = errno;
+
+ if (mci == NULL)
+ {
+ if (tTd(56, 1))
+ sm_dprintf("mci_unlock_host: NULL mci\n");
+ return;
+ }
+
+ if (HostStatDir == NULL || mci->mci_host == NULL)
+ return;
+
+ if (!SingleThreadDelivery && mci_lock_host_statfile(mci) == EX_TEMPFAIL)
+ {
+ if (tTd(56, 1))
+ sm_dprintf("mci_unlock_host: stat file already locked\n");
+ }
+ else
+ {
+ if (tTd(56, 2))
+ sm_dprintf("mci_unlock_host: store prior to unlock\n");
+ mci_store_persistent(mci);
+ }
+
+ if (mci->mci_statfile != NULL)
+ {
+ (void) sm_io_close(mci->mci_statfile, SM_TIME_DEFAULT);
+ mci->mci_statfile = NULL;
+ }
+
+ errno = save_errno;
+}
+/*
+** MCI_LOAD_PERSISTENT -- load persistent host info
+**
+** Load information about host that is kept
+** in common for all running sendmails.
+**
+** Parameters:
+** mci -- the host/connection to load persistent info for.
+**
+** Returns:
+** true -- lock was successful
+** false -- lock failed
+*/
+
+static bool
+mci_load_persistent(mci)
+ MCI *mci;
+{
+ int save_errno = errno;
+ bool locked = true;
+ SM_FILE_T *fp;
+ char fname[MAXPATHLEN];
+
+ if (mci == NULL)
+ {
+ if (tTd(56, 1))
+ sm_dprintf("mci_load_persistent: NULL mci\n");
+ return true;
+ }
+
+ if (IgnoreHostStatus || HostStatDir == NULL || mci->mci_host == NULL)
+ return true;
+
+ /* Already have the persistent information in memory */
+ if (SingleThreadDelivery && mci->mci_statfile != NULL)
+ return true;
+
+ if (tTd(56, 1))
+ sm_dprintf("mci_load_persistent: Attempting to load persistent information for %s\n",
+ mci->mci_host);
+
+ if (mci_generate_persistent_path(mci->mci_host, fname, sizeof fname,
+ false) < 0)
+ {
+ /* Not much we can do if the file isn't there... */
+ if (tTd(56, 1))
+ sm_dprintf("mci_load_persistent: Couldn't generate host path\n");
+ goto cleanup;
+ }
+
+ fp = safefopen(fname, O_RDONLY, FileMode,
+ SFF_NOLOCK|SFF_NOLINK|SFF_OPENASROOT|SFF_REGONLY|SFF_SAFEDIRPATH);
+ if (fp == NULL)
+ {
+ /* I can't think of any reason this should ever happen */
+ if (tTd(56, 1))
+ sm_dprintf("mci_load_persistent: open(%s): %s\n",
+ fname, sm_errstring(errno));
+ goto cleanup;
+ }
+
+ FileName = fname;
+ locked = lockfile(sm_io_getinfo(fp, SM_IO_WHAT_FD, NULL), fname, "",
+ LOCK_SH|LOCK_NB);
+ if (locked)
+ {
+ (void) mci_read_persistent(fp, mci);
+ (void) lockfile(sm_io_getinfo(fp, SM_IO_WHAT_FD, NULL), fname,
+ "", LOCK_UN);
+ }
+ FileName = NULL;
+ (void) sm_io_close(fp, SM_TIME_DEFAULT);
+
+cleanup:
+ errno = save_errno;
+ return locked;
+}
+/*
+** MCI_READ_PERSISTENT -- read persistent host status file
+**
+** Parameters:
+** fp -- the file pointer to read.
+** mci -- the pointer to fill in.
+**
+** Returns:
+** -1 -- if the file was corrupt.
+** 0 -- otherwise.
+**
+** Warning:
+** This code makes the assumption that this data
+** will be read in an atomic fashion, and that the data
+** was written in an atomic fashion. Any other functioning
+** may lead to some form of insanity. This should be
+** perfectly safe due to underlying stdio buffering.
+*/
+
+static int
+mci_read_persistent(fp, mci)
+ SM_FILE_T *fp;
+ register MCI *mci;
+{
+ int ver;
+ register char *p;
+ int saveLineNumber = LineNumber;
+ char buf[MAXLINE];
+
+ if (fp == NULL)
+ syserr("mci_read_persistent: NULL fp");
+ if (mci == NULL)
+ syserr("mci_read_persistent: NULL mci");
+ if (tTd(56, 93))
+ {
+ sm_dprintf("mci_read_persistent: fp=%lx, mci=",
+ (unsigned long) fp);
+ }
+
+ SM_FREE_CLR(mci->mci_status);
+ SM_FREE_CLR(mci->mci_rstatus);
+
+ sm_io_rewind(fp, SM_TIME_DEFAULT);
+ ver = -1;
+ LineNumber = 0;
+ while (sm_io_fgets(fp, SM_TIME_DEFAULT, buf, sizeof buf) != NULL)
+ {
+ LineNumber++;
+ p = strchr(buf, '\n');
+ if (p != NULL)
+ *p = '\0';
+ switch (buf[0])
+ {
+ case 'V': /* version stamp */
+ ver = atoi(&buf[1]);
+ if (ver < 0 || ver > 0)
+ syserr("Unknown host status version %d: %d max",
+ ver, 0);
+ break;
+
+ case 'E': /* UNIX error number */
+ mci->mci_errno = atoi(&buf[1]);
+ break;
+
+ case 'H': /* DNS error number */
+ mci->mci_herrno = atoi(&buf[1]);
+ break;
+
+ case 'S': /* UNIX exit status */
+ mci->mci_exitstat = atoi(&buf[1]);
+ break;
+
+ case 'D': /* DSN status */
+ mci->mci_status = newstr(&buf[1]);
+ break;
+
+ case 'R': /* SMTP status */
+ mci->mci_rstatus = newstr(&buf[1]);
+ break;
+
+ case 'U': /* last usage time */
+ mci->mci_lastuse = atol(&buf[1]);
+ break;
+
+ case '.': /* end of file */
+ if (tTd(56, 93))
+ mci_dump(sm_debug_file(), mci, false);
+ return 0;
+
+ default:
+ sm_syslog(LOG_CRIT, NOQID,
+ "%s: line %d: Unknown host status line \"%s\"",
+ FileName == NULL ? mci->mci_host : FileName,
+ LineNumber, buf);
+ LineNumber = saveLineNumber;
+ return -1;
+ }
+ }
+ LineNumber = saveLineNumber;
+ if (tTd(56, 93))
+ sm_dprintf("incomplete (missing dot for EOF)\n");
+ if (ver < 0)
+ return -1;
+ return 0;
+}
+/*
+** MCI_STORE_PERSISTENT -- Store persistent MCI information
+**
+** Store information about host that is kept
+** in common for all running sendmails.
+**
+** Parameters:
+** mci -- the host/connection to store persistent info for.
+**
+** Returns:
+** none.
+*/
+
+void
+mci_store_persistent(mci)
+ MCI *mci;
+{
+ int save_errno = errno;
+
+ if (mci == NULL)
+ {
+ if (tTd(56, 1))
+ sm_dprintf("mci_store_persistent: NULL mci\n");
+ return;
+ }
+
+ if (HostStatDir == NULL || mci->mci_host == NULL)
+ return;
+
+ if (tTd(56, 1))
+ sm_dprintf("mci_store_persistent: Storing information for %s\n",
+ mci->mci_host);
+
+ if (mci->mci_statfile == NULL)
+ {
+ if (tTd(56, 1))
+ sm_dprintf("mci_store_persistent: no statfile\n");
+ return;
+ }
+
+ sm_io_rewind(mci->mci_statfile, SM_TIME_DEFAULT);
+#if !NOFTRUNCATE
+ (void) ftruncate(sm_io_getinfo(mci->mci_statfile, SM_IO_WHAT_FD, NULL),
+ (off_t) 0);
+#endif /* !NOFTRUNCATE */
+
+ (void) sm_io_fprintf(mci->mci_statfile, SM_TIME_DEFAULT, "V0\n");
+ (void) sm_io_fprintf(mci->mci_statfile, SM_TIME_DEFAULT, "E%d\n",
+ mci->mci_errno);
+ (void) sm_io_fprintf(mci->mci_statfile, SM_TIME_DEFAULT, "H%d\n",
+ mci->mci_herrno);
+ (void) sm_io_fprintf(mci->mci_statfile, SM_TIME_DEFAULT, "S%d\n",
+ mci->mci_exitstat);
+ if (mci->mci_status != NULL)
+ (void) sm_io_fprintf(mci->mci_statfile, SM_TIME_DEFAULT,
+ "D%.80s\n",
+ denlstring(mci->mci_status, true, false));
+ if (mci->mci_rstatus != NULL)
+ (void) sm_io_fprintf(mci->mci_statfile, SM_TIME_DEFAULT,
+ "R%.80s\n",
+ denlstring(mci->mci_rstatus, true, false));
+ (void) sm_io_fprintf(mci->mci_statfile, SM_TIME_DEFAULT, "U%ld\n",
+ (long)(mci->mci_lastuse));
+ (void) sm_io_fprintf(mci->mci_statfile, SM_TIME_DEFAULT, ".\n");
+
+ (void) sm_io_flush(mci->mci_statfile, SM_TIME_DEFAULT);
+
+ errno = save_errno;
+ return;
+}
+/*
+** MCI_TRAVERSE_PERSISTENT -- walk persistent status tree
+**
+** Recursively find all the mci host files in `pathname'. Default to
+** main host status directory if no path is provided.
+** Call (*action)(pathname, host) for each file found.
+**
+** Note: all information is collected in a list before it is processed.
+** This may not be the best way to do it, but it seems safest, since
+** the file system would be touched while we are attempting to traverse
+** the directory tree otherwise (during purges).
+**
+** Parameters:
+** action -- function to call on each node. If returns < 0,
+** return immediately.
+** pathname -- root of tree. If null, use main host status
+** directory.
+**
+** Returns:
+** < 0 -- if any action routine returns a negative value, that
+** value is returned.
+** 0 -- if we successfully went to completion.
+** > 0 -- return status from action()
+*/
+
+int
+mci_traverse_persistent(action, pathname)
+ int (*action)__P((char *, char *));
+ char *pathname;
+{
+ struct stat statbuf;
+ DIR *d;
+ int ret;
+
+ if (pathname == NULL)
+ pathname = HostStatDir;
+ if (pathname == NULL)
+ return -1;
+
+ if (tTd(56, 1))
+ sm_dprintf("mci_traverse: pathname is %s\n", pathname);
+
+ ret = stat(pathname, &statbuf);
+ if (ret < 0)
+ {
+ if (tTd(56, 2))
+ sm_dprintf("mci_traverse: Failed to stat %s: %s\n",
+ pathname, sm_errstring(errno));
+ return ret;
+ }
+ if (S_ISDIR(statbuf.st_mode))
+ {
+ bool leftone, removedone;
+ size_t len;
+ char *newptr;
+ struct dirent *e;
+ char newpath[MAXPATHLEN];
+
+ if ((d = opendir(pathname)) == NULL)
+ {
+ if (tTd(56, 2))
+ sm_dprintf("mci_traverse: opendir %s: %s\n",
+ pathname, sm_errstring(errno));
+ return -1;
+ }
+ len = sizeof(newpath) - MAXNAMLEN - 3;
+ if (sm_strlcpy(newpath, pathname, len) >= len)
+ {
+ if (tTd(56, 2))
+ sm_dprintf("mci_traverse: path \"%s\" too long",
+ pathname);
+ return -1;
+ }
+ newptr = newpath + strlen(newpath);
+ *newptr++ = '/';
+
+ /*
+ ** repeat until no file has been removed
+ ** this may become ugly when several files "expire"
+ ** during these loops, but it's better than doing
+ ** a rewinddir() inside the inner loop
+ */
+
+ do
+ {
+ leftone = removedone = false;
+ while ((e = readdir(d)) != NULL)
+ {
+ if (e->d_name[0] == '.')
+ continue;
+
+ (void) sm_strlcpy(newptr, e->d_name,
+ sizeof newpath -
+ (newptr - newpath));
+
+ if (StopRequest)
+ stop_sendmail();
+ ret = mci_traverse_persistent(action, newpath);
+ if (ret < 0)
+ break;
+ if (ret == 1)
+ leftone = true;
+ if (!removedone && ret == 0 &&
+ action == mci_purge_persistent)
+ removedone = true;
+ }
+ if (ret < 0)
+ break;
+
+ /*
+ ** The following appears to be
+ ** necessary during purges, since
+ ** we modify the directory structure
+ */
+
+ if (removedone)
+ rewinddir(d);
+ if (tTd(56, 40))
+ sm_dprintf("mci_traverse: path %s: ret %d removed %d left %d\n",
+ pathname, ret, removedone, leftone);
+ } while (removedone);
+
+ /* purge (or whatever) the directory proper */
+ if (!leftone)
+ {
+ *--newptr = '\0';
+ ret = (*action)(newpath, NULL);
+ }
+ (void) closedir(d);
+ }
+ else if (S_ISREG(statbuf.st_mode))
+ {
+ char *end = pathname + strlen(pathname) - 1;
+ char *start;
+ char *scan;
+ char host[MAXHOSTNAMELEN];
+ char *hostptr = host;
+
+ /*
+ ** Reconstruct the host name from the path to the
+ ** persistent information.
+ */
+
+ do
+ {
+ if (hostptr != host)
+ *(hostptr++) = '.';
+ start = end;
+ while (start > pathname && *(start - 1) != '/')
+ start--;
+
+ if (*end == '.')
+ end--;
+
+ for (scan = start; scan <= end; scan++)
+ *(hostptr++) = *scan;
+
+ end = start - 2;
+ } while (end > pathname && *end == '.');
+
+ *hostptr = '\0';
+
+ /*
+ ** Do something with the file containing the persistent
+ ** information.
+ */
+
+ ret = (*action)(pathname, host);
+ }
+
+ return ret;
+}
+/*
+** MCI_PRINT_PERSISTENT -- print persistent info
+**
+** Dump the persistent information in the file 'pathname'
+**
+** Parameters:
+** pathname -- the pathname to the status file.
+** hostname -- the corresponding host name.
+**
+** Returns:
+** 0
+*/
+
+int
+mci_print_persistent(pathname, hostname)
+ char *pathname;
+ char *hostname;
+{
+ static bool initflag = false;
+ SM_FILE_T *fp;
+ int width = Verbose ? 78 : 25;
+ bool locked;
+ MCI mcib;
+
+ /* skip directories */
+ if (hostname == NULL)
+ return 0;
+
+ if (StopRequest)
+ stop_sendmail();
+
+ if (!initflag)
+ {
+ initflag = true;
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ " -------------- Hostname --------------- How long ago ---------Results---------\n");
+ }
+
+ fp = safefopen(pathname, O_RDONLY, FileMode,
+ SFF_NOLOCK|SFF_NOLINK|SFF_OPENASROOT|SFF_REGONLY|SFF_SAFEDIRPATH);
+
+ if (fp == NULL)
+ {
+ if (tTd(56, 1))
+ sm_dprintf("mci_print_persistent: cannot open %s: %s\n",
+ pathname, sm_errstring(errno));
+ return 0;
+ }
+
+ FileName = pathname;
+ memset(&mcib, '\0', sizeof mcib);
+ if (mci_read_persistent(fp, &mcib) < 0)
+ {
+ syserr("%s: could not read status file", pathname);
+ (void) sm_io_close(fp, SM_TIME_DEFAULT);
+ FileName = NULL;
+ return 0;
+ }
+
+ locked = !lockfile(sm_io_getinfo(fp, SM_IO_WHAT_FD, NULL), pathname,
+ "", LOCK_SH|LOCK_NB);
+ (void) sm_io_close(fp, SM_TIME_DEFAULT);
+ FileName = NULL;
+
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%c%-39s %12s ",
+ locked ? '*' : ' ', hostname,
+ pintvl(curtime() - mcib.mci_lastuse, true));
+ if (mcib.mci_rstatus != NULL)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%.*s\n", width,
+ mcib.mci_rstatus);
+ else if (mcib.mci_exitstat == EX_TEMPFAIL && mcib.mci_errno != 0)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Deferred: %.*s\n", width - 10,
+ sm_errstring(mcib.mci_errno));
+ else if (mcib.mci_exitstat != 0)
+ {
+ char *exmsg = sm_sysexmsg(mcib.mci_exitstat);
+
+ if (exmsg == NULL)
+ {
+ char buf[80];
+
+ (void) sm_snprintf(buf, sizeof buf,
+ "Unknown mailer error %d",
+ mcib.mci_exitstat);
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%.*s\n",
+ width, buf);
+ }
+ else
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%.*s\n",
+ width, &exmsg[5]);
+ }
+ else if (mcib.mci_errno == 0)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "OK\n");
+ else
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "OK: %.*s\n",
+ width - 4, sm_errstring(mcib.mci_errno));
+
+ return 0;
+}
+/*
+** MCI_PURGE_PERSISTENT -- Remove a persistence status file.
+**
+** Parameters:
+** pathname -- path to the status file.
+** hostname -- name of host corresponding to that file.
+** NULL if this is a directory (domain).
+**
+** Returns:
+** 0 -- ok
+** 1 -- file not deleted (too young, incorrect format)
+** < 0 -- some error occurred
+*/
+
+int
+mci_purge_persistent(pathname, hostname)
+ char *pathname;
+ char *hostname;
+{
+ struct stat statbuf;
+ char *end = pathname + strlen(pathname) - 1;
+ int ret;
+
+ if (tTd(56, 1))
+ sm_dprintf("mci_purge_persistent: purging %s\n", pathname);
+
+ ret = stat(pathname, &statbuf);
+ if (ret < 0)
+ {
+ if (tTd(56, 2))
+ sm_dprintf("mci_purge_persistent: Failed to stat %s: %s\n",
+ pathname, sm_errstring(errno));
+ return ret;
+ }
+ if (curtime() - statbuf.st_mtime <= MciInfoTimeout)
+ return 1;
+ if (hostname != NULL)
+ {
+ /* remove the file */
+ ret = unlink(pathname);
+ if (ret < 0)
+ {
+ if (LogLevel > 8)
+ sm_syslog(LOG_ERR, NOQID,
+ "mci_purge_persistent: failed to unlink %s: %s",
+ pathname, sm_errstring(errno));
+ if (tTd(56, 2))
+ sm_dprintf("mci_purge_persistent: failed to unlink %s: %s\n",
+ pathname, sm_errstring(errno));
+ return ret;
+ }
+ }
+ else
+ {
+ /* remove the directory */
+ if (*end != '.')
+ return 1;
+
+ if (tTd(56, 1))
+ sm_dprintf("mci_purge_persistent: dpurge %s\n", pathname);
+
+ ret = rmdir(pathname);
+ if (ret < 0)
+ {
+ if (tTd(56, 2))
+ sm_dprintf("mci_purge_persistent: rmdir %s: %s\n",
+ pathname, sm_errstring(errno));
+ return ret;
+ }
+ }
+
+ return 0;
+}
+/*
+** MCI_GENERATE_PERSISTENT_PATH -- generate path from hostname
+**
+** Given `host', convert from a.b.c to $HostStatDir/c./b./a,
+** putting the result into `path'. if `createflag' is set, intervening
+** directories will be created as needed.
+**
+** Parameters:
+** host -- host name to convert from.
+** path -- place to store result.
+** pathlen -- length of path buffer.
+** createflag -- if set, create intervening directories as
+** needed.
+**
+** Returns:
+** 0 -- success
+** -1 -- failure
+*/
+
+static int
+mci_generate_persistent_path(host, path, pathlen, createflag)
+ const char *host;
+ char *path;
+ int pathlen;
+ bool createflag;
+{
+ char *elem, *p, *x, ch;
+ int ret = 0;
+ int len;
+ char t_host[MAXHOSTNAMELEN];
+#if NETINET6
+ struct in6_addr in6_addr;
+#endif /* NETINET6 */
+
+ /*
+ ** Rationality check the arguments.
+ */
+
+ if (host == NULL)
+ {
+ syserr("mci_generate_persistent_path: null host");
+ return -1;
+ }
+ if (path == NULL)
+ {
+ syserr("mci_generate_persistent_path: null path");
+ return -1;
+ }
+
+ if (tTd(56, 80))
+ sm_dprintf("mci_generate_persistent_path(%s): ", host);
+
+ if (*host == '\0' || *host == '.')
+ return -1;
+
+ /* make certain this is not a bracketed host number */
+ if (strlen(host) > sizeof t_host - 1)
+ return -1;
+ if (host[0] == '[')
+ (void) sm_strlcpy(t_host, host + 1, sizeof t_host);
+ else
+ (void) sm_strlcpy(t_host, host, sizeof t_host);
+
+ /*
+ ** Delete any trailing dots from the hostname.
+ ** Leave 'elem' pointing at the \0.
+ */
+
+ elem = t_host + strlen(t_host);
+ while (elem > t_host &&
+ (elem[-1] == '.' || (host[0] == '[' && elem[-1] == ']')))
+ *--elem = '\0';
+
+ /* check for bogus bracketed address */
+ if (host[0] == '[')
+ {
+ bool good = false;
+# if NETINET6
+ if (anynet_pton(AF_INET6, t_host, &in6_addr) == 1)
+ good = true;
+# endif /* NETINET6 */
+# if NETINET
+ if (inet_addr(t_host) != INADDR_NONE)
+ good = true;
+# endif /* NETINET */
+ if (!good)
+ return -1;
+ }
+
+ /* check for what will be the final length of the path */
+ len = strlen(HostStatDir) + 2;
+ for (p = (char *) t_host; *p != '\0'; p++)
+ {
+ if (*p == '.')
+ len++;
+ len++;
+ if (p[0] == '.' && p[1] == '.')
+ return -1;
+ }
+ if (len > pathlen || len < 1)
+ return -1;
+ (void) sm_strlcpy(path, HostStatDir, pathlen);
+ p = path + strlen(path);
+ while (elem > t_host)
+ {
+ if (!path_is_dir(path, createflag))
+ {
+ ret = -1;
+ break;
+ }
+ elem--;
+ while (elem >= t_host && *elem != '.')
+ elem--;
+ *p++ = '/';
+ x = elem + 1;
+ while ((ch = *x++) != '\0' && ch != '.')
+ {
+ if (isascii(ch) && isupper(ch))
+ ch = tolower(ch);
+ if (ch == '/')
+ ch = ':'; /* / -> : */
+ *p++ = ch;
+ }
+ if (elem >= t_host)
+ *p++ = '.';
+ *p = '\0';
+ }
+ if (tTd(56, 80))
+ {
+ if (ret < 0)
+ sm_dprintf("FAILURE %d\n", ret);
+ else
+ sm_dprintf("SUCCESS %s\n", path);
+ }
+ return ret;
+}
diff --git a/usr/src/cmd/sendmail/src/milter.c b/usr/src/cmd/sendmail/src/milter.c
new file mode 100644
index 0000000000..85bdaabdc5
--- /dev/null
+++ b/usr/src/cmd/sendmail/src/milter.c
@@ -0,0 +1,4146 @@
+/*
+ * Copyright (c) 1999-2005 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sendmail.h>
+
+SM_RCSID("@(#)$Id: milter.c,v 8.229 2005/03/02 02:32:34 ca Exp $")
+
+#if MILTER
+# include <libmilter/mfapi.h>
+# include <libmilter/mfdef.h>
+
+# include <errno.h>
+# include <sys/time.h>
+# include <sys/uio.h>
+
+# if NETINET || NETINET6
+# include <arpa/inet.h>
+# if _FFR_MILTER_NAGLE
+# include <netinet/tcp.h>
+# endif /* _FFR_MILTER_NAGLE */
+# endif /* NETINET || NETINET6 */
+
+# include <sm/fdset.h>
+
+static void milter_connect_timeout __P((int));
+static void milter_error __P((struct milter *, ENVELOPE *));
+static int milter_open __P((struct milter *, bool, ENVELOPE *));
+static void milter_parse_timeouts __P((char *, struct milter *));
+
+static char *MilterConnectMacros[MAXFILTERMACROS + 1];
+static char *MilterHeloMacros[MAXFILTERMACROS + 1];
+static char *MilterEnvFromMacros[MAXFILTERMACROS + 1];
+static char *MilterEnvRcptMacros[MAXFILTERMACROS + 1];
+static char *MilterDataMacros[MAXFILTERMACROS + 1];
+static char *MilterEOMMacros[MAXFILTERMACROS + 1];
+static size_t MilterMaxDataSize = MILTER_MAX_DATA_SIZE;
+
+# define MILTER_CHECK_DONE_MSG() \
+ if (*state == SMFIR_REPLYCODE || \
+ *state == SMFIR_REJECT || \
+ *state == SMFIR_DISCARD || \
+ *state == SMFIR_TEMPFAIL) \
+ { \
+ /* Abort the filters to let them know we are done with msg */ \
+ milter_abort(e); \
+ }
+
+# define MILTER_CHECK_ERROR(initial, action) \
+ if (!initial && tTd(71, 100)) \
+ { \
+ if (e->e_quarmsg == NULL) \
+ { \
+ e->e_quarmsg = sm_rpool_strdup_x(e->e_rpool, \
+ "filter failure"); \
+ macdefine(&e->e_macro, A_PERM, macid("{quarantine}"), \
+ e->e_quarmsg); \
+ } \
+ } \
+ else if (tTd(71, 101)) \
+ { \
+ if (e->e_quarmsg == NULL) \
+ { \
+ e->e_quarmsg = sm_rpool_strdup_x(e->e_rpool, \
+ "filter failure"); \
+ macdefine(&e->e_macro, A_PERM, macid("{quarantine}"), \
+ e->e_quarmsg); \
+ } \
+ } \
+ else if (bitnset(SMF_TEMPFAIL, m->mf_flags)) \
+ *state = SMFIR_TEMPFAIL; \
+ else if (bitnset(SMF_TEMPDROP, m->mf_flags)) \
+ *state = SMFIR_SHUTDOWN; \
+ else if (bitnset(SMF_REJECT, m->mf_flags)) \
+ *state = SMFIR_REJECT; \
+ else \
+ action;
+
+# define MILTER_CHECK_REPLYCODE(default) \
+ if (response == NULL || \
+ strlen(response) + 1 != (size_t) rlen || \
+ rlen < 3 || \
+ (response[0] != '4' && response[0] != '5') || \
+ !isascii(response[1]) || !isdigit(response[1]) || \
+ !isascii(response[2]) || !isdigit(response[2])) \
+ { \
+ if (response != NULL) \
+ sm_free(response); /* XXX */ \
+ response = newstr(default); \
+ } \
+ else \
+ { \
+ char *ptr = response; \
+ \
+ /* Check for unprotected %'s in the string */ \
+ while (*ptr != '\0') \
+ { \
+ if (*ptr == '%' && *++ptr != '%') \
+ { \
+ sm_free(response); /* XXX */ \
+ response = newstr(default); \
+ break; \
+ } \
+ ptr++; \
+ } \
+ }
+
+# define MILTER_DF_ERROR(msg) \
+{ \
+ int save_errno = errno; \
+ \
+ if (tTd(64, 5)) \
+ { \
+ sm_dprintf(msg, dfname, sm_errstring(save_errno)); \
+ sm_dprintf("\n"); \
+ } \
+ if (MilterLogLevel > 0) \
+ sm_syslog(LOG_ERR, e->e_id, msg, dfname, sm_errstring(save_errno)); \
+ if (SuperSafe == SAFE_REALLY) \
+ { \
+ if (e->e_dfp != NULL) \
+ { \
+ (void) sm_io_close(e->e_dfp, SM_TIME_DEFAULT); \
+ e->e_dfp = NULL; \
+ } \
+ e->e_flags &= ~EF_HAS_DF; \
+ } \
+ errno = save_errno; \
+}
+
+/*
+** MILTER_TIMEOUT -- make sure socket is ready in time
+**
+** Parameters:
+** routine -- routine name for debug/logging
+** secs -- number of seconds in timeout
+** write -- waiting to read or write?
+** started -- whether this is part of a previous sequence
+**
+** Assumes 'm' is a milter structure for the current socket.
+*/
+
+# define MILTER_TIMEOUT(routine, secs, write, started) \
+{ \
+ int ret; \
+ int save_errno; \
+ fd_set fds; \
+ struct timeval tv; \
+ \
+ if (SM_FD_SETSIZE > 0 && m->mf_sock >= SM_FD_SETSIZE) \
+ { \
+ if (tTd(64, 5)) \
+ sm_dprintf("milter_%s(%s): socket %d is larger than FD_SETSIZE %d\n", \
+ (routine), m->mf_name, m->mf_sock, \
+ SM_FD_SETSIZE); \
+ if (MilterLogLevel > 0) \
+ sm_syslog(LOG_ERR, e->e_id, \
+ "Milter (%s): socket(%s) %d is larger than FD_SETSIZE %d", \
+ m->mf_name, (routine), m->mf_sock, \
+ SM_FD_SETSIZE); \
+ milter_error(m, e); \
+ return NULL; \
+ } \
+ \
+ do \
+ { \
+ FD_ZERO(&fds); \
+ SM_FD_SET(m->mf_sock, &fds); \
+ tv.tv_sec = (secs); \
+ tv.tv_usec = 0; \
+ ret = select(m->mf_sock + 1, \
+ (write) ? NULL : &fds, \
+ (write) ? &fds : NULL, \
+ NULL, &tv); \
+ } while (ret < 0 && errno == EINTR); \
+ \
+ switch (ret) \
+ { \
+ case 0: \
+ if (tTd(64, 5)) \
+ sm_dprintf("milter_%s(%s): timeout\n", (routine), \
+ m->mf_name); \
+ if (MilterLogLevel > 0) \
+ sm_syslog(LOG_ERR, e->e_id, \
+ "Milter (%s): %s %s %s %s", \
+ m->mf_name, "timeout", \
+ started ? "during" : "before", \
+ "data", (routine)); \
+ milter_error(m, e); \
+ return NULL; \
+ \
+ case -1: \
+ save_errno = errno; \
+ if (tTd(64, 5)) \
+ sm_dprintf("milter_%s(%s): select: %s\n", (routine), \
+ m->mf_name, sm_errstring(save_errno)); \
+ if (MilterLogLevel > 0) \
+ { \
+ sm_syslog(LOG_ERR, e->e_id, \
+ "Milter (%s): select(%s): %s", \
+ m->mf_name, (routine), \
+ sm_errstring(save_errno)); \
+ } \
+ milter_error(m, e); \
+ return NULL; \
+ \
+ default: \
+ if (SM_FD_ISSET(m->mf_sock, &fds)) \
+ break; \
+ if (tTd(64, 5)) \
+ sm_dprintf("milter_%s(%s): socket not ready\n", \
+ (routine), m->mf_name); \
+ if (MilterLogLevel > 0) \
+ { \
+ sm_syslog(LOG_ERR, e->e_id, \
+ "Milter (%s): socket(%s) not ready", \
+ m->mf_name, (routine)); \
+ } \
+ milter_error(m, e); \
+ return NULL; \
+ } \
+}
+
+/*
+** Low level functions
+*/
+
+/*
+** MILTER_READ -- read from a remote milter filter
+**
+** Parameters:
+** m -- milter to read from.
+** cmd -- return param for command read.
+** rlen -- return length of response string.
+** to -- timeout in seconds.
+** e -- current envelope.
+**
+** Returns:
+** response string (may be NULL)
+*/
+
+static char *
+milter_sysread(m, buf, sz, to, e)
+ struct milter *m;
+ char *buf;
+ ssize_t sz;
+ time_t to;
+ ENVELOPE *e;
+{
+ time_t readstart = 0;
+ ssize_t len, curl;
+ bool started = false;
+
+ curl = 0;
+
+ if (to > 0)
+ readstart = curtime();
+
+ for (;;)
+ {
+ if (to > 0)
+ {
+ time_t now;
+
+ now = curtime();
+ if (now - readstart >= to)
+ {
+ if (tTd(64, 5))
+ sm_dprintf("milter_read (%s): %s %s %s",
+ m->mf_name, "timeout",
+ started ? "during" : "before",
+ "data read");
+ if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "Milter (%s): %s %s %s",
+ m->mf_name, "timeout",
+ started ? "during" : "before",
+ "data read");
+ milter_error(m, e);
+ return NULL;
+ }
+ to -= now - readstart;
+ readstart = now;
+ MILTER_TIMEOUT("read", to, false, started);
+ }
+
+ len = read(m->mf_sock, buf + curl, sz - curl);
+
+ if (len < 0)
+ {
+ int save_errno = errno;
+
+ if (tTd(64, 5))
+ sm_dprintf("milter_read(%s): read returned %ld: %s\n",
+ m->mf_name, (long) len,
+ sm_errstring(save_errno));
+ if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "Milter (%s): read returned %ld: %s",
+ m->mf_name, (long) len,
+ sm_errstring(save_errno));
+ milter_error(m, e);
+ return NULL;
+ }
+
+ started = true;
+ curl += len;
+ if (len == 0 || curl >= sz)
+ break;
+
+ }
+
+ if (curl != sz)
+ {
+ if (tTd(64, 5))
+ sm_dprintf("milter_read(%s): cmd read returned %ld, expecting %ld\n",
+ m->mf_name, (long) curl, (long) sz);
+ if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "milter_read(%s): cmd read returned %ld, expecting %ld",
+ m->mf_name, (long) curl, (long) sz);
+ milter_error(m, e);
+ return NULL;
+ }
+ return buf;
+}
+
+static char *
+milter_read(m, cmd, rlen, to, e)
+ struct milter *m;
+ char *cmd;
+ ssize_t *rlen;
+ time_t to;
+ ENVELOPE *e;
+{
+ time_t readstart = 0;
+ ssize_t expl;
+ mi_int32 i;
+# if _FFR_MILTER_NAGLE
+# ifdef TCP_CORK
+ int cork = 0;
+# endif
+# endif /* _FFR_MILTER_NAGLE */
+ char *buf;
+ char data[MILTER_LEN_BYTES + 1];
+
+ if (m->mf_sock < 0)
+ {
+ if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "milter_read(%s): socket closed",
+ m->mf_name);
+ milter_error(m, e);
+ return NULL;
+ }
+
+ *rlen = 0;
+ *cmd = '\0';
+
+ if (to > 0)
+ readstart = curtime();
+
+# if _FFR_MILTER_NAGLE
+# ifdef TCP_CORK
+ setsockopt(m->mf_sock, IPPROTO_TCP, TCP_CORK, (char *)&cork,
+ sizeof(cork));
+# endif
+# endif /* _FFR_MILTER_NAGLE */
+
+ if (milter_sysread(m, data, sizeof data, to, e) == NULL)
+ return NULL;
+
+# if _FFR_MILTER_NAGLE
+# ifdef TCP_CORK
+ cork = 1;
+ setsockopt(m->mf_sock, IPPROTO_TCP, TCP_CORK, (char *)&cork,
+ sizeof(cork));
+# endif
+# endif /* _FFR_MILTER_NAGLE */
+
+ /* reset timeout */
+ if (to > 0)
+ {
+ time_t now;
+
+ now = curtime();
+ if (now - readstart >= to)
+ {
+ if (tTd(64, 5))
+ sm_dprintf("milter_read(%s): timeout before data read\n",
+ m->mf_name);
+ if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "Milter read(%s): timeout before data read",
+ m->mf_name);
+ milter_error(m, e);
+ return NULL;
+ }
+ to -= now - readstart;
+ }
+
+ *cmd = data[MILTER_LEN_BYTES];
+ data[MILTER_LEN_BYTES] = '\0';
+ (void) memcpy(&i, data, MILTER_LEN_BYTES);
+ expl = ntohl(i) - 1;
+
+ if (tTd(64, 25))
+ sm_dprintf("milter_read(%s): expecting %ld bytes\n",
+ m->mf_name, (long) expl);
+
+ if (expl < 0)
+ {
+ if (tTd(64, 5))
+ sm_dprintf("milter_read(%s): read size %ld out of range\n",
+ m->mf_name, (long) expl);
+ if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "milter_read(%s): read size %ld out of range",
+ m->mf_name, (long) expl);
+ milter_error(m, e);
+ return NULL;
+ }
+
+ if (expl == 0)
+ return NULL;
+
+ buf = (char *) xalloc(expl);
+
+ if (milter_sysread(m, buf, expl, to, e) == NULL)
+ {
+ sm_free(buf); /* XXX */
+ return NULL;
+ }
+
+ if (tTd(64, 50))
+ sm_dprintf("milter_read(%s): Returning %*s\n",
+ m->mf_name, (int) expl, buf);
+ *rlen = expl;
+ return buf;
+}
+
+/*
+** MILTER_WRITE -- write to a remote milter filter
+**
+** Parameters:
+** m -- milter to read from.
+** cmd -- command to send.
+** buf -- optional command data.
+** len -- length of buf.
+** to -- timeout in seconds.
+** e -- current envelope.
+**
+** Returns:
+** buf if successful, NULL otherwise
+** Not actually used anywhere but function prototype
+** must match milter_read()
+*/
+
+static char *
+milter_write(m, cmd, buf, len, to, e)
+ struct milter *m;
+ char cmd;
+ char *buf;
+ ssize_t len;
+ time_t to;
+ ENVELOPE *e;
+{
+ time_t writestart = (time_t) 0;
+ ssize_t sl, i;
+ int num_vectors;
+ mi_int32 nl;
+ char data[MILTER_LEN_BYTES + 1];
+ bool started = false;
+ struct iovec vector[2];
+
+ /*
+ ** At most two buffers will be written, though
+ ** only one may actually be used (see num_vectors).
+ ** The first is the size/command and the second is the command data.
+ */
+
+ if (len < 0 || len > MilterMaxDataSize)
+ {
+ if (tTd(64, 5))
+ sm_dprintf("milter_write(%s): length %ld out of range\n",
+ m->mf_name, (long) len);
+ if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "milter_write(%s): length %ld out of range",
+ m->mf_name, (long) len);
+ milter_error(m, e);
+ return NULL;
+ }
+ if (m->mf_sock < 0)
+ {
+ if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "milter_write(%s): socket closed",
+ m->mf_name);
+ milter_error(m, e);
+ return NULL;
+ }
+
+ if (tTd(64, 20))
+ sm_dprintf("milter_write(%s): cmd %c, len %ld\n",
+ m->mf_name, cmd, (long) len);
+
+ nl = htonl(len + 1); /* add 1 for the cmd char */
+ (void) memcpy(data, (char *) &nl, MILTER_LEN_BYTES);
+ data[MILTER_LEN_BYTES] = cmd;
+ sl = MILTER_LEN_BYTES + 1;
+
+ /* set up the vector for the size / command */
+ vector[0].iov_base = (void *) data;
+ vector[0].iov_len = sl;
+
+ /*
+ ** Determine if there is command data. If so, there will be two
+ ** vectors. If not, there will be only one. The vectors are set
+ ** up here and 'num_vectors' and 'sl' are set appropriately.
+ */
+
+ /* NOTE: len<0 has already been checked for. Pedantic */
+ if (len <= 0 || buf == NULL)
+ {
+ /* There is no command data -- only a size / command data */
+ num_vectors = 1;
+ }
+ else
+ {
+ /*
+ ** There is both size / command and command data.
+ ** Set up the vector for the command data.
+ */
+
+ num_vectors = 2;
+ sl += len;
+ vector[1].iov_base = (void *) buf;
+ vector[1].iov_len = len;
+
+ if (tTd(64, 50))
+ sm_dprintf("milter_write(%s): Sending %*s\n",
+ m->mf_name, (int) len, buf);
+ }
+
+ if (to > 0)
+ {
+ writestart = curtime();
+ MILTER_TIMEOUT("write", to, true, started);
+ }
+
+ /* write the vector(s) */
+ i = writev(m->mf_sock, vector, num_vectors);
+ if (i != sl)
+ {
+ int save_errno = errno;
+
+ if (tTd(64, 5))
+ sm_dprintf("milter_write(%s): write(%c) returned %ld, expected %ld: %s\n",
+ m->mf_name, cmd, (long) i, (long) sl,
+ sm_errstring(save_errno));
+ if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "Milter (%s): write(%c) returned %ld, expected %ld: %s",
+ m->mf_name, cmd, (long) i, (long) sl,
+ sm_errstring(save_errno));
+ milter_error(m, e);
+ return NULL;
+ }
+ return buf;
+}
+
+/*
+** Utility functions
+*/
+
+/*
+** MILTER_OPEN -- connect to remote milter filter
+**
+** Parameters:
+** m -- milter to connect to.
+** parseonly -- parse but don't connect.
+** e -- current envelope.
+**
+** Returns:
+** connected socket if successful && !parseonly,
+** 0 upon parse success if parseonly,
+** -1 otherwise.
+*/
+
+static jmp_buf MilterConnectTimeout;
+
+static int
+milter_open(m, parseonly, e)
+ struct milter *m;
+ bool parseonly;
+ ENVELOPE *e;
+{
+ int sock = 0;
+ SOCKADDR_LEN_T addrlen = 0;
+ int addrno = 0;
+ int save_errno;
+ char *p;
+ char *colon;
+ char *at;
+ struct hostent *hp = NULL;
+ SOCKADDR addr;
+
+ if (m->mf_conn == NULL || m->mf_conn[0] == '\0')
+ {
+ if (tTd(64, 5))
+ sm_dprintf("X%s: empty or missing socket information\n",
+ m->mf_name);
+ if (parseonly)
+ syserr("X%s: empty or missing socket information",
+ m->mf_name);
+ else if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "Milter (%s): empty or missing socket information",
+ m->mf_name);
+ milter_error(m, e);
+ return -1;
+ }
+
+ /* protocol:filename or protocol:port@host */
+ memset(&addr, '\0', sizeof addr);
+ p = m->mf_conn;
+ colon = strchr(p, ':');
+ if (colon != NULL)
+ {
+ *colon = '\0';
+
+ if (*p == '\0')
+ {
+# if NETUNIX
+ /* default to AF_UNIX */
+ addr.sa.sa_family = AF_UNIX;
+# else /* NETUNIX */
+# if NETINET
+ /* default to AF_INET */
+ addr.sa.sa_family = AF_INET;
+# else /* NETINET */
+# if NETINET6
+ /* default to AF_INET6 */
+ addr.sa.sa_family = AF_INET6;
+# else /* NETINET6 */
+ /* no protocols available */
+ if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "Milter (%s): no valid socket protocols available",
+ m->mf_name);
+ milter_error(m, e);
+ return -1;
+# endif /* NETINET6 */
+# endif /* NETINET */
+# endif /* NETUNIX */
+ }
+# if NETUNIX
+ else if (sm_strcasecmp(p, "unix") == 0 ||
+ sm_strcasecmp(p, "local") == 0)
+ addr.sa.sa_family = AF_UNIX;
+# endif /* NETUNIX */
+# if NETINET
+ else if (sm_strcasecmp(p, "inet") == 0)
+ addr.sa.sa_family = AF_INET;
+# endif /* NETINET */
+# if NETINET6
+ else if (sm_strcasecmp(p, "inet6") == 0)
+ addr.sa.sa_family = AF_INET6;
+# endif /* NETINET6 */
+ else
+ {
+# ifdef EPROTONOSUPPORT
+ errno = EPROTONOSUPPORT;
+# else /* EPROTONOSUPPORT */
+ errno = EINVAL;
+# endif /* EPROTONOSUPPORT */
+ if (tTd(64, 5))
+ sm_dprintf("X%s: unknown socket type %s\n",
+ m->mf_name, p);
+ if (parseonly)
+ syserr("X%s: unknown socket type %s",
+ m->mf_name, p);
+ else if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "Milter (%s): unknown socket type %s",
+ m->mf_name, p);
+ milter_error(m, e);
+ return -1;
+ }
+ *colon++ = ':';
+ }
+ else
+ {
+ /* default to AF_UNIX */
+ addr.sa.sa_family = AF_UNIX;
+ colon = p;
+ }
+
+# if NETUNIX
+ if (addr.sa.sa_family == AF_UNIX)
+ {
+ long sff = SFF_SAFEDIRPATH|SFF_OPENASROOT|SFF_NOLINK|SFF_EXECOK;
+
+ at = colon;
+ if (strlen(colon) >= sizeof addr.sunix.sun_path)
+ {
+ if (tTd(64, 5))
+ sm_dprintf("X%s: local socket name %s too long\n",
+ m->mf_name, colon);
+ errno = EINVAL;
+ if (parseonly)
+ syserr("X%s: local socket name %s too long",
+ m->mf_name, colon);
+ else if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "Milter (%s): local socket name %s too long",
+ m->mf_name, colon);
+ milter_error(m, e);
+ return -1;
+ }
+ errno = safefile(colon, RunAsUid, RunAsGid, RunAsUserName, sff,
+ S_IRUSR|S_IWUSR, NULL);
+
+ /* if just parsing .cf file, socket doesn't need to exist */
+ if (parseonly && errno == ENOENT)
+ {
+ if (OpMode == MD_DAEMON ||
+ OpMode == MD_FGDAEMON)
+ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ "WARNING: X%s: local socket name %s missing\n",
+ m->mf_name, colon);
+ }
+ else if (errno != 0)
+ {
+ /* if not safe, don't create */
+ save_errno = errno;
+ if (tTd(64, 5))
+ sm_dprintf("X%s: local socket name %s unsafe\n",
+ m->mf_name, colon);
+ errno = save_errno;
+ if (parseonly)
+ {
+ if (OpMode == MD_DAEMON ||
+ OpMode == MD_FGDAEMON ||
+ OpMode == MD_SMTP)
+ syserr("X%s: local socket name %s unsafe",
+ m->mf_name, colon);
+ }
+ else if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "Milter (%s): local socket name %s unsafe",
+ m->mf_name, colon);
+ milter_error(m, e);
+ return -1;
+ }
+
+ (void) sm_strlcpy(addr.sunix.sun_path, colon,
+ sizeof addr.sunix.sun_path);
+ addrlen = sizeof (struct sockaddr_un);
+ }
+ else
+# endif /* NETUNIX */
+# if NETINET || NETINET6
+ if (false
+# if NETINET
+ || addr.sa.sa_family == AF_INET
+# endif /* NETINET */
+# if NETINET6
+ || addr.sa.sa_family == AF_INET6
+# endif /* NETINET6 */
+ )
+ {
+ unsigned short port;
+
+ /* Parse port@host */
+ at = strchr(colon, '@');
+ if (at == NULL)
+ {
+ if (tTd(64, 5))
+ sm_dprintf("X%s: bad address %s (expected port@host)\n",
+ m->mf_name, colon);
+ if (parseonly)
+ syserr("X%s: bad address %s (expected port@host)",
+ m->mf_name, colon);
+ else if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "Milter (%s): bad address %s (expected port@host)",
+ m->mf_name, colon);
+ milter_error(m, e);
+ return -1;
+ }
+ *at = '\0';
+ if (isascii(*colon) && isdigit(*colon))
+ port = htons((unsigned short) atoi(colon));
+ else
+ {
+# ifdef NO_GETSERVBYNAME
+ if (tTd(64, 5))
+ sm_dprintf("X%s: invalid port number %s\n",
+ m->mf_name, colon);
+ if (parseonly)
+ syserr("X%s: invalid port number %s",
+ m->mf_name, colon);
+ else if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "Milter (%s): invalid port number %s",
+ m->mf_name, colon);
+ milter_error(m, e);
+ return -1;
+# else /* NO_GETSERVBYNAME */
+ register struct servent *sp;
+
+ sp = getservbyname(colon, "tcp");
+ if (sp == NULL)
+ {
+ save_errno = errno;
+ if (tTd(64, 5))
+ sm_dprintf("X%s: unknown port name %s\n",
+ m->mf_name, colon);
+ errno = save_errno;
+ if (parseonly)
+ syserr("X%s: unknown port name %s",
+ m->mf_name, colon);
+ else if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "Milter (%s): unknown port name %s",
+ m->mf_name, colon);
+ milter_error(m, e);
+ return -1;
+ }
+ port = sp->s_port;
+# endif /* NO_GETSERVBYNAME */
+ }
+ *at++ = '@';
+ if (*at == '[')
+ {
+ char *end;
+
+ end = strchr(at, ']');
+ if (end != NULL)
+ {
+ bool found = false;
+# if NETINET
+ unsigned long hid = INADDR_NONE;
+# endif /* NETINET */
+# if NETINET6
+ struct sockaddr_in6 hid6;
+# endif /* NETINET6 */
+
+ *end = '\0';
+# if NETINET
+ if (addr.sa.sa_family == AF_INET &&
+ (hid = inet_addr(&at[1])) != INADDR_NONE)
+ {
+ addr.sin.sin_addr.s_addr = hid;
+ addr.sin.sin_port = port;
+ found = true;
+ }
+# endif /* NETINET */
+# if NETINET6
+ (void) memset(&hid6, '\0', sizeof hid6);
+ if (addr.sa.sa_family == AF_INET6 &&
+ anynet_pton(AF_INET6, &at[1],
+ &hid6.sin6_addr) == 1)
+ {
+ addr.sin6.sin6_addr = hid6.sin6_addr;
+ addr.sin6.sin6_port = port;
+ found = true;
+ }
+# endif /* NETINET6 */
+ *end = ']';
+ if (!found)
+ {
+ if (tTd(64, 5))
+ sm_dprintf("X%s: Invalid numeric domain spec \"%s\"\n",
+ m->mf_name, at);
+ if (parseonly)
+ syserr("X%s: Invalid numeric domain spec \"%s\"",
+ m->mf_name, at);
+ else if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "Milter (%s): Invalid numeric domain spec \"%s\"",
+ m->mf_name, at);
+ milter_error(m, e);
+ return -1;
+ }
+ }
+ else
+ {
+ if (tTd(64, 5))
+ sm_dprintf("X%s: Invalid numeric domain spec \"%s\"\n",
+ m->mf_name, at);
+ if (parseonly)
+ syserr("X%s: Invalid numeric domain spec \"%s\"",
+ m->mf_name, at);
+ else if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "Milter (%s): Invalid numeric domain spec \"%s\"",
+ m->mf_name, at);
+ milter_error(m, e);
+ return -1;
+ }
+ }
+ else
+ {
+ hp = sm_gethostbyname(at, addr.sa.sa_family);
+ if (hp == NULL)
+ {
+ save_errno = errno;
+ if (tTd(64, 5))
+ sm_dprintf("X%s: Unknown host name %s\n",
+ m->mf_name, at);
+ errno = save_errno;
+ if (parseonly)
+ syserr("X%s: Unknown host name %s",
+ m->mf_name, at);
+ else if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "Milter (%s): Unknown host name %s",
+ m->mf_name, at);
+ milter_error(m, e);
+ return -1;
+ }
+ addr.sa.sa_family = hp->h_addrtype;
+ switch (hp->h_addrtype)
+ {
+# if NETINET
+ case AF_INET:
+ memmove(&addr.sin.sin_addr,
+ hp->h_addr, INADDRSZ);
+ addr.sin.sin_port = port;
+ addrlen = sizeof (struct sockaddr_in);
+ addrno = 1;
+ break;
+# endif /* NETINET */
+
+# if NETINET6
+ case AF_INET6:
+ memmove(&addr.sin6.sin6_addr,
+ hp->h_addr, IN6ADDRSZ);
+ addr.sin6.sin6_port = port;
+ addrlen = sizeof (struct sockaddr_in6);
+ addrno = 1;
+ break;
+# endif /* NETINET6 */
+
+ default:
+ if (tTd(64, 5))
+ sm_dprintf("X%s: Unknown protocol for %s (%d)\n",
+ m->mf_name, at,
+ hp->h_addrtype);
+ if (parseonly)
+ syserr("X%s: Unknown protocol for %s (%d)",
+ m->mf_name, at, hp->h_addrtype);
+ else if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "Milter (%s): Unknown protocol for %s (%d)",
+ m->mf_name, at,
+ hp->h_addrtype);
+ milter_error(m, e);
+# if NETINET6
+ freehostent(hp);
+# endif /* NETINET6 */
+ return -1;
+ }
+ }
+ }
+ else
+# endif /* NETINET || NETINET6 */
+ {
+ if (tTd(64, 5))
+ sm_dprintf("X%s: unknown socket protocol\n",
+ m->mf_name);
+ if (parseonly)
+ syserr("X%s: unknown socket protocol", m->mf_name);
+ else if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "Milter (%s): unknown socket protocol",
+ m->mf_name);
+ milter_error(m, e);
+ return -1;
+ }
+
+ /* just parsing through? */
+ if (parseonly)
+ {
+ m->mf_state = SMFS_READY;
+# if NETINET6
+ if (hp != NULL)
+ freehostent(hp);
+# endif /* NETINET6 */
+ return 0;
+ }
+
+ /* sanity check */
+ if (m->mf_state != SMFS_READY &&
+ m->mf_state != SMFS_CLOSED)
+ {
+ /* shouldn't happen */
+ if (tTd(64, 1))
+ sm_dprintf("Milter (%s): Trying to open filter in state %c\n",
+ m->mf_name, (char) m->mf_state);
+ milter_error(m, e);
+# if NETINET6
+ if (hp != NULL)
+ freehostent(hp);
+# endif /* NETINET6 */
+ return -1;
+ }
+
+ /* nope, actually connecting */
+ for (;;)
+ {
+ sock = socket(addr.sa.sa_family, SOCK_STREAM, 0);
+ if (sock < 0)
+ {
+ save_errno = errno;
+ if (tTd(64, 5))
+ sm_dprintf("Milter (%s): error creating socket: %s\n",
+ m->mf_name,
+ sm_errstring(save_errno));
+ if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "Milter (%s): error creating socket: %s",
+ m->mf_name, sm_errstring(save_errno));
+ milter_error(m, e);
+# if NETINET6
+ if (hp != NULL)
+ freehostent(hp);
+# endif /* NETINET6 */
+ return -1;
+ }
+
+ if (setjmp(MilterConnectTimeout) == 0)
+ {
+ SM_EVENT *ev = NULL;
+ int i;
+
+ if (m->mf_timeout[SMFTO_CONNECT] > 0)
+ ev = sm_setevent(m->mf_timeout[SMFTO_CONNECT],
+ milter_connect_timeout, 0);
+
+ i = connect(sock, (struct sockaddr *) &addr, addrlen);
+ save_errno = errno;
+ if (ev != NULL)
+ sm_clrevent(ev);
+ errno = save_errno;
+ if (i >= 0)
+ break;
+ }
+
+ /* couldn't connect.... try next address */
+ save_errno = errno;
+ p = CurHostName;
+ CurHostName = at;
+ if (tTd(64, 5))
+ sm_dprintf("milter_open (%s): open %s failed: %s\n",
+ m->mf_name, at, sm_errstring(save_errno));
+ if (MilterLogLevel > 13)
+ sm_syslog(LOG_INFO, e->e_id,
+ "Milter (%s): open %s failed: %s",
+ m->mf_name, at, sm_errstring(save_errno));
+ CurHostName = p;
+ (void) close(sock);
+
+ /* try next address */
+ if (hp != NULL && hp->h_addr_list[addrno] != NULL)
+ {
+ switch (addr.sa.sa_family)
+ {
+# if NETINET
+ case AF_INET:
+ memmove(&addr.sin.sin_addr,
+ hp->h_addr_list[addrno++],
+ INADDRSZ);
+ break;
+# endif /* NETINET */
+
+# if NETINET6
+ case AF_INET6:
+ memmove(&addr.sin6.sin6_addr,
+ hp->h_addr_list[addrno++],
+ IN6ADDRSZ);
+ break;
+# endif /* NETINET6 */
+
+ default:
+ if (tTd(64, 5))
+ sm_dprintf("X%s: Unknown protocol for %s (%d)\n",
+ m->mf_name, at,
+ hp->h_addrtype);
+ if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "Milter (%s): Unknown protocol for %s (%d)",
+ m->mf_name, at,
+ hp->h_addrtype);
+ milter_error(m, e);
+# if NETINET6
+ freehostent(hp);
+# endif /* NETINET6 */
+ return -1;
+ }
+ continue;
+ }
+ p = CurHostName;
+ CurHostName = at;
+ if (tTd(64, 5))
+ sm_dprintf("X%s: error connecting to filter: %s\n",
+ m->mf_name, sm_errstring(save_errno));
+ if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "Milter (%s): error connecting to filter: %s",
+ m->mf_name, sm_errstring(save_errno));
+ CurHostName = p;
+ milter_error(m, e);
+# if NETINET6
+ if (hp != NULL)
+ freehostent(hp);
+# endif /* NETINET6 */
+ return -1;
+ }
+ m->mf_state = SMFS_OPEN;
+# if NETINET6
+ if (hp != NULL)
+ {
+ freehostent(hp);
+ hp = NULL;
+ }
+# endif /* NETINET6 */
+# if _FFR_MILTER_NAGLE
+# ifndef TCP_CORK
+ {
+ int nodelay = 1;
+
+ setsockopt(m->mf_sock, IPPROTO_TCP, TCP_NODELAY,
+ (char *)&nodelay, sizeof(nodelay));
+ }
+# endif /* TCP_CORK */
+# endif /* _FFR_MILTER_NAGLE */
+ return sock;
+}
+
+static void
+milter_connect_timeout(ignore)
+ int ignore;
+{
+ /*
+ ** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+ ** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+ ** DOING.
+ */
+
+ errno = ETIMEDOUT;
+ longjmp(MilterConnectTimeout, 1);
+}
+/*
+** MILTER_SETUP -- setup structure for a mail filter
+**
+** Parameters:
+** line -- the options line.
+**
+** Returns:
+** none
+*/
+
+void
+milter_setup(line)
+ char *line;
+{
+ char fcode;
+ register char *p;
+ register struct milter *m;
+ STAB *s;
+
+ /* collect the filter name */
+ for (p = line;
+ *p != '\0' && *p != ',' && !(isascii(*p) && isspace(*p));
+ p++)
+ continue;
+ if (*p != '\0')
+ *p++ = '\0';
+ if (line[0] == '\0')
+ {
+ syserr("name required for mail filter");
+ return;
+ }
+ m = (struct milter *) xalloc(sizeof *m);
+ memset((char *) m, '\0', sizeof *m);
+ m->mf_name = newstr(line);
+ m->mf_state = SMFS_READY;
+ m->mf_sock = -1;
+ m->mf_timeout[SMFTO_CONNECT] = (time_t) 300;
+ m->mf_timeout[SMFTO_WRITE] = (time_t) 10;
+ m->mf_timeout[SMFTO_READ] = (time_t) 10;
+ m->mf_timeout[SMFTO_EOM] = (time_t) 300;
+
+ /* now scan through and assign info from the fields */
+ while (*p != '\0')
+ {
+ char *delimptr;
+
+ while (*p != '\0' &&
+ (*p == ',' || (isascii(*p) && isspace(*p))))
+ p++;
+
+ /* p now points to field code */
+ fcode = *p;
+ while (*p != '\0' && *p != '=' && *p != ',')
+ p++;
+ if (*p++ != '=')
+ {
+ syserr("X%s: `=' expected", m->mf_name);
+ return;
+ }
+ while (isascii(*p) && isspace(*p))
+ p++;
+
+ /* p now points to the field body */
+ p = munchstring(p, &delimptr, ',');
+
+ /* install the field into the filter struct */
+ switch (fcode)
+ {
+ case 'S': /* socket */
+ if (p == NULL)
+ m->mf_conn = NULL;
+ else
+ m->mf_conn = newstr(p);
+ break;
+
+ case 'F': /* Milter flags configured on MTA */
+ for (; *p != '\0'; p++)
+ {
+ if (!(isascii(*p) && isspace(*p)))
+ setbitn(bitidx(*p), m->mf_flags);
+ }
+ break;
+
+ case 'T': /* timeouts */
+ milter_parse_timeouts(p, m);
+ break;
+
+ default:
+ syserr("X%s: unknown filter equate %c=",
+ m->mf_name, fcode);
+ break;
+ }
+ p = delimptr;
+ }
+
+ /* early check for errors */
+ (void) milter_open(m, true, CurEnv);
+
+ /* enter the filter into the symbol table */
+ s = stab(m->mf_name, ST_MILTER, ST_ENTER);
+ if (s->s_milter != NULL)
+ syserr("X%s: duplicate filter definition", m->mf_name);
+ else
+ s->s_milter = m;
+}
+/*
+** MILTER_CONFIG -- parse option list into an array and check config
+**
+** Called when reading configuration file.
+**
+** Parameters:
+** spec -- the filter list.
+** list -- the array to fill in.
+** max -- the maximum number of entries in list.
+**
+** Returns:
+** none
+*/
+
+void
+milter_config(spec, list, max)
+ char *spec;
+ struct milter **list;
+ int max;
+{
+ int numitems = 0;
+ register char *p;
+
+ /* leave one for the NULL signifying the end of the list */
+ max--;
+
+ for (p = spec; p != NULL; )
+ {
+ STAB *s;
+
+ while (isascii(*p) && isspace(*p))
+ p++;
+ if (*p == '\0')
+ break;
+ spec = p;
+
+ if (numitems >= max)
+ {
+ syserr("Too many filters defined, %d max", max);
+ if (max > 0)
+ list[0] = NULL;
+ return;
+ }
+ p = strpbrk(p, ";,");
+ if (p != NULL)
+ *p++ = '\0';
+
+ s = stab(spec, ST_MILTER, ST_FIND);
+ if (s == NULL)
+ {
+ syserr("InputFilter %s not defined", spec);
+ ExitStat = EX_CONFIG;
+ return;
+ }
+ list[numitems++] = s->s_milter;
+ }
+ list[numitems] = NULL;
+
+ /* if not set, set to LogLevel */
+ if (MilterLogLevel == -1)
+ MilterLogLevel = LogLevel;
+}
+/*
+** MILTER_PARSE_TIMEOUTS -- parse timeout list
+**
+** Called when reading configuration file.
+**
+** Parameters:
+** spec -- the timeout list.
+** m -- milter to set.
+**
+** Returns:
+** none
+*/
+
+static void
+milter_parse_timeouts(spec, m)
+ char *spec;
+ struct milter *m;
+{
+ char fcode;
+ int tcode;
+ register char *p;
+
+ p = spec;
+
+ /* now scan through and assign info from the fields */
+ while (*p != '\0')
+ {
+ char *delimptr;
+
+ while (*p != '\0' &&
+ (*p == ';' || (isascii(*p) && isspace(*p))))
+ p++;
+
+ /* p now points to field code */
+ fcode = *p;
+ while (*p != '\0' && *p != ':')
+ p++;
+ if (*p++ != ':')
+ {
+ syserr("X%s, T=: `:' expected", m->mf_name);
+ return;
+ }
+ while (isascii(*p) && isspace(*p))
+ p++;
+
+ /* p now points to the field body */
+ p = munchstring(p, &delimptr, ';');
+ tcode = -1;
+
+ /* install the field into the filter struct */
+ switch (fcode)
+ {
+ case 'C':
+ tcode = SMFTO_CONNECT;
+ break;
+
+ case 'S':
+ tcode = SMFTO_WRITE;
+ break;
+
+ case 'R':
+ tcode = SMFTO_READ;
+ break;
+
+ case 'E':
+ tcode = SMFTO_EOM;
+ break;
+
+ default:
+ if (tTd(64, 5))
+ sm_dprintf("X%s: %c unknown\n",
+ m->mf_name, fcode);
+ syserr("X%s: unknown filter timeout %c",
+ m->mf_name, fcode);
+ break;
+ }
+ if (tcode >= 0)
+ {
+ m->mf_timeout[tcode] = convtime(p, 's');
+ if (tTd(64, 5))
+ sm_dprintf("X%s: %c=%ld\n",
+ m->mf_name, fcode,
+ (u_long) m->mf_timeout[tcode]);
+ }
+ p = delimptr;
+ }
+}
+/*
+** MILTER_SET_OPTION -- set an individual milter option
+**
+** Parameters:
+** name -- the name of the option.
+** val -- the value of the option.
+** sticky -- if set, don't let other setoptions override
+** this value.
+**
+** Returns:
+** none.
+*/
+
+/* set if Milter sub-option is stuck */
+static BITMAP256 StickyMilterOpt;
+
+static struct milteropt
+{
+ char *mo_name; /* long name of milter option */
+ unsigned char mo_code; /* code for option */
+} MilterOptTab[] =
+{
+# define MO_MACROS_CONNECT 0x01
+ { "macros.connect", MO_MACROS_CONNECT },
+# define MO_MACROS_HELO 0x02
+ { "macros.helo", MO_MACROS_HELO },
+# define MO_MACROS_ENVFROM 0x03
+ { "macros.envfrom", MO_MACROS_ENVFROM },
+# define MO_MACROS_ENVRCPT 0x04
+ { "macros.envrcpt", MO_MACROS_ENVRCPT },
+# define MO_MACROS_DATA 0x05
+ { "macros.data", MO_MACROS_DATA },
+# define MO_MACROS_EOM 0x06
+ { "macros.eom", MO_MACROS_EOM },
+# define MO_LOGLEVEL 0x07
+ { "loglevel", MO_LOGLEVEL },
+# if _FFR_MAXDATASIZE
+# define MO_MAXDATASIZE 0x08
+ { "maxdatasize", MO_MAXDATASIZE },
+# endif /* _FFR_MAXDATASIZE */
+ { NULL, 0 },
+};
+
+void
+milter_set_option(name, val, sticky)
+ char *name;
+ char *val;
+ bool sticky;
+{
+ int nummac = 0;
+ register struct milteropt *mo;
+ char *p;
+ char **macros = NULL;
+
+ if (tTd(37, 2) || tTd(64, 5))
+ sm_dprintf("milter_set_option(%s = %s)", name, val);
+
+ if (name == NULL)
+ {
+ syserr("milter_set_option: invalid Milter option, must specify suboption");
+ return;
+ }
+
+ for (mo = MilterOptTab; mo->mo_name != NULL; mo++)
+ {
+ if (sm_strcasecmp(mo->mo_name, name) == 0)
+ break;
+ }
+
+ if (mo->mo_name == NULL)
+ {
+ syserr("milter_set_option: invalid Milter option %s", name);
+ return;
+ }
+
+ /*
+ ** See if this option is preset for us.
+ */
+
+ if (!sticky && bitnset(mo->mo_code, StickyMilterOpt))
+ {
+ if (tTd(37, 2) || tTd(64,5))
+ sm_dprintf(" (ignored)\n");
+ return;
+ }
+
+ if (tTd(37, 2) || tTd(64,5))
+ sm_dprintf("\n");
+
+ switch (mo->mo_code)
+ {
+ case MO_LOGLEVEL:
+ MilterLogLevel = atoi(val);
+ break;
+
+#if _FFR_MAXDATASIZE
+ case MO_MAXDATASIZE:
+ MilterMaxDataSize = (size_t)atol(val);
+ break;
+#endif /* _FFR_MAXDATASIZE */
+
+ case MO_MACROS_CONNECT:
+ if (macros == NULL)
+ macros = MilterConnectMacros;
+ /* FALLTHROUGH */
+
+ case MO_MACROS_HELO:
+ if (macros == NULL)
+ macros = MilterHeloMacros;
+ /* FALLTHROUGH */
+
+ case MO_MACROS_ENVFROM:
+ if (macros == NULL)
+ macros = MilterEnvFromMacros;
+ /* FALLTHROUGH */
+
+ case MO_MACROS_ENVRCPT:
+ if (macros == NULL)
+ macros = MilterEnvRcptMacros;
+ /* FALLTHROUGH */
+
+ case MO_MACROS_EOM:
+ if (macros == NULL)
+ macros = MilterEOMMacros;
+ /* FALLTHROUGH */
+
+ case MO_MACROS_DATA:
+ if (macros == NULL)
+ macros = MilterDataMacros;
+
+ p = newstr(val);
+ while (*p != '\0')
+ {
+ char *macro;
+
+ /* Skip leading commas, spaces */
+ while (*p != '\0' &&
+ (*p == ',' || (isascii(*p) && isspace(*p))))
+ p++;
+
+ if (*p == '\0')
+ break;
+
+ /* Find end of macro */
+ macro = p;
+ while (*p != '\0' && *p != ',' &&
+ isascii(*p) && !isspace(*p))
+ p++;
+ if (*p != '\0')
+ *p++ = '\0';
+
+ if (nummac >= MAXFILTERMACROS)
+ {
+ syserr("milter_set_option: too many macros in Milter.%s (max %d)",
+ name, MAXFILTERMACROS);
+ macros[nummac] = NULL;
+ break;
+ }
+ macros[nummac++] = macro;
+ }
+ macros[nummac] = NULL;
+ break;
+
+ default:
+ syserr("milter_set_option: invalid Milter option %s", name);
+ break;
+ }
+ if (sticky)
+ setbitn(mo->mo_code, StickyMilterOpt);
+}
+/*
+** MILTER_REOPEN_DF -- open & truncate the data file (for replbody)
+**
+** Parameters:
+** e -- current envelope.
+**
+** Returns:
+** 0 if succesful, -1 otherwise
+*/
+
+static int
+milter_reopen_df(e)
+ ENVELOPE *e;
+{
+ char dfname[MAXPATHLEN];
+
+ (void) sm_strlcpy(dfname, queuename(e, DATAFL_LETTER), sizeof dfname);
+
+ /*
+ ** In SuperSafe == SAFE_REALLY mode, e->e_dfp is a read-only FP so
+ ** close and reopen writable (later close and reopen
+ ** read only again).
+ **
+ ** In SuperSafe != SAFE_REALLY mode, e->e_dfp still points at the
+ ** buffered file I/O descriptor, still open for writing so there
+ ** isn't any work to do here (except checking for consistency).
+ */
+
+ if (SuperSafe == SAFE_REALLY)
+ {
+ /* close read-only data file */
+ if (bitset(EF_HAS_DF, e->e_flags) && e->e_dfp != NULL)
+ {
+ (void) sm_io_close(e->e_dfp, SM_TIME_DEFAULT);
+ e->e_flags &= ~EF_HAS_DF;
+ }
+
+ /* open writable */
+ if ((e->e_dfp = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, dfname,
+ SM_IO_RDWR_B, NULL)) == NULL)
+ {
+ MILTER_DF_ERROR("milter_reopen_df: sm_io_open %s: %s");
+ return -1;
+ }
+ }
+ else if (e->e_dfp == NULL)
+ {
+ /* shouldn't happen */
+ errno = ENOENT;
+ MILTER_DF_ERROR("milter_reopen_df: NULL e_dfp (%s: %s)");
+ return -1;
+ }
+ return 0;
+}
+/*
+** MILTER_RESET_DF -- re-open read-only the data file (for replbody)
+**
+** Parameters:
+** e -- current envelope.
+**
+** Returns:
+** 0 if succesful, -1 otherwise
+*/
+
+static int
+milter_reset_df(e)
+ ENVELOPE *e;
+{
+ int afd;
+ char dfname[MAXPATHLEN];
+
+ (void) sm_strlcpy(dfname, queuename(e, DATAFL_LETTER), sizeof dfname);
+
+ if (sm_io_flush(e->e_dfp, SM_TIME_DEFAULT) != 0 ||
+ sm_io_error(e->e_dfp))
+ {
+ MILTER_DF_ERROR("milter_reset_df: error writing/flushing %s: %s");
+ return -1;
+ }
+ else if (SuperSafe != SAFE_REALLY)
+ {
+ /* skip next few clauses */
+ /* EMPTY */
+ }
+ else if ((afd = sm_io_getinfo(e->e_dfp, SM_IO_WHAT_FD, NULL)) >= 0
+ && fsync(afd) < 0)
+ {
+ MILTER_DF_ERROR("milter_reset_df: error sync'ing %s: %s");
+ return -1;
+ }
+ else if (sm_io_close(e->e_dfp, SM_TIME_DEFAULT) < 0)
+ {
+ MILTER_DF_ERROR("milter_reset_df: error closing %s: %s");
+ return -1;
+ }
+ else if ((e->e_dfp = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, dfname,
+ SM_IO_RDONLY_B, NULL)) == NULL)
+ {
+ MILTER_DF_ERROR("milter_reset_df: error reopening %s: %s");
+ return -1;
+ }
+ else
+ e->e_flags |= EF_HAS_DF;
+ return 0;
+}
+/*
+** MILTER_CAN_DELRCPTS -- can any milter filters delete recipients?
+**
+** Parameters:
+** none
+**
+** Returns:
+** true if any filter deletes recipients, false otherwise
+*/
+
+bool
+milter_can_delrcpts()
+{
+ bool can = false;
+ int i;
+
+ if (tTd(64, 10))
+ sm_dprintf("milter_can_delrcpts:");
+
+ for (i = 0; InputFilters[i] != NULL; i++)
+ {
+ struct milter *m = InputFilters[i];
+
+ if (bitset(SMFIF_DELRCPT, m->mf_fflags))
+ {
+ can = true;
+ break;
+ }
+ }
+ if (tTd(64, 10))
+ sm_dprintf("%s\n", can ? "true" : "false");
+
+ return can;
+}
+/*
+** MILTER_QUIT_FILTER -- close down a single filter
+**
+** Parameters:
+** m -- milter structure of filter to close down.
+** e -- current envelope.
+**
+** Returns:
+** none
+*/
+
+static void
+milter_quit_filter(m, e)
+ struct milter *m;
+ ENVELOPE *e;
+{
+ if (tTd(64, 10))
+ sm_dprintf("milter_quit_filter(%s)\n", m->mf_name);
+ if (MilterLogLevel > 18)
+ sm_syslog(LOG_INFO, e->e_id, "Milter (%s): quit filter",
+ m->mf_name);
+
+ /* Never replace error state */
+ if (m->mf_state == SMFS_ERROR)
+ return;
+
+ if (m->mf_sock < 0 ||
+ m->mf_state == SMFS_CLOSED ||
+ m->mf_state == SMFS_READY)
+ {
+ m->mf_sock = -1;
+ m->mf_state = SMFS_CLOSED;
+ return;
+ }
+
+ (void) milter_write(m, SMFIC_QUIT, (char *) NULL, 0,
+ m->mf_timeout[SMFTO_WRITE], e);
+ if (m->mf_sock >= 0)
+ {
+ (void) close(m->mf_sock);
+ m->mf_sock = -1;
+ }
+ if (m->mf_state != SMFS_ERROR)
+ m->mf_state = SMFS_CLOSED;
+}
+/*
+** MILTER_ABORT_FILTER -- tell filter to abort current message
+**
+** Parameters:
+** m -- milter structure of filter to abort.
+** e -- current envelope.
+**
+** Returns:
+** none
+*/
+
+static void
+milter_abort_filter(m, e)
+ struct milter *m;
+ ENVELOPE *e;
+{
+ if (tTd(64, 10))
+ sm_dprintf("milter_abort_filter(%s)\n", m->mf_name);
+ if (MilterLogLevel > 10)
+ sm_syslog(LOG_INFO, e->e_id, "Milter (%s): abort filter",
+ m->mf_name);
+
+ if (m->mf_sock < 0 ||
+ m->mf_state != SMFS_INMSG)
+ return;
+
+ (void) milter_write(m, SMFIC_ABORT, (char *) NULL, 0,
+ m->mf_timeout[SMFTO_WRITE], e);
+ if (m->mf_state != SMFS_ERROR)
+ m->mf_state = SMFS_DONE;
+}
+/*
+** MILTER_SEND_MACROS -- provide macros to the filters
+**
+** Parameters:
+** m -- milter to send macros to.
+** macros -- macros to send for filter smfi_getsymval().
+** cmd -- which command the macros are associated with.
+** e -- current envelope (for macro access).
+**
+** Returns:
+** none
+*/
+
+static void
+milter_send_macros(m, macros, cmd, e)
+ struct milter *m;
+ char **macros;
+ char cmd;
+ ENVELOPE *e;
+{
+ int i;
+ int mid;
+ char *v;
+ char *buf, *bp;
+ char exp[MAXLINE];
+ ssize_t s;
+
+ /* sanity check */
+ if (macros == NULL || macros[0] == NULL)
+ return;
+
+ /* put together data */
+ s = 1; /* for the command character */
+ for (i = 0; macros[i] != NULL; i++)
+ {
+ mid = macid(macros[i]);
+ if (mid == 0)
+ continue;
+ v = macvalue(mid, e);
+ if (v == NULL)
+ continue;
+ expand(v, exp, sizeof(exp), e);
+ s += strlen(macros[i]) + 1 + strlen(exp) + 1;
+ }
+
+ if (s < 0)
+ return;
+
+ buf = (char *) xalloc(s);
+ bp = buf;
+ *bp++ = cmd;
+ for (i = 0; macros[i] != NULL; i++)
+ {
+ mid = macid(macros[i]);
+ if (mid == 0)
+ continue;
+ v = macvalue(mid, e);
+ if (v == NULL)
+ continue;
+ expand(v, exp, sizeof(exp), e);
+
+ if (tTd(64, 10))
+ sm_dprintf("milter_send_macros(%s, %c): %s=%s\n",
+ m->mf_name, cmd, macros[i], exp);
+
+ (void) sm_strlcpy(bp, macros[i], s - (bp - buf));
+ bp += strlen(bp) + 1;
+ (void) sm_strlcpy(bp, exp, s - (bp - buf));
+ bp += strlen(bp) + 1;
+ }
+ (void) milter_write(m, SMFIC_MACRO, buf, s,
+ m->mf_timeout[SMFTO_WRITE], e);
+ sm_free(buf);
+}
+
+/*
+** MILTER_SEND_COMMAND -- send a command and return the response for a filter
+**
+** Parameters:
+** m -- current milter filter
+** command -- command to send.
+** data -- optional command data.
+** sz -- length of buf.
+** e -- current envelope (for e->e_id).
+** state -- return state word.
+**
+** Returns:
+** response string (may be NULL)
+*/
+
+static char *
+milter_send_command(m, command, data, sz, e, state)
+ struct milter *m;
+ char command;
+ void *data;
+ ssize_t sz;
+ ENVELOPE *e;
+ char *state;
+{
+ char rcmd;
+ ssize_t rlen;
+ unsigned long skipflag;
+#if _FFR_MILTER_NOHDR_RESP
+ unsigned long norespflag = 0;
+#endif /* _FFR_MILTER_NOHDR_RESP */
+ char *action;
+ char *defresponse;
+ char *response;
+
+ if (tTd(64, 10))
+ sm_dprintf("milter_send_command(%s): cmd %c len %ld\n",
+ m->mf_name, (char) command, (long) sz);
+
+ /* find skip flag and default failure */
+ switch (command)
+ {
+ case SMFIC_CONNECT:
+ skipflag = SMFIP_NOCONNECT;
+ action = "connect";
+ defresponse = "554 Command rejected";
+ break;
+
+ case SMFIC_HELO:
+ skipflag = SMFIP_NOHELO;
+ action = "helo";
+ defresponse = "550 Command rejected";
+ break;
+
+ case SMFIC_MAIL:
+ skipflag = SMFIP_NOMAIL;
+ action = "mail";
+ defresponse = "550 5.7.1 Command rejected";
+ break;
+
+ case SMFIC_RCPT:
+ skipflag = SMFIP_NORCPT;
+ action = "rcpt";
+ defresponse = "550 5.7.1 Command rejected";
+ break;
+
+ case SMFIC_HEADER:
+ skipflag = SMFIP_NOHDRS;
+#if _FFR_MILTER_NOHDR_RESP
+ norespflag = SMFIP_NOHREPL;
+#endif /* _FFR_MILTER_NOHDR_RESP */
+ action = "header";
+ defresponse = "550 5.7.1 Command rejected";
+ break;
+
+ case SMFIC_BODY:
+ skipflag = SMFIP_NOBODY;
+ action = "body";
+ defresponse = "554 5.7.1 Command rejected";
+ break;
+
+ case SMFIC_EOH:
+ skipflag = SMFIP_NOEOH;
+ action = "eoh";
+ defresponse = "550 5.7.1 Command rejected";
+ break;
+
+#if SMFI_VERSION > 2
+ case SMFIC_UNKNOWN:
+ action = "unknown";
+ defresponse = "550 5.7.1 Command rejected";
+ break;
+#endif /* SMFI_VERSION > 2 */
+
+ case SMFIC_BODYEOB:
+ case SMFIC_OPTNEG:
+ case SMFIC_MACRO:
+ case SMFIC_ABORT:
+ case SMFIC_QUIT:
+ /* NOTE: not handled by milter_send_command() */
+ /* FALLTHROUGH */
+
+ default:
+ skipflag = 0;
+ action = "default";
+ defresponse = "550 5.7.1 Command rejected";
+ break;
+ }
+
+ /* check if filter wants this command */
+ if (skipflag != 0 &&
+ bitset(skipflag, m->mf_pflags))
+ return NULL;
+
+ /* send the command to the filter */
+ (void) milter_write(m, command, data, sz,
+ m->mf_timeout[SMFTO_WRITE], e);
+ if (m->mf_state == SMFS_ERROR)
+ {
+ MILTER_CHECK_ERROR(false, return NULL);
+ return NULL;
+ }
+
+#if _FFR_MILTER_NOHDR_RESP
+ /* check if filter sends response to this command */
+ if (norespflag != 0 && bitset(norespflag, m->mf_pflags))
+ return NULL;
+#endif /* _FFR_MILTER_NOHDR_RESP */
+
+ /* get the response from the filter */
+ response = milter_read(m, &rcmd, &rlen,
+ m->mf_timeout[SMFTO_READ], e);
+ if (m->mf_state == SMFS_ERROR)
+ {
+ MILTER_CHECK_ERROR(false, return NULL);
+ return NULL;
+ }
+
+ if (tTd(64, 10))
+ sm_dprintf("milter_send_command(%s): returned %c\n",
+ m->mf_name, (char) rcmd);
+
+ switch (rcmd)
+ {
+ case SMFIR_REPLYCODE:
+ MILTER_CHECK_REPLYCODE(defresponse);
+ if (MilterLogLevel > 10)
+ sm_syslog(LOG_INFO, e->e_id, "milter=%s, action=%s, reject=%s",
+ m->mf_name, action, response);
+ *state = rcmd;
+ break;
+
+ case SMFIR_REJECT:
+ if (MilterLogLevel > 10)
+ sm_syslog(LOG_INFO, e->e_id, "milter=%s, action=%s, reject",
+ m->mf_name, action);
+ *state = rcmd;
+ break;
+
+ case SMFIR_DISCARD:
+ if (MilterLogLevel > 10)
+ sm_syslog(LOG_INFO, e->e_id, "milter=%s, action=%s, discard",
+ m->mf_name, action);
+ *state = rcmd;
+ break;
+
+ case SMFIR_TEMPFAIL:
+ if (MilterLogLevel > 10)
+ sm_syslog(LOG_INFO, e->e_id, "milter=%s, action=%s, tempfail",
+ m->mf_name, action);
+ *state = rcmd;
+ break;
+
+ case SMFIR_ACCEPT:
+ /* this filter is done with message/connection */
+ if (command == SMFIC_HELO ||
+ command == SMFIC_CONNECT)
+ m->mf_state = SMFS_CLOSABLE;
+ else
+ m->mf_state = SMFS_DONE;
+ if (MilterLogLevel > 10)
+ sm_syslog(LOG_INFO, e->e_id, "milter=%s, action=%s, accepted",
+ m->mf_name, action);
+ break;
+
+ case SMFIR_CONTINUE:
+ /* if MAIL command is ok, filter is in message state */
+ if (command == SMFIC_MAIL)
+ m->mf_state = SMFS_INMSG;
+ if (MilterLogLevel > 12)
+ sm_syslog(LOG_INFO, e->e_id, "milter=%s, action=%s, continue",
+ m->mf_name, action);
+ break;
+
+ default:
+ /* Invalid response to command */
+ if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "milter_send_command(%s): action=%s returned bogus response %c",
+ m->mf_name, action, rcmd);
+ milter_error(m, e);
+ break;
+ }
+
+ if (*state != SMFIR_REPLYCODE &&
+ response != NULL)
+ {
+ sm_free(response); /* XXX */
+ response = NULL;
+ }
+ return response;
+}
+
+/*
+** MILTER_COMMAND -- send a command and return the response for each filter
+**
+** Parameters:
+** command -- command to send.
+** data -- optional command data.
+** sz -- length of buf.
+** macros -- macros to send for filter smfi_getsymval().
+** e -- current envelope (for macro access).
+** state -- return state word.
+**
+** Returns:
+** response string (may be NULL)
+*/
+
+static char *
+milter_command(command, data, sz, macros, e, state)
+ char command;
+ void *data;
+ ssize_t sz;
+ char **macros;
+ ENVELOPE *e;
+ char *state;
+{
+ int i;
+ char *response = NULL;
+ time_t tn = 0;
+
+ if (tTd(64, 10))
+ sm_dprintf("milter_command: cmd %c len %ld\n",
+ (char) command, (long) sz);
+
+ *state = SMFIR_CONTINUE;
+ for (i = 0; InputFilters[i] != NULL; i++)
+ {
+ struct milter *m = InputFilters[i];
+
+ /* previous problem? */
+ if (m->mf_state == SMFS_ERROR)
+ {
+ MILTER_CHECK_ERROR(false, continue);
+ break;
+ }
+
+ /* sanity check */
+ if (m->mf_sock < 0 ||
+ (m->mf_state != SMFS_OPEN && m->mf_state != SMFS_INMSG))
+ continue;
+
+ /* send macros (regardless of whether we send command) */
+ if (macros != NULL && macros[0] != NULL)
+ {
+ milter_send_macros(m, macros, command, e);
+ if (m->mf_state == SMFS_ERROR)
+ {
+ MILTER_CHECK_ERROR(false, continue);
+ break;
+ }
+ }
+
+ if (MilterLogLevel > 21)
+ tn = curtime();
+
+ response = milter_send_command(m, command, data, sz, e, state);
+
+ if (MilterLogLevel > 21)
+ {
+ /* log the time it took for the command per filter */
+ sm_syslog(LOG_INFO, e->e_id,
+ "Milter (%s): time command (%c), %d",
+ m->mf_name, command, (int) (tn - curtime()));
+ }
+
+ if (*state != SMFIR_CONTINUE)
+ break;
+ }
+ return response;
+}
+/*
+** MILTER_NEGOTIATE -- get version and flags from filter
+**
+** Parameters:
+** m -- milter filter structure.
+** e -- current envelope.
+**
+** Returns:
+** 0 on success, -1 otherwise
+*/
+
+static int
+milter_negotiate(m, e)
+ struct milter *m;
+ ENVELOPE *e;
+{
+ char rcmd;
+ mi_int32 fvers;
+ mi_int32 fflags;
+ mi_int32 pflags;
+ char *response;
+ ssize_t rlen;
+ char data[MILTER_OPTLEN];
+
+ /* sanity check */
+ if (m->mf_sock < 0 || m->mf_state != SMFS_OPEN)
+ {
+ if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "Milter (%s): negotiate, impossible state",
+ m->mf_name);
+ milter_error(m, e);
+ return -1;
+ }
+
+ fvers = htonl(SMFI_VERSION);
+ fflags = htonl(SMFI_CURR_ACTS);
+ pflags = htonl(SMFI_CURR_PROT);
+ (void) memcpy(data, (char *) &fvers, MILTER_LEN_BYTES);
+ (void) memcpy(data + MILTER_LEN_BYTES,
+ (char *) &fflags, MILTER_LEN_BYTES);
+ (void) memcpy(data + (MILTER_LEN_BYTES * 2),
+ (char *) &pflags, MILTER_LEN_BYTES);
+ (void) milter_write(m, SMFIC_OPTNEG, data, sizeof data,
+ m->mf_timeout[SMFTO_WRITE], e);
+
+ if (m->mf_state == SMFS_ERROR)
+ return -1;
+
+ response = milter_read(m, &rcmd, &rlen, m->mf_timeout[SMFTO_READ], e);
+ if (m->mf_state == SMFS_ERROR)
+ return -1;
+
+ if (rcmd != SMFIC_OPTNEG)
+ {
+ if (tTd(64, 5))
+ sm_dprintf("milter_negotiate(%s): returned %c instead of %c\n",
+ m->mf_name, rcmd, SMFIC_OPTNEG);
+ if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "Milter (%s): negotiate: returned %c instead of %c",
+ m->mf_name, rcmd, SMFIC_OPTNEG);
+ if (response != NULL)
+ sm_free(response); /* XXX */
+ milter_error(m, e);
+ return -1;
+ }
+
+ /* Make sure we have enough bytes for the version */
+ if (response == NULL || rlen < MILTER_LEN_BYTES)
+ {
+ if (tTd(64, 5))
+ sm_dprintf("milter_negotiate(%s): did not return valid info\n",
+ m->mf_name);
+ if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "Milter (%s): negotiate: did not return valid info",
+ m->mf_name);
+ if (response != NULL)
+ sm_free(response); /* XXX */
+ milter_error(m, e);
+ return -1;
+ }
+
+ /* extract information */
+ (void) memcpy((char *) &fvers, response, MILTER_LEN_BYTES);
+
+ /* Now make sure we have enough for the feature bitmap */
+ if (rlen != MILTER_OPTLEN)
+ {
+ if (tTd(64, 5))
+ sm_dprintf("milter_negotiate(%s): did not return enough info\n",
+ m->mf_name);
+ if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "Milter (%s): negotiate: did not return enough info",
+ m->mf_name);
+ if (response != NULL)
+ sm_free(response); /* XXX */
+ milter_error(m, e);
+ return -1;
+ }
+
+ (void) memcpy((char *) &fflags, response + MILTER_LEN_BYTES,
+ MILTER_LEN_BYTES);
+ (void) memcpy((char *) &pflags, response + (MILTER_LEN_BYTES * 2),
+ MILTER_LEN_BYTES);
+ sm_free(response); /* XXX */
+ response = NULL;
+
+ m->mf_fvers = ntohl(fvers);
+ m->mf_fflags = ntohl(fflags);
+ m->mf_pflags = ntohl(pflags);
+
+ /* check for version compatibility */
+ if (m->mf_fvers == 1 ||
+ m->mf_fvers > SMFI_VERSION)
+ {
+ if (tTd(64, 5))
+ sm_dprintf("milter_negotiate(%s): version %d != MTA milter version %d\n",
+ m->mf_name, m->mf_fvers, SMFI_VERSION);
+ if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "Milter (%s): negotiate: version %d != MTA milter version %d",
+ m->mf_name, m->mf_fvers, SMFI_VERSION);
+ milter_error(m, e);
+ return -1;
+ }
+
+ /* check for filter feature mismatch */
+ if ((m->mf_fflags & SMFI_CURR_ACTS) != m->mf_fflags)
+ {
+ if (tTd(64, 5))
+ sm_dprintf("milter_negotiate(%s): filter abilities 0x%x != MTA milter abilities 0x%lx\n",
+ m->mf_name, m->mf_fflags,
+ SMFI_CURR_ACTS);
+ if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "Milter (%s): negotiate: filter abilities 0x%x != MTA milter abilities 0x%lx",
+ m->mf_name, m->mf_fflags,
+ (unsigned long) SMFI_CURR_ACTS);
+ milter_error(m, e);
+ return -1;
+ }
+
+ /* check for protocol feature mismatch */
+ if ((m->mf_pflags & SMFI_CURR_PROT) != m->mf_pflags)
+ {
+ if (tTd(64, 5))
+ sm_dprintf("milter_negotiate(%s): protocol abilities 0x%x != MTA milter abilities 0x%lx\n",
+ m->mf_name, m->mf_pflags,
+ (unsigned long) SMFI_CURR_PROT);
+ if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "Milter (%s): negotiate: protocol abilities 0x%x != MTA milter abilities 0x%lx",
+ m->mf_name, m->mf_pflags,
+ (unsigned long) SMFI_CURR_PROT);
+ milter_error(m, e);
+ return -1;
+ }
+
+ if (tTd(64, 5))
+ sm_dprintf("milter_negotiate(%s): version %u, fflags 0x%x, pflags 0x%x\n",
+ m->mf_name, m->mf_fvers, m->mf_fflags, m->mf_pflags);
+ return 0;
+}
+/*
+** MILTER_PER_CONNECTION_CHECK -- checks on per-connection commands
+**
+** Reduce code duplication by putting these checks in one place
+**
+** Parameters:
+** e -- current envelope.
+**
+** Returns:
+** none
+*/
+
+static void
+milter_per_connection_check(e)
+ ENVELOPE *e;
+{
+ int i;
+
+ /* see if we are done with any of the filters */
+ for (i = 0; InputFilters[i] != NULL; i++)
+ {
+ struct milter *m = InputFilters[i];
+
+ if (m->mf_state == SMFS_CLOSABLE)
+ milter_quit_filter(m, e);
+ }
+}
+/*
+** MILTER_ERROR -- Put a milter filter into error state
+**
+** Parameters:
+** m -- the broken filter.
+** e -- current envelope.
+**
+** Returns:
+** none
+*/
+
+static void
+milter_error(m, e)
+ struct milter *m;
+ ENVELOPE *e;
+{
+ /*
+ ** We could send a quit here but we may have gotten here due to
+ ** an I/O error so we don't want to try to make things worse.
+ */
+
+ if (m->mf_sock >= 0)
+ {
+ (void) close(m->mf_sock);
+ m->mf_sock = -1;
+ }
+ m->mf_state = SMFS_ERROR;
+
+ if (MilterLogLevel > 0)
+ sm_syslog(LOG_INFO, e->e_id, "Milter (%s): to error state",
+ m->mf_name);
+}
+/*
+** MILTER_HEADERS -- send headers to a single milter filter
+**
+** Parameters:
+** m -- current filter.
+** e -- current envelope.
+** state -- return state from response.
+**
+** Returns:
+** response string (may be NULL)
+*/
+
+static char *
+milter_headers(m, e, state)
+ struct milter *m;
+ ENVELOPE *e;
+ char *state;
+{
+ char *response = NULL;
+ HDR *h;
+
+ if (MilterLogLevel > 17)
+ sm_syslog(LOG_INFO, e->e_id, "Milter (%s): headers, send",
+ m->mf_name);
+
+ for (h = e->e_header; h != NULL; h = h->h_link)
+ {
+ char *buf;
+ ssize_t s;
+
+ /* don't send over deleted headers */
+ if (h->h_value == NULL)
+ {
+ /* strip H_USER so not counted in milter_changeheader() */
+ h->h_flags &= ~H_USER;
+ continue;
+ }
+
+ /* skip auto-generated */
+ if (!bitset(H_USER, h->h_flags))
+ continue;
+
+ if (tTd(64, 10))
+ sm_dprintf("milter_headers: %s: %s\n",
+ h->h_field, h->h_value);
+ if (MilterLogLevel > 21)
+ sm_syslog(LOG_INFO, e->e_id, "Milter (%s): header, %s",
+ m->mf_name, h->h_field);
+
+ s = strlen(h->h_field) + 1 + strlen(h->h_value) + 1;
+ if (s < 0)
+ continue;
+ buf = (char *) xalloc(s);
+ (void) sm_snprintf(buf, s, "%s%c%s",
+ h->h_field, '\0', h->h_value);
+
+ /* send it over */
+ response = milter_send_command(m, SMFIC_HEADER, buf,
+ s, e, state);
+ sm_free(buf); /* XXX */
+ if (m->mf_state == SMFS_ERROR ||
+ m->mf_state == SMFS_DONE ||
+ *state != SMFIR_CONTINUE)
+ break;
+ }
+ if (MilterLogLevel > 17)
+ sm_syslog(LOG_INFO, e->e_id, "Milter (%s): headers, sent",
+ m->mf_name);
+ return response;
+}
+/*
+** MILTER_BODY -- send the body to a filter
+**
+** Parameters:
+** m -- current filter.
+** e -- current envelope.
+** state -- return state from response.
+**
+** Returns:
+** response string (may be NULL)
+*/
+
+static char *
+milter_body(m, e, state)
+ struct milter *m;
+ ENVELOPE *e;
+ char *state;
+{
+ char bufchar = '\0';
+ char prevchar = '\0';
+ int c;
+ char *response = NULL;
+ char *bp;
+ char buf[MILTER_CHUNK_SIZE];
+
+ if (tTd(64, 10))
+ sm_dprintf("milter_body\n");
+
+ if (bfrewind(e->e_dfp) < 0)
+ {
+ ExitStat = EX_IOERR;
+ *state = SMFIR_TEMPFAIL;
+ syserr("milter_body: %s/%cf%s: rewind error",
+ qid_printqueue(e->e_qgrp, e->e_qdir),
+ DATAFL_LETTER, e->e_id);
+ return NULL;
+ }
+
+ if (MilterLogLevel > 17)
+ sm_syslog(LOG_INFO, e->e_id, "Milter (%s): body, send",
+ m->mf_name);
+ bp = buf;
+ while ((c = sm_io_getc(e->e_dfp, SM_TIME_DEFAULT)) != SM_IO_EOF)
+ {
+ /* Change LF to CRLF */
+ if (c == '\n')
+ {
+ /* Not a CRLF already? */
+ if (prevchar != '\r')
+ {
+ /* Room for CR now? */
+ if (bp + 2 > &buf[sizeof buf])
+ {
+ /* No room, buffer LF */
+ bufchar = c;
+
+ /* and send CR now */
+ c = '\r';
+ }
+ else
+ {
+ /* Room to do it now */
+ *bp++ = '\r';
+ prevchar = '\r';
+ }
+ }
+ }
+ *bp++ = (char) c;
+ prevchar = c;
+ if (bp >= &buf[sizeof buf])
+ {
+ /* send chunk */
+ response = milter_send_command(m, SMFIC_BODY, buf,
+ bp - buf, e, state);
+ bp = buf;
+ if (bufchar != '\0')
+ {
+ *bp++ = bufchar;
+ bufchar = '\0';
+ prevchar = bufchar;
+ }
+ }
+ if (m->mf_state == SMFS_ERROR ||
+ m->mf_state == SMFS_DONE ||
+ *state != SMFIR_CONTINUE)
+ break;
+ }
+
+ /* check for read errors */
+ if (sm_io_error(e->e_dfp))
+ {
+ ExitStat = EX_IOERR;
+ if (*state == SMFIR_CONTINUE ||
+ *state == SMFIR_ACCEPT)
+ {
+ *state = SMFIR_TEMPFAIL;
+ if (response != NULL)
+ {
+ sm_free(response); /* XXX */
+ response = NULL;
+ }
+ }
+ syserr("milter_body: %s/%cf%s: read error",
+ qid_printqueue(e->e_qgrp, e->e_qdir),
+ DATAFL_LETTER, e->e_id);
+ return response;
+ }
+
+ /* send last body chunk */
+ if (bp > buf &&
+ m->mf_state != SMFS_ERROR &&
+ m->mf_state != SMFS_DONE &&
+ *state == SMFIR_CONTINUE)
+ {
+ /* send chunk */
+ response = milter_send_command(m, SMFIC_BODY, buf, bp - buf,
+ e, state);
+ bp = buf;
+ }
+ if (MilterLogLevel > 17)
+ sm_syslog(LOG_INFO, e->e_id, "Milter (%s): body, sent",
+ m->mf_name);
+ return response;
+}
+
+/*
+** Actions
+*/
+
+/*
+** MILTER_ADDHEADER -- Add the supplied header to the message
+**
+** Parameters:
+** response -- encoded form of header/value.
+** rlen -- length of response.
+** e -- current envelope.
+**
+** Returns:
+** none
+*/
+
+static void
+milter_addheader(response, rlen, e)
+ char *response;
+ ssize_t rlen;
+ ENVELOPE *e;
+{
+ char *val;
+ HDR *h;
+
+ if (tTd(64, 10))
+ sm_dprintf("milter_addheader: ");
+
+ /* sanity checks */
+ if (response == NULL)
+ {
+ if (tTd(64, 10))
+ sm_dprintf("NULL response\n");
+ return;
+ }
+
+ if (rlen < 2 || strlen(response) + 1 >= (size_t) rlen)
+ {
+ if (tTd(64, 10))
+ sm_dprintf("didn't follow protocol (total len)\n");
+ return;
+ }
+
+ /* Find separating NUL */
+ val = response + strlen(response) + 1;
+
+ /* another sanity check */
+ if (strlen(response) + strlen(val) + 2 != (size_t) rlen)
+ {
+ if (tTd(64, 10))
+ sm_dprintf("didn't follow protocol (part len)\n");
+ return;
+ }
+
+ if (*response == '\0')
+ {
+ if (tTd(64, 10))
+ sm_dprintf("empty field name\n");
+ return;
+ }
+
+ for (h = e->e_header; h != NULL; h = h->h_link)
+ {
+ if (sm_strcasecmp(h->h_field, response) == 0 &&
+ !bitset(H_USER, h->h_flags) &&
+ !bitset(H_TRACE, h->h_flags))
+ break;
+ }
+
+ /* add to e_msgsize */
+ e->e_msgsize += strlen(response) + 2 + strlen(val);
+
+ if (h != NULL)
+ {
+ if (tTd(64, 10))
+ sm_dprintf("Replace default header %s value with %s\n",
+ h->h_field, val);
+ if (MilterLogLevel > 8)
+ sm_syslog(LOG_INFO, e->e_id,
+ "Milter change: default header %s value with %s",
+ h->h_field, val);
+ h->h_value = newstr(val);
+ h->h_flags |= H_USER;
+ }
+ else
+ {
+ if (tTd(64, 10))
+ sm_dprintf("Add %s: %s\n", response, val);
+ if (MilterLogLevel > 8)
+ sm_syslog(LOG_INFO, e->e_id, "Milter add: header: %s: %s",
+ response, val);
+ addheader(newstr(response), val, H_USER, e);
+ }
+}
+/*
+** MILTER_INSHEADER -- Insert the supplied header
+**
+** Parameters:
+** response -- encoded form of header/value.
+** rlen -- length of response.
+** e -- current envelope.
+**
+** Returns:
+** none
+**
+** Notes:
+** Unlike milter_addheader(), this does not attempt to determine
+** if the header already exists in the envelope, even a
+** deleted version. It just blindly inserts.
+*/
+
+static void
+milter_insheader(response, rlen, e)
+ char *response;
+ ssize_t rlen;
+ ENVELOPE *e;
+{
+ mi_int32 idx, i;
+ char *field;
+ char *val;
+
+ if (tTd(64, 10))
+ sm_dprintf("milter_insheader: ");
+
+ /* sanity checks */
+ if (response == NULL)
+ {
+ if (tTd(64, 10))
+ sm_dprintf("NULL response\n");
+ return;
+ }
+
+ if (rlen < 2 || strlen(response) + 1 >= (size_t) rlen)
+ {
+ if (tTd(64, 10))
+ sm_dprintf("didn't follow protocol (total len)\n");
+ return;
+ }
+
+ /* decode */
+ (void) memcpy((char *) &i, response, MILTER_LEN_BYTES);
+ idx = ntohl(i);
+ field = response + MILTER_LEN_BYTES;
+ val = field + strlen(field) + 1;
+
+ /* another sanity check */
+ if (MILTER_LEN_BYTES + strlen(field) + 1 +
+ strlen(val) + 1 != (size_t) rlen)
+ {
+ if (tTd(64, 10))
+ sm_dprintf("didn't follow protocol (part len)\n");
+ return;
+ }
+
+ if (*field == '\0')
+ {
+ if (tTd(64, 10))
+ sm_dprintf("empty field name\n");
+ return;
+ }
+
+ /* add to e_msgsize */
+ e->e_msgsize += strlen(response) + 2 + strlen(val);
+
+ if (tTd(64, 10))
+ sm_dprintf("Insert (%d) %s: %s\n", idx, response, val);
+ if (MilterLogLevel > 8)
+ sm_syslog(LOG_INFO, e->e_id,
+ "Milter insert (%d): header: %s: %s",
+ idx, field, val);
+ insheader(idx, newstr(field), val, H_USER, e);
+}
+/*
+** MILTER_CHANGEHEADER -- Change the supplied header in the message
+**
+** Parameters:
+** response -- encoded form of header/index/value.
+** rlen -- length of response.
+** e -- current envelope.
+**
+** Returns:
+** none
+*/
+
+static void
+milter_changeheader(response, rlen, e)
+ char *response;
+ ssize_t rlen;
+ ENVELOPE *e;
+{
+ mi_int32 i, index;
+ char *field, *val;
+ HDR *h, *sysheader;
+
+ if (tTd(64, 10))
+ sm_dprintf("milter_changeheader: ");
+
+ /* sanity checks */
+ if (response == NULL)
+ {
+ if (tTd(64, 10))
+ sm_dprintf("NULL response\n");
+ return;
+ }
+
+ if (rlen < 2 || strlen(response) + 1 >= (size_t) rlen)
+ {
+ if (tTd(64, 10))
+ sm_dprintf("didn't follow protocol (total len)\n");
+ return;
+ }
+
+ /* Find separating NUL */
+ (void) memcpy((char *) &i, response, MILTER_LEN_BYTES);
+ index = ntohl(i);
+ field = response + MILTER_LEN_BYTES;
+ val = field + strlen(field) + 1;
+
+ /* another sanity check */
+ if (MILTER_LEN_BYTES + strlen(field) + 1 +
+ strlen(val) + 1 != (size_t) rlen)
+ {
+ if (tTd(64, 10))
+ sm_dprintf("didn't follow protocol (part len)\n");
+ return;
+ }
+
+ if (*field == '\0')
+ {
+ if (tTd(64, 10))
+ sm_dprintf("empty field name\n");
+ return;
+ }
+
+ sysheader = NULL;
+ for (h = e->e_header; h != NULL; h = h->h_link)
+ {
+ if (sm_strcasecmp(h->h_field, field) == 0)
+ {
+ if (bitset(H_USER, h->h_flags) &&
+ --index <= 0)
+ {
+ sysheader = NULL;
+ break;
+ }
+ else if (!bitset(H_USER, h->h_flags) &&
+ !bitset(H_TRACE, h->h_flags))
+ {
+ /*
+ ** DRUMS msg-fmt draft says can only have
+ ** multiple occurences of trace fields,
+ ** so make sure we replace any non-trace,
+ ** non-user field.
+ */
+
+ sysheader = h;
+ }
+ }
+ }
+
+ /* if not found as user-provided header at index, use sysheader */
+ if (h == NULL)
+ h = sysheader;
+
+ if (h == NULL)
+ {
+ if (*val == '\0')
+ {
+ if (tTd(64, 10))
+ sm_dprintf("Delete (noop) %s\n", field);
+ if (MilterLogLevel > 8)
+ sm_syslog(LOG_INFO, e->e_id,
+ "Milter delete (noop): header: %s"
+ , field);
+ }
+ else
+ {
+ /* treat modify value with no existing header as add */
+ if (tTd(64, 10))
+ sm_dprintf("Add %s: %s\n", field, val);
+ if (MilterLogLevel > 8)
+ sm_syslog(LOG_INFO, e->e_id,
+ "Milter change (add): header: %s: %s"
+ , field, val);
+ addheader(newstr(field), val, H_USER, e);
+ }
+ return;
+ }
+
+ if (tTd(64, 10))
+ {
+ if (*val == '\0')
+ {
+ sm_dprintf("Delete%s %s: %s\n",
+ h == sysheader ? " (default header)" : "",
+ field,
+ h->h_value == NULL ? "<NULL>" : h->h_value);
+ }
+ else
+ {
+ sm_dprintf("Change%s %s: from %s to %s\n",
+ h == sysheader ? " (default header)" : "",
+ field,
+ h->h_value == NULL ? "<NULL>" : h->h_value,
+ val);
+ }
+ }
+
+ if (MilterLogLevel > 8)
+ {
+ if (*val == '\0')
+ {
+ sm_syslog(LOG_INFO, e->e_id,
+ "Milter delete: header%s %s: %s",
+ h == sysheader ? " (default header)" : "",
+ field,
+ h->h_value == NULL ? "<NULL>" : h->h_value);
+ }
+ else
+ {
+ sm_syslog(LOG_INFO, e->e_id,
+ "Milter change: header%s %s: from %s to %s",
+ h == sysheader ? " (default header)" : "",
+ field,
+ h->h_value == NULL ? "<NULL>" : h->h_value,
+ val);
+ }
+ }
+
+ if (h != sysheader && h->h_value != NULL)
+ {
+ size_t l;
+
+ l = strlen(h->h_value);
+ if (l > e->e_msgsize)
+ e->e_msgsize = 0;
+ else
+ e->e_msgsize -= l;
+ /* rpool, don't free: sm_free(h->h_value); XXX */
+ }
+
+ if (*val == '\0')
+ {
+ /* Remove "Field: " from message size */
+ if (h != sysheader)
+ {
+ size_t l;
+
+ l = strlen(h->h_field) + 2;
+ if (l > e->e_msgsize)
+ e->e_msgsize = 0;
+ else
+ e->e_msgsize -= l;
+ }
+ h->h_value = NULL;
+ }
+ else
+ {
+ h->h_value = newstr(val);
+ h->h_flags |= H_USER;
+ e->e_msgsize += strlen(h->h_value);
+ }
+}
+/*
+** MILTER_ADDRCPT -- Add the supplied recipient to the message
+**
+** Parameters:
+** response -- encoded form of recipient address.
+** rlen -- length of response.
+** e -- current envelope.
+**
+** Returns:
+** none
+*/
+
+static void
+milter_addrcpt(response, rlen, e)
+ char *response;
+ ssize_t rlen;
+ ENVELOPE *e;
+{
+ int olderrors;
+
+ if (tTd(64, 10))
+ sm_dprintf("milter_addrcpt: ");
+
+ /* sanity checks */
+ if (response == NULL)
+ {
+ if (tTd(64, 10))
+ sm_dprintf("NULL response\n");
+ return;
+ }
+
+ if (*response == '\0' ||
+ strlen(response) + 1 != (size_t) rlen)
+ {
+ if (tTd(64, 10))
+ sm_dprintf("didn't follow protocol (total len %d != rlen %d)\n",
+ (int) strlen(response), (int) (rlen - 1));
+ return;
+ }
+
+ if (tTd(64, 10))
+ sm_dprintf("%s\n", response);
+ if (MilterLogLevel > 8)
+ sm_syslog(LOG_INFO, e->e_id, "Milter add: rcpt: %s", response);
+ olderrors = Errors;
+ (void) sendtolist(response, NULLADDR, &e->e_sendqueue, 0, e);
+ Errors = olderrors;
+ return;
+}
+/*
+** MILTER_DELRCPT -- Delete the supplied recipient from the message
+**
+** Parameters:
+** response -- encoded form of recipient address.
+** rlen -- length of response.
+** e -- current envelope.
+**
+** Returns:
+** none
+*/
+
+static void
+milter_delrcpt(response, rlen, e)
+ char *response;
+ ssize_t rlen;
+ ENVELOPE *e;
+{
+ if (tTd(64, 10))
+ sm_dprintf("milter_delrcpt: ");
+
+ /* sanity checks */
+ if (response == NULL)
+ {
+ if (tTd(64, 10))
+ sm_dprintf("NULL response\n");
+ return;
+ }
+
+ if (*response == '\0' ||
+ strlen(response) + 1 != (size_t) rlen)
+ {
+ if (tTd(64, 10))
+ sm_dprintf("didn't follow protocol (total len)\n");
+ return;
+ }
+
+ if (tTd(64, 10))
+ sm_dprintf("%s\n", response);
+ if (MilterLogLevel > 8)
+ sm_syslog(LOG_INFO, e->e_id, "Milter delete: rcpt %s",
+ response);
+ (void) removefromlist(response, &e->e_sendqueue, e);
+ return;
+}
+/*
+** MILTER_REPLBODY -- Replace the current data file with new body
+**
+** Parameters:
+** response -- encoded form of new body.
+** rlen -- length of response.
+** newfilter -- if first time called by a new filter
+** e -- current envelope.
+**
+** Returns:
+** 0 upon success, -1 upon failure
+*/
+
+static int
+milter_replbody(response, rlen, newfilter, e)
+ char *response;
+ ssize_t rlen;
+ bool newfilter;
+ ENVELOPE *e;
+{
+ static char prevchar;
+ int i;
+
+ if (tTd(64, 10))
+ sm_dprintf("milter_replbody\n");
+
+ /* If a new filter, reset previous character and truncate data file */
+ if (newfilter)
+ {
+ off_t prevsize;
+ char dfname[MAXPATHLEN];
+
+ (void) sm_strlcpy(dfname, queuename(e, DATAFL_LETTER),
+ sizeof dfname);
+
+ /* Reset prevchar */
+ prevchar = '\0';
+
+ /* Get the current data file information */
+ prevsize = sm_io_getinfo(e->e_dfp, SM_IO_WHAT_SIZE, NULL);
+ if (prevsize < 0)
+ prevsize = 0;
+
+ /* truncate current data file */
+ if (sm_io_getinfo(e->e_dfp, SM_IO_WHAT_ISTYPE, BF_FILE_TYPE))
+ {
+ if (sm_io_setinfo(e->e_dfp, SM_BF_TRUNCATE, NULL) < 0)
+ {
+ MILTER_DF_ERROR("milter_replbody: sm_io truncate %s: %s");
+ return -1;
+ }
+ }
+ else
+ {
+ int err;
+
+ err = sm_io_error(e->e_dfp);
+ (void) sm_io_flush(e->e_dfp, SM_TIME_DEFAULT);
+
+ /*
+ ** Clear error if tried to fflush()
+ ** a read-only file pointer and
+ ** there wasn't a previous error.
+ */
+
+ if (err == 0)
+ sm_io_clearerr(e->e_dfp);
+
+ /* errno is set implicitly by fseek() before return */
+ err = sm_io_seek(e->e_dfp, SM_TIME_DEFAULT,
+ 0, SEEK_SET);
+ if (err < 0)
+ {
+ MILTER_DF_ERROR("milter_replbody: sm_io_seek %s: %s");
+ return -1;
+ }
+# if NOFTRUNCATE
+ /* XXX: Not much we can do except rewind it */
+ errno = EINVAL;
+ MILTER_DF_ERROR("milter_replbody: ftruncate not available on this platform (%s:%s)");
+ return -1;
+# else /* NOFTRUNCATE */
+ err = ftruncate(sm_io_getinfo(e->e_dfp,
+ SM_IO_WHAT_FD, NULL),
+ 0);
+ if (err < 0)
+ {
+ MILTER_DF_ERROR("milter_replbody: sm_io ftruncate %s: %s");
+ return -1;
+ }
+# endif /* NOFTRUNCATE */
+ }
+
+ if (prevsize > e->e_msgsize)
+ e->e_msgsize = 0;
+ else
+ e->e_msgsize -= prevsize;
+ }
+
+ if (newfilter && MilterLogLevel > 8)
+ sm_syslog(LOG_INFO, e->e_id, "Milter message: body replaced");
+
+ if (response == NULL)
+ {
+ /* Flush the buffered '\r' */
+ if (prevchar == '\r')
+ {
+ (void) sm_io_putc(e->e_dfp, SM_TIME_DEFAULT, prevchar);
+ e->e_msgsize++;
+ }
+ return 0;
+ }
+
+ for (i = 0; i < rlen; i++)
+ {
+ /* Buffered char from last chunk */
+ if (i == 0 && prevchar == '\r')
+ {
+ /* Not CRLF, output prevchar */
+ if (response[i] != '\n')
+ {
+ (void) sm_io_putc(e->e_dfp, SM_TIME_DEFAULT,
+ prevchar);
+ e->e_msgsize++;
+ }
+ prevchar = '\0';
+ }
+
+ /* Turn CRLF into LF */
+ if (response[i] == '\r')
+ {
+ /* check if at end of chunk */
+ if (i + 1 < rlen)
+ {
+ /* If LF, strip CR */
+ if (response[i + 1] == '\n')
+ i++;
+ }
+ else
+ {
+ /* check next chunk */
+ prevchar = '\r';
+ continue;
+ }
+ }
+ (void) sm_io_putc(e->e_dfp, SM_TIME_DEFAULT, response[i]);
+ e->e_msgsize++;
+ }
+ return 0;
+}
+
+/*
+** MTA callouts
+*/
+
+/*
+** MILTER_INIT -- open and negotiate with all of the filters
+**
+** Parameters:
+** e -- current envelope.
+** state -- return state from response.
+**
+** Returns:
+** true iff at least one filter is active
+*/
+
+/* ARGSUSED */
+bool
+milter_init(e, state)
+ ENVELOPE *e;
+ char *state;
+{
+ int i;
+
+ if (tTd(64, 10))
+ sm_dprintf("milter_init\n");
+
+ *state = SMFIR_CONTINUE;
+ if (InputFilters[0] == NULL)
+ {
+ if (MilterLogLevel > 10)
+ sm_syslog(LOG_INFO, e->e_id,
+ "Milter: no active filter");
+ return false;
+ }
+
+ for (i = 0; InputFilters[i] != NULL; i++)
+ {
+ struct milter *m = InputFilters[i];
+
+ m->mf_sock = milter_open(m, false, e);
+ if (m->mf_state == SMFS_ERROR)
+ {
+ MILTER_CHECK_ERROR(true, continue);
+ break;
+ }
+
+ if (m->mf_sock < 0 ||
+ milter_negotiate(m, e) < 0 ||
+ m->mf_state == SMFS_ERROR)
+ {
+ if (tTd(64, 5))
+ sm_dprintf("milter_init(%s): failed to %s\n",
+ m->mf_name,
+ m->mf_sock < 0 ? "open" :
+ "negotiate");
+ if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "Milter (%s): init failed to %s",
+ m->mf_name,
+ m->mf_sock < 0 ? "open" :
+ "negotiate");
+
+ /* if negotation failure, close socket */
+ milter_error(m, e);
+ MILTER_CHECK_ERROR(true, continue);
+ continue;
+ }
+ if (MilterLogLevel > 9)
+ sm_syslog(LOG_INFO, e->e_id,
+ "Milter (%s): init success to %s",
+ m->mf_name,
+ m->mf_sock < 0 ? "open" : "negotiate");
+ }
+
+ /*
+ ** If something temp/perm failed with one of the filters,
+ ** we won't be using any of them, so clear any existing
+ ** connections.
+ */
+
+ if (*state != SMFIR_CONTINUE)
+ milter_quit(e);
+
+ return true;
+}
+/*
+** MILTER_CONNECT -- send connection info to milter filters
+**
+** Parameters:
+** hostname -- hostname of remote machine.
+** addr -- address of remote machine.
+** e -- current envelope.
+** state -- return state from response.
+**
+** Returns:
+** response string (may be NULL)
+*/
+
+char *
+milter_connect(hostname, addr, e, state)
+ char *hostname;
+ SOCKADDR addr;
+ ENVELOPE *e;
+ char *state;
+{
+ char family;
+ unsigned short port;
+ char *buf, *bp;
+ char *response;
+ char *sockinfo = NULL;
+ ssize_t s;
+# if NETINET6
+ char buf6[INET6_ADDRSTRLEN];
+# endif /* NETINET6 */
+
+ if (tTd(64, 10))
+ sm_dprintf("milter_connect(%s)\n", hostname);
+ if (MilterLogLevel > 9)
+ sm_syslog(LOG_INFO, e->e_id, "Milter: connect to filters");
+
+ /* gather data */
+ switch (addr.sa.sa_family)
+ {
+# if NETUNIX
+ case AF_UNIX:
+ family = SMFIA_UNIX;
+ port = htons(0);
+ sockinfo = addr.sunix.sun_path;
+ break;
+# endif /* NETUNIX */
+
+# if NETINET
+ case AF_INET:
+ family = SMFIA_INET;
+ port = addr.sin.sin_port;
+ sockinfo = (char *) inet_ntoa(addr.sin.sin_addr);
+ break;
+# endif /* NETINET */
+
+# if NETINET6
+ case AF_INET6:
+ if (IN6_IS_ADDR_V4MAPPED(&addr.sin6.sin6_addr))
+ family = SMFIA_INET;
+ else
+ family = SMFIA_INET6;
+ port = addr.sin6.sin6_port;
+ sockinfo = anynet_ntop(&addr.sin6.sin6_addr, buf6,
+ sizeof buf6);
+ if (sockinfo == NULL)
+ sockinfo = "";
+ break;
+# endif /* NETINET6 */
+
+ default:
+ family = SMFIA_UNKNOWN;
+ break;
+ }
+
+ s = strlen(hostname) + 1 + sizeof(family);
+ if (family != SMFIA_UNKNOWN)
+ s += sizeof(port) + strlen(sockinfo) + 1;
+
+ buf = (char *) xalloc(s);
+ bp = buf;
+
+ /* put together data */
+ (void) memcpy(bp, hostname, strlen(hostname));
+ bp += strlen(hostname);
+ *bp++ = '\0';
+ (void) memcpy(bp, &family, sizeof family);
+ bp += sizeof family;
+ if (family != SMFIA_UNKNOWN)
+ {
+ (void) memcpy(bp, &port, sizeof port);
+ bp += sizeof port;
+
+ /* include trailing '\0' */
+ (void) memcpy(bp, sockinfo, strlen(sockinfo) + 1);
+ }
+
+ response = milter_command(SMFIC_CONNECT, buf, s,
+ MilterConnectMacros, e, state);
+ sm_free(buf); /* XXX */
+
+ /*
+ ** If this message connection is done for,
+ ** close the filters.
+ */
+
+ if (*state != SMFIR_CONTINUE)
+ {
+ if (MilterLogLevel > 9)
+ sm_syslog(LOG_INFO, e->e_id, "Milter: connect, ending");
+ milter_quit(e);
+ }
+ else
+ milter_per_connection_check(e);
+
+ /*
+ ** SMFIR_REPLYCODE can't work with connect due to
+ ** the requirements of SMTP. Therefore, ignore the
+ ** reply code text but keep the state it would reflect.
+ */
+
+ if (*state == SMFIR_REPLYCODE)
+ {
+ if (response != NULL &&
+ *response == '4')
+ {
+ if (strncmp(response, "421 ", 4) == 0)
+ *state = SMFIR_SHUTDOWN;
+ else
+ *state = SMFIR_TEMPFAIL;
+ }
+ else
+ *state = SMFIR_REJECT;
+ if (response != NULL)
+ {
+ sm_free(response); /* XXX */
+ response = NULL;
+ }
+ }
+ return response;
+}
+/*
+** MILTER_HELO -- send SMTP HELO/EHLO command info to milter filters
+**
+** Parameters:
+** helo -- argument to SMTP HELO/EHLO command.
+** e -- current envelope.
+** state -- return state from response.
+**
+** Returns:
+** response string (may be NULL)
+*/
+
+char *
+milter_helo(helo, e, state)
+ char *helo;
+ ENVELOPE *e;
+ char *state;
+{
+ int i;
+ char *response;
+
+ if (tTd(64, 10))
+ sm_dprintf("milter_helo(%s)\n", helo);
+
+ /* HELO/EHLO can come at any point */
+ for (i = 0; InputFilters[i] != NULL; i++)
+ {
+ struct milter *m = InputFilters[i];
+
+ switch (m->mf_state)
+ {
+ case SMFS_INMSG:
+ /* abort in message filters */
+ milter_abort_filter(m, e);
+ /* FALLTHROUGH */
+
+ case SMFS_DONE:
+ /* reset done filters */
+ m->mf_state = SMFS_OPEN;
+ break;
+ }
+ }
+
+ response = milter_command(SMFIC_HELO, helo, strlen(helo) + 1,
+ MilterHeloMacros, e, state);
+ milter_per_connection_check(e);
+ return response;
+}
+/*
+** MILTER_ENVFROM -- send SMTP MAIL command info to milter filters
+**
+** Parameters:
+** args -- SMTP MAIL command args (args[0] == sender).
+** e -- current envelope.
+** state -- return state from response.
+**
+** Returns:
+** response string (may be NULL)
+*/
+
+char *
+milter_envfrom(args, e, state)
+ char **args;
+ ENVELOPE *e;
+ char *state;
+{
+ int i;
+ char *buf, *bp;
+ char *response;
+ ssize_t s;
+
+ if (tTd(64, 10))
+ {
+ sm_dprintf("milter_envfrom:");
+ for (i = 0; args[i] != NULL; i++)
+ sm_dprintf(" %s", args[i]);
+ sm_dprintf("\n");
+ }
+
+ /* sanity check */
+ if (args[0] == NULL)
+ {
+ *state = SMFIR_REJECT;
+ if (MilterLogLevel > 10)
+ sm_syslog(LOG_INFO, e->e_id,
+ "Milter: reject, no sender");
+ return NULL;
+ }
+
+ /* new message, so ... */
+ for (i = 0; InputFilters[i] != NULL; i++)
+ {
+ struct milter *m = InputFilters[i];
+
+ switch (m->mf_state)
+ {
+ case SMFS_INMSG:
+ /* abort in message filters */
+ milter_abort_filter(m, e);
+ /* FALLTHROUGH */
+
+ case SMFS_DONE:
+ /* reset done filters */
+ m->mf_state = SMFS_OPEN;
+ break;
+ }
+ }
+
+ /* put together data */
+ s = 0;
+ for (i = 0; args[i] != NULL; i++)
+ s += strlen(args[i]) + 1;
+
+ if (s < 0)
+ {
+ *state = SMFIR_TEMPFAIL;
+ return NULL;
+ }
+
+ buf = (char *) xalloc(s);
+ bp = buf;
+ for (i = 0; args[i] != NULL; i++)
+ {
+ (void) sm_strlcpy(bp, args[i], s - (bp - buf));
+ bp += strlen(bp) + 1;
+ }
+
+ if (MilterLogLevel > 14)
+ sm_syslog(LOG_INFO, e->e_id, "Milter: senders: %s", buf);
+
+ /* send it over */
+ response = milter_command(SMFIC_MAIL, buf, s,
+ MilterEnvFromMacros, e, state);
+ sm_free(buf); /* XXX */
+
+ /*
+ ** If filter rejects/discards a per message command,
+ ** abort the other filters since we are done with the
+ ** current message.
+ */
+
+ MILTER_CHECK_DONE_MSG();
+ if (MilterLogLevel > 10 && *state == SMFIR_REJECT)
+ sm_syslog(LOG_INFO, e->e_id, "Milter: reject, senders");
+ return response;
+}
+
+/*
+** MILTER_ENVRCPT -- send SMTP RCPT command info to milter filters
+**
+** Parameters:
+** args -- SMTP MAIL command args (args[0] == recipient).
+** e -- current envelope.
+** state -- return state from response.
+**
+** Returns:
+** response string (may be NULL)
+*/
+
+char *
+milter_envrcpt(args, e, state)
+ char **args;
+ ENVELOPE *e;
+ char *state;
+{
+ int i;
+ char *buf, *bp;
+ char *response;
+ ssize_t s;
+
+ if (tTd(64, 10))
+ {
+ sm_dprintf("milter_envrcpt:");
+ for (i = 0; args[i] != NULL; i++)
+ sm_dprintf(" %s", args[i]);
+ sm_dprintf("\n");
+ }
+
+ /* sanity check */
+ if (args[0] == NULL)
+ {
+ *state = SMFIR_REJECT;
+ if (MilterLogLevel > 10)
+ sm_syslog(LOG_INFO, e->e_id, "Milter: reject, no rcpt");
+ return NULL;
+ }
+
+ /* put together data */
+ s = 0;
+ for (i = 0; args[i] != NULL; i++)
+ s += strlen(args[i]) + 1;
+
+ if (s < 0)
+ {
+ *state = SMFIR_TEMPFAIL;
+ return NULL;
+ }
+
+ buf = (char *) xalloc(s);
+ bp = buf;
+ for (i = 0; args[i] != NULL; i++)
+ {
+ (void) sm_strlcpy(bp, args[i], s - (bp - buf));
+ bp += strlen(bp) + 1;
+ }
+
+ if (MilterLogLevel > 14)
+ sm_syslog(LOG_INFO, e->e_id, "Milter: rcpts: %s", buf);
+
+ /* send it over */
+ response = milter_command(SMFIC_RCPT, buf, s,
+ MilterEnvRcptMacros, e, state);
+ sm_free(buf); /* XXX */
+ return response;
+}
+
+#if SMFI_VERSION > 3
+/*
+** MILTER_DATA_CMD -- send SMTP DATA command info to milter filters
+**
+** Parameters:
+** e -- current envelope.
+** state -- return state from response.
+**
+** Returns:
+** response string (may be NULL)
+*/
+
+char *
+milter_data_cmd(e, state)
+ ENVELOPE *e;
+ char *state;
+{
+ if (tTd(64, 10))
+ sm_dprintf("milter_data_cmd\n");
+
+ /* send it over */
+ return milter_command(SMFIC_DATA, NULL, 0, MilterDataMacros, e, state);
+}
+#endif /* SMFI_VERSION > 3 */
+
+/*
+** MILTER_DATA -- send message headers/body and gather final message results
+**
+** Parameters:
+** e -- current envelope.
+** state -- return state from response.
+**
+** Returns:
+** response string (may be NULL)
+**
+** Side effects:
+** - Uses e->e_dfp for access to the body
+** - Can call the various milter action routines to
+** modify the envelope or message.
+*/
+
+# define MILTER_CHECK_RESULTS() \
+ if (*state == SMFIR_ACCEPT || \
+ m->mf_state == SMFS_DONE || \
+ m->mf_state == SMFS_ERROR) \
+ { \
+ if (m->mf_state != SMFS_ERROR) \
+ m->mf_state = SMFS_DONE; \
+ continue; /* to next filter */ \
+ } \
+ if (*state != SMFIR_CONTINUE) \
+ { \
+ m->mf_state = SMFS_DONE; \
+ goto finishup; \
+ }
+
+char *
+milter_data(e, state)
+ ENVELOPE *e;
+ char *state;
+{
+ bool replbody = false; /* milter_replbody() called? */
+ bool replfailed = false; /* milter_replbody() failed? */
+ bool rewind = false; /* rewind data file? */
+ bool dfopen = false; /* data file open for writing? */
+ bool newfilter; /* reset on each new filter */
+ char rcmd;
+ int i;
+ int save_errno;
+ char *response = NULL;
+ time_t eomsent;
+ ssize_t rlen;
+
+ if (tTd(64, 10))
+ sm_dprintf("milter_data\n");
+
+ *state = SMFIR_CONTINUE;
+
+ /*
+ ** XXX: Should actually send body chunks to each filter
+ ** a chunk at a time instead of sending the whole body to
+ ** each filter in turn. However, only if the filters don't
+ ** change the body.
+ */
+
+ for (i = 0; InputFilters[i] != NULL; i++)
+ {
+ struct milter *m = InputFilters[i];
+
+ if (*state != SMFIR_CONTINUE &&
+ *state != SMFIR_ACCEPT)
+ {
+ /*
+ ** A previous filter has dealt with the message,
+ ** safe to stop processing the filters.
+ */
+
+ break;
+ }
+
+ /* Now reset state for later evaluation */
+ *state = SMFIR_CONTINUE;
+ newfilter = true;
+
+ /* previous problem? */
+ if (m->mf_state == SMFS_ERROR)
+ {
+ MILTER_CHECK_ERROR(false, continue);
+ break;
+ }
+
+ /* sanity checks */
+ if (m->mf_sock < 0 ||
+ (m->mf_state != SMFS_OPEN && m->mf_state != SMFS_INMSG))
+ continue;
+
+ m->mf_state = SMFS_INMSG;
+
+ /* check if filter wants the headers */
+ if (!bitset(SMFIP_NOHDRS, m->mf_pflags))
+ {
+ response = milter_headers(m, e, state);
+ MILTER_CHECK_RESULTS();
+ }
+
+ /* check if filter wants EOH */
+ if (!bitset(SMFIP_NOEOH, m->mf_pflags))
+ {
+ if (tTd(64, 10))
+ sm_dprintf("milter_data: eoh\n");
+
+ /* send it over */
+ response = milter_send_command(m, SMFIC_EOH, NULL, 0,
+ e, state);
+ MILTER_CHECK_RESULTS();
+ }
+
+ /* check if filter wants the body */
+ if (!bitset(SMFIP_NOBODY, m->mf_pflags) &&
+ e->e_dfp != NULL)
+ {
+ rewind = true;
+ response = milter_body(m, e, state);
+ MILTER_CHECK_RESULTS();
+ }
+
+ if (MilterEOMMacros[0] != NULL)
+ {
+ milter_send_macros(m, MilterEOMMacros,
+ SMFIC_BODYEOB, e);
+ MILTER_CHECK_RESULTS();
+ }
+
+ /* send the final body chunk */
+ (void) milter_write(m, SMFIC_BODYEOB, NULL, 0,
+ m->mf_timeout[SMFTO_WRITE], e);
+
+ /* Get time EOM sent for timeout */
+ eomsent = curtime();
+
+ /* deal with the possibility of multiple responses */
+ while (*state == SMFIR_CONTINUE)
+ {
+ /* Check total timeout from EOM to final ACK/NAK */
+ if (m->mf_timeout[SMFTO_EOM] > 0 &&
+ curtime() - eomsent >= m->mf_timeout[SMFTO_EOM])
+ {
+ if (tTd(64, 5))
+ sm_dprintf("milter_data(%s): EOM ACK/NAK timeout\n",
+ m->mf_name);
+ if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "milter_data(%s): EOM ACK/NAK timeout",
+ m->mf_name);
+ milter_error(m, e);
+ MILTER_CHECK_ERROR(false, break);
+ break;
+ }
+
+ response = milter_read(m, &rcmd, &rlen,
+ m->mf_timeout[SMFTO_READ], e);
+ if (m->mf_state == SMFS_ERROR)
+ break;
+
+ if (tTd(64, 10))
+ sm_dprintf("milter_data(%s): state %c\n",
+ m->mf_name, (char) rcmd);
+
+ switch (rcmd)
+ {
+ case SMFIR_REPLYCODE:
+ MILTER_CHECK_REPLYCODE("554 5.7.1 Command rejected");
+ if (MilterLogLevel > 12)
+ sm_syslog(LOG_INFO, e->e_id, "milter=%s, reject=%s",
+ m->mf_name, response);
+ *state = rcmd;
+ m->mf_state = SMFS_DONE;
+ break;
+
+ case SMFIR_REJECT: /* log msg at end of function */
+ if (MilterLogLevel > 12)
+ sm_syslog(LOG_INFO, e->e_id, "milter=%s, reject",
+ m->mf_name);
+ *state = rcmd;
+ m->mf_state = SMFS_DONE;
+ break;
+
+ case SMFIR_DISCARD:
+ if (MilterLogLevel > 12)
+ sm_syslog(LOG_INFO, e->e_id, "milter=%s, discard",
+ m->mf_name);
+ *state = rcmd;
+ m->mf_state = SMFS_DONE;
+ break;
+
+ case SMFIR_TEMPFAIL:
+ if (MilterLogLevel > 12)
+ sm_syslog(LOG_INFO, e->e_id, "milter=%s, tempfail",
+ m->mf_name);
+ *state = rcmd;
+ m->mf_state = SMFS_DONE;
+ break;
+
+ case SMFIR_CONTINUE:
+ case SMFIR_ACCEPT:
+ /* this filter is done with message */
+ if (replfailed)
+ *state = SMFIR_TEMPFAIL;
+ else
+ *state = SMFIR_ACCEPT;
+ m->mf_state = SMFS_DONE;
+ break;
+
+ case SMFIR_PROGRESS:
+ break;
+
+ case SMFIR_QUARANTINE:
+ if (!bitset(SMFIF_QUARANTINE, m->mf_fflags))
+ {
+ if (MilterLogLevel > 9)
+ sm_syslog(LOG_WARNING, e->e_id,
+ "milter_data(%s): lied about quarantining, honoring request anyway",
+ m->mf_name);
+ }
+ if (response == NULL)
+ response = newstr("");
+ if (MilterLogLevel > 3)
+ sm_syslog(LOG_INFO, e->e_id,
+ "milter=%s, quarantine=%s",
+ m->mf_name, response);
+ e->e_quarmsg = sm_rpool_strdup_x(e->e_rpool,
+ response);
+ macdefine(&e->e_macro, A_PERM,
+ macid("{quarantine}"), e->e_quarmsg);
+ break;
+
+ case SMFIR_ADDHEADER:
+ if (!bitset(SMFIF_ADDHDRS, m->mf_fflags))
+ {
+ if (MilterLogLevel > 9)
+ sm_syslog(LOG_WARNING, e->e_id,
+ "milter_data(%s): lied about adding headers, honoring request anyway",
+ m->mf_name);
+ }
+ milter_addheader(response, rlen, e);
+ break;
+
+ case SMFIR_INSHEADER:
+ if (!bitset(SMFIF_ADDHDRS, m->mf_fflags))
+ {
+ if (MilterLogLevel > 9)
+ sm_syslog(LOG_WARNING, e->e_id,
+ "milter_data(%s): lied about adding headers, honoring request anyway",
+ m->mf_name);
+ }
+ milter_insheader(response, rlen, e);
+ break;
+
+ case SMFIR_CHGHEADER:
+ if (!bitset(SMFIF_CHGHDRS, m->mf_fflags))
+ {
+ if (MilterLogLevel > 9)
+ sm_syslog(LOG_WARNING, e->e_id,
+ "milter_data(%s): lied about changing headers, honoring request anyway",
+ m->mf_name);
+ }
+ milter_changeheader(response, rlen, e);
+ break;
+
+ case SMFIR_ADDRCPT:
+ if (!bitset(SMFIF_ADDRCPT, m->mf_fflags))
+ {
+ if (MilterLogLevel > 9)
+ sm_syslog(LOG_WARNING, e->e_id,
+ "milter_data(%s) lied about adding recipients, honoring request anyway",
+ m->mf_name);
+ }
+ milter_addrcpt(response, rlen, e);
+ break;
+
+ case SMFIR_DELRCPT:
+ if (!bitset(SMFIF_DELRCPT, m->mf_fflags))
+ {
+ if (MilterLogLevel > 9)
+ sm_syslog(LOG_WARNING, e->e_id,
+ "milter_data(%s): lied about removing recipients, honoring request anyway",
+ m->mf_name);
+ }
+ milter_delrcpt(response, rlen, e);
+ break;
+
+ case SMFIR_REPLBODY:
+ if (!bitset(SMFIF_MODBODY, m->mf_fflags))
+ {
+ if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "milter_data(%s): lied about replacing body, rejecting request and tempfailing message",
+ m->mf_name);
+ replfailed = true;
+ break;
+ }
+
+ /* already failed in attempt */
+ if (replfailed)
+ break;
+
+ if (!dfopen)
+ {
+ if (milter_reopen_df(e) < 0)
+ {
+ replfailed = true;
+ break;
+ }
+ dfopen = true;
+ rewind = true;
+ }
+
+ if (milter_replbody(response, rlen,
+ newfilter, e) < 0)
+ replfailed = true;
+ newfilter = false;
+ replbody = true;
+ break;
+
+ default:
+ /* Invalid response to command */
+ if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "milter_data(%s): returned bogus response %c",
+ m->mf_name, rcmd);
+ milter_error(m, e);
+ break;
+ }
+ if (rcmd != SMFIR_REPLYCODE && response != NULL)
+ {
+ sm_free(response); /* XXX */
+ response = NULL;
+ }
+
+ if (m->mf_state == SMFS_ERROR)
+ break;
+ }
+
+ if (replbody && !replfailed)
+ {
+ /* flush possible buffered character */
+ milter_replbody(NULL, 0, !replbody, e);
+ replbody = false;
+ }
+
+ if (m->mf_state == SMFS_ERROR)
+ {
+ MILTER_CHECK_ERROR(false, continue);
+ goto finishup;
+ }
+ }
+
+finishup:
+ /* leave things in the expected state if we touched it */
+ if (replfailed)
+ {
+ if (*state == SMFIR_CONTINUE ||
+ *state == SMFIR_ACCEPT)
+ {
+ *state = SMFIR_TEMPFAIL;
+ SM_FREE_CLR(response);
+ }
+
+ if (dfopen)
+ {
+ (void) sm_io_close(e->e_dfp, SM_TIME_DEFAULT);
+ e->e_dfp = NULL;
+ e->e_flags &= ~EF_HAS_DF;
+ dfopen = false;
+ }
+ rewind = false;
+ }
+
+ if ((dfopen && milter_reset_df(e) < 0) ||
+ (rewind && bfrewind(e->e_dfp) < 0))
+ {
+ save_errno = errno;
+ ExitStat = EX_IOERR;
+
+ /*
+ ** If filter told us to keep message but we had
+ ** an error, we can't really keep it, tempfail it.
+ */
+
+ if (*state == SMFIR_CONTINUE ||
+ *state == SMFIR_ACCEPT)
+ {
+ *state = SMFIR_TEMPFAIL;
+ SM_FREE_CLR(response);
+ }
+
+ errno = save_errno;
+ syserr("milter_data: %s/%cf%s: read error",
+ qid_printqueue(e->e_qgrp, e->e_qdir),
+ DATAFL_LETTER, e->e_id);
+ }
+
+ MILTER_CHECK_DONE_MSG();
+ if (MilterLogLevel > 10 && *state == SMFIR_REJECT)
+ sm_syslog(LOG_INFO, e->e_id, "Milter: reject, data");
+ return response;
+}
+
+#if SMFI_VERSION > 2
+/*
+** MILTER_UNKNOWN -- send any unrecognized or unimplemented command
+** string to milter filters
+**
+** Parameters:
+** cmd -- the string itself.
+** e -- current envelope.
+** state -- return state from response.
+**
+**
+** Returns:
+** response string (may be NULL)
+*/
+
+char *
+milter_unknown(cmd, e, state)
+ char *cmd;
+ ENVELOPE *e;
+ char *state;
+{
+ if (tTd(64, 10))
+ sm_dprintf("milter_unknown(%s)\n", cmd);
+
+ return milter_command(SMFIC_UNKNOWN, cmd, strlen(cmd) + 1,
+ NULL, e, state);
+}
+#endif /* SMFI_VERSION > 2 */
+
+/*
+** MILTER_QUIT -- informs the filter(s) we are done and closes connection(s)
+**
+** Parameters:
+** e -- current envelope.
+**
+** Returns:
+** none
+*/
+
+void
+milter_quit(e)
+ ENVELOPE *e;
+{
+ int i;
+
+ if (tTd(64, 10))
+ sm_dprintf("milter_quit(%s)\n", e->e_id);
+
+ for (i = 0; InputFilters[i] != NULL; i++)
+ milter_quit_filter(InputFilters[i], e);
+}
+/*
+** MILTER_ABORT -- informs the filter(s) that we are aborting current message
+**
+** Parameters:
+** e -- current envelope.
+**
+** Returns:
+** none
+*/
+
+void
+milter_abort(e)
+ ENVELOPE *e;
+{
+ int i;
+
+ if (tTd(64, 10))
+ sm_dprintf("milter_abort\n");
+
+ for (i = 0; InputFilters[i] != NULL; i++)
+ {
+ struct milter *m = InputFilters[i];
+
+ /* sanity checks */
+ if (m->mf_sock < 0 || m->mf_state != SMFS_INMSG)
+ continue;
+
+ milter_abort_filter(m, e);
+ }
+}
+#endif /* MILTER */
diff --git a/usr/src/cmd/sendmail/src/mime.c b/usr/src/cmd/sendmail/src/mime.c
new file mode 100644
index 0000000000..967ed82922
--- /dev/null
+++ b/usr/src/cmd/sendmail/src/mime.c
@@ -0,0 +1,1259 @@
+/*
+ * Copyright (c) 1998-2003 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1994, 1996-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sendmail.h>
+#include <string.h>
+
+SM_RCSID("@(#)$Id: mime.c,v 8.137 2004/09/02 21:37:26 ca Exp $")
+
+/*
+** MIME support.
+**
+** I am indebted to John Beck of Hewlett-Packard, who contributed
+** his code to me for inclusion. As it turns out, I did not use
+** his code since he used a "minimum change" approach that used
+** several temp files, and I wanted a "minimum impact" approach
+** that would avoid copying. However, looking over his code
+** helped me cement my understanding of the problem.
+**
+** I also looked at, but did not directly use, Nathaniel
+** Borenstein's "code.c" module. Again, it functioned as
+** a file-to-file translator, which did not fit within my
+** design bounds, but it was a useful base for understanding
+** the problem.
+*/
+
+/* use "old" mime 7 to 8 algorithm by default */
+#ifndef MIME7TO8_OLD
+# define MIME7TO8_OLD 1
+#endif /* ! MIME7TO8_OLD */
+
+#if MIME8TO7
+static int isboundary __P((char *, char **));
+static int mimeboundary __P((char *, char **));
+static int mime_getchar __P((SM_FILE_T *, char **, int *));
+static int mime_getchar_crlf __P((SM_FILE_T *, char **, int *));
+
+/* character set for hex and base64 encoding */
+static char Base16Code[] = "0123456789ABCDEF";
+static char Base64Code[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+/* types of MIME boundaries */
+# define MBT_SYNTAX 0 /* syntax error */
+# define MBT_NOTSEP 1 /* not a boundary */
+# define MBT_INTERMED 2 /* intermediate boundary (no trailing --) */
+# define MBT_FINAL 3 /* final boundary (trailing -- included) */
+
+static char *MimeBoundaryNames[] =
+{
+ "SYNTAX", "NOTSEP", "INTERMED", "FINAL"
+};
+
+static bool MapNLtoCRLF;
+
+/*
+** MIME8TO7 -- output 8 bit body in 7 bit format
+**
+** The header has already been output -- this has to do the
+** 8 to 7 bit conversion. It would be easy if we didn't have
+** to deal with nested formats (multipart/xxx and message/rfc822).
+**
+** We won't be called if we don't have to do a conversion, and
+** appropriate MIME-Version: and Content-Type: fields have been
+** output. Any Content-Transfer-Encoding: field has not been
+** output, and we can add it here.
+**
+** Parameters:
+** mci -- mailer connection information.
+** header -- the header for this body part.
+** e -- envelope.
+** boundaries -- the currently pending message boundaries.
+** NULL if we are processing the outer portion.
+** flags -- to tweak processing.
+**
+** Returns:
+** An indicator of what terminated the message part:
+** MBT_FINAL -- the final boundary
+** MBT_INTERMED -- an intermediate boundary
+** MBT_NOTSEP -- an end of file
+*/
+
+struct args
+{
+ char *a_field; /* name of field */
+ char *a_value; /* value of that field */
+};
+
+int
+mime8to7(mci, header, e, boundaries, flags)
+ register MCI *mci;
+ HDR *header;
+ register ENVELOPE *e;
+ char **boundaries;
+ int flags;
+{
+ register char *p;
+ int linelen;
+ int bt;
+ off_t offset;
+ size_t sectionsize, sectionhighbits;
+ int i;
+ char *type;
+ char *subtype;
+ char *cte;
+ char **pvp;
+ int argc = 0;
+ char *bp;
+ bool use_qp = false;
+ struct args argv[MAXMIMEARGS];
+ char bbuf[128];
+ char buf[MAXLINE];
+ char pvpbuf[MAXLINE];
+ extern unsigned char MimeTokenTab[256];
+
+ if (tTd(43, 1))
+ {
+ sm_dprintf("mime8to7: flags = %x, boundaries =", flags);
+ if (boundaries[0] == NULL)
+ sm_dprintf(" <none>");
+ else
+ {
+ for (i = 0; boundaries[i] != NULL; i++)
+ sm_dprintf(" %s", boundaries[i]);
+ }
+ sm_dprintf("\n");
+ }
+ MapNLtoCRLF = true;
+ p = hvalue("Content-Transfer-Encoding", header);
+ if (p == NULL ||
+ (pvp = prescan(p, '\0', pvpbuf, sizeof pvpbuf, NULL,
+ MimeTokenTab, false)) == NULL ||
+ pvp[0] == NULL)
+ {
+ cte = NULL;
+ }
+ else
+ {
+ cataddr(pvp, NULL, buf, sizeof buf, '\0');
+ cte = sm_rpool_strdup_x(e->e_rpool, buf);
+ }
+
+ type = subtype = NULL;
+ p = hvalue("Content-Type", header);
+ if (p == NULL)
+ {
+ if (bitset(M87F_DIGEST, flags))
+ p = "message/rfc822";
+ else
+ p = "text/plain";
+ }
+ if (p != NULL &&
+ (pvp = prescan(p, '\0', pvpbuf, sizeof pvpbuf, NULL,
+ MimeTokenTab, false)) != NULL &&
+ pvp[0] != NULL)
+ {
+ if (tTd(43, 40))
+ {
+ for (i = 0; pvp[i] != NULL; i++)
+ sm_dprintf("pvp[%d] = \"%s\"\n", i, pvp[i]);
+ }
+ type = *pvp++;
+ if (*pvp != NULL && strcmp(*pvp, "/") == 0 &&
+ *++pvp != NULL)
+ {
+ subtype = *pvp++;
+ }
+
+ /* break out parameters */
+ while (*pvp != NULL && argc < MAXMIMEARGS)
+ {
+ /* skip to semicolon separator */
+ while (*pvp != NULL && strcmp(*pvp, ";") != 0)
+ pvp++;
+ if (*pvp++ == NULL || *pvp == NULL)
+ break;
+
+ /* complain about empty values */
+ if (strcmp(*pvp, ";") == 0)
+ {
+ usrerr("mime8to7: Empty parameter in Content-Type header");
+
+ /* avoid bounce loops */
+ e->e_flags |= EF_DONT_MIME;
+ continue;
+ }
+
+ /* extract field name */
+ argv[argc].a_field = *pvp++;
+
+ /* see if there is a value */
+ if (*pvp != NULL && strcmp(*pvp, "=") == 0 &&
+ (*++pvp == NULL || strcmp(*pvp, ";") != 0))
+ {
+ argv[argc].a_value = *pvp;
+ argc++;
+ }
+ }
+ }
+
+ /* check for disaster cases */
+ if (type == NULL)
+ type = "-none-";
+ if (subtype == NULL)
+ subtype = "-none-";
+
+ /* don't propogate some flags more than one level into the message */
+ flags &= ~M87F_DIGEST;
+
+ /*
+ ** Check for cases that can not be encoded.
+ **
+ ** For example, you can't encode certain kinds of types
+ ** or already-encoded messages. If we find this case,
+ ** just copy it through.
+ */
+
+ (void) sm_snprintf(buf, sizeof buf, "%.100s/%.100s", type, subtype);
+ if (wordinclass(buf, 'n') || (cte != NULL && !wordinclass(cte, 'e')))
+ flags |= M87F_NO8BIT;
+
+# ifdef USE_B_CLASS
+ if (wordinclass(buf, 'b') || wordinclass(type, 'b'))
+ MapNLtoCRLF = false;
+# endif /* USE_B_CLASS */
+ if (wordinclass(buf, 'q') || wordinclass(type, 'q'))
+ use_qp = true;
+
+ /*
+ ** Multipart requires special processing.
+ **
+ ** Do a recursive descent into the message.
+ */
+
+ if (sm_strcasecmp(type, "multipart") == 0 &&
+ (!bitset(M87F_NO8BIT, flags) || bitset(M87F_NO8TO7, flags)))
+ {
+
+ if (sm_strcasecmp(subtype, "digest") == 0)
+ flags |= M87F_DIGEST;
+
+ for (i = 0; i < argc; i++)
+ {
+ if (sm_strcasecmp(argv[i].a_field, "boundary") == 0)
+ break;
+ }
+ if (i >= argc || argv[i].a_value == NULL)
+ {
+ usrerr("mime8to7: Content-Type: \"%s\": %s boundary",
+ i >= argc ? "missing" : "bogus", p);
+ p = "---";
+
+ /* avoid bounce loops */
+ e->e_flags |= EF_DONT_MIME;
+ }
+ else
+ {
+ p = argv[i].a_value;
+ stripquotes(p);
+ }
+ if (sm_strlcpy(bbuf, p, sizeof bbuf) >= sizeof bbuf)
+ {
+ usrerr("mime8to7: multipart boundary \"%s\" too long",
+ p);
+
+ /* avoid bounce loops */
+ e->e_flags |= EF_DONT_MIME;
+ }
+
+ if (tTd(43, 1))
+ sm_dprintf("mime8to7: multipart boundary \"%s\"\n",
+ bbuf);
+ for (i = 0; i < MAXMIMENESTING; i++)
+ {
+ if (boundaries[i] == NULL)
+ break;
+ }
+ if (i >= MAXMIMENESTING)
+ {
+ usrerr("mime8to7: multipart nesting boundary too deep");
+
+ /* avoid bounce loops */
+ e->e_flags |= EF_DONT_MIME;
+ }
+ else
+ {
+ boundaries[i] = bbuf;
+ boundaries[i + 1] = NULL;
+ }
+ mci->mci_flags |= MCIF_INMIME;
+
+ /* skip the early "comment" prologue */
+ putline("", mci);
+ mci->mci_flags &= ~MCIF_INHEADER;
+ bt = MBT_FINAL;
+ while (sm_io_fgets(e->e_dfp, SM_TIME_DEFAULT, buf, sizeof buf)
+ != NULL)
+ {
+ bt = mimeboundary(buf, boundaries);
+ if (bt != MBT_NOTSEP)
+ break;
+ putxline(buf, strlen(buf), mci,
+ PXLF_MAPFROM|PXLF_STRIP8BIT);
+ if (tTd(43, 99))
+ sm_dprintf(" ...%s", buf);
+ }
+ if (sm_io_eof(e->e_dfp))
+ bt = MBT_FINAL;
+ while (bt != MBT_FINAL)
+ {
+ auto HDR *hdr = NULL;
+
+ (void) sm_strlcpyn(buf, sizeof buf, 2, "--", bbuf);
+ putline(buf, mci);
+ if (tTd(43, 35))
+ sm_dprintf(" ...%s\n", buf);
+ collect(e->e_dfp, false, &hdr, e, false);
+ if (tTd(43, 101))
+ putline("+++after collect", mci);
+ putheader(mci, hdr, e, flags);
+ if (tTd(43, 101))
+ putline("+++after putheader", mci);
+ bt = mime8to7(mci, hdr, e, boundaries, flags);
+ }
+ (void) sm_strlcpyn(buf, sizeof buf, 3, "--", bbuf, "--");
+ putline(buf, mci);
+ if (tTd(43, 35))
+ sm_dprintf(" ...%s\n", buf);
+ boundaries[i] = NULL;
+ mci->mci_flags &= ~MCIF_INMIME;
+
+ /* skip the late "comment" epilogue */
+ while (sm_io_fgets(e->e_dfp, SM_TIME_DEFAULT, buf, sizeof buf)
+ != NULL)
+ {
+ bt = mimeboundary(buf, boundaries);
+ if (bt != MBT_NOTSEP)
+ break;
+ putxline(buf, strlen(buf), mci,
+ PXLF_MAPFROM|PXLF_STRIP8BIT);
+ if (tTd(43, 99))
+ sm_dprintf(" ...%s", buf);
+ }
+ if (sm_io_eof(e->e_dfp))
+ bt = MBT_FINAL;
+ if (tTd(43, 3))
+ sm_dprintf("\t\t\tmime8to7=>%s (multipart)\n",
+ MimeBoundaryNames[bt]);
+ return bt;
+ }
+
+ /*
+ ** Message/xxx types -- recurse exactly once.
+ **
+ ** Class 's' is predefined to have "rfc822" only.
+ */
+
+ if (sm_strcasecmp(type, "message") == 0)
+ {
+ if (!wordinclass(subtype, 's'))
+ {
+ flags |= M87F_NO8BIT;
+ }
+ else
+ {
+ auto HDR *hdr = NULL;
+
+ putline("", mci);
+
+ mci->mci_flags |= MCIF_INMIME;
+ collect(e->e_dfp, false, &hdr, e, false);
+ if (tTd(43, 101))
+ putline("+++after collect", mci);
+ putheader(mci, hdr, e, flags);
+ if (tTd(43, 101))
+ putline("+++after putheader", mci);
+ if (hvalue("MIME-Version", hdr) == NULL &&
+ !bitset(M87F_NO8TO7, flags))
+ putline("MIME-Version: 1.0", mci);
+ bt = mime8to7(mci, hdr, e, boundaries, flags);
+ mci->mci_flags &= ~MCIF_INMIME;
+ return bt;
+ }
+ }
+
+ /*
+ ** Non-compound body type
+ **
+ ** Compute the ratio of seven to eight bit characters;
+ ** use that as a heuristic to decide how to do the
+ ** encoding.
+ */
+
+ sectionsize = sectionhighbits = 0;
+ if (!bitset(M87F_NO8BIT|M87F_NO8TO7, flags))
+ {
+ /* remember where we were */
+ offset = sm_io_tell(e->e_dfp, SM_TIME_DEFAULT);
+ if (offset == -1)
+ syserr("mime8to7: cannot sm_io_tell on %cf%s",
+ DATAFL_LETTER, e->e_id);
+
+ /* do a scan of this body type to count character types */
+ while (sm_io_fgets(e->e_dfp, SM_TIME_DEFAULT, buf, sizeof buf)
+ != NULL)
+ {
+ if (mimeboundary(buf, boundaries) != MBT_NOTSEP)
+ break;
+ for (p = buf; *p != '\0'; p++)
+ {
+ /* count bytes with the high bit set */
+ sectionsize++;
+ if (bitset(0200, *p))
+ sectionhighbits++;
+ }
+
+ /*
+ ** Heuristic: if 1/4 of the first 4K bytes are 8-bit,
+ ** assume base64. This heuristic avoids double-reading
+ ** large graphics or video files.
+ */
+
+ if (sectionsize >= 4096 &&
+ sectionhighbits > sectionsize / 4)
+ break;
+ }
+
+ /* return to the original offset for processing */
+ /* XXX use relative seeks to handle >31 bit file sizes? */
+ if (sm_io_seek(e->e_dfp, SM_TIME_DEFAULT, offset, SEEK_SET) < 0)
+ syserr("mime8to7: cannot sm_io_fseek on %cf%s",
+ DATAFL_LETTER, e->e_id);
+ else
+ sm_io_clearerr(e->e_dfp);
+ }
+
+ /*
+ ** Heuristically determine encoding method.
+ ** If more than 1/8 of the total characters have the
+ ** eighth bit set, use base64; else use quoted-printable.
+ ** However, only encode binary encoded data as base64,
+ ** since otherwise the NL=>CRLF mapping will be a problem.
+ */
+
+ if (tTd(43, 8))
+ {
+ sm_dprintf("mime8to7: %ld high bit(s) in %ld byte(s), cte=%s, type=%s/%s\n",
+ (long) sectionhighbits, (long) sectionsize,
+ cte == NULL ? "[none]" : cte,
+ type == NULL ? "[none]" : type,
+ subtype == NULL ? "[none]" : subtype);
+ }
+ if (cte != NULL && sm_strcasecmp(cte, "binary") == 0)
+ sectionsize = sectionhighbits;
+ linelen = 0;
+ bp = buf;
+ if (sectionhighbits == 0)
+ {
+ /* no encoding necessary */
+ if (cte != NULL &&
+ bitset(MCIF_CVT8TO7|MCIF_CVT7TO8|MCIF_INMIME,
+ mci->mci_flags) &&
+ !bitset(M87F_NO8TO7, flags))
+ {
+ /*
+ ** Skip _unless_ in MIME mode and potentially
+ ** converting from 8 bit to 7 bit MIME. See
+ ** putheader() for the counterpart where the
+ ** CTE header is skipped in the opposite
+ ** situation.
+ */
+
+ (void) sm_snprintf(buf, sizeof buf,
+ "Content-Transfer-Encoding: %.200s", cte);
+ putline(buf, mci);
+ if (tTd(43, 36))
+ sm_dprintf(" ...%s\n", buf);
+ }
+ putline("", mci);
+ mci->mci_flags &= ~MCIF_INHEADER;
+ while (sm_io_fgets(e->e_dfp, SM_TIME_DEFAULT, buf, sizeof buf)
+ != NULL)
+ {
+ bt = mimeboundary(buf, boundaries);
+ if (bt != MBT_NOTSEP)
+ break;
+ putline(buf, mci);
+ }
+ if (sm_io_eof(e->e_dfp))
+ bt = MBT_FINAL;
+ }
+ else if (!MapNLtoCRLF ||
+ (sectionsize / 8 < sectionhighbits && !use_qp))
+ {
+ /* use base64 encoding */
+ int c1, c2;
+
+ if (tTd(43, 36))
+ sm_dprintf(" ...Content-Transfer-Encoding: base64\n");
+ putline("Content-Transfer-Encoding: base64", mci);
+ (void) sm_snprintf(buf, sizeof buf,
+ "X-MIME-Autoconverted: from 8bit to base64 by %s id %s",
+ MyHostName, e->e_id);
+ putline(buf, mci);
+ putline("", mci);
+ mci->mci_flags &= ~MCIF_INHEADER;
+ while ((c1 = mime_getchar_crlf(e->e_dfp, boundaries, &bt)) !=
+ SM_IO_EOF)
+ {
+ if (linelen > 71)
+ {
+ *bp = '\0';
+ putline(buf, mci);
+ linelen = 0;
+ bp = buf;
+ }
+ linelen += 4;
+ *bp++ = Base64Code[(c1 >> 2)];
+ c1 = (c1 & 0x03) << 4;
+ c2 = mime_getchar_crlf(e->e_dfp, boundaries, &bt);
+ if (c2 == SM_IO_EOF)
+ {
+ *bp++ = Base64Code[c1];
+ *bp++ = '=';
+ *bp++ = '=';
+ break;
+ }
+ c1 |= (c2 >> 4) & 0x0f;
+ *bp++ = Base64Code[c1];
+ c1 = (c2 & 0x0f) << 2;
+ c2 = mime_getchar_crlf(e->e_dfp, boundaries, &bt);
+ if (c2 == SM_IO_EOF)
+ {
+ *bp++ = Base64Code[c1];
+ *bp++ = '=';
+ break;
+ }
+ c1 |= (c2 >> 6) & 0x03;
+ *bp++ = Base64Code[c1];
+ *bp++ = Base64Code[c2 & 0x3f];
+ }
+ *bp = '\0';
+ putline(buf, mci);
+ }
+ else
+ {
+ /* use quoted-printable encoding */
+ int c1, c2;
+ int fromstate;
+ BITMAP256 badchars;
+
+ /* set up map of characters that must be mapped */
+ clrbitmap(badchars);
+ for (c1 = 0x00; c1 < 0x20; c1++)
+ setbitn(c1, badchars);
+ clrbitn('\t', badchars);
+ for (c1 = 0x7f; c1 < 0x100; c1++)
+ setbitn(c1, badchars);
+ setbitn('=', badchars);
+ if (bitnset(M_EBCDIC, mci->mci_mailer->m_flags))
+ for (p = "!\"#$@[\\]^`{|}~"; *p != '\0'; p++)
+ setbitn(*p, badchars);
+
+ if (tTd(43, 36))
+ sm_dprintf(" ...Content-Transfer-Encoding: quoted-printable\n");
+ putline("Content-Transfer-Encoding: quoted-printable", mci);
+ (void) sm_snprintf(buf, sizeof buf,
+ "X-MIME-Autoconverted: from 8bit to quoted-printable by %s id %s",
+ MyHostName, e->e_id);
+ putline(buf, mci);
+ putline("", mci);
+ mci->mci_flags &= ~MCIF_INHEADER;
+ fromstate = 0;
+ c2 = '\n';
+ while ((c1 = mime_getchar(e->e_dfp, boundaries, &bt)) !=
+ SM_IO_EOF)
+ {
+ if (c1 == '\n')
+ {
+ if (c2 == ' ' || c2 == '\t')
+ {
+ *bp++ = '=';
+ *bp++ = Base16Code[(c2 >> 4) & 0x0f];
+ *bp++ = Base16Code[c2 & 0x0f];
+ }
+ if (buf[0] == '.' && bp == &buf[1])
+ {
+ buf[0] = '=';
+ *bp++ = Base16Code[('.' >> 4) & 0x0f];
+ *bp++ = Base16Code['.' & 0x0f];
+ }
+ *bp = '\0';
+ putline(buf, mci);
+ linelen = fromstate = 0;
+ bp = buf;
+ c2 = c1;
+ continue;
+ }
+ if (c2 == ' ' && linelen == 4 && fromstate == 4 &&
+ bitnset(M_ESCFROM, mci->mci_mailer->m_flags))
+ {
+ *bp++ = '=';
+ *bp++ = '2';
+ *bp++ = '0';
+ linelen += 3;
+ }
+ else if (c2 == ' ' || c2 == '\t')
+ {
+ *bp++ = c2;
+ linelen++;
+ }
+ if (linelen > 72 &&
+ (linelen > 75 || c1 != '.' ||
+ (linelen > 73 && c2 == '.')))
+ {
+ if (linelen > 73 && c2 == '.')
+ bp--;
+ else
+ c2 = '\n';
+ *bp++ = '=';
+ *bp = '\0';
+ putline(buf, mci);
+ linelen = fromstate = 0;
+ bp = buf;
+ if (c2 == '.')
+ {
+ *bp++ = '.';
+ linelen++;
+ }
+ }
+ if (bitnset(bitidx(c1), badchars))
+ {
+ *bp++ = '=';
+ *bp++ = Base16Code[(c1 >> 4) & 0x0f];
+ *bp++ = Base16Code[c1 & 0x0f];
+ linelen += 3;
+ }
+ else if (c1 != ' ' && c1 != '\t')
+ {
+ if (linelen < 4 && c1 == "From"[linelen])
+ fromstate++;
+ *bp++ = c1;
+ linelen++;
+ }
+ c2 = c1;
+ }
+
+ /* output any saved character */
+ if (c2 == ' ' || c2 == '\t')
+ {
+ *bp++ = '=';
+ *bp++ = Base16Code[(c2 >> 4) & 0x0f];
+ *bp++ = Base16Code[c2 & 0x0f];
+ linelen += 3;
+ }
+
+ if (linelen > 0 || boundaries[0] != NULL)
+ {
+ *bp = '\0';
+ putline(buf, mci);
+ }
+
+ }
+ if (tTd(43, 3))
+ sm_dprintf("\t\t\tmime8to7=>%s (basic)\n", MimeBoundaryNames[bt]);
+ return bt;
+}
+/*
+** MIME_GETCHAR -- get a character for MIME processing
+**
+** Treats boundaries as SM_IO_EOF.
+**
+** Parameters:
+** fp -- the input file.
+** boundaries -- the current MIME boundaries.
+** btp -- if the return value is SM_IO_EOF, *btp is set to
+** the type of the boundary.
+**
+** Returns:
+** The next character in the input stream.
+*/
+
+static int
+mime_getchar(fp, boundaries, btp)
+ register SM_FILE_T *fp;
+ char **boundaries;
+ int *btp;
+{
+ int c;
+ static unsigned char *bp = NULL;
+ static int buflen = 0;
+ static bool atbol = true; /* at beginning of line */
+ static int bt = MBT_SYNTAX; /* boundary type of next SM_IO_EOF */
+ static unsigned char buf[128]; /* need not be a full line */
+ int start = 0; /* indicates position of - in buffer */
+
+ if (buflen == 1 && *bp == '\n')
+ {
+ /* last \n in buffer may be part of next MIME boundary */
+ c = *bp;
+ }
+ else if (buflen > 0)
+ {
+ buflen--;
+ return *bp++;
+ }
+ else
+ c = sm_io_getc(fp, SM_TIME_DEFAULT);
+ bp = buf;
+ buflen = 0;
+ if (c == '\n')
+ {
+ /* might be part of a MIME boundary */
+ *bp++ = c;
+ atbol = true;
+ c = sm_io_getc(fp, SM_TIME_DEFAULT);
+ if (c == '\n')
+ {
+ (void) sm_io_ungetc(fp, SM_TIME_DEFAULT, c);
+ return c;
+ }
+ start = 1;
+ }
+ if (c != SM_IO_EOF)
+ *bp++ = c;
+ else
+ bt = MBT_FINAL;
+ if (atbol && c == '-')
+ {
+ /* check for a message boundary */
+ c = sm_io_getc(fp, SM_TIME_DEFAULT);
+ if (c != '-')
+ {
+ if (c != SM_IO_EOF)
+ *bp++ = c;
+ else
+ bt = MBT_FINAL;
+ buflen = bp - buf - 1;
+ bp = buf;
+ return *bp++;
+ }
+
+ /* got "--", now check for rest of separator */
+ *bp++ = '-';
+ while (bp < &buf[sizeof buf - 2] &&
+ (c = sm_io_getc(fp, SM_TIME_DEFAULT)) != SM_IO_EOF &&
+ c != '\n')
+ {
+ *bp++ = c;
+ }
+ *bp = '\0'; /* XXX simply cut off? */
+ bt = mimeboundary((char *) &buf[start], boundaries);
+ switch (bt)
+ {
+ case MBT_FINAL:
+ case MBT_INTERMED:
+ /* we have a message boundary */
+ buflen = 0;
+ *btp = bt;
+ return SM_IO_EOF;
+ }
+
+ if (bp < &buf[sizeof buf - 2] && c != SM_IO_EOF)
+ *bp++ = c;
+ }
+
+ atbol = c == '\n';
+ buflen = bp - buf - 1;
+ if (buflen < 0)
+ {
+ *btp = bt;
+ return SM_IO_EOF;
+ }
+ bp = buf;
+ return *bp++;
+}
+/*
+** MIME_GETCHAR_CRLF -- do mime_getchar, but translate NL => CRLF
+**
+** Parameters:
+** fp -- the input file.
+** boundaries -- the current MIME boundaries.
+** btp -- if the return value is SM_IO_EOF, *btp is set to
+** the type of the boundary.
+**
+** Returns:
+** The next character in the input stream.
+*/
+
+static int
+mime_getchar_crlf(fp, boundaries, btp)
+ register SM_FILE_T *fp;
+ char **boundaries;
+ int *btp;
+{
+ static bool sendlf = false;
+ int c;
+
+ if (sendlf)
+ {
+ sendlf = false;
+ return '\n';
+ }
+ c = mime_getchar(fp, boundaries, btp);
+ if (c == '\n' && MapNLtoCRLF)
+ {
+ sendlf = true;
+ return '\r';
+ }
+ return c;
+}
+/*
+** MIMEBOUNDARY -- determine if this line is a MIME boundary & its type
+**
+** Parameters:
+** line -- the input line.
+** boundaries -- the set of currently pending boundaries.
+**
+** Returns:
+** MBT_NOTSEP -- if this is not a separator line
+** MBT_INTERMED -- if this is an intermediate separator
+** MBT_FINAL -- if this is a final boundary
+** MBT_SYNTAX -- if this is a boundary for the wrong
+** enclosure -- i.e., a syntax error.
+*/
+
+static int
+mimeboundary(line, boundaries)
+ register char *line;
+ char **boundaries;
+{
+ int type = MBT_NOTSEP;
+ int i;
+ int savec;
+
+ if (line[0] != '-' || line[1] != '-' || boundaries == NULL)
+ return MBT_NOTSEP;
+ i = strlen(line);
+ if (i > 0 && line[i - 1] == '\n')
+ i--;
+
+ /* strip off trailing whitespace */
+ while (i > 0 && (line[i - 1] == ' ' || line[i - 1] == '\t'
+#if _FFR_MIME_CR_OK
+ || line[i - 1] == '\r'
+#endif /* _FFR_MIME_CR_OK */
+ ))
+ i--;
+ savec = line[i];
+ line[i] = '\0';
+
+ if (tTd(43, 5))
+ sm_dprintf("mimeboundary: line=\"%s\"... ", line);
+
+ /* check for this as an intermediate boundary */
+ if (isboundary(&line[2], boundaries) >= 0)
+ type = MBT_INTERMED;
+ else if (i > 2 && strncmp(&line[i - 2], "--", 2) == 0)
+ {
+ /* check for a final boundary */
+ line[i - 2] = '\0';
+ if (isboundary(&line[2], boundaries) >= 0)
+ type = MBT_FINAL;
+ line[i - 2] = '-';
+ }
+
+ line[i] = savec;
+ if (tTd(43, 5))
+ sm_dprintf("%s\n", MimeBoundaryNames[type]);
+ return type;
+}
+/*
+** DEFCHARSET -- return default character set for message
+**
+** The first choice for character set is for the mailer
+** corresponding to the envelope sender. If neither that
+** nor the global configuration file has a default character
+** set defined, return "unknown-8bit" as recommended by
+** RFC 1428 section 3.
+**
+** Parameters:
+** e -- the envelope for this message.
+**
+** Returns:
+** The default character set for that mailer.
+*/
+
+char *
+defcharset(e)
+ register ENVELOPE *e;
+{
+ if (e != NULL && e->e_from.q_mailer != NULL &&
+ e->e_from.q_mailer->m_defcharset != NULL)
+ return e->e_from.q_mailer->m_defcharset;
+ if (DefaultCharSet != NULL)
+ return DefaultCharSet;
+ return "unknown-8bit";
+}
+/*
+** ISBOUNDARY -- is a given string a currently valid boundary?
+**
+** Parameters:
+** line -- the current input line.
+** boundaries -- the list of valid boundaries.
+**
+** Returns:
+** The index number in boundaries if the line is found.
+** -1 -- otherwise.
+**
+*/
+
+static int
+isboundary(line, boundaries)
+ char *line;
+ char **boundaries;
+{
+ register int i;
+
+ for (i = 0; i <= MAXMIMENESTING && boundaries[i] != NULL; i++)
+ {
+ if (strcmp(line, boundaries[i]) == 0)
+ return i;
+ }
+ return -1;
+}
+#endif /* MIME8TO7 */
+
+#if MIME7TO8
+static int mime_fromqp __P((unsigned char *, unsigned char **, int));
+
+/*
+** MIME7TO8 -- output 7 bit encoded MIME body in 8 bit format
+**
+** This is a hack. Supports translating the two 7-bit body-encodings
+** (quoted-printable and base64) to 8-bit coded bodies.
+**
+** There is not much point in supporting multipart here, as the UA
+** will be able to deal with encoded MIME bodies if it can parse MIME
+** multipart messages.
+**
+** Note also that we won't be called unless it is a text/plain MIME
+** message, encoded base64 or QP and mailer flag '9' has been defined
+** on mailer.
+**
+** Contributed by Marius Olaffson <marius@rhi.hi.is>.
+**
+** Parameters:
+** mci -- mailer connection information.
+** header -- the header for this body part.
+** e -- envelope.
+**
+** Returns:
+** none.
+*/
+
+static char index_64[128] =
+{
+ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
+ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
+ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63,
+ 52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1,-1,-1,-1,
+ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14,
+ 15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1,
+ -1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
+ 41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1
+};
+
+# define CHAR64(c) (((c) < 0 || (c) > 127) ? -1 : index_64[(c)])
+
+void
+mime7to8(mci, header, e)
+ register MCI *mci;
+ HDR *header;
+ register ENVELOPE *e;
+{
+ int pxflags;
+ register char *p;
+ char *cte;
+ char **pvp;
+ unsigned char *fbufp;
+ char buf[MAXLINE];
+ unsigned char fbuf[MAXLINE + 1];
+ char pvpbuf[MAXLINE];
+ extern unsigned char MimeTokenTab[256];
+
+ p = hvalue("Content-Transfer-Encoding", header);
+ if (p == NULL ||
+ (pvp = prescan(p, '\0', pvpbuf, sizeof pvpbuf, NULL,
+ MimeTokenTab, false)) == NULL ||
+ pvp[0] == NULL)
+ {
+ /* "can't happen" -- upper level should have caught this */
+ syserr("mime7to8: unparsable CTE %s", p == NULL ? "<NULL>" : p);
+
+ /* avoid bounce loops */
+ e->e_flags |= EF_DONT_MIME;
+
+ /* cheap failsafe algorithm -- should work on text/plain */
+ if (p != NULL)
+ {
+ (void) sm_snprintf(buf, sizeof buf,
+ "Content-Transfer-Encoding: %s", p);
+ putline(buf, mci);
+ }
+ putline("", mci);
+ mci->mci_flags &= ~MCIF_INHEADER;
+ while (sm_io_fgets(e->e_dfp, SM_TIME_DEFAULT, buf, sizeof buf)
+ != NULL)
+ putline(buf, mci);
+ return;
+ }
+ cataddr(pvp, NULL, buf, sizeof buf, '\0');
+ cte = sm_rpool_strdup_x(e->e_rpool, buf);
+
+ mci->mci_flags |= MCIF_INHEADER;
+ putline("Content-Transfer-Encoding: 8bit", mci);
+ (void) sm_snprintf(buf, sizeof buf,
+ "X-MIME-Autoconverted: from %.200s to 8bit by %s id %s",
+ cte, MyHostName, e->e_id);
+ putline(buf, mci);
+ putline("", mci);
+ mci->mci_flags &= ~MCIF_INHEADER;
+
+ /*
+ ** Translate body encoding to 8-bit. Supports two types of
+ ** encodings; "base64" and "quoted-printable". Assume qp if
+ ** it is not base64.
+ */
+
+ pxflags = PXLF_MAPFROM;
+ if (sm_strcasecmp(cte, "base64") == 0)
+ {
+ int c1, c2, c3, c4;
+
+ fbufp = fbuf;
+ while ((c1 = sm_io_getc(e->e_dfp, SM_TIME_DEFAULT)) !=
+ SM_IO_EOF)
+ {
+ if (isascii(c1) && isspace(c1))
+ continue;
+
+ do
+ {
+ c2 = sm_io_getc(e->e_dfp, SM_TIME_DEFAULT);
+ } while (isascii(c2) && isspace(c2));
+ if (c2 == SM_IO_EOF)
+ break;
+
+ do
+ {
+ c3 = sm_io_getc(e->e_dfp, SM_TIME_DEFAULT);
+ } while (isascii(c3) && isspace(c3));
+ if (c3 == SM_IO_EOF)
+ break;
+
+ do
+ {
+ c4 = sm_io_getc(e->e_dfp, SM_TIME_DEFAULT);
+ } while (isascii(c4) && isspace(c4));
+ if (c4 == SM_IO_EOF)
+ break;
+
+ if (c1 == '=' || c2 == '=')
+ continue;
+ c1 = CHAR64(c1);
+ c2 = CHAR64(c2);
+
+#if MIME7TO8_OLD
+#define CHK_EOL if (*--fbufp != '\n' || (fbufp > fbuf && *--fbufp != '\r')) \
+ ++fbufp;
+#else /* MIME7TO8_OLD */
+#define CHK_EOL if (*--fbufp != '\n' || (fbufp > fbuf && *--fbufp != '\r')) \
+ { \
+ ++fbufp; \
+ pxflags |= PXLF_NOADDEOL; \
+ }
+#endif /* MIME7TO8_OLD */
+
+#define PUTLINE64 \
+ do \
+ { \
+ if (*fbufp++ == '\n' || fbufp >= &fbuf[MAXLINE]) \
+ { \
+ CHK_EOL; \
+ putxline((char *) fbuf, fbufp - fbuf, mci, pxflags); \
+ pxflags &= ~PXLF_NOADDEOL; \
+ fbufp = fbuf; \
+ } \
+ } while (0)
+
+ *fbufp = (c1 << 2) | ((c2 & 0x30) >> 4);
+ PUTLINE64;
+ if (c3 == '=')
+ continue;
+ c3 = CHAR64(c3);
+ *fbufp = ((c2 & 0x0f) << 4) | ((c3 & 0x3c) >> 2);
+ PUTLINE64;
+ if (c4 == '=')
+ continue;
+ c4 = CHAR64(c4);
+ *fbufp = ((c3 & 0x03) << 6) | c4;
+ PUTLINE64;
+ }
+ }
+ else
+ {
+ int off;
+
+ /* quoted-printable */
+ pxflags |= PXLF_NOADDEOL;
+ fbufp = fbuf;
+ while (sm_io_fgets(e->e_dfp, SM_TIME_DEFAULT, buf,
+ sizeof buf) != NULL)
+ {
+ off = mime_fromqp((unsigned char *) buf, &fbufp,
+ &fbuf[MAXLINE] - fbufp);
+again:
+ if (off < -1)
+ continue;
+
+ if (fbufp - fbuf > 0)
+ putxline((char *) fbuf, fbufp - fbuf - 1, mci,
+ pxflags);
+ fbufp = fbuf;
+ if (off >= 0 && buf[off] != '\0')
+ {
+ off = mime_fromqp((unsigned char *) (buf + off),
+ &fbufp,
+ &fbuf[MAXLINE] - fbufp);
+ goto again;
+ }
+ }
+ }
+
+ /* force out partial last line */
+ if (fbufp > fbuf)
+ {
+ *fbufp = '\0';
+ putxline((char *) fbuf, fbufp - fbuf, mci, pxflags);
+ }
+
+ /*
+ ** The decoded text may end without an EOL. Since this function
+ ** is only called for text/plain MIME messages, it is safe to
+ ** add an extra one at the end just in case. This is a hack,
+ ** but so is auto-converting MIME in the first place.
+ */
+
+ putline("", mci);
+
+ if (tTd(43, 3))
+ sm_dprintf("\t\t\tmime7to8 => %s to 8bit done\n", cte);
+}
+/*
+** The following is based on Borenstein's "codes.c" module, with simplifying
+** changes as we do not deal with multipart, and to do the translation in-core,
+** with an attempt to prevent overrun of output buffers.
+**
+** What is needed here are changes to defend this code better against
+** bad encodings. Questionable to always return 0xFF for bad mappings.
+*/
+
+static char index_hex[128] =
+{
+ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
+ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
+ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1, -1,-1,-1,-1,
+ -1,10,11,12, 13,14,15,-1, -1,-1,-1,-1, -1,-1,-1,-1,
+ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
+ -1,10,11,12, 13,14,15,-1, -1,-1,-1,-1, -1,-1,-1,-1,
+ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1
+};
+
+# define HEXCHAR(c) (((c) < 0 || (c) > 127) ? -1 : index_hex[(c)])
+
+/*
+** MIME_FROMQP -- decode quoted printable string
+**
+** Parameters:
+** infile -- input (encoded) string
+** outfile -- output string
+** maxlen -- size of output buffer
+**
+** Returns:
+** -2 if decoding failure
+** -1 if infile completely decoded into outfile
+** >= 0 is the position in infile decoding
+** reached before maxlen was reached
+*/
+
+static int
+mime_fromqp(infile, outfile, maxlen)
+ unsigned char *infile;
+ unsigned char **outfile;
+ int maxlen; /* Max # of chars allowed in outfile */
+{
+ int c1, c2;
+ int nchar = 0;
+ unsigned char *b;
+
+ /* decrement by one for trailing '\0', at least one other char */
+ if (--maxlen < 1)
+ return 0;
+
+ b = infile;
+ while ((c1 = *infile++) != '\0' && nchar < maxlen)
+ {
+ if (c1 == '=')
+ {
+ if ((c1 = *infile++) == '\0')
+ break;
+
+ if (c1 == '\n' || (c1 = HEXCHAR(c1)) == -1)
+ {
+ /* ignore it and the rest of the buffer */
+ return -2;
+ }
+ else
+ {
+ do
+ {
+ if ((c2 = *infile++) == '\0')
+ {
+ c2 = -1;
+ break;
+ }
+ } while ((c2 = HEXCHAR(c2)) == -1);
+
+ if (c2 == -1)
+ break;
+ nchar++;
+ *(*outfile)++ = c1 << 4 | c2;
+ }
+ }
+ else
+ {
+ nchar++;
+ *(*outfile)++ = c1;
+ if (c1 == '\n')
+ break;
+ }
+ }
+ *(*outfile)++ = '\0';
+ if (nchar >= maxlen)
+ return (infile - b - 1);
+ return -1;
+}
+#endif /* MIME7TO8 */
diff --git a/usr/src/cmd/sendmail/src/parseaddr.c b/usr/src/cmd/sendmail/src/parseaddr.c
new file mode 100644
index 0000000000..58a8f73743
--- /dev/null
+++ b/usr/src/cmd/sendmail/src/parseaddr.c
@@ -0,0 +1,3237 @@
+/*
+ * Copyright (c) 1998-2005 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sendmail.h>
+
+SM_RCSID("@(#)$Id: parseaddr.c,v 8.381 2005/02/04 22:01:45 ca Exp $")
+
+static void allocaddr __P((ADDRESS *, int, char *, ENVELOPE *));
+static int callsubr __P((char**, int, ENVELOPE *));
+static char *map_lookup __P((STAB *, char *, char **, int *, ENVELOPE *));
+static ADDRESS *buildaddr __P((char **, ADDRESS *, int, ENVELOPE *));
+static bool hasctrlchar __P((register char *, bool, bool));
+
+/* replacement for illegal characters in addresses */
+#define BAD_CHAR_REPLACEMENT '?'
+
+/*
+** PARSEADDR -- Parse an address
+**
+** Parses an address and breaks it up into three parts: a
+** net to transmit the message on, the host to transmit it
+** to, and a user on that host. These are loaded into an
+** ADDRESS header with the values squirreled away if necessary.
+** The "user" part may not be a real user; the process may
+** just reoccur on that machine. For example, on a machine
+** with an arpanet connection, the address
+** csvax.bill@berkeley
+** will break up to a "user" of 'csvax.bill' and a host
+** of 'berkeley' -- to be transmitted over the arpanet.
+**
+** Parameters:
+** addr -- the address to parse.
+** a -- a pointer to the address descriptor buffer.
+** If NULL, an address will be created.
+** flags -- describe detail for parsing. See RF_ definitions
+** in sendmail.h.
+** delim -- the character to terminate the address, passed
+** to prescan.
+** delimptr -- if non-NULL, set to the location of the
+** delim character that was found.
+** e -- the envelope that will contain this address.
+** isrcpt -- true if the address denotes a recipient; false
+** indicates a sender.
+**
+** Returns:
+** A pointer to the address descriptor header (`a' if
+** `a' is non-NULL).
+** NULL on error.
+**
+** Side Effects:
+** e->e_to = addr
+*/
+
+/* following delimiters are inherent to the internal algorithms */
+#define DELIMCHARS "()<>,;\r\n" /* default word delimiters */
+
+ADDRESS *
+parseaddr(addr, a, flags, delim, delimptr, e, isrcpt)
+ char *addr;
+ register ADDRESS *a;
+ int flags;
+ int delim;
+ char **delimptr;
+ register ENVELOPE *e;
+ bool isrcpt;
+{
+ char **pvp;
+ auto char *delimptrbuf;
+ bool qup;
+ char pvpbuf[PSBUFSIZE];
+
+ /*
+ ** Initialize and prescan address.
+ */
+
+ e->e_to = addr;
+ if (tTd(20, 1))
+ sm_dprintf("\n--parseaddr(%s)\n", addr);
+
+ if (delimptr == NULL)
+ delimptr = &delimptrbuf;
+
+ pvp = prescan(addr, delim, pvpbuf, sizeof pvpbuf, delimptr, NULL, false);
+ if (pvp == NULL)
+ {
+ if (tTd(20, 1))
+ sm_dprintf("parseaddr-->NULL\n");
+ return NULL;
+ }
+
+ if (invalidaddr(addr, delim == '\0' ? NULL : *delimptr, isrcpt))
+ {
+ if (tTd(20, 1))
+ sm_dprintf("parseaddr-->bad address\n");
+ return NULL;
+ }
+
+ /*
+ ** Save addr if we are going to have to.
+ **
+ ** We have to do this early because there is a chance that
+ ** the map lookups in the rewriting rules could clobber
+ ** static memory somewhere.
+ */
+
+ if (bitset(RF_COPYPADDR, flags) && addr != NULL)
+ {
+ char savec = **delimptr;
+
+ if (savec != '\0')
+ **delimptr = '\0';
+ e->e_to = addr = sm_rpool_strdup_x(e->e_rpool, addr);
+ if (savec != '\0')
+ **delimptr = savec;
+ }
+
+ /*
+ ** Apply rewriting rules.
+ ** Ruleset 0 does basic parsing. It must resolve.
+ */
+
+ qup = false;
+ if (REWRITE(pvp, 3, e) == EX_TEMPFAIL)
+ qup = true;
+ if (REWRITE(pvp, 0, e) == EX_TEMPFAIL)
+ qup = true;
+
+ /*
+ ** Build canonical address from pvp.
+ */
+
+ a = buildaddr(pvp, a, flags, e);
+
+ if (hasctrlchar(a->q_user, isrcpt, true))
+ {
+ if (tTd(20, 1))
+ sm_dprintf("parseaddr-->bad q_user\n");
+
+ /*
+ ** Just mark the address as bad so DSNs work.
+ ** hasctrlchar() has to make sure that the address
+ ** has been sanitized, e.g., shortened.
+ */
+
+ a->q_state = QS_BADADDR;
+ }
+
+ /*
+ ** Make local copies of the host & user and then
+ ** transport them out.
+ */
+
+ allocaddr(a, flags, addr, e);
+ if (QS_IS_BADADDR(a->q_state))
+ {
+ /* weed out bad characters in the printable address too */
+ (void) hasctrlchar(a->q_paddr, isrcpt, false);
+ return a;
+ }
+
+ /*
+ ** Select a queue directory for recipient addresses.
+ ** This is done here and in split_across_queue_groups(),
+ ** but the latter applies to addresses after aliasing,
+ ** and only if splitting is done.
+ */
+
+ if ((a->q_qgrp == NOAQGRP || a->q_qgrp == ENVQGRP) &&
+ !bitset(RF_SENDERADDR|RF_HEADERADDR, flags) &&
+ OpMode != MD_INITALIAS)
+ {
+ int r;
+
+ /* call ruleset which should return a queue group name */
+ r = rscap(RS_QUEUEGROUP, a->q_user, NULL, e, &pvp, pvpbuf,
+ sizeof(pvpbuf));
+ if (r == EX_OK &&
+ pvp != NULL && pvp[0] != NULL &&
+ (pvp[0][0] & 0377) == CANONNET &&
+ pvp[1] != NULL && pvp[1][0] != '\0')
+ {
+ r = name2qid(pvp[1]);
+ if (r == NOQGRP && LogLevel > 10)
+ sm_syslog(LOG_INFO, NOQID,
+ "can't find queue group name %s, selection ignored",
+ pvp[1]);
+ if (tTd(20, 4) && r != NOQGRP)
+ sm_syslog(LOG_INFO, NOQID,
+ "queue group name %s -> %d",
+ pvp[1], r);
+ a->q_qgrp = r == NOQGRP ? ENVQGRP : r;
+ }
+ }
+
+ /*
+ ** If there was a parsing failure, mark it for queueing.
+ */
+
+ if (qup && OpMode != MD_INITALIAS)
+ {
+ char *msg = "Transient parse error -- message queued for future delivery";
+
+ if (e->e_sendmode == SM_DEFER)
+ msg = "Deferring message until queue run";
+ if (tTd(20, 1))
+ sm_dprintf("parseaddr: queuing message\n");
+ message(msg);
+ if (e->e_message == NULL && e->e_sendmode != SM_DEFER)
+ e->e_message = sm_rpool_strdup_x(e->e_rpool, msg);
+ a->q_state = QS_QUEUEUP;
+ a->q_status = "4.4.3";
+ }
+
+ /*
+ ** Compute return value.
+ */
+
+ if (tTd(20, 1))
+ {
+ sm_dprintf("parseaddr-->");
+ printaddr(sm_debug_file(), a, false);
+ }
+
+ return a;
+}
+/*
+** INVALIDADDR -- check for address containing characters used for macros
+**
+** Parameters:
+** addr -- the address to check.
+** delimptr -- if non-NULL: end of address to check, i.e.,
+** a pointer in the address string.
+** isrcpt -- true iff the address is for a recipient.
+**
+** Returns:
+** true -- if the address has characters that are reservered
+** for macros or is too long.
+** false -- otherwise.
+*/
+
+bool
+invalidaddr(addr, delimptr, isrcpt)
+ register char *addr;
+ char *delimptr;
+ bool isrcpt;
+{
+ bool result = false;
+ char savedelim = '\0';
+ char *b = addr;
+ int len = 0;
+
+ if (delimptr != NULL)
+ {
+ /* delimptr points to the end of the address to test */
+ savedelim = *delimptr;
+ if (savedelim != '\0') /* if that isn't '\0' already: */
+ *delimptr = '\0'; /* set it */
+ }
+ for (; *addr != '\0'; addr++)
+ {
+ if ((*addr & 0340) == 0200)
+ {
+ setstat(EX_USAGE);
+ result = true;
+ *addr = BAD_CHAR_REPLACEMENT;
+ }
+ if (++len > MAXNAME - 1)
+ {
+ char saved = *addr;
+
+ *addr = '\0';
+ usrerr("553 5.1.0 Address \"%s\" too long (%d bytes max)",
+ b, MAXNAME - 1);
+ *addr = saved;
+ result = true;
+ goto delim;
+ }
+ }
+ if (result)
+ {
+ if (isrcpt)
+ usrerr("501 5.1.3 8-bit character in mailbox address \"%s\"",
+ b);
+ else
+ usrerr("501 5.1.7 8-bit character in mailbox address \"%s\"",
+ b);
+ }
+delim:
+ if (delimptr != NULL && savedelim != '\0')
+ *delimptr = savedelim; /* restore old character at delimptr */
+ return result;
+}
+/*
+** HASCTRLCHAR -- check for address containing meta-characters
+**
+** Checks that the address contains no meta-characters, and contains
+** no "non-printable" characters unless they are quoted or escaped.
+** Quoted or escaped characters are literals.
+**
+** Parameters:
+** addr -- the address to check.
+** isrcpt -- true if the address is for a recipient; false
+** indicates a from.
+** complain -- true if an error should issued if the address
+** is invalid and should be "repaired".
+**
+** Returns:
+** true -- if the address has any "wierd" characters or
+** non-printable characters or if a quote is unbalanced.
+** false -- otherwise.
+*/
+
+static bool
+hasctrlchar(addr, isrcpt, complain)
+ register char *addr;
+ bool isrcpt, complain;
+{
+ bool quoted = false;
+ int len = 0;
+ char *result = NULL;
+ char *b = addr;
+
+ if (addr == NULL)
+ return false;
+ for (; *addr != '\0'; addr++)
+ {
+ if (++len > MAXNAME - 1)
+ {
+ if (complain)
+ {
+ (void) shorten_rfc822_string(b, MAXNAME - 1);
+ usrerr("553 5.1.0 Address \"%s\" too long (%d bytes max)",
+ b, MAXNAME - 1);
+ return true;
+ }
+ result = "too long";
+ }
+ if (!quoted && (*addr < 32 || *addr == 127))
+ {
+ result = "non-printable character";
+ *addr = BAD_CHAR_REPLACEMENT;
+ continue;
+ }
+ if (*addr == '"')
+ quoted = !quoted;
+ else if (*addr == '\\')
+ {
+ /* XXX Generic problem: no '\0' in strings. */
+ if (*++addr == '\0')
+ {
+ result = "trailing \\ character";
+ *--addr = BAD_CHAR_REPLACEMENT;
+ break;
+ }
+ }
+ if ((*addr & 0340) == 0200)
+ {
+ setstat(EX_USAGE);
+ result = "8-bit character";
+ *addr = BAD_CHAR_REPLACEMENT;
+ continue;
+ }
+ }
+ if (quoted)
+ result = "unbalanced quote"; /* unbalanced quote */
+ if (result != NULL && complain)
+ {
+ if (isrcpt)
+ usrerr("501 5.1.3 Syntax error in mailbox address \"%s\" (%s)",
+ b, result);
+ else
+ usrerr("501 5.1.7 Syntax error in mailbox address \"%s\" (%s)",
+ b, result);
+ }
+ return result != NULL;
+}
+/*
+** ALLOCADDR -- do local allocations of address on demand.
+**
+** Also lowercases the host name if requested.
+**
+** Parameters:
+** a -- the address to reallocate.
+** flags -- the copy flag (see RF_ definitions in sendmail.h
+** for a description).
+** paddr -- the printname of the address.
+** e -- envelope
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Copies portions of a into local buffers as requested.
+*/
+
+static void
+allocaddr(a, flags, paddr, e)
+ register ADDRESS *a;
+ int flags;
+ char *paddr;
+ ENVELOPE *e;
+{
+ if (tTd(24, 4))
+ sm_dprintf("allocaddr(flags=%x, paddr=%s)\n", flags, paddr);
+
+ a->q_paddr = paddr;
+
+ if (a->q_user == NULL)
+ a->q_user = "";
+ if (a->q_host == NULL)
+ a->q_host = "";
+
+ if (bitset(RF_COPYPARSE, flags))
+ {
+ a->q_host = sm_rpool_strdup_x(e->e_rpool, a->q_host);
+ if (a->q_user != a->q_paddr)
+ a->q_user = sm_rpool_strdup_x(e->e_rpool, a->q_user);
+ }
+
+ if (a->q_paddr == NULL)
+ a->q_paddr = sm_rpool_strdup_x(e->e_rpool, a->q_user);
+ a->q_qgrp = NOAQGRP;
+}
+/*
+** PRESCAN -- Prescan name and make it canonical
+**
+** Scans a name and turns it into a set of tokens. This process
+** deletes blanks and comments (in parentheses) (if the token type
+** for left paren is SPC).
+**
+** This routine knows about quoted strings and angle brackets.
+**
+** There are certain subtleties to this routine. The one that
+** comes to mind now is that backslashes on the ends of names
+** are silently stripped off; this is intentional. The problem
+** is that some versions of sndmsg (like at LBL) set the kill
+** character to something other than @ when reading addresses;
+** so people type "csvax.eric\@berkeley" -- which screws up the
+** berknet mailer.
+**
+** Parameters:
+** addr -- the name to chomp.
+** delim -- the delimiter for the address, normally
+** '\0' or ','; \0 is accepted in any case.
+** If '\t' then we are reading the .cf file.
+** pvpbuf -- place to put the saved text -- note that
+** the pointers are static.
+** pvpbsize -- size of pvpbuf.
+** delimptr -- if non-NULL, set to the location of the
+** terminating delimiter.
+** toktab -- if set, a token table to use for parsing.
+** If NULL, use the default table.
+** ignore -- if true, ignore unbalanced addresses
+**
+** Returns:
+** A pointer to a vector of tokens.
+** NULL on error.
+*/
+
+/* states and character types */
+#define OPR 0 /* operator */
+#define ATM 1 /* atom */
+#define QST 2 /* in quoted string */
+#define SPC 3 /* chewing up spaces */
+#define ONE 4 /* pick up one character */
+#define ILL 5 /* illegal character */
+
+#define NSTATES 6 /* number of states */
+#define TYPE 017 /* mask to select state type */
+
+/* meta bits for table */
+#define M 020 /* meta character; don't pass through */
+#define B 040 /* cause a break */
+#define MB M|B /* meta-break */
+
+static short StateTab[NSTATES][NSTATES] =
+{
+ /* oldst chtype> OPR ATM QST SPC ONE ILL */
+ /*OPR*/ { OPR|B, ATM|B, QST|B, SPC|MB, ONE|B, ILL|MB },
+ /*ATM*/ { OPR|B, ATM, QST|B, SPC|MB, ONE|B, ILL|MB },
+ /*QST*/ { QST, QST, OPR, QST, QST, QST },
+ /*SPC*/ { OPR, ATM, QST, SPC|M, ONE, ILL|MB },
+ /*ONE*/ { OPR, OPR, OPR, OPR, OPR, ILL|MB },
+ /*ILL*/ { OPR|B, ATM|B, QST|B, SPC|MB, ONE|B, ILL|M },
+};
+
+/* token type table -- it gets modified with $o characters */
+static unsigned char TokTypeTab[256] =
+{
+ /* nul soh stx etx eot enq ack bel bs ht nl vt np cr so si */
+ ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,SPC,SPC,SPC,SPC,SPC,ATM,ATM,
+ /* dle dc1 dc2 dc3 dc4 nak syn etb can em sub esc fs gs rs us */
+ ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM,
+ /* sp ! " # $ % & ' ( ) * + , - . / */
+ SPC,ATM,QST,ATM,ATM,ATM,ATM,ATM, SPC,SPC,ATM,ATM,ATM,ATM,ATM,ATM,
+ /* 0 1 2 3 4 5 6 7 8 9 : ; < = > ? */
+ ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM,
+ /* @ A B C D E F G H I J K L M N O */
+ ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM,
+ /* P Q R S T U V W X Y Z [ \ ] ^ _ */
+ ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM,
+ /* ` a b c d e f g h i j k l m n o */
+ ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM,
+ /* p q r s t u v w x y z { | } ~ del */
+ ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM,
+
+ /* nul soh stx etx eot enq ack bel bs ht nl vt np cr so si */
+ OPR,OPR,ONE,OPR,OPR,OPR,OPR,OPR, OPR,OPR,OPR,OPR,OPR,OPR,OPR,OPR,
+ /* dle dc1 dc2 dc3 dc4 nak syn etb can em sub esc fs gs rs us */
+ OPR,OPR,OPR,ONE,ONE,ONE,OPR,OPR, OPR,OPR,OPR,OPR,OPR,OPR,OPR,OPR,
+ /* sp ! " # $ % & ' ( ) * + , - . / */
+ ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM,
+ /* 0 1 2 3 4 5 6 7 8 9 : ; < = > ? */
+ ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM,
+ /* @ A B C D E F G H I J K L M N O */
+ ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM,
+ /* P Q R S T U V W X Y Z [ \ ] ^ _ */
+ ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM,
+ /* ` a b c d e f g h i j k l m n o */
+ ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM,
+ /* p q r s t u v w x y z { | } ~ del */
+ ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM,
+};
+
+/* token type table for MIME parsing */
+unsigned char MimeTokenTab[256] =
+{
+ /* nul soh stx etx eot enq ack bel bs ht nl vt np cr so si */
+ ILL,ILL,ILL,ILL,ILL,ILL,ILL,ILL, ILL,SPC,SPC,SPC,SPC,SPC,ILL,ILL,
+ /* dle dc1 dc2 dc3 dc4 nak syn etb can em sub esc fs gs rs us */
+ ILL,ILL,ILL,ILL,ILL,ILL,ILL,ILL, ILL,ILL,ILL,ILL,ILL,ILL,ILL,ILL,
+ /* sp ! " # $ % & ' ( ) * + , - . / */
+ SPC,ATM,QST,ATM,ATM,ATM,ATM,ATM, SPC,SPC,ATM,ATM,OPR,ATM,ATM,OPR,
+ /* 0 1 2 3 4 5 6 7 8 9 : ; < = > ? */
+ ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,ATM,OPR,OPR,OPR,OPR,OPR,OPR,
+ /* @ A B C D E F G H I J K L M N O */
+ OPR,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM,
+ /* P Q R S T U V W X Y Z [ \ ] ^ _ */
+ ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,ATM,ATM,OPR,OPR,OPR,ATM,ATM,
+ /* ` a b c d e f g h i j k l m n o */
+ ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM,
+ /* p q r s t u v w x y z { | } ~ del */
+ ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM,
+
+ /* nul soh stx etx eot enq ack bel bs ht nl vt np cr so si */
+ ILL,ILL,ILL,ILL,ILL,ILL,ILL,ILL, ILL,ILL,ILL,ILL,ILL,ILL,ILL,ILL,
+ /* dle dc1 dc2 dc3 dc4 nak syn etb can em sub esc fs gs rs us */
+ ILL,ILL,ILL,ILL,ILL,ILL,ILL,ILL, ILL,ILL,ILL,ILL,ILL,ILL,ILL,ILL,
+ /* sp ! " # $ % & ' ( ) * + , - . / */
+ ILL,ILL,ILL,ILL,ILL,ILL,ILL,ILL, ILL,ILL,ILL,ILL,ILL,ILL,ILL,ILL,
+ /* 0 1 2 3 4 5 6 7 8 9 : ; < = > ? */
+ ILL,ILL,ILL,ILL,ILL,ILL,ILL,ILL, ILL,ILL,ILL,ILL,ILL,ILL,ILL,ILL,
+ /* @ A B C D E F G H I J K L M N O */
+ ILL,ILL,ILL,ILL,ILL,ILL,ILL,ILL, ILL,ILL,ILL,ILL,ILL,ILL,ILL,ILL,
+ /* P Q R S T U V W X Y Z [ \ ] ^ _ */
+ ILL,ILL,ILL,ILL,ILL,ILL,ILL,ILL, ILL,ILL,ILL,ILL,ILL,ILL,ILL,ILL,
+ /* ` a b c d e f g h i j k l m n o */
+ ILL,ILL,ILL,ILL,ILL,ILL,ILL,ILL, ILL,ILL,ILL,ILL,ILL,ILL,ILL,ILL,
+ /* p q r s t u v w x y z { | } ~ del */
+ ILL,ILL,ILL,ILL,ILL,ILL,ILL,ILL, ILL,ILL,ILL,ILL,ILL,ILL,ILL,ILL,
+};
+
+/* token type table: don't strip comments */
+unsigned char TokTypeNoC[256] =
+{
+ /* nul soh stx etx eot enq ack bel bs ht nl vt np cr so si */
+ ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,SPC,SPC,SPC,SPC,SPC,ATM,ATM,
+ /* dle dc1 dc2 dc3 dc4 nak syn etb can em sub esc fs gs rs us */
+ ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM,
+ /* sp ! " # $ % & ' ( ) * + , - . / */
+ SPC,ATM,QST,ATM,ATM,ATM,ATM,ATM, OPR,OPR,ATM,ATM,ATM,ATM,ATM,ATM,
+ /* 0 1 2 3 4 5 6 7 8 9 : ; < = > ? */
+ ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM,
+ /* @ A B C D E F G H I J K L M N O */
+ ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM,
+ /* P Q R S T U V W X Y Z [ \ ] ^ _ */
+ ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM,
+ /* ` a b c d e f g h i j k l m n o */
+ ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM,
+ /* p q r s t u v w x y z { | } ~ del */
+ ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM,
+
+ /* nul soh stx etx eot enq ack bel bs ht nl vt np cr so si */
+ OPR,OPR,ONE,OPR,OPR,OPR,OPR,OPR, OPR,OPR,OPR,OPR,OPR,OPR,OPR,OPR,
+ /* dle dc1 dc2 dc3 dc4 nak syn etb can em sub esc fs gs rs us */
+ OPR,OPR,OPR,ONE,ONE,ONE,OPR,OPR, OPR,OPR,OPR,OPR,OPR,OPR,OPR,OPR,
+ /* sp ! " # $ % & ' ( ) * + , - . / */
+ ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM,
+ /* 0 1 2 3 4 5 6 7 8 9 : ; < = > ? */
+ ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM,
+ /* @ A B C D E F G H I J K L M N O */
+ ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM,
+ /* P Q R S T U V W X Y Z [ \ ] ^ _ */
+ ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM,
+ /* ` a b c d e f g h i j k l m n o */
+ ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM,
+ /* p q r s t u v w x y z { | } ~ del */
+ ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM,
+};
+
+
+#define NOCHAR (-1) /* signal nothing in lookahead token */
+
+char **
+prescan(addr, delim, pvpbuf, pvpbsize, delimptr, toktab, ignore)
+ char *addr;
+ int delim;
+ char pvpbuf[];
+ int pvpbsize;
+ char **delimptr;
+ unsigned char *toktab;
+ bool ignore;
+{
+ register char *p;
+ register char *q;
+ register int c;
+ char **avp;
+ bool bslashmode;
+ bool route_syntax;
+ int cmntcnt;
+ int anglecnt;
+ char *tok;
+ int state;
+ int newstate;
+ char *saveto = CurEnv->e_to;
+ static char *av[MAXATOM + 1];
+ static bool firsttime = true;
+
+ if (firsttime)
+ {
+ /* initialize the token type table */
+ char obuf[50];
+
+ firsttime = false;
+ if (OperatorChars == NULL)
+ {
+ if (ConfigLevel < 7)
+ OperatorChars = macvalue('o', CurEnv);
+ if (OperatorChars == NULL)
+ OperatorChars = ".:@[]";
+ }
+ expand(OperatorChars, obuf, sizeof obuf - sizeof DELIMCHARS,
+ CurEnv);
+ (void) sm_strlcat(obuf, DELIMCHARS, sizeof obuf);
+ for (p = obuf; *p != '\0'; p++)
+ {
+ if (TokTypeTab[*p & 0xff] == ATM)
+ TokTypeTab[*p & 0xff] = OPR;
+ if (TokTypeNoC[*p & 0xff] == ATM)
+ TokTypeNoC[*p & 0xff] = OPR;
+ }
+ }
+ if (toktab == NULL)
+ toktab = TokTypeTab;
+
+ /* make sure error messages don't have garbage on them */
+ errno = 0;
+
+ q = pvpbuf;
+ bslashmode = false;
+ route_syntax = false;
+ cmntcnt = 0;
+ anglecnt = 0;
+ avp = av;
+ state = ATM;
+ c = NOCHAR;
+ p = addr;
+ CurEnv->e_to = p;
+ if (tTd(22, 11))
+ {
+ sm_dprintf("prescan: ");
+ xputs(sm_debug_file(), p);
+ sm_dprintf("\n");
+ }
+
+ do
+ {
+ /* read a token */
+ tok = q;
+ for (;;)
+ {
+ /* store away any old lookahead character */
+ if (c != NOCHAR && !bslashmode)
+ {
+ /* see if there is room */
+ if (q >= &pvpbuf[pvpbsize - 5])
+ {
+ addrtoolong:
+ usrerr("553 5.1.1 Address too long");
+ if (strlen(addr) > MAXNAME)
+ addr[MAXNAME] = '\0';
+ returnnull:
+ if (delimptr != NULL)
+ {
+ if (p > addr)
+ --p;
+ *delimptr = p;
+ }
+ CurEnv->e_to = saveto;
+ return NULL;
+ }
+
+ /* squirrel it away */
+#if !ALLOW_255
+ if ((char) c == (char) -1 && !tTd(82, 101))
+ c &= 0x7f;
+#endif /* !ALLOW_255 */
+ *q++ = c;
+ }
+
+ /* read a new input character */
+ c = (*p++) & 0x00ff;
+ if (c == '\0')
+ {
+ /* diagnose and patch up bad syntax */
+ if (ignore)
+ break;
+ else if (state == QST)
+ {
+ usrerr("553 Unbalanced '\"'");
+ c = '"';
+ }
+ else if (cmntcnt > 0)
+ {
+ usrerr("553 Unbalanced '('");
+ c = ')';
+ }
+ else if (anglecnt > 0)
+ {
+ c = '>';
+ usrerr("553 Unbalanced '<'");
+ }
+ else
+ break;
+
+ p--;
+ }
+ else if (c == delim && cmntcnt <= 0 && state != QST)
+ {
+ if (anglecnt <= 0)
+ break;
+
+ /* special case for better error management */
+ if (delim == ',' && !route_syntax && !ignore)
+ {
+ usrerr("553 Unbalanced '<'");
+ c = '>';
+ p--;
+ }
+ }
+
+ if (tTd(22, 101))
+ sm_dprintf("c=%c, s=%d; ", c, state);
+
+ /* chew up special characters */
+ *q = '\0';
+ if (bslashmode)
+ {
+ bslashmode = false;
+
+ /* kludge \! for naive users */
+ if (cmntcnt > 0)
+ {
+ c = NOCHAR;
+ continue;
+ }
+ else if (c != '!' || state == QST)
+ {
+ /* see if there is room */
+ if (q >= &pvpbuf[pvpbsize - 5])
+ goto addrtoolong;
+ *q++ = '\\';
+ continue;
+ }
+ }
+
+ if (c == '\\')
+ {
+ bslashmode = true;
+ }
+ else if (state == QST)
+ {
+ /* EMPTY */
+ /* do nothing, just avoid next clauses */
+ }
+ else if (c == '(' && toktab['('] == SPC)
+ {
+ cmntcnt++;
+ c = NOCHAR;
+ }
+ else if (c == ')' && toktab['('] == SPC)
+ {
+ if (cmntcnt <= 0)
+ {
+ if (!ignore)
+ {
+ usrerr("553 Unbalanced ')'");
+ c = NOCHAR;
+ }
+ }
+ else
+ cmntcnt--;
+ }
+ else if (cmntcnt > 0)
+ {
+ c = NOCHAR;
+ }
+ else if (c == '<')
+ {
+ char *ptr = p;
+
+ anglecnt++;
+ while (isascii(*ptr) && isspace(*ptr))
+ ptr++;
+ if (*ptr == '@')
+ route_syntax = true;
+ }
+ else if (c == '>')
+ {
+ if (anglecnt <= 0)
+ {
+ if (!ignore)
+ {
+ usrerr("553 Unbalanced '>'");
+ c = NOCHAR;
+ }
+ }
+ else
+ anglecnt--;
+ route_syntax = false;
+ }
+ else if (delim == ' ' && isascii(c) && isspace(c))
+ c = ' ';
+
+ if (c == NOCHAR)
+ continue;
+
+ /* see if this is end of input */
+ if (c == delim && anglecnt <= 0 && state != QST)
+ break;
+
+ newstate = StateTab[state][toktab[c & 0xff]];
+ if (tTd(22, 101))
+ sm_dprintf("ns=%02o\n", newstate);
+ state = newstate & TYPE;
+ if (state == ILL)
+ {
+ if (isascii(c) && isprint(c))
+ usrerr("553 Illegal character %c", c);
+ else
+ usrerr("553 Illegal character 0x%02x",
+ c & 0x0ff);
+ }
+ if (bitset(M, newstate))
+ c = NOCHAR;
+ if (bitset(B, newstate))
+ break;
+ }
+
+ /* new token */
+ if (tok != q)
+ {
+ /* see if there is room */
+ if (q >= &pvpbuf[pvpbsize - 5])
+ goto addrtoolong;
+ *q++ = '\0';
+ if (tTd(22, 36))
+ {
+ sm_dprintf("tok=");
+ xputs(sm_debug_file(), tok);
+ sm_dprintf("\n");
+ }
+ if (avp >= &av[MAXATOM])
+ {
+ usrerr("553 5.1.0 prescan: too many tokens");
+ goto returnnull;
+ }
+ if (q - tok > MAXNAME)
+ {
+ usrerr("553 5.1.0 prescan: token too long");
+ goto returnnull;
+ }
+ *avp++ = tok;
+ }
+ } while (c != '\0' && (c != delim || anglecnt > 0));
+ *avp = NULL;
+ if (delimptr != NULL)
+ {
+ if (p > addr)
+ p--;
+ *delimptr = p;
+ }
+ if (tTd(22, 12))
+ {
+ sm_dprintf("prescan==>");
+ printav(sm_debug_file(), av);
+ }
+ CurEnv->e_to = saveto;
+ if (av[0] == NULL)
+ {
+ if (tTd(22, 1))
+ sm_dprintf("prescan: null leading token\n");
+ return NULL;
+ }
+ return av;
+}
+/*
+** REWRITE -- apply rewrite rules to token vector.
+**
+** This routine is an ordered production system. Each rewrite
+** rule has a LHS (called the pattern) and a RHS (called the
+** rewrite); 'rwr' points the the current rewrite rule.
+**
+** For each rewrite rule, 'avp' points the address vector we
+** are trying to match against, and 'pvp' points to the pattern.
+** If pvp points to a special match value (MATCHZANY, MATCHANY,
+** MATCHONE, MATCHCLASS, MATCHNCLASS) then the address in avp
+** matched is saved away in the match vector (pointed to by 'mvp').
+**
+** When a match between avp & pvp does not match, we try to
+** back out. If we back up over MATCHONE, MATCHCLASS, or MATCHNCLASS
+** we must also back out the match in mvp. If we reach a
+** MATCHANY or MATCHZANY we just extend the match and start
+** over again.
+**
+** When we finally match, we rewrite the address vector
+** and try over again.
+**
+** Parameters:
+** pvp -- pointer to token vector.
+** ruleset -- the ruleset to use for rewriting.
+** reclevel -- recursion level (to catch loops).
+** e -- the current envelope.
+** maxatom -- maximum length of buffer (usually MAXATOM)
+**
+** Returns:
+** A status code. If EX_TEMPFAIL, higher level code should
+** attempt recovery.
+**
+** Side Effects:
+** pvp is modified.
+*/
+
+struct match
+{
+ char **match_first; /* first token matched */
+ char **match_last; /* last token matched */
+ char **match_pattern; /* pointer to pattern */
+};
+
+int
+rewrite(pvp, ruleset, reclevel, e, maxatom)
+ char **pvp;
+ int ruleset;
+ int reclevel;
+ register ENVELOPE *e;
+ int maxatom;
+{
+ register char *ap; /* address pointer */
+ register char *rp; /* rewrite pointer */
+ register char *rulename; /* ruleset name */
+ register char *prefix;
+ register char **avp; /* address vector pointer */
+ register char **rvp; /* rewrite vector pointer */
+ register struct match *mlp; /* cur ptr into mlist */
+ register struct rewrite *rwr; /* pointer to current rewrite rule */
+ int ruleno; /* current rule number */
+ int rstat = EX_OK; /* return status */
+ int loopcount;
+ struct match mlist[MAXMATCH]; /* stores match on LHS */
+ char *npvp[MAXATOM + 1]; /* temporary space for rebuild */
+ char buf[MAXLINE];
+ char name[6];
+
+ /*
+ ** mlp will not exceed mlist[] because readcf enforces
+ ** the upper limit of entries when reading rulesets.
+ */
+
+ if (ruleset < 0 || ruleset >= MAXRWSETS)
+ {
+ syserr("554 5.3.5 rewrite: illegal ruleset number %d", ruleset);
+ return EX_CONFIG;
+ }
+ rulename = RuleSetNames[ruleset];
+ if (rulename == NULL)
+ {
+ (void) sm_snprintf(name, sizeof name, "%d", ruleset);
+ rulename = name;
+ }
+ if (OpMode == MD_TEST)
+ prefix = "";
+ else
+ prefix = "rewrite: ruleset ";
+ if (OpMode == MD_TEST)
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "%s%-16.16s input:", prefix, rulename);
+ printav(smioout, pvp);
+ }
+ else if (tTd(21, 1))
+ {
+ sm_dprintf("%s%-16.16s input:", prefix, rulename);
+ printav(sm_debug_file(), pvp);
+ }
+ if (reclevel++ > MaxRuleRecursion)
+ {
+ syserr("rewrite: excessive recursion (max %d), ruleset %s",
+ MaxRuleRecursion, rulename);
+ return EX_CONFIG;
+ }
+ if (pvp == NULL)
+ return EX_USAGE;
+ if (maxatom <= 0)
+ return EX_USAGE;
+
+ /*
+ ** Run through the list of rewrite rules, applying
+ ** any that match.
+ */
+
+ ruleno = 1;
+ loopcount = 0;
+ for (rwr = RewriteRules[ruleset]; rwr != NULL; )
+ {
+ int status;
+
+ /* if already canonical, quit now */
+ if (pvp[0] != NULL && (pvp[0][0] & 0377) == CANONNET)
+ break;
+
+ if (tTd(21, 12))
+ {
+ if (tTd(21, 15))
+ sm_dprintf("-----trying rule (line %d):",
+ rwr->r_line);
+ else
+ sm_dprintf("-----trying rule:");
+ printav(sm_debug_file(), rwr->r_lhs);
+ }
+
+ /* try to match on this rule */
+ mlp = mlist;
+ rvp = rwr->r_lhs;
+ avp = pvp;
+ if (++loopcount > 100)
+ {
+ syserr("554 5.3.5 Infinite loop in ruleset %s, rule %d",
+ rulename, ruleno);
+ if (tTd(21, 1))
+ {
+ sm_dprintf("workspace: ");
+ printav(sm_debug_file(), pvp);
+ }
+ break;
+ }
+
+ while ((ap = *avp) != NULL || *rvp != NULL)
+ {
+ rp = *rvp;
+ if (tTd(21, 35))
+ {
+ sm_dprintf("ADVANCE rp=");
+ xputs(sm_debug_file(), rp);
+ sm_dprintf(", ap=");
+ xputs(sm_debug_file(), ap);
+ sm_dprintf("\n");
+ }
+ if (rp == NULL)
+ {
+ /* end-of-pattern before end-of-address */
+ goto backup;
+ }
+ if (ap == NULL && (*rp & 0377) != MATCHZANY &&
+ (*rp & 0377) != MATCHZERO)
+ {
+ /* end-of-input with patterns left */
+ goto backup;
+ }
+
+ switch (*rp & 0377)
+ {
+ case MATCHCLASS:
+ /* match any phrase in a class */
+ mlp->match_pattern = rvp;
+ mlp->match_first = avp;
+ extendclass:
+ ap = *avp;
+ if (ap == NULL)
+ goto backup;
+ mlp->match_last = avp++;
+ cataddr(mlp->match_first, mlp->match_last,
+ buf, sizeof buf, '\0');
+ if (!wordinclass(buf, rp[1]))
+ {
+ if (tTd(21, 36))
+ {
+ sm_dprintf("EXTEND rp=");
+ xputs(sm_debug_file(), rp);
+ sm_dprintf(", ap=");
+ xputs(sm_debug_file(), ap);
+ sm_dprintf("\n");
+ }
+ goto extendclass;
+ }
+ if (tTd(21, 36))
+ sm_dprintf("CLMATCH\n");
+ mlp++;
+ break;
+
+ case MATCHNCLASS:
+ /* match any token not in a class */
+ if (wordinclass(ap, rp[1]))
+ goto backup;
+
+ /* FALLTHROUGH */
+
+ case MATCHONE:
+ case MATCHANY:
+ /* match exactly one token */
+ mlp->match_pattern = rvp;
+ mlp->match_first = avp;
+ mlp->match_last = avp++;
+ mlp++;
+ break;
+
+ case MATCHZANY:
+ /* match zero or more tokens */
+ mlp->match_pattern = rvp;
+ mlp->match_first = avp;
+ mlp->match_last = avp - 1;
+ mlp++;
+ break;
+
+ case MATCHZERO:
+ /* match zero tokens */
+ break;
+
+ case MACRODEXPAND:
+ /*
+ ** Match against run-time macro.
+ ** This algorithm is broken for the
+ ** general case (no recursive macros,
+ ** improper tokenization) but should
+ ** work for the usual cases.
+ */
+
+ ap = macvalue(rp[1], e);
+ mlp->match_first = avp;
+ if (tTd(21, 2))
+ sm_dprintf("rewrite: LHS $&{%s} => \"%s\"\n",
+ macname(rp[1]),
+ ap == NULL ? "(NULL)" : ap);
+
+ if (ap == NULL)
+ break;
+ while (*ap != '\0')
+ {
+ if (*avp == NULL ||
+ sm_strncasecmp(ap, *avp,
+ strlen(*avp)) != 0)
+ {
+ /* no match */
+ avp = mlp->match_first;
+ goto backup;
+ }
+ ap += strlen(*avp++);
+ }
+
+ /* match */
+ break;
+
+ default:
+ /* must have exact match */
+ if (sm_strcasecmp(rp, ap))
+ goto backup;
+ avp++;
+ break;
+ }
+
+ /* successful match on this token */
+ rvp++;
+ continue;
+
+ backup:
+ /* match failed -- back up */
+ while (--mlp >= mlist)
+ {
+ rvp = mlp->match_pattern;
+ rp = *rvp;
+ avp = mlp->match_last + 1;
+ ap = *avp;
+
+ if (tTd(21, 36))
+ {
+ sm_dprintf("BACKUP rp=");
+ xputs(sm_debug_file(), rp);
+ sm_dprintf(", ap=");
+ xputs(sm_debug_file(), ap);
+ sm_dprintf("\n");
+ }
+
+ if (ap == NULL)
+ {
+ /* run off the end -- back up again */
+ continue;
+ }
+ if ((*rp & 0377) == MATCHANY ||
+ (*rp & 0377) == MATCHZANY)
+ {
+ /* extend binding and continue */
+ mlp->match_last = avp++;
+ rvp++;
+ mlp++;
+ break;
+ }
+ if ((*rp & 0377) == MATCHCLASS)
+ {
+ /* extend binding and try again */
+ mlp->match_last = avp;
+ goto extendclass;
+ }
+ }
+
+ if (mlp < mlist)
+ {
+ /* total failure to match */
+ break;
+ }
+ }
+
+ /*
+ ** See if we successfully matched
+ */
+
+ if (mlp < mlist || *rvp != NULL)
+ {
+ if (tTd(21, 10))
+ sm_dprintf("----- rule fails\n");
+ rwr = rwr->r_next;
+ ruleno++;
+ loopcount = 0;
+ continue;
+ }
+
+ rvp = rwr->r_rhs;
+ if (tTd(21, 12))
+ {
+ sm_dprintf("-----rule matches:");
+ printav(sm_debug_file(), rvp);
+ }
+
+ rp = *rvp;
+ if (rp != NULL)
+ {
+ if ((*rp & 0377) == CANONUSER)
+ {
+ rvp++;
+ rwr = rwr->r_next;
+ ruleno++;
+ loopcount = 0;
+ }
+ else if ((*rp & 0377) == CANONHOST)
+ {
+ rvp++;
+ rwr = NULL;
+ }
+ }
+
+ /* substitute */
+ for (avp = npvp; *rvp != NULL; rvp++)
+ {
+ register struct match *m;
+ register char **pp;
+
+ rp = *rvp;
+ if ((*rp & 0377) == MATCHREPL)
+ {
+ /* substitute from LHS */
+ m = &mlist[rp[1] - '1'];
+ if (m < mlist || m >= mlp)
+ {
+ syserr("554 5.3.5 rewrite: ruleset %s: replacement $%c out of bounds",
+ rulename, rp[1]);
+ return EX_CONFIG;
+ }
+ if (tTd(21, 15))
+ {
+ sm_dprintf("$%c:", rp[1]);
+ pp = m->match_first;
+ while (pp <= m->match_last)
+ {
+ sm_dprintf(" %p=\"", *pp);
+ sm_dflush();
+ sm_dprintf("%s\"", *pp++);
+ }
+ sm_dprintf("\n");
+ }
+ pp = m->match_first;
+ while (pp <= m->match_last)
+ {
+ if (avp >= &npvp[maxatom])
+ goto toolong;
+ *avp++ = *pp++;
+ }
+ }
+ else
+ {
+ /* some sort of replacement */
+ if (avp >= &npvp[maxatom])
+ {
+ toolong:
+ syserr("554 5.3.0 rewrite: expansion too long");
+ if (LogLevel > 9)
+ sm_syslog(LOG_ERR, e->e_id,
+ "rewrite: expansion too long, ruleset=%s, ruleno=%d",
+ rulename, ruleno);
+ return EX_DATAERR;
+ }
+ if ((*rp & 0377) != MACRODEXPAND)
+ {
+ /* vanilla replacement */
+ *avp++ = rp;
+ }
+ else
+ {
+ /* $&{x} replacement */
+ char *mval = macvalue(rp[1], e);
+ char **xpvp;
+ int trsize = 0;
+ static size_t pvpb1_size = 0;
+ static char **pvpb1 = NULL;
+ char pvpbuf[PSBUFSIZE];
+
+ if (tTd(21, 2))
+ sm_dprintf("rewrite: RHS $&{%s} => \"%s\"\n",
+ macname(rp[1]),
+ mval == NULL ? "(NULL)" : mval);
+ if (mval == NULL || *mval == '\0')
+ continue;
+
+ /* save the remainder of the input */
+ for (xpvp = pvp; *xpvp != NULL; xpvp++)
+ trsize += sizeof *xpvp;
+ if ((size_t) trsize > pvpb1_size)
+ {
+ if (pvpb1 != NULL)
+ sm_free(pvpb1);
+ pvpb1 = (char **)
+ sm_pmalloc_x(trsize);
+ pvpb1_size = trsize;
+ }
+
+ memmove((char *) pvpb1,
+ (char *) pvp,
+ trsize);
+
+ /* scan the new replacement */
+ xpvp = prescan(mval, '\0', pvpbuf,
+ sizeof pvpbuf, NULL,
+ NULL, false);
+ if (xpvp == NULL)
+ {
+ /* prescan pre-printed error */
+ return EX_DATAERR;
+ }
+
+ /* insert it into the output stream */
+ while (*xpvp != NULL)
+ {
+ if (tTd(21, 19))
+ sm_dprintf(" ... %s\n",
+ *xpvp);
+ *avp++ = sm_rpool_strdup_x(
+ e->e_rpool, *xpvp);
+ if (avp >= &npvp[maxatom])
+ goto toolong;
+ xpvp++;
+ }
+ if (tTd(21, 19))
+ sm_dprintf(" ... DONE\n");
+
+ /* restore the old trailing input */
+ memmove((char *) pvp,
+ (char *) pvpb1,
+ trsize);
+ }
+ }
+ }
+ *avp++ = NULL;
+
+ /*
+ ** Check for any hostname/keyword lookups.
+ */
+
+ for (rvp = npvp; *rvp != NULL; rvp++)
+ {
+ char **hbrvp;
+ char **xpvp;
+ int trsize;
+ char *replac;
+ int endtoken;
+ STAB *map;
+ char *mapname;
+ char **key_rvp;
+ char **arg_rvp;
+ char **default_rvp;
+ char cbuf[MAXNAME + 1];
+ char *pvpb1[MAXATOM + 1];
+ char *argvect[MAX_MAP_ARGS];
+ char pvpbuf[PSBUFSIZE];
+ char *nullpvp[1];
+
+ if ((**rvp & 0377) != HOSTBEGIN &&
+ (**rvp & 0377) != LOOKUPBEGIN)
+ continue;
+
+ /*
+ ** Got a hostname/keyword lookup.
+ **
+ ** This could be optimized fairly easily.
+ */
+
+ hbrvp = rvp;
+ if ((**rvp & 0377) == HOSTBEGIN)
+ {
+ endtoken = HOSTEND;
+ mapname = "host";
+ }
+ else
+ {
+ endtoken = LOOKUPEND;
+ mapname = *++rvp;
+ if (mapname == NULL)
+ syserr("554 5.3.0 rewrite: missing mapname");
+ }
+ map = stab(mapname, ST_MAP, ST_FIND);
+ if (map == NULL)
+ syserr("554 5.3.0 rewrite: map %s not found",
+ mapname);
+
+ /* extract the match part */
+ key_rvp = ++rvp;
+ if (key_rvp == NULL)
+ syserr("554 5.3.0 rewrite: missing key for map %s",
+ mapname);
+ default_rvp = NULL;
+ arg_rvp = argvect;
+ xpvp = NULL;
+ replac = pvpbuf;
+ while (*rvp != NULL && (**rvp & 0377) != endtoken)
+ {
+ int nodetype = **rvp & 0377;
+
+ if (nodetype != CANONHOST &&
+ nodetype != CANONUSER)
+ {
+ rvp++;
+ continue;
+ }
+
+ *rvp++ = NULL;
+
+ if (xpvp != NULL)
+ {
+ cataddr(xpvp, NULL, replac,
+ &pvpbuf[sizeof pvpbuf] - replac,
+ '\0');
+ if (arg_rvp <
+ &argvect[MAX_MAP_ARGS - 1])
+ *++arg_rvp = replac;
+ replac += strlen(replac) + 1;
+ xpvp = NULL;
+ }
+ switch (nodetype)
+ {
+ case CANONHOST:
+ xpvp = rvp;
+ break;
+
+ case CANONUSER:
+ default_rvp = rvp;
+ break;
+ }
+ }
+ if (*rvp != NULL)
+ *rvp++ = NULL;
+ if (xpvp != NULL)
+ {
+ cataddr(xpvp, NULL, replac,
+ &pvpbuf[sizeof pvpbuf] - replac,
+ '\0');
+ if (arg_rvp < &argvect[MAX_MAP_ARGS - 1])
+ *++arg_rvp = replac;
+ }
+ if (arg_rvp >= &argvect[MAX_MAP_ARGS - 1])
+ argvect[MAX_MAP_ARGS - 1] = NULL;
+ else
+ *++arg_rvp = NULL;
+
+ /* save the remainder of the input string */
+ trsize = (int) (avp - rvp + 1) * sizeof *rvp;
+ memmove((char *) pvpb1, (char *) rvp, trsize);
+
+ /* look it up */
+ cataddr(key_rvp, NULL, cbuf, sizeof cbuf,
+ map == NULL ? '\0' : map->s_map.map_spacesub);
+ argvect[0] = cbuf;
+ replac = map_lookup(map, cbuf, argvect, &rstat, e);
+
+ /* if no replacement, use default */
+ if (replac == NULL && default_rvp != NULL)
+ {
+ /* create the default */
+ cataddr(default_rvp, NULL, cbuf, sizeof cbuf, '\0');
+ replac = cbuf;
+ }
+
+ if (replac == NULL)
+ {
+ xpvp = key_rvp;
+ }
+ else if (*replac == '\0')
+ {
+ /* null replacement */
+ nullpvp[0] = NULL;
+ xpvp = nullpvp;
+ }
+ else
+ {
+ /* scan the new replacement */
+ xpvp = prescan(replac, '\0', pvpbuf,
+ sizeof pvpbuf, NULL, NULL, false);
+ if (xpvp == NULL)
+ {
+ /* prescan already printed error */
+ return EX_DATAERR;
+ }
+ }
+
+ /* append it to the token list */
+ for (avp = hbrvp; *xpvp != NULL; xpvp++)
+ {
+ *avp++ = sm_rpool_strdup_x(e->e_rpool, *xpvp);
+ if (avp >= &npvp[maxatom])
+ goto toolong;
+ }
+
+ /* restore the old trailing information */
+ rvp = avp - 1;
+ for (xpvp = pvpb1; (*avp++ = *xpvp++) != NULL; )
+ if (avp >= &npvp[maxatom])
+ goto toolong;
+ }
+
+ /*
+ ** Check for subroutine calls.
+ */
+
+ status = callsubr(npvp, reclevel, e);
+ if (rstat == EX_OK || status == EX_TEMPFAIL)
+ rstat = status;
+
+ /* copy vector back into original space. */
+ for (avp = npvp; *avp++ != NULL;)
+ continue;
+ memmove((char *) pvp, (char *) npvp,
+ (int) (avp - npvp) * sizeof *avp);
+
+ if (tTd(21, 4))
+ {
+ sm_dprintf("rewritten as:");
+ printav(sm_debug_file(), pvp);
+ }
+ }
+
+ if (OpMode == MD_TEST)
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "%s%-16.16s returns:", prefix, rulename);
+ printav(smioout, pvp);
+ }
+ else if (tTd(21, 1))
+ {
+ sm_dprintf("%s%-16.16s returns:", prefix, rulename);
+ printav(sm_debug_file(), pvp);
+ }
+ return rstat;
+}
+/*
+** CALLSUBR -- call subroutines in rewrite vector
+**
+** Parameters:
+** pvp -- pointer to token vector.
+** reclevel -- the current recursion level.
+** e -- the current envelope.
+**
+** Returns:
+** The status from the subroutine call.
+**
+** Side Effects:
+** pvp is modified.
+*/
+
+static int
+callsubr(pvp, reclevel, e)
+ char **pvp;
+ int reclevel;
+ ENVELOPE *e;
+{
+ char **avp;
+ register int i;
+ int subr, j;
+ int nsubr;
+ int status;
+ int rstat = EX_OK;
+#define MAX_SUBR 16
+ int subrnumber[MAX_SUBR];
+ int subrindex[MAX_SUBR];
+
+ nsubr = 0;
+
+ /*
+ ** Look for subroutine calls in pvp, collect them into subr*[]
+ ** We will perform the calls in the next loop, because we will
+ ** call the "last" subroutine first to avoid recursive calls
+ ** and too much copying.
+ */
+
+ for (avp = pvp, j = 0; *avp != NULL; avp++, j++)
+ {
+ if ((**avp & 0377) == CALLSUBR && avp[1] != NULL)
+ {
+ stripquotes(avp[1]);
+ subr = strtorwset(avp[1], NULL, ST_FIND);
+ if (subr < 0)
+ {
+ syserr("554 5.3.5 Unknown ruleset %s", avp[1]);
+ return EX_CONFIG;
+ }
+
+ /*
+ ** XXX instead of doing this we could optimize
+ ** the rules after reading them: just remove
+ ** calls to empty rulesets
+ */
+
+ /* subroutine is an empty ruleset? don't call it */
+ if (RewriteRules[subr] == NULL)
+ {
+ if (tTd(21, 3))
+ sm_dprintf("-----skip subr %s (%d)\n",
+ avp[1], subr);
+ for (i = 2; avp[i] != NULL; i++)
+ avp[i - 2] = avp[i];
+ avp[i - 2] = NULL;
+ continue;
+ }
+ if (++nsubr >= MAX_SUBR)
+ {
+ syserr("554 5.3.0 Too many subroutine calls (%d max)",
+ MAX_SUBR);
+ return EX_CONFIG;
+ }
+ subrnumber[nsubr] = subr;
+ subrindex[nsubr] = j;
+ }
+ }
+
+ /*
+ ** Perform the actual subroutines calls, "last" one first, i.e.,
+ ** go from the right to the left through all calls,
+ ** do the rewriting in place.
+ */
+
+ for (; nsubr > 0; nsubr--)
+ {
+ subr = subrnumber[nsubr];
+ avp = pvp + subrindex[nsubr];
+
+ /* remove the subroutine call and name */
+ for (i = 2; avp[i] != NULL; i++)
+ avp[i - 2] = avp[i];
+ avp[i - 2] = NULL;
+
+ /*
+ ** Now we need to call the ruleset specified for
+ ** the subroutine. We can do this in place since
+ ** we call the "last" subroutine first.
+ */
+
+ status = rewrite(avp, subr, reclevel, e,
+ MAXATOM - subrindex[nsubr]);
+ if (status != EX_OK && status != EX_TEMPFAIL)
+ return status;
+ if (rstat == EX_OK || status == EX_TEMPFAIL)
+ rstat = status;
+ }
+ return rstat;
+}
+/*
+** MAP_LOOKUP -- do lookup in map
+**
+** Parameters:
+** smap -- the map to use for the lookup.
+** key -- the key to look up.
+** argvect -- arguments to pass to the map lookup.
+** pstat -- a pointer to an integer in which to store the
+** status from the lookup.
+** e -- the current envelope.
+**
+** Returns:
+** The result of the lookup.
+** NULL -- if there was no data for the given key.
+*/
+
+static char *
+map_lookup(smap, key, argvect, pstat, e)
+ STAB *smap;
+ char key[];
+ char **argvect;
+ int *pstat;
+ ENVELOPE *e;
+{
+ auto int status = EX_OK;
+ MAP *map;
+ char *replac;
+
+ if (smap == NULL)
+ return NULL;
+
+ map = &smap->s_map;
+ DYNOPENMAP(map);
+
+ if (e->e_sendmode == SM_DEFER &&
+ bitset(MF_DEFER, map->map_mflags))
+ {
+ /* don't do any map lookups */
+ if (tTd(60, 1))
+ sm_dprintf("map_lookup(%s, %s) => DEFERRED\n",
+ smap->s_name, key);
+ *pstat = EX_TEMPFAIL;
+ return NULL;
+ }
+
+ if (!bitset(MF_KEEPQUOTES, map->map_mflags))
+ stripquotes(key);
+
+ if (tTd(60, 1))
+ {
+ sm_dprintf("map_lookup(%s, %s", smap->s_name, key);
+ if (tTd(60, 5))
+ {
+ int i;
+
+ for (i = 0; argvect[i] != NULL; i++)
+ sm_dprintf(", %%%d=%s", i, argvect[i]);
+ }
+ sm_dprintf(") => ");
+ }
+ replac = (*map->map_class->map_lookup)(map, key, argvect, &status);
+ if (tTd(60, 1))
+ sm_dprintf("%s (%d)\n",
+ replac != NULL ? replac : "NOT FOUND",
+ status);
+
+ /* should recover if status == EX_TEMPFAIL */
+ if (status == EX_TEMPFAIL && !bitset(MF_NODEFER, map->map_mflags))
+ {
+ *pstat = EX_TEMPFAIL;
+ if (tTd(60, 1))
+ sm_dprintf("map_lookup(%s, %s) tempfail: errno=%d\n",
+ smap->s_name, key, errno);
+ if (e->e_message == NULL)
+ {
+ char mbuf[320];
+
+ (void) sm_snprintf(mbuf, sizeof mbuf,
+ "%.80s map: lookup (%s): deferred",
+ smap->s_name,
+ shortenstring(key, MAXSHORTSTR));
+ e->e_message = sm_rpool_strdup_x(e->e_rpool, mbuf);
+ }
+ }
+ if (status == EX_TEMPFAIL && map->map_tapp != NULL)
+ {
+ size_t i = strlen(key) + strlen(map->map_tapp) + 1;
+ static char *rwbuf = NULL;
+ static size_t rwbuflen = 0;
+
+ if (i > rwbuflen)
+ {
+ if (rwbuf != NULL)
+ sm_free(rwbuf);
+ rwbuflen = i;
+ rwbuf = (char *) sm_pmalloc_x(rwbuflen);
+ }
+ (void) sm_strlcpyn(rwbuf, rwbuflen, 2, key, map->map_tapp);
+ if (tTd(60, 4))
+ sm_dprintf("map_lookup tempfail: returning \"%s\"\n",
+ rwbuf);
+ return rwbuf;
+ }
+ return replac;
+}
+/*
+** INITERRMAILERS -- initialize error and discard mailers
+**
+** Parameters:
+** none.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** initializes error and discard mailers.
+*/
+
+static MAILER discardmailer;
+static MAILER errormailer;
+static char *discardargv[] = { "DISCARD", NULL };
+static char *errorargv[] = { "ERROR", NULL };
+
+void
+initerrmailers()
+{
+ if (discardmailer.m_name == NULL)
+ {
+ /* initialize the discard mailer */
+ discardmailer.m_name = "*discard*";
+ discardmailer.m_mailer = "DISCARD";
+ discardmailer.m_argv = discardargv;
+ }
+ if (errormailer.m_name == NULL)
+ {
+ /* initialize the bogus mailer */
+ errormailer.m_name = "*error*";
+ errormailer.m_mailer = "ERROR";
+ errormailer.m_argv = errorargv;
+ }
+}
+/*
+** BUILDADDR -- build address from token vector.
+**
+** Parameters:
+** tv -- token vector.
+** a -- pointer to address descriptor to fill.
+** If NULL, one will be allocated.
+** flags -- info regarding whether this is a sender or
+** a recipient.
+** e -- the current envelope.
+**
+** Returns:
+** NULL if there was an error.
+** 'a' otherwise.
+**
+** Side Effects:
+** fills in 'a'
+*/
+
+static struct errcodes
+{
+ char *ec_name; /* name of error code */
+ int ec_code; /* numeric code */
+} ErrorCodes[] =
+{
+ { "usage", EX_USAGE },
+ { "nouser", EX_NOUSER },
+ { "nohost", EX_NOHOST },
+ { "unavailable", EX_UNAVAILABLE },
+ { "software", EX_SOFTWARE },
+ { "tempfail", EX_TEMPFAIL },
+ { "protocol", EX_PROTOCOL },
+ { "config", EX_CONFIG },
+ { NULL, EX_UNAVAILABLE }
+};
+
+static ADDRESS *
+buildaddr(tv, a, flags, e)
+ register char **tv;
+ register ADDRESS *a;
+ int flags;
+ register ENVELOPE *e;
+{
+ bool tempfail = false;
+ int maxatom;
+ struct mailer **mp;
+ register struct mailer *m;
+ register char *p;
+ char *mname;
+ char **hostp;
+ char hbuf[MAXNAME + 1];
+ static char ubuf[MAXNAME + 2];
+
+ if (tTd(24, 5))
+ {
+ sm_dprintf("buildaddr, flags=%x, tv=", flags);
+ printav(sm_debug_file(), tv);
+ }
+
+ maxatom = MAXATOM;
+ if (a == NULL)
+ a = (ADDRESS *) sm_rpool_malloc_x(e->e_rpool, sizeof *a);
+ memset((char *) a, '\0', sizeof *a);
+ hbuf[0] = '\0';
+
+ /* set up default error return flags */
+ a->q_flags |= DefaultNotify;
+
+ /* figure out what net/mailer to use */
+ if (*tv == NULL || (**tv & 0377) != CANONNET)
+ {
+ syserr("554 5.3.5 buildaddr: no mailer in parsed address");
+badaddr:
+ /*
+ ** ExitStat may have been set by an earlier map open
+ ** failure (to a permanent error (EX_OSERR) in syserr())
+ ** so we also need to check if this particular $#error
+ ** return wanted a 4XX failure.
+ **
+ ** XXX the real fix is probably to set ExitStat correctly,
+ ** i.e., to EX_TEMPFAIL if the map open is just a temporary
+ ** error.
+ */
+
+ if (ExitStat == EX_TEMPFAIL || tempfail)
+ a->q_state = QS_QUEUEUP;
+ else
+ {
+ a->q_state = QS_BADADDR;
+ a->q_mailer = &errormailer;
+ }
+ return a;
+ }
+ mname = *++tv;
+ --maxatom;
+
+ /* extract host and user portions */
+ if (*++tv != NULL && (**tv & 0377) == CANONHOST)
+ {
+ hostp = ++tv;
+ --maxatom;
+ }
+ else
+ hostp = NULL;
+ --maxatom;
+ while (*tv != NULL && (**tv & 0377) != CANONUSER)
+ {
+ tv++;
+ --maxatom;
+ }
+ if (*tv == NULL)
+ {
+ syserr("554 5.3.5 buildaddr: no user");
+ goto badaddr;
+ }
+ if (tv == hostp)
+ hostp = NULL;
+ else if (hostp != NULL)
+ cataddr(hostp, tv - 1, hbuf, sizeof hbuf, '\0');
+ cataddr(++tv, NULL, ubuf, sizeof ubuf, ' ');
+ --maxatom;
+
+ /* save away the host name */
+ if (sm_strcasecmp(mname, "error") == 0)
+ {
+ /* Set up triplet for use by -bv */
+ a->q_mailer = &errormailer;
+ a->q_user = sm_rpool_strdup_x(e->e_rpool, ubuf);
+ /* XXX wrong place? */
+
+ if (hostp != NULL)
+ {
+ register struct errcodes *ep;
+
+ a->q_host = sm_rpool_strdup_x(e->e_rpool, hbuf);
+ if (strchr(hbuf, '.') != NULL)
+ {
+ a->q_status = sm_rpool_strdup_x(e->e_rpool,
+ hbuf);
+ setstat(dsntoexitstat(hbuf));
+ }
+ else if (isascii(hbuf[0]) && isdigit(hbuf[0]))
+ {
+ setstat(atoi(hbuf));
+ }
+ else
+ {
+ for (ep = ErrorCodes; ep->ec_name != NULL; ep++)
+ if (sm_strcasecmp(ep->ec_name, hbuf) == 0)
+ break;
+ setstat(ep->ec_code);
+ }
+ }
+ else
+ {
+ a->q_host = NULL;
+ setstat(EX_UNAVAILABLE);
+ }
+ stripquotes(ubuf);
+ if (ISSMTPCODE(ubuf) && ubuf[3] == ' ')
+ {
+ char fmt[16];
+ int off;
+
+ if ((off = isenhsc(ubuf + 4, ' ')) > 0)
+ {
+ ubuf[off + 4] = '\0';
+ off += 5;
+ }
+ else
+ {
+ off = 4;
+ ubuf[3] = '\0';
+ }
+ (void) sm_strlcpyn(fmt, sizeof fmt, 2, ubuf, " %s");
+ if (off > 4)
+ usrerr(fmt, ubuf + off);
+ else if (isenhsc(hbuf, '\0') > 0)
+ usrerrenh(hbuf, fmt, ubuf + off);
+ else
+ usrerr(fmt, ubuf + off);
+ /* XXX ubuf[off - 1] = ' '; */
+ if (ubuf[0] == '4')
+ tempfail = true;
+ }
+ else
+ {
+ usrerr("553 5.3.0 %s", ubuf);
+ }
+ goto badaddr;
+ }
+
+ for (mp = Mailer; (m = *mp++) != NULL; )
+ {
+ if (sm_strcasecmp(m->m_name, mname) == 0)
+ break;
+ }
+ if (m == NULL)
+ {
+ syserr("554 5.3.5 buildaddr: unknown mailer %s", mname);
+ goto badaddr;
+ }
+ a->q_mailer = m;
+
+ /* figure out what host (if any) */
+ if (hostp == NULL)
+ {
+ if (!bitnset(M_LOCALMAILER, m->m_flags))
+ {
+ syserr("554 5.3.5 buildaddr: no host");
+ goto badaddr;
+ }
+ a->q_host = NULL;
+ }
+ else
+ a->q_host = sm_rpool_strdup_x(e->e_rpool, hbuf);
+
+ /* figure out the user */
+ p = ubuf;
+ if (bitnset(M_CHECKUDB, m->m_flags) && *p == '@')
+ {
+ p++;
+ tv++;
+ --maxatom;
+ a->q_flags |= QNOTREMOTE;
+ }
+
+ /* do special mapping for local mailer */
+ if (*p == '"')
+ p++;
+ if (*p == '|' && bitnset(M_CHECKPROG, m->m_flags))
+ a->q_mailer = m = ProgMailer;
+ else if (*p == '/' && bitnset(M_CHECKFILE, m->m_flags))
+ a->q_mailer = m = FileMailer;
+ else if (*p == ':' && bitnset(M_CHECKINCLUDE, m->m_flags))
+ {
+ /* may be :include: */
+ stripquotes(ubuf);
+ if (sm_strncasecmp(ubuf, ":include:", 9) == 0)
+ {
+ /* if :include:, don't need further rewriting */
+ a->q_mailer = m = InclMailer;
+ a->q_user = sm_rpool_strdup_x(e->e_rpool, &ubuf[9]);
+ return a;
+ }
+ }
+
+ /* rewrite according recipient mailer rewriting rules */
+ macdefine(&e->e_macro, A_PERM, 'h', a->q_host);
+
+ if (ConfigLevel >= 10 ||
+ !bitset(RF_SENDERADDR|RF_HEADERADDR, flags))
+ {
+ /* sender addresses done later */
+ (void) rewrite(tv, 2, 0, e, maxatom);
+ if (m->m_re_rwset > 0)
+ (void) rewrite(tv, m->m_re_rwset, 0, e, maxatom);
+ }
+ (void) rewrite(tv, 4, 0, e, maxatom);
+
+ /* save the result for the command line/RCPT argument */
+ cataddr(tv, NULL, ubuf, sizeof ubuf, '\0');
+ a->q_user = sm_rpool_strdup_x(e->e_rpool, ubuf);
+
+ /*
+ ** Do mapping to lower case as requested by mailer
+ */
+
+ if (a->q_host != NULL && !bitnset(M_HST_UPPER, m->m_flags))
+ makelower(a->q_host);
+ if (!bitnset(M_USR_UPPER, m->m_flags))
+ makelower(a->q_user);
+
+ if (tTd(24, 6))
+ {
+ sm_dprintf("buildaddr => ");
+ printaddr(sm_debug_file(), a, false);
+ }
+ return a;
+}
+
+/*
+** CATADDR -- concatenate pieces of addresses (putting in <LWSP> subs)
+**
+** Parameters:
+** pvp -- parameter vector to rebuild.
+** evp -- last parameter to include. Can be NULL to
+** use entire pvp.
+** buf -- buffer to build the string into.
+** sz -- size of buf.
+** spacesub -- the space separator character; if '\0',
+** use SpaceSub.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Destroys buf.
+*/
+
+void
+cataddr(pvp, evp, buf, sz, spacesub)
+ char **pvp;
+ char **evp;
+ char *buf;
+ register int sz;
+ int spacesub;
+{
+ bool oatomtok = false;
+ bool natomtok = false;
+ register int i;
+ register char *p;
+
+ if (sz <= 0)
+ return;
+
+ if (spacesub == '\0')
+ spacesub = SpaceSub;
+
+ if (pvp == NULL)
+ {
+ *buf = '\0';
+ return;
+ }
+ p = buf;
+ sz -= 2;
+ while (*pvp != NULL && sz > 0)
+ {
+ natomtok = (TokTypeTab[**pvp & 0xff] == ATM);
+ if (oatomtok && natomtok)
+ {
+ *p++ = spacesub;
+ if (--sz <= 0)
+ break;
+ }
+ i = sm_strlcpy(p, *pvp, sz);
+ sz -= i;
+ if (sz <= 0)
+ break;
+ oatomtok = natomtok;
+ p += i;
+ if (pvp++ == evp)
+ break;
+ }
+
+#if 0
+ /*
+ ** Silently truncate long strings: even though this doesn't
+ ** seem like a good idea it is necessary because header checks
+ ** send the whole header value to rscheck() and hence rewrite().
+ ** The latter however sometimes uses a "short" buffer (e.g.,
+ ** cbuf[MAXNAME + 1]) to call cataddr() which then triggers this
+ ** error function. One possible fix to the problem is to pass
+ ** flags to rscheck() and rewrite() to distinguish the various
+ ** calls and only trigger the error if necessary. For now just
+ ** undo the change from 8.13.0.
+ */
+
+ if (sz <= 0)
+ usrerr("cataddr: string too long");
+#endif
+ *p = '\0';
+}
+/*
+** SAMEADDR -- Determine if two addresses are the same
+**
+** This is not just a straight comparison -- if the mailer doesn't
+** care about the host we just ignore it, etc.
+**
+** Parameters:
+** a, b -- pointers to the internal forms to compare.
+**
+** Returns:
+** true -- they represent the same mailbox.
+** false -- they don't.
+**
+** Side Effects:
+** none.
+*/
+
+bool
+sameaddr(a, b)
+ register ADDRESS *a;
+ register ADDRESS *b;
+{
+ register ADDRESS *ca, *cb;
+
+ /* if they don't have the same mailer, forget it */
+ if (a->q_mailer != b->q_mailer)
+ return false;
+
+ /* if the user isn't the same, we can drop out */
+ if (strcmp(a->q_user, b->q_user) != 0)
+ return false;
+
+ /* if we have good uids for both but they differ, these are different */
+ if (a->q_mailer == ProgMailer)
+ {
+ ca = getctladdr(a);
+ cb = getctladdr(b);
+ if (ca != NULL && cb != NULL &&
+ bitset(QGOODUID, ca->q_flags & cb->q_flags) &&
+ ca->q_uid != cb->q_uid)
+ return false;
+ }
+
+ /* otherwise compare hosts (but be careful for NULL ptrs) */
+ if (a->q_host == b->q_host)
+ {
+ /* probably both null pointers */
+ return true;
+ }
+ if (a->q_host == NULL || b->q_host == NULL)
+ {
+ /* only one is a null pointer */
+ return false;
+ }
+ if (strcmp(a->q_host, b->q_host) != 0)
+ return false;
+
+ return true;
+}
+/*
+** PRINTADDR -- print address (for debugging)
+**
+** Parameters:
+** a -- the address to print
+** follow -- follow the q_next chain.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** none.
+*/
+
+struct qflags
+{
+ char *qf_name;
+ unsigned long qf_bit;
+};
+
+static struct qflags AddressFlags[] =
+{
+ { "QGOODUID", QGOODUID },
+ { "QPRIMARY", QPRIMARY },
+ { "QNOTREMOTE", QNOTREMOTE },
+ { "QSELFREF", QSELFREF },
+ { "QBOGUSSHELL", QBOGUSSHELL },
+ { "QUNSAFEADDR", QUNSAFEADDR },
+ { "QPINGONSUCCESS", QPINGONSUCCESS },
+ { "QPINGONFAILURE", QPINGONFAILURE },
+ { "QPINGONDELAY", QPINGONDELAY },
+ { "QHASNOTIFY", QHASNOTIFY },
+ { "QRELAYED", QRELAYED },
+ { "QEXPANDED", QEXPANDED },
+ { "QDELIVERED", QDELIVERED },
+ { "QDELAYED", QDELAYED },
+ { "QTHISPASS", QTHISPASS },
+ { "QRCPTOK", QRCPTOK },
+ { NULL, 0 }
+};
+
+void
+printaddr(fp, a, follow)
+ SM_FILE_T *fp;
+ register ADDRESS *a;
+ bool follow;
+{
+ register MAILER *m;
+ MAILER pseudomailer;
+ register struct qflags *qfp;
+ bool firstone;
+
+ if (a == NULL)
+ {
+ (void) sm_io_fprintf(fp, SM_TIME_DEFAULT, "[NULL]\n");
+ return;
+ }
+
+ while (a != NULL)
+ {
+ (void) sm_io_fprintf(fp, SM_TIME_DEFAULT, "%p=", a);
+ (void) sm_io_flush(fp, SM_TIME_DEFAULT);
+
+ /* find the mailer -- carefully */
+ m = a->q_mailer;
+ if (m == NULL)
+ {
+ m = &pseudomailer;
+ m->m_mno = -1;
+ m->m_name = "NULL";
+ }
+
+ (void) sm_io_fprintf(fp, SM_TIME_DEFAULT,
+ "%s:\n\tmailer %d (%s), host `%s'\n",
+ a->q_paddr == NULL ? "<null>" : a->q_paddr,
+ m->m_mno, m->m_name,
+ a->q_host == NULL ? "<null>" : a->q_host);
+ (void) sm_io_fprintf(fp, SM_TIME_DEFAULT,
+ "\tuser `%s', ruser `%s'\n",
+ a->q_user,
+ a->q_ruser == NULL ? "<null>" : a->q_ruser);
+ (void) sm_io_fprintf(fp, SM_TIME_DEFAULT, "\tstate=");
+ switch (a->q_state)
+ {
+ case QS_OK:
+ (void) sm_io_fprintf(fp, SM_TIME_DEFAULT, "OK");
+ break;
+
+ case QS_DONTSEND:
+ (void) sm_io_fprintf(fp, SM_TIME_DEFAULT,
+ "DONTSEND");
+ break;
+
+ case QS_BADADDR:
+ (void) sm_io_fprintf(fp, SM_TIME_DEFAULT,
+ "BADADDR");
+ break;
+
+ case QS_QUEUEUP:
+ (void) sm_io_fprintf(fp, SM_TIME_DEFAULT,
+ "QUEUEUP");
+ break;
+
+ case QS_RETRY:
+ (void) sm_io_fprintf(fp, SM_TIME_DEFAULT, "RETRY");
+ break;
+
+ case QS_SENT:
+ (void) sm_io_fprintf(fp, SM_TIME_DEFAULT, "SENT");
+ break;
+
+ case QS_VERIFIED:
+ (void) sm_io_fprintf(fp, SM_TIME_DEFAULT,
+ "VERIFIED");
+ break;
+
+ case QS_EXPANDED:
+ (void) sm_io_fprintf(fp, SM_TIME_DEFAULT,
+ "EXPANDED");
+ break;
+
+ case QS_SENDER:
+ (void) sm_io_fprintf(fp, SM_TIME_DEFAULT,
+ "SENDER");
+ break;
+
+ case QS_CLONED:
+ (void) sm_io_fprintf(fp, SM_TIME_DEFAULT,
+ "CLONED");
+ break;
+
+ case QS_DISCARDED:
+ (void) sm_io_fprintf(fp, SM_TIME_DEFAULT,
+ "DISCARDED");
+ break;
+
+ case QS_REPLACED:
+ (void) sm_io_fprintf(fp, SM_TIME_DEFAULT,
+ "REPLACED");
+ break;
+
+ case QS_REMOVED:
+ (void) sm_io_fprintf(fp, SM_TIME_DEFAULT,
+ "REMOVED");
+ break;
+
+ case QS_DUPLICATE:
+ (void) sm_io_fprintf(fp, SM_TIME_DEFAULT,
+ "DUPLICATE");
+ break;
+
+ case QS_INCLUDED:
+ (void) sm_io_fprintf(fp, SM_TIME_DEFAULT,
+ "INCLUDED");
+ break;
+
+ default:
+ (void) sm_io_fprintf(fp, SM_TIME_DEFAULT,
+ "%d", a->q_state);
+ break;
+ }
+ (void) sm_io_fprintf(fp, SM_TIME_DEFAULT,
+ ", next=%p, alias %p, uid %d, gid %d\n",
+ a->q_next, a->q_alias,
+ (int) a->q_uid, (int) a->q_gid);
+ (void) sm_io_fprintf(fp, SM_TIME_DEFAULT, "\tflags=%lx<",
+ a->q_flags);
+ firstone = true;
+ for (qfp = AddressFlags; qfp->qf_name != NULL; qfp++)
+ {
+ if (!bitset(qfp->qf_bit, a->q_flags))
+ continue;
+ if (!firstone)
+ (void) sm_io_fprintf(fp, SM_TIME_DEFAULT,
+ ",");
+ firstone = false;
+ (void) sm_io_fprintf(fp, SM_TIME_DEFAULT, "%s",
+ qfp->qf_name);
+ }
+ (void) sm_io_fprintf(fp, SM_TIME_DEFAULT, ">\n");
+ (void) sm_io_fprintf(fp, SM_TIME_DEFAULT,
+ "\towner=%s, home=\"%s\", fullname=\"%s\"\n",
+ a->q_owner == NULL ? "(none)" : a->q_owner,
+ a->q_home == NULL ? "(none)" : a->q_home,
+ a->q_fullname == NULL ? "(none)" : a->q_fullname);
+ (void) sm_io_fprintf(fp, SM_TIME_DEFAULT,
+ "\torcpt=\"%s\", statmta=%s, status=%s\n",
+ a->q_orcpt == NULL ? "(none)" : a->q_orcpt,
+ a->q_statmta == NULL ? "(none)" : a->q_statmta,
+ a->q_status == NULL ? "(none)" : a->q_status);
+ (void) sm_io_fprintf(fp, SM_TIME_DEFAULT,
+ "\tfinalrcpt=\"%s\"\n",
+ a->q_finalrcpt == NULL ? "(none)" : a->q_finalrcpt);
+ (void) sm_io_fprintf(fp, SM_TIME_DEFAULT,
+ "\trstatus=\"%s\"\n",
+ a->q_rstatus == NULL ? "(none)" : a->q_rstatus);
+ (void) sm_io_fprintf(fp, SM_TIME_DEFAULT,
+ "\tstatdate=%s\n",
+ a->q_statdate == 0 ? "(none)" : ctime(&a->q_statdate));
+
+ if (!follow)
+ return;
+ a = a->q_next;
+ }
+}
+/*
+** EMPTYADDR -- return true if this address is empty (``<>'')
+**
+** Parameters:
+** a -- pointer to the address
+**
+** Returns:
+** true -- if this address is "empty" (i.e., no one should
+** ever generate replies to it.
+** false -- if it is a "regular" (read: replyable) address.
+*/
+
+bool
+emptyaddr(a)
+ register ADDRESS *a;
+{
+ return a->q_paddr == NULL || strcmp(a->q_paddr, "<>") == 0 ||
+ a->q_user == NULL || strcmp(a->q_user, "<>") == 0;
+}
+/*
+** REMOTENAME -- return the name relative to the current mailer
+**
+** Parameters:
+** name -- the name to translate.
+** m -- the mailer that we want to do rewriting relative to.
+** flags -- fine tune operations.
+** pstat -- pointer to status word.
+** e -- the current envelope.
+**
+** Returns:
+** the text string representing this address relative to
+** the receiving mailer.
+**
+** Side Effects:
+** none.
+**
+** Warnings:
+** The text string returned is tucked away locally;
+** copy it if you intend to save it.
+*/
+
+char *
+remotename(name, m, flags, pstat, e)
+ char *name;
+ struct mailer *m;
+ int flags;
+ int *pstat;
+ register ENVELOPE *e;
+{
+ register char **pvp;
+ char *SM_NONVOLATILE fancy;
+ char *oldg;
+ int rwset;
+ static char buf[MAXNAME + 1];
+ char lbuf[MAXNAME + 1];
+ char pvpbuf[PSBUFSIZE];
+ char addrtype[4];
+
+ if (tTd(12, 1))
+ sm_dprintf("remotename(%s)\n", name);
+
+ /* don't do anything if we are tagging it as special */
+ if (bitset(RF_SENDERADDR, flags))
+ {
+ rwset = bitset(RF_HEADERADDR, flags) ? m->m_sh_rwset
+ : m->m_se_rwset;
+ addrtype[2] = 's';
+ }
+ else
+ {
+ rwset = bitset(RF_HEADERADDR, flags) ? m->m_rh_rwset
+ : m->m_re_rwset;
+ addrtype[2] = 'r';
+ }
+ if (rwset < 0)
+ return name;
+ addrtype[1] = ' ';
+ addrtype[3] = '\0';
+ addrtype[0] = bitset(RF_HEADERADDR, flags) ? 'h' : 'e';
+ macdefine(&e->e_macro, A_TEMP, macid("{addr_type}"), addrtype);
+
+ /*
+ ** Do a heuristic crack of this name to extract any comment info.
+ ** This will leave the name as a comment and a $g macro.
+ */
+
+ if (bitset(RF_CANONICAL, flags) || bitnset(M_NOCOMMENT, m->m_flags))
+ fancy = "\201g";
+ else
+ fancy = crackaddr(name, e);
+
+ /*
+ ** Turn the name into canonical form.
+ ** Normally this will be RFC 822 style, i.e., "user@domain".
+ ** If this only resolves to "user", and the "C" flag is
+ ** specified in the sending mailer, then the sender's
+ ** domain will be appended.
+ */
+
+ pvp = prescan(name, '\0', pvpbuf, sizeof pvpbuf, NULL, NULL, false);
+ if (pvp == NULL)
+ return name;
+ if (REWRITE(pvp, 3, e) == EX_TEMPFAIL)
+ *pstat = EX_TEMPFAIL;
+ if (bitset(RF_ADDDOMAIN, flags) && e->e_fromdomain != NULL)
+ {
+ /* append from domain to this address */
+ register char **pxp = pvp;
+ int l = MAXATOM; /* size of buffer for pvp */
+
+ /* see if there is an "@domain" in the current name */
+ while (*pxp != NULL && strcmp(*pxp, "@") != 0)
+ {
+ pxp++;
+ --l;
+ }
+ if (*pxp == NULL)
+ {
+ /* no.... append the "@domain" from the sender */
+ register char **qxq = e->e_fromdomain;
+
+ while ((*pxp++ = *qxq++) != NULL)
+ {
+ if (--l <= 0)
+ {
+ *--pxp = NULL;
+ usrerr("553 5.1.0 remotename: too many tokens");
+ *pstat = EX_UNAVAILABLE;
+ break;
+ }
+ }
+ if (REWRITE(pvp, 3, e) == EX_TEMPFAIL)
+ *pstat = EX_TEMPFAIL;
+ }
+ }
+
+ /*
+ ** Do more specific rewriting.
+ ** Rewrite using ruleset 1 or 2 depending on whether this is
+ ** a sender address or not.
+ ** Then run it through any receiving-mailer-specific rulesets.
+ */
+
+ if (bitset(RF_SENDERADDR, flags))
+ {
+ if (REWRITE(pvp, 1, e) == EX_TEMPFAIL)
+ *pstat = EX_TEMPFAIL;
+ }
+ else
+ {
+ if (REWRITE(pvp, 2, e) == EX_TEMPFAIL)
+ *pstat = EX_TEMPFAIL;
+ }
+ if (rwset > 0)
+ {
+ if (REWRITE(pvp, rwset, e) == EX_TEMPFAIL)
+ *pstat = EX_TEMPFAIL;
+ }
+
+ /*
+ ** Do any final sanitation the address may require.
+ ** This will normally be used to turn internal forms
+ ** (e.g., user@host.LOCAL) into external form. This
+ ** may be used as a default to the above rules.
+ */
+
+ if (REWRITE(pvp, 4, e) == EX_TEMPFAIL)
+ *pstat = EX_TEMPFAIL;
+
+ /*
+ ** Now restore the comment information we had at the beginning.
+ */
+
+ cataddr(pvp, NULL, lbuf, sizeof lbuf, '\0');
+ oldg = macget(&e->e_macro, 'g');
+ macset(&e->e_macro, 'g', lbuf);
+
+ SM_TRY
+ /* need to make sure route-addrs have <angle brackets> */
+ if (bitset(RF_CANONICAL, flags) && lbuf[0] == '@')
+ expand("<\201g>", buf, sizeof buf, e);
+ else
+ expand(fancy, buf, sizeof buf, e);
+ SM_FINALLY
+ macset(&e->e_macro, 'g', oldg);
+ SM_END_TRY
+
+ if (tTd(12, 1))
+ sm_dprintf("remotename => `%s'\n", buf);
+ return buf;
+}
+/*
+** MAPLOCALUSER -- run local username through ruleset 5 for final redirection
+**
+** Parameters:
+** a -- the address to map (but just the user name part).
+** sendq -- the sendq in which to install any replacement
+** addresses.
+** aliaslevel -- the alias nesting depth.
+** e -- the envelope.
+**
+** Returns:
+** none.
+*/
+
+#define Q_COPYFLAGS (QPRIMARY|QBOGUSSHELL|QUNSAFEADDR|\
+ Q_PINGFLAGS|QHASNOTIFY|\
+ QRELAYED|QEXPANDED|QDELIVERED|QDELAYED|\
+ QBYTRACE|QBYNDELAY|QBYNRELAY)
+
+void
+maplocaluser(a, sendq, aliaslevel, e)
+ register ADDRESS *a;
+ ADDRESS **sendq;
+ int aliaslevel;
+ ENVELOPE *e;
+{
+ register char **pvp;
+ register ADDRESS *SM_NONVOLATILE a1 = NULL;
+ char pvpbuf[PSBUFSIZE];
+
+ if (tTd(29, 1))
+ {
+ sm_dprintf("maplocaluser: ");
+ printaddr(sm_debug_file(), a, false);
+ }
+ pvp = prescan(a->q_user, '\0', pvpbuf, sizeof pvpbuf, NULL, NULL, false);
+ if (pvp == NULL)
+ {
+ if (tTd(29, 9))
+ sm_dprintf("maplocaluser: cannot prescan %s\n",
+ a->q_user);
+ return;
+ }
+
+ macdefine(&e->e_macro, A_PERM, 'h', a->q_host);
+ macdefine(&e->e_macro, A_PERM, 'u', a->q_user);
+ macdefine(&e->e_macro, A_PERM, 'z', a->q_home);
+
+ macdefine(&e->e_macro, A_PERM, macid("{addr_type}"), "e r");
+ if (REWRITE(pvp, 5, e) == EX_TEMPFAIL)
+ {
+ if (tTd(29, 9))
+ sm_dprintf("maplocaluser: rewrite tempfail\n");
+ a->q_state = QS_QUEUEUP;
+ a->q_status = "4.4.3";
+ return;
+ }
+ if (pvp[0] == NULL || (pvp[0][0] & 0377) != CANONNET)
+ {
+ if (tTd(29, 9))
+ sm_dprintf("maplocaluser: doesn't resolve\n");
+ return;
+ }
+
+ SM_TRY
+ a1 = buildaddr(pvp, NULL, 0, e);
+ SM_EXCEPT(exc, "E:mta.quickabort")
+
+ /*
+ ** mark address as bad, S5 returned an error
+ ** and we gave that back to the SMTP client.
+ */
+
+ a->q_state = QS_DONTSEND;
+ sm_exc_raisenew_x(&EtypeQuickAbort, 2);
+ SM_END_TRY
+
+ /* if non-null, mailer destination specified -- has it changed? */
+ if (a1 == NULL || sameaddr(a, a1))
+ {
+ if (tTd(29, 9))
+ sm_dprintf("maplocaluser: address unchanged\n");
+ return;
+ }
+
+ /* make new address take on flags and print attributes of old */
+ a1->q_flags &= ~Q_COPYFLAGS;
+ a1->q_flags |= a->q_flags & Q_COPYFLAGS;
+ a1->q_paddr = sm_rpool_strdup_x(e->e_rpool, a->q_paddr);
+ a1->q_finalrcpt = a->q_finalrcpt;
+ a1->q_orcpt = a->q_orcpt;
+
+ /* mark old address as dead; insert new address */
+ a->q_state = QS_REPLACED;
+ if (tTd(29, 5))
+ {
+ sm_dprintf("maplocaluser: QS_REPLACED ");
+ printaddr(sm_debug_file(), a, false);
+ }
+ a1->q_alias = a;
+ allocaddr(a1, RF_COPYALL, sm_rpool_strdup_x(e->e_rpool, a->q_paddr), e);
+ (void) recipient(a1, sendq, aliaslevel, e);
+}
+/*
+** DEQUOTE_INIT -- initialize dequote map
+**
+** Parameters:
+** map -- the internal map structure.
+** args -- arguments.
+**
+** Returns:
+** true.
+*/
+
+bool
+dequote_init(map, args)
+ MAP *map;
+ char *args;
+{
+ register char *p = args;
+
+ /* there is no check whether there is really an argument */
+ map->map_mflags |= MF_KEEPQUOTES;
+ for (;;)
+ {
+ while (isascii(*p) && isspace(*p))
+ p++;
+ if (*p != '-')
+ break;
+ switch (*++p)
+ {
+ case 'a':
+ map->map_app = ++p;
+ break;
+
+ case 'D':
+ map->map_mflags |= MF_DEFER;
+ break;
+
+ case 'S':
+ case 's':
+ map->map_spacesub = *++p;
+ break;
+ }
+ while (*p != '\0' && !(isascii(*p) && isspace(*p)))
+ p++;
+ if (*p != '\0')
+ *p = '\0';
+ }
+ if (map->map_app != NULL)
+ map->map_app = newstr(map->map_app);
+
+ return true;
+}
+/*
+** DEQUOTE_MAP -- unquote an address
+**
+** Parameters:
+** map -- the internal map structure (ignored).
+** name -- the name to dequote.
+** av -- arguments (ignored).
+** statp -- pointer to status out-parameter.
+**
+** Returns:
+** NULL -- if there were no quotes, or if the resulting
+** unquoted buffer would not be acceptable to prescan.
+** else -- The dequoted buffer.
+*/
+
+/* ARGSUSED2 */
+char *
+dequote_map(map, name, av, statp)
+ MAP *map;
+ char *name;
+ char **av;
+ int *statp;
+{
+ register char *p;
+ register char *q;
+ register char c;
+ int anglecnt = 0;
+ int cmntcnt = 0;
+ int quotecnt = 0;
+ int spacecnt = 0;
+ bool quotemode = false;
+ bool bslashmode = false;
+ char spacesub = map->map_spacesub;
+
+ for (p = q = name; (c = *p++) != '\0'; )
+ {
+ if (bslashmode)
+ {
+ bslashmode = false;
+ *q++ = c;
+ continue;
+ }
+
+ if (c == ' ' && spacesub != '\0')
+ c = spacesub;
+
+ switch (c)
+ {
+ case '\\':
+ bslashmode = true;
+ break;
+
+ case '(':
+ cmntcnt++;
+ break;
+
+ case ')':
+ if (cmntcnt-- <= 0)
+ return NULL;
+ break;
+
+ case ' ':
+ case '\t':
+ spacecnt++;
+ break;
+ }
+
+ if (cmntcnt > 0)
+ {
+ *q++ = c;
+ continue;
+ }
+
+ switch (c)
+ {
+ case '"':
+ quotemode = !quotemode;
+ quotecnt++;
+ continue;
+
+ case '<':
+ anglecnt++;
+ break;
+
+ case '>':
+ if (anglecnt-- <= 0)
+ return NULL;
+ break;
+ }
+ *q++ = c;
+ }
+
+ if (anglecnt != 0 || cmntcnt != 0 || bslashmode ||
+ quotemode || quotecnt <= 0 || spacecnt != 0)
+ return NULL;
+ *q++ = '\0';
+ return map_rewrite(map, name, strlen(name), NULL);
+}
+/*
+** RSCHECK -- check string(s) for validity using rewriting sets
+**
+** Parameters:
+** rwset -- the rewriting set to use.
+** p1 -- the first string to check.
+** p2 -- the second string to check -- may be null.
+** e -- the current envelope.
+** flags -- control some behavior, see RSF_ in sendmail.h
+** logl -- logging level.
+** host -- NULL or relay host.
+** logid -- id for sm_syslog.
+**
+** Returns:
+** EX_OK -- if the rwset doesn't resolve to $#error
+** else -- the failure status (message printed)
+*/
+
+int
+rscheck(rwset, p1, p2, e, flags, logl, host, logid)
+ char *rwset;
+ char *p1;
+ char *p2;
+ ENVELOPE *e;
+ int flags;
+ int logl;
+ char *host;
+ char *logid;
+{
+ char *volatile buf;
+ int bufsize;
+ int saveexitstat;
+ int volatile rstat = EX_OK;
+ char **pvp;
+ int rsno;
+ bool volatile discard = false;
+ auto ADDRESS a1;
+ bool saveQuickAbort = QuickAbort;
+ bool saveSuprErrs = SuprErrs;
+ bool quarantine = false;
+ char ubuf[BUFSIZ * 2];
+ char buf0[MAXLINE];
+ char pvpbuf[PSBUFSIZE];
+ extern char MsgBuf[];
+
+ if (tTd(48, 2))
+ sm_dprintf("rscheck(%s, %s, %s)\n", rwset, p1,
+ p2 == NULL ? "(NULL)" : p2);
+
+ rsno = strtorwset(rwset, NULL, ST_FIND);
+ if (rsno < 0)
+ return EX_OK;
+
+ if (p2 != NULL)
+ {
+ bufsize = strlen(p1) + strlen(p2) + 2;
+ if (bufsize > sizeof buf0)
+ buf = sm_malloc_x(bufsize);
+ else
+ {
+ buf = buf0;
+ bufsize = sizeof buf0;
+ }
+ (void) sm_snprintf(buf, bufsize, "%s%c%s", p1, CONDELSE, p2);
+ }
+ else
+ {
+ bufsize = strlen(p1) + 1;
+ if (bufsize > sizeof buf0)
+ buf = sm_malloc_x(bufsize);
+ else
+ {
+ buf = buf0;
+ bufsize = sizeof buf0;
+ }
+ (void) sm_strlcpy(buf, p1, bufsize);
+ }
+ SM_TRY
+ {
+ SuprErrs = true;
+ QuickAbort = false;
+ pvp = prescan(buf, '\0', pvpbuf, sizeof pvpbuf, NULL,
+ bitset(RSF_RMCOMM, flags) ? NULL : TokTypeNoC,
+ bitset(RSF_RMCOMM, flags) ? false : true);
+ SuprErrs = saveSuprErrs;
+ if (pvp == NULL)
+ {
+ if (tTd(48, 2))
+ sm_dprintf("rscheck: cannot prescan input\n");
+ /*
+ syserr("rscheck: cannot prescan input: \"%s\"",
+ shortenstring(buf, MAXSHORTSTR));
+ rstat = EX_DATAERR;
+ */
+ goto finis;
+ }
+ if (bitset(RSF_UNSTRUCTURED, flags))
+ SuprErrs = true;
+ (void) REWRITE(pvp, rsno, e);
+ if (bitset(RSF_UNSTRUCTURED, flags))
+ SuprErrs = saveSuprErrs;
+ if (pvp[0] == NULL || (pvp[0][0] & 0377) != CANONNET ||
+ pvp[1] == NULL || (strcmp(pvp[1], "error") != 0 &&
+ strcmp(pvp[1], "discard") != 0))
+ {
+ goto finis;
+ }
+
+ if (strcmp(pvp[1], "discard") == 0)
+ {
+ if (tTd(48, 2))
+ sm_dprintf("rscheck: discard mailer selected\n");
+ e->e_flags |= EF_DISCARD;
+ discard = true;
+ }
+ else if (strcmp(pvp[1], "error") == 0 &&
+ pvp[2] != NULL && (pvp[2][0] & 0377) == CANONHOST &&
+ pvp[3] != NULL && strcmp(pvp[3], "quarantine") == 0)
+ {
+ if (pvp[4] == NULL ||
+ (pvp[4][0] & 0377) != CANONUSER ||
+ pvp[5] == NULL)
+ e->e_quarmsg = sm_rpool_strdup_x(e->e_rpool,
+ rwset);
+ else
+ {
+ cataddr(&(pvp[5]), NULL, ubuf,
+ sizeof ubuf, ' ');
+ e->e_quarmsg = sm_rpool_strdup_x(e->e_rpool,
+ ubuf);
+ }
+ macdefine(&e->e_macro, A_PERM,
+ macid("{quarantine}"), e->e_quarmsg);
+ quarantine = true;
+ }
+ else
+ {
+ int savelogusrerrs = LogUsrErrs;
+ static bool logged = false;
+
+ /* got an error -- process it */
+ saveexitstat = ExitStat;
+ LogUsrErrs = false;
+ (void) buildaddr(pvp, &a1, 0, e);
+ LogUsrErrs = savelogusrerrs;
+ rstat = ExitStat;
+ ExitStat = saveexitstat;
+ if (!logged)
+ {
+ if (bitset(RSF_COUNT, flags))
+ markstats(e, &a1, STATS_REJECT);
+ logged = true;
+ }
+ }
+
+ if (LogLevel > logl)
+ {
+ char *relay;
+ char *p;
+ char lbuf[MAXLINE];
+
+ p = lbuf;
+ if (p2 != NULL)
+ {
+ (void) sm_snprintf(p, SPACELEFT(lbuf, p),
+ ", arg2=%s",
+ p2);
+ p += strlen(p);
+ }
+
+ if (host != NULL)
+ relay = host;
+ else
+ relay = macvalue('_', e);
+ if (relay != NULL)
+ {
+ (void) sm_snprintf(p, SPACELEFT(lbuf, p),
+ ", relay=%s", relay);
+ p += strlen(p);
+ }
+ *p = '\0';
+ if (discard)
+ sm_syslog(LOG_NOTICE, logid,
+ "ruleset=%s, arg1=%s%s, discard",
+ rwset, p1, lbuf);
+ else if (quarantine)
+ sm_syslog(LOG_NOTICE, logid,
+ "ruleset=%s, arg1=%s%s, quarantine=%s",
+ rwset, p1, lbuf, ubuf);
+ else
+ sm_syslog(LOG_NOTICE, logid,
+ "ruleset=%s, arg1=%s%s, reject=%s",
+ rwset, p1, lbuf, MsgBuf);
+ }
+
+ finis: ;
+ }
+ SM_FINALLY
+ {
+ /* clean up */
+ if (buf != buf0)
+ sm_free(buf);
+ QuickAbort = saveQuickAbort;
+ }
+ SM_END_TRY
+
+ setstat(rstat);
+
+ /* rulesets don't set errno */
+ errno = 0;
+ if (rstat != EX_OK && QuickAbort)
+ sm_exc_raisenew_x(&EtypeQuickAbort, 2);
+ return rstat;
+}
+/*
+** RSCAP -- call rewriting set to return capabilities
+**
+** Parameters:
+** rwset -- the rewriting set to use.
+** p1 -- the first string to check.
+** p2 -- the second string to check -- may be null.
+** e -- the current envelope.
+** pvp -- pointer to token vector.
+** pvpbuf -- buffer space.
+** size -- size of buffer space.
+**
+** Returns:
+** EX_UNAVAILABLE -- ruleset doesn't exist.
+** EX_DATAERR -- prescan() failed.
+** EX_OK -- rewrite() was successful.
+** else -- return status from rewrite().
+*/
+
+int
+rscap(rwset, p1, p2, e, pvp, pvpbuf, size)
+ char *rwset;
+ char *p1;
+ char *p2;
+ ENVELOPE *e;
+ char ***pvp;
+ char *pvpbuf;
+ int size;
+{
+ char *volatile buf;
+ int bufsize;
+ int volatile rstat = EX_OK;
+ int rsno;
+ bool saveQuickAbort = QuickAbort;
+ bool saveSuprErrs = SuprErrs;
+ char buf0[MAXLINE];
+ extern char MsgBuf[];
+
+ if (tTd(48, 2))
+ sm_dprintf("rscap(%s, %s, %s)\n", rwset, p1,
+ p2 == NULL ? "(NULL)" : p2);
+
+ if (pvp != NULL)
+ *pvp = NULL;
+ rsno = strtorwset(rwset, NULL, ST_FIND);
+ if (rsno < 0)
+ return EX_UNAVAILABLE;
+
+ if (p2 != NULL)
+ {
+ bufsize = strlen(p1) + strlen(p2) + 2;
+ if (bufsize > sizeof buf0)
+ buf = sm_malloc_x(bufsize);
+ else
+ {
+ buf = buf0;
+ bufsize = sizeof buf0;
+ }
+ (void) sm_snprintf(buf, bufsize, "%s%c%s", p1, CONDELSE, p2);
+ }
+ else
+ {
+ bufsize = strlen(p1) + 1;
+ if (bufsize > sizeof buf0)
+ buf = sm_malloc_x(bufsize);
+ else
+ {
+ buf = buf0;
+ bufsize = sizeof buf0;
+ }
+ (void) sm_strlcpy(buf, p1, bufsize);
+ }
+ SM_TRY
+ {
+ SuprErrs = true;
+ QuickAbort = false;
+ *pvp = prescan(buf, '\0', pvpbuf, size, NULL, NULL, false);
+ if (*pvp != NULL)
+ rstat = rewrite(*pvp, rsno, 0, e, size);
+ else
+ {
+ if (tTd(48, 2))
+ sm_dprintf("rscap: cannot prescan input\n");
+ rstat = EX_DATAERR;
+ }
+ }
+ SM_FINALLY
+ {
+ /* clean up */
+ if (buf != buf0)
+ sm_free(buf);
+ SuprErrs = saveSuprErrs;
+ QuickAbort = saveQuickAbort;
+
+ /* prevent information leak, this may contain rewrite error */
+ MsgBuf[0] = '\0';
+ }
+ SM_END_TRY
+ return rstat;
+}
diff --git a/usr/src/cmd/sendmail/src/queue.c b/usr/src/cmd/sendmail/src/queue.c
new file mode 100644
index 0000000000..4ec9fc318c
--- /dev/null
+++ b/usr/src/cmd/sendmail/src/queue.c
@@ -0,0 +1,8818 @@
+/*
+ * Copyright (c) 1998-2005 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sendmail.h>
+#include <sm/sem.h>
+
+SM_RCSID("@(#)$Id: queue.c,v 8.944 2005/02/17 23:58:58 ca Exp $")
+
+#include <dirent.h>
+
+# define RELEASE_QUEUE (void) 0
+# define ST_INODE(st) (st).st_ino
+
+# define sm_file_exists(errno) ((errno) == EEXIST)
+
+# if HASFLOCK && defined(O_EXLOCK)
+# define SM_OPEN_EXLOCK 1
+# define TF_OPEN_FLAGS (O_CREAT|O_WRONLY|O_EXCL|O_EXLOCK)
+# else /* HASFLOCK && defined(O_EXLOCK) */
+# define TF_OPEN_FLAGS (O_CREAT|O_WRONLY|O_EXCL)
+# endif /* HASFLOCK && defined(O_EXLOCK) */
+
+#ifndef SM_OPEN_EXLOCK
+# define SM_OPEN_EXLOCK 0
+#endif /* ! SM_OPEN_EXLOCK */
+
+/*
+** Historical notes:
+** QF_VERSION == 4 was sendmail 8.10/8.11 without _FFR_QUEUEDELAY
+** QF_VERSION == 5 was sendmail 8.10/8.11 with _FFR_QUEUEDELAY
+** QF_VERSION == 6 was sendmail 8.12 without _FFR_QUEUEDELAY
+** QF_VERSION == 7 was sendmail 8.12 with _FFR_QUEUEDELAY
+** QF_VERSION == 8 is sendmail 8.13
+*/
+
+#define QF_VERSION 8 /* version number of this queue format */
+
+static char queue_letter __P((ENVELOPE *, int));
+static bool quarantine_queue_item __P((int, int, ENVELOPE *, char *));
+
+/* Naming convention: qgrp: index of queue group, qg: QUEUEGROUP */
+
+/*
+** Work queue.
+*/
+
+struct work
+{
+ char *w_name; /* name of control file */
+ char *w_host; /* name of recipient host */
+ bool w_lock; /* is message locked? */
+ bool w_tooyoung; /* is it too young to run? */
+ long w_pri; /* priority of message, see below */
+ time_t w_ctime; /* creation time */
+ time_t w_mtime; /* modification time */
+ int w_qgrp; /* queue group located in */
+ int w_qdir; /* queue directory located in */
+ struct work *w_next; /* next in queue */
+};
+
+typedef struct work WORK;
+
+static WORK *WorkQ; /* queue of things to be done */
+static int NumWorkGroups; /* number of work groups */
+static time_t Current_LA_time = 0;
+
+/* Get new load average every 30 seconds. */
+#define GET_NEW_LA_TIME 30
+
+#define SM_GET_LA(now) \
+ do \
+ { \
+ now = curtime(); \
+ if (Current_LA_time < now - GET_NEW_LA_TIME) \
+ { \
+ sm_getla(); \
+ Current_LA_time = now; \
+ } \
+ } while (0)
+
+/*
+** DoQueueRun indicates that a queue run is needed.
+** Notice: DoQueueRun is modified in a signal handler!
+*/
+
+static bool volatile DoQueueRun; /* non-interrupt time queue run needed */
+
+/*
+** Work group definition structure.
+** Each work group contains one or more queue groups. This is done
+** to manage the number of queue group runners active at the same time
+** to be within the constraints of MaxQueueChildren (if it is set).
+** The number of queue groups that can be run on the next work run
+** is kept track of. The queue groups are run in a round robin.
+*/
+
+struct workgrp
+{
+ int wg_numqgrp; /* number of queue groups in work grp */
+ int wg_runners; /* total runners */
+ int wg_curqgrp; /* current queue group */
+ QUEUEGRP **wg_qgs; /* array of queue groups */
+ int wg_maxact; /* max # of active runners */
+ time_t wg_lowqintvl; /* lowest queue interval */
+ int wg_restart; /* needs restarting? */
+ int wg_restartcnt; /* count of times restarted */
+};
+
+typedef struct workgrp WORKGRP;
+
+static WORKGRP volatile WorkGrp[MAXWORKGROUPS + 1]; /* work groups */
+
+#if SM_HEAP_CHECK
+static SM_DEBUG_T DebugLeakQ = SM_DEBUG_INITIALIZER("leak_q",
+ "@(#)$Debug: leak_q - trace memory leaks during queue processing $");
+#endif /* SM_HEAP_CHECK */
+
+/*
+** We use EmptyString instead of "" to avoid
+** 'zero-length format string' warnings from gcc
+*/
+
+static const char EmptyString[] = "";
+
+static void grow_wlist __P((int, int));
+static int multiqueue_cache __P((char *, int, QUEUEGRP *, int, unsigned int *));
+static int gatherq __P((int, int, bool, bool *, bool *));
+static int sortq __P((int));
+static void printctladdr __P((ADDRESS *, SM_FILE_T *));
+static bool readqf __P((ENVELOPE *, bool));
+static void restart_work_group __P((int));
+static void runner_work __P((ENVELOPE *, int, bool, int, int));
+static void schedule_queue_runs __P((bool, int, bool));
+static char *strrev __P((char *));
+static ADDRESS *setctluser __P((char *, int, ENVELOPE *));
+#if _FFR_RHS
+static int sm_strshufflecmp __P((char *, char *));
+static void init_shuffle_alphabet __P(());
+#endif /* _FFR_RHS */
+static int workcmpf0();
+static int workcmpf1();
+static int workcmpf2();
+static int workcmpf3();
+static int workcmpf4();
+static int randi = 3; /* index for workcmpf5() */
+static int workcmpf5();
+static int workcmpf6();
+#if _FFR_RHS
+static int workcmpf7();
+#endif /* _FFR_RHS */
+
+#if RANDOMSHIFT
+# define get_rand_mod(m) ((get_random() >> RANDOMSHIFT) % (m))
+#else /* RANDOMSHIFT */
+# define get_rand_mod(m) (get_random() % (m))
+#endif /* RANDOMSHIFT */
+
+/*
+** File system definition.
+** Used to keep track of how much free space is available
+** on a file system in which one or more queue directories reside.
+*/
+
+typedef struct filesys_shared FILESYS;
+
+struct filesys_shared
+{
+ dev_t fs_dev; /* unique device id */
+ long fs_avail; /* number of free blocks available */
+ long fs_blksize; /* block size, in bytes */
+};
+
+/* probably kept in shared memory */
+static FILESYS FileSys[MAXFILESYS]; /* queue file systems */
+static char *FSPath[MAXFILESYS]; /* pathnames for file systems */
+
+#if SM_CONF_SHM
+
+/*
+** Shared memory data
+**
+** Current layout:
+** size -- size of shared memory segment
+** pid -- pid of owner, should be a unique id to avoid misinterpretations
+** by other processes.
+** tag -- should be a unique id to avoid misinterpretations by others.
+** idea: hash over configuration data that will be stored here.
+** NumFileSys -- number of file systems.
+** FileSys -- (arrary of) structure for used file systems.
+** RSATmpCnt -- counter for number of uses of ephemeral RSA key.
+** QShm -- (array of) structure for information about queue directories.
+*/
+
+/*
+** Queue data in shared memory
+*/
+
+typedef struct queue_shared QUEUE_SHM_T;
+
+struct queue_shared
+{
+ int qs_entries; /* number of entries */
+ /* XXX more to follow? */
+};
+
+static void *Pshm; /* pointer to shared memory */
+static FILESYS *PtrFileSys; /* pointer to queue file system array */
+int ShmId = SM_SHM_NO_ID; /* shared memory id */
+static QUEUE_SHM_T *QShm; /* pointer to shared queue data */
+static size_t shms;
+
+# define SHM_OFF_PID(p) (((char *) (p)) + sizeof(int))
+# define SHM_OFF_TAG(p) (((char *) (p)) + sizeof(pid_t) + sizeof(int))
+# define SHM_OFF_HEAD (sizeof(pid_t) + sizeof(int) * 2)
+
+/* how to access FileSys */
+# define FILE_SYS(i) (PtrFileSys[i])
+
+/* first entry is a tag, for now just the size */
+# define OFF_FILE_SYS(p) (((char *) (p)) + SHM_OFF_HEAD)
+
+/* offset for PNumFileSys */
+# define OFF_NUM_FILE_SYS(p) (((char *) (p)) + SHM_OFF_HEAD + sizeof(FileSys))
+
+/* offset for PRSATmpCnt */
+# define OFF_RSA_TMP_CNT(p) (((char *) (p)) + SHM_OFF_HEAD + sizeof(FileSys) + sizeof(int))
+int *PRSATmpCnt;
+
+/* offset for queue_shm */
+# define OFF_QUEUE_SHM(p) (((char *) (p)) + SHM_OFF_HEAD + sizeof(FileSys) + sizeof(int) * 2)
+
+# define QSHM_ENTRIES(i) QShm[i].qs_entries
+
+/* basic size of shared memory segment */
+# define SM_T_SIZE (SHM_OFF_HEAD + sizeof(FileSys) + sizeof(int) * 2)
+
+static unsigned int hash_q __P((char *, unsigned int));
+
+/*
+** HASH_Q -- simple hash function
+**
+** Parameters:
+** p -- string to hash.
+** h -- hash start value (from previous run).
+**
+** Returns:
+** hash value.
+*/
+
+static unsigned int
+hash_q(p, h)
+ char *p;
+ unsigned int h;
+{
+ int c, d;
+
+ while (*p != '\0')
+ {
+ d = *p++;
+ c = d;
+ c ^= c<<6;
+ h += (c<<11) ^ (c>>1);
+ h ^= (d<<14) + (d<<7) + (d<<4) + d;
+ }
+ return h;
+}
+
+
+#else /* SM_CONF_SHM */
+# define FILE_SYS(i) FileSys[i]
+#endif /* SM_CONF_SHM */
+
+/* access to the various components of file system data */
+#define FILE_SYS_NAME(i) FSPath[i]
+#define FILE_SYS_AVAIL(i) FILE_SYS(i).fs_avail
+#define FILE_SYS_BLKSIZE(i) FILE_SYS(i).fs_blksize
+#define FILE_SYS_DEV(i) FILE_SYS(i).fs_dev
+
+
+/*
+** Current qf file field assignments:
+**
+** A AUTH= parameter
+** B body type
+** C controlling user
+** D data file name
+** d data file directory name (added in 8.12)
+** E error recipient
+** F flag bits
+** G free (was: queue delay algorithm if _FFR_QUEUEDELAY)
+** H header
+** I data file's inode number
+** K time of last delivery attempt
+** L Solaris Content-Length: header (obsolete)
+** M message
+** N number of delivery attempts
+** P message priority
+** q quarantine reason
+** Q original recipient (ORCPT=)
+** r final recipient (Final-Recipient: DSN field)
+** R recipient
+** S sender
+** T init time
+** V queue file version
+** X free (was: character set if _FFR_SAVE_CHARSET)
+** Y free (was: current delay if _FFR_QUEUEDELAY)
+** Z original envelope id from ESMTP
+** ! deliver by (added in 8.12)
+** $ define macro
+** . terminate file
+*/
+
+/*
+** QUEUEUP -- queue a message up for future transmission.
+**
+** Parameters:
+** e -- the envelope to queue up.
+** announce -- if true, tell when you are queueing up.
+** msync -- if true, then fsync() if SuperSafe interactive mode.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** The current request is saved in a control file.
+** The queue file is left locked.
+*/
+
+void
+queueup(e, announce, msync)
+ register ENVELOPE *e;
+ bool announce;
+ bool msync;
+{
+ register SM_FILE_T *tfp;
+ register HDR *h;
+ register ADDRESS *q;
+ int tfd = -1;
+ int i;
+ bool newid;
+ register char *p;
+ MAILER nullmailer;
+ MCI mcibuf;
+ char qf[MAXPATHLEN];
+ char tf[MAXPATHLEN];
+ char df[MAXPATHLEN];
+ char buf[MAXLINE];
+
+ /*
+ ** Create control file.
+ */
+
+#define OPEN_TF do \
+ { \
+ MODE_T oldumask = 0; \
+ \
+ if (bitset(S_IWGRP, QueueFileMode)) \
+ oldumask = umask(002); \
+ tfd = open(tf, TF_OPEN_FLAGS, QueueFileMode); \
+ if (bitset(S_IWGRP, QueueFileMode)) \
+ (void) umask(oldumask); \
+ } while (0)
+
+
+ newid = (e->e_id == NULL) || !bitset(EF_INQUEUE, e->e_flags);
+ (void) sm_strlcpy(tf, queuename(e, NEWQFL_LETTER), sizeof tf);
+ tfp = e->e_lockfp;
+ if (tfp == NULL && newid)
+ {
+ /*
+ ** open qf file directly: this will give an error if the file
+ ** already exists and hence prevent problems if a queue-id
+ ** is reused (e.g., because the clock is set back).
+ */
+
+ (void) sm_strlcpy(tf, queuename(e, ANYQFL_LETTER), sizeof tf);
+ OPEN_TF;
+ if (tfd < 0 ||
+#if !SM_OPEN_EXLOCK
+ !lockfile(tfd, tf, NULL, LOCK_EX|LOCK_NB) ||
+#endif /* !SM_OPEN_EXLOCK */
+ (tfp = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT,
+ (void *) &tfd, SM_IO_WRONLY,
+ NULL)) == NULL)
+ {
+ int save_errno = errno;
+
+ printopenfds(true);
+ errno = save_errno;
+ syserr("!queueup: cannot create queue file %s, euid=%d, fd=%d, fp=%p",
+ tf, (int) geteuid(), tfd, tfp);
+ /* NOTREACHED */
+ }
+ e->e_lockfp = tfp;
+ upd_qs(e, 1, 0, "queueup");
+ }
+
+ /* if newid, write the queue file directly (instead of temp file) */
+ if (!newid)
+ {
+ /* get a locked tf file */
+ for (i = 0; i < 128; i++)
+ {
+ if (tfd < 0)
+ {
+ OPEN_TF;
+ if (tfd < 0)
+ {
+ if (errno != EEXIST)
+ break;
+ if (LogLevel > 0 && (i % 32) == 0)
+ sm_syslog(LOG_ALERT, e->e_id,
+ "queueup: cannot create %s, uid=%d: %s",
+ tf, (int) geteuid(),
+ sm_errstring(errno));
+ }
+#if SM_OPEN_EXLOCK
+ else
+ break;
+#endif /* SM_OPEN_EXLOCK */
+ }
+ if (tfd >= 0)
+ {
+#if SM_OPEN_EXLOCK
+ /* file is locked by open() */
+ break;
+#else /* SM_OPEN_EXLOCK */
+ if (lockfile(tfd, tf, NULL, LOCK_EX|LOCK_NB))
+ break;
+ else
+#endif /* SM_OPEN_EXLOCK */
+ if (LogLevel > 0 && (i % 32) == 0)
+ sm_syslog(LOG_ALERT, e->e_id,
+ "queueup: cannot lock %s: %s",
+ tf, sm_errstring(errno));
+ if ((i % 32) == 31)
+ {
+ (void) close(tfd);
+ tfd = -1;
+ }
+ }
+
+ if ((i % 32) == 31)
+ {
+ /* save the old temp file away */
+ (void) rename(tf, queuename(e, TEMPQF_LETTER));
+ }
+ else
+ (void) sleep(i % 32);
+ }
+ if (tfd < 0 || (tfp = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT,
+ (void *) &tfd, SM_IO_WRONLY_B,
+ NULL)) == NULL)
+ {
+ int save_errno = errno;
+
+ printopenfds(true);
+ errno = save_errno;
+ syserr("!queueup: cannot create queue temp file %s, uid=%d",
+ tf, (int) geteuid());
+ }
+ }
+
+ if (tTd(40, 1))
+ sm_dprintf("\n>>>>> queueing %s/%s%s >>>>>\n",
+ qid_printqueue(e->e_qgrp, e->e_qdir),
+ queuename(e, ANYQFL_LETTER),
+ newid ? " (new id)" : "");
+ if (tTd(40, 3))
+ {
+ sm_dprintf(" e_flags=");
+ printenvflags(e);
+ }
+ if (tTd(40, 32))
+ {
+ sm_dprintf(" sendq=");
+ printaddr(sm_debug_file(), e->e_sendqueue, true);
+ }
+ if (tTd(40, 9))
+ {
+ sm_dprintf(" tfp=");
+ dumpfd(sm_io_getinfo(tfp, SM_IO_WHAT_FD, NULL), true, false);
+ sm_dprintf(" lockfp=");
+ if (e->e_lockfp == NULL)
+ sm_dprintf("NULL\n");
+ else
+ dumpfd(sm_io_getinfo(e->e_lockfp, SM_IO_WHAT_FD, NULL),
+ true, false);
+ }
+
+ /*
+ ** If there is no data file yet, create one.
+ */
+
+ (void) sm_strlcpy(df, queuename(e, DATAFL_LETTER), sizeof df);
+ if (bitset(EF_HAS_DF, e->e_flags))
+ {
+ if (e->e_dfp != NULL &&
+ SuperSafe != SAFE_REALLY &&
+ SuperSafe != SAFE_REALLY_POSTMILTER &&
+ sm_io_setinfo(e->e_dfp, SM_BF_COMMIT, NULL) < 0 &&
+ errno != EINVAL)
+ {
+ syserr("!queueup: cannot commit data file %s, uid=%d",
+ queuename(e, DATAFL_LETTER), (int) geteuid());
+ }
+ if (e->e_dfp != NULL &&
+ SuperSafe == SAFE_INTERACTIVE && msync)
+ {
+ if (tTd(40,32))
+ sm_syslog(LOG_INFO, e->e_id,
+ "queueup: fsync(e->e_dfp)");
+
+ if (fsync(sm_io_getinfo(e->e_dfp, SM_IO_WHAT_FD,
+ NULL)) < 0)
+ {
+ if (newid)
+ syserr("!552 Error writing data file %s",
+ df);
+ else
+ syserr("!452 Error writing data file %s",
+ df);
+ }
+ }
+ }
+ else
+ {
+ int dfd;
+ MODE_T oldumask = 0;
+ register SM_FILE_T *dfp = NULL;
+ struct stat stbuf;
+
+ if (e->e_dfp != NULL &&
+ sm_io_getinfo(e->e_dfp, SM_IO_WHAT_ISTYPE, BF_FILE_TYPE))
+ syserr("committing over bf file");
+
+ if (bitset(S_IWGRP, QueueFileMode))
+ oldumask = umask(002);
+ dfd = open(df, O_WRONLY|O_CREAT|O_TRUNC|QF_O_EXTRA,
+ QueueFileMode);
+ if (bitset(S_IWGRP, QueueFileMode))
+ (void) umask(oldumask);
+ if (dfd < 0 || (dfp = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT,
+ (void *) &dfd, SM_IO_WRONLY_B,
+ NULL)) == NULL)
+ syserr("!queueup: cannot create data temp file %s, uid=%d",
+ df, (int) geteuid());
+ if (fstat(dfd, &stbuf) < 0)
+ e->e_dfino = -1;
+ else
+ {
+ e->e_dfdev = stbuf.st_dev;
+ e->e_dfino = ST_INODE(stbuf);
+ }
+ e->e_flags |= EF_HAS_DF;
+ memset(&mcibuf, '\0', sizeof mcibuf);
+ mcibuf.mci_out = dfp;
+ mcibuf.mci_mailer = FileMailer;
+ (*e->e_putbody)(&mcibuf, e, NULL);
+
+ if (SuperSafe == SAFE_REALLY ||
+ SuperSafe == SAFE_REALLY_POSTMILTER ||
+ (SuperSafe == SAFE_INTERACTIVE && msync))
+ {
+ if (tTd(40,32))
+ sm_syslog(LOG_INFO, e->e_id,
+ "queueup: fsync(dfp)");
+
+ if (fsync(sm_io_getinfo(dfp, SM_IO_WHAT_FD, NULL)) < 0)
+ {
+ if (newid)
+ syserr("!552 Error writing data file %s",
+ df);
+ else
+ syserr("!452 Error writing data file %s",
+ df);
+ }
+ }
+
+ if (sm_io_close(dfp, SM_TIME_DEFAULT) < 0)
+ syserr("!queueup: cannot save data temp file %s, uid=%d",
+ df, (int) geteuid());
+ e->e_putbody = putbody;
+ }
+
+ /*
+ ** Output future work requests.
+ ** Priority and creation time should be first, since
+ ** they are required by gatherq.
+ */
+
+ /* output queue version number (must be first!) */
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "V%d\n", QF_VERSION);
+
+ /* output creation time */
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "T%ld\n", (long) e->e_ctime);
+
+ /* output last delivery time */
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "K%ld\n", (long) e->e_dtime);
+
+ /* output number of delivery attempts */
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "N%d\n", e->e_ntries);
+
+ /* output message priority */
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "P%ld\n", e->e_msgpriority);
+
+ /*
+ ** If data file is in a different directory than the queue file,
+ ** output a "d" record naming the directory of the data file.
+ */
+
+ if (e->e_dfqgrp != e->e_qgrp)
+ {
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "d%s\n",
+ Queue[e->e_dfqgrp]->qg_qpaths[e->e_dfqdir].qp_name);
+ }
+
+ /* output inode number of data file */
+ /* XXX should probably include device major/minor too */
+ if (e->e_dfino != -1)
+ {
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "I%ld/%ld/%llu\n",
+ (long) major(e->e_dfdev),
+ (long) minor(e->e_dfdev),
+ (ULONGLONG_T) e->e_dfino);
+ }
+
+ /* output body type */
+ if (e->e_bodytype != NULL)
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "B%s\n",
+ denlstring(e->e_bodytype, true, false));
+
+ /* quarantine reason */
+ if (e->e_quarmsg != NULL)
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "q%s\n",
+ denlstring(e->e_quarmsg, true, false));
+
+ /* message from envelope, if it exists */
+ if (e->e_message != NULL)
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "M%s\n",
+ denlstring(e->e_message, true, false));
+
+ /* send various flag bits through */
+ p = buf;
+ if (bitset(EF_WARNING, e->e_flags))
+ *p++ = 'w';
+ if (bitset(EF_RESPONSE, e->e_flags))
+ *p++ = 'r';
+ if (bitset(EF_HAS8BIT, e->e_flags))
+ *p++ = '8';
+ if (bitset(EF_DELETE_BCC, e->e_flags))
+ *p++ = 'b';
+ if (bitset(EF_RET_PARAM, e->e_flags))
+ *p++ = 'd';
+ if (bitset(EF_NO_BODY_RETN, e->e_flags))
+ *p++ = 'n';
+ if (bitset(EF_SPLIT, e->e_flags))
+ *p++ = 's';
+ *p++ = '\0';
+ if (buf[0] != '\0')
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "F%s\n", buf);
+
+ /* save $={persistentMacros} macro values */
+ queueup_macros(macid("{persistentMacros}"), tfp, e);
+
+ /* output name of sender */
+ if (bitnset(M_UDBENVELOPE, e->e_from.q_mailer->m_flags))
+ p = e->e_sender;
+ else
+ p = e->e_from.q_paddr;
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "S%s\n",
+ denlstring(p, true, false));
+
+ /* output ESMTP-supplied "original" information */
+ if (e->e_envid != NULL)
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "Z%s\n",
+ denlstring(e->e_envid, true, false));
+
+ /* output AUTH= parameter */
+ if (e->e_auth_param != NULL)
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "A%s\n",
+ denlstring(e->e_auth_param, true, false));
+ if (e->e_dlvr_flag != 0)
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "!%c %ld\n",
+ (char) e->e_dlvr_flag, e->e_deliver_by);
+
+ /* output list of recipient addresses */
+ printctladdr(NULL, NULL);
+ for (q = e->e_sendqueue; q != NULL; q = q->q_next)
+ {
+ if (!QS_IS_UNDELIVERED(q->q_state))
+ continue;
+
+ /* message for this recipient, if it exists */
+ if (q->q_message != NULL)
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "M%s\n",
+ denlstring(q->q_message, true,
+ false));
+
+ printctladdr(q, tfp);
+ if (q->q_orcpt != NULL)
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "Q%s\n",
+ denlstring(q->q_orcpt, true,
+ false));
+ if (q->q_finalrcpt != NULL)
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "r%s\n",
+ denlstring(q->q_finalrcpt, true,
+ false));
+ (void) sm_io_putc(tfp, SM_TIME_DEFAULT, 'R');
+ if (bitset(QPRIMARY, q->q_flags))
+ (void) sm_io_putc(tfp, SM_TIME_DEFAULT, 'P');
+ if (bitset(QHASNOTIFY, q->q_flags))
+ (void) sm_io_putc(tfp, SM_TIME_DEFAULT, 'N');
+ if (bitset(QPINGONSUCCESS, q->q_flags))
+ (void) sm_io_putc(tfp, SM_TIME_DEFAULT, 'S');
+ if (bitset(QPINGONFAILURE, q->q_flags))
+ (void) sm_io_putc(tfp, SM_TIME_DEFAULT, 'F');
+ if (bitset(QPINGONDELAY, q->q_flags))
+ (void) sm_io_putc(tfp, SM_TIME_DEFAULT, 'D');
+ if (q->q_alias != NULL &&
+ bitset(QALIAS, q->q_alias->q_flags))
+ (void) sm_io_putc(tfp, SM_TIME_DEFAULT, 'A');
+ (void) sm_io_putc(tfp, SM_TIME_DEFAULT, ':');
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "%s\n",
+ denlstring(q->q_paddr, true, false));
+ if (announce)
+ {
+ char *tag = "queued";
+
+ if (e->e_quarmsg != NULL)
+ tag = "quarantined";
+
+ e->e_to = q->q_paddr;
+ message(tag);
+ if (LogLevel > 8)
+ logdelivery(q->q_mailer, NULL, q->q_status,
+ tag, NULL, (time_t) 0, e);
+ e->e_to = NULL;
+ }
+ if (tTd(40, 1))
+ {
+ sm_dprintf("queueing ");
+ printaddr(sm_debug_file(), q, false);
+ }
+ }
+
+ /*
+ ** Output headers for this message.
+ ** Expand macros completely here. Queue run will deal with
+ ** everything as absolute headers.
+ ** All headers that must be relative to the recipient
+ ** can be cracked later.
+ ** We set up a "null mailer" -- i.e., a mailer that will have
+ ** no effect on the addresses as they are output.
+ */
+
+ memset((char *) &nullmailer, '\0', sizeof nullmailer);
+ nullmailer.m_re_rwset = nullmailer.m_rh_rwset =
+ nullmailer.m_se_rwset = nullmailer.m_sh_rwset = -1;
+ nullmailer.m_eol = "\n";
+ memset(&mcibuf, '\0', sizeof mcibuf);
+ mcibuf.mci_mailer = &nullmailer;
+ mcibuf.mci_out = tfp;
+
+ macdefine(&e->e_macro, A_PERM, 'g', "\201f");
+ for (h = e->e_header; h != NULL; h = h->h_link)
+ {
+ if (h->h_value == NULL)
+ continue;
+
+ /* don't output resent headers on non-resent messages */
+ if (bitset(H_RESENT, h->h_flags) &&
+ !bitset(EF_RESENT, e->e_flags))
+ continue;
+
+ /* expand macros; if null, don't output header at all */
+ if (bitset(H_DEFAULT, h->h_flags))
+ {
+ (void) expand(h->h_value, buf, sizeof buf, e);
+ if (buf[0] == '\0')
+ continue;
+ }
+
+ /* output this header */
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "H?");
+
+ /* output conditional macro if present */
+ if (h->h_macro != '\0')
+ {
+ if (bitset(0200, h->h_macro))
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT,
+ "${%s}",
+ macname(bitidx(h->h_macro)));
+ else
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT,
+ "$%c", h->h_macro);
+ }
+ else if (!bitzerop(h->h_mflags) &&
+ bitset(H_CHECK|H_ACHECK, h->h_flags))
+ {
+ int j;
+
+ /* if conditional, output the set of conditions */
+ for (j = '\0'; j <= '\177'; j++)
+ if (bitnset(j, h->h_mflags))
+ (void) sm_io_putc(tfp, SM_TIME_DEFAULT,
+ j);
+ }
+ (void) sm_io_putc(tfp, SM_TIME_DEFAULT, '?');
+
+ /* output the header: expand macros, convert addresses */
+ if (bitset(H_DEFAULT, h->h_flags) &&
+ !bitset(H_BINDLATE, h->h_flags))
+ {
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "%s: %s\n",
+ h->h_field,
+ denlstring(buf, false, true));
+ }
+ else if (bitset(H_FROM|H_RCPT, h->h_flags) &&
+ !bitset(H_BINDLATE, h->h_flags))
+ {
+ bool oldstyle = bitset(EF_OLDSTYLE, e->e_flags);
+ SM_FILE_T *savetrace = TrafficLogFile;
+
+ TrafficLogFile = NULL;
+
+ if (bitset(H_FROM, h->h_flags))
+ oldstyle = false;
+
+ commaize(h, h->h_value, oldstyle, &mcibuf, e);
+
+ TrafficLogFile = savetrace;
+ }
+ else
+ {
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "%s: %s\n",
+ h->h_field,
+ denlstring(h->h_value, false,
+ true));
+ }
+ }
+
+ /*
+ ** Clean up.
+ **
+ ** Write a terminator record -- this is to prevent
+ ** scurrilous crackers from appending any data.
+ */
+
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, ".\n");
+
+ if (sm_io_flush(tfp, SM_TIME_DEFAULT) != 0 ||
+ ((SuperSafe == SAFE_REALLY ||
+ SuperSafe == SAFE_REALLY_POSTMILTER ||
+ (SuperSafe == SAFE_INTERACTIVE && msync)) &&
+ fsync(sm_io_getinfo(tfp, SM_IO_WHAT_FD, NULL)) < 0) ||
+ sm_io_error(tfp))
+ {
+ if (newid)
+ syserr("!552 Error writing control file %s", tf);
+ else
+ syserr("!452 Error writing control file %s", tf);
+ }
+
+ if (!newid)
+ {
+ char new = queue_letter(e, ANYQFL_LETTER);
+
+ /* rename (locked) tf to be (locked) [qh]f */
+ (void) sm_strlcpy(qf, queuename(e, ANYQFL_LETTER),
+ sizeof qf);
+ if (rename(tf, qf) < 0)
+ syserr("cannot rename(%s, %s), uid=%d",
+ tf, qf, (int) geteuid());
+ else
+ {
+ /*
+ ** Check if type has changed and only
+ ** remove the old item if the rename above
+ ** succeeded.
+ */
+
+ if (e->e_qfletter != '\0' &&
+ e->e_qfletter != new)
+ {
+ if (tTd(40, 5))
+ {
+ sm_dprintf("type changed from %c to %c\n",
+ e->e_qfletter, new);
+ }
+
+ if (unlink(queuename(e, e->e_qfletter)) < 0)
+ {
+ /* XXX: something more drastic? */
+ if (LogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "queueup: unlink(%s) failed: %s",
+ queuename(e, e->e_qfletter),
+ sm_errstring(errno));
+ }
+ }
+ }
+ e->e_qfletter = new;
+
+ /*
+ ** fsync() after renaming to make sure metadata is
+ ** written to disk on filesystems in which renames are
+ ** not guaranteed.
+ */
+
+ if (SuperSafe != SAFE_NO)
+ {
+ /* for softupdates */
+ if (tfd >= 0 && fsync(tfd) < 0)
+ {
+ syserr("!queueup: cannot fsync queue temp file %s",
+ tf);
+ }
+ SYNC_DIR(qf, true);
+ }
+
+ /* close and unlock old (locked) queue file */
+ if (e->e_lockfp != NULL)
+ (void) sm_io_close(e->e_lockfp, SM_TIME_DEFAULT);
+ e->e_lockfp = tfp;
+
+ /* save log info */
+ if (LogLevel > 79)
+ sm_syslog(LOG_DEBUG, e->e_id, "queueup %s", qf);
+ }
+ else
+ {
+ /* save log info */
+ if (LogLevel > 79)
+ sm_syslog(LOG_DEBUG, e->e_id, "queueup %s", tf);
+
+ e->e_qfletter = queue_letter(e, ANYQFL_LETTER);
+ }
+
+ errno = 0;
+ e->e_flags |= EF_INQUEUE;
+
+ if (tTd(40, 1))
+ sm_dprintf("<<<<< done queueing %s <<<<<\n\n", e->e_id);
+ return;
+}
+
+/*
+** PRINTCTLADDR -- print control address to file.
+**
+** Parameters:
+** a -- address.
+** tfp -- file pointer.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** The control address (if changed) is printed to the file.
+** The last control address and uid are saved.
+*/
+
+static void
+printctladdr(a, tfp)
+ register ADDRESS *a;
+ SM_FILE_T *tfp;
+{
+ char *user;
+ register ADDRESS *q;
+ uid_t uid;
+ gid_t gid;
+ static ADDRESS *lastctladdr = NULL;
+ static uid_t lastuid;
+
+ /* initialization */
+ if (a == NULL || a->q_alias == NULL || tfp == NULL)
+ {
+ if (lastctladdr != NULL && tfp != NULL)
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "C\n");
+ lastctladdr = NULL;
+ lastuid = 0;
+ return;
+ }
+
+ /* find the active uid */
+ q = getctladdr(a);
+ if (q == NULL)
+ {
+ user = NULL;
+ uid = 0;
+ gid = 0;
+ }
+ else
+ {
+ user = q->q_ruser != NULL ? q->q_ruser : q->q_user;
+ uid = q->q_uid;
+ gid = q->q_gid;
+ }
+ a = a->q_alias;
+
+ /* check to see if this is the same as last time */
+ if (lastctladdr != NULL && uid == lastuid &&
+ strcmp(lastctladdr->q_paddr, a->q_paddr) == 0)
+ return;
+ lastuid = uid;
+ lastctladdr = a;
+
+ if (uid == 0 || user == NULL || user[0] == '\0')
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "C");
+ else
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "C%s:%ld:%ld",
+ denlstring(user, true, false), (long) uid,
+ (long) gid);
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, ":%s\n",
+ denlstring(a->q_paddr, true, false));
+}
+
+/*
+** RUNNERS_SIGTERM -- propagate a SIGTERM to queue runner process
+**
+** This propagates the signal to the child processes that are queue
+** runners. This is for a queue runner "cleanup". After all of the
+** child queue runner processes are signaled (it should be SIGTERM
+** being the sig) then the old signal handler (Oldsh) is called
+** to handle any cleanup set for this process (provided it is not
+** SIG_DFL or SIG_IGN). The signal may not be handled immediately
+** if the BlockOldsh flag is set. If the current process doesn't
+** have a parent then handle the signal immediately, regardless of
+** BlockOldsh.
+**
+** Parameters:
+** sig -- the signal number being sent
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Sets the NoMoreRunners boolean to true to stop more runners
+** from being started in runqueue().
+**
+** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+** DOING.
+*/
+
+static bool volatile NoMoreRunners = false;
+static sigfunc_t Oldsh_term = SIG_DFL;
+static sigfunc_t Oldsh_hup = SIG_DFL;
+static sigfunc_t volatile Oldsh = SIG_DFL;
+static bool BlockOldsh = false;
+static int volatile Oldsig = 0;
+static SIGFUNC_DECL runners_sigterm __P((int));
+static SIGFUNC_DECL runners_sighup __P((int));
+
+static SIGFUNC_DECL
+runners_sigterm(sig)
+ int sig;
+{
+ int save_errno = errno;
+
+ FIX_SYSV_SIGNAL(sig, runners_sigterm);
+ errno = save_errno;
+ CHECK_CRITICAL(sig);
+ NoMoreRunners = true;
+ Oldsh = Oldsh_term;
+ Oldsig = sig;
+ proc_list_signal(PROC_QUEUE, sig);
+
+ if (!BlockOldsh || getppid() <= 1)
+ {
+ /* Check that a valid 'old signal handler' is callable */
+ if (Oldsh_term != SIG_DFL && Oldsh_term != SIG_IGN &&
+ Oldsh_term != runners_sigterm)
+ (*Oldsh_term)(sig);
+ }
+ errno = save_errno;
+ return SIGFUNC_RETURN;
+}
+/*
+** RUNNERS_SIGHUP -- propagate a SIGHUP to queue runner process
+**
+** This propagates the signal to the child processes that are queue
+** runners. This is for a queue runner "cleanup". After all of the
+** child queue runner processes are signaled (it should be SIGHUP
+** being the sig) then the old signal handler (Oldsh) is called to
+** handle any cleanup set for this process (provided it is not SIG_DFL
+** or SIG_IGN). The signal may not be handled immediately if the
+** BlockOldsh flag is set. If the current process doesn't have
+** a parent then handle the signal immediately, regardless of
+** BlockOldsh.
+**
+** Parameters:
+** sig -- the signal number being sent
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Sets the NoMoreRunners boolean to true to stop more runners
+** from being started in runqueue().
+**
+** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+** DOING.
+*/
+
+static SIGFUNC_DECL
+runners_sighup(sig)
+ int sig;
+{
+ int save_errno = errno;
+
+ FIX_SYSV_SIGNAL(sig, runners_sighup);
+ errno = save_errno;
+ CHECK_CRITICAL(sig);
+ NoMoreRunners = true;
+ Oldsh = Oldsh_hup;
+ Oldsig = sig;
+ proc_list_signal(PROC_QUEUE, sig);
+
+ if (!BlockOldsh || getppid() <= 1)
+ {
+ /* Check that a valid 'old signal handler' is callable */
+ if (Oldsh_hup != SIG_DFL && Oldsh_hup != SIG_IGN &&
+ Oldsh_hup != runners_sighup)
+ (*Oldsh_hup)(sig);
+ }
+ errno = save_errno;
+ return SIGFUNC_RETURN;
+}
+/*
+** MARK_WORK_GROUP_RESTART -- mark a work group as needing a restart
+**
+** Sets a workgroup for restarting.
+**
+** Parameters:
+** wgrp -- the work group id to restart.
+** reason -- why (signal?), -1 to turn off restart
+**
+** Returns:
+** none.
+**
+** Side effects:
+** May set global RestartWorkGroup to true.
+**
+** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+** DOING.
+*/
+
+void
+mark_work_group_restart(wgrp, reason)
+ int wgrp;
+ int reason;
+{
+ if (wgrp < 0 || wgrp > NumWorkGroups)
+ return;
+
+ WorkGrp[wgrp].wg_restart = reason;
+ if (reason >= 0)
+ RestartWorkGroup = true;
+}
+/*
+** RESTART_MARKED_WORK_GROUPS -- restart work groups marked as needing restart
+**
+** Restart any workgroup marked as needing a restart provided more
+** runners are allowed.
+**
+** Parameters:
+** none.
+**
+** Returns:
+** none.
+**
+** Side effects:
+** Sets global RestartWorkGroup to false.
+*/
+
+void
+restart_marked_work_groups()
+{
+ int i;
+ int wasblocked;
+
+ if (NoMoreRunners)
+ return;
+
+ /* Block SIGCHLD so reapchild() doesn't mess with us */
+ wasblocked = sm_blocksignal(SIGCHLD);
+
+ for (i = 0; i < NumWorkGroups; i++)
+ {
+ if (WorkGrp[i].wg_restart >= 0)
+ {
+ if (LogLevel > 8)
+ sm_syslog(LOG_ERR, NOQID,
+ "restart queue runner=%d due to signal 0x%x",
+ i, WorkGrp[i].wg_restart);
+ restart_work_group(i);
+ }
+ }
+ RestartWorkGroup = false;
+
+ if (wasblocked == 0)
+ (void) sm_releasesignal(SIGCHLD);
+}
+/*
+** RESTART_WORK_GROUP -- restart a specific work group
+**
+** Restart a specific workgroup provided more runners are allowed.
+** If the requested work group has been restarted too many times log
+** this and refuse to restart.
+**
+** Parameters:
+** wgrp -- the work group id to restart
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** starts another process doing the work of wgrp
+*/
+
+#define MAX_PERSIST_RESTART 10 /* max allowed number of restarts */
+
+static void
+restart_work_group(wgrp)
+ int wgrp;
+{
+ if (NoMoreRunners ||
+ wgrp < 0 || wgrp > NumWorkGroups)
+ return;
+
+ WorkGrp[wgrp].wg_restart = -1;
+ if (WorkGrp[wgrp].wg_restartcnt < MAX_PERSIST_RESTART)
+ {
+ /* avoid overflow; increment here */
+ WorkGrp[wgrp].wg_restartcnt++;
+ (void) run_work_group(wgrp, RWG_FORK|RWG_PERSISTENT|RWG_RUNALL);
+ }
+ else
+ {
+ sm_syslog(LOG_ERR, NOQID,
+ "ERROR: persistent queue runner=%d restarted too many times, queue runner lost",
+ wgrp);
+ }
+}
+/*
+** SCHEDULE_QUEUE_RUNS -- schedule the next queue run for a work group.
+**
+** Parameters:
+** runall -- schedule even if individual bit is not set.
+** wgrp -- the work group id to schedule.
+** didit -- the queue run was performed for this work group.
+**
+** Returns:
+** nothing
+*/
+
+#define INCR_MOD(v, m) if (++v >= m) \
+ v = 0; \
+ else
+
+static void
+schedule_queue_runs(runall, wgrp, didit)
+ bool runall;
+ int wgrp;
+ bool didit;
+{
+ int qgrp, cgrp, endgrp;
+#if _FFR_QUEUE_SCHED_DBG
+ time_t lastsched;
+ bool sched;
+#endif /* _FFR_QUEUE_SCHED_DBG */
+ time_t now;
+ time_t minqintvl;
+
+ /*
+ ** This is a bit ugly since we have to duplicate the
+ ** code that "walks" through a work queue group.
+ */
+
+ now = curtime();
+ minqintvl = 0;
+ cgrp = endgrp = WorkGrp[wgrp].wg_curqgrp;
+ do
+ {
+ time_t qintvl;
+
+#if _FFR_QUEUE_SCHED_DBG
+ lastsched = 0;
+ sched = false;
+#endif /* _FFR_QUEUE_SCHED_DBG */
+ qgrp = WorkGrp[wgrp].wg_qgs[cgrp]->qg_index;
+ if (Queue[qgrp]->qg_queueintvl > 0)
+ qintvl = Queue[qgrp]->qg_queueintvl;
+ else if (QueueIntvl > 0)
+ qintvl = QueueIntvl;
+ else
+ qintvl = (time_t) 0;
+#if _FFR_QUEUE_SCHED_DBG
+ lastsched = Queue[qgrp]->qg_nextrun;
+#endif /* _FFR_QUEUE_SCHED_DBG */
+ if ((runall || Queue[qgrp]->qg_nextrun <= now) && qintvl > 0)
+ {
+#if _FFR_QUEUE_SCHED_DBG
+ sched = true;
+#endif /* _FFR_QUEUE_SCHED_DBG */
+ if (minqintvl == 0 || qintvl < minqintvl)
+ minqintvl = qintvl;
+
+ /*
+ ** Only set a new time if a queue run was performed
+ ** for this queue group. If the queue was not run,
+ ** we could starve it by setting a new time on each
+ ** call.
+ */
+
+ if (didit)
+ Queue[qgrp]->qg_nextrun += qintvl;
+ }
+#if _FFR_QUEUE_SCHED_DBG
+ if (tTd(69, 10))
+ sm_syslog(LOG_INFO, NOQID,
+ "sqr: wgrp=%d, cgrp=%d, qgrp=%d, intvl=%ld, QI=%ld, runall=%d, lastrun=%ld, nextrun=%ld, sched=%d",
+ wgrp, cgrp, qgrp, Queue[qgrp]->qg_queueintvl,
+ QueueIntvl, runall, lastsched,
+ Queue[qgrp]->qg_nextrun, sched);
+#endif /* _FFR_QUEUE_SCHED_DBG */
+ INCR_MOD(cgrp, WorkGrp[wgrp].wg_numqgrp);
+ } while (endgrp != cgrp);
+ if (minqintvl > 0)
+ (void) sm_setevent(minqintvl, runqueueevent, 0);
+}
+
+#if _FFR_QUEUE_RUN_PARANOIA
+/*
+** CHECKQUEUERUNNER -- check whether a queue group hasn't been run.
+**
+** Use this if events may get lost and hence queue runners may not
+** be started and mail will pile up in a queue.
+**
+** Parameters:
+** none.
+**
+** Returns:
+** true if a queue run is necessary.
+**
+** Side Effects:
+** may schedule a queue run.
+*/
+
+bool
+checkqueuerunner()
+{
+ int qgrp;
+ time_t now, minqintvl;
+
+ now = curtime();
+ minqintvl = 0;
+ for (qgrp = 0; qgrp < NumQueue && Queue[qgrp] != NULL; qgrp++)
+ {
+ time_t qintvl;
+
+ if (Queue[qgrp]->qg_queueintvl > 0)
+ qintvl = Queue[qgrp]->qg_queueintvl;
+ else if (QueueIntvl > 0)
+ qintvl = QueueIntvl;
+ else
+ qintvl = (time_t) 0;
+ if (Queue[qgrp]->qg_nextrun <= now - qintvl)
+ {
+ if (minqintvl == 0 || qintvl < minqintvl)
+ minqintvl = qintvl;
+ if (LogLevel > 1)
+ sm_syslog(LOG_WARNING, NOQID,
+ "checkqueuerunner: queue %d should have been run at %s, queue interval %ld",
+ qgrp,
+ arpadate(ctime(&Queue[qgrp]->qg_nextrun)),
+ qintvl);
+ }
+ }
+ if (minqintvl > 0)
+ {
+ (void) sm_setevent(minqintvl, runqueueevent, 0);
+ return true;
+ }
+ return false;
+}
+#endif /* _FFR_QUEUE_RUN_PARANOIA */
+
+/*
+** RUNQUEUE -- run the jobs in the queue.
+**
+** Gets the stuff out of the queue in some presumably logical
+** order and processes them.
+**
+** Parameters:
+** forkflag -- true if the queue scanning should be done in
+** a child process. We double-fork so it is not our
+** child and we don't have to clean up after it.
+** false can be ignored if we have multiple queues.
+** verbose -- if true, print out status information.
+** persistent -- persistent queue runner?
+** runall -- run all groups or only a subset (DoQueueRun)?
+**
+** Returns:
+** true if the queue run successfully began.
+**
+** Side Effects:
+** runs things in the mail queue using run_work_group().
+** maybe schedules next queue run.
+*/
+
+static ENVELOPE QueueEnvelope; /* the queue run envelope */
+static time_t LastQueueTime = 0; /* last time a queue ID assigned */
+static pid_t LastQueuePid = -1; /* last PID which had a queue ID */
+
+/* values for qp_supdirs */
+#define QP_NOSUB 0x0000 /* No subdirectories */
+#define QP_SUBDF 0x0001 /* "df" subdirectory */
+#define QP_SUBQF 0x0002 /* "qf" subdirectory */
+#define QP_SUBXF 0x0004 /* "xf" subdirectory */
+
+bool
+runqueue(forkflag, verbose, persistent, runall)
+ bool forkflag;
+ bool verbose;
+ bool persistent;
+ bool runall;
+{
+ int i;
+ bool ret = true;
+ static int curnum = 0;
+ sigfunc_t cursh;
+#if SM_HEAP_CHECK
+ SM_NONVOLATILE int oldgroup = 0;
+
+ if (sm_debug_active(&DebugLeakQ, 1))
+ {
+ oldgroup = sm_heap_group();
+ sm_heap_newgroup();
+ sm_dprintf("runqueue() heap group #%d\n", sm_heap_group());
+ }
+#endif /* SM_HEAP_CHECK */
+
+ /* queue run has been started, don't do any more this time */
+ DoQueueRun = false;
+
+ /* more than one queue or more than one directory per queue */
+ if (!forkflag && !verbose &&
+ (WorkGrp[0].wg_qgs[0]->qg_numqueues > 1 || NumWorkGroups > 1 ||
+ WorkGrp[0].wg_numqgrp > 1))
+ forkflag = true;
+
+ /*
+ ** For controlling queue runners via signals sent to this process.
+ ** Oldsh* will get called too by runners_sig* (if it is not SIG_IGN
+ ** or SIG_DFL) to preserve cleanup behavior. Now that this process
+ ** will have children (and perhaps grandchildren) this handler will
+ ** be left in place. This is because this process, once it has
+ ** finished spinning off queue runners, may go back to doing something
+ ** else (like being a daemon). And we still want on a SIG{TERM,HUP} to
+ ** clean up the child queue runners. Only install 'runners_sig*' once
+ ** else we'll get stuck looping forever.
+ */
+
+ cursh = sm_signal(SIGTERM, runners_sigterm);
+ if (cursh != runners_sigterm)
+ Oldsh_term = cursh;
+ cursh = sm_signal(SIGHUP, runners_sighup);
+ if (cursh != runners_sighup)
+ Oldsh_hup = cursh;
+
+ for (i = 0; i < NumWorkGroups && !NoMoreRunners; i++)
+ {
+ int rwgflags = RWG_NONE;
+
+ /*
+ ** If MaxQueueChildren active then test whether the start
+ ** of the next queue group's additional queue runners (maximum)
+ ** will result in MaxQueueChildren being exceeded.
+ **
+ ** Note: do not use continue; even though another workgroup
+ ** may have fewer queue runners, this would be "unfair",
+ ** i.e., this work group might "starve" then.
+ */
+
+#if _FFR_QUEUE_SCHED_DBG
+ if (tTd(69, 10))
+ sm_syslog(LOG_INFO, NOQID,
+ "rq: curnum=%d, MaxQueueChildren=%d, CurRunners=%d, WorkGrp[curnum].wg_maxact=%d",
+ curnum, MaxQueueChildren, CurRunners,
+ WorkGrp[curnum].wg_maxact);
+#endif /* _FFR_QUEUE_SCHED_DBG */
+ if (MaxQueueChildren > 0 &&
+ CurRunners + WorkGrp[curnum].wg_maxact > MaxQueueChildren)
+ break;
+
+ /*
+ ** Pick up where we left off (curnum), in case we
+ ** used up all the children last time without finishing.
+ ** This give a round-robin fairness to queue runs.
+ **
+ ** Increment CurRunners before calling run_work_group()
+ ** to avoid a "race condition" with proc_list_drop() which
+ ** decrements CurRunners if the queue runners terminate.
+ ** Notice: CurRunners is an upper limit, in some cases
+ ** (too few jobs in the queue) this value is larger than
+ ** the actual number of queue runners. The discrepancy can
+ ** increase if some queue runners "hang" for a long time.
+ */
+
+ CurRunners += WorkGrp[curnum].wg_maxact;
+ if (forkflag)
+ rwgflags |= RWG_FORK;
+ if (verbose)
+ rwgflags |= RWG_VERBOSE;
+ if (persistent)
+ rwgflags |= RWG_PERSISTENT;
+ if (runall)
+ rwgflags |= RWG_RUNALL;
+ ret = run_work_group(curnum, rwgflags);
+
+ /*
+ ** Failure means a message was printed for ETRN
+ ** and subsequent queues are likely to fail as well.
+ ** Decrement CurRunners in that case because
+ ** none have been started.
+ */
+
+ if (!ret)
+ {
+ CurRunners -= WorkGrp[curnum].wg_maxact;
+ break;
+ }
+
+ if (!persistent)
+ schedule_queue_runs(runall, curnum, true);
+ INCR_MOD(curnum, NumWorkGroups);
+ }
+
+ /* schedule left over queue runs */
+ if (i < NumWorkGroups && !NoMoreRunners && !persistent)
+ {
+ int h;
+
+ for (h = curnum; i < NumWorkGroups; i++)
+ {
+ schedule_queue_runs(runall, h, false);
+ INCR_MOD(h, NumWorkGroups);
+ }
+ }
+
+
+#if SM_HEAP_CHECK
+ if (sm_debug_active(&DebugLeakQ, 1))
+ sm_heap_setgroup(oldgroup);
+#endif /* SM_HEAP_CHECK */
+ return ret;
+}
+
+#if _FFR_SKIP_DOMAINS
+/*
+** SKIP_DOMAINS -- Skip 'skip' number of domains in the WorkQ.
+**
+** Added by Stephen Frost <sfrost@snowman.net> to support
+** having each runner process every N'th domain instead of
+** every N'th message.
+**
+** Parameters:
+** skip -- number of domains in WorkQ to skip.
+**
+** Returns:
+** total number of messages skipped.
+**
+** Side Effects:
+** may change WorkQ
+*/
+
+static int
+skip_domains(skip)
+ int skip;
+{
+ int n, seqjump;
+
+ for (n = 0, seqjump = 0; n < skip && WorkQ != NULL; seqjump++)
+ {
+ if (WorkQ->w_next != NULL)
+ {
+ if (WorkQ->w_host != NULL &&
+ WorkQ->w_next->w_host != NULL)
+ {
+ if (sm_strcasecmp(WorkQ->w_host,
+ WorkQ->w_next->w_host) != 0)
+ n++;
+ }
+ else
+ {
+ if ((WorkQ->w_host != NULL &&
+ WorkQ->w_next->w_host == NULL) ||
+ (WorkQ->w_host == NULL &&
+ WorkQ->w_next->w_host != NULL))
+ n++;
+ }
+ }
+ WorkQ = WorkQ->w_next;
+ }
+ return seqjump;
+}
+#endif /* _FFR_SKIP_DOMAINS */
+
+/*
+** RUNNER_WORK -- have a queue runner do its work
+**
+** Have a queue runner do its work a list of entries.
+** When work isn't directly being done then this process can take a signal
+** and terminate immediately (in a clean fashion of course).
+** When work is directly being done, it's not to be interrupted
+** immediately: the work should be allowed to finish at a clean point
+** before termination (in a clean fashion of course).
+**
+** Parameters:
+** e -- envelope.
+** sequenceno -- 'th process to run WorkQ.
+** didfork -- did the calling process fork()?
+** skip -- process only each skip'th item.
+** njobs -- number of jobs in WorkQ.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** runs things in the mail queue.
+*/
+
+static void
+runner_work(e, sequenceno, didfork, skip, njobs)
+ register ENVELOPE *e;
+ int sequenceno;
+ bool didfork;
+ int skip;
+ int njobs;
+{
+ int n, seqjump;
+ WORK *w;
+ time_t now;
+
+ SM_GET_LA(now);
+
+ /*
+ ** Here we temporarily block the second calling of the handlers.
+ ** This allows us to handle the signal without terminating in the
+ ** middle of direct work. If a signal does come, the test for
+ ** NoMoreRunners will find it.
+ */
+
+ BlockOldsh = true;
+ seqjump = skip;
+
+ /* process them once at a time */
+ while (WorkQ != NULL)
+ {
+#if SM_HEAP_CHECK
+ SM_NONVOLATILE int oldgroup = 0;
+
+ if (sm_debug_active(&DebugLeakQ, 1))
+ {
+ oldgroup = sm_heap_group();
+ sm_heap_newgroup();
+ sm_dprintf("run_queue_group() heap group #%d\n",
+ sm_heap_group());
+ }
+#endif /* SM_HEAP_CHECK */
+
+ /* do no more work */
+ if (NoMoreRunners)
+ {
+ /* Check that a valid signal handler is callable */
+ if (Oldsh != SIG_DFL && Oldsh != SIG_IGN &&
+ Oldsh != runners_sighup &&
+ Oldsh != runners_sigterm)
+ (*Oldsh)(Oldsig);
+ break;
+ }
+
+ w = WorkQ; /* assign current work item */
+
+ /*
+ ** Set the head of the WorkQ to the next work item.
+ ** It is set 'skip' ahead (the number of parallel queue
+ ** runners working on WorkQ together) since each runner
+ ** works on every 'skip'th (N-th) item.
+#if _FFR_SKIP_DOMAINS
+ ** In the case of the BYHOST Queue Sort Order, the 'item'
+ ** is a domain, so we work on every 'skip'th (N-th) domain.
+#endif * _FFR_SKIP_DOMAINS *
+ */
+
+#if _FFR_SKIP_DOMAINS
+ if (QueueSortOrder == QSO_BYHOST)
+ {
+ seqjump = 1;
+ if (WorkQ->w_next != NULL)
+ {
+ if (WorkQ->w_host != NULL &&
+ WorkQ->w_next->w_host != NULL)
+ {
+ if (sm_strcasecmp(WorkQ->w_host,
+ WorkQ->w_next->w_host)
+ != 0)
+ seqjump = skip_domains(skip);
+ else
+ WorkQ = WorkQ->w_next;
+ }
+ else
+ {
+ if ((WorkQ->w_host != NULL &&
+ WorkQ->w_next->w_host == NULL) ||
+ (WorkQ->w_host == NULL &&
+ WorkQ->w_next->w_host != NULL))
+ seqjump = skip_domains(skip);
+ else
+ WorkQ = WorkQ->w_next;
+ }
+ }
+ else
+ WorkQ = WorkQ->w_next;
+ }
+ else
+#endif /* _FFR_SKIP_DOMAINS */
+ {
+ for (n = 0; n < skip && WorkQ != NULL; n++)
+ WorkQ = WorkQ->w_next;
+ }
+
+ e->e_to = NULL;
+
+ /*
+ ** Ignore jobs that are too expensive for the moment.
+ **
+ ** Get new load average every GET_NEW_LA_TIME seconds.
+ */
+
+ SM_GET_LA(now);
+ if (shouldqueue(WkRecipFact, Current_LA_time))
+ {
+ char *msg = "Aborting queue run: load average too high";
+
+ if (Verbose)
+ message("%s", msg);
+ if (LogLevel > 8)
+ sm_syslog(LOG_INFO, NOQID, "runqueue: %s", msg);
+ break;
+ }
+ if (shouldqueue(w->w_pri, w->w_ctime))
+ {
+ if (Verbose)
+ message(EmptyString);
+ if (QueueSortOrder == QSO_BYPRIORITY)
+ {
+ if (Verbose)
+ message("Skipping %s/%s (sequence %d of %d) and flushing rest of queue",
+ qid_printqueue(w->w_qgrp,
+ w->w_qdir),
+ w->w_name + 2, sequenceno,
+ njobs);
+ if (LogLevel > 8)
+ sm_syslog(LOG_INFO, NOQID,
+ "runqueue: Flushing queue from %s/%s (pri %ld, LA %d, %d of %d)",
+ qid_printqueue(w->w_qgrp,
+ w->w_qdir),
+ w->w_name + 2, w->w_pri,
+ CurrentLA, sequenceno,
+ njobs);
+ break;
+ }
+ else if (Verbose)
+ message("Skipping %s/%s (sequence %d of %d)",
+ qid_printqueue(w->w_qgrp, w->w_qdir),
+ w->w_name + 2, sequenceno, njobs);
+ }
+ else
+ {
+ if (Verbose)
+ {
+ message(EmptyString);
+ message("Running %s/%s (sequence %d of %d)",
+ qid_printqueue(w->w_qgrp, w->w_qdir),
+ w->w_name + 2, sequenceno, njobs);
+ }
+ if (didfork && MaxQueueChildren > 0)
+ {
+ sm_blocksignal(SIGCHLD);
+ (void) sm_signal(SIGCHLD, reapchild);
+ }
+ if (tTd(63, 100))
+ sm_syslog(LOG_DEBUG, NOQID,
+ "runqueue %s dowork(%s)",
+ qid_printqueue(w->w_qgrp, w->w_qdir),
+ w->w_name + 2);
+
+ (void) dowork(w->w_qgrp, w->w_qdir, w->w_name + 2,
+ ForkQueueRuns, false, e);
+ errno = 0;
+ }
+ sm_free(w->w_name); /* XXX */
+ if (w->w_host != NULL)
+ sm_free(w->w_host); /* XXX */
+ sm_free((char *) w); /* XXX */
+ sequenceno += seqjump; /* next sequence number */
+#if SM_HEAP_CHECK
+ if (sm_debug_active(&DebugLeakQ, 1))
+ sm_heap_setgroup(oldgroup);
+#endif /* SM_HEAP_CHECK */
+ }
+
+ BlockOldsh = false;
+
+ /* check the signals didn't happen during the revert */
+ if (NoMoreRunners)
+ {
+ /* Check that a valid signal handler is callable */
+ if (Oldsh != SIG_DFL && Oldsh != SIG_IGN &&
+ Oldsh != runners_sighup && Oldsh != runners_sigterm)
+ (*Oldsh)(Oldsig);
+ }
+
+ Oldsh = SIG_DFL; /* after the NoMoreRunners check */
+}
+/*
+** RUN_WORK_GROUP -- run the jobs in a queue group from a work group.
+**
+** Gets the stuff out of the queue in some presumably logical
+** order and processes them.
+**
+** Parameters:
+** wgrp -- work group to process.
+** flags -- RWG_* flags
+**
+** Returns:
+** true if the queue run successfully began.
+**
+** Side Effects:
+** runs things in the mail queue.
+*/
+
+/* Minimum sleep time for persistent queue runners */
+#define MIN_SLEEP_TIME 5
+
+bool
+run_work_group(wgrp, flags)
+ int wgrp;
+ int flags;
+{
+ register ENVELOPE *e;
+ int njobs, qdir;
+ int sequenceno = 1;
+ int qgrp, endgrp, h, i;
+ time_t now;
+ bool full, more;
+ SM_RPOOL_T *rpool;
+ extern void rmexpstab __P((void));
+ extern ENVELOPE BlankEnvelope;
+ extern SIGFUNC_DECL reapchild __P((int));
+
+ if (wgrp < 0)
+ return false;
+
+ /*
+ ** If no work will ever be selected, don't even bother reading
+ ** the queue.
+ */
+
+ SM_GET_LA(now);
+
+ if (!bitset(RWG_PERSISTENT, flags) &&
+ shouldqueue(WkRecipFact, Current_LA_time))
+ {
+ char *msg = "Skipping queue run -- load average too high";
+
+ if (bitset(RWG_VERBOSE, flags))
+ message("458 %s\n", msg);
+ if (LogLevel > 8)
+ sm_syslog(LOG_INFO, NOQID, "runqueue: %s", msg);
+ return false;
+ }
+
+ /*
+ ** See if we already have too many children.
+ */
+
+ if (bitset(RWG_FORK, flags) &&
+ WorkGrp[wgrp].wg_lowqintvl > 0 &&
+ !bitset(RWG_PERSISTENT, flags) &&
+ MaxChildren > 0 && CurChildren >= MaxChildren)
+ {
+ char *msg = "Skipping queue run -- too many children";
+
+ if (bitset(RWG_VERBOSE, flags))
+ message("458 %s (%d)\n", msg, CurChildren);
+ if (LogLevel > 8)
+ sm_syslog(LOG_INFO, NOQID, "runqueue: %s (%d)",
+ msg, CurChildren);
+ return false;
+ }
+
+ /*
+ ** See if we want to go off and do other useful work.
+ */
+
+ if (bitset(RWG_FORK, flags))
+ {
+ pid_t pid;
+
+ (void) sm_blocksignal(SIGCHLD);
+ (void) sm_signal(SIGCHLD, reapchild);
+
+ pid = dofork();
+ if (pid == -1)
+ {
+ const char *msg = "Skipping queue run -- fork() failed";
+ const char *err = sm_errstring(errno);
+
+ if (bitset(RWG_VERBOSE, flags))
+ message("458 %s: %s\n", msg, err);
+ if (LogLevel > 8)
+ sm_syslog(LOG_INFO, NOQID, "runqueue: %s: %s",
+ msg, err);
+ (void) sm_releasesignal(SIGCHLD);
+ return false;
+ }
+ if (pid != 0)
+ {
+ /* parent -- pick up intermediate zombie */
+ (void) sm_blocksignal(SIGALRM);
+
+ /* wgrp only used when queue runners are persistent */
+ proc_list_add(pid, "Queue runner", PROC_QUEUE,
+ WorkGrp[wgrp].wg_maxact,
+ bitset(RWG_PERSISTENT, flags) ? wgrp : -1,
+ NULL);
+ (void) sm_releasesignal(SIGALRM);
+ (void) sm_releasesignal(SIGCHLD);
+ return true;
+ }
+
+ /* child -- clean up signals */
+
+ /* Reset global flags */
+ RestartRequest = NULL;
+ RestartWorkGroup = false;
+ ShutdownRequest = NULL;
+ PendingSignal = 0;
+ CurrentPid = getpid();
+ close_sendmail_pid();
+
+ /*
+ ** Initialize exception stack and default exception
+ ** handler for child process.
+ */
+
+ sm_exc_newthread(fatal_error);
+ clrcontrol();
+ proc_list_clear();
+
+ /* Add parent process as first child item */
+ proc_list_add(CurrentPid, "Queue runner child process",
+ PROC_QUEUE_CHILD, 0, -1, NULL);
+ (void) sm_releasesignal(SIGCHLD);
+ (void) sm_signal(SIGCHLD, SIG_DFL);
+ (void) sm_signal(SIGHUP, SIG_DFL);
+ (void) sm_signal(SIGTERM, intsig);
+ }
+
+ /*
+ ** Release any resources used by the daemon code.
+ */
+
+ clrdaemon();
+
+ /* force it to run expensive jobs */
+ NoConnect = false;
+
+ /* drop privileges */
+ if (geteuid() == (uid_t) 0)
+ (void) drop_privileges(false);
+
+ /*
+ ** Create ourselves an envelope
+ */
+
+ CurEnv = &QueueEnvelope;
+ rpool = sm_rpool_new_x(NULL);
+ e = newenvelope(&QueueEnvelope, CurEnv, rpool);
+ e->e_flags = BlankEnvelope.e_flags;
+ e->e_parent = NULL;
+
+ /* make sure we have disconnected from parent */
+ if (bitset(RWG_FORK, flags))
+ {
+ disconnect(1, e);
+ QuickAbort = false;
+ }
+
+ /*
+ ** If we are running part of the queue, always ignore stored
+ ** host status.
+ */
+
+ if (QueueLimitId != NULL || QueueLimitSender != NULL ||
+ QueueLimitQuarantine != NULL ||
+ QueueLimitRecipient != NULL)
+ {
+ IgnoreHostStatus = true;
+ MinQueueAge = 0;
+ }
+
+ /*
+ ** Here is where we choose the queue group from the work group.
+ ** The caller of the "domorework" label must setup a new envelope.
+ */
+
+ endgrp = WorkGrp[wgrp].wg_curqgrp; /* to not spin endlessly */
+
+ domorework:
+
+ /*
+ ** Run a queue group if:
+ ** RWG_RUNALL bit is set or the bit for this group is set.
+ */
+
+ now = curtime();
+ for (;;)
+ {
+ /*
+ ** Find the next queue group within the work group that
+ ** has been marked as needing a run.
+ */
+
+ qgrp = WorkGrp[wgrp].wg_qgs[WorkGrp[wgrp].wg_curqgrp]->qg_index;
+ WorkGrp[wgrp].wg_curqgrp++; /* advance */
+ WorkGrp[wgrp].wg_curqgrp %= WorkGrp[wgrp].wg_numqgrp; /* wrap */
+ if (bitset(RWG_RUNALL, flags) ||
+ (Queue[qgrp]->qg_nextrun <= now &&
+ Queue[qgrp]->qg_nextrun != (time_t) -1))
+ break;
+ if (endgrp == WorkGrp[wgrp].wg_curqgrp)
+ {
+ e->e_id = NULL;
+ if (bitset(RWG_FORK, flags))
+ finis(true, true, ExitStat);
+ return true; /* we're done */
+ }
+ }
+
+ qdir = Queue[qgrp]->qg_curnum; /* round-robin init of queue position */
+#if _FFR_QUEUE_SCHED_DBG
+ if (tTd(69, 12))
+ sm_syslog(LOG_INFO, NOQID,
+ "rwg: wgrp=%d, qgrp=%d, qdir=%d, name=%s, curqgrp=%d, numgrps=%d",
+ wgrp, qgrp, qdir, qid_printqueue(qgrp, qdir),
+ WorkGrp[wgrp].wg_curqgrp, WorkGrp[wgrp].wg_numqgrp);
+#endif /* _FFR_QUEUE_SCHED_DBG */
+
+#if HASNICE
+ /* tweak niceness of queue runs */
+ if (Queue[qgrp]->qg_nice > 0)
+ (void) nice(Queue[qgrp]->qg_nice);
+#endif /* HASNICE */
+
+ /* XXX running queue group... */
+ sm_setproctitle(true, CurEnv, "running queue: %s",
+ qid_printqueue(qgrp, qdir));
+
+ if (LogLevel > 69 || tTd(63, 99))
+ sm_syslog(LOG_DEBUG, NOQID,
+ "runqueue %s, pid=%d, forkflag=%d",
+ qid_printqueue(qgrp, qdir), (int) CurrentPid,
+ bitset(RWG_FORK, flags));
+
+ /*
+ ** Start making passes through the queue.
+ ** First, read and sort the entire queue.
+ ** Then, process the work in that order.
+ ** But if you take too long, start over.
+ */
+
+ for (i = 0; i < Queue[qgrp]->qg_numqueues; i++)
+ {
+ h = gatherq(qgrp, qdir, false, &full, &more);
+#if SM_CONF_SHM
+ if (ShmId != SM_SHM_NO_ID)
+ QSHM_ENTRIES(Queue[qgrp]->qg_qpaths[qdir].qp_idx) = h;
+#endif /* SM_CONF_SHM */
+ /* If there are no more items in this queue advance */
+ if (!more)
+ {
+ /* A round-robin advance */
+ qdir++;
+ qdir %= Queue[qgrp]->qg_numqueues;
+ }
+
+ /* Has the WorkList reached the limit? */
+ if (full)
+ break; /* don't try to gather more */
+ }
+
+ /* order the existing work requests */
+ njobs = sortq(Queue[qgrp]->qg_maxlist);
+ Queue[qgrp]->qg_curnum = qdir; /* update */
+
+
+ if (!Verbose && bitnset(QD_FORK, Queue[qgrp]->qg_flags))
+ {
+ int loop, maxrunners;
+ pid_t pid;
+
+ /*
+ ** For this WorkQ we want to fork off N children (maxrunners)
+ ** at this point. Each child has a copy of WorkQ. Each child
+ ** will process every N-th item. The parent will wait for all
+ ** of the children to finish before moving on to the next
+ ** queue group within the work group. This saves us forking
+ ** a new runner-child for each work item.
+ ** It's valid for qg_maxqrun == 0 since this may be an
+ ** explicit "don't run this queue" setting.
+ */
+
+ maxrunners = Queue[qgrp]->qg_maxqrun;
+
+ /* No need to have more runners then there are jobs */
+ if (maxrunners > njobs)
+ maxrunners = njobs;
+ for (loop = 0; loop < maxrunners; loop++)
+ {
+ /*
+ ** Since the delivery may happen in a child and the
+ ** parent does not wait, the parent may close the
+ ** maps thereby removing any shared memory used by
+ ** the map. Therefore, close the maps now so the
+ ** child will dynamically open them if necessary.
+ */
+
+ closemaps(false);
+
+ pid = fork();
+ if (pid < 0)
+ {
+ syserr("run_work_group: cannot fork");
+ return false;
+ }
+ else if (pid > 0)
+ {
+ /* parent -- clean out connection cache */
+ mci_flush(false, NULL);
+#if _FFR_SKIP_DOMAINS
+ if (QueueSortOrder == QSO_BYHOST)
+ {
+ sequenceno += skip_domains(1);
+ }
+ else
+#endif /* _FFR_SKIP_DOMAINS */
+ {
+ /* for the skip */
+ WorkQ = WorkQ->w_next;
+ sequenceno++;
+ }
+ proc_list_add(pid, "Queue child runner process",
+ PROC_QUEUE_CHILD, 0, -1, NULL);
+
+ /* No additional work, no additional runners */
+ if (WorkQ == NULL)
+ break;
+ }
+ else
+ {
+ /* child -- Reset global flags */
+ RestartRequest = NULL;
+ RestartWorkGroup = false;
+ ShutdownRequest = NULL;
+ PendingSignal = 0;
+ CurrentPid = getpid();
+ close_sendmail_pid();
+
+ /*
+ ** Initialize exception stack and default
+ ** exception handler for child process.
+ ** When fork()'d the child now has a private
+ ** copy of WorkQ at its current position.
+ */
+
+ sm_exc_newthread(fatal_error);
+
+ /*
+ ** SMTP processes (whether -bd or -bs) set
+ ** SIGCHLD to reapchild to collect
+ ** children status. However, at delivery
+ ** time, that status must be collected
+ ** by sm_wait() to be dealt with properly
+ ** (check success of delivery based
+ ** on status code, etc). Therefore, if we
+ ** are an SMTP process, reset SIGCHLD
+ ** back to the default so reapchild
+ ** doesn't collect status before
+ ** sm_wait().
+ */
+
+ if (OpMode == MD_SMTP ||
+ OpMode == MD_DAEMON ||
+ MaxQueueChildren > 0)
+ {
+ proc_list_clear();
+ sm_releasesignal(SIGCHLD);
+ (void) sm_signal(SIGCHLD, SIG_DFL);
+ }
+
+ /* child -- error messages to the transcript */
+ QuickAbort = OnlyOneError = false;
+ runner_work(e, sequenceno, true,
+ maxrunners, njobs);
+
+ /* This child is done */
+ finis(true, true, ExitStat);
+ /* NOTREACHED */
+ }
+ }
+
+ sm_releasesignal(SIGCHLD);
+
+ /*
+ ** Wait until all of the runners have completed before
+ ** seeing if there is another queue group in the
+ ** work group to process.
+ ** XXX Future enhancement: don't wait() for all children
+ ** here, just go ahead and make sure that overall the number
+ ** of children is not exceeded.
+ */
+
+ while (CurChildren > 0)
+ {
+ int status;
+ pid_t ret;
+
+ while ((ret = sm_wait(&status)) <= 0)
+ continue;
+ proc_list_drop(ret, status, NULL);
+ }
+ }
+ else if (Queue[qgrp]->qg_maxqrun > 0 || bitset(RWG_FORCE, flags))
+ {
+ /*
+ ** When current process will not fork children to do the work,
+ ** it will do the work itself. The 'skip' will be 1 since
+ ** there are no child runners to divide the work across.
+ */
+
+ runner_work(e, sequenceno, false, 1, njobs);
+ }
+
+ /* free memory allocated by newenvelope() above */
+ sm_rpool_free(rpool);
+ QueueEnvelope.e_rpool = NULL;
+
+ /* Are there still more queues in the work group to process? */
+ if (endgrp != WorkGrp[wgrp].wg_curqgrp)
+ {
+ rpool = sm_rpool_new_x(NULL);
+ e = newenvelope(&QueueEnvelope, CurEnv, rpool);
+ e->e_flags = BlankEnvelope.e_flags;
+ goto domorework;
+ }
+
+ /* No more queues in work group to process. Now check persistent. */
+ if (bitset(RWG_PERSISTENT, flags))
+ {
+ sequenceno = 1;
+ sm_setproctitle(true, CurEnv, "running queue: %s",
+ qid_printqueue(qgrp, qdir));
+
+ /*
+ ** close bogus maps, i.e., maps which caused a tempfail,
+ ** so we get fresh map connections on the next lookup.
+ ** closemaps() is also called when children are started.
+ */
+
+ closemaps(true);
+
+ /* Close any cached connections. */
+ mci_flush(true, NULL);
+
+ /* Clean out expired related entries. */
+ rmexpstab();
+
+#if NAMED_BIND
+ /* Update MX records for FallbackMX. */
+ if (FallbackMX != NULL)
+ (void) getfallbackmxrr(FallbackMX);
+#endif /* NAMED_BIND */
+
+#if USERDB
+ /* close UserDatabase */
+ _udbx_close();
+#endif /* USERDB */
+
+#if SM_HEAP_CHECK
+ if (sm_debug_active(&SmHeapCheck, 2)
+ && access("memdump", F_OK) == 0
+ )
+ {
+ SM_FILE_T *out;
+
+ remove("memdump");
+ out = sm_io_open(SmFtStdio, SM_TIME_DEFAULT,
+ "memdump.out", SM_IO_APPEND, NULL);
+ if (out != NULL)
+ {
+ (void) sm_io_fprintf(out, SM_TIME_DEFAULT, "----------------------\n");
+ sm_heap_report(out,
+ sm_debug_level(&SmHeapCheck) - 1);
+ (void) sm_io_close(out, SM_TIME_DEFAULT);
+ }
+ }
+#endif /* SM_HEAP_CHECK */
+
+ /* let me rest for a second to catch my breath */
+ if (njobs == 0 && WorkGrp[wgrp].wg_lowqintvl < MIN_SLEEP_TIME)
+ sleep(MIN_SLEEP_TIME);
+ else if (WorkGrp[wgrp].wg_lowqintvl <= 0)
+ sleep(QueueIntvl > 0 ? QueueIntvl : MIN_SLEEP_TIME);
+ else
+ sleep(WorkGrp[wgrp].wg_lowqintvl);
+
+ /*
+ ** Get the LA outside the WorkQ loop if necessary.
+ ** In a persistent queue runner the code is repeated over
+ ** and over but gatherq() may ignore entries due to
+ ** shouldqueue() (do we really have to do this twice?).
+ ** Hence the queue runners would just idle around when once
+ ** CurrentLA caused all entries in a queue to be ignored.
+ */
+
+ if (njobs == 0)
+ SM_GET_LA(now);
+ rpool = sm_rpool_new_x(NULL);
+ e = newenvelope(&QueueEnvelope, CurEnv, rpool);
+ e->e_flags = BlankEnvelope.e_flags;
+ goto domorework;
+ }
+
+ /* exit without the usual cleanup */
+ e->e_id = NULL;
+ if (bitset(RWG_FORK, flags))
+ finis(true, true, ExitStat);
+ /* NOTREACHED */
+ return true;
+}
+
+/*
+** DOQUEUERUN -- do a queue run?
+*/
+
+bool
+doqueuerun()
+{
+ return DoQueueRun;
+}
+
+/*
+** RUNQUEUEEVENT -- Sets a flag to indicate that a queue run should be done.
+**
+** Parameters:
+** none.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** The invocation of this function via an alarm may interrupt
+** a set of actions. Thus errno may be set in that context.
+** We need to restore errno at the end of this function to ensure
+** that any work done here that sets errno doesn't return a
+** misleading/false errno value. Errno may be EINTR upon entry to
+** this function because of non-restartable/continuable system
+** API was active. Iff this is true we will override errno as
+** a timeout (as a more accurate error message).
+**
+** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+** DOING.
+*/
+
+void
+runqueueevent(ignore)
+ int ignore;
+{
+ int save_errno = errno;
+
+ /*
+ ** Set the general bit that we want a queue run,
+ ** tested in doqueuerun()
+ */
+
+ DoQueueRun = true;
+#if _FFR_QUEUE_SCHED_DBG
+ if (tTd(69, 10))
+ sm_syslog(LOG_INFO, NOQID, "rqe: done");
+#endif /* _FFR_QUEUE_SCHED_DBG */
+
+ errno = save_errno;
+ if (errno == EINTR)
+ errno = ETIMEDOUT;
+}
+/*
+** GATHERQ -- gather messages from the message queue(s) the work queue.
+**
+** Parameters:
+** qgrp -- the index of the queue group.
+** qdir -- the index of the queue directory.
+** doall -- if set, include everything in the queue (even
+** the jobs that cannot be run because the load
+** average is too high, or MaxQueueRun is reached).
+** Otherwise, exclude those jobs.
+** full -- (optional) to be set 'true' if WorkList is full
+** more -- (optional) to be set 'true' if there are still more
+** messages in this queue not added to WorkList
+**
+** Returns:
+** The number of request in the queue (not necessarily
+** the number of requests in WorkList however).
+**
+** Side Effects:
+** prepares available work into WorkList
+*/
+
+#define NEED_P 0001 /* 'P': priority */
+#define NEED_T 0002 /* 'T': time */
+#define NEED_R 0004 /* 'R': recipient */
+#define NEED_S 0010 /* 'S': sender */
+#define NEED_H 0020 /* host */
+#define HAS_QUARANTINE 0040 /* has an unexpected 'q' line */
+#define NEED_QUARANTINE 0100 /* 'q': reason */
+
+static WORK *WorkList = NULL; /* list of unsort work */
+static int WorkListSize = 0; /* current max size of WorkList */
+static int WorkListCount = 0; /* # of work items in WorkList */
+
+static int
+gatherq(qgrp, qdir, doall, full, more)
+ int qgrp;
+ int qdir;
+ bool doall;
+ bool *full;
+ bool *more;
+{
+ register struct dirent *d;
+ register WORK *w;
+ register char *p;
+ DIR *f;
+ int i, num_ent;
+ int wn;
+ QUEUE_CHAR *check;
+ char qd[MAXPATHLEN];
+ char qf[MAXPATHLEN];
+
+ wn = WorkListCount - 1;
+ num_ent = 0;
+ if (qdir == NOQDIR)
+ (void) sm_strlcpy(qd, ".", sizeof qd);
+ else
+ (void) sm_strlcpyn(qd, sizeof qd, 2,
+ Queue[qgrp]->qg_qpaths[qdir].qp_name,
+ (bitset(QP_SUBQF,
+ Queue[qgrp]->qg_qpaths[qdir].qp_subdirs)
+ ? "/qf" : ""));
+
+ if (tTd(41, 1))
+ {
+ sm_dprintf("gatherq:\n");
+
+ check = QueueLimitId;
+ while (check != NULL)
+ {
+ sm_dprintf("\tQueueLimitId = %s%s\n",
+ check->queue_negate ? "!" : "",
+ check->queue_match);
+ check = check->queue_next;
+ }
+
+ check = QueueLimitSender;
+ while (check != NULL)
+ {
+ sm_dprintf("\tQueueLimitSender = %s%s\n",
+ check->queue_negate ? "!" : "",
+ check->queue_match);
+ check = check->queue_next;
+ }
+
+ check = QueueLimitRecipient;
+ while (check != NULL)
+ {
+ sm_dprintf("\tQueueLimitRecipient = %s%s\n",
+ check->queue_negate ? "!" : "",
+ check->queue_match);
+ check = check->queue_next;
+ }
+
+ if (QueueMode == QM_QUARANTINE)
+ {
+ check = QueueLimitQuarantine;
+ while (check != NULL)
+ {
+ sm_dprintf("\tQueueLimitQuarantine = %s%s\n",
+ check->queue_negate ? "!" : "",
+ check->queue_match);
+ check = check->queue_next;
+ }
+ }
+ }
+
+ /* open the queue directory */
+ f = opendir(qd);
+ if (f == NULL)
+ {
+ syserr("gatherq: cannot open \"%s\"",
+ qid_printqueue(qgrp, qdir));
+ if (full != NULL)
+ *full = WorkListCount >= MaxQueueRun && MaxQueueRun > 0;
+ if (more != NULL)
+ *more = false;
+ return 0;
+ }
+
+ /*
+ ** Read the work directory.
+ */
+
+ while ((d = readdir(f)) != NULL)
+ {
+ SM_FILE_T *cf;
+ int qfver = 0;
+ char lbuf[MAXNAME + 1];
+ struct stat sbuf;
+
+ if (tTd(41, 50))
+ sm_dprintf("gatherq: checking %s..", d->d_name);
+
+ /* is this an interesting entry? */
+ if (!(((QueueMode == QM_NORMAL &&
+ d->d_name[0] == NORMQF_LETTER) ||
+ (QueueMode == QM_QUARANTINE &&
+ d->d_name[0] == QUARQF_LETTER) ||
+ (QueueMode == QM_LOST &&
+ d->d_name[0] == LOSEQF_LETTER)) &&
+ d->d_name[1] == 'f'))
+ {
+ if (tTd(41, 50))
+ sm_dprintf(" skipping\n");
+ continue;
+ }
+ if (tTd(41, 50))
+ sm_dprintf("\n");
+
+ if (strlen(d->d_name) >= MAXQFNAME)
+ {
+ if (Verbose)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "gatherq: %s too long, %d max characters\n",
+ d->d_name, MAXQFNAME);
+ if (LogLevel > 0)
+ sm_syslog(LOG_ALERT, NOQID,
+ "gatherq: %s too long, %d max characters",
+ d->d_name, MAXQFNAME);
+ continue;
+ }
+
+ check = QueueLimitId;
+ while (check != NULL)
+ {
+ if (strcontainedin(false, check->queue_match,
+ d->d_name) != check->queue_negate)
+ break;
+ else
+ check = check->queue_next;
+ }
+ if (QueueLimitId != NULL && check == NULL)
+ continue;
+
+ /* grow work list if necessary */
+ if (++wn >= MaxQueueRun && MaxQueueRun > 0)
+ {
+ if (wn == MaxQueueRun && LogLevel > 0)
+ sm_syslog(LOG_WARNING, NOQID,
+ "WorkList for %s maxed out at %d",
+ qid_printqueue(qgrp, qdir),
+ MaxQueueRun);
+ if (doall)
+ continue; /* just count entries */
+ break;
+ }
+ if (wn >= WorkListSize)
+ {
+ grow_wlist(qgrp, qdir);
+ if (wn >= WorkListSize)
+ continue;
+ }
+ SM_ASSERT(wn >= 0);
+ w = &WorkList[wn];
+
+ (void) sm_strlcpyn(qf, sizeof qf, 3, qd, "/", d->d_name);
+ if (stat(qf, &sbuf) < 0)
+ {
+ if (errno != ENOENT)
+ sm_syslog(LOG_INFO, NOQID,
+ "gatherq: can't stat %s/%s",
+ qid_printqueue(qgrp, qdir),
+ d->d_name);
+ wn--;
+ continue;
+ }
+ if (!bitset(S_IFREG, sbuf.st_mode))
+ {
+ /* Yikes! Skip it or we will hang on open! */
+ if (!((d->d_name[0] == DATAFL_LETTER ||
+ d->d_name[0] == NORMQF_LETTER ||
+ d->d_name[0] == QUARQF_LETTER ||
+ d->d_name[0] == LOSEQF_LETTER ||
+ d->d_name[0] == XSCRPT_LETTER) &&
+ d->d_name[1] == 'f' && d->d_name[2] == '\0'))
+ syserr("gatherq: %s/%s is not a regular file",
+ qid_printqueue(qgrp, qdir), d->d_name);
+ wn--;
+ continue;
+ }
+
+ /* avoid work if possible */
+ if ((QueueSortOrder == QSO_BYFILENAME ||
+ QueueSortOrder == QSO_BYMODTIME ||
+ QueueSortOrder == QSO_RANDOM) &&
+ QueueLimitQuarantine == NULL &&
+ QueueLimitSender == NULL &&
+ QueueLimitRecipient == NULL)
+ {
+ w->w_qgrp = qgrp;
+ w->w_qdir = qdir;
+ w->w_name = newstr(d->d_name);
+ w->w_host = NULL;
+ w->w_lock = w->w_tooyoung = false;
+ w->w_pri = 0;
+ w->w_ctime = 0;
+ w->w_mtime = sbuf.st_mtime;
+ ++num_ent;
+ continue;
+ }
+
+ /* open control file */
+ cf = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, qf, SM_IO_RDONLY_B,
+ NULL);
+ if (cf == NULL && OpMode != MD_PRINT)
+ {
+ /* this may be some random person sending hir msgs */
+ if (tTd(41, 2))
+ sm_dprintf("gatherq: cannot open %s: %s\n",
+ d->d_name, sm_errstring(errno));
+ errno = 0;
+ wn--;
+ continue;
+ }
+ w->w_qgrp = qgrp;
+ w->w_qdir = qdir;
+ w->w_name = newstr(d->d_name);
+ w->w_host = NULL;
+ if (cf != NULL)
+ {
+ w->w_lock = !lockfile(sm_io_getinfo(cf, SM_IO_WHAT_FD,
+ NULL),
+ w->w_name, NULL,
+ LOCK_SH|LOCK_NB);
+ }
+ w->w_tooyoung = false;
+
+ /* make sure jobs in creation don't clog queue */
+ w->w_pri = 0x7fffffff;
+ w->w_ctime = 0;
+ w->w_mtime = sbuf.st_mtime;
+
+ /* extract useful information */
+ i = NEED_P|NEED_T;
+ if (QueueSortOrder == QSO_BYHOST
+#if _FFR_RHS
+ || QueueSortOrder == QSO_BYSHUFFLE
+#endif /* _FFR_RHS */
+ )
+ {
+ /* need w_host set for host sort order */
+ i |= NEED_H;
+ }
+ if (QueueLimitSender != NULL)
+ i |= NEED_S;
+ if (QueueLimitRecipient != NULL)
+ i |= NEED_R;
+ if (QueueLimitQuarantine != NULL)
+ i |= NEED_QUARANTINE;
+ while (cf != NULL && i != 0 &&
+ sm_io_fgets(cf, SM_TIME_DEFAULT, lbuf,
+ sizeof lbuf) != NULL)
+ {
+ int c;
+ time_t age;
+
+ p = strchr(lbuf, '\n');
+ if (p != NULL)
+ *p = '\0';
+ else
+ {
+ /* flush rest of overly long line */
+ while ((c = sm_io_getc(cf, SM_TIME_DEFAULT))
+ != SM_IO_EOF && c != '\n')
+ continue;
+ }
+
+ switch (lbuf[0])
+ {
+ case 'V':
+ qfver = atoi(&lbuf[1]);
+ break;
+
+ case 'P':
+ w->w_pri = atol(&lbuf[1]);
+ i &= ~NEED_P;
+ break;
+
+ case 'T':
+ w->w_ctime = atol(&lbuf[1]);
+ i &= ~NEED_T;
+ break;
+
+ case 'q':
+ if (QueueMode != QM_QUARANTINE &&
+ QueueMode != QM_LOST)
+ {
+ if (tTd(41, 49))
+ sm_dprintf("%s not marked as quarantined but has a 'q' line\n",
+ w->w_name);
+ i |= HAS_QUARANTINE;
+ }
+ else if (QueueMode == QM_QUARANTINE)
+ {
+ if (QueueLimitQuarantine == NULL)
+ {
+ i &= ~NEED_QUARANTINE;
+ break;
+ }
+ p = &lbuf[1];
+ check = QueueLimitQuarantine;
+ while (check != NULL)
+ {
+ if (strcontainedin(false,
+ check->queue_match,
+ p) !=
+ check->queue_negate)
+ break;
+ else
+ check = check->queue_next;
+ }
+ if (check != NULL)
+ i &= ~NEED_QUARANTINE;
+ }
+ break;
+
+ case 'R':
+ if (w->w_host == NULL &&
+ (p = strrchr(&lbuf[1], '@')) != NULL)
+ {
+#if _FFR_RHS
+ if (QueueSortOrder == QSO_BYSHUFFLE)
+ w->w_host = newstr(&p[1]);
+ else
+#endif /* _FFR_RHS */
+ w->w_host = strrev(&p[1]);
+ makelower(w->w_host);
+ i &= ~NEED_H;
+ }
+ if (QueueLimitRecipient == NULL)
+ {
+ i &= ~NEED_R;
+ break;
+ }
+ if (qfver > 0)
+ {
+ p = strchr(&lbuf[1], ':');
+ if (p == NULL)
+ p = &lbuf[1];
+ else
+ ++p; /* skip over ':' */
+ }
+ else
+ p = &lbuf[1];
+ check = QueueLimitRecipient;
+ while (check != NULL)
+ {
+ if (strcontainedin(true,
+ check->queue_match,
+ p) !=
+ check->queue_negate)
+ break;
+ else
+ check = check->queue_next;
+ }
+ if (check != NULL)
+ i &= ~NEED_R;
+ break;
+
+ case 'S':
+ check = QueueLimitSender;
+ while (check != NULL)
+ {
+ if (strcontainedin(true,
+ check->queue_match,
+ &lbuf[1]) !=
+ check->queue_negate)
+ break;
+ else
+ check = check->queue_next;
+ }
+ if (check != NULL)
+ i &= ~NEED_S;
+ break;
+
+ case 'K':
+ age = curtime() - (time_t) atol(&lbuf[1]);
+ if (age >= 0 && MinQueueAge > 0 &&
+ age < MinQueueAge)
+ w->w_tooyoung = true;
+ break;
+
+ case 'N':
+ if (atol(&lbuf[1]) == 0)
+ w->w_tooyoung = false;
+ break;
+ }
+ }
+ if (cf != NULL)
+ (void) sm_io_close(cf, SM_TIME_DEFAULT);
+
+ if ((!doall && shouldqueue(w->w_pri, w->w_ctime)) ||
+ bitset(HAS_QUARANTINE, i) ||
+ bitset(NEED_QUARANTINE, i) ||
+ bitset(NEED_R|NEED_S, i))
+ {
+ /* don't even bother sorting this job in */
+ if (tTd(41, 49))
+ sm_dprintf("skipping %s (%x)\n", w->w_name, i);
+ sm_free(w->w_name); /* XXX */
+ if (w->w_host != NULL)
+ sm_free(w->w_host); /* XXX */
+ wn--;
+ }
+ else
+ ++num_ent;
+ }
+ (void) closedir(f);
+ wn++;
+
+ i = wn - WorkListCount;
+ WorkListCount += SM_MIN(num_ent, WorkListSize);
+
+ if (more != NULL)
+ *more = WorkListCount < wn;
+
+ if (full != NULL)
+ *full = (wn >= MaxQueueRun && MaxQueueRun > 0) ||
+ (WorkList == NULL && wn > 0);
+
+ return i;
+}
+/*
+** SORTQ -- sort the work list
+**
+** First the old WorkQ is cleared away. Then the WorkList is sorted
+** for all items so that important (higher sorting value) items are not
+** trunctated off. Then the most important items are moved from
+** WorkList to WorkQ. The lower count of 'max' or MaxListCount items
+** are moved.
+**
+** Parameters:
+** max -- maximum number of items to be placed in WorkQ
+**
+** Returns:
+** the number of items in WorkQ
+**
+** Side Effects:
+** WorkQ gets released and filled with new work. WorkList
+** gets released. Work items get sorted in order.
+*/
+
+static int
+sortq(max)
+ int max;
+{
+ register int i; /* local counter */
+ register WORK *w; /* tmp item pointer */
+ int wc = WorkListCount; /* trim size for WorkQ */
+
+ if (WorkQ != NULL)
+ {
+ WORK *nw;
+
+ /* Clear out old WorkQ. */
+ for (w = WorkQ; w != NULL; w = nw)
+ {
+ nw = w->w_next;
+ sm_free(w->w_name); /* XXX */
+ if (w->w_host != NULL)
+ sm_free(w->w_host); /* XXX */
+ sm_free((char *) w); /* XXX */
+ }
+ WorkQ = NULL;
+ }
+
+ if (WorkList == NULL || wc <= 0)
+ return 0;
+
+ /* Check if the per queue group item limit will be exceeded */
+ if (wc > max && max > 0)
+ wc = max;
+
+ /*
+ ** The sort now takes place using all of the items in WorkList.
+ ** The list gets trimmed to the most important items after the sort.
+ ** If the trim were to happen before the sort then one or more
+ ** important items might get truncated off -- not what we want.
+ */
+
+ if (QueueSortOrder == QSO_BYHOST)
+ {
+ /*
+ ** Sort the work directory for the first time,
+ ** based on host name, lock status, and priority.
+ */
+
+ qsort((char *) WorkList, wc, sizeof *WorkList, workcmpf1);
+
+ /*
+ ** If one message to host is locked, "lock" all messages
+ ** to that host.
+ */
+
+ i = 0;
+ while (i < wc)
+ {
+ if (!WorkList[i].w_lock)
+ {
+ i++;
+ continue;
+ }
+ w = &WorkList[i];
+ while (++i < wc)
+ {
+ if (WorkList[i].w_host == NULL &&
+ w->w_host == NULL)
+ WorkList[i].w_lock = true;
+ else if (WorkList[i].w_host != NULL &&
+ w->w_host != NULL &&
+ sm_strcasecmp(WorkList[i].w_host,
+ w->w_host) == 0)
+ WorkList[i].w_lock = true;
+ else
+ break;
+ }
+ }
+
+ /*
+ ** Sort the work directory for the second time,
+ ** based on lock status, host name, and priority.
+ */
+
+ qsort((char *) WorkList, wc, sizeof *WorkList, workcmpf2);
+ }
+ else if (QueueSortOrder == QSO_BYTIME)
+ {
+ /*
+ ** Simple sort based on submission time only.
+ */
+
+ qsort((char *) WorkList, wc, sizeof *WorkList, workcmpf3);
+ }
+ else if (QueueSortOrder == QSO_BYFILENAME)
+ {
+ /*
+ ** Sort based on queue filename.
+ */
+
+ qsort((char *) WorkList, wc, sizeof *WorkList, workcmpf4);
+ }
+ else if (QueueSortOrder == QSO_RANDOM)
+ {
+ /*
+ ** Sort randomly. To avoid problems with an instable sort,
+ ** use a random index into the queue file name to start
+ ** comparison.
+ */
+
+ randi = get_rand_mod(MAXQFNAME);
+ if (randi < 2)
+ randi = 3;
+ qsort((char *) WorkList, wc, sizeof *WorkList, workcmpf5);
+ }
+ else if (QueueSortOrder == QSO_BYMODTIME)
+ {
+ /*
+ ** Simple sort based on modification time of queue file.
+ ** This puts the oldest items first.
+ */
+
+ qsort((char *) WorkList, wc, sizeof *WorkList, workcmpf6);
+ }
+#if _FFR_RHS
+ else if (QueueSortOrder == QSO_BYSHUFFLE)
+ {
+ /*
+ ** Simple sort based on shuffled host name.
+ */
+
+ init_shuffle_alphabet();
+ qsort((char *) WorkList, wc, sizeof *WorkList, workcmpf7);
+ }
+#endif /* _FFR_RHS */
+ else if (QueueSortOrder == QSO_BYPRIORITY)
+ {
+ /*
+ ** Simple sort based on queue priority only.
+ */
+
+ qsort((char *) WorkList, wc, sizeof *WorkList, workcmpf0);
+ }
+ /* else don't sort at all */
+
+ /*
+ ** Convert the work list into canonical form.
+ ** Should be turning it into a list of envelopes here perhaps.
+ ** Only take the most important items up to the per queue group
+ ** maximum.
+ */
+
+ for (i = wc; --i >= 0; )
+ {
+ w = (WORK *) xalloc(sizeof *w);
+ w->w_qgrp = WorkList[i].w_qgrp;
+ w->w_qdir = WorkList[i].w_qdir;
+ w->w_name = WorkList[i].w_name;
+ w->w_host = WorkList[i].w_host;
+ w->w_lock = WorkList[i].w_lock;
+ w->w_tooyoung = WorkList[i].w_tooyoung;
+ w->w_pri = WorkList[i].w_pri;
+ w->w_ctime = WorkList[i].w_ctime;
+ w->w_mtime = WorkList[i].w_mtime;
+ w->w_next = WorkQ;
+ WorkQ = w;
+ }
+
+ /* free the rest of the list */
+ for (i = WorkListCount; --i >= wc; )
+ {
+ sm_free(WorkList[i].w_name);
+ if (WorkList[i].w_host != NULL)
+ sm_free(WorkList[i].w_host);
+ }
+
+ if (WorkList != NULL)
+ sm_free(WorkList); /* XXX */
+ WorkList = NULL;
+ WorkListSize = 0;
+ WorkListCount = 0;
+
+ if (tTd(40, 1))
+ {
+ for (w = WorkQ; w != NULL; w = w->w_next)
+ {
+ if (w->w_host != NULL)
+ sm_dprintf("%22s: pri=%ld %s\n",
+ w->w_name, w->w_pri, w->w_host);
+ else
+ sm_dprintf("%32s: pri=%ld\n",
+ w->w_name, w->w_pri);
+ }
+ }
+
+ return wc; /* return number of WorkQ items */
+}
+/*
+** GROW_WLIST -- make the work list larger
+**
+** Parameters:
+** qgrp -- the index for the queue group.
+** qdir -- the index for the queue directory.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Adds another QUEUESEGSIZE entries to WorkList if possible.
+** It can fail if there isn't enough memory, so WorkListSize
+** should be checked again upon return.
+*/
+
+static void
+grow_wlist(qgrp, qdir)
+ int qgrp;
+ int qdir;
+{
+ if (tTd(41, 1))
+ sm_dprintf("grow_wlist: WorkListSize=%d\n", WorkListSize);
+ if (WorkList == NULL)
+ {
+ WorkList = (WORK *) xalloc((sizeof *WorkList) *
+ (QUEUESEGSIZE + 1));
+ WorkListSize = QUEUESEGSIZE;
+ }
+ else
+ {
+ int newsize = WorkListSize + QUEUESEGSIZE;
+ WORK *newlist = (WORK *) sm_realloc((char *) WorkList,
+ (unsigned) sizeof(WORK) * (newsize + 1));
+
+ if (newlist != NULL)
+ {
+ WorkListSize = newsize;
+ WorkList = newlist;
+ if (LogLevel > 1)
+ {
+ sm_syslog(LOG_INFO, NOQID,
+ "grew WorkList for %s to %d",
+ qid_printqueue(qgrp, qdir),
+ WorkListSize);
+ }
+ }
+ else if (LogLevel > 0)
+ {
+ sm_syslog(LOG_ALERT, NOQID,
+ "FAILED to grow WorkList for %s to %d",
+ qid_printqueue(qgrp, qdir), newsize);
+ }
+ }
+ if (tTd(41, 1))
+ sm_dprintf("grow_wlist: WorkListSize now %d\n", WorkListSize);
+}
+/*
+** WORKCMPF0 -- simple priority-only compare function.
+**
+** Parameters:
+** a -- the first argument.
+** b -- the second argument.
+**
+** Returns:
+** -1 if a < b
+** 0 if a == b
+** +1 if a > b
+**
+*/
+
+static int
+workcmpf0(a, b)
+ register WORK *a;
+ register WORK *b;
+{
+ long pa = a->w_pri;
+ long pb = b->w_pri;
+
+ if (pa == pb)
+ return 0;
+ else if (pa > pb)
+ return 1;
+ else
+ return -1;
+}
+/*
+** WORKCMPF1 -- first compare function for ordering work based on host name.
+**
+** Sorts on host name, lock status, and priority in that order.
+**
+** Parameters:
+** a -- the first argument.
+** b -- the second argument.
+**
+** Returns:
+** <0 if a < b
+** 0 if a == b
+** >0 if a > b
+**
+*/
+
+static int
+workcmpf1(a, b)
+ register WORK *a;
+ register WORK *b;
+{
+ int i;
+
+ /* host name */
+ if (a->w_host != NULL && b->w_host == NULL)
+ return 1;
+ else if (a->w_host == NULL && b->w_host != NULL)
+ return -1;
+ if (a->w_host != NULL && b->w_host != NULL &&
+ (i = sm_strcasecmp(a->w_host, b->w_host)) != 0)
+ return i;
+
+ /* lock status */
+ if (a->w_lock != b->w_lock)
+ return b->w_lock - a->w_lock;
+
+ /* job priority */
+ return workcmpf0(a, b);
+}
+/*
+** WORKCMPF2 -- second compare function for ordering work based on host name.
+**
+** Sorts on lock status, host name, and priority in that order.
+**
+** Parameters:
+** a -- the first argument.
+** b -- the second argument.
+**
+** Returns:
+** <0 if a < b
+** 0 if a == b
+** >0 if a > b
+**
+*/
+
+static int
+workcmpf2(a, b)
+ register WORK *a;
+ register WORK *b;
+{
+ int i;
+
+ /* lock status */
+ if (a->w_lock != b->w_lock)
+ return a->w_lock - b->w_lock;
+
+ /* host name */
+ if (a->w_host != NULL && b->w_host == NULL)
+ return 1;
+ else if (a->w_host == NULL && b->w_host != NULL)
+ return -1;
+ if (a->w_host != NULL && b->w_host != NULL &&
+ (i = sm_strcasecmp(a->w_host, b->w_host)) != 0)
+ return i;
+
+ /* job priority */
+ return workcmpf0(a, b);
+}
+/*
+** WORKCMPF3 -- simple submission-time-only compare function.
+**
+** Parameters:
+** a -- the first argument.
+** b -- the second argument.
+**
+** Returns:
+** -1 if a < b
+** 0 if a == b
+** +1 if a > b
+**
+*/
+
+static int
+workcmpf3(a, b)
+ register WORK *a;
+ register WORK *b;
+{
+ if (a->w_ctime > b->w_ctime)
+ return 1;
+ else if (a->w_ctime < b->w_ctime)
+ return -1;
+ else
+ return 0;
+}
+/*
+** WORKCMPF4 -- compare based on file name
+**
+** Parameters:
+** a -- the first argument.
+** b -- the second argument.
+**
+** Returns:
+** -1 if a < b
+** 0 if a == b
+** +1 if a > b
+**
+*/
+
+static int
+workcmpf4(a, b)
+ register WORK *a;
+ register WORK *b;
+{
+ return strcmp(a->w_name, b->w_name);
+}
+/*
+** WORKCMPF5 -- compare based on assigned random number
+**
+** Parameters:
+** a -- the first argument (ignored).
+** b -- the second argument (ignored).
+**
+** Returns:
+** randomly 1/-1
+*/
+
+/* ARGSUSED0 */
+static int
+workcmpf5(a, b)
+ register WORK *a;
+ register WORK *b;
+{
+ if (strlen(a->w_name) < randi || strlen(b->w_name) < randi)
+ return -1;
+ return a->w_name[randi] - b->w_name[randi];
+}
+/*
+** WORKCMPF6 -- simple modification-time-only compare function.
+**
+** Parameters:
+** a -- the first argument.
+** b -- the second argument.
+**
+** Returns:
+** -1 if a < b
+** 0 if a == b
+** +1 if a > b
+**
+*/
+
+static int
+workcmpf6(a, b)
+ register WORK *a;
+ register WORK *b;
+{
+ if (a->w_mtime > b->w_mtime)
+ return 1;
+ else if (a->w_mtime < b->w_mtime)
+ return -1;
+ else
+ return 0;
+}
+#if _FFR_RHS
+/*
+** WORKCMPF7 -- compare function for ordering work based on shuffled host name.
+**
+** Sorts on lock status, host name, and priority in that order.
+**
+** Parameters:
+** a -- the first argument.
+** b -- the second argument.
+**
+** Returns:
+** <0 if a < b
+** 0 if a == b
+** >0 if a > b
+**
+*/
+
+static int
+workcmpf7(a, b)
+ register WORK *a;
+ register WORK *b;
+{
+ int i;
+
+ /* lock status */
+ if (a->w_lock != b->w_lock)
+ return a->w_lock - b->w_lock;
+
+ /* host name */
+ if (a->w_host != NULL && b->w_host == NULL)
+ return 1;
+ else if (a->w_host == NULL && b->w_host != NULL)
+ return -1;
+ if (a->w_host != NULL && b->w_host != NULL &&
+ (i = sm_strshufflecmp(a->w_host, b->w_host)) != 0)
+ return i;
+
+ /* job priority */
+ return workcmpf0(a, b);
+}
+#endif /* _FFR_RHS */
+/*
+** STRREV -- reverse string
+**
+** Returns a pointer to a new string that is the reverse of
+** the string pointed to by fwd. The space for the new
+** string is obtained using xalloc().
+**
+** Parameters:
+** fwd -- the string to reverse.
+**
+** Returns:
+** the reversed string.
+*/
+
+static char *
+strrev(fwd)
+ char *fwd;
+{
+ char *rev = NULL;
+ int len, cnt;
+
+ len = strlen(fwd);
+ rev = xalloc(len + 1);
+ for (cnt = 0; cnt < len; ++cnt)
+ rev[cnt] = fwd[len - cnt - 1];
+ rev[len] = '\0';
+ return rev;
+}
+
+#if _FFR_RHS
+
+# define NASCII 128
+# define NCHAR 256
+
+static unsigned char ShuffledAlphabet[NCHAR];
+
+void
+init_shuffle_alphabet()
+{
+ static bool init = false;
+ int i;
+
+ if (init)
+ return;
+
+ /* fill the ShuffledAlphabet */
+ for (i = 0; i < NCHAR; i++)
+ ShuffledAlphabet[i] = i;
+
+ /* mix it */
+ for (i = 1; i < NCHAR; i++)
+ {
+ register int j = get_random() % NCHAR;
+ register int tmp;
+
+ tmp = ShuffledAlphabet[j];
+ ShuffledAlphabet[j] = ShuffledAlphabet[i];
+ ShuffledAlphabet[i] = tmp;
+ }
+
+ /* make it case insensitive */
+ for (i = 'A'; i <= 'Z'; i++)
+ ShuffledAlphabet[i] = ShuffledAlphabet[i + 'a' - 'A'];
+
+ /* fill the upper part */
+ for (i = 0; i < NCHAR; i++)
+ ShuffledAlphabet[i + NCHAR] = ShuffledAlphabet[i];
+ init = true;
+}
+
+static int
+sm_strshufflecmp(a, b)
+ char *a;
+ char *b;
+{
+ const unsigned char *us1 = (const unsigned char *) a;
+ const unsigned char *us2 = (const unsigned char *) b;
+
+ while (ShuffledAlphabet[*us1] == ShuffledAlphabet[*us2++])
+ {
+ if (*us1++ == '\0')
+ return 0;
+ }
+ return (ShuffledAlphabet[*us1] - ShuffledAlphabet[*--us2]);
+}
+#endif /* _FFR_RHS */
+
+/*
+** DOWORK -- do a work request.
+**
+** Parameters:
+** qgrp -- the index of the queue group for the job.
+** qdir -- the index of the queue directory for the job.
+** id -- the ID of the job to run.
+** forkflag -- if set, run this in background.
+** requeueflag -- if set, reinstantiate the queue quickly.
+** This is used when expanding aliases in the queue.
+** If forkflag is also set, it doesn't wait for the
+** child.
+** e - the envelope in which to run it.
+**
+** Returns:
+** process id of process that is running the queue job.
+**
+** Side Effects:
+** The work request is satisfied if possible.
+*/
+
+pid_t
+dowork(qgrp, qdir, id, forkflag, requeueflag, e)
+ int qgrp;
+ int qdir;
+ char *id;
+ bool forkflag;
+ bool requeueflag;
+ register ENVELOPE *e;
+{
+ register pid_t pid;
+ SM_RPOOL_T *rpool;
+
+ if (tTd(40, 1))
+ sm_dprintf("dowork(%s/%s)\n", qid_printqueue(qgrp, qdir), id);
+
+ /*
+ ** Fork for work.
+ */
+
+ if (forkflag)
+ {
+ /*
+ ** Since the delivery may happen in a child and the
+ ** parent does not wait, the parent may close the
+ ** maps thereby removing any shared memory used by
+ ** the map. Therefore, close the maps now so the
+ ** child will dynamically open them if necessary.
+ */
+
+ closemaps(false);
+
+ pid = fork();
+ if (pid < 0)
+ {
+ syserr("dowork: cannot fork");
+ return 0;
+ }
+ else if (pid > 0)
+ {
+ /* parent -- clean out connection cache */
+ mci_flush(false, NULL);
+ }
+ else
+ {
+ /*
+ ** Initialize exception stack and default exception
+ ** handler for child process.
+ */
+
+ /* Reset global flags */
+ RestartRequest = NULL;
+ RestartWorkGroup = false;
+ ShutdownRequest = NULL;
+ PendingSignal = 0;
+ CurrentPid = getpid();
+ sm_exc_newthread(fatal_error);
+
+ /*
+ ** See note above about SMTP processes and SIGCHLD.
+ */
+
+ if (OpMode == MD_SMTP ||
+ OpMode == MD_DAEMON ||
+ MaxQueueChildren > 0)
+ {
+ proc_list_clear();
+ sm_releasesignal(SIGCHLD);
+ (void) sm_signal(SIGCHLD, SIG_DFL);
+ }
+
+ /* child -- error messages to the transcript */
+ QuickAbort = OnlyOneError = false;
+ }
+ }
+ else
+ {
+ pid = 0;
+ }
+
+ if (pid == 0)
+ {
+ /*
+ ** CHILD
+ ** Lock the control file to avoid duplicate deliveries.
+ ** Then run the file as though we had just read it.
+ ** We save an idea of the temporary name so we
+ ** can recover on interrupt.
+ */
+
+ if (forkflag)
+ {
+ /* Reset global flags */
+ RestartRequest = NULL;
+ RestartWorkGroup = false;
+ ShutdownRequest = NULL;
+ PendingSignal = 0;
+ }
+
+ /* set basic modes, etc. */
+ sm_clear_events();
+ clearstats();
+ rpool = sm_rpool_new_x(NULL);
+ clearenvelope(e, false, rpool);
+ e->e_flags |= EF_QUEUERUN|EF_GLOBALERRS;
+ set_delivery_mode(SM_DELIVER, e);
+ e->e_errormode = EM_MAIL;
+ e->e_id = id;
+ e->e_qgrp = qgrp;
+ e->e_qdir = qdir;
+ GrabTo = UseErrorsTo = false;
+ ExitStat = EX_OK;
+ if (forkflag)
+ {
+ disconnect(1, e);
+ set_op_mode(MD_QUEUERUN);
+ }
+ sm_setproctitle(true, e, "%s from queue", qid_printname(e));
+ if (LogLevel > 76)
+ sm_syslog(LOG_DEBUG, e->e_id, "dowork, pid=%d",
+ (int) CurrentPid);
+
+ /* don't use the headers from sendmail.cf... */
+ e->e_header = NULL;
+
+ /* read the queue control file -- return if locked */
+ if (!readqf(e, false))
+ {
+ if (tTd(40, 4) && e->e_id != NULL)
+ sm_dprintf("readqf(%s) failed\n",
+ qid_printname(e));
+ e->e_id = NULL;
+ if (forkflag)
+ finis(false, true, EX_OK);
+ else
+ {
+ /* adding this frees 8 bytes */
+ clearenvelope(e, false, rpool);
+
+ /* adding this frees 12 bytes */
+ sm_rpool_free(rpool);
+ e->e_rpool = NULL;
+ return 0;
+ }
+ }
+
+ e->e_flags |= EF_INQUEUE;
+ eatheader(e, requeueflag, true);
+
+ if (requeueflag)
+ queueup(e, false, false);
+
+ /* do the delivery */
+ sendall(e, SM_DELIVER);
+
+ /* finish up and exit */
+ if (forkflag)
+ finis(true, true, ExitStat);
+ else
+ {
+ dropenvelope(e, true, false);
+ sm_rpool_free(rpool);
+ e->e_rpool = NULL;
+ }
+ }
+ e->e_id = NULL;
+ return pid;
+}
+
+/*
+** DOWORKLIST -- process a list of envelopes as work requests
+**
+** Similar to dowork(), except that after forking, it processes an
+** envelope and its siblings, treating each envelope as a work request.
+**
+** Parameters:
+** el -- envelope to be processed including its siblings.
+** forkflag -- if set, run this in background.
+** requeueflag -- if set, reinstantiate the queue quickly.
+** This is used when expanding aliases in the queue.
+** If forkflag is also set, it doesn't wait for the
+** child.
+**
+** Returns:
+** process id of process that is running the queue job.
+**
+** Side Effects:
+** The work request is satisfied if possible.
+*/
+
+pid_t
+doworklist(el, forkflag, requeueflag)
+ ENVELOPE *el;
+ bool forkflag;
+ bool requeueflag;
+{
+ register pid_t pid;
+ ENVELOPE *ei;
+
+ if (tTd(40, 1))
+ sm_dprintf("doworklist()\n");
+
+ /*
+ ** Fork for work.
+ */
+
+ if (forkflag)
+ {
+ /*
+ ** Since the delivery may happen in a child and the
+ ** parent does not wait, the parent may close the
+ ** maps thereby removing any shared memory used by
+ ** the map. Therefore, close the maps now so the
+ ** child will dynamically open them if necessary.
+ */
+
+ closemaps(false);
+
+ pid = fork();
+ if (pid < 0)
+ {
+ syserr("doworklist: cannot fork");
+ return 0;
+ }
+ else if (pid > 0)
+ {
+ /* parent -- clean out connection cache */
+ mci_flush(false, NULL);
+ }
+ else
+ {
+ /*
+ ** Initialize exception stack and default exception
+ ** handler for child process.
+ */
+
+ /* Reset global flags */
+ RestartRequest = NULL;
+ RestartWorkGroup = false;
+ ShutdownRequest = NULL;
+ PendingSignal = 0;
+ CurrentPid = getpid();
+ sm_exc_newthread(fatal_error);
+
+ /*
+ ** See note above about SMTP processes and SIGCHLD.
+ */
+
+ if (OpMode == MD_SMTP ||
+ OpMode == MD_DAEMON ||
+ MaxQueueChildren > 0)
+ {
+ proc_list_clear();
+ sm_releasesignal(SIGCHLD);
+ (void) sm_signal(SIGCHLD, SIG_DFL);
+ }
+
+ /* child -- error messages to the transcript */
+ QuickAbort = OnlyOneError = false;
+ }
+ }
+ else
+ {
+ pid = 0;
+ }
+
+ if (pid != 0)
+ return pid;
+
+ /*
+ ** IN CHILD
+ ** Lock the control file to avoid duplicate deliveries.
+ ** Then run the file as though we had just read it.
+ ** We save an idea of the temporary name so we
+ ** can recover on interrupt.
+ */
+
+ if (forkflag)
+ {
+ /* Reset global flags */
+ RestartRequest = NULL;
+ RestartWorkGroup = false;
+ ShutdownRequest = NULL;
+ PendingSignal = 0;
+ }
+
+ /* set basic modes, etc. */
+ sm_clear_events();
+ clearstats();
+ GrabTo = UseErrorsTo = false;
+ ExitStat = EX_OK;
+ if (forkflag)
+ {
+ disconnect(1, el);
+ set_op_mode(MD_QUEUERUN);
+ }
+ if (LogLevel > 76)
+ sm_syslog(LOG_DEBUG, el->e_id, "doworklist, pid=%d",
+ (int) CurrentPid);
+
+ for (ei = el; ei != NULL; ei = ei->e_sibling)
+ {
+ ENVELOPE e;
+ SM_RPOOL_T *rpool;
+
+ if (WILL_BE_QUEUED(ei->e_sendmode))
+ continue;
+ else if (QueueMode != QM_QUARANTINE &&
+ ei->e_quarmsg != NULL)
+ continue;
+
+ rpool = sm_rpool_new_x(NULL);
+ clearenvelope(&e, true, rpool);
+ e.e_flags |= EF_QUEUERUN|EF_GLOBALERRS;
+ set_delivery_mode(SM_DELIVER, &e);
+ e.e_errormode = EM_MAIL;
+ e.e_id = ei->e_id;
+ e.e_qgrp = ei->e_qgrp;
+ e.e_qdir = ei->e_qdir;
+ openxscript(&e);
+ sm_setproctitle(true, &e, "%s from queue", qid_printname(&e));
+
+ /* don't use the headers from sendmail.cf... */
+ e.e_header = NULL;
+ CurEnv = &e;
+
+ /* read the queue control file -- return if locked */
+ if (readqf(&e, false))
+ {
+ e.e_flags |= EF_INQUEUE;
+ eatheader(&e, requeueflag, true);
+
+ if (requeueflag)
+ queueup(&e, false, false);
+
+ /* do the delivery */
+ sendall(&e, SM_DELIVER);
+ dropenvelope(&e, true, false);
+ }
+ else
+ {
+ if (tTd(40, 4) && e.e_id != NULL)
+ sm_dprintf("readqf(%s) failed\n",
+ qid_printname(&e));
+ }
+ sm_rpool_free(rpool);
+ ei->e_id = NULL;
+ }
+
+ /* restore CurEnv */
+ CurEnv = el;
+
+ /* finish up and exit */
+ if (forkflag)
+ finis(true, true, ExitStat);
+ return 0;
+}
+/*
+** READQF -- read queue file and set up environment.
+**
+** Parameters:
+** e -- the envelope of the job to run.
+** openonly -- only open the qf (returned as e_lockfp)
+**
+** Returns:
+** true if it successfully read the queue file.
+** false otherwise.
+**
+** Side Effects:
+** The queue file is returned locked.
+*/
+
+static bool
+readqf(e, openonly)
+ register ENVELOPE *e;
+ bool openonly;
+{
+ register SM_FILE_T *qfp;
+ ADDRESS *ctladdr;
+ struct stat st, stf;
+ char *bp;
+ int qfver = 0;
+ long hdrsize = 0;
+ register char *p;
+ char *frcpt = NULL;
+ char *orcpt = NULL;
+ bool nomore = false;
+ bool bogus = false;
+ MODE_T qsafe;
+ char *err;
+ char qf[MAXPATHLEN];
+ char buf[MAXLINE];
+
+ /*
+ ** Read and process the file.
+ */
+
+ (void) sm_strlcpy(qf, queuename(e, ANYQFL_LETTER), sizeof qf);
+ qfp = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, qf, SM_IO_RDWR_B, NULL);
+ if (qfp == NULL)
+ {
+ int save_errno = errno;
+
+ if (tTd(40, 8))
+ sm_dprintf("readqf(%s): sm_io_open failure (%s)\n",
+ qf, sm_errstring(errno));
+ errno = save_errno;
+ if (errno != ENOENT
+ )
+ syserr("readqf: no control file %s", qf);
+ RELEASE_QUEUE;
+ return false;
+ }
+
+ if (!lockfile(sm_io_getinfo(qfp, SM_IO_WHAT_FD, NULL), qf, NULL,
+ LOCK_EX|LOCK_NB))
+ {
+ /* being processed by another queuer */
+ if (Verbose)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "%s: locked\n", e->e_id);
+ if (tTd(40, 8))
+ sm_dprintf("%s: locked\n", e->e_id);
+ if (LogLevel > 19)
+ sm_syslog(LOG_DEBUG, e->e_id, "locked");
+ (void) sm_io_close(qfp, SM_TIME_DEFAULT);
+ RELEASE_QUEUE;
+ return false;
+ }
+
+ RELEASE_QUEUE;
+
+ /*
+ ** Prevent locking race condition.
+ **
+ ** Process A: readqf(): qfp = fopen(qffile)
+ ** Process B: queueup(): rename(tf, qf)
+ ** Process B: unlocks(tf)
+ ** Process A: lockfile(qf);
+ **
+ ** Process A (us) has the old qf file (before the rename deleted
+ ** the directory entry) and will be delivering based on old data.
+ ** This can lead to multiple deliveries of the same recipients.
+ **
+ ** Catch this by checking if the underlying qf file has changed
+ ** *after* acquiring our lock and if so, act as though the file
+ ** was still locked (i.e., just return like the lockfile() case
+ ** above.
+ */
+
+ if (stat(qf, &stf) < 0 ||
+ fstat(sm_io_getinfo(qfp, SM_IO_WHAT_FD, NULL), &st) < 0)
+ {
+ /* must have been being processed by someone else */
+ if (tTd(40, 8))
+ sm_dprintf("readqf(%s): [f]stat failure (%s)\n",
+ qf, sm_errstring(errno));
+ (void) sm_io_close(qfp, SM_TIME_DEFAULT);
+ return false;
+ }
+
+ if (st.st_nlink != stf.st_nlink ||
+ st.st_dev != stf.st_dev ||
+ ST_INODE(st) != ST_INODE(stf) ||
+#if HAS_ST_GEN && 0 /* AFS returns garbage in st_gen */
+ st.st_gen != stf.st_gen ||
+#endif /* HAS_ST_GEN && 0 */
+ st.st_uid != stf.st_uid ||
+ st.st_gid != stf.st_gid ||
+ st.st_size != stf.st_size)
+ {
+ /* changed after opened */
+ if (Verbose)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "%s: changed\n", e->e_id);
+ if (tTd(40, 8))
+ sm_dprintf("%s: changed\n", e->e_id);
+ if (LogLevel > 19)
+ sm_syslog(LOG_DEBUG, e->e_id, "changed");
+ (void) sm_io_close(qfp, SM_TIME_DEFAULT);
+ return false;
+ }
+
+ /*
+ ** Check the queue file for plausibility to avoid attacks.
+ */
+
+ qsafe = S_IWOTH|S_IWGRP;
+ if (bitset(S_IWGRP, QueueFileMode))
+ qsafe &= ~S_IWGRP;
+
+ bogus = st.st_uid != geteuid() &&
+ st.st_uid != TrustedUid &&
+ geteuid() != RealUid;
+
+ /*
+ ** If this qf file results from a set-group-ID binary, then
+ ** we check whether the directory is group-writable,
+ ** the queue file mode contains the group-writable bit, and
+ ** the groups are the same.
+ ** Notice: this requires that the set-group-ID binary is used to
+ ** run the queue!
+ */
+
+ if (bogus && st.st_gid == getegid() && UseMSP)
+ {
+ char delim;
+ struct stat dst;
+
+ bp = SM_LAST_DIR_DELIM(qf);
+ if (bp == NULL)
+ delim = '\0';
+ else
+ {
+ delim = *bp;
+ *bp = '\0';
+ }
+ if (stat(delim == '\0' ? "." : qf, &dst) < 0)
+ syserr("readqf: cannot stat directory %s",
+ delim == '\0' ? "." : qf);
+ else
+ {
+ bogus = !(bitset(S_IWGRP, QueueFileMode) &&
+ bitset(S_IWGRP, dst.st_mode) &&
+ dst.st_gid == st.st_gid);
+ }
+ if (delim != '\0')
+ *bp = delim;
+ }
+ if (!bogus)
+ bogus = bitset(qsafe, st.st_mode);
+ if (bogus)
+ {
+ if (LogLevel > 0)
+ {
+ sm_syslog(LOG_ALERT, e->e_id,
+ "bogus queue file, uid=%d, gid=%d, mode=%o",
+ st.st_uid, st.st_gid, st.st_mode);
+ }
+ if (tTd(40, 8))
+ sm_dprintf("readqf(%s): bogus file\n", qf);
+ e->e_flags |= EF_INQUEUE;
+ if (!openonly)
+ loseqfile(e, "bogus file uid/gid in mqueue");
+ (void) sm_io_close(qfp, SM_TIME_DEFAULT);
+ return false;
+ }
+
+ if (st.st_size == 0)
+ {
+ /* must be a bogus file -- if also old, just remove it */
+ if (!openonly && st.st_ctime + 10 * 60 < curtime())
+ {
+ (void) xunlink(queuename(e, DATAFL_LETTER));
+ (void) xunlink(queuename(e, ANYQFL_LETTER));
+ }
+ (void) sm_io_close(qfp, SM_TIME_DEFAULT);
+ return false;
+ }
+
+ if (st.st_nlink == 0)
+ {
+ /*
+ ** Race condition -- we got a file just as it was being
+ ** unlinked. Just assume it is zero length.
+ */
+
+ (void) sm_io_close(qfp, SM_TIME_DEFAULT);
+ return false;
+ }
+
+#if _FFR_TRUSTED_QF
+ /*
+ ** If we don't own the file mark it as unsafe.
+ ** However, allow TrustedUser to own it as well
+ ** in case TrustedUser manipulates the queue.
+ */
+
+ if (st.st_uid != geteuid() && st.st_uid != TrustedUid)
+ e->e_flags |= EF_UNSAFE;
+#else /* _FFR_TRUSTED_QF */
+ /* If we don't own the file mark it as unsafe */
+ if (st.st_uid != geteuid())
+ e->e_flags |= EF_UNSAFE;
+#endif /* _FFR_TRUSTED_QF */
+
+ /* good file -- save this lock */
+ e->e_lockfp = qfp;
+
+ /* Just wanted the open file */
+ if (openonly)
+ return true;
+
+ /* do basic system initialization */
+ initsys(e);
+ macdefine(&e->e_macro, A_PERM, 'i', e->e_id);
+
+ LineNumber = 0;
+ e->e_flags |= EF_GLOBALERRS;
+ set_op_mode(MD_QUEUERUN);
+ ctladdr = NULL;
+ e->e_qfletter = queue_letter(e, ANYQFL_LETTER);
+ e->e_dfqgrp = e->e_qgrp;
+ e->e_dfqdir = e->e_qdir;
+#if _FFR_QUEUE_MACRO
+ macdefine(&e->e_macro, A_TEMP, macid("{queue}"),
+ qid_printqueue(e->e_qgrp, e->e_qdir));
+#endif /* _FFR_QUEUE_MACRO */
+ e->e_dfino = -1;
+ e->e_msgsize = -1;
+ while ((bp = fgetfolded(buf, sizeof buf, qfp)) != NULL)
+ {
+ unsigned long qflags;
+ ADDRESS *q;
+ int r;
+ time_t now;
+ auto char *ep;
+
+ if (tTd(40, 4))
+ sm_dprintf("+++++ %s\n", bp);
+ if (nomore)
+ {
+ /* hack attack */
+ hackattack:
+ syserr("SECURITY ALERT: extra or bogus data in queue file: %s",
+ bp);
+ err = "bogus queue line";
+ goto fail;
+ }
+ switch (bp[0])
+ {
+ case 'A': /* AUTH= parameter */
+ if (!xtextok(&bp[1]))
+ goto hackattack;
+ e->e_auth_param = sm_rpool_strdup_x(e->e_rpool, &bp[1]);
+ break;
+
+ case 'B': /* body type */
+ r = check_bodytype(&bp[1]);
+ if (!BODYTYPE_VALID(r))
+ goto hackattack;
+ e->e_bodytype = sm_rpool_strdup_x(e->e_rpool, &bp[1]);
+ break;
+
+ case 'C': /* specify controlling user */
+ ctladdr = setctluser(&bp[1], qfver, e);
+ break;
+
+ case 'D': /* data file name */
+ /* obsolete -- ignore */
+ break;
+
+ case 'd': /* data file directory name */
+ {
+ int qgrp, qdir;
+
+#if _FFR_MSP_PARANOIA
+ /* forbid queue groups in MSP? */
+ if (UseMSP)
+ goto hackattack;
+#endif /* _FFR_MSP_PARANOIA */
+ for (qgrp = 0;
+ qgrp < NumQueue && Queue[qgrp] != NULL;
+ ++qgrp)
+ {
+ for (qdir = 0;
+ qdir < Queue[qgrp]->qg_numqueues;
+ ++qdir)
+ {
+ if (strcmp(&bp[1],
+ Queue[qgrp]->qg_qpaths[qdir].qp_name)
+ == 0)
+ {
+ e->e_dfqgrp = qgrp;
+ e->e_dfqdir = qdir;
+ goto done;
+ }
+ }
+ }
+ err = "bogus queue file directory";
+ goto fail;
+ done:
+ break;
+ }
+
+ case 'E': /* specify error recipient */
+ /* no longer used */
+ break;
+
+ case 'F': /* flag bits */
+ if (strncmp(bp, "From ", 5) == 0)
+ {
+ /* we are being spoofed! */
+ syserr("SECURITY ALERT: bogus qf line %s", bp);
+ err = "bogus queue line";
+ goto fail;
+ }
+ for (p = &bp[1]; *p != '\0'; p++)
+ {
+ switch (*p)
+ {
+ case '8': /* has 8 bit data */
+ e->e_flags |= EF_HAS8BIT;
+ break;
+
+ case 'b': /* delete Bcc: header */
+ e->e_flags |= EF_DELETE_BCC;
+ break;
+
+ case 'd': /* envelope has DSN RET= */
+ e->e_flags |= EF_RET_PARAM;
+ break;
+
+ case 'n': /* don't return body */
+ e->e_flags |= EF_NO_BODY_RETN;
+ break;
+
+ case 'r': /* response */
+ e->e_flags |= EF_RESPONSE;
+ break;
+
+ case 's': /* split */
+ e->e_flags |= EF_SPLIT;
+ break;
+
+ case 'w': /* warning sent */
+ e->e_flags |= EF_WARNING;
+ break;
+ }
+ }
+ break;
+
+ case 'q': /* quarantine reason */
+ e->e_quarmsg = sm_rpool_strdup_x(e->e_rpool, &bp[1]);
+ macdefine(&e->e_macro, A_PERM,
+ macid("{quarantine}"), e->e_quarmsg);
+ break;
+
+ case 'H': /* header */
+
+ /*
+ ** count size before chompheader() destroys the line.
+ ** this isn't accurate due to macro expansion, but
+ ** better than before. "-3" to skip H?? at least.
+ */
+
+ hdrsize += strlen(bp) - 3;
+ (void) chompheader(&bp[1], CHHDR_QUEUE, NULL, e);
+ break;
+
+ case 'I': /* data file's inode number */
+ /* regenerated below */
+ break;
+
+ case 'K': /* time of last delivery attempt */
+ e->e_dtime = atol(&buf[1]);
+ break;
+
+ case 'L': /* Solaris Content-Length: */
+ case 'M': /* message */
+ /* ignore this; we want a new message next time */
+ break;
+
+ case 'N': /* number of delivery attempts */
+ e->e_ntries = atoi(&buf[1]);
+
+ /* if this has been tried recently, let it be */
+ now = curtime();
+ if (e->e_ntries > 0 && e->e_dtime <= now &&
+ now < e->e_dtime + MinQueueAge)
+ {
+ char *howlong;
+
+ howlong = pintvl(now - e->e_dtime, true);
+ if (Verbose)
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT,
+ "%s: too young (%s)\n",
+ e->e_id, howlong);
+ if (tTd(40, 8))
+ sm_dprintf("%s: too young (%s)\n",
+ e->e_id, howlong);
+ if (LogLevel > 19)
+ sm_syslog(LOG_DEBUG, e->e_id,
+ "too young (%s)",
+ howlong);
+ e->e_id = NULL;
+ unlockqueue(e);
+ return false;
+ }
+ macdefine(&e->e_macro, A_TEMP,
+ macid("{ntries}"), &buf[1]);
+
+#if NAMED_BIND
+ /* adjust BIND parameters immediately */
+ if (e->e_ntries == 0)
+ {
+ _res.retry = TimeOuts.res_retry[RES_TO_FIRST];
+ _res.retrans = TimeOuts.res_retrans[RES_TO_FIRST];
+ }
+ else
+ {
+ _res.retry = TimeOuts.res_retry[RES_TO_NORMAL];
+ _res.retrans = TimeOuts.res_retrans[RES_TO_NORMAL];
+ }
+#endif /* NAMED_BIND */
+ break;
+
+ case 'P': /* message priority */
+ e->e_msgpriority = atol(&bp[1]) + WkTimeFact;
+ break;
+
+ case 'Q': /* original recipient */
+ orcpt = sm_rpool_strdup_x(e->e_rpool, &bp[1]);
+ break;
+
+ case 'r': /* final recipient */
+ frcpt = sm_rpool_strdup_x(e->e_rpool, &bp[1]);
+ break;
+
+ case 'R': /* specify recipient */
+ p = bp;
+ qflags = 0;
+ if (qfver >= 1)
+ {
+ /* get flag bits */
+ while (*++p != '\0' && *p != ':')
+ {
+ switch (*p)
+ {
+ case 'N':
+ qflags |= QHASNOTIFY;
+ break;
+
+ case 'S':
+ qflags |= QPINGONSUCCESS;
+ break;
+
+ case 'F':
+ qflags |= QPINGONFAILURE;
+ break;
+
+ case 'D':
+ qflags |= QPINGONDELAY;
+ break;
+
+ case 'P':
+ qflags |= QPRIMARY;
+ break;
+
+ case 'A':
+ if (ctladdr != NULL)
+ ctladdr->q_flags |= QALIAS;
+ break;
+
+ default: /* ignore or complain? */
+ break;
+ }
+ }
+ }
+ else
+ qflags |= QPRIMARY;
+ macdefine(&e->e_macro, A_PERM, macid("{addr_type}"),
+ "e r");
+ if (*p != '\0')
+ q = parseaddr(++p, NULLADDR, RF_COPYALL, '\0',
+ NULL, e, true);
+ else
+ q = NULL;
+ if (q != NULL)
+ {
+ /* make sure we keep the current qgrp */
+ if (ISVALIDQGRP(e->e_qgrp))
+ q->q_qgrp = e->e_qgrp;
+ q->q_alias = ctladdr;
+ if (qfver >= 1)
+ q->q_flags &= ~Q_PINGFLAGS;
+ q->q_flags |= qflags;
+ q->q_finalrcpt = frcpt;
+ q->q_orcpt = orcpt;
+ (void) recipient(q, &e->e_sendqueue, 0, e);
+ }
+ frcpt = NULL;
+ orcpt = NULL;
+ macdefine(&e->e_macro, A_PERM, macid("{addr_type}"),
+ NULL);
+ break;
+
+ case 'S': /* sender */
+ setsender(sm_rpool_strdup_x(e->e_rpool, &bp[1]),
+ e, NULL, '\0', true);
+ break;
+
+ case 'T': /* init time */
+ e->e_ctime = atol(&bp[1]);
+ break;
+
+ case 'V': /* queue file version number */
+ qfver = atoi(&bp[1]);
+ if (qfver <= QF_VERSION)
+ break;
+ syserr("Version number in queue file (%d) greater than max (%d)",
+ qfver, QF_VERSION);
+ err = "unsupported queue file version";
+ goto fail;
+ /* NOTREACHED */
+ break;
+
+ case 'Z': /* original envelope id from ESMTP */
+ e->e_envid = sm_rpool_strdup_x(e->e_rpool, &bp[1]);
+ macdefine(&e->e_macro, A_PERM,
+ macid("{dsn_envid}"), e->e_envid);
+ break;
+
+ case '!': /* deliver by */
+
+ /* format: flag (1 char) space long-integer */
+ e->e_dlvr_flag = buf[1];
+ e->e_deliver_by = strtol(&buf[3], NULL, 10);
+
+ case '$': /* define macro */
+ {
+ char *p;
+
+ /* XXX elimate p? */
+ r = macid_parse(&bp[1], &ep);
+ if (r == 0)
+ break;
+ p = sm_rpool_strdup_x(e->e_rpool, ep);
+ macdefine(&e->e_macro, A_PERM, r, p);
+ }
+ break;
+
+ case '.': /* terminate file */
+ nomore = true;
+ break;
+
+#if _FFR_QUEUEDELAY
+ case 'G':
+ case 'Y':
+
+ /*
+ ** Maintain backward compatibility for
+ ** users who defined _FFR_QUEUEDELAY in
+ ** previous releases. Remove this
+ ** code in 8.14 or 8.15.
+ */
+
+ if (qfver == 5 || qfver == 7)
+ break;
+
+ /* If not qfver 5 or 7, then 'G' or 'Y' is invalid */
+ /* FALLTHROUGH */
+#endif /* _FFR_QUEUEDELAY */
+
+ default:
+ syserr("readqf: %s: line %d: bad line \"%s\"",
+ qf, LineNumber, shortenstring(bp, MAXSHORTSTR));
+ err = "unrecognized line";
+ goto fail;
+ }
+
+ if (bp != buf)
+ sm_free(bp); /* XXX */
+ }
+
+ /*
+ ** If we haven't read any lines, this queue file is empty.
+ ** Arrange to remove it without referencing any null pointers.
+ */
+
+ if (LineNumber == 0)
+ {
+ errno = 0;
+ e->e_flags |= EF_CLRQUEUE|EF_FATALERRS|EF_RESPONSE;
+ return true;
+ }
+
+ /* Check to make sure we have a complete queue file read */
+ if (!nomore)
+ {
+ syserr("readqf: %s: incomplete queue file read", qf);
+ (void) sm_io_close(qfp, SM_TIME_DEFAULT);
+ return false;
+ }
+
+ /* possibly set ${dsn_ret} macro */
+ if (bitset(EF_RET_PARAM, e->e_flags))
+ {
+ if (bitset(EF_NO_BODY_RETN, e->e_flags))
+ macdefine(&e->e_macro, A_PERM,
+ macid("{dsn_ret}"), "hdrs");
+ else
+ macdefine(&e->e_macro, A_PERM,
+ macid("{dsn_ret}"), "full");
+ }
+
+ /*
+ ** Arrange to read the data file.
+ */
+
+ p = queuename(e, DATAFL_LETTER);
+ e->e_dfp = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, p, SM_IO_RDONLY_B,
+ NULL);
+ if (e->e_dfp == NULL)
+ {
+ syserr("readqf: cannot open %s", p);
+ }
+ else
+ {
+ e->e_flags |= EF_HAS_DF;
+ if (fstat(sm_io_getinfo(e->e_dfp, SM_IO_WHAT_FD, NULL), &st)
+ >= 0)
+ {
+ e->e_msgsize = st.st_size + hdrsize;
+ e->e_dfdev = st.st_dev;
+ e->e_dfino = ST_INODE(st);
+ (void) sm_snprintf(buf, sizeof buf, "%ld",
+ e->e_msgsize);
+ macdefine(&e->e_macro, A_TEMP, macid("{msg_size}"),
+ buf);
+ }
+ }
+
+ return true;
+
+ fail:
+ /*
+ ** There was some error reading the qf file (reason is in err var.)
+ ** Cleanup:
+ ** close file; clear e_lockfp since it is the same as qfp,
+ ** hence it is invalid (as file) after qfp is closed;
+ ** the qf file is on disk, so set the flag to avoid calling
+ ** queueup() with bogus data.
+ */
+
+ if (qfp != NULL)
+ (void) sm_io_close(qfp, SM_TIME_DEFAULT);
+ e->e_lockfp = NULL;
+ e->e_flags |= EF_INQUEUE;
+ loseqfile(e, err);
+ return false;
+}
+/*
+** PRTSTR -- print a string, "unprintable" characters are shown as \oct
+**
+** Parameters:
+** s -- string to print
+** ml -- maximum length of output
+**
+** Returns:
+** number of entries
+**
+** Side Effects:
+** Prints a string on stdout.
+*/
+
+static void
+prtstr(s, ml)
+ char *s;
+ int ml;
+{
+ int c;
+
+ if (s == NULL)
+ return;
+ while (ml-- > 0 && ((c = *s++) != '\0'))
+ {
+ if (c == '\\')
+ {
+ if (ml-- > 0)
+ {
+ (void) sm_io_putc(smioout, SM_TIME_DEFAULT, c);
+ (void) sm_io_putc(smioout, SM_TIME_DEFAULT, c);
+ }
+ }
+ else if (isascii(c) && isprint(c))
+ (void) sm_io_putc(smioout, SM_TIME_DEFAULT, c);
+ else
+ {
+ if ((ml -= 3) > 0)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "\\%03o", c & 0xFF);
+ }
+ }
+}
+/*
+** PRINTNQE -- print out number of entries in the mail queue
+**
+** Parameters:
+** out -- output file pointer.
+** prefix -- string to output in front of each line.
+**
+** Returns:
+** none.
+*/
+
+void
+printnqe(out, prefix)
+ SM_FILE_T *out;
+ char *prefix;
+{
+#if SM_CONF_SHM
+ int i, k = 0, nrequests = 0;
+ bool unknown = false;
+
+ if (ShmId == SM_SHM_NO_ID)
+ {
+ if (prefix == NULL)
+ (void) sm_io_fprintf(out, SM_TIME_DEFAULT,
+ "Data unavailable: shared memory not updated\n");
+ else
+ (void) sm_io_fprintf(out, SM_TIME_DEFAULT,
+ "%sNOTCONFIGURED:-1\r\n", prefix);
+ return;
+ }
+ for (i = 0; i < NumQueue && Queue[i] != NULL; i++)
+ {
+ int j;
+
+ k++;
+ for (j = 0; j < Queue[i]->qg_numqueues; j++)
+ {
+ int n;
+
+ if (StopRequest)
+ stop_sendmail();
+
+ n = QSHM_ENTRIES(Queue[i]->qg_qpaths[j].qp_idx);
+ if (prefix != NULL)
+ (void) sm_io_fprintf(out, SM_TIME_DEFAULT,
+ "%s%s:%d\r\n",
+ prefix, qid_printqueue(i, j), n);
+ else if (n < 0)
+ {
+ (void) sm_io_fprintf(out, SM_TIME_DEFAULT,
+ "%s: unknown number of entries\n",
+ qid_printqueue(i, j));
+ unknown = true;
+ }
+ else if (n == 0)
+ {
+ (void) sm_io_fprintf(out, SM_TIME_DEFAULT,
+ "%s is empty\n",
+ qid_printqueue(i, j));
+ }
+ else if (n > 0)
+ {
+ (void) sm_io_fprintf(out, SM_TIME_DEFAULT,
+ "%s: entries=%d\n",
+ qid_printqueue(i, j), n);
+ nrequests += n;
+ k++;
+ }
+ }
+ }
+ if (prefix == NULL && k > 1)
+ (void) sm_io_fprintf(out, SM_TIME_DEFAULT,
+ "\t\tTotal requests: %d%s\n",
+ nrequests, unknown ? " (about)" : "");
+#else /* SM_CONF_SHM */
+ if (prefix == NULL)
+ (void) sm_io_fprintf(out, SM_TIME_DEFAULT,
+ "Data unavailable without shared memory support\n");
+ else
+ (void) sm_io_fprintf(out, SM_TIME_DEFAULT,
+ "%sNOTAVAILABLE:-1\r\n", prefix);
+#endif /* SM_CONF_SHM */
+}
+/*
+** PRINTQUEUE -- print out a representation of the mail queue
+**
+** Parameters:
+** none.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Prints a listing of the mail queue on the standard output.
+*/
+
+void
+printqueue()
+{
+ int i, k = 0, nrequests = 0;
+
+ for (i = 0; i < NumQueue && Queue[i] != NULL; i++)
+ {
+ int j;
+
+ k++;
+ for (j = 0; j < Queue[i]->qg_numqueues; j++)
+ {
+ if (StopRequest)
+ stop_sendmail();
+ nrequests += print_single_queue(i, j);
+ k++;
+ }
+ }
+ if (k > 1)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "\t\tTotal requests: %d\n",
+ nrequests);
+}
+/*
+** PRINT_SINGLE_QUEUE -- print out a representation of a single mail queue
+**
+** Parameters:
+** qgrp -- the index of the queue group.
+** qdir -- the queue directory.
+**
+** Returns:
+** number of requests in mail queue.
+**
+** Side Effects:
+** Prints a listing of the mail queue on the standard output.
+*/
+
+int
+print_single_queue(qgrp, qdir)
+ int qgrp;
+ int qdir;
+{
+ register WORK *w;
+ SM_FILE_T *f;
+ int nrequests;
+ char qd[MAXPATHLEN];
+ char qddf[MAXPATHLEN];
+ char buf[MAXLINE];
+
+ if (qdir == NOQDIR)
+ {
+ (void) sm_strlcpy(qd, ".", sizeof qd);
+ (void) sm_strlcpy(qddf, ".", sizeof qddf);
+ }
+ else
+ {
+ (void) sm_strlcpyn(qd, sizeof qd, 2,
+ Queue[qgrp]->qg_qpaths[qdir].qp_name,
+ (bitset(QP_SUBQF,
+ Queue[qgrp]->qg_qpaths[qdir].qp_subdirs)
+ ? "/qf" : ""));
+ (void) sm_strlcpyn(qddf, sizeof qddf, 2,
+ Queue[qgrp]->qg_qpaths[qdir].qp_name,
+ (bitset(QP_SUBDF,
+ Queue[qgrp]->qg_qpaths[qdir].qp_subdirs)
+ ? "/df" : ""));
+ }
+
+ /*
+ ** Check for permission to print the queue
+ */
+
+ if (bitset(PRIV_RESTRICTMAILQ, PrivacyFlags) && RealUid != 0)
+ {
+ struct stat st;
+#ifdef NGROUPS_MAX
+ int n;
+ extern GIDSET_T InitialGidSet[NGROUPS_MAX];
+#endif /* NGROUPS_MAX */
+
+ if (stat(qd, &st) < 0)
+ {
+ syserr("Cannot stat %s",
+ qid_printqueue(qgrp, qdir));
+ return 0;
+ }
+#ifdef NGROUPS_MAX
+ n = NGROUPS_MAX;
+ while (--n >= 0)
+ {
+ if (InitialGidSet[n] == st.st_gid)
+ break;
+ }
+ if (n < 0 && RealGid != st.st_gid)
+#else /* NGROUPS_MAX */
+ if (RealGid != st.st_gid)
+#endif /* NGROUPS_MAX */
+ {
+ usrerr("510 You are not permitted to see the queue");
+ setstat(EX_NOPERM);
+ return 0;
+ }
+ }
+
+ /*
+ ** Read and order the queue.
+ */
+
+ nrequests = gatherq(qgrp, qdir, true, NULL, NULL);
+ (void) sortq(Queue[qgrp]->qg_maxlist);
+
+ /*
+ ** Print the work list that we have read.
+ */
+
+ /* first see if there is anything */
+ if (nrequests <= 0)
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%s is empty\n",
+ qid_printqueue(qgrp, qdir));
+ return 0;
+ }
+
+ sm_getla(); /* get load average */
+
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "\t\t%s (%d request%s",
+ qid_printqueue(qgrp, qdir),
+ nrequests, nrequests == 1 ? "" : "s");
+ if (MaxQueueRun > 0 && nrequests > MaxQueueRun)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ ", only %d printed", MaxQueueRun);
+ if (Verbose)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ ")\n-----Q-ID----- --Size-- -Priority- ---Q-Time--- --------Sender/Recipient--------\n");
+ else
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ ")\n-----Q-ID----- --Size-- -----Q-Time----- ------------Sender/Recipient-----------\n");
+ for (w = WorkQ; w != NULL; w = w->w_next)
+ {
+ struct stat st;
+ auto time_t submittime = 0;
+ long dfsize;
+ int flags = 0;
+ int qfver;
+ char quarmsg[MAXLINE];
+ char statmsg[MAXLINE];
+ char bodytype[MAXNAME + 1];
+ char qf[MAXPATHLEN];
+
+ if (StopRequest)
+ stop_sendmail();
+
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%13s",
+ w->w_name + 2);
+ (void) sm_strlcpyn(qf, sizeof qf, 3, qd, "/", w->w_name);
+ f = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, qf, SM_IO_RDONLY_B,
+ NULL);
+ if (f == NULL)
+ {
+ if (errno == EPERM)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ " (permission denied)\n");
+ else if (errno == ENOENT)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ " (job completed)\n");
+ else
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ " (%s)\n",
+ sm_errstring(errno));
+ errno = 0;
+ continue;
+ }
+ w->w_name[0] = DATAFL_LETTER;
+ (void) sm_strlcpyn(qf, sizeof qf, 3, qddf, "/", w->w_name);
+ if (stat(qf, &st) >= 0)
+ dfsize = st.st_size;
+ else
+ {
+ ENVELOPE e;
+
+ /*
+ ** Maybe the df file can't be statted because
+ ** it is in a different directory than the qf file.
+ ** In order to find out, we must read the qf file.
+ */
+
+ newenvelope(&e, &BlankEnvelope, sm_rpool_new_x(NULL));
+ e.e_id = w->w_name + 2;
+ e.e_qgrp = qgrp;
+ e.e_qdir = qdir;
+ dfsize = -1;
+ if (readqf(&e, false))
+ {
+ char *df = queuename(&e, DATAFL_LETTER);
+ if (stat(df, &st) >= 0)
+ dfsize = st.st_size;
+ }
+ if (e.e_lockfp != NULL)
+ {
+ (void) sm_io_close(e.e_lockfp, SM_TIME_DEFAULT);
+ e.e_lockfp = NULL;
+ }
+ clearenvelope(&e, false, e.e_rpool);
+ sm_rpool_free(e.e_rpool);
+ }
+ if (w->w_lock)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "*");
+ else if (QueueMode == QM_LOST)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "?");
+ else if (w->w_tooyoung)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "-");
+ else if (shouldqueue(w->w_pri, w->w_ctime))
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "X");
+ else
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, " ");
+
+ errno = 0;
+
+ quarmsg[0] = '\0';
+ statmsg[0] = bodytype[0] = '\0';
+ qfver = 0;
+ while (sm_io_fgets(f, SM_TIME_DEFAULT, buf, sizeof buf) != NULL)
+ {
+ register int i;
+ register char *p;
+
+ if (StopRequest)
+ stop_sendmail();
+
+ fixcrlf(buf, true);
+ switch (buf[0])
+ {
+ case 'V': /* queue file version */
+ qfver = atoi(&buf[1]);
+ break;
+
+ case 'M': /* error message */
+ if ((i = strlen(&buf[1])) >= sizeof statmsg)
+ i = sizeof statmsg - 1;
+ memmove(statmsg, &buf[1], i);
+ statmsg[i] = '\0';
+ break;
+
+ case 'q': /* quarantine reason */
+ if ((i = strlen(&buf[1])) >= sizeof quarmsg)
+ i = sizeof quarmsg - 1;
+ memmove(quarmsg, &buf[1], i);
+ quarmsg[i] = '\0';
+ break;
+
+ case 'B': /* body type */
+ if ((i = strlen(&buf[1])) >= sizeof bodytype)
+ i = sizeof bodytype - 1;
+ memmove(bodytype, &buf[1], i);
+ bodytype[i] = '\0';
+ break;
+
+ case 'S': /* sender name */
+ if (Verbose)
+ {
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT,
+ "%8ld %10ld%c%.12s ",
+ dfsize,
+ w->w_pri,
+ bitset(EF_WARNING, flags)
+ ? '+' : ' ',
+ ctime(&submittime) + 4);
+ prtstr(&buf[1], 78);
+ }
+ else
+ {
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT,
+ "%8ld %.16s ",
+ dfsize,
+ ctime(&submittime));
+ prtstr(&buf[1], 39);
+ }
+
+ if (quarmsg[0] != '\0')
+ {
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT,
+ "\n QUARANTINE: %.*s",
+ Verbose ? 100 : 60,
+ quarmsg);
+ quarmsg[0] = '\0';
+ }
+
+ if (statmsg[0] != '\0' || bodytype[0] != '\0')
+ {
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT,
+ "\n %10.10s",
+ bodytype);
+ if (statmsg[0] != '\0')
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT,
+ " (%.*s)",
+ Verbose ? 100 : 60,
+ statmsg);
+ statmsg[0] = '\0';
+ }
+ break;
+
+ case 'C': /* controlling user */
+ if (Verbose)
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT,
+ "\n\t\t\t\t\t\t(---%.64s---)",
+ &buf[1]);
+ break;
+
+ case 'R': /* recipient name */
+ p = &buf[1];
+ if (qfver >= 1)
+ {
+ p = strchr(p, ':');
+ if (p == NULL)
+ break;
+ p++;
+ }
+ if (Verbose)
+ {
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT,
+ "\n\t\t\t\t\t\t");
+ prtstr(p, 71);
+ }
+ else
+ {
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT,
+ "\n\t\t\t\t\t ");
+ prtstr(p, 38);
+ }
+ if (Verbose && statmsg[0] != '\0')
+ {
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT,
+ "\n\t\t (%.100s)",
+ statmsg);
+ statmsg[0] = '\0';
+ }
+ break;
+
+ case 'T': /* creation time */
+ submittime = atol(&buf[1]);
+ break;
+
+ case 'F': /* flag bits */
+ for (p = &buf[1]; *p != '\0'; p++)
+ {
+ switch (*p)
+ {
+ case 'w':
+ flags |= EF_WARNING;
+ break;
+ }
+ }
+ }
+ }
+ if (submittime == (time_t) 0)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ " (no control file)");
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "\n");
+ (void) sm_io_close(f, SM_TIME_DEFAULT);
+ }
+ return nrequests;
+}
+
+/*
+** QUEUE_LETTER -- get the proper queue letter for the current QueueMode.
+**
+** Parameters:
+** e -- envelope to build it in/from.
+** type -- the file type, used as the first character
+** of the file name.
+**
+** Returns:
+** the letter to use
+*/
+
+static char
+queue_letter(e, type)
+ ENVELOPE *e;
+ int type;
+{
+ /* Change type according to QueueMode */
+ if (type == ANYQFL_LETTER)
+ {
+ if (e->e_quarmsg != NULL)
+ type = QUARQF_LETTER;
+ else
+ {
+ switch (QueueMode)
+ {
+ case QM_NORMAL:
+ type = NORMQF_LETTER;
+ break;
+
+ case QM_QUARANTINE:
+ type = QUARQF_LETTER;
+ break;
+
+ case QM_LOST:
+ type = LOSEQF_LETTER;
+ break;
+
+ default:
+ /* should never happen */
+ abort();
+ /* NOTREACHED */
+ }
+ }
+ }
+ return type;
+}
+
+/*
+** QUEUENAME -- build a file name in the queue directory for this envelope.
+**
+** Parameters:
+** e -- envelope to build it in/from.
+** type -- the file type, used as the first character
+** of the file name.
+**
+** Returns:
+** a pointer to the queue name (in a static buffer).
+**
+** Side Effects:
+** If no id code is already assigned, queuename() will
+** assign an id code with assign_queueid(). If no queue
+** directory is assigned, one will be set with setnewqueue().
+*/
+
+char *
+queuename(e, type)
+ register ENVELOPE *e;
+ int type;
+{
+ int qd, qg;
+ char *sub = "/";
+ char pref[3];
+ static char buf[MAXPATHLEN];
+
+ /* Assign an ID if needed */
+ if (e->e_id == NULL)
+ assign_queueid(e);
+ type = queue_letter(e, type);
+
+ /* begin of filename */
+ pref[0] = (char) type;
+ pref[1] = 'f';
+ pref[2] = '\0';
+
+ /* Assign a queue group/directory if needed */
+ if (type == XSCRPT_LETTER)
+ {
+ /*
+ ** We don't want to call setnewqueue() if we are fetching
+ ** the pathname of the transcript file, because setnewqueue
+ ** chooses a queue, and sometimes we need to write to the
+ ** transcript file before we have gathered enough information
+ ** to choose a queue.
+ */
+
+ if (e->e_xfqgrp == NOQGRP || e->e_xfqdir == NOQDIR)
+ {
+ if (e->e_qgrp != NOQGRP && e->e_qdir != NOQDIR)
+ {
+ e->e_xfqgrp = e->e_qgrp;
+ e->e_xfqdir = e->e_qdir;
+ }
+ else
+ {
+ e->e_xfqgrp = 0;
+ if (Queue[e->e_xfqgrp]->qg_numqueues <= 1)
+ e->e_xfqdir = 0;
+ else
+ {
+ e->e_xfqdir = get_rand_mod(
+ Queue[e->e_xfqgrp]->qg_numqueues);
+ }
+ }
+ }
+ qd = e->e_xfqdir;
+ qg = e->e_xfqgrp;
+ }
+ else
+ {
+ if (e->e_qgrp == NOQGRP || e->e_qdir == NOQDIR)
+ setnewqueue(e);
+ if (type == DATAFL_LETTER)
+ {
+ qd = e->e_dfqdir;
+ qg = e->e_dfqgrp;
+ }
+ else
+ {
+ qd = e->e_qdir;
+ qg = e->e_qgrp;
+ }
+ }
+
+ /* xf files always have a valid qd and qg picked above */
+ if (e->e_qdir == NOQDIR && type != XSCRPT_LETTER)
+ (void) sm_strlcpyn(buf, sizeof buf, 2, pref, e->e_id);
+ else
+ {
+ switch (type)
+ {
+ case DATAFL_LETTER:
+ if (bitset(QP_SUBDF, Queue[qg]->qg_qpaths[qd].qp_subdirs))
+ sub = "/df/";
+ break;
+
+ case QUARQF_LETTER:
+ case TEMPQF_LETTER:
+ case NEWQFL_LETTER:
+ case LOSEQF_LETTER:
+ case NORMQF_LETTER:
+ if (bitset(QP_SUBQF, Queue[qg]->qg_qpaths[qd].qp_subdirs))
+ sub = "/qf/";
+ break;
+
+ case XSCRPT_LETTER:
+ if (bitset(QP_SUBXF, Queue[qg]->qg_qpaths[qd].qp_subdirs))
+ sub = "/xf/";
+ break;
+
+ default:
+ sm_abort("queuename: bad queue file type %d", type);
+ }
+
+ (void) sm_strlcpyn(buf, sizeof buf, 4,
+ Queue[qg]->qg_qpaths[qd].qp_name,
+ sub, pref, e->e_id);
+ }
+
+ if (tTd(7, 2))
+ sm_dprintf("queuename: %s\n", buf);
+ return buf;
+}
+
+/*
+** INIT_QID_ALG -- Initialize the (static) parameters that are used to
+** generate a queue ID.
+**
+** This function is called by the daemon to reset
+** LastQueueTime and LastQueuePid which are used by assign_queueid().
+** Otherwise the algorithm may cause problems because
+** LastQueueTime and LastQueuePid are set indirectly by main()
+** before the daemon process is started, hence LastQueuePid is not
+** the pid of the daemon and therefore a child of the daemon can
+** actually have the same pid as LastQueuePid which means the section
+** in assign_queueid():
+** * see if we need to get a new base time/pid *
+** is NOT triggered which will cause the same queue id to be generated.
+**
+** Parameters:
+** none
+**
+** Returns:
+** none.
+*/
+
+void
+init_qid_alg()
+{
+ LastQueueTime = 0;
+ LastQueuePid = -1;
+}
+
+/*
+** ASSIGN_QUEUEID -- assign a queue ID for this envelope.
+**
+** Assigns an id code if one does not already exist.
+** This code assumes that nothing will remain in the queue for
+** longer than 60 years. It is critical that files with the given
+** name do not already exist in the queue.
+** [No longer initializes e_qdir to NOQDIR.]
+**
+** Parameters:
+** e -- envelope to set it in.
+**
+** Returns:
+** none.
+*/
+
+static const char QueueIdChars[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+# define QIC_LEN 60
+# define QIC_LEN_R 62
+
+/*
+** Note: the length is "officially" 60 because minutes and seconds are
+** usually only 0-59. However (Linux):
+** tm_sec The number of seconds after the minute, normally in
+** the range 0 to 59, but can be up to 61 to allow for
+** leap seconds.
+** Hence the real length of the string is 62 to take this into account.
+** Alternatively % QIC_LEN can (should) be used for access everywhere.
+*/
+
+# define queuenextid() CurrentPid
+
+
+void
+assign_queueid(e)
+ register ENVELOPE *e;
+{
+ pid_t pid = queuenextid();
+ static int cX = 0;
+ static long random_offset;
+ struct tm *tm;
+ char idbuf[MAXQFNAME - 2];
+ int seq;
+
+ if (e->e_id != NULL)
+ return;
+
+ /* see if we need to get a new base time/pid */
+ if (cX >= QIC_LEN * QIC_LEN || LastQueueTime == 0 ||
+ LastQueuePid != pid)
+ {
+ time_t then = LastQueueTime;
+
+ /* if the first time through, pick a random offset */
+ if (LastQueueTime == 0)
+ random_offset = get_random();
+
+ while ((LastQueueTime = curtime()) == then &&
+ LastQueuePid == pid)
+ {
+ (void) sleep(1);
+ }
+ LastQueuePid = queuenextid();
+ cX = 0;
+ }
+
+ /*
+ ** Generate a new sequence number between 0 and QIC_LEN*QIC_LEN-1.
+ ** This lets us generate up to QIC_LEN*QIC_LEN unique queue ids
+ ** per second, per process. With envelope splitting,
+ ** a single message can consume many queue ids.
+ */
+
+ seq = (int)((cX + random_offset) % (QIC_LEN * QIC_LEN));
+ ++cX;
+ if (tTd(7, 50))
+ sm_dprintf("assign_queueid: random_offset = %ld (%d)\n",
+ random_offset, seq);
+
+ tm = gmtime(&LastQueueTime);
+ idbuf[0] = QueueIdChars[tm->tm_year % QIC_LEN];
+ idbuf[1] = QueueIdChars[tm->tm_mon];
+ idbuf[2] = QueueIdChars[tm->tm_mday];
+ idbuf[3] = QueueIdChars[tm->tm_hour];
+ idbuf[4] = QueueIdChars[tm->tm_min % QIC_LEN_R];
+ idbuf[5] = QueueIdChars[tm->tm_sec % QIC_LEN_R];
+ idbuf[6] = QueueIdChars[seq / QIC_LEN];
+ idbuf[7] = QueueIdChars[seq % QIC_LEN];
+ (void) sm_snprintf(&idbuf[8], sizeof idbuf - 8, "%06d",
+ (int) LastQueuePid);
+ e->e_id = sm_rpool_strdup_x(e->e_rpool, idbuf);
+ macdefine(&e->e_macro, A_PERM, 'i', e->e_id);
+#if 0
+ /* XXX: inherited from MainEnvelope */
+ e->e_qgrp = NOQGRP; /* too early to do anything else */
+ e->e_qdir = NOQDIR;
+ e->e_xfqgrp = NOQGRP;
+#endif /* 0 */
+
+ /* New ID means it's not on disk yet */
+ e->e_qfletter = '\0';
+
+ if (tTd(7, 1))
+ sm_dprintf("assign_queueid: assigned id %s, e=%p\n",
+ e->e_id, e);
+ if (LogLevel > 93)
+ sm_syslog(LOG_DEBUG, e->e_id, "assigned id");
+}
+/*
+** SYNC_QUEUE_TIME -- Assure exclusive PID in any given second
+**
+** Make sure one PID can't be used by two processes in any one second.
+**
+** If the system rotates PIDs fast enough, may get the
+** same pid in the same second for two distinct processes.
+** This will interfere with the queue file naming system.
+**
+** Parameters:
+** none
+**
+** Returns:
+** none
+*/
+
+void
+sync_queue_time()
+{
+#if FAST_PID_RECYCLE
+ if (OpMode != MD_TEST &&
+ OpMode != MD_VERIFY &&
+ LastQueueTime > 0 &&
+ LastQueuePid == CurrentPid &&
+ curtime() == LastQueueTime)
+ (void) sleep(1);
+#endif /* FAST_PID_RECYCLE */
+}
+/*
+** UNLOCKQUEUE -- unlock the queue entry for a specified envelope
+**
+** Parameters:
+** e -- the envelope to unlock.
+**
+** Returns:
+** none
+**
+** Side Effects:
+** unlocks the queue for `e'.
+*/
+
+void
+unlockqueue(e)
+ ENVELOPE *e;
+{
+ if (tTd(51, 4))
+ sm_dprintf("unlockqueue(%s)\n",
+ e->e_id == NULL ? "NOQUEUE" : e->e_id);
+
+
+ /* if there is a lock file in the envelope, close it */
+ if (e->e_lockfp != NULL)
+ (void) sm_io_close(e->e_lockfp, SM_TIME_DEFAULT);
+ e->e_lockfp = NULL;
+
+ /* don't create a queue id if we don't already have one */
+ if (e->e_id == NULL)
+ return;
+
+ /* remove the transcript */
+ if (LogLevel > 87)
+ sm_syslog(LOG_DEBUG, e->e_id, "unlock");
+ if (!tTd(51, 104))
+ (void) xunlink(queuename(e, XSCRPT_LETTER));
+}
+/*
+** SETCTLUSER -- create a controlling address
+**
+** Create a fake "address" given only a local login name; this is
+** used as a "controlling user" for future recipient addresses.
+**
+** Parameters:
+** user -- the user name of the controlling user.
+** qfver -- the version stamp of this queue file.
+** e -- envelope
+**
+** Returns:
+** An address descriptor for the controlling user,
+** using storage allocated from e->e_rpool.
+**
+*/
+
+static ADDRESS *
+setctluser(user, qfver, e)
+ char *user;
+ int qfver;
+ ENVELOPE *e;
+{
+ register ADDRESS *a;
+ struct passwd *pw;
+ char *p;
+
+ /*
+ ** See if this clears our concept of controlling user.
+ */
+
+ if (user == NULL || *user == '\0')
+ return NULL;
+
+ /*
+ ** Set up addr fields for controlling user.
+ */
+
+ a = (ADDRESS *) sm_rpool_malloc_x(e->e_rpool, sizeof *a);
+ memset((char *) a, '\0', sizeof *a);
+
+ if (*user == ':')
+ {
+ p = &user[1];
+ a->q_user = sm_rpool_strdup_x(e->e_rpool, p);
+ }
+ else
+ {
+ p = strtok(user, ":");
+ a->q_user = sm_rpool_strdup_x(e->e_rpool, user);
+ if (qfver >= 2)
+ {
+ if ((p = strtok(NULL, ":")) != NULL)
+ a->q_uid = atoi(p);
+ if ((p = strtok(NULL, ":")) != NULL)
+ a->q_gid = atoi(p);
+ if ((p = strtok(NULL, ":")) != NULL)
+ {
+ char *o;
+
+ a->q_flags |= QGOODUID;
+
+ /* if there is another ':': restore it */
+ if ((o = strtok(NULL, ":")) != NULL && o > p)
+ o[-1] = ':';
+ }
+ }
+ else if ((pw = sm_getpwnam(user)) != NULL)
+ {
+ if (*pw->pw_dir == '\0')
+ a->q_home = NULL;
+ else if (strcmp(pw->pw_dir, "/") == 0)
+ a->q_home = "";
+ else
+ a->q_home = sm_rpool_strdup_x(e->e_rpool, pw->pw_dir);
+ a->q_uid = pw->pw_uid;
+ a->q_gid = pw->pw_gid;
+ a->q_flags |= QGOODUID;
+ }
+ }
+
+ a->q_flags |= QPRIMARY; /* flag as a "ctladdr" */
+ a->q_mailer = LocalMailer;
+ if (p == NULL)
+ a->q_paddr = sm_rpool_strdup_x(e->e_rpool, a->q_user);
+ else
+ a->q_paddr = sm_rpool_strdup_x(e->e_rpool, p);
+ return a;
+}
+/*
+** LOSEQFILE -- rename queue file with LOSEQF_LETTER & try to let someone know
+**
+** Parameters:
+** e -- the envelope (e->e_id will be used).
+** why -- reported to whomever can hear.
+**
+** Returns:
+** none.
+*/
+
+void
+loseqfile(e, why)
+ register ENVELOPE *e;
+ char *why;
+{
+ bool loseit = true;
+ char *p;
+ char buf[MAXPATHLEN];
+
+ if (e == NULL || e->e_id == NULL)
+ return;
+ p = queuename(e, ANYQFL_LETTER);
+ if (sm_strlcpy(buf, p, sizeof buf) >= sizeof buf)
+ return;
+ if (!bitset(EF_INQUEUE, e->e_flags))
+ queueup(e, false, true);
+ else if (QueueMode == QM_LOST)
+ loseit = false;
+
+ /* if already lost, no need to re-lose */
+ if (loseit)
+ {
+ p = queuename(e, LOSEQF_LETTER);
+ if (rename(buf, p) < 0)
+ syserr("cannot rename(%s, %s), uid=%d",
+ buf, p, (int) geteuid());
+ else if (LogLevel > 0)
+ sm_syslog(LOG_ALERT, e->e_id,
+ "Losing %s: %s", buf, why);
+ }
+ if (e->e_dfp != NULL)
+ {
+ (void) sm_io_close(e->e_dfp, SM_TIME_DEFAULT);
+ e->e_dfp = NULL;
+ }
+ e->e_flags &= ~EF_HAS_DF;
+}
+/*
+** NAME2QID -- translate a queue group name to a queue group id
+**
+** Parameters:
+** queuename -- name of queue group.
+**
+** Returns:
+** queue group id if found.
+** NOQGRP otherwise.
+*/
+
+int
+name2qid(queuename)
+ char *queuename;
+{
+ register STAB *s;
+
+ s = stab(queuename, ST_QUEUE, ST_FIND);
+ if (s == NULL)
+ return NOQGRP;
+ return s->s_quegrp->qg_index;
+}
+/*
+** QID_PRINTNAME -- create externally printable version of queue id
+**
+** Parameters:
+** e -- the envelope.
+**
+** Returns:
+** a printable version
+*/
+
+char *
+qid_printname(e)
+ ENVELOPE *e;
+{
+ char *id;
+ static char idbuf[MAXQFNAME + 34];
+
+ if (e == NULL)
+ return "";
+
+ if (e->e_id == NULL)
+ id = "";
+ else
+ id = e->e_id;
+
+ if (e->e_qdir == NOQDIR)
+ return id;
+
+ (void) sm_snprintf(idbuf, sizeof idbuf, "%.32s/%s",
+ Queue[e->e_qgrp]->qg_qpaths[e->e_qdir].qp_name,
+ id);
+ return idbuf;
+}
+/*
+** QID_PRINTQUEUE -- create full version of queue directory for data files
+**
+** Parameters:
+** qgrp -- index in queue group.
+** qdir -- the short version of the queue directory
+**
+** Returns:
+** the full pathname to the queue (might point to a static var)
+*/
+
+char *
+qid_printqueue(qgrp, qdir)
+ int qgrp;
+ int qdir;
+{
+ char *subdir;
+ static char dir[MAXPATHLEN];
+
+ if (qdir == NOQDIR)
+ return Queue[qgrp]->qg_qdir;
+
+ if (strcmp(Queue[qgrp]->qg_qpaths[qdir].qp_name, ".") == 0)
+ subdir = NULL;
+ else
+ subdir = Queue[qgrp]->qg_qpaths[qdir].qp_name;
+
+ (void) sm_strlcpyn(dir, sizeof dir, 4,
+ Queue[qgrp]->qg_qdir,
+ subdir == NULL ? "" : "/",
+ subdir == NULL ? "" : subdir,
+ (bitset(QP_SUBDF,
+ Queue[qgrp]->qg_qpaths[qdir].qp_subdirs)
+ ? "/df" : ""));
+ return dir;
+}
+
+/*
+** PICKQDIR -- Pick a queue directory from a queue group
+**
+** Parameters:
+** qg -- queue group
+** fsize -- file size in bytes
+** e -- envelope, or NULL
+**
+** Result:
+** NOQDIR if no queue directory in qg has enough free space to
+** hold a file of size 'fsize', otherwise the index of
+** a randomly selected queue directory which resides on a
+** file system with enough disk space.
+** XXX This could be extended to select a queuedir with
+** a few (the fewest?) number of entries. That data
+** is available if shared memory is used.
+**
+** Side Effects:
+** If the request fails and e != NULL then sm_syslog is called.
+*/
+
+int
+pickqdir(qg, fsize, e)
+ QUEUEGRP *qg;
+ long fsize;
+ ENVELOPE *e;
+{
+ int qdir;
+ int i;
+ long avail = 0;
+
+ /* Pick a random directory, as a starting point. */
+ if (qg->qg_numqueues <= 1)
+ qdir = 0;
+ else
+ qdir = get_rand_mod(qg->qg_numqueues);
+
+ if (MinBlocksFree <= 0 && fsize <= 0)
+ return qdir;
+
+ /*
+ ** Now iterate over the queue directories,
+ ** looking for a directory with enough space for this message.
+ */
+
+ i = qdir;
+ do
+ {
+ QPATHS *qp = &qg->qg_qpaths[i];
+ long needed = 0;
+ long fsavail = 0;
+
+ if (fsize > 0)
+ needed += fsize / FILE_SYS_BLKSIZE(qp->qp_fsysidx)
+ + ((fsize % FILE_SYS_BLKSIZE(qp->qp_fsysidx)
+ > 0) ? 1 : 0);
+ if (MinBlocksFree > 0)
+ needed += MinBlocksFree;
+ fsavail = FILE_SYS_AVAIL(qp->qp_fsysidx);
+#if SM_CONF_SHM
+ if (fsavail <= 0)
+ {
+ long blksize;
+
+ /*
+ ** might be not correctly updated,
+ ** let's try to get the info directly.
+ */
+
+ fsavail = freediskspace(FILE_SYS_NAME(qp->qp_fsysidx),
+ &blksize);
+ if (fsavail < 0)
+ fsavail = 0;
+ }
+#endif /* SM_CONF_SHM */
+ if (needed <= fsavail)
+ return i;
+ if (avail < fsavail)
+ avail = fsavail;
+
+ if (qg->qg_numqueues > 0)
+ i = (i + 1) % qg->qg_numqueues;
+ } while (i != qdir);
+
+ if (e != NULL && LogLevel > 0)
+ sm_syslog(LOG_ALERT, e->e_id,
+ "low on space (%s needs %ld bytes + %ld blocks in %s), max avail: %ld",
+ CurHostName == NULL ? "SMTP-DAEMON" : CurHostName,
+ fsize, MinBlocksFree,
+ qg->qg_qdir, avail);
+ return NOQDIR;
+}
+/*
+** SETNEWQUEUE -- Sets a new queue group and directory
+**
+** Assign a queue group and directory to an envelope and store the
+** directory in e->e_qdir.
+**
+** Parameters:
+** e -- envelope to assign a queue for.
+**
+** Returns:
+** true if successful
+** false otherwise
+**
+** Side Effects:
+** On success, e->e_qgrp and e->e_qdir are non-negative.
+** On failure (not enough disk space),
+** e->qgrp = NOQGRP, e->e_qdir = NOQDIR
+** and usrerr() is invoked (which could raise an exception).
+*/
+
+bool
+setnewqueue(e)
+ ENVELOPE *e;
+{
+ if (tTd(41, 20))
+ sm_dprintf("setnewqueue: called\n");
+
+ /* not set somewhere else */
+ if (e->e_qgrp == NOQGRP)
+ {
+ ADDRESS *q;
+
+ /*
+ ** Use the queue group of the "first" recipient, as set by
+ ** the "queuegroup" rule set. If that is not defined, then
+ ** use the queue group of the mailer of the first recipient.
+ ** If that is not defined either, then use the default
+ ** queue group.
+ ** Notice: "first" depends on the sorting of sendqueue
+ ** in recipient().
+ ** To avoid problems with "bad" recipients look
+ ** for a valid address first.
+ */
+
+ q = e->e_sendqueue;
+ while (q != NULL &&
+ (QS_IS_BADADDR(q->q_state) || QS_IS_DEAD(q->q_state)))
+ {
+ q = q->q_next;
+ }
+ if (q == NULL)
+ e->e_qgrp = 0;
+ else if (q->q_qgrp >= 0)
+ e->e_qgrp = q->q_qgrp;
+ else if (q->q_mailer != NULL &&
+ ISVALIDQGRP(q->q_mailer->m_qgrp))
+ e->e_qgrp = q->q_mailer->m_qgrp;
+ else
+ e->e_qgrp = 0;
+ e->e_dfqgrp = e->e_qgrp;
+ }
+
+ if (ISVALIDQDIR(e->e_qdir) && ISVALIDQDIR(e->e_dfqdir))
+ {
+ if (tTd(41, 20))
+ sm_dprintf("setnewqueue: e_qdir already assigned (%s)\n",
+ qid_printqueue(e->e_qgrp, e->e_qdir));
+ return true;
+ }
+
+ filesys_update();
+ e->e_qdir = pickqdir(Queue[e->e_qgrp], e->e_msgsize, e);
+ if (e->e_qdir == NOQDIR)
+ {
+ e->e_qgrp = NOQGRP;
+ if (!bitset(EF_FATALERRS, e->e_flags))
+ usrerr("452 4.4.5 Insufficient disk space; try again later");
+ e->e_flags |= EF_FATALERRS;
+ return false;
+ }
+
+ if (tTd(41, 3))
+ sm_dprintf("setnewqueue: Assigned queue directory %s\n",
+ qid_printqueue(e->e_qgrp, e->e_qdir));
+
+ if (e->e_xfqgrp == NOQGRP || e->e_xfqdir == NOQDIR)
+ {
+ e->e_xfqgrp = e->e_qgrp;
+ e->e_xfqdir = e->e_qdir;
+ }
+ e->e_dfqdir = e->e_qdir;
+ return true;
+}
+/*
+** CHKQDIR -- check a queue directory
+**
+** Parameters:
+** name -- name of queue directory
+** sff -- flags for safefile()
+**
+** Returns:
+** is it a queue directory?
+*/
+
+static bool
+chkqdir(name, sff)
+ char *name;
+ long sff;
+{
+ struct stat statb;
+ int i;
+
+ /* skip over . and .. directories */
+ if (name[0] == '.' &&
+ (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')))
+ return false;
+#if HASLSTAT
+ if (lstat(name, &statb) < 0)
+#else /* HASLSTAT */
+ if (stat(name, &statb) < 0)
+#endif /* HASLSTAT */
+ {
+ if (tTd(41, 2))
+ sm_dprintf("chkqdir: stat(\"%s\"): %s\n",
+ name, sm_errstring(errno));
+ return false;
+ }
+#if HASLSTAT
+ if (S_ISLNK(statb.st_mode))
+ {
+ /*
+ ** For a symlink we need to make sure the
+ ** target is a directory
+ */
+
+ if (stat(name, &statb) < 0)
+ {
+ if (tTd(41, 2))
+ sm_dprintf("chkqdir: stat(\"%s\"): %s\n",
+ name, sm_errstring(errno));
+ return false;
+ }
+ }
+#endif /* HASLSTAT */
+
+ if (!S_ISDIR(statb.st_mode))
+ {
+ if (tTd(41, 2))
+ sm_dprintf("chkqdir: \"%s\": Not a directory\n",
+ name);
+ return false;
+ }
+
+ /* Print a warning if unsafe (but still use it) */
+ /* XXX do this only if we want the warning? */
+ i = safedirpath(name, RunAsUid, RunAsGid, NULL, sff, 0, 0);
+ if (i != 0)
+ {
+ if (tTd(41, 2))
+ sm_dprintf("chkqdir: \"%s\": Not safe: %s\n",
+ name, sm_errstring(i));
+#if _FFR_CHK_QUEUE
+ if (LogLevel > 8)
+ sm_syslog(LOG_WARNING, NOQID,
+ "queue directory \"%s\": Not safe: %s",
+ name, sm_errstring(i));
+#endif /* _FFR_CHK_QUEUE */
+ }
+ return true;
+}
+/*
+** MULTIQUEUE_CACHE -- cache a list of paths to queues.
+**
+** Each potential queue is checked as the cache is built.
+** Thereafter, each is blindly trusted.
+** Note that we can be called again after a timeout to rebuild
+** (although code for that is not ready yet).
+**
+** Parameters:
+** basedir -- base of all queue directories.
+** blen -- strlen(basedir).
+** qg -- queue group.
+** qn -- number of queue directories already cached.
+** phash -- pointer to hash value over queue dirs.
+#if SM_CONF_SHM
+** only used if shared memory is active.
+#endif * SM_CONF_SHM *
+**
+** Returns:
+** new number of queue directories.
+*/
+
+#define INITIAL_SLOTS 20
+#define ADD_SLOTS 10
+
+static int
+multiqueue_cache(basedir, blen, qg, qn, phash)
+ char *basedir;
+ int blen;
+ QUEUEGRP *qg;
+ int qn;
+ unsigned int *phash;
+{
+ char *cp;
+ int i, len;
+ int slotsleft = 0;
+ long sff = SFF_ANYFILE;
+ char qpath[MAXPATHLEN];
+ char subdir[MAXPATHLEN];
+ char prefix[MAXPATHLEN]; /* dir relative to basedir */
+
+ if (tTd(41, 20))
+ sm_dprintf("multiqueue_cache: called\n");
+
+ /* Initialize to current directory */
+ prefix[0] = '.';
+ prefix[1] = '\0';
+ if (qg->qg_numqueues != 0 && qg->qg_qpaths != NULL)
+ {
+ for (i = 0; i < qg->qg_numqueues; i++)
+ {
+ if (qg->qg_qpaths[i].qp_name != NULL)
+ (void) sm_free(qg->qg_qpaths[i].qp_name); /* XXX */
+ }
+ (void) sm_free((char *) qg->qg_qpaths); /* XXX */
+ qg->qg_qpaths = NULL;
+ qg->qg_numqueues = 0;
+ }
+
+ /* If running as root, allow safedirpath() checks to use privs */
+ if (RunAsUid == 0)
+ sff |= SFF_ROOTOK;
+#if _FFR_CHK_QUEUE
+ sff |= SFF_SAFEDIRPATH|SFF_NOWWFILES;
+ if (!UseMSP)
+ sff |= SFF_NOGWFILES;
+#endif /* _FFR_CHK_QUEUE */
+
+ if (!SM_IS_DIR_START(qg->qg_qdir))
+ {
+ /*
+ ** XXX we could add basedir, but then we have to realloc()
+ ** the string... Maybe another time.
+ */
+
+ syserr("QueuePath %s not absolute", qg->qg_qdir);
+ ExitStat = EX_CONFIG;
+ return qn;
+ }
+
+ /* qpath: directory of current workgroup */
+ len = sm_strlcpy(qpath, qg->qg_qdir, sizeof qpath);
+ if (len >= sizeof qpath)
+ {
+ syserr("QueuePath %.256s too long (%d max)",
+ qg->qg_qdir, (int) sizeof qpath);
+ ExitStat = EX_CONFIG;
+ return qn;
+ }
+
+ /* begin of qpath must be same as basedir */
+ if (strncmp(basedir, qpath, blen) != 0 &&
+ (strncmp(basedir, qpath, blen - 1) != 0 || len != blen - 1))
+ {
+ syserr("QueuePath %s not subpath of QueueDirectory %s",
+ qpath, basedir);
+ ExitStat = EX_CONFIG;
+ return qn;
+ }
+
+ /* Do we have a nested subdirectory? */
+ if (blen < len && SM_FIRST_DIR_DELIM(qg->qg_qdir + blen) != NULL)
+ {
+
+ /* Copy subdirectory into prefix for later use */
+ if (sm_strlcpy(prefix, qg->qg_qdir + blen, sizeof prefix) >=
+ sizeof prefix)
+ {
+ syserr("QueuePath %.256s too long (%d max)",
+ qg->qg_qdir, (int) sizeof qpath);
+ ExitStat = EX_CONFIG;
+ return qn;
+ }
+ cp = SM_LAST_DIR_DELIM(prefix);
+ SM_ASSERT(cp != NULL);
+ *cp = '\0'; /* cut off trailing / */
+ }
+
+ /* This is guaranteed by the basedir check above */
+ SM_ASSERT(len >= blen - 1);
+ cp = &qpath[len - 1];
+ if (*cp == '*')
+ {
+ register DIR *dp;
+ register struct dirent *d;
+ int off;
+ char *delim;
+ char relpath[MAXPATHLEN];
+
+ *cp = '\0'; /* Overwrite wildcard */
+ if ((cp = SM_LAST_DIR_DELIM(qpath)) == NULL)
+ {
+ syserr("QueueDirectory: can not wildcard relative path");
+ if (tTd(41, 2))
+ sm_dprintf("multiqueue_cache: \"%s*\": Can not wildcard relative path.\n",
+ qpath);
+ ExitStat = EX_CONFIG;
+ return qn;
+ }
+ if (cp == qpath)
+ {
+ /*
+ ** Special case of top level wildcard, like /foo*
+ ** Change to //foo*
+ */
+
+ (void) sm_strlcpy(qpath + 1, qpath, sizeof qpath - 1);
+ ++cp;
+ }
+ delim = cp;
+ *(cp++) = '\0'; /* Replace / with \0 */
+ len = strlen(cp); /* Last component of queue directory */
+
+ /*
+ ** Path relative to basedir, with trailing /
+ ** It will be modified below to specify the subdirectories
+ ** so they can be opened without chdir().
+ */
+
+ off = sm_strlcpyn(relpath, sizeof relpath, 2, prefix, "/");
+ SM_ASSERT(off < sizeof relpath);
+
+ if (tTd(41, 2))
+ sm_dprintf("multiqueue_cache: prefix=\"%s%s\"\n",
+ relpath, cp);
+
+ /* It is always basedir: we don't need to store it per group */
+ /* XXX: optimize this! -> one more global? */
+ qg->qg_qdir = newstr(basedir);
+ qg->qg_qdir[blen - 1] = '\0'; /* cut off trailing / */
+
+ /*
+ ** XXX Should probably wrap this whole loop in a timeout
+ ** in case some wag decides to NFS mount the queues.
+ */
+
+ /* Test path to get warning messages. */
+ if (qn == 0)
+ {
+ /* XXX qg_runasuid and qg_runasgid for specials? */
+ i = safedirpath(basedir, RunAsUid, RunAsGid, NULL,
+ sff, 0, 0);
+ if (i != 0 && tTd(41, 2))
+ sm_dprintf("multiqueue_cache: \"%s\": Not safe: %s\n",
+ basedir, sm_errstring(i));
+ }
+
+ if ((dp = opendir(prefix)) == NULL)
+ {
+ syserr("can not opendir(%s/%s)", qg->qg_qdir, prefix);
+ if (tTd(41, 2))
+ sm_dprintf("multiqueue_cache: opendir(\"%s/%s\"): %s\n",
+ qg->qg_qdir, prefix,
+ sm_errstring(errno));
+ ExitStat = EX_CONFIG;
+ return qn;
+ }
+ while ((d = readdir(dp)) != NULL)
+ {
+ i = strlen(d->d_name);
+ if (i < len || strncmp(d->d_name, cp, len) != 0)
+ {
+ if (tTd(41, 5))
+ sm_dprintf("multiqueue_cache: \"%s\", skipped\n",
+ d->d_name);
+ continue;
+ }
+
+ /* Create relative pathname: prefix + local directory */
+ i = sizeof(relpath) - off;
+ if (sm_strlcpy(relpath + off, d->d_name, i) >= i)
+ continue; /* way too long */
+
+ if (!chkqdir(relpath, sff))
+ continue;
+
+ if (qg->qg_qpaths == NULL)
+ {
+ slotsleft = INITIAL_SLOTS;
+ qg->qg_qpaths = (QPATHS *)xalloc((sizeof *qg->qg_qpaths) *
+ slotsleft);
+ qg->qg_numqueues = 0;
+ }
+ else if (slotsleft < 1)
+ {
+ qg->qg_qpaths = (QPATHS *)sm_realloc((char *)qg->qg_qpaths,
+ (sizeof *qg->qg_qpaths) *
+ (qg->qg_numqueues +
+ ADD_SLOTS));
+ if (qg->qg_qpaths == NULL)
+ {
+ (void) closedir(dp);
+ return qn;
+ }
+ slotsleft += ADD_SLOTS;
+ }
+
+ /* check subdirs */
+ qg->qg_qpaths[qg->qg_numqueues].qp_subdirs = QP_NOSUB;
+
+#define CHKRSUBDIR(name, flag) \
+ (void) sm_strlcpyn(subdir, sizeof subdir, 3, relpath, "/", name); \
+ if (chkqdir(subdir, sff)) \
+ qg->qg_qpaths[qg->qg_numqueues].qp_subdirs |= flag; \
+ else
+
+
+ CHKRSUBDIR("qf", QP_SUBQF);
+ CHKRSUBDIR("df", QP_SUBDF);
+ CHKRSUBDIR("xf", QP_SUBXF);
+
+ /* assert(strlen(d->d_name) < MAXPATHLEN - 14) */
+ /* maybe even - 17 (subdirs) */
+
+ if (prefix[0] != '.')
+ qg->qg_qpaths[qg->qg_numqueues].qp_name =
+ newstr(relpath);
+ else
+ qg->qg_qpaths[qg->qg_numqueues].qp_name =
+ newstr(d->d_name);
+
+ if (tTd(41, 2))
+ sm_dprintf("multiqueue_cache: %d: \"%s\" cached (%x).\n",
+ qg->qg_numqueues, relpath,
+ qg->qg_qpaths[qg->qg_numqueues].qp_subdirs);
+#if SM_CONF_SHM
+ qg->qg_qpaths[qg->qg_numqueues].qp_idx = qn;
+ *phash = hash_q(relpath, *phash);
+#endif /* SM_CONF_SHM */
+ qg->qg_numqueues++;
+ ++qn;
+ slotsleft--;
+ }
+ (void) closedir(dp);
+
+ /* undo damage */
+ *delim = '/';
+ }
+ if (qg->qg_numqueues == 0)
+ {
+ qg->qg_qpaths = (QPATHS *) xalloc(sizeof *qg->qg_qpaths);
+
+ /* test path to get warning messages */
+ i = safedirpath(qpath, RunAsUid, RunAsGid, NULL, sff, 0, 0);
+ if (i == ENOENT)
+ {
+ syserr("can not opendir(%s)", qpath);
+ if (tTd(41, 2))
+ sm_dprintf("multiqueue_cache: opendir(\"%s\"): %s\n",
+ qpath, sm_errstring(i));
+ ExitStat = EX_CONFIG;
+ return qn;
+ }
+
+ qg->qg_qpaths[0].qp_subdirs = QP_NOSUB;
+ qg->qg_numqueues = 1;
+
+ /* check subdirs */
+#define CHKSUBDIR(name, flag) \
+ (void) sm_strlcpyn(subdir, sizeof subdir, 3, qg->qg_qdir, "/", name); \
+ if (chkqdir(subdir, sff)) \
+ qg->qg_qpaths[0].qp_subdirs |= flag; \
+ else
+
+ CHKSUBDIR("qf", QP_SUBQF);
+ CHKSUBDIR("df", QP_SUBDF);
+ CHKSUBDIR("xf", QP_SUBXF);
+
+ if (qg->qg_qdir[blen - 1] != '\0' &&
+ qg->qg_qdir[blen] != '\0')
+ {
+ /*
+ ** Copy the last component into qpaths and
+ ** cut off qdir
+ */
+
+ qg->qg_qpaths[0].qp_name = newstr(qg->qg_qdir + blen);
+ qg->qg_qdir[blen - 1] = '\0';
+ }
+ else
+ qg->qg_qpaths[0].qp_name = newstr(".");
+
+#if SM_CONF_SHM
+ qg->qg_qpaths[0].qp_idx = qn;
+ *phash = hash_q(qg->qg_qpaths[0].qp_name, *phash);
+#endif /* SM_CONF_SHM */
+ ++qn;
+ }
+ return qn;
+}
+
+/*
+** FILESYS_FIND -- find entry in FileSys table, or add new one
+**
+** Given the pathname of a directory, determine the file system
+** in which that directory resides, and return a pointer to the
+** entry in the FileSys table that describes the file system.
+** A new entry is added if necessary (and requested).
+** If the directory does not exist, -1 is returned.
+**
+** Parameters:
+** path -- pathname of directory
+** add -- add to structure if not found.
+**
+** Returns:
+** >=0: found: index in file system table
+** <0: some error, i.e.,
+** FSF_TOO_MANY: too many filesystems (-> syserr())
+** FSF_STAT_FAIL: can't stat() filesystem (-> syserr())
+** FSF_NOT_FOUND: not in list
+*/
+
+static short filesys_find __P((char *, bool));
+
+#define FSF_NOT_FOUND (-1)
+#define FSF_STAT_FAIL (-2)
+#define FSF_TOO_MANY (-3)
+
+static short
+filesys_find(path, add)
+ char *path;
+ bool add;
+{
+ struct stat st;
+ short i;
+
+ if (stat(path, &st) < 0)
+ {
+ syserr("cannot stat queue directory %s", path);
+ return FSF_STAT_FAIL;
+ }
+ for (i = 0; i < NumFileSys; ++i)
+ {
+ if (FILE_SYS_DEV(i) == st.st_dev)
+ return i;
+ }
+ if (i >= MAXFILESYS)
+ {
+ syserr("too many queue file systems (%d max)", MAXFILESYS);
+ return FSF_TOO_MANY;
+ }
+ if (!add)
+ return FSF_NOT_FOUND;
+
+ ++NumFileSys;
+ FILE_SYS_NAME(i) = path;
+ FILE_SYS_DEV(i) = st.st_dev;
+ FILE_SYS_AVAIL(i) = 0;
+ FILE_SYS_BLKSIZE(i) = 1024; /* avoid divide by zero */
+ return i;
+}
+
+/*
+** FILESYS_SETUP -- set up mapping from queue directories to file systems
+**
+** This data structure is used to efficiently check the amount of
+** free space available in a set of queue directories.
+**
+** Parameters:
+** add -- initialize structure if necessary.
+**
+** Returns:
+** 0: success
+** <0: some error, i.e.,
+** FSF_NOT_FOUND: not in list
+** FSF_STAT_FAIL: can't stat() filesystem (-> syserr())
+** FSF_TOO_MANY: too many filesystems (-> syserr())
+*/
+
+static int filesys_setup __P((bool));
+
+static int
+filesys_setup(add)
+ bool add;
+{
+ int i, j;
+ short fs;
+ int ret;
+
+ ret = 0;
+ for (i = 0; i < NumQueue && Queue[i] != NULL; i++)
+ {
+ for (j = 0; j < Queue[i]->qg_numqueues; ++j)
+ {
+ QPATHS *qp = &Queue[i]->qg_qpaths[j];
+
+ fs = filesys_find(qp->qp_name, add);
+ if (fs >= 0)
+ qp->qp_fsysidx = fs;
+ else
+ qp->qp_fsysidx = 0;
+ if (fs < ret)
+ ret = fs;
+ }
+ }
+ return ret;
+}
+
+/*
+** FILESYS_UPDATE -- update amount of free space on all file systems
+**
+** The FileSys table is used to cache the amount of free space
+** available on all queue directory file systems.
+** This function updates the cached information if it has expired.
+**
+** Parameters:
+** none.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Updates FileSys table.
+*/
+
+void
+filesys_update()
+{
+ int i;
+ long avail, blksize;
+ time_t now;
+ static time_t nextupdate = 0;
+
+#if SM_CONF_SHM
+ /* only the daemon updates this structure */
+ if (ShmId != SM_SHM_NO_ID && DaemonPid != CurrentPid)
+ return;
+#endif /* SM_CONF_SHM */
+ now = curtime();
+ if (now < nextupdate)
+ return;
+ nextupdate = now + FILESYS_UPDATE_INTERVAL;
+ for (i = 0; i < NumFileSys; ++i)
+ {
+ FILESYS *fs = &FILE_SYS(i);
+
+ avail = freediskspace(FILE_SYS_NAME(i), &blksize);
+ if (avail < 0 || blksize <= 0)
+ {
+ if (LogLevel > 5)
+ sm_syslog(LOG_ERR, NOQID,
+ "filesys_update failed: %s, fs=%s, avail=%ld, blocksize=%ld",
+ sm_errstring(errno),
+ FILE_SYS_NAME(i), avail, blksize);
+ fs->fs_avail = 0;
+ fs->fs_blksize = 1024; /* avoid divide by zero */
+ nextupdate = now + 2; /* let's do this soon again */
+ }
+ else
+ {
+ fs->fs_avail = avail;
+ fs->fs_blksize = blksize;
+ }
+ }
+}
+
+#if _FFR_ANY_FREE_FS
+/*
+** FILESYS_FREE -- check whether there is at least one fs with enough space.
+**
+** Parameters:
+** fsize -- file size in bytes
+**
+** Returns:
+** true iff there is one fs with more than fsize bytes free.
+*/
+
+bool
+filesys_free(fsize)
+ long fsize;
+{
+ int i;
+
+ if (fsize <= 0)
+ return true;
+ for (i = 0; i < NumFileSys; ++i)
+ {
+ long needed = 0;
+
+ if (FILE_SYS_AVAIL(i) < 0 || FILE_SYS_BLKSIZE(i) <= 0)
+ continue;
+ needed += fsize / FILE_SYS_BLKSIZE(i)
+ + ((fsize % FILE_SYS_BLKSIZE(i)
+ > 0) ? 1 : 0)
+ + MinBlocksFree;
+ if (needed <= FILE_SYS_AVAIL(i))
+ return true;
+ }
+ return false;
+}
+#endif /* _FFR_ANY_FREE_FS */
+
+#if _FFR_CONTROL_MSTAT
+/*
+** DISK_STATUS -- show amount of free space in queue directories
+**
+** Parameters:
+** out -- output file pointer.
+** prefix -- string to output in front of each line.
+**
+** Returns:
+** none.
+*/
+
+void
+disk_status(out, prefix)
+ SM_FILE_T *out;
+ char *prefix;
+{
+ int i;
+ long avail, blksize;
+ long free;
+
+ for (i = 0; i < NumFileSys; ++i)
+ {
+ avail = freediskspace(FILE_SYS_NAME(i), &blksize);
+ if (avail >= 0 && blksize > 0)
+ {
+ free = (long)((double) avail *
+ ((double) blksize / 1024));
+ }
+ else
+ free = -1;
+ (void) sm_io_fprintf(out, SM_TIME_DEFAULT,
+ "%s%d/%s/%ld\r\n",
+ prefix, i,
+ FILE_SYS_NAME(i),
+ free);
+ }
+}
+#endif /* _FFR_CONTROL_MSTAT */
+
+#if SM_CONF_SHM
+
+/*
+** INIT_SEM -- initialize semaphore system
+**
+** Parameters:
+** owner -- is this the owner of semaphores?
+**
+** Returns:
+** none.
+*/
+
+#if _FFR_USE_SEM_LOCKING
+#if SM_CONF_SEM
+static int SemId = -1; /* Semaphore Id */
+int SemKey = SM_SEM_KEY;
+#endif /* SM_CONF_SEM */
+#endif /* _FFR_USE_SEM_LOCKING */
+
+static void init_sem __P((bool));
+
+static void
+init_sem(owner)
+ bool owner;
+{
+#if _FFR_USE_SEM_LOCKING
+#if SM_CONF_SEM
+ SemId = sm_sem_start(SemKey, 1, 0, owner);
+ if (SemId < 0)
+ {
+ sm_syslog(LOG_ERR, NOQID,
+ "func=init_sem, sem_key=%ld, sm_sem_start=%d",
+ (long) SemKey, SemId);
+ return;
+ }
+#endif /* SM_CONF_SEM */
+#endif /* _FFR_USE_SEM_LOCKING */
+ return;
+}
+
+/*
+** STOP_SEM -- stop semaphore system
+**
+** Parameters:
+** owner -- is this the owner of semaphores?
+**
+** Returns:
+** none.
+*/
+
+static void stop_sem __P((bool));
+
+static void
+stop_sem(owner)
+ bool owner;
+{
+#if _FFR_USE_SEM_LOCKING
+#if SM_CONF_SEM
+ if (owner && SemId >= 0)
+ sm_sem_stop(SemId);
+#endif /* SM_CONF_SEM */
+#endif /* _FFR_USE_SEM_LOCKING */
+ return;
+}
+
+/*
+** UPD_QS -- update information about queue when adding/deleting an entry
+**
+** Parameters:
+** e -- envelope.
+** count -- add/remove entry (+1/0/-1: add/no change/remove)
+** space -- update the space available as well.
+** (>0/0/<0: add/no change/remove)
+** where -- caller (for logging)
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Modifies available space in filesystem.
+** Changes number of entries in queue directory.
+*/
+
+void
+upd_qs(e, count, space, where)
+ ENVELOPE *e;
+ int count;
+ int space;
+ char *where;
+{
+ short fidx;
+ int idx;
+# if _FFR_USE_SEM_LOCKING
+ int r;
+# endif /* _FFR_USE_SEM_LOCKING */
+ long s;
+
+ if (ShmId == SM_SHM_NO_ID || e == NULL)
+ return;
+ if (e->e_qgrp == NOQGRP || e->e_qdir == NOQDIR)
+ return;
+ idx = Queue[e->e_qgrp]->qg_qpaths[e->e_qdir].qp_idx;
+ if (tTd(73,2))
+ sm_dprintf("func=upd_qs, count=%d, space=%d, where=%s, idx=%d, entries=%d\n",
+ count, space, where, idx, QSHM_ENTRIES(idx));
+
+ /* XXX in theory this needs to be protected with a mutex */
+ if (QSHM_ENTRIES(idx) >= 0 && count != 0)
+ {
+# if _FFR_USE_SEM_LOCKING
+ r = sm_sem_acq(SemId, 0, 1);
+# endif /* _FFR_USE_SEM_LOCKING */
+ QSHM_ENTRIES(idx) += count;
+# if _FFR_USE_SEM_LOCKING
+ if (r >= 0)
+ r = sm_sem_rel(SemId, 0, 1);
+# endif /* _FFR_USE_SEM_LOCKING */
+ }
+
+ fidx = Queue[e->e_qgrp]->qg_qpaths[e->e_qdir].qp_fsysidx;
+ if (fidx < 0)
+ return;
+
+ /* update available space also? (might be loseqfile) */
+ if (space == 0)
+ return;
+
+ /* convert size to blocks; this causes rounding errors */
+ s = e->e_msgsize / FILE_SYS_BLKSIZE(fidx);
+ if (s == 0)
+ return;
+
+ /* XXX in theory this needs to be protected with a mutex */
+ if (space > 0)
+ FILE_SYS_AVAIL(fidx) += s;
+ else
+ FILE_SYS_AVAIL(fidx) -= s;
+
+}
+
+#if _FFR_SELECT_SHM
+
+static bool write_key_file __P((char *, long));
+static long read_key_file __P((char *, long));
+
+/*
+** WRITE_KEY_FILE -- record some key into a file.
+**
+** Parameters:
+** keypath -- file name.
+** key -- key to write.
+**
+** Returns:
+** true iff file could be written.
+**
+** Side Effects:
+** writes file.
+*/
+
+static bool
+write_key_file(keypath, key)
+ char *keypath;
+ long key;
+{
+ bool ok;
+ long sff;
+ SM_FILE_T *keyf;
+
+ ok = false;
+ if (keypath == NULL || *keypath == '\0')
+ return ok;
+ sff = SFF_NOLINK|SFF_ROOTOK|SFF_REGONLY|SFF_CREAT;
+ if (TrustedUid != 0 && RealUid == TrustedUid)
+ sff |= SFF_OPENASROOT;
+ keyf = safefopen(keypath, O_WRONLY|O_TRUNC, FileMode, sff);
+ if (keyf == NULL)
+ {
+ sm_syslog(LOG_ERR, NOQID, "unable to write %s: %s",
+ keypath, sm_errstring(errno));
+ }
+ else
+ {
+ ok = sm_io_fprintf(keyf, SM_TIME_DEFAULT, "%ld\n", key) !=
+ SM_IO_EOF;
+ ok = (sm_io_close(keyf, SM_TIME_DEFAULT) != SM_IO_EOF) && ok;
+ }
+ return ok;
+}
+
+/*
+** READ_KEY_FILE -- read a key from a file.
+**
+** Parameters:
+** keypath -- file name.
+** key -- default key.
+**
+** Returns:
+** key.
+*/
+
+static long
+read_key_file(keypath, key)
+ char *keypath;
+ long key;
+{
+ int r;
+ long sff, n;
+ SM_FILE_T *keyf;
+
+ if (keypath == NULL || *keypath == '\0')
+ return key;
+ sff = SFF_NOLINK|SFF_ROOTOK|SFF_REGONLY;
+ if (RealUid == 0 || (TrustedUid != 0 && RealUid == TrustedUid))
+ sff |= SFF_OPENASROOT;
+ keyf = safefopen(keypath, O_RDONLY, FileMode, sff);
+ if (keyf == NULL)
+ {
+ sm_syslog(LOG_ERR, NOQID, "unable to read %s: %s",
+ keypath, sm_errstring(errno));
+ }
+ else
+ {
+ r = sm_io_fscanf(keyf, SM_TIME_DEFAULT, "%ld", &n);
+ if (r == 1)
+ key = n;
+ (void) sm_io_close(keyf, SM_TIME_DEFAULT);
+ }
+ return key;
+}
+#endif /* _FFR_SELECT_SHM */
+
+/*
+** INIT_SHM -- initialize shared memory structure
+**
+** Initialize or attach to shared memory segment.
+** Currently it is not a fatal error if this doesn't work.
+** However, it causes us to have a "fallback" storage location
+** for everything that is supposed to be in the shared memory,
+** which makes the code slightly ugly.
+**
+** Parameters:
+** qn -- number of queue directories.
+** owner -- owner of shared memory.
+** hash -- identifies data that is stored in shared memory.
+**
+** Returns:
+** none.
+*/
+
+static void init_shm __P((int, bool, unsigned int));
+
+static void
+init_shm(qn, owner, hash)
+ int qn;
+ bool owner;
+ unsigned int hash;
+{
+ int i;
+ int count;
+ int save_errno;
+#if _FFR_SELECT_SHM
+ bool keyselect;
+#endif /* _FFR_SELECT_SHM */
+
+ PtrFileSys = &FileSys[0];
+ PNumFileSys = &Numfilesys;
+#if _FFR_SELECT_SHM
+/* if this "key" is specified: select one yourself */
+# define SEL_SHM_KEY ((key_t) -1)
+# define FIRST_SHM_KEY 25
+#endif /* _FFR_SELECT_SHM */
+
+ /* This allows us to disable shared memory at runtime. */
+ if (ShmKey == 0)
+ return;
+
+ count = 0;
+ shms = SM_T_SIZE + qn * sizeof(QUEUE_SHM_T);
+#if _FFR_SELECT_SHM
+ keyselect = ShmKey == SEL_SHM_KEY;
+ if (keyselect)
+ {
+ if (owner)
+ ShmKey = FIRST_SHM_KEY;
+ else
+ {
+ ShmKey = read_key_file(ShmKeyFile, ShmKey);
+ keyselect = false;
+ if (ShmKey == SEL_SHM_KEY)
+ goto error;
+ }
+ }
+#endif /* _FFR_SELECT_SHM */
+ for (;;)
+ {
+ /* allow read/write access for group? */
+ Pshm = sm_shmstart(ShmKey, shms,
+ SHM_R|SHM_W|(SHM_R>>3)|(SHM_W>>3),
+ &ShmId, owner);
+ save_errno = errno;
+ if (Pshm != NULL || !sm_file_exists(save_errno))
+ break;
+ if (++count >= 3)
+ {
+#if _FFR_SELECT_SHM
+ if (keyselect)
+ {
+ ++ShmKey;
+
+ /* back where we started? */
+ if (ShmKey == SEL_SHM_KEY)
+ break;
+ continue;
+ }
+#endif /* _FFR_SELECT_SHM */
+ break;
+ }
+#if _FFR_SELECT_SHM
+ /* only sleep if we are at the first key */
+ if (!keyselect || ShmKey == SEL_SHM_KEY)
+#endif /* _FFR_SELECT_SHM */
+ sleep(count);
+ }
+ if (Pshm != NULL)
+ {
+ int *p;
+
+#if _FFR_SELECT_SHM
+ if (keyselect)
+ (void) write_key_file(ShmKeyFile, (long) ShmKey);
+#endif /* _FFR_SELECT_SHM */
+ if (owner && RunAsUid != 0)
+ {
+ i = sm_shmsetowner(ShmId, RunAsUid, RunAsGid,
+ 0660);
+ if (i != 0)
+ sm_syslog(LOG_ERR, NOQID,
+ "key=%ld, sm_shmsetowner=%d, RunAsUid=%d, RunAsGid=%d",
+ (long) ShmKey, i,
+ RunAsUid, RunAsGid);
+ }
+ p = (int *) Pshm;
+ if (owner)
+ {
+ *p = (int) shms;
+ *((pid_t *) SHM_OFF_PID(Pshm)) = CurrentPid;
+ p = (int *) SHM_OFF_TAG(Pshm);
+ *p = hash;
+ }
+ else
+ {
+ if (*p != (int) shms)
+ {
+ save_errno = EINVAL;
+ cleanup_shm(false);
+ goto error;
+ }
+ p = (int *) SHM_OFF_TAG(Pshm);
+ if (*p != (int) hash)
+ {
+ save_errno = EINVAL;
+ cleanup_shm(false);
+ goto error;
+ }
+
+ /*
+ ** XXX how to check the pid?
+ ** Read it from the pid-file? That does
+ ** not need to exist.
+ ** We could disable shm if we can't confirm
+ ** that it is the right one.
+ */
+ }
+
+ PtrFileSys = (FILESYS *) OFF_FILE_SYS(Pshm);
+ PNumFileSys = (int *) OFF_NUM_FILE_SYS(Pshm);
+ QShm = (QUEUE_SHM_T *) OFF_QUEUE_SHM(Pshm);
+ PRSATmpCnt = (int *) OFF_RSA_TMP_CNT(Pshm);
+ *PRSATmpCnt = 0;
+ if (owner)
+ {
+ /* initialize values in shared memory */
+ NumFileSys = 0;
+ for (i = 0; i < qn; i++)
+ QShm[i].qs_entries = -1;
+ }
+ init_sem(owner);
+ return;
+ }
+ error:
+ if (LogLevel > (owner ? 8 : 11))
+ {
+ sm_syslog(owner ? LOG_ERR : LOG_NOTICE, NOQID,
+ "can't %s shared memory, key=%ld: %s",
+ owner ? "initialize" : "attach to",
+ (long) ShmKey, sm_errstring(save_errno));
+ }
+}
+#endif /* SM_CONF_SHM */
+
+
+/*
+** SETUP_QUEUES -- setup all queue groups
+**
+** Parameters:
+** owner -- owner of shared memory.
+**
+** Returns:
+** none.
+**
+#if SM_CONF_SHM
+** Side Effects:
+** attaches shared memory.
+#endif * SM_CONF_SHM *
+*/
+
+void
+setup_queues(owner)
+ bool owner;
+{
+ int i, qn, len;
+ unsigned int hashval;
+ time_t now;
+ char basedir[MAXPATHLEN];
+ struct stat st;
+
+ /*
+ ** Determine basedir for all queue directories.
+ ** All queue directories must be (first level) subdirectories
+ ** of the basedir. The basedir is the QueueDir
+ ** without wildcards, but with trailing /
+ */
+
+ hashval = 0;
+ errno = 0;
+ len = sm_strlcpy(basedir, QueueDir, sizeof basedir);
+
+ /* Provide space for trailing '/' */
+ if (len >= sizeof basedir - 1)
+ {
+ syserr("QueueDirectory: path too long: %d, max %d",
+ len, (int) sizeof basedir - 1);
+ ExitStat = EX_CONFIG;
+ return;
+ }
+ SM_ASSERT(len > 0);
+ if (basedir[len - 1] == '*')
+ {
+ char *cp;
+
+ cp = SM_LAST_DIR_DELIM(basedir);
+ if (cp == NULL)
+ {
+ syserr("QueueDirectory: can not wildcard relative path \"%s\"",
+ QueueDir);
+ if (tTd(41, 2))
+ sm_dprintf("setup_queues: \"%s\": Can not wildcard relative path.\n",
+ QueueDir);
+ ExitStat = EX_CONFIG;
+ return;
+ }
+
+ /* cut off wildcard pattern */
+ *++cp = '\0';
+ len = cp - basedir;
+ }
+ else if (!SM_IS_DIR_DELIM(basedir[len - 1]))
+ {
+ /* append trailing slash since it is a directory */
+ basedir[len] = '/';
+ basedir[++len] = '\0';
+ }
+
+ /* len counts up to the last directory delimiter */
+ SM_ASSERT(basedir[len - 1] == '/');
+
+ if (chdir(basedir) < 0)
+ {
+ int save_errno = errno;
+
+ syserr("can not chdir(%s)", basedir);
+ if (save_errno == EACCES)
+ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ "Program mode requires special privileges, e.g., root or TrustedUser.\n");
+ if (tTd(41, 2))
+ sm_dprintf("setup_queues: \"%s\": %s\n",
+ basedir, sm_errstring(errno));
+ ExitStat = EX_CONFIG;
+ return;
+ }
+#if SM_CONF_SHM
+ hashval = hash_q(basedir, hashval);
+#endif /* SM_CONF_SHM */
+
+ /* initialize for queue runs */
+ DoQueueRun = false;
+ now = curtime();
+ for (i = 0; i < NumQueue && Queue[i] != NULL; i++)
+ Queue[i]->qg_nextrun = now;
+
+
+ if (UseMSP && OpMode != MD_TEST)
+ {
+ long sff = SFF_CREAT;
+
+ if (stat(".", &st) < 0)
+ {
+ syserr("can not stat(%s)", basedir);
+ if (tTd(41, 2))
+ sm_dprintf("setup_queues: \"%s\": %s\n",
+ basedir, sm_errstring(errno));
+ ExitStat = EX_CONFIG;
+ return;
+ }
+ if (RunAsUid == 0)
+ sff |= SFF_ROOTOK;
+
+ /*
+ ** Check queue directory permissions.
+ ** Can we write to a group writable queue directory?
+ */
+
+ if (bitset(S_IWGRP, QueueFileMode) &&
+ bitset(S_IWGRP, st.st_mode) &&
+ safefile(" ", RunAsUid, RunAsGid, RunAsUserName, sff,
+ QueueFileMode, NULL) != 0)
+ {
+ syserr("can not write to queue directory %s (RunAsGid=%d, required=%d)",
+ basedir, (int) RunAsGid, (int) st.st_gid);
+ }
+ if (bitset(S_IWOTH|S_IXOTH, st.st_mode))
+ {
+#if _FFR_MSP_PARANOIA
+ syserr("dangerous permissions=%o on queue directory %s",
+ (int) st.st_mode, basedir);
+#else /* _FFR_MSP_PARANOIA */
+ if (LogLevel > 0)
+ sm_syslog(LOG_ERR, NOQID,
+ "dangerous permissions=%o on queue directory %s",
+ (int) st.st_mode, basedir);
+#endif /* _FFR_MSP_PARANOIA */
+ }
+#if _FFR_MSP_PARANOIA
+ if (NumQueue > 1)
+ syserr("can not use multiple queues for MSP");
+#endif /* _FFR_MSP_PARANOIA */
+ }
+
+ /* initial number of queue directories */
+ qn = 0;
+ for (i = 0; i < NumQueue && Queue[i] != NULL; i++)
+ qn = multiqueue_cache(basedir, len, Queue[i], qn, &hashval);
+
+#if SM_CONF_SHM
+ init_shm(qn, owner, hashval);
+ i = filesys_setup(owner || ShmId == SM_SHM_NO_ID);
+ if (i == FSF_NOT_FOUND)
+ {
+ /*
+ ** We didn't get the right filesystem data
+ ** This may happen if we don't have the right shared memory.
+ ** So let's do this without shared memory.
+ */
+
+ SM_ASSERT(!owner);
+ cleanup_shm(false); /* release shared memory */
+ i = filesys_setup(false);
+ if (i < 0)
+ syserr("filesys_setup failed twice, result=%d", i);
+ else if (LogLevel > 8)
+ sm_syslog(LOG_WARNING, NOQID,
+ "shared memory does not contain expected data, ignored");
+ }
+#else /* SM_CONF_SHM */
+ i = filesys_setup(true);
+#endif /* SM_CONF_SHM */
+ if (i < 0)
+ ExitStat = EX_CONFIG;
+}
+
+#if SM_CONF_SHM
+/*
+** CLEANUP_SHM -- do some cleanup work for shared memory etc
+**
+** Parameters:
+** owner -- owner of shared memory?
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** detaches shared memory.
+*/
+
+void
+cleanup_shm(owner)
+ bool owner;
+{
+ if (ShmId != SM_SHM_NO_ID)
+ {
+ if (sm_shmstop(Pshm, ShmId, owner) < 0 && LogLevel > 8)
+ sm_syslog(LOG_INFO, NOQID, "sm_shmstop failed=%s",
+ sm_errstring(errno));
+ Pshm = NULL;
+ ShmId = SM_SHM_NO_ID;
+ }
+ stop_sem(owner);
+}
+#endif /* SM_CONF_SHM */
+
+/*
+** CLEANUP_QUEUES -- do some cleanup work for queues
+**
+** Parameters:
+** none.
+**
+** Returns:
+** none.
+**
+*/
+
+void
+cleanup_queues()
+{
+ sync_queue_time();
+}
+/*
+** SET_DEF_QUEUEVAL -- set default values for a queue group.
+**
+** Parameters:
+** qg -- queue group
+** all -- set all values (true for default group)?
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** sets default values for the queue group.
+*/
+
+void
+set_def_queueval(qg, all)
+ QUEUEGRP *qg;
+ bool all;
+{
+ if (bitnset(QD_DEFINED, qg->qg_flags))
+ return;
+ if (all)
+ qg->qg_qdir = QueueDir;
+#if _FFR_QUEUE_GROUP_SORTORDER
+ qg->qg_sortorder = QueueSortOrder;
+#endif /* _FFR_QUEUE_GROUP_SORTORDER */
+ qg->qg_maxqrun = all ? MaxRunnersPerQueue : -1;
+ qg->qg_nice = NiceQueueRun;
+}
+/*
+** MAKEQUEUE -- define a new queue.
+**
+** Parameters:
+** line -- description of queue. This is in labeled fields.
+** The fields are:
+** F -- the flags associated with the queue
+** I -- the interval between running the queue
+** J -- the maximum # of jobs in work list
+** [M -- the maximum # of jobs in a queue run]
+** N -- the niceness at which to run
+** P -- the path to the queue
+** S -- the queue sorting order
+** R -- number of parallel queue runners
+** r -- max recipients per envelope
+** The first word is the canonical name of the queue.
+** qdef -- this is a 'Q' definition from .cf
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** enters the queue into the queue table.
+*/
+
+void
+makequeue(line, qdef)
+ char *line;
+ bool qdef;
+{
+ register char *p;
+ register QUEUEGRP *qg;
+ register STAB *s;
+ int i;
+ char fcode;
+
+ /* allocate a queue and set up defaults */
+ qg = (QUEUEGRP *) xalloc(sizeof *qg);
+ memset((char *) qg, '\0', sizeof *qg);
+
+ if (line[0] == '\0')
+ {
+ syserr("name required for queue");
+ return;
+ }
+
+ /* collect the queue name */
+ for (p = line;
+ *p != '\0' && *p != ',' && !(isascii(*p) && isspace(*p));
+ p++)
+ continue;
+ if (*p != '\0')
+ *p++ = '\0';
+ qg->qg_name = newstr(line);
+
+ /* set default values, can be overridden below */
+ set_def_queueval(qg, false);
+
+ /* now scan through and assign info from the fields */
+ while (*p != '\0')
+ {
+ auto char *delimptr;
+
+ while (*p != '\0' &&
+ (*p == ',' || (isascii(*p) && isspace(*p))))
+ p++;
+
+ /* p now points to field code */
+ fcode = *p;
+ while (*p != '\0' && *p != '=' && *p != ',')
+ p++;
+ if (*p++ != '=')
+ {
+ syserr("queue %s: `=' expected", qg->qg_name);
+ return;
+ }
+ while (isascii(*p) && isspace(*p))
+ p++;
+
+ /* p now points to the field body */
+ p = munchstring(p, &delimptr, ',');
+
+ /* install the field into the queue struct */
+ switch (fcode)
+ {
+ case 'P': /* pathname */
+ if (*p == '\0')
+ syserr("queue %s: empty path name",
+ qg->qg_name);
+ else
+ qg->qg_qdir = newstr(p);
+ break;
+
+ case 'F': /* flags */
+ for (; *p != '\0'; p++)
+ if (!(isascii(*p) && isspace(*p)))
+ setbitn(*p, qg->qg_flags);
+ break;
+
+ /*
+ ** Do we need two intervals here:
+ ** One for persistent queue runners,
+ ** one for "normal" queue runs?
+ */
+
+ case 'I': /* interval between running the queue */
+ qg->qg_queueintvl = convtime(p, 'm');
+ break;
+
+ case 'N': /* run niceness */
+ qg->qg_nice = atoi(p);
+ break;
+
+ case 'R': /* maximum # of runners for the group */
+ i = atoi(p);
+
+ /* can't have more runners than allowed total */
+ if (MaxQueueChildren > 0 && i > MaxQueueChildren)
+ {
+ qg->qg_maxqrun = MaxQueueChildren;
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Q=%s: R=%d exceeds MaxQueueChildren=%d, set to MaxQueueChildren\n",
+ qg->qg_name, i,
+ MaxQueueChildren);
+ }
+ else
+ qg->qg_maxqrun = i;
+ break;
+
+ case 'J': /* maximum # of jobs in work list */
+ qg->qg_maxlist = atoi(p);
+ break;
+
+ case 'r': /* max recipients per envelope */
+ qg->qg_maxrcpt = atoi(p);
+ break;
+
+#if _FFR_QUEUE_GROUP_SORTORDER
+ case 'S': /* queue sorting order */
+ switch (*p)
+ {
+ case 'h': /* Host first */
+ case 'H':
+ qg->qg_sortorder = QSO_BYHOST;
+ break;
+
+ case 'p': /* Priority order */
+ case 'P':
+ qg->qg_sortorder = QSO_BYPRIORITY;
+ break;
+
+ case 't': /* Submission time */
+ case 'T':
+ qg->qg_sortorder = QSO_BYTIME;
+ break;
+
+ case 'f': /* File name */
+ case 'F':
+ qg->qg_sortorder = QSO_BYFILENAME;
+ break;
+
+ case 'm': /* Modification time */
+ case 'M':
+ qg->qg_sortorder = QSO_BYMODTIME;
+ break;
+
+ case 'r': /* Random */
+ case 'R':
+ qg->qg_sortorder = QSO_RANDOM;
+ break;
+
+# if _FFR_RHS
+ case 's': /* Shuffled host name */
+ case 'S':
+ qg->qg_sortorder = QSO_BYSHUFFLE;
+ break;
+# endif /* _FFR_RHS */
+
+ case 'n': /* none */
+ case 'N':
+ qg->qg_sortorder = QSO_NONE;
+ break;
+
+ default:
+ syserr("Invalid queue sort order \"%s\"", p);
+ }
+ break;
+#endif /* _FFR_QUEUE_GROUP_SORTORDER */
+
+ default:
+ syserr("Q%s: unknown queue equate %c=",
+ qg->qg_name, fcode);
+ break;
+ }
+
+ p = delimptr;
+ }
+
+#if !HASNICE
+ if (qg->qg_nice != NiceQueueRun)
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Q%s: Warning: N= set on system that doesn't support nice()\n",
+ qg->qg_name);
+ }
+#endif /* !HASNICE */
+
+ /* do some rationality checking */
+ if (NumQueue >= MAXQUEUEGROUPS)
+ {
+ syserr("too many queue groups defined (%d max)",
+ MAXQUEUEGROUPS);
+ return;
+ }
+
+ if (qg->qg_qdir == NULL)
+ {
+ if (QueueDir == NULL || *QueueDir == '\0')
+ {
+ syserr("QueueDir must be defined before queue groups");
+ return;
+ }
+ qg->qg_qdir = newstr(QueueDir);
+ }
+
+ if (qg->qg_maxqrun > 1 && !bitnset(QD_FORK, qg->qg_flags))
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Warning: Q=%s: R=%d: multiple queue runners specified\n\tbut flag '%c' is not set\n",
+ qg->qg_name, qg->qg_maxqrun, QD_FORK);
+ }
+
+ /* enter the queue into the symbol table */
+ if (tTd(37, 8))
+ sm_syslog(LOG_INFO, NOQID,
+ "Adding %s to stab, path: %s", qg->qg_name,
+ qg->qg_qdir);
+ s = stab(qg->qg_name, ST_QUEUE, ST_ENTER);
+ if (s->s_quegrp != NULL)
+ {
+ i = s->s_quegrp->qg_index;
+
+ /* XXX what about the pointers inside this struct? */
+ sm_free(s->s_quegrp); /* XXX */
+ }
+ else
+ i = NumQueue++;
+ Queue[i] = s->s_quegrp = qg;
+ qg->qg_index = i;
+
+ /* set default value for max queue runners */
+ if (qg->qg_maxqrun < 0)
+ {
+ if (MaxRunnersPerQueue > 0)
+ qg->qg_maxqrun = MaxRunnersPerQueue;
+ else
+ qg->qg_maxqrun = 1;
+ }
+ if (qdef)
+ setbitn(QD_DEFINED, qg->qg_flags);
+}
+#if 0
+/*
+** HASHFQN -- calculate a hash value for a fully qualified host name
+**
+** Arguments:
+** fqn -- an all lower-case host.domain string
+** buckets -- the number of buckets (queue directories)
+**
+** Returns:
+** a bucket number (signed integer)
+** -1 on error
+**
+** Contributed by Exactis.com, Inc.
+*/
+
+int
+hashfqn(fqn, buckets)
+ register char *fqn;
+ int buckets;
+{
+ register char *p;
+ register int h = 0, hash, cnt;
+
+ if (fqn == NULL)
+ return -1;
+
+ /*
+ ** A variation on the gdb hash
+ ** This is the best as of Feb 19, 1996 --bcx
+ */
+
+ p = fqn;
+ h = 0x238F13AF * strlen(p);
+ for (cnt = 0; *p != 0; ++p, cnt++)
+ {
+ h = (h + (*p << (cnt * 5 % 24))) & 0x7FFFFFFF;
+ }
+ h = (1103515243 * h + 12345) & 0x7FFFFFFF;
+ if (buckets < 2)
+ hash = 0;
+ else
+ hash = (h % buckets);
+
+ return hash;
+}
+#endif /* 0 */
+
+/*
+** A structure for sorting Queue according to maxqrun without
+** screwing up Queue itself.
+*/
+
+struct sortqgrp
+{
+ int sg_idx; /* original index */
+ int sg_maxqrun; /* max queue runners */
+};
+typedef struct sortqgrp SORTQGRP_T;
+static int cmpidx __P((const void *, const void *));
+
+static int
+cmpidx(a, b)
+ const void *a;
+ const void *b;
+{
+ /* The sort is highest to lowest, so the comparison is reversed */
+ if (((SORTQGRP_T *)a)->sg_maxqrun < ((SORTQGRP_T *)b)->sg_maxqrun)
+ return 1;
+ else if (((SORTQGRP_T *)a)->sg_maxqrun > ((SORTQGRP_T *)b)->sg_maxqrun)
+ return -1;
+ else
+ return 0;
+}
+
+/*
+** MAKEWORKGROUP -- balance queue groups into work groups per MaxQueueChildren
+**
+** Take the now defined queue groups and assign them to work groups.
+** This is done to balance out the number of concurrently active
+** queue runners such that MaxQueueChildren is not exceeded. This may
+** result in more than one queue group per work group. In such a case
+** the number of running queue groups in that work group will have no
+** more than the work group maximum number of runners (a "fair" portion
+** of MaxQueueRunners). All queue groups within a work group will get a
+** chance at running.
+**
+** Parameters:
+** none.
+**
+** Returns:
+** nothing.
+**
+** Side Effects:
+** Sets up WorkGrp structure.
+*/
+
+void
+makeworkgroups()
+{
+ int i, j, total_runners, dir, h;
+ SORTQGRP_T si[MAXQUEUEGROUPS + 1];
+
+ total_runners = 0;
+ if (NumQueue == 1 && strcmp(Queue[0]->qg_name, "mqueue") == 0)
+ {
+ /*
+ ** There is only the "mqueue" queue group (a default)
+ ** containing all of the queues. We want to provide to
+ ** this queue group the maximum allowable queue runners.
+ ** To match older behavior (8.10/8.11) we'll try for
+ ** 1 runner per queue capping it at MaxQueueChildren.
+ ** So if there are N queues, then there will be N runners
+ ** for the "mqueue" queue group (where N is kept less than
+ ** MaxQueueChildren).
+ */
+
+ NumWorkGroups = 1;
+ WorkGrp[0].wg_numqgrp = 1;
+ WorkGrp[0].wg_qgs = (QUEUEGRP **) xalloc(sizeof(QUEUEGRP *));
+ WorkGrp[0].wg_qgs[0] = Queue[0];
+ if (MaxQueueChildren > 0 &&
+ Queue[0]->qg_numqueues > MaxQueueChildren)
+ WorkGrp[0].wg_runners = MaxQueueChildren;
+ else
+ WorkGrp[0].wg_runners = Queue[0]->qg_numqueues;
+
+ Queue[0]->qg_wgrp = 0;
+
+ /* can't have more runners than allowed total */
+ if (MaxQueueChildren > 0 &&
+ Queue[0]->qg_maxqrun > MaxQueueChildren)
+ Queue[0]->qg_maxqrun = MaxQueueChildren;
+ WorkGrp[0].wg_maxact = Queue[0]->qg_maxqrun;
+ WorkGrp[0].wg_lowqintvl = Queue[0]->qg_queueintvl;
+ return;
+ }
+
+ for (i = 0; i < NumQueue; i++)
+ {
+ si[i].sg_maxqrun = Queue[i]->qg_maxqrun;
+ si[i].sg_idx = i;
+ }
+ qsort(si, NumQueue, sizeof(si[0]), cmpidx);
+
+ NumWorkGroups = 0;
+ for (i = 0; i < NumQueue; i++)
+ {
+ total_runners += si[i].sg_maxqrun;
+ if (MaxQueueChildren <= 0 || total_runners <= MaxQueueChildren)
+ NumWorkGroups++;
+ else
+ break;
+ }
+
+ if (NumWorkGroups < 1)
+ NumWorkGroups = 1; /* gotta have one at least */
+ else if (NumWorkGroups > MAXWORKGROUPS)
+ NumWorkGroups = MAXWORKGROUPS; /* the limit */
+
+ /*
+ ** We now know the number of work groups to pack the queue groups
+ ** into. The queue groups in 'Queue' are sorted from highest
+ ** to lowest for the number of runners per queue group.
+ ** We put the queue groups with the largest number of runners
+ ** into work groups first. Then the smaller ones are fitted in
+ ** where it looks best.
+ */
+
+ j = 0;
+ dir = 1;
+ for (i = 0; i < NumQueue; i++)
+ {
+ /* a to-and-fro packing scheme, continue from last position */
+ if (j >= NumWorkGroups)
+ {
+ dir = -1;
+ j = NumWorkGroups - 1;
+ }
+ else if (j < 0)
+ {
+ j = 0;
+ dir = 1;
+ }
+
+ if (WorkGrp[j].wg_qgs == NULL)
+ WorkGrp[j].wg_qgs = (QUEUEGRP **)sm_malloc(sizeof(QUEUEGRP *) *
+ (WorkGrp[j].wg_numqgrp + 1));
+ else
+ WorkGrp[j].wg_qgs = (QUEUEGRP **)sm_realloc(WorkGrp[j].wg_qgs,
+ sizeof(QUEUEGRP *) *
+ (WorkGrp[j].wg_numqgrp + 1));
+ if (WorkGrp[j].wg_qgs == NULL)
+ {
+ syserr("!cannot allocate memory for work queues, need %d bytes",
+ (int) (sizeof(QUEUEGRP *) *
+ (WorkGrp[j].wg_numqgrp + 1)));
+ }
+
+ h = si[i].sg_idx;
+ WorkGrp[j].wg_qgs[WorkGrp[j].wg_numqgrp] = Queue[h];
+ WorkGrp[j].wg_numqgrp++;
+ WorkGrp[j].wg_runners += Queue[h]->qg_maxqrun;
+ Queue[h]->qg_wgrp = j;
+
+ if (WorkGrp[j].wg_maxact == 0)
+ {
+ /* can't have more runners than allowed total */
+ if (MaxQueueChildren > 0 &&
+ Queue[h]->qg_maxqrun > MaxQueueChildren)
+ Queue[h]->qg_maxqrun = MaxQueueChildren;
+ WorkGrp[j].wg_maxact = Queue[h]->qg_maxqrun;
+ }
+
+ /*
+ ** XXX: must wg_lowqintvl be the GCD?
+ ** qg1: 2m, qg2: 3m, minimum: 2m, when do queue runs for
+ ** qg2 occur?
+ */
+
+ /* keep track of the lowest interval for a persistent runner */
+ if (Queue[h]->qg_queueintvl > 0 &&
+ WorkGrp[j].wg_lowqintvl < Queue[h]->qg_queueintvl)
+ WorkGrp[j].wg_lowqintvl = Queue[h]->qg_queueintvl;
+ j += dir;
+ }
+ if (tTd(41, 9))
+ {
+ for (i = 0; i < NumWorkGroups; i++)
+ {
+ sm_dprintf("Workgroup[%d]=", i);
+ for (j = 0; j < WorkGrp[i].wg_numqgrp; j++)
+ {
+ sm_dprintf("%s, ",
+ WorkGrp[i].wg_qgs[j]->qg_name);
+ }
+ sm_dprintf("\n");
+ }
+ }
+}
+
+/*
+** DUP_DF -- duplicate envelope data file
+**
+** Copy the data file from the 'old' envelope to the 'new' envelope
+** in the most efficient way possible.
+**
+** Create a hard link from the 'old' data file to the 'new' data file.
+** If the old and new queue directories are on different file systems,
+** then the new data file link is created in the old queue directory,
+** and the new queue file will contain a 'd' record pointing to the
+** directory containing the new data file.
+**
+** Parameters:
+** old -- old envelope.
+** new -- new envelope.
+**
+** Results:
+** Returns true on success, false on failure.
+**
+** Side Effects:
+** On success, the new data file is created.
+** On fatal failure, EF_FATALERRS is set in old->e_flags.
+*/
+
+static bool dup_df __P((ENVELOPE *, ENVELOPE *));
+
+static bool
+dup_df(old, new)
+ ENVELOPE *old;
+ ENVELOPE *new;
+{
+ int ofs, nfs, r;
+ char opath[MAXPATHLEN];
+ char npath[MAXPATHLEN];
+
+ if (!bitset(EF_HAS_DF, old->e_flags))
+ {
+ /*
+ ** this can happen if: SuperSafe != True
+ ** and a bounce mail is sent that is split.
+ */
+
+ queueup(old, false, true);
+ }
+ SM_REQUIRE(ISVALIDQGRP(old->e_qgrp) && ISVALIDQDIR(old->e_qdir));
+ SM_REQUIRE(ISVALIDQGRP(new->e_qgrp) && ISVALIDQDIR(new->e_qdir));
+
+ (void) sm_strlcpy(opath, queuename(old, DATAFL_LETTER), sizeof opath);
+ (void) sm_strlcpy(npath, queuename(new, DATAFL_LETTER), sizeof npath);
+
+ if (old->e_dfp != NULL)
+ {
+ r = sm_io_setinfo(old->e_dfp, SM_BF_COMMIT, NULL);
+ if (r < 0 && errno != EINVAL)
+ {
+ syserr("@can't commit %s", opath);
+ old->e_flags |= EF_FATALERRS;
+ return false;
+ }
+ }
+
+ /*
+ ** Attempt to create a hard link, if we think both old and new
+ ** are on the same file system, otherwise copy the file.
+ **
+ ** Don't waste time attempting a hard link unless old and new
+ ** are on the same file system.
+ */
+
+ ofs = Queue[old->e_qgrp]->qg_qpaths[old->e_qdir].qp_fsysidx;
+ nfs = Queue[new->e_qgrp]->qg_qpaths[new->e_qdir].qp_fsysidx;
+ if (FILE_SYS_DEV(ofs) == FILE_SYS_DEV(nfs))
+ {
+ if (link(opath, npath) == 0)
+ {
+ new->e_flags |= EF_HAS_DF;
+ SYNC_DIR(npath, true);
+ return true;
+ }
+ goto error;
+ }
+
+ /*
+ ** Can't link across queue directories, so try to create a hard
+ ** link in the same queue directory as the old df file.
+ ** The qf file will refer to the new df file using a 'd' record.
+ */
+
+ new->e_dfqgrp = old->e_dfqgrp;
+ new->e_dfqdir = old->e_dfqdir;
+ (void) sm_strlcpy(npath, queuename(new, DATAFL_LETTER), sizeof npath);
+ if (link(opath, npath) == 0)
+ {
+ new->e_flags |= EF_HAS_DF;
+ SYNC_DIR(npath, true);
+ return true;
+ }
+
+ error:
+ if (LogLevel > 0)
+ sm_syslog(LOG_ERR, old->e_id,
+ "dup_df: can't link %s to %s, error=%s, envelope splitting failed",
+ opath, npath, sm_errstring(errno));
+ return false;
+}
+
+/*
+** SPLIT_ENV -- Allocate a new envelope based on a given envelope.
+**
+** Parameters:
+** e -- envelope.
+** sendqueue -- sendqueue for new envelope.
+** qgrp -- index of queue group.
+** qdir -- queue directory.
+**
+** Results:
+** new envelope.
+**
+*/
+
+static ENVELOPE *split_env __P((ENVELOPE *, ADDRESS *, int, int));
+
+static ENVELOPE *
+split_env(e, sendqueue, qgrp, qdir)
+ ENVELOPE *e;
+ ADDRESS *sendqueue;
+ int qgrp;
+ int qdir;
+{
+ ENVELOPE *ee;
+
+ ee = (ENVELOPE *) sm_rpool_malloc_x(e->e_rpool, sizeof *ee);
+ STRUCTCOPY(*e, *ee);
+ ee->e_message = NULL; /* XXX use original message? */
+ ee->e_id = NULL;
+ assign_queueid(ee);
+ ee->e_sendqueue = sendqueue;
+ ee->e_flags &= ~(EF_INQUEUE|EF_CLRQUEUE|EF_FATALERRS
+ |EF_SENDRECEIPT|EF_RET_PARAM|EF_HAS_DF);
+ ee->e_flags |= EF_NORECEIPT; /* XXX really? */
+ ee->e_from.q_state = QS_SENDER;
+ ee->e_dfp = NULL;
+ ee->e_lockfp = NULL;
+ if (e->e_xfp != NULL)
+ ee->e_xfp = sm_io_dup(e->e_xfp);
+
+ /* failed to dup e->e_xfp, start a new transcript */
+ if (ee->e_xfp == NULL)
+ openxscript(ee);
+
+ ee->e_qgrp = ee->e_dfqgrp = qgrp;
+ ee->e_qdir = ee->e_dfqdir = qdir;
+ ee->e_errormode = EM_MAIL;
+ ee->e_statmsg = NULL;
+ if (e->e_quarmsg != NULL)
+ ee->e_quarmsg = sm_rpool_strdup_x(ee->e_rpool,
+ e->e_quarmsg);
+
+ /*
+ ** XXX Not sure if this copying is necessary.
+ ** sendall() does this copying, but I (dm) don't know if that is
+ ** because of the storage management discipline we were using
+ ** before rpools were introduced, or if it is because these lists
+ ** can be modified later.
+ */
+
+ ee->e_header = copyheader(e->e_header, ee->e_rpool);
+ ee->e_errorqueue = copyqueue(e->e_errorqueue, ee->e_rpool);
+
+ return ee;
+}
+
+/* return values from split functions, check also below! */
+#define SM_SPLIT_FAIL (0)
+#define SM_SPLIT_NONE (1)
+#define SM_SPLIT_NEW(n) (1 + (n))
+
+/*
+** SPLIT_ACROSS_QUEUE_GROUPS
+**
+** This function splits an envelope across multiple queue groups
+** based on the queue group of each recipient.
+**
+** Parameters:
+** e -- envelope.
+**
+** Results:
+** SM_SPLIT_FAIL on failure
+** SM_SPLIT_NONE if no splitting occurred,
+** or 1 + the number of additional envelopes created.
+**
+** Side Effects:
+** On success, e->e_sibling points to a list of zero or more
+** additional envelopes, and the associated data files exist
+** on disk. But the queue files are not created.
+**
+** On failure, e->e_sibling is not changed.
+** The order of recipients in e->e_sendqueue is permuted.
+** Abandoned data files for additional envelopes that failed
+** to be created may exist on disk.
+*/
+
+static int q_qgrp_compare __P((const void *, const void *));
+static int e_filesys_compare __P((const void *, const void *));
+
+static int
+q_qgrp_compare(p1, p2)
+ const void *p1;
+ const void *p2;
+{
+ ADDRESS **pq1 = (ADDRESS **) p1;
+ ADDRESS **pq2 = (ADDRESS **) p2;
+
+ return (*pq1)->q_qgrp - (*pq2)->q_qgrp;
+}
+
+static int
+e_filesys_compare(p1, p2)
+ const void *p1;
+ const void *p2;
+{
+ ENVELOPE **pe1 = (ENVELOPE **) p1;
+ ENVELOPE **pe2 = (ENVELOPE **) p2;
+ int fs1, fs2;
+
+ fs1 = Queue[(*pe1)->e_qgrp]->qg_qpaths[(*pe1)->e_qdir].qp_fsysidx;
+ fs2 = Queue[(*pe2)->e_qgrp]->qg_qpaths[(*pe2)->e_qdir].qp_fsysidx;
+ if (FILE_SYS_DEV(fs1) < FILE_SYS_DEV(fs2))
+ return -1;
+ if (FILE_SYS_DEV(fs1) > FILE_SYS_DEV(fs2))
+ return 1;
+ return 0;
+}
+
+static int
+split_across_queue_groups(e)
+ ENVELOPE *e;
+{
+ int naddrs, nsplits, i;
+ bool changed;
+ char **pvp;
+ ADDRESS *q, **addrs;
+ ENVELOPE *ee, *es;
+ ENVELOPE *splits[MAXQUEUEGROUPS];
+ char pvpbuf[PSBUFSIZE];
+
+ SM_REQUIRE(ISVALIDQGRP(e->e_qgrp));
+
+ /* Count addresses and assign queue groups. */
+ naddrs = 0;
+ changed = false;
+ for (q = e->e_sendqueue; q != NULL; q = q->q_next)
+ {
+ if (QS_IS_DEAD(q->q_state))
+ continue;
+ ++naddrs;
+
+ /* bad addresses and those already sent stay put */
+ if (QS_IS_BADADDR(q->q_state) ||
+ QS_IS_SENT(q->q_state))
+ q->q_qgrp = e->e_qgrp;
+ else if (!ISVALIDQGRP(q->q_qgrp))
+ {
+ /* call ruleset which should return a queue group */
+ i = rscap(RS_QUEUEGROUP, q->q_user, NULL, e, &pvp,
+ pvpbuf, sizeof(pvpbuf));
+ if (i == EX_OK &&
+ pvp != NULL && pvp[0] != NULL &&
+ (pvp[0][0] & 0377) == CANONNET &&
+ pvp[1] != NULL && pvp[1][0] != '\0')
+ {
+ i = name2qid(pvp[1]);
+ if (ISVALIDQGRP(i))
+ {
+ q->q_qgrp = i;
+ changed = true;
+ if (tTd(20, 4))
+ sm_syslog(LOG_INFO, NOQID,
+ "queue group name %s -> %d",
+ pvp[1], i);
+ continue;
+ }
+ else if (LogLevel > 10)
+ sm_syslog(LOG_INFO, NOQID,
+ "can't find queue group name %s, selection ignored",
+ pvp[1]);
+ }
+ if (q->q_mailer != NULL &&
+ ISVALIDQGRP(q->q_mailer->m_qgrp))
+ {
+ changed = true;
+ q->q_qgrp = q->q_mailer->m_qgrp;
+ }
+ else if (ISVALIDQGRP(e->e_qgrp))
+ q->q_qgrp = e->e_qgrp;
+ else
+ q->q_qgrp = 0;
+ }
+ }
+
+ /* only one address? nothing to split. */
+ if (naddrs <= 1 && !changed)
+ return SM_SPLIT_NONE;
+
+ /* sort the addresses by queue group */
+ addrs = sm_rpool_malloc_x(e->e_rpool, naddrs * sizeof(ADDRESS *));
+ for (i = 0, q = e->e_sendqueue; q != NULL; q = q->q_next)
+ {
+ if (QS_IS_DEAD(q->q_state))
+ continue;
+ addrs[i++] = q;
+ }
+ qsort(addrs, naddrs, sizeof(ADDRESS *), q_qgrp_compare);
+
+ /* split into multiple envelopes, by queue group */
+ nsplits = 0;
+ es = NULL;
+ e->e_sendqueue = NULL;
+ for (i = 0; i < naddrs; ++i)
+ {
+ if (i == naddrs - 1 || addrs[i]->q_qgrp != addrs[i + 1]->q_qgrp)
+ addrs[i]->q_next = NULL;
+ else
+ addrs[i]->q_next = addrs[i + 1];
+
+ /* same queue group as original envelope? */
+ if (addrs[i]->q_qgrp == e->e_qgrp)
+ {
+ if (e->e_sendqueue == NULL)
+ e->e_sendqueue = addrs[i];
+ continue;
+ }
+
+ /* different queue group than original envelope */
+ if (es == NULL || addrs[i]->q_qgrp != es->e_qgrp)
+ {
+ ee = split_env(e, addrs[i], addrs[i]->q_qgrp, NOQDIR);
+ es = ee;
+ splits[nsplits++] = ee;
+ }
+ }
+
+ /* no splits? return right now. */
+ if (nsplits <= 0)
+ return SM_SPLIT_NONE;
+
+ /* assign a queue directory to each additional envelope */
+ for (i = 0; i < nsplits; ++i)
+ {
+ es = splits[i];
+#if 0
+ es->e_qdir = pickqdir(Queue[es->e_qgrp], es->e_msgsize, es);
+#endif /* 0 */
+ if (!setnewqueue(es))
+ goto failure;
+ }
+
+ /* sort the additional envelopes by queue file system */
+ qsort(splits, nsplits, sizeof(ENVELOPE *), e_filesys_compare);
+
+ /* create data files for each additional envelope */
+ if (!dup_df(e, splits[0]))
+ {
+ i = 0;
+ goto failure;
+ }
+ for (i = 1; i < nsplits; ++i)
+ {
+ /* copy or link to the previous data file */
+ if (!dup_df(splits[i - 1], splits[i]))
+ goto failure;
+ }
+
+ /* success: prepend the new envelopes to the e->e_sibling list */
+ for (i = 0; i < nsplits; ++i)
+ {
+ es = splits[i];
+ es->e_sibling = e->e_sibling;
+ e->e_sibling = es;
+ }
+ return SM_SPLIT_NEW(nsplits);
+
+ /* failure: clean up */
+ failure:
+ if (i > 0)
+ {
+ int j;
+
+ for (j = 0; j < i; j++)
+ (void) unlink(queuename(splits[j], DATAFL_LETTER));
+ }
+ e->e_sendqueue = addrs[0];
+ for (i = 0; i < naddrs - 1; ++i)
+ addrs[i]->q_next = addrs[i + 1];
+ addrs[naddrs - 1]->q_next = NULL;
+ return SM_SPLIT_FAIL;
+}
+
+/*
+** SPLIT_WITHIN_QUEUE
+**
+** Split an envelope with multiple recipients into several
+** envelopes within the same queue directory, if the number of
+** recipients exceeds the limit for the queue group.
+**
+** Parameters:
+** e -- envelope.
+**
+** Results:
+** SM_SPLIT_FAIL on failure
+** SM_SPLIT_NONE if no splitting occurred,
+** or 1 + the number of additional envelopes created.
+*/
+
+#define SPLIT_LOG_LEVEL 8
+
+static int split_within_queue __P((ENVELOPE *));
+
+static int
+split_within_queue(e)
+ ENVELOPE *e;
+{
+ int maxrcpt, nrcpt, ndead, nsplit, i;
+ int j, l;
+ char *lsplits;
+ ADDRESS *q, **addrs;
+ ENVELOPE *ee, *firstsibling;
+
+ if (!ISVALIDQGRP(e->e_qgrp) || bitset(EF_SPLIT, e->e_flags))
+ return SM_SPLIT_NONE;
+
+ /* don't bother if there is no recipient limit */
+ maxrcpt = Queue[e->e_qgrp]->qg_maxrcpt;
+ if (maxrcpt <= 0)
+ return SM_SPLIT_NONE;
+
+ /* count recipients */
+ nrcpt = 0;
+ for (q = e->e_sendqueue; q != NULL; q = q->q_next)
+ {
+ if (QS_IS_DEAD(q->q_state))
+ continue;
+ ++nrcpt;
+ }
+ if (nrcpt <= maxrcpt)
+ return SM_SPLIT_NONE;
+
+ /*
+ ** Preserve the recipient list
+ ** so that we can restore it in case of error.
+ ** (But we discard dead addresses.)
+ */
+
+ addrs = sm_rpool_malloc_x(e->e_rpool, nrcpt * sizeof(ADDRESS *));
+ for (i = 0, q = e->e_sendqueue; q != NULL; q = q->q_next)
+ {
+ if (QS_IS_DEAD(q->q_state))
+ continue;
+ addrs[i++] = q;
+ }
+
+ /*
+ ** Partition the recipient list so that bad and sent addresses
+ ** come first. These will go with the original envelope, and
+ ** do not count towards the maxrcpt limit.
+ ** addrs[] does not contain QS_IS_DEAD() addresses.
+ */
+
+ ndead = 0;
+ for (i = 0; i < nrcpt; ++i)
+ {
+ if (QS_IS_BADADDR(addrs[i]->q_state) ||
+ QS_IS_SENT(addrs[i]->q_state) ||
+ QS_IS_DEAD(addrs[i]->q_state)) /* for paranoia's sake */
+ {
+ if (i > ndead)
+ {
+ ADDRESS *tmp = addrs[i];
+
+ addrs[i] = addrs[ndead];
+ addrs[ndead] = tmp;
+ }
+ ++ndead;
+ }
+ }
+
+ /* Check if no splitting required. */
+ if (nrcpt - ndead <= maxrcpt)
+ return SM_SPLIT_NONE;
+
+ /* fix links */
+ for (i = 0; i < nrcpt - 1; ++i)
+ addrs[i]->q_next = addrs[i + 1];
+ addrs[nrcpt - 1]->q_next = NULL;
+ e->e_sendqueue = addrs[0];
+
+ /* prepare buffer for logging */
+ if (LogLevel > SPLIT_LOG_LEVEL)
+ {
+ l = MAXLINE;
+ lsplits = sm_malloc(l);
+ if (lsplits != NULL)
+ *lsplits = '\0';
+ j = 0;
+ }
+ else
+ {
+ /* get rid of stupid compiler warnings */
+ lsplits = NULL;
+ j = l = 0;
+ }
+
+ /* split the envelope */
+ firstsibling = e->e_sibling;
+ i = maxrcpt + ndead;
+ nsplit = 0;
+ for (;;)
+ {
+ addrs[i - 1]->q_next = NULL;
+ ee = split_env(e, addrs[i], e->e_qgrp, e->e_qdir);
+ if (!dup_df(e, ee))
+ {
+
+ ee = firstsibling;
+ while (ee != NULL)
+ {
+ (void) unlink(queuename(ee, DATAFL_LETTER));
+ ee = ee->e_sibling;
+ }
+
+ /* Error. Restore e's sibling & recipient lists. */
+ e->e_sibling = firstsibling;
+ for (i = 0; i < nrcpt - 1; ++i)
+ addrs[i]->q_next = addrs[i + 1];
+ if (lsplits != NULL)
+ sm_free(lsplits);
+ return SM_SPLIT_FAIL;
+ }
+
+ /* prepend the new envelope to e->e_sibling */
+ ee->e_sibling = e->e_sibling;
+ e->e_sibling = ee;
+ ++nsplit;
+ if (LogLevel > SPLIT_LOG_LEVEL && lsplits != NULL)
+ {
+ if (j >= l - strlen(ee->e_id) - 3)
+ {
+ char *p;
+
+ l += MAXLINE;
+ p = sm_realloc(lsplits, l);
+ if (p == NULL)
+ {
+ /* let's try to get this done */
+ sm_free(lsplits);
+ lsplits = NULL;
+ }
+ else
+ lsplits = p;
+ }
+ if (lsplits != NULL)
+ {
+ if (j == 0)
+ j += sm_strlcat(lsplits + j,
+ ee->e_id,
+ l - j);
+ else
+ j += sm_strlcat2(lsplits + j,
+ "; ",
+ ee->e_id,
+ l - j);
+ SM_ASSERT(j < l);
+ }
+ }
+ if (nrcpt - i <= maxrcpt)
+ break;
+ i += maxrcpt;
+ }
+ if (LogLevel > SPLIT_LOG_LEVEL && lsplits != NULL)
+ {
+ if (nsplit > 0)
+ {
+ sm_syslog(LOG_NOTICE, e->e_id,
+ "split: maxrcpts=%d, rcpts=%d, count=%d, id%s=%s",
+ maxrcpt, nrcpt - ndead, nsplit,
+ nsplit > 1 ? "s" : "", lsplits);
+ }
+ sm_free(lsplits);
+ }
+ return SM_SPLIT_NEW(nsplit);
+}
+/*
+** SPLIT_BY_RECIPIENT
+**
+** Split an envelope with multiple recipients into multiple
+** envelopes as required by the sendmail configuration.
+**
+** Parameters:
+** e -- envelope.
+**
+** Results:
+** Returns true on success, false on failure.
+**
+** Side Effects:
+** see split_across_queue_groups(), split_within_queue(e)
+*/
+
+bool
+split_by_recipient(e)
+ ENVELOPE *e;
+{
+ int split, n, i, j, l;
+ char *lsplits;
+ ENVELOPE *ee, *next, *firstsibling;
+
+ if (OpMode == SM_VERIFY || !ISVALIDQGRP(e->e_qgrp) ||
+ bitset(EF_SPLIT, e->e_flags))
+ return true;
+ n = split_across_queue_groups(e);
+ if (n == SM_SPLIT_FAIL)
+ return false;
+ firstsibling = ee = e->e_sibling;
+ if (n > 1 && LogLevel > SPLIT_LOG_LEVEL)
+ {
+ l = MAXLINE;
+ lsplits = sm_malloc(l);
+ if (lsplits != NULL)
+ *lsplits = '\0';
+ j = 0;
+ }
+ else
+ {
+ /* get rid of stupid compiler warnings */
+ lsplits = NULL;
+ j = l = 0;
+ }
+ for (i = 1; i < n; ++i)
+ {
+ next = ee->e_sibling;
+ if (split_within_queue(ee) == SM_SPLIT_FAIL)
+ {
+ e->e_sibling = firstsibling;
+ return false;
+ }
+ ee->e_flags |= EF_SPLIT;
+ if (LogLevel > SPLIT_LOG_LEVEL && lsplits != NULL)
+ {
+ if (j >= l - strlen(ee->e_id) - 3)
+ {
+ char *p;
+
+ l += MAXLINE;
+ p = sm_realloc(lsplits, l);
+ if (p == NULL)
+ {
+ /* let's try to get this done */
+ sm_free(lsplits);
+ lsplits = NULL;
+ }
+ else
+ lsplits = p;
+ }
+ if (lsplits != NULL)
+ {
+ if (j == 0)
+ j += sm_strlcat(lsplits + j,
+ ee->e_id, l - j);
+ else
+ j += sm_strlcat2(lsplits + j, "; ",
+ ee->e_id, l - j);
+ SM_ASSERT(j < l);
+ }
+ }
+ ee = next;
+ }
+ if (LogLevel > SPLIT_LOG_LEVEL && lsplits != NULL && n > 1)
+ {
+ sm_syslog(LOG_NOTICE, e->e_id, "split: count=%d, id%s=%s",
+ n - 1, n > 2 ? "s" : "", lsplits);
+ sm_free(lsplits);
+ }
+ split = split_within_queue(e) != SM_SPLIT_FAIL;
+ if (split)
+ e->e_flags |= EF_SPLIT;
+ return split;
+}
+
+/*
+** QUARANTINE_QUEUE_ITEM -- {un,}quarantine a single envelope
+**
+** Add/remove quarantine reason and requeue appropriately.
+**
+** Parameters:
+** qgrp -- queue group for the item
+** qdir -- queue directory in the given queue group
+** e -- envelope information for the item
+** reason -- quarantine reason, NULL means unquarantine.
+**
+** Results:
+** true if item changed, false otherwise
+**
+** Side Effects:
+** Changes quarantine tag in queue file and renames it.
+*/
+
+static bool
+quarantine_queue_item(qgrp, qdir, e, reason)
+ int qgrp;
+ int qdir;
+ ENVELOPE *e;
+ char *reason;
+{
+ bool dirty = false;
+ bool failing = false;
+ bool foundq = false;
+ bool finished = false;
+ int fd;
+ int flags;
+ int oldtype;
+ int newtype;
+ int save_errno;
+ MODE_T oldumask = 0;
+ SM_FILE_T *oldqfp, *tempqfp;
+ char *bp;
+ char oldqf[MAXPATHLEN];
+ char tempqf[MAXPATHLEN];
+ char newqf[MAXPATHLEN];
+ char buf[MAXLINE];
+
+ oldtype = queue_letter(e, ANYQFL_LETTER);
+ (void) sm_strlcpy(oldqf, queuename(e, ANYQFL_LETTER), sizeof oldqf);
+ (void) sm_strlcpy(tempqf, queuename(e, NEWQFL_LETTER), sizeof tempqf);
+
+ /*
+ ** Instead of duplicating all the open
+ ** and lock code here, tell readqf() to
+ ** do that work and return the open
+ ** file pointer in e_lockfp. Note that
+ ** we must release the locks properly when
+ ** we are done.
+ */
+
+ if (!readqf(e, true))
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Skipping %s\n", qid_printname(e));
+ return false;
+ }
+ oldqfp = e->e_lockfp;
+
+ /* open the new queue file */
+ flags = O_CREAT|O_WRONLY|O_EXCL;
+ if (bitset(S_IWGRP, QueueFileMode))
+ oldumask = umask(002);
+ fd = open(tempqf, flags, QueueFileMode);
+ if (bitset(S_IWGRP, QueueFileMode))
+ (void) umask(oldumask);
+ RELEASE_QUEUE;
+
+ if (fd < 0)
+ {
+ save_errno = errno;
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Skipping %s: Could not open %s: %s\n",
+ qid_printname(e), tempqf,
+ sm_errstring(save_errno));
+ (void) sm_io_close(oldqfp, SM_TIME_DEFAULT);
+ return false;
+ }
+ if (!lockfile(fd, tempqf, NULL, LOCK_EX|LOCK_NB))
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Skipping %s: Could not lock %s\n",
+ qid_printname(e), tempqf);
+ (void) close(fd);
+ (void) sm_io_close(oldqfp, SM_TIME_DEFAULT);
+ return false;
+ }
+
+ tempqfp = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT, (void *) &fd,
+ SM_IO_WRONLY_B, NULL);
+ if (tempqfp == NULL)
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Skipping %s: Could not lock %s\n",
+ qid_printname(e), tempqf);
+ (void) close(fd);
+ (void) sm_io_close(oldqfp, SM_TIME_DEFAULT);
+ return false;
+ }
+
+ /* Copy the data over, changing the quarantine reason */
+ while ((bp = fgetfolded(buf, sizeof buf, oldqfp)) != NULL)
+ {
+ if (tTd(40, 4))
+ sm_dprintf("+++++ %s\n", bp);
+ switch (bp[0])
+ {
+ case 'q': /* quarantine reason */
+ foundq = true;
+ if (reason == NULL)
+ {
+ if (Verbose)
+ {
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT,
+ "%s: Removed quarantine of \"%s\"\n",
+ e->e_id, &bp[1]);
+ }
+ sm_syslog(LOG_INFO, e->e_id, "unquarantine");
+ dirty = true;
+ continue;
+ }
+ else if (strcmp(reason, &bp[1]) == 0)
+ {
+ if (Verbose)
+ {
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT,
+ "%s: Already quarantined with \"%s\"\n",
+ e->e_id, reason);
+ }
+ (void) sm_io_fprintf(tempqfp, SM_TIME_DEFAULT,
+ "q%s\n", reason);
+ }
+ else
+ {
+ if (Verbose)
+ {
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT,
+ "%s: Quarantine changed from \"%s\" to \"%s\"\n",
+ e->e_id, &bp[1],
+ reason);
+ }
+ (void) sm_io_fprintf(tempqfp, SM_TIME_DEFAULT,
+ "q%s\n", reason);
+ sm_syslog(LOG_INFO, e->e_id, "quarantine=%s",
+ reason);
+ dirty = true;
+ }
+ break;
+
+ case 'S':
+ /*
+ ** If we are quarantining an unquarantined item,
+ ** need to put in a new 'q' line before it's
+ ** too late.
+ */
+
+ if (!foundq && reason != NULL)
+ {
+ if (Verbose)
+ {
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT,
+ "%s: Quarantined with \"%s\"\n",
+ e->e_id, reason);
+ }
+ (void) sm_io_fprintf(tempqfp, SM_TIME_DEFAULT,
+ "q%s\n", reason);
+ sm_syslog(LOG_INFO, e->e_id, "quarantine=%s",
+ reason);
+ foundq = true;
+ dirty = true;
+ }
+
+ /* Copy the line to the new file */
+ (void) sm_io_fprintf(tempqfp, SM_TIME_DEFAULT,
+ "%s\n", bp);
+ break;
+
+ case '.':
+ finished = true;
+ /* FALLTHROUGH */
+
+ default:
+ /* Copy the line to the new file */
+ (void) sm_io_fprintf(tempqfp, SM_TIME_DEFAULT,
+ "%s\n", bp);
+ break;
+ }
+ }
+
+ /* Make sure we read the whole old file */
+ errno = sm_io_error(tempqfp);
+ if (errno != 0 && errno != SM_IO_EOF)
+ {
+ save_errno = errno;
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Skipping %s: Error reading %s: %s\n",
+ qid_printname(e), oldqf,
+ sm_errstring(save_errno));
+ failing = true;
+ }
+
+ if (!failing && !finished)
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Skipping %s: Incomplete file: %s\n",
+ qid_printname(e), oldqf);
+ failing = true;
+ }
+
+ /* Check if we actually changed anything or we can just bail now */
+ if (!dirty)
+ {
+ /* pretend we failed, even though we technically didn't */
+ failing = true;
+ }
+
+ /* Make sure we wrote things out safely */
+ if (!failing &&
+ (sm_io_flush(tempqfp, SM_TIME_DEFAULT) != 0 ||
+ ((SuperSafe == SAFE_REALLY ||
+ SuperSafe == SAFE_REALLY_POSTMILTER ||
+ SuperSafe == SAFE_INTERACTIVE) &&
+ fsync(sm_io_getinfo(tempqfp, SM_IO_WHAT_FD, NULL)) < 0) ||
+ ((errno = sm_io_error(tempqfp)) != 0)))
+ {
+ save_errno = errno;
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Skipping %s: Error writing %s: %s\n",
+ qid_printname(e), tempqf,
+ sm_errstring(save_errno));
+ failing = true;
+ }
+
+
+ /* Figure out the new filename */
+ newtype = (reason == NULL ? NORMQF_LETTER : QUARQF_LETTER);
+ if (oldtype == newtype)
+ {
+ /* going to rename tempqf to oldqf */
+ (void) sm_strlcpy(newqf, oldqf, sizeof newqf);
+ }
+ else
+ {
+ /* going to rename tempqf to new name based on newtype */
+ (void) sm_strlcpy(newqf, queuename(e, newtype), sizeof newqf);
+ }
+
+ save_errno = 0;
+
+ /* rename tempqf to newqf */
+ if (!failing &&
+ rename(tempqf, newqf) < 0)
+ save_errno = (errno == 0) ? EINVAL : errno;
+
+ /* Check rename() success */
+ if (!failing && save_errno != 0)
+ {
+ sm_syslog(LOG_DEBUG, e->e_id,
+ "quarantine_queue_item: rename(%s, %s): %s",
+ tempqf, newqf, sm_errstring(save_errno));
+
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Error renaming %s to %s: %s\n",
+ tempqf, newqf,
+ sm_errstring(save_errno));
+ if (oldtype == newtype)
+ {
+ /*
+ ** Bail here since we don't know the state of
+ ** the filesystem and may need to keep tempqf
+ ** for the user to rescue us.
+ */
+
+ RELEASE_QUEUE;
+ errno = save_errno;
+ syserr("!452 Error renaming control file %s", tempqf);
+ /* NOTREACHED */
+ }
+ else
+ {
+ /* remove new file (if rename() half completed) */
+ if (xunlink(newqf) < 0)
+ {
+ save_errno = errno;
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Error removing %s: %s\n",
+ newqf,
+ sm_errstring(save_errno));
+ }
+
+ /* tempqf removed below */
+ failing = true;
+ }
+
+ }
+
+ /* If changing file types, need to remove old type */
+ if (!failing && oldtype != newtype)
+ {
+ if (xunlink(oldqf) < 0)
+ {
+ save_errno = errno;
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Error removing %s: %s\n",
+ oldqf, sm_errstring(save_errno));
+ }
+ }
+
+ /* see if anything above failed */
+ if (failing)
+ {
+ /* Something failed: remove new file, old file still there */
+ (void) xunlink(tempqf);
+ }
+
+ /*
+ ** fsync() after file operations to make sure metadata is
+ ** written to disk on filesystems in which renames are
+ ** not guaranteed. It's ok if they fail, mail won't be lost.
+ */
+
+ if (SuperSafe != SAFE_NO)
+ {
+ /* for soft-updates */
+ (void) fsync(sm_io_getinfo(tempqfp,
+ SM_IO_WHAT_FD, NULL));
+
+ if (!failing)
+ {
+ /* for soft-updates */
+ (void) fsync(sm_io_getinfo(oldqfp,
+ SM_IO_WHAT_FD, NULL));
+ }
+
+ /* for other odd filesystems */
+ SYNC_DIR(tempqf, false);
+ }
+
+ /* Close up shop */
+ RELEASE_QUEUE;
+ if (tempqfp != NULL)
+ (void) sm_io_close(tempqfp, SM_TIME_DEFAULT);
+ if (oldqfp != NULL)
+ (void) sm_io_close(oldqfp, SM_TIME_DEFAULT);
+
+ /* All went well */
+ return !failing;
+}
+
+/*
+** QUARANTINE_QUEUE -- {un,}quarantine matching items in the queue
+**
+** Read all matching queue items, add/remove quarantine
+** reason, and requeue appropriately.
+**
+** Parameters:
+** reason -- quarantine reason, "." means unquarantine.
+** qgrplimit -- limit to single queue group unless NOQGRP
+**
+** Results:
+** none.
+**
+** Side Effects:
+** Lots of changes to the queue.
+*/
+
+void
+quarantine_queue(reason, qgrplimit)
+ char *reason;
+ int qgrplimit;
+{
+ int changed = 0;
+ int qgrp;
+
+ /* Convert internal representation of unquarantine */
+ if (reason != NULL && reason[0] == '.' && reason[1] == '\0')
+ reason = NULL;
+
+ if (reason != NULL)
+ {
+ /* clean it */
+ reason = newstr(denlstring(reason, true, true));
+ }
+
+ for (qgrp = 0; qgrp < NumQueue && Queue[qgrp] != NULL; qgrp++)
+ {
+ int qdir;
+
+ if (qgrplimit != NOQGRP && qgrplimit != qgrp)
+ continue;
+
+ for (qdir = 0; qdir < Queue[qgrp]->qg_numqueues; qdir++)
+ {
+ int i;
+ int nrequests;
+
+ if (StopRequest)
+ stop_sendmail();
+
+ nrequests = gatherq(qgrp, qdir, true, NULL, NULL);
+
+ /* first see if there is anything */
+ if (nrequests <= 0)
+ {
+ if (Verbose)
+ {
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT, "%s: no matches\n",
+ qid_printqueue(qgrp, qdir));
+ }
+ continue;
+ }
+
+ if (Verbose)
+ {
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT, "Processing %s:\n",
+ qid_printqueue(qgrp, qdir));
+ }
+
+ for (i = 0; i < WorkListCount; i++)
+ {
+ ENVELOPE e;
+
+ if (StopRequest)
+ stop_sendmail();
+
+ /* setup envelope */
+ clearenvelope(&e, true, sm_rpool_new_x(NULL));
+ e.e_id = WorkList[i].w_name + 2;
+ e.e_qgrp = qgrp;
+ e.e_qdir = qdir;
+
+ if (tTd(70, 101))
+ {
+ sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Would do %s\n", e.e_id);
+ changed++;
+ }
+ else if (quarantine_queue_item(qgrp, qdir,
+ &e, reason))
+ changed++;
+
+ /* clean up */
+ sm_rpool_free(e.e_rpool);
+ e.e_rpool = NULL;
+ }
+ if (WorkList != NULL)
+ sm_free(WorkList); /* XXX */
+ WorkList = NULL;
+ WorkListSize = 0;
+ WorkListCount = 0;
+ }
+ }
+ if (Verbose)
+ {
+ if (changed == 0)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "No changes\n");
+ else
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "%d change%s\n",
+ changed,
+ changed == 1 ? "" : "s");
+ }
+}
diff --git a/usr/src/cmd/sendmail/src/ratectrl.c b/usr/src/cmd/sendmail/src/ratectrl.c
new file mode 100644
index 0000000000..dce62baba7
--- /dev/null
+++ b/usr/src/cmd/sendmail/src/ratectrl.c
@@ -0,0 +1,536 @@
+/*
+ * Copyright (c) 2003 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ * Contributed by Jose Marcio Martins da Cruz - Ecole des Mines de Paris
+ * Jose-Marcio.Martins@ensmp.fr
+ */
+
+/* a part of this code is based on inetd.c for which this copyright applies: */
+/*
+ * Copyright (c) 1983, 1991, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sendmail.h>
+SM_RCSID("@(#)$Id: ratectrl.c,v 8.9 2004/07/07 21:23:57 ca Exp $")
+
+/*
+** stuff included - given some warnings (inet_ntoa)
+** - surely not everything is needed
+*/
+
+#if NETINET || NETINET6
+# include <arpa/inet.h>
+#endif /* NETINET || NETINET6 */
+
+#include <sys/time.h>
+
+#ifndef HASH_ALG
+# define HASH_ALG 2
+#endif /* HASH_ALG */
+
+#ifndef RATECTL_DEBUG
+# define RATECTL_DEBUG 0
+#endif /* RATECTL_DEBUG */
+
+/* forward declarations */
+static int client_rate __P((time_t, SOCKADDR *, bool));
+static int total_rate __P((time_t, bool));
+#if 0
+static int sockaddrcmp __P((SOCKADDR *, SOCKADDR *));
+#endif /* 0 */
+
+/*
+** CONNECTION_RATE_CHECK - updates connection history data
+** and computes connection rate for the given host
+**
+** Parameters:
+** hostaddr -- ip address of smtp client
+** e -- envelope
+**
+** Returns:
+** true (always)
+**
+** Side Effects:
+** updates connection history
+**
+** Warnings:
+** For each connection, this call shall be
+** done only once with the value true for the
+** update parameter.
+** Typically, this call is done with the value
+** true by the father, and once again with
+** the value false by the children.
+**
+*/
+
+bool
+connection_rate_check(hostaddr, e)
+ SOCKADDR *hostaddr;
+ ENVELOPE *e;
+{
+ time_t now;
+ int totalrate, clientrate;
+ static int clientconn = 0;
+
+ now = time(NULL);
+#if RATECTL_DEBUG
+ sm_syslog(LOG_INFO, NOQID, "connection_rate_check entering...");
+#endif /* RATECTL_DEBUG */
+
+ /* update server connection rate */
+ totalrate = total_rate(now, e == NULL);
+#if RATECTL_DEBUG
+ sm_syslog(LOG_INFO, NOQID, "global connection rate: %d", globalRate);
+#endif /* RATECTL_DEBUG */
+
+ /* update client connection rate */
+ clientrate = client_rate(now, hostaddr, e == NULL);
+
+ if (e == NULL)
+ clientconn = count_open_connections(hostaddr);
+
+ if (e != NULL)
+ {
+ char s[16];
+
+ sm_snprintf(s, sizeof(s), "%d", clientrate);
+ macdefine(&e->e_macro, A_TEMP, macid("{client_rate}"), s);
+ sm_snprintf(s, sizeof(s), "%d", totalrate);
+ macdefine(&e->e_macro, A_TEMP, macid("{total_rate}"), s);
+ sm_snprintf(s, sizeof(s), "%d", clientconn);
+ macdefine(&e->e_macro, A_TEMP, macid("{client_connections}"),
+ s);
+ }
+ return true;
+}
+
+/*
+** Data declarations needed to evaluate connection rate
+*/
+
+static int CollTime = 60;
+
+/* this should be a power of 2, otherwise CPMHMASK doesn't work well */
+#ifndef CPMHSIZE
+# define CPMHSIZE 1024
+#endif /* CPMHSIZE */
+
+#define CPMHMASK (CPMHSIZE-1)
+
+#ifndef MAX_CT_STEPS
+# define MAX_CT_STEPS 10
+#endif /* MAX_CT_STEPS */
+
+/*
+** time granularity: 10s (that's one "tick")
+** will be initialised to ConnectionRateWindowSize/CHTSIZE
+** before being used the first time
+*/
+
+static int ChtGran = -1;
+
+#define CHTSIZE 6
+
+/* Number of connections for a certain "tick" */
+typedef struct CTime
+{
+ unsigned long ct_Ticks;
+ int ct_Count;
+}
+CTime_T;
+
+typedef struct CHash
+{
+#if NETINET6 && NETINET
+ union
+ {
+ struct in_addr c4_Addr;
+ struct in6_addr c6_Addr;
+ } cu_Addr;
+# define ch_Addr4 cu_Addr.c4_Addr
+# define ch_Addr6 cu_Addr.c6_Addr
+#else /* NETINET6 && NETINET */
+# if NETINET6
+ struct in6_addr ch_Addr;
+# define ch_Addr6 ch_Addr
+# else /* NETINET6 */
+ struct in_addr ch_Addr;
+# define ch_Addr4 ch_Addr
+# endif /* NETINET6 */
+#endif /* NETINET6 && NETINET */
+
+ int ch_Family;
+ time_t ch_LTime;
+ unsigned long ch_colls;
+
+ /* 6 buckets for ticks: 60s */
+ CTime_T ch_Times[CHTSIZE];
+}
+CHash_T;
+
+static CHash_T CHashAry[CPMHSIZE];
+static bool CHashAryOK = false;
+
+/*
+** CLIENT_RATE - Evaluate connection rate per smtp client
+**
+** Parameters:
+** now - current time in secs
+** saddr - client address
+** update - update data / check only
+**
+** Returns:
+** connection rate (connections / ConnectionRateWindowSize)
+**
+** Side effects:
+** update static global data
+**
+*/
+
+static int
+client_rate(now, saddr, update)
+ time_t now;
+ SOCKADDR *saddr;
+ bool update;
+{
+ unsigned int hv;
+ int i;
+ int cnt;
+ bool coll;
+ CHash_T *chBest = NULL;
+ unsigned int ticks;
+
+ cnt = 0;
+ hv = 0xABC3D20F;
+ if (ChtGran < 0)
+ ChtGran = ConnectionRateWindowSize / CHTSIZE;
+ if (ChtGran <= 0)
+ ChtGran = 10;
+
+ ticks = now / ChtGran;
+
+ if (!CHashAryOK)
+ {
+ memset(CHashAry, 0, sizeof (CHashAry));
+ CHashAryOK = true;
+ }
+
+ {
+ char *p;
+ int addrlen;
+#if HASH_ALG != 1
+ int c, d;
+#endif /* HASH_ALG != 1 */
+
+ switch (saddr->sa.sa_family)
+ {
+#if NETINET
+ case AF_INET:
+ p = (char *)&saddr->sin.sin_addr;
+ addrlen = sizeof(struct in_addr);
+ break;
+#endif /* NETINET */
+#if NETINET6
+ case AF_INET6:
+ p = (char *)&saddr->sin6.sin6_addr;
+ addrlen = sizeof(struct in6_addr);
+ break;
+#endif /* NETINET6 */
+ default:
+ /* should not happen */
+ return -1;
+ }
+
+ /* compute hash value */
+ for (i = 0; i < addrlen; ++i, ++p)
+#if HASH_ALG == 1
+ hv = (hv << 5) ^ (hv >> 23) ^ *p;
+ hv = (hv ^ (hv >> 16));
+#elif HASH_ALG == 2
+ {
+ d = *p;
+ c = d;
+ c ^= c<<6;
+ hv += (c<<11) ^ (c>>1);
+ hv ^= (d<<14) + (d<<7) + (d<<4) + d;
+ }
+#elif HASH_ALG == 3
+ {
+ hv = (hv << 4) + *p;
+ d = hv & 0xf0000000;
+ if (d != 0)
+ {
+ hv ^= (d >> 24);
+ hv ^= d;
+ }
+ }
+#else /* HASH_ALG == 1 */
+ hv = ((hv << 1) ^ (*p & 0377)) % cctx->cc_size;
+#endif /* HASH_ALG == 1 */
+ }
+
+ coll = true;
+ for (i = 0; i < MAX_CT_STEPS; ++i)
+ {
+ CHash_T *ch = &CHashAry[(hv + i) & CPMHMASK];
+
+#if NETINET
+ if (saddr->sa.sa_family == AF_INET &&
+ ch->ch_Family == AF_INET &&
+ (saddr->sin.sin_addr.s_addr == ch->ch_Addr4.s_addr ||
+ ch->ch_Addr4.s_addr == 0))
+ {
+ chBest = ch;
+ coll = false;
+ break;
+ }
+#endif /* NETINET */
+#if NETINET6
+ if (saddr->sa.sa_family == AF_INET6 &&
+ ch->ch_Family == AF_INET6 &&
+ (IN6_ARE_ADDR_EQUAL(&saddr->sin6.sin6_addr,
+ &ch->ch_Addr6) != 0 ||
+ IN6_IS_ADDR_UNSPECIFIED(&ch->ch_Addr6)))
+ {
+ chBest = ch;
+ coll = false;
+ break;
+ }
+#endif /* NETINET6 */
+ if (chBest == NULL || ch->ch_LTime == 0 ||
+ ch->ch_LTime < chBest->ch_LTime)
+ chBest = ch;
+ }
+
+ /* Let's update data... */
+ if (update)
+ {
+ if (coll && (now - chBest->ch_LTime < CollTime))
+ {
+ /*
+ ** increment the number of collisions last
+ ** CollTime for this client
+ */
+
+ chBest->ch_colls++;
+
+ /*
+ ** Maybe shall log if collision rate is too high...
+ ** and take measures to resize tables
+ ** if this is the case
+ */
+ }
+
+ /*
+ ** If it's not a match, then replace the data.
+ ** Note: this purges the history of a colliding entry,
+ ** which may cause "overruns", i.e., if two entries are
+ ** "cancelling" each other out, then they may exceed
+ ** the limits that are set. This might be mitigated a bit
+ ** by the above "best of 5" function however.
+ **
+ ** Alternative approach: just use the old data, which may
+ ** cause false positives however.
+ ** To activate this, change deactivate following memset call.
+ */
+
+ if (coll)
+ {
+#if NETINET
+ if (saddr->sa.sa_family == AF_INET)
+ {
+ chBest->ch_Family = AF_INET;
+ chBest->ch_Addr4 = saddr->sin.sin_addr;
+ }
+#endif /* NETINET */
+#if NETINET6
+ if (saddr->sa.sa_family == AF_INET6)
+ {
+ chBest->ch_Family = AF_INET6;
+ chBest->ch_Addr6 = saddr->sin6.sin6_addr;
+ }
+#endif /* NETINET6 */
+#if 1
+ memset(chBest->ch_Times, '\0',
+ sizeof (chBest->ch_Times));
+#endif /* 1 */
+ }
+
+ chBest->ch_LTime = now;
+ {
+ CTime_T *ct = &chBest->ch_Times[ticks % CHTSIZE];
+
+ if (ct->ct_Ticks != ticks)
+ {
+ ct->ct_Ticks = ticks;
+ ct->ct_Count = 0;
+ }
+ ++ct->ct_Count;
+ }
+ }
+
+ /* Now let's count connections on the window */
+ for (i = 0; i < CHTSIZE; ++i)
+ {
+ CTime_T *ct = &chBest->ch_Times[i];
+
+ if (ct->ct_Ticks <= ticks && ct->ct_Ticks >= ticks - CHTSIZE)
+ cnt += ct->ct_Count;
+ }
+
+#if RATECTL_DEBUG
+ sm_syslog(LOG_WARNING, NOQID,
+ "cln: cnt=(%d), CHTSIZE=(%d), ChtGran=(%d)",
+ cnt, CHTSIZE, ChtGran);
+#endif /* RATECTL_DEBUG */
+ return cnt;
+}
+
+/*
+** TOTAL_RATE - Evaluate global connection rate
+**
+** Parameters:
+** now - current time in secs
+** update - update data / check only
+**
+** Returns:
+** connection rate (connections / ConnectionRateWindowSize)
+*/
+
+static CTime_T srv_Times[CHTSIZE];
+static bool srv_Times_OK = false;
+
+static int
+total_rate(now, update)
+ time_t now;
+ bool update;
+{
+ int i;
+ int cnt = 0;
+ CTime_T *ct;
+ unsigned int ticks;
+
+ if (ChtGran < 0)
+ ChtGran = ConnectionRateWindowSize / CHTSIZE;
+ if (ChtGran == 0)
+ ChtGran = 10;
+ ticks = now / ChtGran;
+ if (!srv_Times_OK)
+ {
+ memset(srv_Times, 0, sizeof(srv_Times));
+ srv_Times_OK = true;
+ }
+
+ /* Let's update data */
+ if (update)
+ {
+ ct = &srv_Times[ticks % CHTSIZE];
+
+ if (ct->ct_Ticks != ticks)
+ {
+ ct->ct_Ticks = ticks;
+ ct->ct_Count = 0;
+ }
+ ++ct->ct_Count;
+ }
+
+ /* Let's count connections on the window */
+ for (i = 0; i < CHTSIZE; ++i)
+ {
+ ct = &srv_Times[i];
+
+ if (ct->ct_Ticks <= ticks && ct->ct_Ticks >= ticks - CHTSIZE)
+ cnt += ct->ct_Count;
+ }
+
+#if RATECTL_DEBUG
+ sm_syslog(LOG_WARNING, NOQID,
+ "srv: cnt=(%d), CHTSIZE=(%d), ChtGran=(%d)",
+ cnt, CHTSIZE, ChtGran);
+#endif /* RATECTL_DEBUG */
+
+ return cnt;
+}
+
+#if 0
+/*
+** SOCKADDRCMP - compare two SOCKADDR structures
+** this function may be used to compare SOCKADDR
+** structures when using bsearch and qsort functions
+** in the same way we do with strcmp
+**
+** Parameters:
+** a, b - addresses
+**
+** Returns:
+** 1 if a > b
+** -1 if a < b
+** 0 if a = b
+**
+** OBS: This call isn't used at the moment, it will
+** be used when code will be extended to work with IPV6
+*/
+
+static int
+sockaddrcmp(a, b)
+ SOCKADDR *a;
+ SOCKADDR *b;
+{
+ if (a->sa.sa_family > b->sa.sa_family)
+ return 1;
+ if (a->sa.sa_family < b->sa.sa_family)
+ return -1;
+
+ switch (a->sa.sa_family)
+ {
+ case AF_INET:
+ if (a->sin.sin_addr.s_addr > b->sin.sin_addr.s_addr)
+ return 1;
+ if (a->sin.sin_addr.s_addr < b->sin.sin_addr.s_addr)
+ return -1;
+ return 0;
+ break;
+
+ case AF_INET6:
+ /* TO BE DONE */
+ break;
+ }
+ return 0;
+}
+#endif /* 0 */
diff --git a/usr/src/cmd/sendmail/src/readcf.c b/usr/src/cmd/sendmail/src/readcf.c
new file mode 100644
index 0000000000..42362f1617
--- /dev/null
+++ b/usr/src/cmd/sendmail/src/readcf.c
@@ -0,0 +1,4460 @@
+/*
+ * Copyright (c) 1998-2004 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sendmail.h>
+
+SM_RCSID("@(#)$Id: readcf.c,v 8.642 2004/08/04 21:17:57 ca Exp $")
+
+#if NETINET || NETINET6
+# include <arpa/inet.h>
+#endif /* NETINET || NETINET6 */
+
+#define SECONDS
+#define MINUTES * 60
+#define HOUR * 3600
+#define HOURS HOUR
+
+static void fileclass __P((int, char *, char *, bool, bool, bool));
+static char **makeargv __P((char *));
+static void settimeout __P((char *, char *, bool));
+static void toomany __P((int, int));
+static char *extrquotstr __P((char *, char **, char *, bool *));
+static void parse_class_words __P((int, char *));
+
+/*
+** READCF -- read configuration file.
+**
+** This routine reads the configuration file and builds the internal
+** form.
+**
+** The file is formatted as a sequence of lines, each taken
+** atomically. The first character of each line describes how
+** the line is to be interpreted. The lines are:
+** Dxval Define macro x to have value val.
+** Cxword Put word into class x.
+** Fxfile [fmt] Read file for lines to put into
+** class x. Use scanf string 'fmt'
+** or "%s" if not present. Fmt should
+** only produce one string-valued result.
+** Hname: value Define header with field-name 'name'
+** and value as specified; this will be
+** macro expanded immediately before
+** use.
+** Sn Use rewriting set n.
+** Rlhs rhs Rewrite addresses that match lhs to
+** be rhs.
+** Mn arg=val... Define mailer. n is the internal name.
+** Args specify mailer parameters.
+** Oxvalue Set option x to value.
+** O option value Set option (long name) to value.
+** Pname=value Set precedence name to value.
+** Qn arg=val... Define queue groups. n is the internal name.
+** Args specify queue parameters.
+** Vversioncode[/vendorcode]
+** Version level/vendor name of
+** configuration syntax.
+** Kmapname mapclass arguments....
+** Define keyed lookup of a given class.
+** Arguments are class dependent.
+** Eenvar=value Set the environment value to the given value.
+**
+** Parameters:
+** cfname -- configuration file name.
+** safe -- true if this is the system config file;
+** false otherwise.
+** e -- the main envelope.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Builds several internal tables.
+*/
+
+void
+readcf(cfname, safe, e)
+ char *cfname;
+ bool safe;
+ register ENVELOPE *e;
+{
+ SM_FILE_T *cf;
+ int ruleset = -1;
+ char *q;
+ struct rewrite *rwp = NULL;
+ char *bp;
+ auto char *ep;
+ int nfuzzy;
+ char *file;
+ bool optional;
+ bool ok;
+ bool ismap;
+ int mid;
+ register char *p;
+ long sff = SFF_OPENASROOT;
+ struct stat statb;
+ char buf[MAXLINE];
+ char exbuf[MAXLINE];
+ char pvpbuf[MAXLINE + MAXATOM];
+ static char *null_list[1] = { NULL };
+ extern unsigned char TokTypeNoC[];
+
+ FileName = cfname;
+ LineNumber = 0;
+
+ if (DontLockReadFiles)
+ sff |= SFF_NOLOCK;
+ cf = safefopen(cfname, O_RDONLY, 0444, sff);
+ if (cf == NULL)
+ {
+ syserr("cannot open");
+ finis(false, true, EX_OSFILE);
+ }
+
+ if (fstat(sm_io_getinfo(cf, SM_IO_WHAT_FD, NULL), &statb) < 0)
+ {
+ syserr("cannot fstat");
+ finis(false, true, EX_OSFILE);
+ }
+
+ if (!S_ISREG(statb.st_mode))
+ {
+ syserr("not a plain file");
+ finis(false, true, EX_OSFILE);
+ }
+
+ if (OpMode != MD_TEST && bitset(S_IWGRP|S_IWOTH, statb.st_mode))
+ {
+ if (OpMode == MD_DAEMON || OpMode == MD_INITALIAS)
+ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ "%s: WARNING: dangerous write permissions\n",
+ FileName);
+ if (LogLevel > 0)
+ sm_syslog(LOG_CRIT, NOQID,
+ "%s: WARNING: dangerous write permissions",
+ FileName);
+ }
+
+#if XLA
+ xla_zero();
+#endif /* XLA */
+
+ while ((bp = fgetfolded(buf, sizeof buf, cf)) != NULL)
+ {
+ if (bp[0] == '#')
+ {
+ if (bp != buf)
+ sm_free(bp); /* XXX */
+ continue;
+ }
+
+ /* do macro expansion mappings */
+ translate_dollars(bp);
+
+ /* interpret this line */
+ errno = 0;
+ switch (bp[0])
+ {
+ case '\0':
+ case '#': /* comment */
+ break;
+
+ case 'R': /* rewriting rule */
+ if (ruleset < 0)
+ {
+ syserr("missing valid ruleset for \"%s\"", bp);
+ break;
+ }
+ for (p = &bp[1]; *p != '\0' && *p != '\t'; p++)
+ continue;
+
+ if (*p == '\0')
+ {
+ syserr("invalid rewrite line \"%s\" (tab expected)", bp);
+ break;
+ }
+
+ /* allocate space for the rule header */
+ if (rwp == NULL)
+ {
+ RewriteRules[ruleset] = rwp =
+ (struct rewrite *) xalloc(sizeof *rwp);
+ }
+ else
+ {
+ rwp->r_next = (struct rewrite *) xalloc(sizeof *rwp);
+ rwp = rwp->r_next;
+ }
+ rwp->r_next = NULL;
+
+ /* expand and save the LHS */
+ *p = '\0';
+ expand(&bp[1], exbuf, sizeof exbuf, e);
+ rwp->r_lhs = prescan(exbuf, '\t', pvpbuf,
+ sizeof pvpbuf, NULL,
+ ConfigLevel >= 9 ? TokTypeNoC : NULL,
+ true);
+ nfuzzy = 0;
+ if (rwp->r_lhs != NULL)
+ {
+ register char **ap;
+
+ rwp->r_lhs = copyplist(rwp->r_lhs, true, NULL);
+
+ /* count the number of fuzzy matches in LHS */
+ for (ap = rwp->r_lhs; *ap != NULL; ap++)
+ {
+ char *botch;
+
+ botch = NULL;
+ switch (**ap & 0377)
+ {
+ case MATCHZANY:
+ case MATCHANY:
+ case MATCHONE:
+ case MATCHCLASS:
+ case MATCHNCLASS:
+ nfuzzy++;
+ break;
+
+ case MATCHREPL:
+ botch = "$0-$9";
+ break;
+
+ case CANONUSER:
+ botch = "$:";
+ break;
+
+ case CALLSUBR:
+ botch = "$>";
+ break;
+
+ case CONDIF:
+ botch = "$?";
+ break;
+
+ case CONDFI:
+ botch = "$.";
+ break;
+
+ case HOSTBEGIN:
+ botch = "$[";
+ break;
+
+ case HOSTEND:
+ botch = "$]";
+ break;
+
+ case LOOKUPBEGIN:
+ botch = "$(";
+ break;
+
+ case LOOKUPEND:
+ botch = "$)";
+ break;
+ }
+ if (botch != NULL)
+ syserr("Inappropriate use of %s on LHS",
+ botch);
+ }
+ rwp->r_line = LineNumber;
+ }
+ else
+ {
+ syserr("R line: null LHS");
+ rwp->r_lhs = null_list;
+ }
+ if (nfuzzy > MAXMATCH)
+ {
+ syserr("R line: too many wildcards");
+ rwp->r_lhs = null_list;
+ }
+
+ /* expand and save the RHS */
+ while (*++p == '\t')
+ continue;
+ q = p;
+ while (*p != '\0' && *p != '\t')
+ p++;
+ *p = '\0';
+ expand(q, exbuf, sizeof exbuf, e);
+ rwp->r_rhs = prescan(exbuf, '\t', pvpbuf,
+ sizeof pvpbuf, NULL,
+ ConfigLevel >= 9 ? TokTypeNoC : NULL,
+ true);
+ if (rwp->r_rhs != NULL)
+ {
+ register char **ap;
+ int args, endtoken;
+#if _FFR_EXTRA_MAP_CHECK
+ int nexttoken;
+#endif /* _FFR_EXTRA_MAP_CHECK */
+ bool inmap;
+
+ rwp->r_rhs = copyplist(rwp->r_rhs, true, NULL);
+
+ /* check no out-of-bounds replacements */
+ nfuzzy += '0';
+ inmap = false;
+ args = 0;
+ endtoken = 0;
+ for (ap = rwp->r_rhs; *ap != NULL; ap++)
+ {
+ char *botch;
+
+ botch = NULL;
+ switch (**ap & 0377)
+ {
+ case MATCHREPL:
+ if ((*ap)[1] <= '0' || (*ap)[1] > nfuzzy)
+ {
+ syserr("replacement $%c out of bounds",
+ (*ap)[1]);
+ }
+ break;
+
+ case MATCHZANY:
+ botch = "$*";
+ break;
+
+ case MATCHANY:
+ botch = "$+";
+ break;
+
+ case MATCHONE:
+ botch = "$-";
+ break;
+
+ case MATCHCLASS:
+ botch = "$=";
+ break;
+
+ case MATCHNCLASS:
+ botch = "$~";
+ break;
+
+ case CANONHOST:
+ if (!inmap)
+ break;
+ if (++args >= MAX_MAP_ARGS)
+ syserr("too many arguments for map lookup");
+ break;
+
+ case HOSTBEGIN:
+ endtoken = HOSTEND;
+ /* FALLTHROUGH */
+ case LOOKUPBEGIN:
+ /* see above... */
+ if ((**ap & 0377) == LOOKUPBEGIN)
+ endtoken = LOOKUPEND;
+ if (inmap)
+ syserr("cannot nest map lookups");
+ inmap = true;
+ args = 0;
+#if _FFR_EXTRA_MAP_CHECK
+ if (*(ap + 1) == NULL)
+ {
+ syserr("syntax error in map lookup");
+ break;
+ }
+ nexttoken = **(ap + 1) & 0377;
+ if (nexttoken == CANONHOST ||
+ nexttoken == CANONUSER ||
+ nexttoken == endtoken)
+ {
+ syserr("missing map name for lookup");
+ break;
+ }
+ if (*(ap + 2) == NULL)
+ {
+ syserr("syntax error in map lookup");
+ break;
+ }
+ if ((**ap & 0377) == HOSTBEGIN)
+ break;
+ nexttoken = **(ap + 2) & 0377;
+ if (nexttoken == CANONHOST ||
+ nexttoken == CANONUSER ||
+ nexttoken == endtoken)
+ {
+ syserr("missing key name for lookup");
+ break;
+ }
+#endif /* _FFR_EXTRA_MAP_CHECK */
+ break;
+
+ case HOSTEND:
+ case LOOKUPEND:
+ if ((**ap & 0377) != endtoken)
+ break;
+ inmap = false;
+ endtoken = 0;
+ break;
+
+
+#if 0
+/*
+** This doesn't work yet as there are maps defined *after* the cf
+** is read such as host, user, and alias. So for now, it's removed.
+** When it comes back, the RELEASE_NOTES entry will be:
+** Emit warnings for unknown maps when reading the .cf file. Based on
+** patch from Robert Harker of Harker Systems.
+*/
+
+ case LOOKUPBEGIN:
+ /*
+ ** Got a database lookup,
+ ** check if map is defined.
+ */
+
+ ep = *(ap + 1);
+ if ((*ep & 0377) != MACRODEXPAND &&
+ stab(ep, ST_MAP,
+ ST_FIND) == NULL)
+ {
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT,
+ "Warning: %s: line %d: map %s not found\n",
+ FileName,
+ LineNumber,
+ ep);
+ }
+ break;
+#endif /* 0 */
+ }
+ if (botch != NULL)
+ syserr("Inappropriate use of %s on RHS",
+ botch);
+ }
+ if (inmap)
+ syserr("missing map closing token");
+ }
+ else
+ {
+ syserr("R line: null RHS");
+ rwp->r_rhs = null_list;
+ }
+ break;
+
+ case 'S': /* select rewriting set */
+ expand(&bp[1], exbuf, sizeof exbuf, e);
+ ruleset = strtorwset(exbuf, NULL, ST_ENTER);
+ if (ruleset < 0)
+ break;
+
+ rwp = RewriteRules[ruleset];
+ if (rwp != NULL)
+ {
+ if (OpMode == MD_TEST)
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT,
+ "WARNING: Ruleset %s has multiple definitions\n",
+ &bp[1]);
+ if (tTd(37, 1))
+ sm_dprintf("WARNING: Ruleset %s has multiple definitions\n",
+ &bp[1]);
+ while (rwp->r_next != NULL)
+ rwp = rwp->r_next;
+ }
+ break;
+
+ case 'D': /* macro definition */
+ mid = macid_parse(&bp[1], &ep);
+ if (mid == 0)
+ break;
+ p = munchstring(ep, NULL, '\0');
+ macdefine(&e->e_macro, A_TEMP, mid, p);
+ break;
+
+ case 'H': /* required header line */
+ (void) chompheader(&bp[1], CHHDR_DEF, NULL, e);
+ break;
+
+ case 'C': /* word class */
+ case 'T': /* trusted user (set class `t') */
+ if (bp[0] == 'C')
+ {
+ mid = macid_parse(&bp[1], &ep);
+ if (mid == 0)
+ break;
+ expand(ep, exbuf, sizeof exbuf, e);
+ p = exbuf;
+ }
+ else
+ {
+ mid = 't';
+ p = &bp[1];
+ }
+ while (*p != '\0')
+ {
+ register char *wd;
+ char delim;
+
+ while (*p != '\0' && isascii(*p) && isspace(*p))
+ p++;
+ wd = p;
+ while (*p != '\0' && !(isascii(*p) && isspace(*p)))
+ p++;
+ delim = *p;
+ *p = '\0';
+ if (wd[0] != '\0')
+ setclass(mid, wd);
+ *p = delim;
+ }
+ break;
+
+ case 'F': /* word class from file */
+ mid = macid_parse(&bp[1], &ep);
+ if (mid == 0)
+ break;
+ for (p = ep; isascii(*p) && isspace(*p); )
+ p++;
+ if (p[0] == '-' && p[1] == 'o')
+ {
+ optional = true;
+ while (*p != '\0' &&
+ !(isascii(*p) && isspace(*p)))
+ p++;
+ while (isascii(*p) && isspace(*p))
+ p++;
+ file = p;
+ }
+ else
+ optional = false;
+
+ /* check if [key]@map:spec */
+ ismap = false;
+ if (!SM_IS_DIR_DELIM(*p) &&
+ *p != '|' &&
+ (q = strchr(p, '@')) != NULL)
+ {
+ q++;
+
+ /* look for @LDAP or @map: in string */
+ if (strcmp(q, "LDAP") == 0 ||
+ (*q != ':' &&
+ strchr(q, ':') != NULL))
+ ismap = true;
+ }
+
+ if (ismap)
+ {
+ /* use entire spec */
+ file = p;
+ }
+ else
+ {
+ file = extrquotstr(p, &q, " ", &ok);
+ if (!ok)
+ {
+ syserr("illegal filename '%s'", p);
+ break;
+ }
+ }
+
+ if (*file == '|' || ismap)
+ p = "%s";
+ else
+ {
+ p = q;
+ if (*p == '\0')
+ p = "%s";
+ else
+ {
+ *p = '\0';
+ while (isascii(*++p) && isspace(*p))
+ continue;
+ }
+ }
+ fileclass(mid, file, p, ismap, safe, optional);
+ break;
+
+#if XLA
+ case 'L': /* extended load average description */
+ xla_init(&bp[1]);
+ break;
+#endif /* XLA */
+
+#if defined(SUN_EXTENSIONS) && defined(SUN_LOOKUP_MACRO)
+ case 'L': /* lookup macro */
+ case 'G': /* lookup class */
+ /* reserved for Sun -- NIS+ database lookup */
+ if (VendorCode != VENDOR_SUN)
+ goto badline;
+ sun_lg_config_line(bp, e);
+ break;
+#endif /* defined(SUN_EXTENSIONS) && defined(SUN_LOOKUP_MACRO) */
+
+ case 'M': /* define mailer */
+ makemailer(&bp[1]);
+ break;
+
+ case 'O': /* set option */
+ setoption(bp[1], &bp[2], safe, false, e);
+ break;
+
+ case 'P': /* set precedence */
+ if (NumPriorities >= MAXPRIORITIES)
+ {
+ toomany('P', MAXPRIORITIES);
+ break;
+ }
+ for (p = &bp[1]; *p != '\0' && *p != '='; p++)
+ continue;
+ if (*p == '\0')
+ goto badline;
+ *p = '\0';
+ Priorities[NumPriorities].pri_name = newstr(&bp[1]);
+ Priorities[NumPriorities].pri_val = atoi(++p);
+ NumPriorities++;
+ break;
+
+ case 'Q': /* define queue */
+ makequeue(&bp[1], true);
+ break;
+
+ case 'V': /* configuration syntax version */
+ for (p = &bp[1]; isascii(*p) && isspace(*p); p++)
+ continue;
+ if (!isascii(*p) || !isdigit(*p))
+ {
+ syserr("invalid argument to V line: \"%.20s\"",
+ &bp[1]);
+ break;
+ }
+ ConfigLevel = strtol(p, &ep, 10);
+
+ /*
+ ** Do heuristic tweaking for back compatibility.
+ */
+
+ if (ConfigLevel >= 5)
+ {
+ /* level 5 configs have short name in $w */
+ p = macvalue('w', e);
+ if (p != NULL && (p = strchr(p, '.')) != NULL)
+ {
+ *p = '\0';
+ macdefine(&e->e_macro, A_TEMP, 'w',
+ macvalue('w', e));
+ }
+ }
+ if (ConfigLevel >= 6)
+ {
+ ColonOkInAddr = false;
+ }
+
+ /*
+ ** Look for vendor code.
+ */
+
+ if (*ep++ == '/')
+ {
+ /* extract vendor code */
+ for (p = ep; isascii(*p) && isalpha(*p); )
+ p++;
+ *p = '\0';
+
+ if (!setvendor(ep))
+ syserr("invalid V line vendor code: \"%s\"",
+ ep);
+ }
+ break;
+
+ case 'K':
+ expand(&bp[1], exbuf, sizeof exbuf, e);
+ (void) makemapentry(exbuf);
+ break;
+
+ case 'E':
+ p = strchr(bp, '=');
+ if (p != NULL)
+ *p++ = '\0';
+ setuserenv(&bp[1], p);
+ break;
+
+ case 'X': /* mail filter */
+#if MILTER
+ milter_setup(&bp[1]);
+#else /* MILTER */
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Warning: Filter usage ('X') requires Milter support (-DMILTER)\n");
+#endif /* MILTER */
+ break;
+
+ default:
+ badline:
+ syserr("unknown configuration line \"%s\"", bp);
+ }
+ if (bp != buf)
+ sm_free(bp); /* XXX */
+ }
+ if (sm_io_error(cf))
+ {
+ syserr("I/O read error");
+ finis(false, true, EX_OSFILE);
+ }
+ (void) sm_io_close(cf, SM_TIME_DEFAULT);
+ FileName = NULL;
+
+ /* initialize host maps from local service tables */
+ inithostmaps();
+
+ /* initialize daemon (if not defined yet) */
+ initdaemon();
+
+ /* determine if we need to do special name-server frotz */
+ {
+ int nmaps;
+ char *maptype[MAXMAPSTACK];
+ short mapreturn[MAXMAPACTIONS];
+
+ nmaps = switch_map_find("hosts", maptype, mapreturn);
+ UseNameServer = false;
+ if (nmaps > 0 && nmaps <= MAXMAPSTACK)
+ {
+ register int mapno;
+
+ for (mapno = 0; mapno < nmaps && !UseNameServer;
+ mapno++)
+ {
+ if (strcmp(maptype[mapno], "dns") == 0)
+ UseNameServer = true;
+ }
+ }
+ }
+}
+/*
+** TRANSLATE_DOLLARS -- convert $x into internal form
+**
+** Actually does all appropriate pre-processing of a config line
+** to turn it into internal form.
+**
+** Parameters:
+** bp -- the buffer to translate.
+**
+** Returns:
+** None. The buffer is translated in place. Since the
+** translations always make the buffer shorter, this is
+** safe without a size parameter.
+*/
+
+void
+translate_dollars(bp)
+ char *bp;
+{
+ register char *p;
+ auto char *ep;
+
+ for (p = bp; *p != '\0'; p++)
+ {
+ if (*p == '#' && p > bp && ConfigLevel >= 3)
+ {
+ register char *e;
+
+ switch (*--p & 0377)
+ {
+ case MACROEXPAND:
+ /* it's from $# -- let it go through */
+ p++;
+ break;
+
+ case '\\':
+ /* it's backslash escaped */
+ (void) sm_strlcpy(p, p + 1, strlen(p));
+ break;
+
+ default:
+ /* delete leading white space */
+ while (isascii(*p) && isspace(*p) &&
+ *p != '\n' && p > bp)
+ p--;
+ if ((e = strchr(++p, '\n')) != NULL)
+ (void) sm_strlcpy(p, e, strlen(p));
+ else
+ *p-- = '\0';
+ break;
+ }
+ continue;
+ }
+
+ if (*p != '$' || p[1] == '\0')
+ continue;
+
+ if (p[1] == '$')
+ {
+ /* actual dollar sign.... */
+ (void) sm_strlcpy(p, p + 1, strlen(p));
+ continue;
+ }
+
+ /* convert to macro expansion character */
+ *p++ = MACROEXPAND;
+
+ /* special handling for $=, $~, $&, and $? */
+ if (*p == '=' || *p == '~' || *p == '&' || *p == '?')
+ p++;
+
+ /* convert macro name to code */
+ *p = macid_parse(p, &ep);
+ if (ep != p + 1)
+ (void) sm_strlcpy(p + 1, ep, strlen(p + 1));
+ }
+
+ /* strip trailing white space from the line */
+ while (--p > bp && isascii(*p) && isspace(*p))
+ *p = '\0';
+}
+/*
+** TOOMANY -- signal too many of some option
+**
+** Parameters:
+** id -- the id of the error line
+** maxcnt -- the maximum possible values
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** gives a syserr.
+*/
+
+static void
+toomany(id, maxcnt)
+ int id;
+ int maxcnt;
+{
+ syserr("too many %c lines, %d max", id, maxcnt);
+}
+/*
+** FILECLASS -- read members of a class from a file
+**
+** Parameters:
+** class -- class to define.
+** filename -- name of file to read.
+** fmt -- scanf string to use for match.
+** ismap -- if set, this is a map lookup.
+** safe -- if set, this is a safe read.
+** optional -- if set, it is not an error for the file to
+** not exist.
+**
+** Returns:
+** none
+**
+** Side Effects:
+** puts all lines in filename that match a scanf into
+** the named class.
+*/
+
+/*
+** Break up the match into words and add to class.
+*/
+
+static void
+parse_class_words(class, line)
+ int class;
+ char *line;
+{
+ while (line != NULL && *line != '\0')
+ {
+ register char *q;
+
+ /* strip leading spaces */
+ while (isascii(*line) && isspace(*line))
+ line++;
+ if (*line == '\0')
+ break;
+
+ /* find the end of the word */
+ q = line;
+ while (*line != '\0' && !(isascii(*line) && isspace(*line)))
+ line++;
+ if (*line != '\0')
+ *line++ = '\0';
+
+ /* enter the word in the symbol table */
+ setclass(class, q);
+ }
+}
+
+static void
+fileclass(class, filename, fmt, ismap, safe, optional)
+ int class;
+ char *filename;
+ char *fmt;
+ bool ismap;
+ bool safe;
+ bool optional;
+{
+ SM_FILE_T *f;
+ long sff;
+ pid_t pid;
+ register char *p;
+ char buf[MAXLINE];
+
+ if (tTd(37, 2))
+ sm_dprintf("fileclass(%s, fmt=%s)\n", filename, fmt);
+
+ if (*filename == '\0')
+ {
+ syserr("fileclass: missing file name");
+ return;
+ }
+ else if (ismap)
+ {
+ int status = 0;
+ char *key;
+ char *mn;
+ char *cl, *spec;
+ STAB *mapclass;
+ MAP map;
+
+ mn = newstr(macname(class));
+
+ key = filename;
+
+ /* skip past key */
+ if ((p = strchr(filename, '@')) == NULL)
+ {
+ /* should not happen */
+ syserr("fileclass: bogus map specification");
+ sm_free(mn);
+ return;
+ }
+
+ /* skip past '@' */
+ *p++ = '\0';
+ cl = p;
+
+#if LDAPMAP
+ if (strcmp(cl, "LDAP") == 0)
+ {
+ int n;
+ char *lc;
+ char jbuf[MAXHOSTNAMELEN];
+ char lcbuf[MAXLINE];
+
+ /* Get $j */
+ expand("\201j", jbuf, sizeof jbuf, &BlankEnvelope);
+ if (jbuf[0] == '\0')
+ {
+ (void) sm_strlcpy(jbuf, "localhost",
+ sizeof jbuf);
+ }
+
+ /* impose the default schema */
+ lc = macvalue(macid("{sendmailMTACluster}"), CurEnv);
+ if (lc == NULL)
+ lc = "";
+ else
+ {
+ expand(lc, lcbuf, sizeof lcbuf, CurEnv);
+ lc = lcbuf;
+ }
+
+ cl = "ldap";
+ n = sm_snprintf(buf, sizeof buf,
+ "-k (&(objectClass=sendmailMTAClass)(sendmailMTAClassName=%s)(|(sendmailMTACluster=%s)(sendmailMTAHost=%s))) -v sendmailMTAClassValue,sendmailMTAClassSearch:FILTER:sendmailMTAClass,sendmailMTAClassURL:URL:sendmailMTAClass",
+ mn, lc, jbuf);
+ if (n >= sizeof buf)
+ {
+ syserr("fileclass: F{%s}: Default LDAP string too long",
+ mn);
+ sm_free(mn);
+ return;
+ }
+ spec = buf;
+ }
+ else
+#endif /* LDAPMAP */
+ {
+ if ((spec = strchr(cl, ':')) == NULL)
+ {
+ syserr("fileclass: F{%s}: missing map class",
+ mn);
+ sm_free(mn);
+ return;
+ }
+ *spec++ ='\0';
+ }
+
+ /* set up map structure */
+ mapclass = stab(cl, ST_MAPCLASS, ST_FIND);
+ if (mapclass == NULL)
+ {
+ syserr("fileclass: F{%s}: class %s not available",
+ mn, cl);
+ sm_free(mn);
+ return;
+ }
+ memset(&map, '\0', sizeof map);
+ map.map_class = &mapclass->s_mapclass;
+ map.map_mname = mn;
+ map.map_mflags |= MF_FILECLASS;
+
+ if (tTd(37, 5))
+ sm_dprintf("fileclass: F{%s}: map class %s, key %s, spec %s\n",
+ mn, cl, key, spec);
+
+
+ /* parse map spec */
+ if (!map.map_class->map_parse(&map, spec))
+ {
+ /* map_parse() showed the error already */
+ sm_free(mn);
+ return;
+ }
+ map.map_mflags |= MF_VALID;
+
+ /* open map */
+ if (map.map_class->map_open(&map, O_RDONLY))
+ {
+ map.map_mflags |= MF_OPEN;
+ map.map_pid = getpid();
+ }
+ else
+ {
+ if (!optional &&
+ !bitset(MF_OPTIONAL, map.map_mflags))
+ syserr("fileclass: F{%s}: map open failed",
+ mn);
+ sm_free(mn);
+ return;
+ }
+
+ /* lookup */
+ p = (*map.map_class->map_lookup)(&map, key, NULL, &status);
+ if (status != EX_OK && status != EX_NOTFOUND)
+ {
+ if (!optional)
+ syserr("fileclass: F{%s}: map lookup failed",
+ mn);
+ p = NULL;
+ }
+
+ /* use the results */
+ if (p != NULL)
+ parse_class_words(class, p);
+
+ /* close map */
+ map.map_mflags |= MF_CLOSING;
+ map.map_class->map_close(&map);
+ map.map_mflags &= ~(MF_OPEN|MF_WRITABLE|MF_CLOSING);
+ sm_free(mn);
+ return;
+ }
+ else if (filename[0] == '|')
+ {
+ auto int fd;
+ int i;
+ char *argv[MAXPV + 1];
+
+ i = 0;
+ for (p = strtok(&filename[1], " \t");
+ p != NULL && i < MAXPV;
+ p = strtok(NULL, " \t"))
+ argv[i++] = p;
+ argv[i] = NULL;
+ pid = prog_open(argv, &fd, CurEnv);
+ if (pid < 0)
+ f = NULL;
+ else
+ f = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT,
+ (void *) &fd, SM_IO_RDONLY, NULL);
+ }
+ else
+ {
+ pid = -1;
+ sff = SFF_REGONLY;
+ if (!bitnset(DBS_CLASSFILEINUNSAFEDIRPATH, DontBlameSendmail))
+ sff |= SFF_SAFEDIRPATH;
+ if (!bitnset(DBS_LINKEDCLASSFILEINWRITABLEDIR,
+ DontBlameSendmail))
+ sff |= SFF_NOWLINK;
+ if (safe)
+ sff |= SFF_OPENASROOT;
+ else if (RealUid == 0)
+ sff |= SFF_ROOTOK;
+ if (DontLockReadFiles)
+ sff |= SFF_NOLOCK;
+ f = safefopen(filename, O_RDONLY, 0, sff);
+ }
+ if (f == NULL)
+ {
+ if (!optional)
+ syserr("fileclass: cannot open '%s'", filename);
+ return;
+ }
+
+ while (sm_io_fgets(f, SM_TIME_DEFAULT, buf, sizeof buf) != NULL)
+ {
+#if SCANF
+ char wordbuf[MAXLINE + 1];
+#endif /* SCANF */
+
+ if (buf[0] == '#')
+ continue;
+#if SCANF
+ if (sm_io_sscanf(buf, fmt, wordbuf) != 1)
+ continue;
+ p = wordbuf;
+#else /* SCANF */
+ p = buf;
+#endif /* SCANF */
+
+ parse_class_words(class, p);
+
+ /*
+ ** If anything else is added here,
+ ** check if the '@' map case above
+ ** needs the code as well.
+ */
+ }
+
+ (void) sm_io_close(f, SM_TIME_DEFAULT);
+ if (pid > 0)
+ (void) waitfor(pid);
+}
+/*
+** MAKEMAILER -- define a new mailer.
+**
+** Parameters:
+** line -- description of mailer. This is in labeled
+** fields. The fields are:
+** A -- the argv for this mailer
+** C -- the character set for MIME conversions
+** D -- the directory to run in
+** E -- the eol string
+** F -- the flags associated with the mailer
+** L -- the maximum line length
+** M -- the maximum message size
+** N -- the niceness at which to run
+** P -- the path to the mailer
+** Q -- the queue group for the mailer
+** R -- the recipient rewriting set
+** S -- the sender rewriting set
+** T -- the mailer type (for DSNs)
+** U -- the uid to run as
+** W -- the time to wait at the end
+** m -- maximum messages per connection
+** r -- maximum number of recipients per message
+** / -- new root directory
+** The first word is the canonical name of the mailer.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** enters the mailer into the mailer table.
+*/
+
+void
+makemailer(line)
+ char *line;
+{
+ register char *p;
+ register struct mailer *m;
+ register STAB *s;
+ int i;
+ char fcode;
+ auto char *endp;
+ static int nextmailer = 0; /* "free" index into Mailer struct */
+
+ /* allocate a mailer and set up defaults */
+ m = (struct mailer *) xalloc(sizeof *m);
+ memset((char *) m, '\0', sizeof *m);
+ errno = 0; /* avoid bogus error text */
+
+ /* collect the mailer name */
+ for (p = line;
+ *p != '\0' && *p != ',' && !(isascii(*p) && isspace(*p));
+ p++)
+ continue;
+ if (*p != '\0')
+ *p++ = '\0';
+ if (line[0] == '\0')
+ {
+ syserr("name required for mailer");
+ return;
+ }
+ m->m_name = newstr(line);
+ m->m_qgrp = NOQGRP;
+ m->m_uid = NO_UID;
+ m->m_gid = NO_GID;
+
+ /* now scan through and assign info from the fields */
+ while (*p != '\0')
+ {
+ auto char *delimptr;
+
+ while (*p != '\0' &&
+ (*p == ',' || (isascii(*p) && isspace(*p))))
+ p++;
+
+ /* p now points to field code */
+ fcode = *p;
+ while (*p != '\0' && *p != '=' && *p != ',')
+ p++;
+ if (*p++ != '=')
+ {
+ syserr("mailer %s: `=' expected", m->m_name);
+ return;
+ }
+ while (isascii(*p) && isspace(*p))
+ p++;
+
+ /* p now points to the field body */
+ p = munchstring(p, &delimptr, ',');
+
+ /* install the field into the mailer struct */
+ switch (fcode)
+ {
+ case 'P': /* pathname */
+ if (*p != '\0') /* error is issued below */
+ m->m_mailer = newstr(p);
+ break;
+
+ case 'F': /* flags */
+ for (; *p != '\0'; p++)
+ {
+ if (!(isascii(*p) && isspace(*p)))
+ {
+#if _FFR_DEPRECATE_MAILER_FLAG_I
+ if (*p == M_INTERNAL)
+ sm_syslog(LOG_WARNING, NOQID,
+ "WARNING: mailer=%s, flag=%c deprecated",
+ m->m_name, *p);
+#endif /* _FFR_DEPRECATE_MAILER_FLAG_I */
+ setbitn(bitidx(*p), m->m_flags);
+ }
+ }
+ break;
+
+ case 'S': /* sender rewriting ruleset */
+ case 'R': /* recipient rewriting ruleset */
+ i = strtorwset(p, &endp, ST_ENTER);
+ if (i < 0)
+ return;
+ if (fcode == 'S')
+ m->m_sh_rwset = m->m_se_rwset = i;
+ else
+ m->m_rh_rwset = m->m_re_rwset = i;
+
+ p = endp;
+ if (*p++ == '/')
+ {
+ i = strtorwset(p, NULL, ST_ENTER);
+ if (i < 0)
+ return;
+ if (fcode == 'S')
+ m->m_sh_rwset = i;
+ else
+ m->m_rh_rwset = i;
+ }
+ break;
+
+ case 'E': /* end of line string */
+ if (*p == '\0')
+ syserr("mailer %s: null end-of-line string",
+ m->m_name);
+ else
+ m->m_eol = newstr(p);
+ break;
+
+ case 'A': /* argument vector */
+ if (*p != '\0') /* error is issued below */
+ m->m_argv = makeargv(p);
+ break;
+
+ case 'M': /* maximum message size */
+ m->m_maxsize = atol(p);
+ break;
+
+ case 'm': /* maximum messages per connection */
+ m->m_maxdeliveries = atoi(p);
+ break;
+
+ case 'r': /* max recipient per envelope */
+ m->m_maxrcpt = atoi(p);
+ break;
+
+ case 'L': /* maximum line length */
+ m->m_linelimit = atoi(p);
+ if (m->m_linelimit < 0)
+ m->m_linelimit = 0;
+ break;
+
+ case 'N': /* run niceness */
+ m->m_nice = atoi(p);
+ break;
+
+ case 'D': /* working directory */
+ if (*p == '\0')
+ syserr("mailer %s: null working directory",
+ m->m_name);
+ else
+ m->m_execdir = newstr(p);
+ break;
+
+ case 'C': /* default charset */
+ if (*p == '\0')
+ syserr("mailer %s: null charset", m->m_name);
+ else
+ m->m_defcharset = newstr(p);
+ break;
+
+ case 'Q': /* queue for this mailer */
+ if (*p == '\0')
+ {
+ syserr("mailer %s: null queue", m->m_name);
+ break;
+ }
+ s = stab(p, ST_QUEUE, ST_FIND);
+ if (s == NULL)
+ syserr("mailer %s: unknown queue %s",
+ m->m_name, p);
+ else
+ m->m_qgrp = s->s_quegrp->qg_index;
+ break;
+
+ case 'T': /* MTA-Name/Address/Diagnostic types */
+ /* extract MTA name type; default to "dns" */
+ m->m_mtatype = newstr(p);
+ p = strchr(m->m_mtatype, '/');
+ if (p != NULL)
+ {
+ *p++ = '\0';
+ if (*p == '\0')
+ p = NULL;
+ }
+ if (*m->m_mtatype == '\0')
+ m->m_mtatype = "dns";
+
+ /* extract address type; default to "rfc822" */
+ m->m_addrtype = p;
+ if (p != NULL)
+ p = strchr(p, '/');
+ if (p != NULL)
+ {
+ *p++ = '\0';
+ if (*p == '\0')
+ p = NULL;
+ }
+ if (m->m_addrtype == NULL || *m->m_addrtype == '\0')
+ m->m_addrtype = "rfc822";
+
+ /* extract diagnostic type; default to "smtp" */
+ m->m_diagtype = p;
+ if (m->m_diagtype == NULL || *m->m_diagtype == '\0')
+ m->m_diagtype = "smtp";
+ break;
+
+ case 'U': /* user id */
+ if (isascii(*p) && !isdigit(*p))
+ {
+ char *q = p;
+ struct passwd *pw;
+
+ while (*p != '\0' && isascii(*p) &&
+ (isalnum(*p) || strchr("-_", *p) != NULL))
+ p++;
+ while (isascii(*p) && isspace(*p))
+ *p++ = '\0';
+ if (*p != '\0')
+ *p++ = '\0';
+ if (*q == '\0')
+ {
+ syserr("mailer %s: null user name",
+ m->m_name);
+ break;
+ }
+ pw = sm_getpwnam(q);
+ if (pw == NULL)
+ {
+ syserr("readcf: mailer U= flag: unknown user %s", q);
+ break;
+ }
+ else
+ {
+ m->m_uid = pw->pw_uid;
+ m->m_gid = pw->pw_gid;
+ }
+ }
+ else
+ {
+ auto char *q;
+
+ m->m_uid = strtol(p, &q, 0);
+ p = q;
+ while (isascii(*p) && isspace(*p))
+ p++;
+ if (*p != '\0')
+ p++;
+ }
+ while (isascii(*p) && isspace(*p))
+ p++;
+ if (*p == '\0')
+ break;
+ if (isascii(*p) && !isdigit(*p))
+ {
+ char *q = p;
+ struct group *gr;
+
+ while (isascii(*p) && isalnum(*p))
+ p++;
+ *p++ = '\0';
+ if (*q == '\0')
+ {
+ syserr("mailer %s: null group name",
+ m->m_name);
+ break;
+ }
+ gr = getgrnam(q);
+ if (gr == NULL)
+ {
+ syserr("readcf: mailer U= flag: unknown group %s", q);
+ break;
+ }
+ else
+ m->m_gid = gr->gr_gid;
+ }
+ else
+ {
+ m->m_gid = strtol(p, NULL, 0);
+ }
+ break;
+
+ case 'W': /* wait timeout */
+ m->m_wait = convtime(p, 's');
+ break;
+
+ case '/': /* new root directory */
+ if (*p == '\0')
+ syserr("mailer %s: null root directory",
+ m->m_name);
+ else
+ m->m_rootdir = newstr(p);
+ break;
+
+ default:
+ syserr("M%s: unknown mailer equate %c=",
+ m->m_name, fcode);
+ break;
+ }
+
+ p = delimptr;
+ }
+
+#if !HASRRESVPORT
+ if (bitnset(M_SECURE_PORT, m->m_flags))
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "M%s: Warning: F=%c set on system that doesn't support rresvport()\n",
+ m->m_name, M_SECURE_PORT);
+ }
+#endif /* !HASRRESVPORT */
+
+#if !HASNICE
+ if (m->m_nice != 0)
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "M%s: Warning: N= set on system that doesn't support nice()\n",
+ m->m_name);
+ }
+#endif /* !HASNICE */
+
+ /* do some rationality checking */
+ if (m->m_argv == NULL)
+ {
+ syserr("M%s: A= argument required", m->m_name);
+ return;
+ }
+ if (m->m_mailer == NULL)
+ {
+ syserr("M%s: P= argument required", m->m_name);
+ return;
+ }
+
+ if (nextmailer >= MAXMAILERS)
+ {
+ syserr("too many mailers defined (%d max)", MAXMAILERS);
+ return;
+ }
+
+ if (m->m_maxrcpt <= 0)
+ m->m_maxrcpt = DEFAULT_MAX_RCPT;
+
+ /* do some heuristic cleanup for back compatibility */
+ if (bitnset(M_LIMITS, m->m_flags))
+ {
+ if (m->m_linelimit == 0)
+ m->m_linelimit = SMTPLINELIM;
+ if (ConfigLevel < 2)
+ setbitn(M_7BITS, m->m_flags);
+ }
+
+ if (strcmp(m->m_mailer, "[TCP]") == 0)
+ {
+ syserr("M%s: P=[TCP] must be replaced by P=[IPC]", m->m_name);
+ return;
+ }
+
+ if (strcmp(m->m_mailer, "[IPC]") == 0)
+ {
+ /* Use the second argument for host or path to socket */
+ if (m->m_argv[0] == NULL || m->m_argv[1] == NULL ||
+ m->m_argv[1][0] == '\0')
+ {
+ syserr("M%s: too few parameters for %s mailer",
+ m->m_name, m->m_mailer);
+ return;
+ }
+ if (strcmp(m->m_argv[0], "TCP") != 0
+#if NETUNIX
+ && strcmp(m->m_argv[0], "FILE") != 0
+#endif /* NETUNIX */
+ )
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "M%s: Warning: first argument in %s mailer must be %s\n",
+ m->m_name, m->m_mailer,
+#if NETUNIX
+ "TCP or FILE"
+#else /* NETUNIX */
+ "TCP"
+#endif /* NETUNIX */
+ );
+ }
+ if (m->m_mtatype == NULL)
+ m->m_mtatype = "dns";
+ if (m->m_addrtype == NULL)
+ m->m_addrtype = "rfc822";
+ if (m->m_diagtype == NULL)
+ {
+ if (m->m_argv[0] != NULL &&
+ strcmp(m->m_argv[0], "FILE") == 0)
+ m->m_diagtype = "x-unix";
+ else
+ m->m_diagtype = "smtp";
+ }
+ }
+ else if (strcmp(m->m_mailer, "[FILE]") == 0)
+ {
+ /* Use the second argument for filename */
+ if (m->m_argv[0] == NULL || m->m_argv[1] == NULL ||
+ m->m_argv[2] != NULL)
+ {
+ syserr("M%s: too %s parameters for [FILE] mailer",
+ m->m_name,
+ (m->m_argv[0] == NULL ||
+ m->m_argv[1] == NULL) ? "few" : "many");
+ return;
+ }
+ else if (strcmp(m->m_argv[0], "FILE") != 0)
+ {
+ syserr("M%s: first argument in [FILE] mailer must be FILE",
+ m->m_name);
+ return;
+ }
+ }
+
+ if (m->m_eol == NULL)
+ {
+ char **pp;
+
+ /* default for SMTP is \r\n; use \n for local delivery */
+ for (pp = m->m_argv; *pp != NULL; pp++)
+ {
+ for (p = *pp; *p != '\0'; )
+ {
+ if ((*p++ & 0377) == MACROEXPAND && *p == 'u')
+ break;
+ }
+ if (*p != '\0')
+ break;
+ }
+ if (*pp == NULL)
+ m->m_eol = "\r\n";
+ else
+ m->m_eol = "\n";
+ }
+
+ /* enter the mailer into the symbol table */
+ s = stab(m->m_name, ST_MAILER, ST_ENTER);
+ if (s->s_mailer != NULL)
+ {
+ i = s->s_mailer->m_mno;
+ sm_free(s->s_mailer); /* XXX */
+ }
+ else
+ {
+ i = nextmailer++;
+ }
+ Mailer[i] = s->s_mailer = m;
+ m->m_mno = i;
+}
+/*
+** MUNCHSTRING -- translate a string into internal form.
+**
+** Parameters:
+** p -- the string to munch.
+** delimptr -- if non-NULL, set to the pointer of the
+** field delimiter character.
+** delim -- the delimiter for the field.
+**
+** Returns:
+** the munched string.
+**
+** Side Effects:
+** the munched string is a local static buffer.
+** it must be copied before the function is called again.
+*/
+
+char *
+munchstring(p, delimptr, delim)
+ register char *p;
+ char **delimptr;
+ int delim;
+{
+ register char *q;
+ bool backslash = false;
+ bool quotemode = false;
+ static char buf[MAXLINE];
+
+ for (q = buf; *p != '\0' && q < &buf[sizeof buf - 1]; p++)
+ {
+ if (backslash)
+ {
+ /* everything is roughly literal */
+ backslash = false;
+ switch (*p)
+ {
+ case 'r': /* carriage return */
+ *q++ = '\r';
+ continue;
+
+ case 'n': /* newline */
+ *q++ = '\n';
+ continue;
+
+ case 'f': /* form feed */
+ *q++ = '\f';
+ continue;
+
+ case 'b': /* backspace */
+ *q++ = '\b';
+ continue;
+ }
+ *q++ = *p;
+ }
+ else
+ {
+ if (*p == '\\')
+ backslash = true;
+ else if (*p == '"')
+ quotemode = !quotemode;
+ else if (quotemode || *p != delim)
+ *q++ = *p;
+ else
+ break;
+ }
+ }
+
+ if (delimptr != NULL)
+ *delimptr = p;
+ *q++ = '\0';
+ return buf;
+}
+/*
+** EXTRQUOTSTR -- extract a (quoted) string.
+**
+** This routine deals with quoted (") strings and escaped
+** spaces (\\ ).
+**
+** Parameters:
+** p -- source string.
+** delimptr -- if non-NULL, set to the pointer of the
+** field delimiter character.
+** delimbuf -- delimiters for the field.
+** st -- if non-NULL, store the return value (whether the
+** string was correctly quoted) here.
+**
+** Returns:
+** the extracted string.
+**
+** Side Effects:
+** the returned string is a local static buffer.
+** it must be copied before the function is called again.
+*/
+
+static char *
+extrquotstr(p, delimptr, delimbuf, st)
+ register char *p;
+ char **delimptr;
+ char *delimbuf;
+ bool *st;
+{
+ register char *q;
+ bool backslash = false;
+ bool quotemode = false;
+ static char buf[MAXLINE];
+
+ for (q = buf; *p != '\0' && q < &buf[sizeof buf - 1]; p++)
+ {
+ if (backslash)
+ {
+ backslash = false;
+ if (*p != ' ')
+ *q++ = '\\';
+ }
+ if (*p == '\\')
+ backslash = true;
+ else if (*p == '"')
+ quotemode = !quotemode;
+ else if (quotemode ||
+ strchr(delimbuf, (int) *p) == NULL)
+ *q++ = *p;
+ else
+ break;
+ }
+
+ if (delimptr != NULL)
+ *delimptr = p;
+ *q++ = '\0';
+ if (st != NULL)
+ *st = !(quotemode || backslash);
+ return buf;
+}
+/*
+** MAKEARGV -- break up a string into words
+**
+** Parameters:
+** p -- the string to break up.
+**
+** Returns:
+** a char **argv (dynamically allocated)
+**
+** Side Effects:
+** munges p.
+*/
+
+static char **
+makeargv(p)
+ register char *p;
+{
+ char *q;
+ int i;
+ char **avp;
+ char *argv[MAXPV + 1];
+
+ /* take apart the words */
+ i = 0;
+ while (*p != '\0' && i < MAXPV)
+ {
+ q = p;
+ while (*p != '\0' && !(isascii(*p) && isspace(*p)))
+ p++;
+ while (isascii(*p) && isspace(*p))
+ *p++ = '\0';
+ argv[i++] = newstr(q);
+ }
+ argv[i++] = NULL;
+
+ /* now make a copy of the argv */
+ avp = (char **) xalloc(sizeof *avp * i);
+ memmove((char *) avp, (char *) argv, sizeof *avp * i);
+
+ return avp;
+}
+/*
+** PRINTRULES -- print rewrite rules (for debugging)
+**
+** Parameters:
+** none.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** prints rewrite rules.
+*/
+
+void
+printrules()
+{
+ register struct rewrite *rwp;
+ register int ruleset;
+
+ for (ruleset = 0; ruleset < 10; ruleset++)
+ {
+ if (RewriteRules[ruleset] == NULL)
+ continue;
+ sm_dprintf("\n----Rule Set %d:", ruleset);
+
+ for (rwp = RewriteRules[ruleset]; rwp != NULL; rwp = rwp->r_next)
+ {
+ sm_dprintf("\nLHS:");
+ printav(sm_debug_file(), rwp->r_lhs);
+ sm_dprintf("RHS:");
+ printav(sm_debug_file(), rwp->r_rhs);
+ }
+ }
+}
+/*
+** PRINTMAILER -- print mailer structure (for debugging)
+**
+** Parameters:
+** fp -- output file
+** m -- the mailer to print
+**
+** Returns:
+** none.
+*/
+
+void
+printmailer(fp, m)
+ SM_FILE_T *fp;
+ register MAILER *m;
+{
+ int j;
+
+ (void) sm_io_fprintf(fp, SM_TIME_DEFAULT,
+ "mailer %d (%s): P=%s S=", m->m_mno, m->m_name,
+ m->m_mailer);
+ if (RuleSetNames[m->m_se_rwset] == NULL)
+ (void) sm_io_fprintf(fp, SM_TIME_DEFAULT, "%d/",
+ m->m_se_rwset);
+ else
+ (void) sm_io_fprintf(fp, SM_TIME_DEFAULT, "%s/",
+ RuleSetNames[m->m_se_rwset]);
+ if (RuleSetNames[m->m_sh_rwset] == NULL)
+ (void) sm_io_fprintf(fp, SM_TIME_DEFAULT, "%d R=",
+ m->m_sh_rwset);
+ else
+ (void) sm_io_fprintf(fp, SM_TIME_DEFAULT, "%s R=",
+ RuleSetNames[m->m_sh_rwset]);
+ if (RuleSetNames[m->m_re_rwset] == NULL)
+ (void) sm_io_fprintf(fp, SM_TIME_DEFAULT, "%d/",
+ m->m_re_rwset);
+ else
+ (void) sm_io_fprintf(fp, SM_TIME_DEFAULT, "%s/",
+ RuleSetNames[m->m_re_rwset]);
+ if (RuleSetNames[m->m_rh_rwset] == NULL)
+ (void) sm_io_fprintf(fp, SM_TIME_DEFAULT, "%d ",
+ m->m_rh_rwset);
+ else
+ (void) sm_io_fprintf(fp, SM_TIME_DEFAULT, "%s ",
+ RuleSetNames[m->m_rh_rwset]);
+ (void) sm_io_fprintf(fp, SM_TIME_DEFAULT, "M=%ld U=%d:%d F=",
+ m->m_maxsize, (int) m->m_uid, (int) m->m_gid);
+ for (j = '\0'; j <= '\177'; j++)
+ if (bitnset(j, m->m_flags))
+ (void) sm_io_putc(fp, SM_TIME_DEFAULT, j);
+ (void) sm_io_fprintf(fp, SM_TIME_DEFAULT, " L=%d E=",
+ m->m_linelimit);
+ xputs(fp, m->m_eol);
+ if (m->m_defcharset != NULL)
+ (void) sm_io_fprintf(fp, SM_TIME_DEFAULT, " C=%s",
+ m->m_defcharset);
+ (void) sm_io_fprintf(fp, SM_TIME_DEFAULT, " T=%s/%s/%s",
+ m->m_mtatype == NULL
+ ? "<undefined>" : m->m_mtatype,
+ m->m_addrtype == NULL
+ ? "<undefined>" : m->m_addrtype,
+ m->m_diagtype == NULL
+ ? "<undefined>" : m->m_diagtype);
+ (void) sm_io_fprintf(fp, SM_TIME_DEFAULT, " r=%d", m->m_maxrcpt);
+ if (m->m_argv != NULL)
+ {
+ char **a = m->m_argv;
+
+ (void) sm_io_fprintf(fp, SM_TIME_DEFAULT, " A=");
+ while (*a != NULL)
+ {
+ if (a != m->m_argv)
+ (void) sm_io_fprintf(fp, SM_TIME_DEFAULT,
+ " ");
+ xputs(fp, *a++);
+ }
+ }
+ (void) sm_io_fprintf(fp, SM_TIME_DEFAULT, "\n");
+}
+/*
+** SETOPTION -- set global processing option
+**
+** Parameters:
+** opt -- option name.
+** val -- option value (as a text string).
+** safe -- set if this came from a configuration file.
+** Some options (if set from the command line) will
+** reset the user id to avoid security problems.
+** sticky -- if set, don't let other setoptions override
+** this value.
+** e -- the main envelope.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Sets options as implied by the arguments.
+*/
+
+static BITMAP256 StickyOpt; /* set if option is stuck */
+
+#if NAMED_BIND
+
+static struct resolverflags
+{
+ char *rf_name; /* name of the flag */
+ long rf_bits; /* bits to set/clear */
+} ResolverFlags[] =
+{
+ { "debug", RES_DEBUG },
+ { "aaonly", RES_AAONLY },
+ { "usevc", RES_USEVC },
+ { "primary", RES_PRIMARY },
+ { "igntc", RES_IGNTC },
+ { "recurse", RES_RECURSE },
+ { "defnames", RES_DEFNAMES },
+ { "stayopen", RES_STAYOPEN },
+ { "dnsrch", RES_DNSRCH },
+# ifdef RES_USE_INET6
+ { "use_inet6", RES_USE_INET6 },
+# endif /* RES_USE_INET6 */
+ { "true", 0 }, /* avoid error on old syntax */
+ { NULL, 0 }
+};
+
+#endif /* NAMED_BIND */
+
+#define OI_NONE 0 /* no special treatment */
+#define OI_SAFE 0x0001 /* safe for random people to use */
+#define OI_SUBOPT 0x0002 /* option has suboptions */
+
+static struct optioninfo
+{
+ char *o_name; /* long name of option */
+ unsigned char o_code; /* short name of option */
+ unsigned short o_flags; /* option flags */
+} OptionTab[] =
+{
+#if defined(SUN_EXTENSIONS) && defined(REMOTE_MODE)
+ { "RemoteMode", '>', OI_NONE },
+#endif /* defined(SUN_EXTENSIONS) && defined(REMOTE_MODE) */
+ { "SevenBitInput", '7', OI_SAFE },
+ { "EightBitMode", '8', OI_SAFE },
+ { "AliasFile", 'A', OI_NONE },
+ { "AliasWait", 'a', OI_NONE },
+ { "BlankSub", 'B', OI_NONE },
+ { "MinFreeBlocks", 'b', OI_SAFE },
+ { "CheckpointInterval", 'C', OI_SAFE },
+ { "HoldExpensive", 'c', OI_NONE },
+ { "DeliveryMode", 'd', OI_SAFE },
+ { "ErrorHeader", 'E', OI_NONE },
+ { "ErrorMode", 'e', OI_SAFE },
+ { "TempFileMode", 'F', OI_NONE },
+ { "SaveFromLine", 'f', OI_NONE },
+ { "MatchGECOS", 'G', OI_NONE },
+
+ /* no long name, just here to avoid problems in setoption */
+ { "", 'g', OI_NONE },
+ { "HelpFile", 'H', OI_NONE },
+ { "MaxHopCount", 'h', OI_NONE },
+ { "ResolverOptions", 'I', OI_NONE },
+ { "IgnoreDots", 'i', OI_SAFE },
+ { "ForwardPath", 'J', OI_NONE },
+ { "SendMimeErrors", 'j', OI_SAFE },
+ { "ConnectionCacheSize", 'k', OI_NONE },
+ { "ConnectionCacheTimeout", 'K', OI_NONE },
+ { "UseErrorsTo", 'l', OI_NONE },
+ { "LogLevel", 'L', OI_SAFE },
+ { "MeToo", 'm', OI_SAFE },
+
+ /* no long name, just here to avoid problems in setoption */
+ { "", 'M', OI_NONE },
+ { "CheckAliases", 'n', OI_NONE },
+ { "OldStyleHeaders", 'o', OI_SAFE },
+ { "DaemonPortOptions", 'O', OI_NONE },
+ { "PrivacyOptions", 'p', OI_SAFE },
+ { "PostmasterCopy", 'P', OI_NONE },
+ { "QueueFactor", 'q', OI_NONE },
+ { "QueueDirectory", 'Q', OI_NONE },
+ { "DontPruneRoutes", 'R', OI_NONE },
+ { "Timeout", 'r', OI_SUBOPT },
+ { "StatusFile", 'S', OI_NONE },
+ { "SuperSafe", 's', OI_SAFE },
+ { "QueueTimeout", 'T', OI_NONE },
+ { "TimeZoneSpec", 't', OI_NONE },
+ { "UserDatabaseSpec", 'U', OI_NONE },
+ { "DefaultUser", 'u', OI_NONE },
+ { "FallbackMXhost", 'V', OI_NONE },
+ { "Verbose", 'v', OI_SAFE },
+ { "TryNullMXList", 'w', OI_NONE },
+ { "QueueLA", 'x', OI_NONE },
+ { "RefuseLA", 'X', OI_NONE },
+ { "RecipientFactor", 'y', OI_NONE },
+ { "ForkEachJob", 'Y', OI_NONE },
+ { "ClassFactor", 'z', OI_NONE },
+ { "RetryFactor", 'Z', OI_NONE },
+#define O_QUEUESORTORD 0x81
+ { "QueueSortOrder", O_QUEUESORTORD, OI_SAFE },
+#define O_HOSTSFILE 0x82
+ { "HostsFile", O_HOSTSFILE, OI_NONE },
+#define O_MQA 0x83
+ { "MinQueueAge", O_MQA, OI_SAFE },
+#define O_DEFCHARSET 0x85
+ { "DefaultCharSet", O_DEFCHARSET, OI_SAFE },
+#define O_SSFILE 0x86
+ { "ServiceSwitchFile", O_SSFILE, OI_NONE },
+#define O_DIALDELAY 0x87
+ { "DialDelay", O_DIALDELAY, OI_SAFE },
+#define O_NORCPTACTION 0x88
+ { "NoRecipientAction", O_NORCPTACTION, OI_SAFE },
+#define O_SAFEFILEENV 0x89
+ { "SafeFileEnvironment", O_SAFEFILEENV, OI_NONE },
+#define O_MAXMSGSIZE 0x8a
+ { "MaxMessageSize", O_MAXMSGSIZE, OI_NONE },
+#define O_COLONOKINADDR 0x8b
+ { "ColonOkInAddr", O_COLONOKINADDR, OI_SAFE },
+#define O_MAXQUEUERUN 0x8c
+ { "MaxQueueRunSize", O_MAXQUEUERUN, OI_SAFE },
+#define O_MAXCHILDREN 0x8d
+ { "MaxDaemonChildren", O_MAXCHILDREN, OI_NONE },
+#define O_KEEPCNAMES 0x8e
+ { "DontExpandCnames", O_KEEPCNAMES, OI_NONE },
+#define O_MUSTQUOTE 0x8f
+ { "MustQuoteChars", O_MUSTQUOTE, OI_NONE },
+#define O_SMTPGREETING 0x90
+ { "SmtpGreetingMessage", O_SMTPGREETING, OI_NONE },
+#define O_UNIXFROM 0x91
+ { "UnixFromLine", O_UNIXFROM, OI_NONE },
+#define O_OPCHARS 0x92
+ { "OperatorChars", O_OPCHARS, OI_NONE },
+#define O_DONTINITGRPS 0x93
+ { "DontInitGroups", O_DONTINITGRPS, OI_NONE },
+#define O_SLFH 0x94
+ { "SingleLineFromHeader", O_SLFH, OI_SAFE },
+#define O_ABH 0x95
+ { "AllowBogusHELO", O_ABH, OI_SAFE },
+#define O_CONNTHROT 0x97
+ { "ConnectionRateThrottle", O_CONNTHROT, OI_NONE },
+#define O_UGW 0x99
+ { "UnsafeGroupWrites", O_UGW, OI_NONE },
+#define O_DBLBOUNCE 0x9a
+ { "DoubleBounceAddress", O_DBLBOUNCE, OI_NONE },
+#define O_HSDIR 0x9b
+ { "HostStatusDirectory", O_HSDIR, OI_NONE },
+#define O_SINGTHREAD 0x9c
+ { "SingleThreadDelivery", O_SINGTHREAD, OI_NONE },
+#define O_RUNASUSER 0x9d
+ { "RunAsUser", O_RUNASUSER, OI_NONE },
+#define O_DSN_RRT 0x9e
+ { "RrtImpliesDsn", O_DSN_RRT, OI_NONE },
+#define O_PIDFILE 0x9f
+ { "PidFile", O_PIDFILE, OI_NONE },
+#define O_DONTBLAMESENDMAIL 0xa0
+ { "DontBlameSendmail", O_DONTBLAMESENDMAIL, OI_NONE },
+#define O_DPI 0xa1
+ { "DontProbeInterfaces", O_DPI, OI_NONE },
+#define O_MAXRCPT 0xa2
+ { "MaxRecipientsPerMessage", O_MAXRCPT, OI_SAFE },
+#define O_DEADLETTER 0xa3
+ { "DeadLetterDrop", O_DEADLETTER, OI_NONE },
+#if _FFR_DONTLOCKFILESFORREAD_OPTION
+# define O_DONTLOCK 0xa4
+ { "DontLockFilesForRead", O_DONTLOCK, OI_NONE },
+#endif /* _FFR_DONTLOCKFILESFORREAD_OPTION */
+#define O_MAXALIASRCSN 0xa5
+ { "MaxAliasRecursion", O_MAXALIASRCSN, OI_NONE },
+#define O_CNCTONLYTO 0xa6
+ { "ConnectOnlyTo", O_CNCTONLYTO, OI_NONE },
+#define O_TRUSTUSER 0xa7
+ { "TrustedUser", O_TRUSTUSER, OI_NONE },
+#define O_MAXMIMEHDRLEN 0xa8
+ { "MaxMimeHeaderLength", O_MAXMIMEHDRLEN, OI_NONE },
+#define O_CONTROLSOCKET 0xa9
+ { "ControlSocketName", O_CONTROLSOCKET, OI_NONE },
+#define O_MAXHDRSLEN 0xaa
+ { "MaxHeadersLength", O_MAXHDRSLEN, OI_NONE },
+#if _FFR_MAX_FORWARD_ENTRIES
+# define O_MAXFORWARD 0xab
+ { "MaxForwardEntries", O_MAXFORWARD, OI_NONE },
+#endif /* _FFR_MAX_FORWARD_ENTRIES */
+#define O_PROCTITLEPREFIX 0xac
+ { "ProcessTitlePrefix", O_PROCTITLEPREFIX, OI_NONE },
+#define O_SASLINFO 0xad
+#if _FFR_ALLOW_SASLINFO
+ { "DefaultAuthInfo", O_SASLINFO, OI_SAFE },
+#else /* _FFR_ALLOW_SASLINFO */
+ { "DefaultAuthInfo", O_SASLINFO, OI_NONE },
+#endif /* _FFR_ALLOW_SASLINFO */
+#define O_SASLMECH 0xae
+ { "AuthMechanisms", O_SASLMECH, OI_NONE },
+#define O_CLIENTPORT 0xaf
+ { "ClientPortOptions", O_CLIENTPORT, OI_NONE },
+#define O_DF_BUFSIZE 0xb0
+ { "DataFileBufferSize", O_DF_BUFSIZE, OI_NONE },
+#define O_XF_BUFSIZE 0xb1
+ { "XscriptFileBufferSize", O_XF_BUFSIZE, OI_NONE },
+#define O_LDAPDEFAULTSPEC 0xb2
+ { "LDAPDefaultSpec", O_LDAPDEFAULTSPEC, OI_NONE },
+#define O_SRVCERTFILE 0xb4
+ { "ServerCertFile", O_SRVCERTFILE, OI_NONE },
+#define O_SRVKEYFILE 0xb5
+ { "ServerKeyFile", O_SRVKEYFILE, OI_NONE },
+#define O_CLTCERTFILE 0xb6
+ { "ClientCertFile", O_CLTCERTFILE, OI_NONE },
+#define O_CLTKEYFILE 0xb7
+ { "ClientKeyFile", O_CLTKEYFILE, OI_NONE },
+#define O_CACERTFILE 0xb8
+ { "CACertFile", O_CACERTFILE, OI_NONE },
+#define O_CACERTPATH 0xb9
+ { "CACertPath", O_CACERTPATH, OI_NONE },
+#define O_DHPARAMS 0xba
+ { "DHParameters", O_DHPARAMS, OI_NONE },
+#define O_INPUTMILTER 0xbb
+ { "InputMailFilters", O_INPUTMILTER, OI_NONE },
+#define O_MILTER 0xbc
+ { "Milter", O_MILTER, OI_SUBOPT },
+#define O_SASLOPTS 0xbd
+ { "AuthOptions", O_SASLOPTS, OI_NONE },
+#define O_QUEUE_FILE_MODE 0xbe
+ { "QueueFileMode", O_QUEUE_FILE_MODE, OI_NONE },
+#if _FFR_TLS_1
+# define O_DHPARAMS5 0xbf
+ { "DHParameters512", O_DHPARAMS5, OI_NONE },
+# define O_CIPHERLIST 0xc0
+ { "CipherList", O_CIPHERLIST, OI_NONE },
+#endif /* _FFR_TLS_1 */
+#define O_RANDFILE 0xc1
+ { "RandFile", O_RANDFILE, OI_NONE },
+#define O_TLS_SRV_OPTS 0xc2
+ { "TLSSrvOptions", O_TLS_SRV_OPTS, OI_NONE },
+#define O_RCPTTHROT 0xc3
+ { "BadRcptThrottle", O_RCPTTHROT, OI_SAFE },
+#define O_DLVR_MIN 0xc4
+ { "DeliverByMin", O_DLVR_MIN, OI_NONE },
+#define O_MAXQUEUECHILDREN 0xc5
+ { "MaxQueueChildren", O_MAXQUEUECHILDREN, OI_NONE },
+#define O_MAXRUNNERSPERQUEUE 0xc6
+ { "MaxRunnersPerQueue", O_MAXRUNNERSPERQUEUE, OI_NONE },
+#define O_DIRECTSUBMODIFIERS 0xc7
+ { "DirectSubmissionModifiers", O_DIRECTSUBMODIFIERS, OI_NONE },
+#define O_NICEQUEUERUN 0xc8
+ { "NiceQueueRun", O_NICEQUEUERUN, OI_NONE },
+#define O_SHMKEY 0xc9
+ { "SharedMemoryKey", O_SHMKEY, OI_NONE },
+#define O_SASLBITS 0xca
+ { "AuthMaxBits", O_SASLBITS, OI_NONE },
+#define O_MBDB 0xcb
+ { "MailboxDatabase", O_MBDB, OI_NONE },
+#define O_MSQ 0xcc
+ { "UseMSP", O_MSQ, OI_NONE },
+#define O_DELAY_LA 0xcd
+ { "DelayLA", O_DELAY_LA, OI_NONE },
+#define O_FASTSPLIT 0xce
+ { "FastSplit", O_FASTSPLIT, OI_NONE },
+#if _FFR_SOFT_BOUNCE
+# define O_SOFTBOUNCE 0xcf
+ { "SoftBounce", O_SOFTBOUNCE, OI_NONE },
+#endif /* _FFR_SOFT_BOUNCE */
+#if _FFR_SELECT_SHM
+# define O_SHMKEYFILE 0xd0
+ { "SharedMemoryKeyFile", O_SHMKEYFILE, OI_NONE },
+#endif /* _FFR_SELECT_SHM */
+#define O_REJECTLOGINTERVAL 0xd1
+ { "RejectLogInterval", O_REJECTLOGINTERVAL, OI_NONE },
+#define O_REQUIRES_DIR_FSYNC 0xd2
+ { "RequiresDirfsync", O_REQUIRES_DIR_FSYNC, OI_NONE },
+#define O_CONNECTION_RATE_WINDOW_SIZE 0xd3
+ { "ConnectionRateWindowSize", O_CONNECTION_RATE_WINDOW_SIZE, OI_NONE },
+#define O_CRLFILE 0xd4
+ { "CRLFile", O_CRLFILE, OI_NONE },
+#define O_FALLBACKSMARTHOST 0xd5
+ { "FallbackSmartHost", O_FALLBACKSMARTHOST, OI_NONE },
+#define O_SASLREALM 0xd6
+ { "AuthRealm", O_SASLREALM, OI_NONE },
+#if _FFR_CRLPATH
+# define O_CRLPATH 0xd7
+ { "CRLPath", O_CRLPATH, OI_NONE },
+#endif /* _FFR_CRLPATH */
+#if _FFR_HELONAME
+# define O_HELONAME 0xd8
+ { "HeloName", O_HELONAME, OI_NONE },
+#endif /* _FFR_HELONAME */
+
+ { NULL, '\0', OI_NONE }
+};
+
+# define CANONIFY(val)
+
+# define SET_OPT_DEFAULT(opt, val) opt = val
+
+/* set a string option by expanding the value and assigning it */
+/* WARNING this belongs ONLY into a case statement! */
+#define SET_STRING_EXP(str) \
+ expand(val, exbuf, sizeof exbuf, e); \
+ newval = sm_pstrdup_x(exbuf); \
+ if (str != NULL) \
+ sm_free(str); \
+ CANONIFY(newval); \
+ str = newval; \
+ break
+
+#define OPTNAME o->o_name == NULL ? "<unknown>" : o->o_name
+
+void
+setoption(opt, val, safe, sticky, e)
+ int opt;
+ char *val;
+ bool safe;
+ bool sticky;
+ register ENVELOPE *e;
+{
+ register char *p;
+ register struct optioninfo *o;
+ char *subopt;
+ int mid;
+ bool can_setuid = RunAsUid == 0;
+ auto char *ep;
+ char buf[50];
+ extern bool Warn_Q_option;
+#if _FFR_ALLOW_SASLINFO
+ extern unsigned int SubmitMode;
+#endif /* _FFR_ALLOW_SASLINFO */
+#if STARTTLS
+ char *newval;
+ char exbuf[MAXLINE];
+#endif /* STARTTLS */
+
+ errno = 0;
+ if (opt == ' ')
+ {
+ /* full word options */
+ struct optioninfo *sel;
+
+ p = strchr(val, '=');
+ if (p == NULL)
+ p = &val[strlen(val)];
+ while (*--p == ' ')
+ continue;
+ while (*++p == ' ')
+ *p = '\0';
+ if (p == val)
+ {
+ syserr("readcf: null option name");
+ return;
+ }
+ if (*p == '=')
+ *p++ = '\0';
+ while (*p == ' ')
+ p++;
+ subopt = strchr(val, '.');
+ if (subopt != NULL)
+ *subopt++ = '\0';
+ sel = NULL;
+ for (o = OptionTab; o->o_name != NULL; o++)
+ {
+ if (sm_strncasecmp(o->o_name, val, strlen(val)) != 0)
+ continue;
+ if (strlen(o->o_name) == strlen(val))
+ {
+ /* completely specified -- this must be it */
+ sel = NULL;
+ break;
+ }
+ if (sel != NULL)
+ break;
+ sel = o;
+ }
+ if (sel != NULL && o->o_name == NULL)
+ o = sel;
+ else if (o->o_name == NULL)
+ {
+ syserr("readcf: unknown option name %s", val);
+ return;
+ }
+ else if (sel != NULL)
+ {
+ syserr("readcf: ambiguous option name %s (matches %s and %s)",
+ val, sel->o_name, o->o_name);
+ return;
+ }
+ if (strlen(val) != strlen(o->o_name))
+ {
+ int oldVerbose = Verbose;
+
+ Verbose = 1;
+ message("Option %s used as abbreviation for %s",
+ val, o->o_name);
+ Verbose = oldVerbose;
+ }
+ opt = o->o_code;
+ val = p;
+ }
+ else
+ {
+ for (o = OptionTab; o->o_name != NULL; o++)
+ {
+ if (o->o_code == opt)
+ break;
+ }
+ if (o->o_name == NULL)
+ {
+ syserr("readcf: unknown option name 0x%x", opt & 0xff);
+ return;
+ }
+ subopt = NULL;
+ }
+
+ if (subopt != NULL && !bitset(OI_SUBOPT, o->o_flags))
+ {
+ if (tTd(37, 1))
+ sm_dprintf("setoption: %s does not support suboptions, ignoring .%s\n",
+ OPTNAME, subopt);
+ subopt = NULL;
+ }
+
+ if (tTd(37, 1))
+ {
+ sm_dprintf(isascii(opt) && isprint(opt) ?
+ "setoption %s (%c)%s%s=" :
+ "setoption %s (0x%x)%s%s=",
+ OPTNAME, opt, subopt == NULL ? "" : ".",
+ subopt == NULL ? "" : subopt);
+ xputs(sm_debug_file(), val);
+ }
+
+ /*
+ ** See if this option is preset for us.
+ */
+
+ if (!sticky && bitnset(opt, StickyOpt))
+ {
+ if (tTd(37, 1))
+ sm_dprintf(" (ignored)\n");
+ return;
+ }
+
+ /*
+ ** Check to see if this option can be specified by this user.
+ */
+
+ if (!safe && RealUid == 0)
+ safe = true;
+ if (!safe && !bitset(OI_SAFE, o->o_flags))
+ {
+ if (opt != 'M' || (val[0] != 'r' && val[0] != 's'))
+ {
+ int dp;
+
+ if (tTd(37, 1))
+ sm_dprintf(" (unsafe)");
+ dp = drop_privileges(true);
+ setstat(dp);
+ }
+ }
+ if (tTd(37, 1))
+ sm_dprintf("\n");
+
+ switch (opt & 0xff)
+ {
+ case '7': /* force seven-bit input */
+ SevenBitInput = atobool(val);
+ break;
+
+ case '8': /* handling of 8-bit input */
+#if MIME8TO7
+ switch (*val)
+ {
+ case 'p': /* pass 8 bit, convert MIME */
+ MimeMode = MM_CVTMIME|MM_PASS8BIT;
+ break;
+
+ case 'm': /* convert 8-bit, convert MIME */
+ MimeMode = MM_CVTMIME|MM_MIME8BIT;
+ break;
+
+ case 's': /* strict adherence */
+ MimeMode = MM_CVTMIME;
+ break;
+
+# if 0
+ case 'r': /* reject 8-bit, don't convert MIME */
+ MimeMode = 0;
+ break;
+
+ case 'j': /* "just send 8" */
+ MimeMode = MM_PASS8BIT;
+ break;
+
+ case 'a': /* encode 8 bit if available */
+ MimeMode = MM_MIME8BIT|MM_PASS8BIT|MM_CVTMIME;
+ break;
+
+ case 'c': /* convert 8 bit to MIME, never 7 bit */
+ MimeMode = MM_MIME8BIT;
+ break;
+# endif /* 0 */
+
+ default:
+ syserr("Unknown 8-bit mode %c", *val);
+ finis(false, true, EX_USAGE);
+ }
+#else /* MIME8TO7 */
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Warning: Option: %s requires MIME8TO7 support\n",
+ OPTNAME);
+#endif /* MIME8TO7 */
+ break;
+
+ case 'A': /* set default alias file */
+ if (val[0] == '\0')
+ {
+ char *al;
+
+ SET_OPT_DEFAULT(al, "aliases");
+ setalias(al);
+ }
+ else
+ setalias(val);
+ break;
+
+ case 'a': /* look N minutes for "@:@" in alias file */
+ if (val[0] == '\0')
+ SafeAlias = 5 MINUTES;
+ else
+ SafeAlias = convtime(val, 'm');
+ break;
+
+ case 'B': /* substitution for blank character */
+ SpaceSub = val[0];
+ if (SpaceSub == '\0')
+ SpaceSub = ' ';
+ break;
+
+ case 'b': /* min blocks free on queue fs/max msg size */
+ p = strchr(val, '/');
+ if (p != NULL)
+ {
+ *p++ = '\0';
+ MaxMessageSize = atol(p);
+ }
+ MinBlocksFree = atol(val);
+ break;
+
+ case 'c': /* don't connect to "expensive" mailers */
+ NoConnect = atobool(val);
+ break;
+
+ case 'C': /* checkpoint every N addresses */
+ if (safe || CheckpointInterval > atoi(val))
+ CheckpointInterval = atoi(val);
+ break;
+
+ case 'd': /* delivery mode */
+ switch (*val)
+ {
+ case '\0':
+ set_delivery_mode(SM_DELIVER, e);
+ break;
+
+ case SM_QUEUE: /* queue only */
+ case SM_DEFER: /* queue only and defer map lookups */
+ case SM_DELIVER: /* do everything */
+ case SM_FORK: /* fork after verification */
+ set_delivery_mode(*val, e);
+ break;
+
+ default:
+ syserr("Unknown delivery mode %c", *val);
+ finis(false, true, EX_USAGE);
+ }
+ break;
+
+ case 'E': /* error message header/header file */
+ if (*val != '\0')
+ ErrMsgFile = newstr(val);
+ break;
+
+ case 'e': /* set error processing mode */
+ switch (*val)
+ {
+ case EM_QUIET: /* be silent about it */
+ case EM_MAIL: /* mail back */
+ case EM_BERKNET: /* do berknet error processing */
+ case EM_WRITE: /* write back (or mail) */
+ case EM_PRINT: /* print errors normally (default) */
+ e->e_errormode = *val;
+ break;
+ }
+ break;
+
+ case 'F': /* file mode */
+ FileMode = atooct(val) & 0777;
+ break;
+
+ case 'f': /* save Unix-style From lines on front */
+ SaveFrom = atobool(val);
+ break;
+
+ case 'G': /* match recipients against GECOS field */
+ MatchGecos = atobool(val);
+ break;
+
+ case 'g': /* default gid */
+ g_opt:
+ if (isascii(*val) && isdigit(*val))
+ DefGid = atoi(val);
+ else
+ {
+ register struct group *gr;
+
+ DefGid = -1;
+ gr = getgrnam(val);
+ if (gr == NULL)
+ syserr("readcf: option %c: unknown group %s",
+ opt, val);
+ else
+ DefGid = gr->gr_gid;
+ }
+ break;
+
+ case 'H': /* help file */
+ if (val[0] == '\0')
+ {
+ SET_OPT_DEFAULT(HelpFile, "helpfile");
+ }
+ else
+ {
+ CANONIFY(val);
+ HelpFile = newstr(val);
+ }
+ break;
+
+ case 'h': /* maximum hop count */
+ MaxHopCount = atoi(val);
+ break;
+
+ case 'I': /* use internet domain name server */
+#if NAMED_BIND
+ for (p = val; *p != 0; )
+ {
+ bool clearmode;
+ char *q;
+ struct resolverflags *rfp;
+
+ while (*p == ' ')
+ p++;
+ if (*p == '\0')
+ break;
+ clearmode = false;
+ if (*p == '-')
+ clearmode = true;
+ else if (*p != '+')
+ p--;
+ p++;
+ q = p;
+ while (*p != '\0' && !(isascii(*p) && isspace(*p)))
+ p++;
+ if (*p != '\0')
+ *p++ = '\0';
+ if (sm_strcasecmp(q, "HasWildcardMX") == 0)
+ {
+ HasWildcardMX = !clearmode;
+ continue;
+ }
+ if (sm_strcasecmp(q, "WorkAroundBrokenAAAA") == 0)
+ {
+ WorkAroundBrokenAAAA = !clearmode;
+ continue;
+ }
+ for (rfp = ResolverFlags; rfp->rf_name != NULL; rfp++)
+ {
+ if (sm_strcasecmp(q, rfp->rf_name) == 0)
+ break;
+ }
+ if (rfp->rf_name == NULL)
+ syserr("readcf: I option value %s unrecognized", q);
+ else if (clearmode)
+ _res.options &= ~rfp->rf_bits;
+ else
+ _res.options |= rfp->rf_bits;
+ }
+ if (tTd(8, 2))
+ sm_dprintf("_res.options = %x, HasWildcardMX = %d\n",
+ (unsigned int) _res.options, HasWildcardMX);
+#else /* NAMED_BIND */
+ usrerr("name server (I option) specified but BIND not compiled in");
+#endif /* NAMED_BIND */
+ break;
+
+ case 'i': /* ignore dot lines in message */
+ IgnrDot = atobool(val);
+ break;
+
+ case 'j': /* send errors in MIME (RFC 1341) format */
+ SendMIMEErrors = atobool(val);
+ break;
+
+ case 'J': /* .forward search path */
+ CANONIFY(val);
+ ForwardPath = newstr(val);
+ break;
+
+ case 'k': /* connection cache size */
+ MaxMciCache = atoi(val);
+ if (MaxMciCache < 0)
+ MaxMciCache = 0;
+ break;
+
+ case 'K': /* connection cache timeout */
+ MciCacheTimeout = convtime(val, 'm');
+ break;
+
+ case 'l': /* use Errors-To: header */
+ UseErrorsTo = atobool(val);
+ break;
+
+ case 'L': /* log level */
+ if (safe || LogLevel < atoi(val))
+ LogLevel = atoi(val);
+ break;
+
+ case 'M': /* define macro */
+ sticky = false;
+ mid = macid_parse(val, &ep);
+ if (mid == 0)
+ break;
+ p = newstr(ep);
+ if (!safe)
+ cleanstrcpy(p, p, strlen(p) + 1);
+ macdefine(&CurEnv->e_macro, A_TEMP, mid, p);
+ break;
+
+ case 'm': /* send to me too */
+ MeToo = atobool(val);
+ break;
+
+ case 'n': /* validate RHS in newaliases */
+ CheckAliases = atobool(val);
+ break;
+
+ /* 'N' available -- was "net name" */
+
+ case 'O': /* daemon options */
+ if (!setdaemonoptions(val))
+ syserr("too many daemons defined (%d max)", MAXDAEMONS);
+ break;
+
+ case 'o': /* assume old style headers */
+ if (atobool(val))
+ CurEnv->e_flags |= EF_OLDSTYLE;
+ else
+ CurEnv->e_flags &= ~EF_OLDSTYLE;
+ break;
+
+ case 'p': /* select privacy level */
+ p = val;
+ for (;;)
+ {
+ register struct prival *pv;
+ extern struct prival PrivacyValues[];
+
+ while (isascii(*p) && (isspace(*p) || ispunct(*p)))
+ p++;
+ if (*p == '\0')
+ break;
+ val = p;
+ while (isascii(*p) && isalnum(*p))
+ p++;
+ if (*p != '\0')
+ *p++ = '\0';
+
+ for (pv = PrivacyValues; pv->pv_name != NULL; pv++)
+ {
+ if (sm_strcasecmp(val, pv->pv_name) == 0)
+ break;
+ }
+ if (pv->pv_name == NULL)
+ syserr("readcf: Op line: %s unrecognized", val);
+ else
+ PrivacyFlags |= pv->pv_flag;
+ }
+ sticky = false;
+ break;
+
+ case 'P': /* postmaster copy address for returned mail */
+ PostMasterCopy = newstr(val);
+ break;
+
+ case 'q': /* slope of queue only function */
+ QueueFactor = atoi(val);
+ break;
+
+ case 'Q': /* queue directory */
+ if (val[0] == '\0')
+ {
+ QueueDir = "mqueue";
+ }
+ else
+ {
+ QueueDir = newstr(val);
+ }
+ if (RealUid != 0 && !safe)
+ Warn_Q_option = true;
+ break;
+
+ case 'R': /* don't prune routes */
+ DontPruneRoutes = atobool(val);
+ break;
+
+ case 'r': /* read timeout */
+ if (subopt == NULL)
+ inittimeouts(val, sticky);
+ else
+ settimeout(subopt, val, sticky);
+ break;
+
+ case 'S': /* status file */
+ if (val[0] == '\0')
+ {
+ SET_OPT_DEFAULT(StatFile, "statistics");
+ }
+ else
+ {
+ CANONIFY(val);
+ StatFile = newstr(val);
+ }
+ break;
+
+ case 's': /* be super safe, even if expensive */
+ if (tolower(*val) == 'i')
+ SuperSafe = SAFE_INTERACTIVE;
+ else if (tolower(*val) == 'p')
+#if MILTER
+ SuperSafe = SAFE_REALLY_POSTMILTER;
+#else /* MILTER */
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Warning: SuperSafe=PostMilter requires Milter support (-DMILTER)\n");
+#endif /* MILTER */
+ else
+ SuperSafe = atobool(val) ? SAFE_REALLY : SAFE_NO;
+ break;
+
+ case 'T': /* queue timeout */
+ p = strchr(val, '/');
+ if (p != NULL)
+ {
+ *p++ = '\0';
+ settimeout("queuewarn", p, sticky);
+ }
+ settimeout("queuereturn", val, sticky);
+ break;
+
+ case 't': /* time zone name */
+ TimeZoneSpec = newstr(val);
+ break;
+
+ case 'U': /* location of user database */
+ UdbSpec = newstr(val);
+ break;
+
+ case 'u': /* set default uid */
+ for (p = val; *p != '\0'; p++)
+ {
+# if _FFR_DOTTED_USERNAMES
+ if (*p == '/' || *p == ':')
+# else /* _FFR_DOTTED_USERNAMES */
+ if (*p == '.' || *p == '/' || *p == ':')
+# endif /* _FFR_DOTTED_USERNAMES */
+ {
+ *p++ = '\0';
+ break;
+ }
+ }
+ if (isascii(*val) && isdigit(*val))
+ {
+ DefUid = atoi(val);
+ setdefuser();
+ }
+ else
+ {
+ register struct passwd *pw;
+
+ DefUid = -1;
+ pw = sm_getpwnam(val);
+ if (pw == NULL)
+ {
+ syserr("readcf: option u: unknown user %s", val);
+ break;
+ }
+ else
+ {
+ DefUid = pw->pw_uid;
+ DefGid = pw->pw_gid;
+ DefUser = newstr(pw->pw_name);
+ }
+ }
+
+# ifdef UID_MAX
+ if (DefUid > UID_MAX)
+ {
+ syserr("readcf: option u: uid value (%ld) > UID_MAX (%ld); ignored",
+ (long)DefUid, (long)UID_MAX);
+ break;
+ }
+# endif /* UID_MAX */
+
+ /* handle the group if it is there */
+ if (*p == '\0')
+ break;
+ val = p;
+ goto g_opt;
+
+ case 'V': /* fallback MX host */
+ if (val[0] != '\0')
+ FallbackMX = newstr(val);
+ break;
+
+ case 'v': /* run in verbose mode */
+ Verbose = atobool(val) ? 1 : 0;
+ break;
+
+ case 'w': /* if we are best MX, try host directly */
+ TryNullMXList = atobool(val);
+ break;
+
+ /* 'W' available -- was wizard password */
+
+ case 'x': /* load avg at which to auto-queue msgs */
+ QueueLA = atoi(val);
+ break;
+
+ case 'X': /* load avg at which to auto-reject connections */
+ RefuseLA = atoi(val);
+ break;
+
+ case O_DELAY_LA: /* load avg at which to delay connections */
+ DelayLA = atoi(val);
+ break;
+
+ case 'y': /* work recipient factor */
+ WkRecipFact = atoi(val);
+ break;
+
+ case 'Y': /* fork jobs during queue runs */
+ ForkQueueRuns = atobool(val);
+ break;
+
+ case 'z': /* work message class factor */
+ WkClassFact = atoi(val);
+ break;
+
+ case 'Z': /* work time factor */
+ WkTimeFact = atoi(val);
+ break;
+
+
+#if _FFR_QUEUE_GROUP_SORTORDER
+ /* coordinate this with makequeue() */
+#endif /* _FFR_QUEUE_GROUP_SORTORDER */
+ case O_QUEUESORTORD: /* queue sorting order */
+ switch (*val)
+ {
+ case 'f': /* File Name */
+ case 'F':
+ QueueSortOrder = QSO_BYFILENAME;
+ break;
+
+ case 'h': /* Host first */
+ case 'H':
+ QueueSortOrder = QSO_BYHOST;
+ break;
+
+ case 'm': /* Modification time */
+ case 'M':
+ QueueSortOrder = QSO_BYMODTIME;
+ break;
+
+ case 'p': /* Priority order */
+ case 'P':
+ QueueSortOrder = QSO_BYPRIORITY;
+ break;
+
+ case 't': /* Submission time */
+ case 'T':
+ QueueSortOrder = QSO_BYTIME;
+ break;
+
+ case 'r': /* Random */
+ case 'R':
+ QueueSortOrder = QSO_RANDOM;
+ break;
+
+#if _FFR_RHS
+ case 's': /* Shuffled host name */
+ case 'S':
+ QueueSortOrder = QSO_BYSHUFFLE;
+ break;
+#endif /* _FFR_RHS */
+
+ case 'n': /* none */
+ case 'N':
+ QueueSortOrder = QSO_NONE;
+ break;
+
+ default:
+ syserr("Invalid queue sort order \"%s\"", val);
+ }
+ break;
+
+ case O_HOSTSFILE: /* pathname of /etc/hosts file */
+ CANONIFY(val);
+ HostsFile = newstr(val);
+ break;
+
+ case O_MQA: /* minimum queue age between deliveries */
+ MinQueueAge = convtime(val, 'm');
+ break;
+
+ case O_DEFCHARSET: /* default character set for mimefying */
+ DefaultCharSet = newstr(denlstring(val, true, true));
+ break;
+
+ case O_SSFILE: /* service switch file */
+ CANONIFY(val);
+ ServiceSwitchFile = newstr(val);
+ break;
+
+ case O_DIALDELAY: /* delay for dial-on-demand operation */
+ DialDelay = convtime(val, 's');
+ break;
+
+ case O_NORCPTACTION: /* what to do if no recipient */
+ if (sm_strcasecmp(val, "none") == 0)
+ NoRecipientAction = NRA_NO_ACTION;
+ else if (sm_strcasecmp(val, "add-to") == 0)
+ NoRecipientAction = NRA_ADD_TO;
+ else if (sm_strcasecmp(val, "add-apparently-to") == 0)
+ NoRecipientAction = NRA_ADD_APPARENTLY_TO;
+ else if (sm_strcasecmp(val, "add-bcc") == 0)
+ NoRecipientAction = NRA_ADD_BCC;
+ else if (sm_strcasecmp(val, "add-to-undisclosed") == 0)
+ NoRecipientAction = NRA_ADD_TO_UNDISCLOSED;
+ else
+ syserr("Invalid NoRecipientAction: %s", val);
+ break;
+
+ case O_SAFEFILEENV: /* chroot() environ for writing to files */
+ if (*val == '\0')
+ break;
+
+ /* strip trailing slashes */
+ p = val + strlen(val) - 1;
+ while (p >= val && *p == '/')
+ *p-- = '\0';
+
+ if (*val == '\0')
+ break;
+
+ SafeFileEnv = newstr(val);
+ break;
+
+ case O_MAXMSGSIZE: /* maximum message size */
+ MaxMessageSize = atol(val);
+ break;
+
+ case O_COLONOKINADDR: /* old style handling of colon addresses */
+ ColonOkInAddr = atobool(val);
+ break;
+
+ case O_MAXQUEUERUN: /* max # of jobs in a single queue run */
+ MaxQueueRun = atoi(val);
+ break;
+
+ case O_MAXCHILDREN: /* max # of children of daemon */
+ MaxChildren = atoi(val);
+ break;
+
+ case O_MAXQUEUECHILDREN: /* max # of children of daemon */
+ MaxQueueChildren = atoi(val);
+ break;
+
+ case O_MAXRUNNERSPERQUEUE: /* max # runners in a queue group */
+ MaxRunnersPerQueue = atoi(val);
+ break;
+
+ case O_NICEQUEUERUN: /* nice queue runs */
+#if !HASNICE
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Warning: NiceQueueRun set on system that doesn't support nice()\n");
+#endif /* !HASNICE */
+
+ /* XXX do we want to check the range? > 0 ? */
+ NiceQueueRun = atoi(val);
+ break;
+
+ case O_SHMKEY: /* shared memory key */
+#if SM_CONF_SHM
+ ShmKey = atol(val);
+#else /* SM_CONF_SHM */
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Warning: Option: %s requires shared memory support (-DSM_CONF_SHM)\n",
+ OPTNAME);
+#endif /* SM_CONF_SHM */
+ break;
+
+#if _FFR_SELECT_SHM
+ case O_SHMKEYFILE: /* shared memory key file */
+# if SM_CONF_SHM
+ SET_STRING_EXP(ShmKeyFile);
+# else /* SM_CONF_SHM */
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Warning: Option: %s requires shared memory support (-DSM_CONF_SHM)\n",
+ OPTNAME);
+ break;
+# endif /* SM_CONF_SHM */
+#endif /* _FFR_SELECT_SHM */
+
+#if _FFR_MAX_FORWARD_ENTRIES
+ case O_MAXFORWARD: /* max # of forward entries */
+ MaxForwardEntries = atoi(val);
+ break;
+#endif /* _FFR_MAX_FORWARD_ENTRIES */
+
+ case O_KEEPCNAMES: /* don't expand CNAME records */
+ DontExpandCnames = atobool(val);
+ break;
+
+ case O_MUSTQUOTE: /* must quote these characters in phrases */
+ (void) sm_strlcpy(buf, "@,;:\\()[]", sizeof buf);
+ if (strlen(val) < sizeof buf - 10)
+ (void) sm_strlcat(buf, val, sizeof buf);
+ else
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Warning: MustQuoteChars too long, ignored.\n");
+ MustQuoteChars = newstr(buf);
+ break;
+
+ case O_SMTPGREETING: /* SMTP greeting message (old $e macro) */
+ SmtpGreeting = newstr(munchstring(val, NULL, '\0'));
+ break;
+
+ case O_UNIXFROM: /* UNIX From_ line (old $l macro) */
+ UnixFromLine = newstr(munchstring(val, NULL, '\0'));
+ break;
+
+ case O_OPCHARS: /* operator characters (old $o macro) */
+ if (OperatorChars != NULL)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Warning: OperatorChars is being redefined.\n It should only be set before ruleset definitions.\n");
+ OperatorChars = newstr(munchstring(val, NULL, '\0'));
+ break;
+
+ case O_DONTINITGRPS: /* don't call initgroups(3) */
+ DontInitGroups = atobool(val);
+ break;
+
+ case O_SLFH: /* make sure from fits on one line */
+ SingleLineFromHeader = atobool(val);
+ break;
+
+ case O_ABH: /* allow HELO commands with syntax errors */
+ AllowBogusHELO = atobool(val);
+ break;
+
+ case O_CONNTHROT: /* connection rate throttle */
+ ConnRateThrottle = atoi(val);
+ break;
+
+ case O_UGW: /* group writable files are unsafe */
+ if (!atobool(val))
+ {
+ setbitn(DBS_GROUPWRITABLEFORWARDFILESAFE,
+ DontBlameSendmail);
+ setbitn(DBS_GROUPWRITABLEINCLUDEFILESAFE,
+ DontBlameSendmail);
+ }
+ break;
+
+ case O_DBLBOUNCE: /* address to which to send double bounces */
+ DoubleBounceAddr = newstr(val);
+ break;
+
+ case O_HSDIR: /* persistent host status directory */
+ if (val[0] != '\0')
+ {
+ CANONIFY(val);
+ HostStatDir = newstr(val);
+ }
+ break;
+
+ case O_SINGTHREAD: /* single thread deliveries (requires hsdir) */
+ SingleThreadDelivery = atobool(val);
+ break;
+
+ case O_RUNASUSER: /* run bulk of code as this user */
+ for (p = val; *p != '\0'; p++)
+ {
+# if _FFR_DOTTED_USERNAMES
+ if (*p == '/' || *p == ':')
+# else /* _FFR_DOTTED_USERNAMES */
+ if (*p == '.' || *p == '/' || *p == ':')
+# endif /* _FFR_DOTTED_USERNAMES */
+ {
+ *p++ = '\0';
+ break;
+ }
+ }
+ if (isascii(*val) && isdigit(*val))
+ {
+ if (can_setuid)
+ RunAsUid = atoi(val);
+ }
+ else
+ {
+ register struct passwd *pw;
+
+ pw = sm_getpwnam(val);
+ if (pw == NULL)
+ {
+ syserr("readcf: option RunAsUser: unknown user %s", val);
+ break;
+ }
+ else if (can_setuid)
+ {
+ if (*p == '\0')
+ RunAsUserName = newstr(val);
+ RunAsUid = pw->pw_uid;
+ RunAsGid = pw->pw_gid;
+ }
+ else if (EffGid == pw->pw_gid)
+ RunAsGid = pw->pw_gid;
+ else if (UseMSP && *p == '\0')
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "WARNING: RunAsUser for MSP ignored, check group ids (egid=%d, want=%d)\n",
+ (int) EffGid,
+ (int) pw->pw_gid);
+ }
+# ifdef UID_MAX
+ if (RunAsUid > UID_MAX)
+ {
+ syserr("readcf: option RunAsUser: uid value (%ld) > UID_MAX (%ld); ignored",
+ (long) RunAsUid, (long) UID_MAX);
+ break;
+ }
+# endif /* UID_MAX */
+ if (*p != '\0')
+ {
+ if (isascii(*p) && isdigit(*p))
+ {
+ gid_t runasgid;
+
+ runasgid = (gid_t) atoi(p);
+ if (can_setuid || EffGid == runasgid)
+ RunAsGid = runasgid;
+ else if (UseMSP)
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT,
+ "WARNING: RunAsUser for MSP ignored, check group ids (egid=%d, want=%d)\n",
+ (int) EffGid,
+ (int) runasgid);
+ }
+ else
+ {
+ register struct group *gr;
+
+ gr = getgrnam(p);
+ if (gr == NULL)
+ syserr("readcf: option RunAsUser: unknown group %s",
+ p);
+ else if (can_setuid || EffGid == gr->gr_gid)
+ RunAsGid = gr->gr_gid;
+ else if (UseMSP)
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT,
+ "WARNING: RunAsUser for MSP ignored, check group ids (egid=%d, want=%d)\n",
+ (int) EffGid,
+ (int) gr->gr_gid);
+ }
+ }
+ if (tTd(47, 5))
+ sm_dprintf("readcf: RunAsUser = %d:%d\n",
+ (int) RunAsUid, (int) RunAsGid);
+ break;
+
+ case O_DSN_RRT:
+ RrtImpliesDsn = atobool(val);
+ break;
+
+ case O_PIDFILE:
+ PSTRSET(PidFile, val);
+ break;
+
+ case O_DONTBLAMESENDMAIL:
+ p = val;
+ for (;;)
+ {
+ register struct dbsval *dbs;
+ extern struct dbsval DontBlameSendmailValues[];
+
+ while (isascii(*p) && (isspace(*p) || ispunct(*p)))
+ p++;
+ if (*p == '\0')
+ break;
+ val = p;
+ while (isascii(*p) && isalnum(*p))
+ p++;
+ if (*p != '\0')
+ *p++ = '\0';
+
+ for (dbs = DontBlameSendmailValues;
+ dbs->dbs_name != NULL; dbs++)
+ {
+ if (sm_strcasecmp(val, dbs->dbs_name) == 0)
+ break;
+ }
+ if (dbs->dbs_name == NULL)
+ syserr("readcf: DontBlameSendmail option: %s unrecognized", val);
+ else if (dbs->dbs_flag == DBS_SAFE)
+ clrbitmap(DontBlameSendmail);
+ else
+ setbitn(dbs->dbs_flag, DontBlameSendmail);
+ }
+ sticky = false;
+ break;
+
+ case O_DPI:
+ if (sm_strcasecmp(val, "loopback") == 0)
+ DontProbeInterfaces = DPI_SKIPLOOPBACK;
+ else if (atobool(val))
+ DontProbeInterfaces = DPI_PROBENONE;
+ else
+ DontProbeInterfaces = DPI_PROBEALL;
+ break;
+
+ case O_MAXRCPT:
+ MaxRcptPerMsg = atoi(val);
+ break;
+
+ case O_RCPTTHROT:
+ BadRcptThrottle = atoi(val);
+ break;
+
+ case O_DEADLETTER:
+ CANONIFY(val);
+ PSTRSET(DeadLetterDrop, val);
+ break;
+
+#if _FFR_DONTLOCKFILESFORREAD_OPTION
+ case O_DONTLOCK:
+ DontLockReadFiles = atobool(val);
+ break;
+#endif /* _FFR_DONTLOCKFILESFORREAD_OPTION */
+
+ case O_MAXALIASRCSN:
+ MaxAliasRecursion = atoi(val);
+ break;
+
+ case O_CNCTONLYTO:
+ /* XXX should probably use gethostbyname */
+#if NETINET || NETINET6
+ ConnectOnlyTo.sa.sa_family = AF_UNSPEC;
+# if NETINET6
+ if (anynet_pton(AF_INET6, val,
+ &ConnectOnlyTo.sin6.sin6_addr) != 1)
+ ConnectOnlyTo.sa.sa_family = AF_INET6;
+ else
+# endif /* NETINET6 */
+# if NETINET
+ {
+ ConnectOnlyTo.sin.sin_addr.s_addr = inet_addr(val);
+ if (ConnectOnlyTo.sin.sin_addr.s_addr != INADDR_NONE)
+ ConnectOnlyTo.sa.sa_family = AF_INET;
+ }
+
+# endif /* NETINET */
+ if (ConnectOnlyTo.sa.sa_family == AF_UNSPEC)
+ {
+ syserr("readcf: option ConnectOnlyTo: invalid IP address %s",
+ val);
+ break;
+ }
+#endif /* NETINET || NETINET6 */
+ break;
+
+ case O_TRUSTUSER:
+# if !HASFCHOWN && !defined(_FFR_DROP_TRUSTUSER_WARNING)
+ if (!UseMSP)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "readcf: option TrustedUser may cause problems on systems\n which do not support fchown() if UseMSP is not set.\n");
+# endif /* !HASFCHOWN && !defined(_FFR_DROP_TRUSTUSER_WARNING) */
+ if (isascii(*val) && isdigit(*val))
+ TrustedUid = atoi(val);
+ else
+ {
+ register struct passwd *pw;
+
+ TrustedUid = 0;
+ pw = sm_getpwnam(val);
+ if (pw == NULL)
+ {
+ syserr("readcf: option TrustedUser: unknown user %s", val);
+ break;
+ }
+ else
+ TrustedUid = pw->pw_uid;
+ }
+
+# ifdef UID_MAX
+ if (TrustedUid > UID_MAX)
+ {
+ syserr("readcf: option TrustedUser: uid value (%ld) > UID_MAX (%ld)",
+ (long) TrustedUid, (long) UID_MAX);
+ TrustedUid = 0;
+ }
+# endif /* UID_MAX */
+ break;
+
+ case O_MAXMIMEHDRLEN:
+ p = strchr(val, '/');
+ if (p != NULL)
+ *p++ = '\0';
+ MaxMimeHeaderLength = atoi(val);
+ if (p != NULL && *p != '\0')
+ MaxMimeFieldLength = atoi(p);
+ else
+ MaxMimeFieldLength = MaxMimeHeaderLength / 2;
+
+ if (MaxMimeHeaderLength <= 0)
+ MaxMimeHeaderLength = 0;
+ else if (MaxMimeHeaderLength < 128)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Warning: MaxMimeHeaderLength: header length limit set lower than 128\n");
+
+ if (MaxMimeFieldLength <= 0)
+ MaxMimeFieldLength = 0;
+ else if (MaxMimeFieldLength < 40)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Warning: MaxMimeHeaderLength: field length limit set lower than 40\n");
+ break;
+
+ case O_CONTROLSOCKET:
+ PSTRSET(ControlSocketName, val);
+ break;
+
+ case O_MAXHDRSLEN:
+ MaxHeadersLength = atoi(val);
+
+ if (MaxHeadersLength > 0 &&
+ MaxHeadersLength < (MAXHDRSLEN / 2))
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Warning: MaxHeadersLength: headers length limit set lower than %d\n",
+ (MAXHDRSLEN / 2));
+ break;
+
+ case O_PROCTITLEPREFIX:
+ PSTRSET(ProcTitlePrefix, val);
+ break;
+
+#if SASL
+ case O_SASLINFO:
+# if _FFR_ALLOW_SASLINFO
+ /*
+ ** Allow users to select their own authinfo file
+ ** under certain circumstances, otherwise just ignore
+ ** the option. If the option isn't ignored, several
+ ** commands don't work very well, e.g., mailq.
+ ** However, this is not a "perfect" solution.
+ ** If mail is queued, the authentication info
+ ** will not be used in subsequent delivery attempts.
+ ** If we really want to support this, then it has
+ ** to be stored in the queue file.
+ */
+ if (!bitset(SUBMIT_MSA, SubmitMode) && RealUid != 0 &&
+ RunAsUid != RealUid)
+ break;
+# endif /* _FFR_ALLOW_SASLINFO */
+ PSTRSET(SASLInfo, val);
+ break;
+
+ case O_SASLMECH:
+ if (AuthMechanisms != NULL)
+ sm_free(AuthMechanisms); /* XXX */
+ if (*val != '\0')
+ AuthMechanisms = newstr(val);
+ else
+ AuthMechanisms = NULL;
+ break;
+
+ case O_SASLREALM:
+ if (AuthRealm != NULL)
+ sm_free(AuthRealm);
+ if (*val != '\0')
+ AuthRealm = newstr(val);
+ else
+ AuthRealm = NULL;
+ break;
+
+ case O_SASLOPTS:
+ while (val != NULL && *val != '\0')
+ {
+ switch (*val)
+ {
+ case 'A':
+ SASLOpts |= SASL_AUTH_AUTH;
+ break;
+
+ case 'a':
+ SASLOpts |= SASL_SEC_NOACTIVE;
+ break;
+
+ case 'c':
+ SASLOpts |= SASL_SEC_PASS_CREDENTIALS;
+ break;
+
+ case 'd':
+ SASLOpts |= SASL_SEC_NODICTIONARY;
+ break;
+
+ case 'f':
+ SASLOpts |= SASL_SEC_FORWARD_SECRECY;
+ break;
+
+# if SASL >= 20101
+ case 'm':
+ SASLOpts |= SASL_SEC_MUTUAL_AUTH;
+ break;
+# endif /* SASL >= 20101 */
+
+ case 'p':
+ SASLOpts |= SASL_SEC_NOPLAINTEXT;
+ break;
+
+ case 'y':
+ SASLOpts |= SASL_SEC_NOANONYMOUS;
+ break;
+
+ case ' ': /* ignore */
+ case '\t': /* ignore */
+ case ',': /* ignore */
+ break;
+
+ default:
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Warning: Option: %s unknown parameter '%c'\n",
+ OPTNAME,
+ (isascii(*val) &&
+ isprint(*val))
+ ? *val : '?');
+ break;
+ }
+ ++val;
+ val = strpbrk(val, ", \t");
+ if (val != NULL)
+ ++val;
+ }
+ break;
+
+ case O_SASLBITS:
+ MaxSLBits = atoi(val);
+ break;
+
+#else /* SASL */
+ case O_SASLINFO:
+ case O_SASLMECH:
+ case O_SASLREALM:
+ case O_SASLOPTS:
+ case O_SASLBITS:
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Warning: Option: %s requires SASL support (-DSASL)\n",
+ OPTNAME);
+ break;
+#endif /* SASL */
+
+#if STARTTLS
+ case O_SRVCERTFILE:
+ SET_STRING_EXP(SrvCertFile);
+ case O_SRVKEYFILE:
+ SET_STRING_EXP(SrvKeyFile);
+ case O_CLTCERTFILE:
+ SET_STRING_EXP(CltCertFile);
+ case O_CLTKEYFILE:
+ SET_STRING_EXP(CltKeyFile);
+ case O_CACERTFILE:
+ SET_STRING_EXP(CACertFile);
+ case O_CACERTPATH:
+ SET_STRING_EXP(CACertPath);
+ case O_DHPARAMS:
+ SET_STRING_EXP(DHParams);
+# if _FFR_TLS_1
+ case O_DHPARAMS5:
+ SET_STRING_EXP(DHParams5);
+ case O_CIPHERLIST:
+ SET_STRING_EXP(CipherList);
+# endif /* _FFR_TLS_1 */
+ case O_CRLFILE:
+# if OPENSSL_VERSION_NUMBER > 0x00907000L
+ SET_STRING_EXP(CRLFile);
+# else /* OPENSSL_VERSION_NUMBER > 0x00907000L */
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Warning: Option: %s requires at least OpenSSL 0.9.7\n",
+ OPTNAME);
+ break;
+# endif /* OPENSSL_VERSION_NUMBER > 0x00907000L */
+
+# if _FFR_CRLPATH
+ case O_CRLPATH:
+# if OPENSSL_VERSION_NUMBER > 0x00907000L
+ SET_STRING_EXP(CRLPath);
+# else /* OPENSSL_VERSION_NUMBER > 0x00907000L */
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Warning: Option: %s requires at least OpenSSL 0.9.7\n",
+ OPTNAME);
+ break;
+# endif /* OPENSSL_VERSION_NUMBER > 0x00907000L */
+# endif /* _FFR_CRLPATH */
+
+ /*
+ ** XXX How about options per daemon/client instead of globally?
+ ** This doesn't work well for some options, e.g., no server cert,
+ ** but fine for others.
+ **
+ ** XXX Some people may want different certs per server.
+ **
+ ** See also srvfeatures()
+ */
+
+ case O_TLS_SRV_OPTS:
+ while (val != NULL && *val != '\0')
+ {
+ switch (*val)
+ {
+ case 'V':
+ TLS_Srv_Opts |= TLS_I_NO_VRFY;
+ break;
+# if _FFR_TLS_1
+ /*
+ ** Server without a cert? That works only if
+ ** AnonDH is enabled as cipher, which is not in the
+ ** default list. Hence the CipherList option must
+ ** be available. Moreover: which clients support this
+ ** besides sendmail with this setting?
+ */
+
+ case 'C':
+ TLS_Srv_Opts &= ~TLS_I_SRV_CERT;
+ break;
+# endif /* _FFR_TLS_1 */
+ case ' ': /* ignore */
+ case '\t': /* ignore */
+ case ',': /* ignore */
+ break;
+ default:
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Warning: Option: %s unknown parameter '%c'\n",
+ OPTNAME,
+ (isascii(*val) &&
+ isprint(*val))
+ ? *val : '?');
+ break;
+ }
+ ++val;
+ val = strpbrk(val, ", \t");
+ if (val != NULL)
+ ++val;
+ }
+ break;
+
+ case O_RANDFILE:
+ PSTRSET(RandFile, val);
+ break;
+
+#else /* STARTTLS */
+ case O_SRVCERTFILE:
+ case O_SRVKEYFILE:
+ case O_CLTCERTFILE:
+ case O_CLTKEYFILE:
+ case O_CACERTFILE:
+ case O_CACERTPATH:
+ case O_DHPARAMS:
+# if _FFR_TLS_1
+ case O_DHPARAMS5:
+ case O_CIPHERLIST:
+# endif /* _FFR_TLS_1 */
+ case O_CRLFILE:
+# if _FFR_CRLPATH
+ case O_CRLPATH:
+# endif /* _FFR_CRLPATH */
+ case O_RANDFILE:
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Warning: Option: %s requires TLS support\n",
+ OPTNAME);
+ break;
+
+#endif /* STARTTLS */
+
+ case O_CLIENTPORT:
+ setclientoptions(val);
+ break;
+
+ case O_DF_BUFSIZE:
+ DataFileBufferSize = atoi(val);
+ break;
+
+ case O_XF_BUFSIZE:
+ XscriptFileBufferSize = atoi(val);
+ break;
+
+ case O_LDAPDEFAULTSPEC:
+#if LDAPMAP
+ ldapmap_set_defaults(val);
+#else /* LDAPMAP */
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Warning: Option: %s requires LDAP support (-DLDAPMAP)\n",
+ OPTNAME);
+#endif /* LDAPMAP */
+ break;
+
+ case O_INPUTMILTER:
+#if MILTER
+ InputFilterList = newstr(val);
+#else /* MILTER */
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Warning: Option: %s requires Milter support (-DMILTER)\n",
+ OPTNAME);
+#endif /* MILTER */
+ break;
+
+ case O_MILTER:
+#if MILTER
+ milter_set_option(subopt, val, sticky);
+#else /* MILTER */
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Warning: Option: %s requires Milter support (-DMILTER)\n",
+ OPTNAME);
+#endif /* MILTER */
+ break;
+
+ case O_QUEUE_FILE_MODE: /* queue file mode */
+ QueueFileMode = atooct(val) & 0777;
+ break;
+
+ case O_DLVR_MIN: /* deliver by minimum time */
+ DeliverByMin = convtime(val, 's');
+ break;
+
+ /* modifiers {daemon_flags} for direct submissions */
+ case O_DIRECTSUBMODIFIERS:
+ {
+ BITMAP256 m; /* ignored */
+ extern ENVELOPE BlankEnvelope;
+
+ macdefine(&BlankEnvelope.e_macro, A_PERM,
+ macid("{daemon_flags}"),
+ getmodifiers(val, m));
+ }
+ break;
+
+ case O_FASTSPLIT:
+ FastSplit = atoi(val);
+ break;
+
+ case O_MBDB:
+ Mbdb = newstr(val);
+ break;
+
+ case O_MSQ:
+ UseMSP = atobool(val);
+ break;
+
+#if _FFR_SOFT_BOUNCE
+ case O_SOFTBOUNCE:
+ SoftBounce = atobool(val);
+ break;
+#endif /* _FFR_SOFT_BOUNCE */
+
+ case O_REJECTLOGINTERVAL: /* time btwn log msgs while refusing */
+ RejectLogInterval = convtime(val, 'h');
+ break;
+
+ case O_REQUIRES_DIR_FSYNC:
+#if REQUIRES_DIR_FSYNC
+ RequiresDirfsync = atobool(val);
+#else /* REQUIRES_DIR_FSYNC */
+ /* silently ignored... required for cf file option */
+#endif /* REQUIRES_DIR_FSYNC */
+ break;
+
+ case O_CONNECTION_RATE_WINDOW_SIZE:
+ ConnectionRateWindowSize = convtime(val, 's');
+ break;
+
+ case O_FALLBACKSMARTHOST: /* fallback smart host */
+ if (val[0] != '\0')
+ FallbackSmartHost = newstr(val);
+ break;
+
+#if _FFR_HELONAME
+ case O_HELONAME:
+ HeloName = newstr(val);
+ break;
+#endif /* _FFR_HELONAME */
+
+ default:
+ if (tTd(37, 1))
+ {
+ if (isascii(opt) && isprint(opt))
+ sm_dprintf("Warning: option %c unknown\n", opt);
+ else
+ sm_dprintf("Warning: option 0x%x unknown\n", opt);
+ }
+ break;
+ }
+
+ /*
+ ** Options with suboptions are responsible for taking care
+ ** of sticky-ness (e.g., that a command line setting is kept
+ ** when reading in the sendmail.cf file). This has to be done
+ ** when the suboptions are parsed since each suboption must be
+ ** sticky, not the root option.
+ */
+
+ if (sticky && !bitset(OI_SUBOPT, o->o_flags))
+ setbitn(opt, StickyOpt);
+}
+/*
+** SETCLASS -- set a string into a class
+**
+** Parameters:
+** class -- the class to put the string in.
+** str -- the string to enter
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** puts the word into the symbol table.
+*/
+
+void
+setclass(class, str)
+ int class;
+ char *str;
+{
+ register STAB *s;
+
+ if ((*str & 0377) == MATCHCLASS)
+ {
+ int mid;
+
+ str++;
+ mid = macid(str);
+ if (mid == 0)
+ return;
+
+ if (tTd(37, 8))
+ sm_dprintf("setclass(%s, $=%s)\n",
+ macname(class), macname(mid));
+ copy_class(mid, class);
+ }
+ else
+ {
+ if (tTd(37, 8))
+ sm_dprintf("setclass(%s, %s)\n", macname(class), str);
+
+ s = stab(str, ST_CLASS, ST_ENTER);
+ setbitn(bitidx(class), s->s_class);
+ }
+}
+/*
+** MAKEMAPENTRY -- create a map entry
+**
+** Parameters:
+** line -- the config file line
+**
+** Returns:
+** A pointer to the map that has been created.
+** NULL if there was a syntax error.
+**
+** Side Effects:
+** Enters the map into the dictionary.
+*/
+
+MAP *
+makemapentry(line)
+ char *line;
+{
+ register char *p;
+ char *mapname;
+ char *classname;
+ register STAB *s;
+ STAB *class;
+
+ for (p = line; isascii(*p) && isspace(*p); p++)
+ continue;
+ if (!(isascii(*p) && isalnum(*p)))
+ {
+ syserr("readcf: config K line: no map name");
+ return NULL;
+ }
+
+ mapname = p;
+ while ((isascii(*++p) && isalnum(*p)) || *p == '_' || *p == '.')
+ continue;
+ if (*p != '\0')
+ *p++ = '\0';
+ while (isascii(*p) && isspace(*p))
+ p++;
+ if (!(isascii(*p) && isalnum(*p)))
+ {
+ syserr("readcf: config K line, map %s: no map class", mapname);
+ return NULL;
+ }
+ classname = p;
+ while (isascii(*++p) && isalnum(*p))
+ continue;
+ if (*p != '\0')
+ *p++ = '\0';
+ while (isascii(*p) && isspace(*p))
+ p++;
+
+ /* look up the class */
+ class = stab(classname, ST_MAPCLASS, ST_FIND);
+ if (class == NULL)
+ {
+ syserr("readcf: map %s: class %s not available", mapname,
+ classname);
+ return NULL;
+ }
+
+ /* enter the map */
+ s = stab(mapname, ST_MAP, ST_ENTER);
+ s->s_map.map_class = &class->s_mapclass;
+ s->s_map.map_mname = newstr(mapname);
+
+ if (class->s_mapclass.map_parse(&s->s_map, p))
+ s->s_map.map_mflags |= MF_VALID;
+
+ if (tTd(37, 5))
+ {
+ sm_dprintf("map %s, class %s, flags %lx, file %s,\n",
+ s->s_map.map_mname, s->s_map.map_class->map_cname,
+ s->s_map.map_mflags, s->s_map.map_file);
+ sm_dprintf("\tapp %s, domain %s, rebuild %s\n",
+ s->s_map.map_app, s->s_map.map_domain,
+ s->s_map.map_rebuild);
+ }
+ return &s->s_map;
+}
+/*
+** STRTORWSET -- convert string to rewriting set number
+**
+** Parameters:
+** p -- the pointer to the string to decode.
+** endp -- if set, store the trailing delimiter here.
+** stabmode -- ST_ENTER to create this entry, ST_FIND if
+** it must already exist.
+**
+** Returns:
+** The appropriate ruleset number.
+** -1 if it is not valid (error already printed)
+*/
+
+int
+strtorwset(p, endp, stabmode)
+ char *p;
+ char **endp;
+ int stabmode;
+{
+ int ruleset;
+ static int nextruleset = MAXRWSETS;
+
+ while (isascii(*p) && isspace(*p))
+ p++;
+ if (!isascii(*p))
+ {
+ syserr("invalid ruleset name: \"%.20s\"", p);
+ return -1;
+ }
+ if (isdigit(*p))
+ {
+ ruleset = strtol(p, endp, 10);
+ if (ruleset >= MAXRWSETS / 2 || ruleset < 0)
+ {
+ syserr("bad ruleset %d (%d max)",
+ ruleset, MAXRWSETS / 2);
+ ruleset = -1;
+ }
+ }
+ else
+ {
+ STAB *s;
+ char delim;
+ char *q = NULL;
+
+ q = p;
+ while (*p != '\0' && isascii(*p) &&
+ (isalnum(*p) || *p == '_'))
+ p++;
+ if (q == p || !(isascii(*q) && isalpha(*q)))
+ {
+ /* no valid characters */
+ syserr("invalid ruleset name: \"%.20s\"", q);
+ return -1;
+ }
+ while (isascii(*p) && isspace(*p))
+ *p++ = '\0';
+ delim = *p;
+ if (delim != '\0')
+ *p = '\0';
+ s = stab(q, ST_RULESET, stabmode);
+ if (delim != '\0')
+ *p = delim;
+
+ if (s == NULL)
+ return -1;
+
+ if (stabmode == ST_ENTER && delim == '=')
+ {
+ while (isascii(*++p) && isspace(*p))
+ continue;
+ if (!(isascii(*p) && isdigit(*p)))
+ {
+ syserr("bad ruleset definition \"%s\" (number required after `=')", q);
+ ruleset = -1;
+ }
+ else
+ {
+ ruleset = strtol(p, endp, 10);
+ if (ruleset >= MAXRWSETS / 2 || ruleset < 0)
+ {
+ syserr("bad ruleset number %d in \"%s\" (%d max)",
+ ruleset, q, MAXRWSETS / 2);
+ ruleset = -1;
+ }
+ }
+ }
+ else
+ {
+ if (endp != NULL)
+ *endp = p;
+ if (s->s_ruleset >= 0)
+ ruleset = s->s_ruleset;
+ else if ((ruleset = --nextruleset) < MAXRWSETS / 2)
+ {
+ syserr("%s: too many named rulesets (%d max)",
+ q, MAXRWSETS / 2);
+ ruleset = -1;
+ }
+ }
+ if (s->s_ruleset >= 0 &&
+ ruleset >= 0 &&
+ ruleset != s->s_ruleset)
+ {
+ syserr("%s: ruleset changed value (old %d, new %d)",
+ q, s->s_ruleset, ruleset);
+ ruleset = s->s_ruleset;
+ }
+ else if (ruleset >= 0)
+ {
+ s->s_ruleset = ruleset;
+ }
+ if (stabmode == ST_ENTER && ruleset >= 0)
+ {
+ char *h = NULL;
+
+ if (RuleSetNames[ruleset] != NULL)
+ sm_free(RuleSetNames[ruleset]); /* XXX */
+ if (delim != '\0' && (h = strchr(q, delim)) != NULL)
+ *h = '\0';
+ RuleSetNames[ruleset] = newstr(q);
+ if (delim == '/' && h != NULL)
+ *h = delim; /* put back delim */
+ }
+ }
+ return ruleset;
+}
+/*
+** SETTIMEOUT -- set an individual timeout
+**
+** Parameters:
+** name -- the name of the timeout.
+** val -- the value of the timeout.
+** sticky -- if set, don't let other setoptions override
+** this value.
+**
+** Returns:
+** none.
+*/
+
+/* set if Timeout sub-option is stuck */
+static BITMAP256 StickyTimeoutOpt;
+
+static struct timeoutinfo
+{
+ char *to_name; /* long name of timeout */
+ unsigned char to_code; /* code for option */
+} TimeOutTab[] =
+{
+#define TO_INITIAL 0x01
+ { "initial", TO_INITIAL },
+#define TO_MAIL 0x02
+ { "mail", TO_MAIL },
+#define TO_RCPT 0x03
+ { "rcpt", TO_RCPT },
+#define TO_DATAINIT 0x04
+ { "datainit", TO_DATAINIT },
+#define TO_DATABLOCK 0x05
+ { "datablock", TO_DATABLOCK },
+#define TO_DATAFINAL 0x06
+ { "datafinal", TO_DATAFINAL },
+#define TO_COMMAND 0x07
+ { "command", TO_COMMAND },
+#define TO_RSET 0x08
+ { "rset", TO_RSET },
+#define TO_HELO 0x09
+ { "helo", TO_HELO },
+#define TO_QUIT 0x0A
+ { "quit", TO_QUIT },
+#define TO_MISC 0x0B
+ { "misc", TO_MISC },
+#define TO_IDENT 0x0C
+ { "ident", TO_IDENT },
+#define TO_FILEOPEN 0x0D
+ { "fileopen", TO_FILEOPEN },
+#define TO_CONNECT 0x0E
+ { "connect", TO_CONNECT },
+#define TO_ICONNECT 0x0F
+ { "iconnect", TO_ICONNECT },
+#define TO_QUEUEWARN 0x10
+ { "queuewarn", TO_QUEUEWARN },
+ { "queuewarn.*", TO_QUEUEWARN },
+#define TO_QUEUEWARN_NORMAL 0x11
+ { "queuewarn.normal", TO_QUEUEWARN_NORMAL },
+#define TO_QUEUEWARN_URGENT 0x12
+ { "queuewarn.urgent", TO_QUEUEWARN_URGENT },
+#define TO_QUEUEWARN_NON_URGENT 0x13
+ { "queuewarn.non-urgent", TO_QUEUEWARN_NON_URGENT },
+#define TO_QUEUERETURN 0x14
+ { "queuereturn", TO_QUEUERETURN },
+ { "queuereturn.*", TO_QUEUERETURN },
+#define TO_QUEUERETURN_NORMAL 0x15
+ { "queuereturn.normal", TO_QUEUERETURN_NORMAL },
+#define TO_QUEUERETURN_URGENT 0x16
+ { "queuereturn.urgent", TO_QUEUERETURN_URGENT },
+#define TO_QUEUERETURN_NON_URGENT 0x17
+ { "queuereturn.non-urgent", TO_QUEUERETURN_NON_URGENT },
+#define TO_HOSTSTATUS 0x18
+ { "hoststatus", TO_HOSTSTATUS },
+#define TO_RESOLVER_RETRANS 0x19
+ { "resolver.retrans", TO_RESOLVER_RETRANS },
+#define TO_RESOLVER_RETRANS_NORMAL 0x1A
+ { "resolver.retrans.normal", TO_RESOLVER_RETRANS_NORMAL },
+#define TO_RESOLVER_RETRANS_FIRST 0x1B
+ { "resolver.retrans.first", TO_RESOLVER_RETRANS_FIRST },
+#define TO_RESOLVER_RETRY 0x1C
+ { "resolver.retry", TO_RESOLVER_RETRY },
+#define TO_RESOLVER_RETRY_NORMAL 0x1D
+ { "resolver.retry.normal", TO_RESOLVER_RETRY_NORMAL },
+#define TO_RESOLVER_RETRY_FIRST 0x1E
+ { "resolver.retry.first", TO_RESOLVER_RETRY_FIRST },
+#define TO_CONTROL 0x1F
+ { "control", TO_CONTROL },
+#define TO_LHLO 0x20
+ { "lhlo", TO_LHLO },
+#define TO_AUTH 0x21
+ { "auth", TO_AUTH },
+#define TO_STARTTLS 0x22
+ { "starttls", TO_STARTTLS },
+#define TO_ACONNECT 0x23
+ { "aconnect", TO_ACONNECT },
+#define TO_QUEUEWARN_DSN 0x24
+ { "queuewarn.dsn", TO_QUEUEWARN_DSN },
+#define TO_QUEUERETURN_DSN 0x25
+ { "queuereturn.dsn", TO_QUEUERETURN_DSN },
+ { NULL, 0 },
+};
+
+
+static void
+settimeout(name, val, sticky)
+ char *name;
+ char *val;
+ bool sticky;
+{
+ register struct timeoutinfo *to;
+ int i, addopts;
+ time_t toval;
+
+ if (tTd(37, 2))
+ sm_dprintf("settimeout(%s = %s)", name, val);
+
+ for (to = TimeOutTab; to->to_name != NULL; to++)
+ {
+ if (sm_strcasecmp(to->to_name, name) == 0)
+ break;
+ }
+
+ if (to->to_name == NULL)
+ {
+ errno = 0; /* avoid bogus error text */
+ syserr("settimeout: invalid timeout %s", name);
+ return;
+ }
+
+ /*
+ ** See if this option is preset for us.
+ */
+
+ if (!sticky && bitnset(to->to_code, StickyTimeoutOpt))
+ {
+ if (tTd(37, 2))
+ sm_dprintf(" (ignored)\n");
+ return;
+ }
+
+ if (tTd(37, 2))
+ sm_dprintf("\n");
+
+ toval = convtime(val, 'm');
+ addopts = 0;
+
+ switch (to->to_code)
+ {
+ case TO_INITIAL:
+ TimeOuts.to_initial = toval;
+ break;
+
+ case TO_MAIL:
+ TimeOuts.to_mail = toval;
+ break;
+
+ case TO_RCPT:
+ TimeOuts.to_rcpt = toval;
+ break;
+
+ case TO_DATAINIT:
+ TimeOuts.to_datainit = toval;
+ break;
+
+ case TO_DATABLOCK:
+ TimeOuts.to_datablock = toval;
+ break;
+
+ case TO_DATAFINAL:
+ TimeOuts.to_datafinal = toval;
+ break;
+
+ case TO_COMMAND:
+ TimeOuts.to_nextcommand = toval;
+ break;
+
+ case TO_RSET:
+ TimeOuts.to_rset = toval;
+ break;
+
+ case TO_HELO:
+ TimeOuts.to_helo = toval;
+ break;
+
+ case TO_QUIT:
+ TimeOuts.to_quit = toval;
+ break;
+
+ case TO_MISC:
+ TimeOuts.to_miscshort = toval;
+ break;
+
+ case TO_IDENT:
+ TimeOuts.to_ident = toval;
+ break;
+
+ case TO_FILEOPEN:
+ TimeOuts.to_fileopen = toval;
+ break;
+
+ case TO_CONNECT:
+ TimeOuts.to_connect = toval;
+ break;
+
+ case TO_ICONNECT:
+ TimeOuts.to_iconnect = toval;
+ break;
+
+ case TO_ACONNECT:
+ TimeOuts.to_aconnect = toval;
+ break;
+
+ case TO_QUEUEWARN:
+ toval = convtime(val, 'h');
+ TimeOuts.to_q_warning[TOC_NORMAL] = toval;
+ TimeOuts.to_q_warning[TOC_URGENT] = toval;
+ TimeOuts.to_q_warning[TOC_NONURGENT] = toval;
+ TimeOuts.to_q_warning[TOC_DSN] = toval;
+ addopts = 2;
+ break;
+
+ case TO_QUEUEWARN_NORMAL:
+ toval = convtime(val, 'h');
+ TimeOuts.to_q_warning[TOC_NORMAL] = toval;
+ break;
+
+ case TO_QUEUEWARN_URGENT:
+ toval = convtime(val, 'h');
+ TimeOuts.to_q_warning[TOC_URGENT] = toval;
+ break;
+
+ case TO_QUEUEWARN_NON_URGENT:
+ toval = convtime(val, 'h');
+ TimeOuts.to_q_warning[TOC_NONURGENT] = toval;
+ break;
+
+ case TO_QUEUEWARN_DSN:
+ toval = convtime(val, 'h');
+ TimeOuts.to_q_warning[TOC_DSN] = toval;
+ break;
+
+ case TO_QUEUERETURN:
+ toval = convtime(val, 'd');
+ TimeOuts.to_q_return[TOC_NORMAL] = toval;
+ TimeOuts.to_q_return[TOC_URGENT] = toval;
+ TimeOuts.to_q_return[TOC_NONURGENT] = toval;
+ TimeOuts.to_q_return[TOC_DSN] = toval;
+ addopts = 2;
+ break;
+
+ case TO_QUEUERETURN_NORMAL:
+ toval = convtime(val, 'd');
+ TimeOuts.to_q_return[TOC_NORMAL] = toval;
+ break;
+
+ case TO_QUEUERETURN_URGENT:
+ toval = convtime(val, 'd');
+ TimeOuts.to_q_return[TOC_URGENT] = toval;
+ break;
+
+ case TO_QUEUERETURN_NON_URGENT:
+ toval = convtime(val, 'd');
+ TimeOuts.to_q_return[TOC_NONURGENT] = toval;
+ break;
+
+ case TO_QUEUERETURN_DSN:
+ toval = convtime(val, 'd');
+ TimeOuts.to_q_return[TOC_DSN] = toval;
+ break;
+
+ case TO_HOSTSTATUS:
+ MciInfoTimeout = toval;
+ break;
+
+ case TO_RESOLVER_RETRANS:
+ toval = convtime(val, 's');
+ TimeOuts.res_retrans[RES_TO_DEFAULT] = toval;
+ TimeOuts.res_retrans[RES_TO_FIRST] = toval;
+ TimeOuts.res_retrans[RES_TO_NORMAL] = toval;
+ addopts = 2;
+ break;
+
+ case TO_RESOLVER_RETRY:
+ i = atoi(val);
+ TimeOuts.res_retry[RES_TO_DEFAULT] = i;
+ TimeOuts.res_retry[RES_TO_FIRST] = i;
+ TimeOuts.res_retry[RES_TO_NORMAL] = i;
+ addopts = 2;
+ break;
+
+ case TO_RESOLVER_RETRANS_NORMAL:
+ TimeOuts.res_retrans[RES_TO_NORMAL] = convtime(val, 's');
+ break;
+
+ case TO_RESOLVER_RETRY_NORMAL:
+ TimeOuts.res_retry[RES_TO_NORMAL] = atoi(val);
+ break;
+
+ case TO_RESOLVER_RETRANS_FIRST:
+ TimeOuts.res_retrans[RES_TO_FIRST] = convtime(val, 's');
+ break;
+
+ case TO_RESOLVER_RETRY_FIRST:
+ TimeOuts.res_retry[RES_TO_FIRST] = atoi(val);
+ break;
+
+ case TO_CONTROL:
+ TimeOuts.to_control = toval;
+ break;
+
+ case TO_LHLO:
+ TimeOuts.to_lhlo = toval;
+ break;
+
+#if SASL
+ case TO_AUTH:
+ TimeOuts.to_auth = toval;
+ break;
+#endif /* SASL */
+
+#if STARTTLS
+ case TO_STARTTLS:
+ TimeOuts.to_starttls = toval;
+ break;
+#endif /* STARTTLS */
+
+ default:
+ syserr("settimeout: invalid timeout %s", name);
+ break;
+ }
+
+ if (sticky)
+ {
+ for (i = 0; i <= addopts; i++)
+ setbitn(to->to_code + i, StickyTimeoutOpt);
+ }
+}
+/*
+** INITTIMEOUTS -- parse and set timeout values
+**
+** Parameters:
+** val -- a pointer to the values. If NULL, do initial
+** settings.
+** sticky -- if set, don't let other setoptions override
+** this suboption value.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Initializes the TimeOuts structure
+*/
+
+void
+inittimeouts(val, sticky)
+ register char *val;
+ bool sticky;
+{
+ register char *p;
+
+ if (tTd(37, 2))
+ sm_dprintf("inittimeouts(%s)\n", val == NULL ? "<NULL>" : val);
+ if (val == NULL)
+ {
+ TimeOuts.to_connect = (time_t) 0 SECONDS;
+ TimeOuts.to_aconnect = (time_t) 0 SECONDS;
+ TimeOuts.to_iconnect = (time_t) 0 SECONDS;
+ TimeOuts.to_initial = (time_t) 5 MINUTES;
+ TimeOuts.to_helo = (time_t) 5 MINUTES;
+ TimeOuts.to_mail = (time_t) 10 MINUTES;
+ TimeOuts.to_rcpt = (time_t) 1 HOUR;
+ TimeOuts.to_datainit = (time_t) 5 MINUTES;
+ TimeOuts.to_datablock = (time_t) 1 HOUR;
+ TimeOuts.to_datafinal = (time_t) 1 HOUR;
+ TimeOuts.to_rset = (time_t) 5 MINUTES;
+ TimeOuts.to_quit = (time_t) 2 MINUTES;
+ TimeOuts.to_nextcommand = (time_t) 1 HOUR;
+ TimeOuts.to_miscshort = (time_t) 2 MINUTES;
+#if IDENTPROTO
+ TimeOuts.to_ident = (time_t) 5 SECONDS;
+#else /* IDENTPROTO */
+ TimeOuts.to_ident = (time_t) 0 SECONDS;
+#endif /* IDENTPROTO */
+ TimeOuts.to_fileopen = (time_t) 60 SECONDS;
+ TimeOuts.to_control = (time_t) 2 MINUTES;
+ TimeOuts.to_lhlo = (time_t) 2 MINUTES;
+#if SASL
+ TimeOuts.to_auth = (time_t) 10 MINUTES;
+#endif /* SASL */
+#if STARTTLS
+ TimeOuts.to_starttls = (time_t) 1 HOUR;
+#endif /* STARTTLS */
+ if (tTd(37, 5))
+ {
+ sm_dprintf("Timeouts:\n");
+ sm_dprintf(" connect = %ld\n",
+ (long) TimeOuts.to_connect);
+ sm_dprintf(" aconnect = %ld\n",
+ (long) TimeOuts.to_aconnect);
+ sm_dprintf(" initial = %ld\n",
+ (long) TimeOuts.to_initial);
+ sm_dprintf(" helo = %ld\n", (long) TimeOuts.to_helo);
+ sm_dprintf(" mail = %ld\n", (long) TimeOuts.to_mail);
+ sm_dprintf(" rcpt = %ld\n", (long) TimeOuts.to_rcpt);
+ sm_dprintf(" datainit = %ld\n",
+ (long) TimeOuts.to_datainit);
+ sm_dprintf(" datablock = %ld\n",
+ (long) TimeOuts.to_datablock);
+ sm_dprintf(" datafinal = %ld\n",
+ (long) TimeOuts.to_datafinal);
+ sm_dprintf(" rset = %ld\n", (long) TimeOuts.to_rset);
+ sm_dprintf(" quit = %ld\n", (long) TimeOuts.to_quit);
+ sm_dprintf(" nextcommand = %ld\n",
+ (long) TimeOuts.to_nextcommand);
+ sm_dprintf(" miscshort = %ld\n",
+ (long) TimeOuts.to_miscshort);
+ sm_dprintf(" ident = %ld\n", (long) TimeOuts.to_ident);
+ sm_dprintf(" fileopen = %ld\n",
+ (long) TimeOuts.to_fileopen);
+ sm_dprintf(" lhlo = %ld\n",
+ (long) TimeOuts.to_lhlo);
+ sm_dprintf(" control = %ld\n",
+ (long) TimeOuts.to_control);
+ }
+ return;
+ }
+
+ for (;; val = p)
+ {
+ while (isascii(*val) && isspace(*val))
+ val++;
+ if (*val == '\0')
+ break;
+ for (p = val; *p != '\0' && *p != ','; p++)
+ continue;
+ if (*p != '\0')
+ *p++ = '\0';
+
+ if (isascii(*val) && isdigit(*val))
+ {
+ /* old syntax -- set everything */
+ TimeOuts.to_mail = convtime(val, 'm');
+ TimeOuts.to_rcpt = TimeOuts.to_mail;
+ TimeOuts.to_datainit = TimeOuts.to_mail;
+ TimeOuts.to_datablock = TimeOuts.to_mail;
+ TimeOuts.to_datafinal = TimeOuts.to_mail;
+ TimeOuts.to_nextcommand = TimeOuts.to_mail;
+ if (sticky)
+ {
+ setbitn(TO_MAIL, StickyTimeoutOpt);
+ setbitn(TO_RCPT, StickyTimeoutOpt);
+ setbitn(TO_DATAINIT, StickyTimeoutOpt);
+ setbitn(TO_DATABLOCK, StickyTimeoutOpt);
+ setbitn(TO_DATAFINAL, StickyTimeoutOpt);
+ setbitn(TO_COMMAND, StickyTimeoutOpt);
+ }
+ continue;
+ }
+ else
+ {
+ register char *q = strchr(val, ':');
+
+ if (q == NULL && (q = strchr(val, '=')) == NULL)
+ {
+ /* syntax error */
+ continue;
+ }
+ *q++ = '\0';
+ settimeout(val, q, sticky);
+ }
+ }
+}
diff --git a/usr/src/cmd/sendmail/src/recipient.c b/usr/src/cmd/sendmail/src/recipient.c
new file mode 100644
index 0000000000..85d1db452d
--- /dev/null
+++ b/usr/src/cmd/sendmail/src/recipient.c
@@ -0,0 +1,2044 @@
+/*
+ * Copyright (c) 1998-2003 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sendmail.h>
+
+SM_RCSID("@(#)$Id: recipient.c,v 8.337 2004/08/03 19:57:23 ca Exp $")
+
+static void includetimeout __P((int));
+static ADDRESS *self_reference __P((ADDRESS *));
+static int sortexpensive __P((ADDRESS *, ADDRESS *));
+static int sortbysignature __P((ADDRESS *, ADDRESS *));
+static int sorthost __P((ADDRESS *, ADDRESS *));
+
+typedef int sortfn_t __P((ADDRESS *, ADDRESS *));
+
+/*
+** SORTHOST -- strcmp()-like func for host portion of an ADDRESS
+**
+** Parameters:
+** xx -- first ADDRESS
+** yy -- second ADDRESS
+**
+** Returns:
+** <0 when xx->q_host is less than yy->q_host
+** >0 when xx->q_host is greater than yy->q_host
+** 0 when equal
+*/
+
+static int
+sorthost(xx, yy)
+ register ADDRESS *xx;
+ register ADDRESS *yy;
+{
+#if _FFR_HOST_SORT_REVERSE
+ /* XXX maybe compare hostnames from the end? */
+ return sm_strrevcasecmp(xx->q_host, yy->q_host);
+#else /* _FFR_HOST_SORT_REVERSE */
+ return sm_strcasecmp(xx->q_host, yy->q_host);
+#endif /* _FFR_HOST_SORT_REVERSE */
+}
+
+/*
+** SORTEXPENSIVE -- strcmp()-like func for expensive mailers
+**
+** The mailer has been noted already as "expensive" for 'xx'. This
+** will give a result relative to 'yy'. Expensive mailers get rated
+** "greater than" non-expensive mailers because during the delivery phase
+** it will get queued -- no use it getting in the way of less expensive
+** recipients. We avoid an MX RR lookup when both 'xx' and 'yy' are
+** expensive since an MX RR lookup happens when extracted from the queue
+** later.
+**
+** Parameters:
+** xx -- first ADDRESS
+** yy -- second ADDRESS
+**
+** Returns:
+** <0 when xx->q_host is less than yy->q_host and both are
+** expensive
+** >0 when xx->q_host is greater than yy->q_host, or when
+** 'yy' is non-expensive
+** 0 when equal (by expense and q_host)
+*/
+
+static int
+sortexpensive(xx, yy)
+ ADDRESS *xx;
+ ADDRESS *yy;
+{
+ if (!bitnset(M_EXPENSIVE, yy->q_mailer->m_flags))
+ return 1; /* xx should go later */
+#if _FFR_HOST_SORT_REVERSE
+ /* XXX maybe compare hostnames from the end? */
+ return sm_strrevcasecmp(xx->q_host, yy->q_host);
+#else /* _FFR_HOST_SORT_REVERSE */
+ return sm_strcasecmp(xx->q_host, yy->q_host);
+#endif /* _FFR_HOST_SORT_REVERSE */
+}
+
+/*
+** SORTBYSIGNATURE -- a strcmp()-like func for q_mailer and q_host in ADDRESS
+**
+** Parameters:
+** xx -- first ADDRESS
+** yy -- second ADDRESS
+**
+** Returns:
+** 0 when the "signature"'s are same
+** <0 when xx->q_signature is less than yy->q_signature
+** >0 when xx->q_signature is greater than yy->q_signature
+**
+** Side Effect:
+** May set ADDRESS pointer for q_signature if not already set.
+*/
+
+static int
+sortbysignature(xx, yy)
+ ADDRESS *xx;
+ ADDRESS *yy;
+{
+ register int ret;
+
+ /* Let's avoid redoing the signature over and over again */
+ if (xx->q_signature == NULL)
+ xx->q_signature = hostsignature(xx->q_mailer, xx->q_host);
+ if (yy->q_signature == NULL)
+ yy->q_signature = hostsignature(yy->q_mailer, yy->q_host);
+ ret = strcmp(xx->q_signature, yy->q_signature);
+
+ /*
+ ** If the two signatures are the same then we will return a sort
+ ** value based on 'q_user'. But note that we have reversed xx and yy
+ ** on purpose. This additional compare helps reduce the number of
+ ** sameaddr() calls and loops in recipient() for the case when
+ ** the rcpt list has been provided already in-order.
+ */
+
+ if (ret == 0)
+ return strcmp(yy->q_user, xx->q_user);
+ else
+ return ret;
+}
+
+/*
+** SENDTOLIST -- Designate a send list.
+**
+** The parameter is a comma-separated list of people to send to.
+** This routine arranges to send to all of them.
+**
+** Parameters:
+** list -- the send list.
+** ctladdr -- the address template for the person to
+** send to -- effective uid/gid are important.
+** This is typically the alias that caused this
+** expansion.
+** sendq -- a pointer to the head of a queue to put
+** these people into.
+** aliaslevel -- the current alias nesting depth -- to
+** diagnose loops.
+** e -- the envelope in which to add these recipients.
+**
+** Returns:
+** The number of addresses actually on the list.
+*/
+
+/* q_flags bits inherited from ctladdr */
+#define QINHERITEDBITS (QPINGONSUCCESS|QPINGONFAILURE|QPINGONDELAY|QHASNOTIFY)
+
+int
+sendtolist(list, ctladdr, sendq, aliaslevel, e)
+ char *list;
+ ADDRESS *ctladdr;
+ ADDRESS **sendq;
+ int aliaslevel;
+ register ENVELOPE *e;
+{
+ register char *p;
+ register ADDRESS *SM_NONVOLATILE al; /* list of addresses to send to */
+ SM_NONVOLATILE char delimiter; /* the address delimiter */
+ SM_NONVOLATILE int naddrs;
+ SM_NONVOLATILE int i;
+ char *endp;
+ char *oldto = e->e_to;
+ char *SM_NONVOLATILE bufp;
+ char buf[MAXNAME + 1];
+
+ if (list == NULL)
+ {
+ syserr("sendtolist: null list");
+ return 0;
+ }
+
+ if (tTd(25, 1))
+ {
+ sm_dprintf("sendto: %s\n ctladdr=", list);
+ printaddr(sm_debug_file(), ctladdr, false);
+ }
+
+ /* heuristic to determine old versus new style addresses */
+ if (ctladdr == NULL &&
+ (strchr(list, ',') != NULL || strchr(list, ';') != NULL ||
+ strchr(list, '<') != NULL || strchr(list, '(') != NULL))
+ e->e_flags &= ~EF_OLDSTYLE;
+ delimiter = ' ';
+ if (!bitset(EF_OLDSTYLE, e->e_flags) || ctladdr != NULL)
+ delimiter = ',';
+
+ al = NULL;
+ naddrs = 0;
+
+ /* make sure we have enough space to copy the string */
+ i = strlen(list) + 1;
+ if (i <= sizeof buf)
+ {
+ bufp = buf;
+ i = sizeof buf;
+ }
+ else
+ bufp = sm_malloc_x(i);
+ endp = bufp + i;
+
+ SM_TRY
+ {
+ (void) sm_strlcpy(bufp, denlstring(list, false, true), i);
+
+ macdefine(&e->e_macro, A_PERM, macid("{addr_type}"), "e r");
+ for (p = bufp; *p != '\0'; )
+ {
+ auto char *delimptr;
+ register ADDRESS *a;
+
+ SM_ASSERT(p < endp);
+
+ /* parse the address */
+ while ((isascii(*p) && isspace(*p)) || *p == ',')
+ p++;
+ SM_ASSERT(p < endp);
+ a = parseaddr(p, NULLADDR, RF_COPYALL, delimiter,
+ &delimptr, e, true);
+ p = delimptr;
+ SM_ASSERT(p < endp);
+ if (a == NULL)
+ continue;
+ a->q_next = al;
+ a->q_alias = ctladdr;
+
+ /* arrange to inherit attributes from parent */
+ if (ctladdr != NULL)
+ {
+ ADDRESS *b;
+
+ /* self reference test */
+ if (sameaddr(ctladdr, a))
+ {
+ if (tTd(27, 5))
+ {
+ sm_dprintf("sendtolist: QSELFREF ");
+ printaddr(sm_debug_file(), ctladdr, false);
+ }
+ ctladdr->q_flags |= QSELFREF;
+ }
+
+ /* check for address loops */
+ b = self_reference(a);
+ if (b != NULL)
+ {
+ b->q_flags |= QSELFREF;
+ if (tTd(27, 5))
+ {
+ sm_dprintf("sendtolist: QSELFREF ");
+ printaddr(sm_debug_file(), b, false);
+ }
+ if (a != b)
+ {
+ if (tTd(27, 5))
+ {
+ sm_dprintf("sendtolist: QS_DONTSEND ");
+ printaddr(sm_debug_file(), a, false);
+ }
+ a->q_state = QS_DONTSEND;
+ b->q_flags |= a->q_flags & QNOTREMOTE;
+ continue;
+ }
+ }
+
+ /* full name */
+ if (a->q_fullname == NULL)
+ a->q_fullname = ctladdr->q_fullname;
+
+ /* various flag bits */
+ a->q_flags &= ~QINHERITEDBITS;
+ a->q_flags |= ctladdr->q_flags & QINHERITEDBITS;
+
+ /* DSN recipient information */
+ a->q_finalrcpt = ctladdr->q_finalrcpt;
+ a->q_orcpt = ctladdr->q_orcpt;
+ }
+
+ al = a;
+ }
+
+ /* arrange to send to everyone on the local send list */
+ while (al != NULL)
+ {
+ register ADDRESS *a = al;
+
+ al = a->q_next;
+ a = recipient(a, sendq, aliaslevel, e);
+ naddrs++;
+ }
+ }
+ SM_FINALLY
+ {
+ e->e_to = oldto;
+ if (bufp != buf)
+ sm_free(bufp);
+ macdefine(&e->e_macro, A_PERM, macid("{addr_type}"), NULL);
+ }
+ SM_END_TRY
+ return naddrs;
+}
+#if MILTER
+/*
+** REMOVEFROMLIST -- Remove addresses from a send list.
+**
+** The parameter is a comma-separated list of recipients to remove.
+** Note that it only deletes matching addresses. If those addresses
+** have been expanded already in the sendq, it won't mark the
+** expanded recipients as QS_REMOVED.
+**
+** Parameters:
+** list -- the list to remove.
+** sendq -- a pointer to the head of a queue to remove
+** these addresses from.
+** e -- the envelope in which to remove these recipients.
+**
+** Returns:
+** The number of addresses removed from the list.
+**
+*/
+
+int
+removefromlist(list, sendq, e)
+ char *list;
+ ADDRESS **sendq;
+ ENVELOPE *e;
+{
+ SM_NONVOLATILE char delimiter; /* the address delimiter */
+ SM_NONVOLATILE int naddrs;
+ SM_NONVOLATILE int i;
+ char *p;
+ char *oldto = e->e_to;
+ char *SM_NONVOLATILE bufp;
+ char buf[MAXNAME + 1];
+
+ if (list == NULL)
+ {
+ syserr("removefromlist: null list");
+ return 0;
+ }
+
+ if (tTd(25, 1))
+ sm_dprintf("removefromlist: %s\n", list);
+
+ /* heuristic to determine old versus new style addresses */
+ if (strchr(list, ',') != NULL || strchr(list, ';') != NULL ||
+ strchr(list, '<') != NULL || strchr(list, '(') != NULL)
+ e->e_flags &= ~EF_OLDSTYLE;
+ delimiter = ' ';
+ if (!bitset(EF_OLDSTYLE, e->e_flags))
+ delimiter = ',';
+
+ naddrs = 0;
+
+ /* make sure we have enough space to copy the string */
+ i = strlen(list) + 1;
+ if (i <= sizeof buf)
+ {
+ bufp = buf;
+ i = sizeof buf;
+ }
+ else
+ bufp = sm_malloc_x(i);
+
+ SM_TRY
+ {
+ (void) sm_strlcpy(bufp, denlstring(list, false, true), i);
+
+ macdefine(&e->e_macro, A_PERM, macid("{addr_type}"), "e r");
+ for (p = bufp; *p != '\0'; )
+ {
+ ADDRESS a; /* parsed address to be removed */
+ ADDRESS *q;
+ ADDRESS **pq;
+ char *delimptr;
+
+ /* parse the address */
+ while ((isascii(*p) && isspace(*p)) || *p == ',')
+ p++;
+ if (parseaddr(p, &a, RF_COPYALL,
+ delimiter, &delimptr, e, true) == NULL)
+ {
+ p = delimptr;
+ continue;
+ }
+ p = delimptr;
+ for (pq = sendq; (q = *pq) != NULL; pq = &q->q_next)
+ {
+ if (!QS_IS_DEAD(q->q_state) &&
+ (sameaddr(q, &a) ||
+ strcmp(q->q_paddr, a.q_paddr) == 0))
+ {
+ if (tTd(25, 5))
+ {
+ sm_dprintf("removefromlist: QS_REMOVED ");
+ printaddr(sm_debug_file(), &a, false);
+ }
+ q->q_state = QS_REMOVED;
+ naddrs++;
+ break;
+ }
+ }
+ }
+ }
+ SM_FINALLY
+ {
+ e->e_to = oldto;
+ if (bufp != buf)
+ sm_free(bufp);
+ macdefine(&e->e_macro, A_PERM, macid("{addr_type}"), NULL);
+ }
+ SM_END_TRY
+ return naddrs;
+}
+#endif /* MILTER */
+/*
+** RECIPIENT -- Designate a message recipient
+**
+** Saves the named person for future mailing.
+**
+** Parameters:
+** new -- the (preparsed) address header for the recipient.
+** sendq -- a pointer to the head of a queue to put the
+** recipient in. Duplicate suppression is done
+** in this queue.
+** aliaslevel -- the current alias nesting depth.
+** e -- the current envelope.
+**
+** Returns:
+** The actual address in the queue. This will be "a" if
+** the address is not a duplicate, else the original address.
+**
+*/
+
+ADDRESS *
+recipient(new, sendq, aliaslevel, e)
+ register ADDRESS *new;
+ register ADDRESS **sendq;
+ int aliaslevel;
+ register ENVELOPE *e;
+{
+ register ADDRESS *q;
+ ADDRESS **pq;
+ ADDRESS **prev;
+ register struct mailer *m;
+ register char *p;
+ int i, buflen;
+ bool quoted; /* set if the addr has a quote bit */
+ bool insert;
+ int findusercount;
+ bool initialdontsend;
+ char *buf;
+ char buf0[MAXNAME + 1]; /* unquoted image of the user name */
+ sortfn_t *sortfn;
+
+ p = NULL;
+ quoted = false;
+ insert = false;
+ findusercount = 0;
+ initialdontsend = QS_IS_DEAD(new->q_state);
+ e->e_to = new->q_paddr;
+ m = new->q_mailer;
+ errno = 0;
+ if (aliaslevel == 0)
+ new->q_flags |= QPRIMARY;
+ if (tTd(26, 1))
+ {
+ sm_dprintf("\nrecipient (%d): ", aliaslevel);
+ printaddr(sm_debug_file(), new, false);
+ }
+
+ /* if this is primary, use it as original recipient */
+ if (new->q_alias == NULL)
+ {
+ if (e->e_origrcpt == NULL)
+ e->e_origrcpt = new->q_paddr;
+ else if (e->e_origrcpt != new->q_paddr)
+ e->e_origrcpt = "";
+ }
+
+ /* find parent recipient for finalrcpt and orcpt */
+ for (q = new; q->q_alias != NULL; q = q->q_alias)
+ continue;
+
+ /* find final recipient DSN address */
+ if (new->q_finalrcpt == NULL &&
+ e->e_from.q_mailer != NULL)
+ {
+ char frbuf[MAXLINE];
+
+ p = e->e_from.q_mailer->m_addrtype;
+ if (p == NULL)
+ p = "rfc822";
+ if (sm_strcasecmp(p, "rfc822") != 0)
+ {
+ (void) sm_snprintf(frbuf, sizeof frbuf, "%s; %.800s",
+ q->q_mailer->m_addrtype,
+ q->q_user);
+ }
+ else if (strchr(q->q_user, '@') != NULL)
+ {
+ (void) sm_snprintf(frbuf, sizeof frbuf, "%s; %.800s",
+ p, q->q_user);
+ }
+ else if (strchr(q->q_paddr, '@') != NULL)
+ {
+ char *qp;
+ bool b;
+
+ qp = q->q_paddr;
+
+ /* strip brackets from address */
+ b = false;
+ if (*qp == '<')
+ {
+ b = qp[strlen(qp) - 1] == '>';
+ if (b)
+ qp[strlen(qp) - 1] = '\0';
+ qp++;
+ }
+ (void) sm_snprintf(frbuf, sizeof frbuf, "%s; %.800s",
+ p, qp);
+
+ /* undo damage */
+ if (b)
+ qp[strlen(qp)] = '>';
+ }
+ else
+ {
+ (void) sm_snprintf(frbuf, sizeof frbuf,
+ "%s; %.700s@%.100s",
+ p, q->q_user, MyHostName);
+ }
+ new->q_finalrcpt = sm_rpool_strdup_x(e->e_rpool, frbuf);
+ }
+
+#if _FFR_GEN_ORCPT
+ /* set ORCPT DSN arg if not already set */
+ if (new->q_orcpt == NULL)
+ {
+ /* check for an existing ORCPT */
+ if (q->q_orcpt != NULL)
+ new->q_orcpt = q->q_orcpt;
+ else
+ {
+ /* make our own */
+ bool b = false;
+ char *qp;
+ char obuf[MAXLINE];
+
+ if (e->e_from.q_mailer != NULL)
+ p = e->e_from.q_mailer->m_addrtype;
+ if (p == NULL)
+ p = "rfc822";
+ (void) sm_strlcpyn(obuf, sizeof obuf, 2, p, ";");
+
+ qp = q->q_paddr;
+
+ /* FFR: Needs to strip comments from stdin addrs */
+
+ /* strip brackets from address */
+ if (*qp == '<')
+ {
+ b = qp[strlen(qp) - 1] == '>';
+ if (b)
+ qp[strlen(qp) - 1] = '\0';
+ qp++;
+ }
+
+ p = xtextify(denlstring(qp, true, false), NULL);
+
+ if (sm_strlcat(obuf, p, sizeof obuf) >= sizeof obuf)
+ {
+ /* if too big, don't use it */
+ obuf[0] = '\0';
+ }
+
+ /* undo damage */
+ if (b)
+ qp[strlen(qp)] = '>';
+
+ if (obuf[0] != '\0')
+ new->q_orcpt =
+ sm_rpool_strdup_x(e->e_rpool, obuf);
+ }
+ }
+#endif /* _FFR_GEN_ORCPT */
+
+ /* break aliasing loops */
+ if (aliaslevel > MaxAliasRecursion)
+ {
+ new->q_state = QS_BADADDR;
+ new->q_status = "5.4.6";
+ usrerrenh(new->q_status,
+ "554 aliasing/forwarding loop broken (%d aliases deep; %d max)",
+ aliaslevel, MaxAliasRecursion);
+ return new;
+ }
+
+ /*
+ ** Finish setting up address structure.
+ */
+
+ /* get unquoted user for file, program or user.name check */
+ i = strlen(new->q_user);
+ if (i >= sizeof buf0)
+ {
+ buflen = i + 1;
+ buf = xalloc(buflen);
+ }
+ else
+ {
+ buf = buf0;
+ buflen = sizeof buf0;
+ }
+ (void) sm_strlcpy(buf, new->q_user, buflen);
+ for (p = buf; *p != '\0' && !quoted; p++)
+ {
+ if (*p == '\\')
+ quoted = true;
+ }
+ stripquotes(buf);
+
+ /* check for direct mailing to restricted mailers */
+ if (m == ProgMailer)
+ {
+ if (new->q_alias == NULL || UseMSP ||
+ bitset(EF_UNSAFE, e->e_flags))
+ {
+ new->q_state = QS_BADADDR;
+ new->q_status = "5.7.1";
+ usrerrenh(new->q_status,
+ "550 Cannot mail directly to programs");
+ }
+ else if (bitset(QBOGUSSHELL, new->q_alias->q_flags))
+ {
+ new->q_state = QS_BADADDR;
+ new->q_status = "5.7.1";
+ if (new->q_alias->q_ruser == NULL)
+ usrerrenh(new->q_status,
+ "550 UID %d is an unknown user: cannot mail to programs",
+ new->q_alias->q_uid);
+ else
+ usrerrenh(new->q_status,
+ "550 User %s@%s doesn't have a valid shell for mailing to programs",
+ new->q_alias->q_ruser, MyHostName);
+ }
+ else if (bitset(QUNSAFEADDR, new->q_alias->q_flags))
+ {
+ new->q_state = QS_BADADDR;
+ new->q_status = "5.7.1";
+ new->q_rstatus = "550 Unsafe for mailing to programs";
+ usrerrenh(new->q_status,
+ "550 Address %s is unsafe for mailing to programs",
+ new->q_alias->q_paddr);
+ }
+ }
+
+ /*
+ ** Look up this person in the recipient list.
+ ** If they are there already, return, otherwise continue.
+ ** If the list is empty, just add it. Notice the cute
+ ** hack to make from addresses suppress things correctly:
+ ** the QS_DUPLICATE state will be set in the send list.
+ ** [Please note: the emphasis is on "hack."]
+ */
+
+ prev = NULL;
+
+ /*
+ ** If this message is going to the queue or FastSplit is set
+ ** and it is the first try and the envelope hasn't split, then we
+ ** avoid doing an MX RR lookup now because one will be done when the
+ ** message is extracted from the queue later. It can go to the queue
+ ** because all messages are going to the queue or this mailer of
+ ** the current recipient is marked expensive.
+ */
+
+ if (UseMSP || WILL_BE_QUEUED(e->e_sendmode) ||
+ (!bitset(EF_SPLIT, e->e_flags) && e->e_ntries == 0 &&
+ FastSplit > 0))
+ sortfn = sorthost;
+ else if (NoConnect && bitnset(M_EXPENSIVE, new->q_mailer->m_flags))
+ sortfn = sortexpensive;
+ else
+ sortfn = sortbysignature;
+
+ for (pq = sendq; (q = *pq) != NULL; pq = &q->q_next)
+ {
+ /*
+ ** If address is "less than" it should be inserted now.
+ ** If address is "greater than" current comparison it'll
+ ** insert later in the list; so loop again (if possible).
+ ** If address is "equal" (different equal than sameaddr()
+ ** call) then check if sameaddr() will be true.
+ ** Because this list is now sorted, it'll mean fewer
+ ** comparisons and fewer loops which is important for more
+ ** recipients.
+ */
+
+ i = (*sortfn)(new, q);
+ if (i == 0) /* equal */
+ {
+ /*
+ ** Sortbysignature() has said that the two have
+ ** equal MX RR's and the same user. Calling sameaddr()
+ ** now checks if the two hosts are as identical as the
+ ** MX RR's are (which might not be the case)
+ ** before saying these are the identical addresses.
+ */
+
+ if (sameaddr(q, new) &&
+ (bitset(QRCPTOK, q->q_flags) ||
+ !bitset(QPRIMARY, q->q_flags)))
+ {
+ if (tTd(26, 1))
+ {
+ sm_dprintf("%s in sendq: ",
+ new->q_paddr);
+ printaddr(sm_debug_file(), q, false);
+ }
+ if (!bitset(QPRIMARY, q->q_flags))
+ {
+ if (!QS_IS_DEAD(new->q_state))
+ message("duplicate suppressed");
+ else
+ q->q_state = QS_DUPLICATE;
+ q->q_flags |= new->q_flags;
+ }
+ else if (bitset(QSELFREF, q->q_flags)
+ || q->q_state == QS_REMOVED)
+ {
+ /*
+ ** If an earlier milter removed the
+ ** address, a later one can still add
+ ** it back.
+ */
+
+ q->q_state = new->q_state;
+ q->q_flags |= new->q_flags;
+ }
+ new = q;
+ goto done;
+ }
+ }
+ else if (i < 0) /* less than */
+ {
+ insert = true;
+ break;
+ }
+ prev = pq;
+ }
+
+ /* pq should point to an address, never NULL */
+ SM_ASSERT(pq != NULL);
+
+ /* add address on list */
+ if (insert)
+ {
+ /*
+ ** insert before 'pq'. Only possible when at least 1
+ ** ADDRESS is in the list already.
+ */
+
+ new->q_next = *pq;
+ if (prev == NULL)
+ *sendq = new; /* To be the first ADDRESS */
+ else
+ (*prev)->q_next = new;
+ }
+ else
+ {
+ /*
+ ** Place in list at current 'pq' position. Possible
+ ** when there are 0 or more ADDRESS's in the list.
+ */
+
+ new->q_next = NULL;
+ *pq = new;
+ }
+
+ /* added a new address: clear split flag */
+ e->e_flags &= ~EF_SPLIT;
+
+ /*
+ ** Alias the name and handle special mailer types.
+ */
+
+ trylocaluser:
+ if (tTd(29, 7))
+ {
+ sm_dprintf("at trylocaluser: ");
+ printaddr(sm_debug_file(), new, false);
+ }
+
+ if (!QS_IS_OK(new->q_state))
+ {
+ if (QS_IS_UNDELIVERED(new->q_state))
+ e->e_nrcpts++;
+ goto testselfdestruct;
+ }
+
+ if (m == InclMailer)
+ {
+ new->q_state = QS_INCLUDED;
+ if (new->q_alias == NULL || UseMSP ||
+ bitset(EF_UNSAFE, e->e_flags))
+ {
+ new->q_state = QS_BADADDR;
+ new->q_status = "5.7.1";
+ usrerrenh(new->q_status,
+ "550 Cannot mail directly to :include:s");
+ }
+ else
+ {
+ int ret;
+
+ message("including file %s", new->q_user);
+ ret = include(new->q_user, false, new,
+ sendq, aliaslevel, e);
+ if (transienterror(ret))
+ {
+ if (LogLevel > 2)
+ sm_syslog(LOG_ERR, e->e_id,
+ "include %s: transient error: %s",
+ shortenstring(new->q_user,
+ MAXSHORTSTR),
+ sm_errstring(ret));
+ new->q_state = QS_QUEUEUP;
+ usrerr("451 4.2.4 Cannot open %s: %s",
+ shortenstring(new->q_user,
+ MAXSHORTSTR),
+ sm_errstring(ret));
+ }
+ else if (ret != 0)
+ {
+ new->q_state = QS_BADADDR;
+ new->q_status = "5.2.4";
+ usrerrenh(new->q_status,
+ "550 Cannot open %s: %s",
+ shortenstring(new->q_user,
+ MAXSHORTSTR),
+ sm_errstring(ret));
+ }
+ }
+ }
+ else if (m == FileMailer)
+ {
+ /* check if allowed */
+ if (new->q_alias == NULL || UseMSP ||
+ bitset(EF_UNSAFE, e->e_flags))
+ {
+ new->q_state = QS_BADADDR;
+ new->q_status = "5.7.1";
+ usrerrenh(new->q_status,
+ "550 Cannot mail directly to files");
+ }
+ else if (bitset(QBOGUSSHELL, new->q_alias->q_flags))
+ {
+ new->q_state = QS_BADADDR;
+ new->q_status = "5.7.1";
+ if (new->q_alias->q_ruser == NULL)
+ usrerrenh(new->q_status,
+ "550 UID %d is an unknown user: cannot mail to files",
+ new->q_alias->q_uid);
+ else
+ usrerrenh(new->q_status,
+ "550 User %s@%s doesn't have a valid shell for mailing to files",
+ new->q_alias->q_ruser, MyHostName);
+ }
+ else if (bitset(QUNSAFEADDR, new->q_alias->q_flags))
+ {
+ new->q_state = QS_BADADDR;
+ new->q_status = "5.7.1";
+ new->q_rstatus = "550 Unsafe for mailing to files";
+ usrerrenh(new->q_status,
+ "550 Address %s is unsafe for mailing to files",
+ new->q_alias->q_paddr);
+ }
+ }
+
+ /* try aliasing */
+ if (!quoted && QS_IS_OK(new->q_state) &&
+ bitnset(M_ALIASABLE, m->m_flags))
+ alias(new, sendq, aliaslevel, e);
+
+#if USERDB
+ /* if not aliased, look it up in the user database */
+ if (!bitset(QNOTREMOTE, new->q_flags) &&
+ QS_IS_SENDABLE(new->q_state) &&
+ bitnset(M_CHECKUDB, m->m_flags))
+ {
+ if (udbexpand(new, sendq, aliaslevel, e) == EX_TEMPFAIL)
+ {
+ new->q_state = QS_QUEUEUP;
+ if (e->e_message == NULL)
+ e->e_message = "Deferred: user database error";
+ if (new->q_message == NULL)
+ new->q_message = "Deferred: user database error";
+ if (LogLevel > 8)
+ sm_syslog(LOG_INFO, e->e_id,
+ "deferred: udbexpand: %s",
+ sm_errstring(errno));
+ message("queued (user database error): %s",
+ sm_errstring(errno));
+ e->e_nrcpts++;
+ goto testselfdestruct;
+ }
+ }
+#endif /* USERDB */
+
+ /*
+ ** If we have a level two config file, then pass the name through
+ ** Ruleset 5 before sending it off. Ruleset 5 has the right
+ ** to rewrite it to another mailer. This gives us a hook
+ ** after local aliasing has been done.
+ */
+
+ if (tTd(29, 5))
+ {
+ sm_dprintf("recipient: testing local? cl=%d, rr5=%p\n\t",
+ ConfigLevel, RewriteRules[5]);
+ printaddr(sm_debug_file(), new, false);
+ }
+ if (ConfigLevel >= 2 && RewriteRules[5] != NULL &&
+ bitnset(M_TRYRULESET5, m->m_flags) &&
+ !bitset(QNOTREMOTE, new->q_flags) &&
+ QS_IS_OK(new->q_state))
+ {
+ maplocaluser(new, sendq, aliaslevel + 1, e);
+ }
+
+ /*
+ ** If it didn't get rewritten to another mailer, go ahead
+ ** and deliver it.
+ */
+
+ if (QS_IS_OK(new->q_state) &&
+ bitnset(M_HASPWENT, m->m_flags))
+ {
+ auto bool fuzzy;
+ SM_MBDB_T user;
+ int status;
+
+ /* warning -- finduser may trash buf */
+ status = finduser(buf, &fuzzy, &user);
+ switch (status)
+ {
+ case EX_TEMPFAIL:
+ new->q_state = QS_QUEUEUP;
+ new->q_status = "4.5.2";
+ giveresponse(EX_TEMPFAIL, new->q_status, m, NULL,
+ new->q_alias, (time_t) 0, e, new);
+ break;
+ default:
+ new->q_state = QS_BADADDR;
+ new->q_status = "5.1.1";
+ new->q_rstatus = "550 5.1.1 User unknown";
+ giveresponse(EX_NOUSER, new->q_status, m, NULL,
+ new->q_alias, (time_t) 0, e, new);
+ break;
+ case EX_OK:
+ if (fuzzy)
+ {
+ /* name was a fuzzy match */
+ new->q_user = sm_rpool_strdup_x(e->e_rpool,
+ user.mbdb_name);
+ if (findusercount++ > 3)
+ {
+ new->q_state = QS_BADADDR;
+ new->q_status = "5.4.6";
+ usrerrenh(new->q_status,
+ "554 aliasing/forwarding loop for %s broken",
+ user.mbdb_name);
+ goto done;
+ }
+
+ /* see if it aliases */
+ (void) sm_strlcpy(buf, user.mbdb_name, buflen);
+ goto trylocaluser;
+ }
+ if (*user.mbdb_homedir == '\0')
+ new->q_home = NULL;
+ else if (strcmp(user.mbdb_homedir, "/") == 0)
+ new->q_home = "";
+ else
+ new->q_home = sm_rpool_strdup_x(e->e_rpool,
+ user.mbdb_homedir);
+ if (user.mbdb_uid != SM_NO_UID)
+ {
+ new->q_uid = user.mbdb_uid;
+ new->q_gid = user.mbdb_gid;
+ new->q_flags |= QGOODUID;
+ }
+ new->q_ruser = sm_rpool_strdup_x(e->e_rpool,
+ user.mbdb_name);
+ if (user.mbdb_fullname[0] != '\0')
+ new->q_fullname = sm_rpool_strdup_x(e->e_rpool,
+ user.mbdb_fullname);
+ if (!usershellok(user.mbdb_name, user.mbdb_shell))
+ {
+ new->q_flags |= QBOGUSSHELL;
+ }
+ if (bitset(EF_VRFYONLY, e->e_flags))
+ {
+ /* don't do any more now */
+ new->q_state = QS_VERIFIED;
+ }
+ else if (!quoted)
+ forward(new, sendq, aliaslevel, e);
+ }
+ }
+ if (!QS_IS_DEAD(new->q_state))
+ e->e_nrcpts++;
+
+ testselfdestruct:
+ new->q_flags |= QTHISPASS;
+ if (tTd(26, 8))
+ {
+ sm_dprintf("testselfdestruct: ");
+ printaddr(sm_debug_file(), new, false);
+ if (tTd(26, 10))
+ {
+ sm_dprintf("SENDQ:\n");
+ printaddr(sm_debug_file(), *sendq, true);
+ sm_dprintf("----\n");
+ }
+ }
+ if (new->q_alias == NULL && new != &e->e_from &&
+ QS_IS_DEAD(new->q_state))
+ {
+ for (q = *sendq; q != NULL; q = q->q_next)
+ {
+ if (!QS_IS_DEAD(q->q_state))
+ break;
+ }
+ if (q == NULL)
+ {
+ new->q_state = QS_BADADDR;
+ new->q_status = "5.4.6";
+ usrerrenh(new->q_status,
+ "554 aliasing/forwarding loop broken");
+ }
+ }
+
+ done:
+ new->q_flags |= QTHISPASS;
+ if (buf != buf0)
+ sm_free(buf); /* XXX leak if above code raises exception */
+
+ /*
+ ** If we are at the top level, check to see if this has
+ ** expanded to exactly one address. If so, it can inherit
+ ** the primaryness of the address.
+ **
+ ** While we're at it, clear the QTHISPASS bits.
+ */
+
+ if (aliaslevel == 0)
+ {
+ int nrcpts = 0;
+ ADDRESS *only = NULL;
+
+ for (q = *sendq; q != NULL; q = q->q_next)
+ {
+ if (bitset(QTHISPASS, q->q_flags) &&
+ QS_IS_SENDABLE(q->q_state))
+ {
+ nrcpts++;
+ only = q;
+ }
+ q->q_flags &= ~QTHISPASS;
+ }
+ if (nrcpts == 1)
+ {
+ /* check to see if this actually got a new owner */
+ q = only;
+ while ((q = q->q_alias) != NULL)
+ {
+ if (q->q_owner != NULL)
+ break;
+ }
+ if (q == NULL)
+ only->q_flags |= QPRIMARY;
+ }
+ else if (!initialdontsend && nrcpts > 0)
+ {
+ /* arrange for return receipt */
+ e->e_flags |= EF_SENDRECEIPT;
+ new->q_flags |= QEXPANDED;
+ if (e->e_xfp != NULL &&
+ bitset(QPINGONSUCCESS, new->q_flags))
+ (void) sm_io_fprintf(e->e_xfp, SM_TIME_DEFAULT,
+ "%s... expanded to multiple addresses\n",
+ new->q_paddr);
+ }
+ }
+ new->q_flags |= QRCPTOK;
+ (void) sm_snprintf(buf0, sizeof buf0, "%d", e->e_nrcpts);
+ macdefine(&e->e_macro, A_TEMP, macid("{nrcpts}"), buf0);
+ return new;
+}
+/*
+** FINDUSER -- find the password entry for a user.
+**
+** This looks a lot like getpwnam, except that it may want to
+** do some fancier pattern matching in /etc/passwd.
+**
+** This routine contains most of the time of many sendmail runs.
+** It deserves to be optimized.
+**
+** Parameters:
+** name -- the name to match against.
+** fuzzyp -- an outarg that is set to true if this entry
+** was found using the fuzzy matching algorithm;
+** set to false otherwise.
+** user -- structure to fill in if user is found
+**
+** Returns:
+** On success, fill in *user, set *fuzzyp and return EX_OK.
+** If the user was not found, return EX_NOUSER.
+** On error, return EX_TEMPFAIL or EX_OSERR.
+**
+** Side Effects:
+** may modify name.
+*/
+
+int
+finduser(name, fuzzyp, user)
+ char *name;
+ bool *fuzzyp;
+ SM_MBDB_T *user;
+{
+#if MATCHGECOS
+ register struct passwd *pw;
+#endif /* MATCHGECOS */
+ register char *p;
+ bool tryagain;
+ int status;
+
+ if (tTd(29, 4))
+ sm_dprintf("finduser(%s): ", name);
+
+ *fuzzyp = false;
+
+#if HESIOD
+ /* DEC Hesiod getpwnam accepts numeric strings -- short circuit it */
+ for (p = name; *p != '\0'; p++)
+ if (!isascii(*p) || !isdigit(*p))
+ break;
+ if (*p == '\0')
+ {
+ if (tTd(29, 4))
+ sm_dprintf("failed (numeric input)\n");
+ return EX_NOUSER;
+ }
+#endif /* HESIOD */
+
+ /* look up this login name using fast path */
+ status = sm_mbdb_lookup(name, user);
+ if (status != EX_NOUSER)
+ {
+ if (tTd(29, 4))
+ sm_dprintf("%s (non-fuzzy)\n", sm_strexit(status));
+ return status;
+ }
+
+ /* try mapping it to lower case */
+ tryagain = false;
+ for (p = name; *p != '\0'; p++)
+ {
+ if (isascii(*p) && isupper(*p))
+ {
+ *p = tolower(*p);
+ tryagain = true;
+ }
+ }
+ if (tryagain && (status = sm_mbdb_lookup(name, user)) != EX_NOUSER)
+ {
+ if (tTd(29, 4))
+ sm_dprintf("%s (lower case)\n", sm_strexit(status));
+ *fuzzyp = true;
+ return status;
+ }
+
+#if MATCHGECOS
+ /* see if fuzzy matching allowed */
+ if (!MatchGecos)
+ {
+ if (tTd(29, 4))
+ sm_dprintf("not found (fuzzy disabled)\n");
+ return EX_NOUSER;
+ }
+
+ /* search for a matching full name instead */
+ for (p = name; *p != '\0'; p++)
+ {
+ if (*p == (SpaceSub & 0177) || *p == '_')
+ *p = ' ';
+ }
+ (void) setpwent();
+ while ((pw = getpwent()) != NULL)
+ {
+ char buf[MAXNAME + 1];
+
+# if 0
+ if (sm_strcasecmp(pw->pw_name, name) == 0)
+ {
+ if (tTd(29, 4))
+ sm_dprintf("found (case wrapped)\n");
+ break;
+ }
+# endif /* 0 */
+
+ sm_pwfullname(pw->pw_gecos, pw->pw_name, buf, sizeof buf);
+ if (strchr(buf, ' ') != NULL && sm_strcasecmp(buf, name) == 0)
+ {
+ if (tTd(29, 4))
+ sm_dprintf("fuzzy matches %s\n", pw->pw_name);
+ message("sending to login name %s", pw->pw_name);
+ break;
+ }
+ }
+ if (pw != NULL)
+ *fuzzyp = true;
+ else if (tTd(29, 4))
+ sm_dprintf("no fuzzy match found\n");
+# if DEC_OSF_BROKEN_GETPWENT /* DEC OSF/1 3.2 or earlier */
+ endpwent();
+# endif /* DEC_OSF_BROKEN_GETPWENT */
+ if (pw == NULL)
+ return EX_NOUSER;
+ sm_mbdb_frompw(user, pw);
+ return EX_OK;
+#else /* MATCHGECOS */
+ if (tTd(29, 4))
+ sm_dprintf("not found (fuzzy disabled)\n");
+ return EX_NOUSER;
+#endif /* MATCHGECOS */
+}
+/*
+** WRITABLE -- predicate returning if the file is writable.
+**
+** This routine must duplicate the algorithm in sys/fio.c.
+** Unfortunately, we cannot use the access call since we
+** won't necessarily be the real uid when we try to
+** actually open the file.
+**
+** Notice that ANY file with ANY execute bit is automatically
+** not writable. This is also enforced by mailfile.
+**
+** Parameters:
+** filename -- the file name to check.
+** ctladdr -- the controlling address for this file.
+** flags -- SFF_* flags to control the function.
+**
+** Returns:
+** true -- if we will be able to write this file.
+** false -- if we cannot write this file.
+**
+** Side Effects:
+** none.
+*/
+
+bool
+writable(filename, ctladdr, flags)
+ char *filename;
+ ADDRESS *ctladdr;
+ long flags;
+{
+ uid_t euid = 0;
+ gid_t egid = 0;
+ char *user = NULL;
+
+ if (tTd(44, 5))
+ sm_dprintf("writable(%s, 0x%lx)\n", filename, flags);
+
+ /*
+ ** File does exist -- check that it is writable.
+ */
+
+ if (geteuid() != 0)
+ {
+ euid = geteuid();
+ egid = getegid();
+ user = NULL;
+ }
+ else if (ctladdr != NULL)
+ {
+ euid = ctladdr->q_uid;
+ egid = ctladdr->q_gid;
+ user = ctladdr->q_user;
+ }
+ else if (bitset(SFF_RUNASREALUID, flags))
+ {
+ euid = RealUid;
+ egid = RealGid;
+ user = RealUserName;
+ }
+ else if (FileMailer != NULL && !bitset(SFF_ROOTOK, flags))
+ {
+ if (FileMailer->m_uid == NO_UID)
+ {
+ euid = DefUid;
+ user = DefUser;
+ }
+ else
+ {
+ euid = FileMailer->m_uid;
+ user = NULL;
+ }
+ if (FileMailer->m_gid == NO_GID)
+ egid = DefGid;
+ else
+ egid = FileMailer->m_gid;
+ }
+ else
+ {
+ euid = egid = 0;
+ user = NULL;
+ }
+ if (!bitset(SFF_ROOTOK, flags))
+ {
+ if (euid == 0)
+ {
+ euid = DefUid;
+ user = DefUser;
+ }
+ if (egid == 0)
+ egid = DefGid;
+ }
+ if (geteuid() == 0 &&
+ (ctladdr == NULL || !bitset(QGOODUID, ctladdr->q_flags)))
+ flags |= SFF_SETUIDOK;
+
+ if (!bitnset(DBS_FILEDELIVERYTOSYMLINK, DontBlameSendmail))
+ flags |= SFF_NOSLINK;
+ if (!bitnset(DBS_FILEDELIVERYTOHARDLINK, DontBlameSendmail))
+ flags |= SFF_NOHLINK;
+
+ errno = safefile(filename, euid, egid, user, flags, S_IWRITE, NULL);
+ return errno == 0;
+}
+/*
+** INCLUDE -- handle :include: specification.
+**
+** Parameters:
+** fname -- filename to include.
+** forwarding -- if true, we are reading a .forward file.
+** if false, it's a :include: file.
+** ctladdr -- address template to use to fill in these
+** addresses -- effective user/group id are
+** the important things.
+** sendq -- a pointer to the head of the send queue
+** to put these addresses in.
+** aliaslevel -- the alias nesting depth.
+** e -- the current envelope.
+**
+** Returns:
+** open error status
+**
+** Side Effects:
+** reads the :include: file and sends to everyone
+** listed in that file.
+**
+** Security Note:
+** If you have restricted chown (that is, you can't
+** give a file away), it is reasonable to allow programs
+** and files called from this :include: file to be to be
+** run as the owner of the :include: file. This is bogus
+** if there is any chance of someone giving away a file.
+** We assume that pre-POSIX systems can give away files.
+**
+** There is an additional restriction that if you
+** forward to a :include: file, it will not take on
+** the ownership of the :include: file. This may not
+** be necessary, but shouldn't hurt.
+*/
+
+static jmp_buf CtxIncludeTimeout;
+
+int
+include(fname, forwarding, ctladdr, sendq, aliaslevel, e)
+ char *fname;
+ bool forwarding;
+ ADDRESS *ctladdr;
+ ADDRESS **sendq;
+ int aliaslevel;
+ ENVELOPE *e;
+{
+ SM_FILE_T *volatile fp = NULL;
+ char *oldto = e->e_to;
+ char *oldfilename = FileName;
+ int oldlinenumber = LineNumber;
+ register SM_EVENT *ev = NULL;
+ int nincludes;
+ int mode;
+ volatile bool maxreached = false;
+ register ADDRESS *ca;
+ volatile uid_t saveduid;
+ volatile gid_t savedgid;
+ volatile uid_t uid;
+ volatile gid_t gid;
+ char *volatile user;
+ int rval = 0;
+ volatile long sfflags = SFF_REGONLY;
+ register char *p;
+ bool safechown = false;
+ volatile bool safedir = false;
+ struct stat st;
+ char buf[MAXLINE];
+
+ if (tTd(27, 2))
+ sm_dprintf("include(%s)\n", fname);
+ if (tTd(27, 4))
+ sm_dprintf(" ruid=%d euid=%d\n",
+ (int) getuid(), (int) geteuid());
+ if (tTd(27, 14))
+ {
+ sm_dprintf("ctladdr ");
+ printaddr(sm_debug_file(), ctladdr, false);
+ }
+
+ if (tTd(27, 9))
+ sm_dprintf("include: old uid = %d/%d\n",
+ (int) getuid(), (int) geteuid());
+
+ if (forwarding)
+ {
+ sfflags |= SFF_MUSTOWN|SFF_ROOTOK;
+ if (!bitnset(DBS_GROUPWRITABLEFORWARDFILE, DontBlameSendmail))
+ sfflags |= SFF_NOGWFILES;
+ if (!bitnset(DBS_WORLDWRITABLEFORWARDFILE, DontBlameSendmail))
+ sfflags |= SFF_NOWWFILES;
+ }
+ else
+ {
+ if (!bitnset(DBS_GROUPWRITABLEINCLUDEFILE, DontBlameSendmail))
+ sfflags |= SFF_NOGWFILES;
+ if (!bitnset(DBS_WORLDWRITABLEINCLUDEFILE, DontBlameSendmail))
+ sfflags |= SFF_NOWWFILES;
+ }
+
+ /*
+ ** If RunAsUser set, won't be able to run programs as user
+ ** so mark them as unsafe unless the administrator knows better.
+ */
+
+ if ((geteuid() != 0 || RunAsUid != 0) &&
+ !bitnset(DBS_NONROOTSAFEADDR, DontBlameSendmail))
+ {
+ if (tTd(27, 4))
+ sm_dprintf("include: not safe (euid=%d, RunAsUid=%d)\n",
+ (int) geteuid(), (int) RunAsUid);
+ ctladdr->q_flags |= QUNSAFEADDR;
+ }
+
+ ca = getctladdr(ctladdr);
+ if (ca == NULL ||
+ (ca->q_uid == DefUid && ca->q_gid == 0))
+ {
+ uid = DefUid;
+ gid = DefGid;
+ user = DefUser;
+ }
+ else
+ {
+ uid = ca->q_uid;
+ gid = ca->q_gid;
+ user = ca->q_user;
+ }
+#if MAILER_SETUID_METHOD != USE_SETUID
+ saveduid = geteuid();
+ savedgid = getegid();
+ if (saveduid == 0)
+ {
+ if (!DontInitGroups)
+ {
+ if (initgroups(user, gid) == -1)
+ {
+ rval = EAGAIN;
+ syserr("include: initgroups(%s, %d) failed",
+ user, gid);
+ goto resetuid;
+ }
+ }
+ else
+ {
+ GIDSET_T gidset[1];
+
+ gidset[0] = gid;
+ if (setgroups(1, gidset) == -1)
+ {
+ rval = EAGAIN;
+ syserr("include: setgroups() failed");
+ goto resetuid;
+ }
+ }
+
+ if (gid != 0 && setgid(gid) < -1)
+ {
+ rval = EAGAIN;
+ syserr("setgid(%d) failure", gid);
+ goto resetuid;
+ }
+ if (uid != 0)
+ {
+# if MAILER_SETUID_METHOD == USE_SETEUID
+ if (seteuid(uid) < 0)
+ {
+ rval = EAGAIN;
+ syserr("seteuid(%d) failure (real=%d, eff=%d)",
+ uid, (int) getuid(), (int) geteuid());
+ goto resetuid;
+ }
+# endif /* MAILER_SETUID_METHOD == USE_SETEUID */
+# if MAILER_SETUID_METHOD == USE_SETREUID
+ if (setreuid(0, uid) < 0)
+ {
+ rval = EAGAIN;
+ syserr("setreuid(0, %d) failure (real=%d, eff=%d)",
+ uid, (int) getuid(), (int) geteuid());
+ goto resetuid;
+ }
+# endif /* MAILER_SETUID_METHOD == USE_SETREUID */
+ }
+ }
+#endif /* MAILER_SETUID_METHOD != USE_SETUID */
+
+ if (tTd(27, 9))
+ sm_dprintf("include: new uid = %d/%d\n",
+ (int) getuid(), (int) geteuid());
+
+ /*
+ ** If home directory is remote mounted but server is down,
+ ** this can hang or give errors; use a timeout to avoid this
+ */
+
+ if (setjmp(CtxIncludeTimeout) != 0)
+ {
+ ctladdr->q_state = QS_QUEUEUP;
+ errno = 0;
+
+ /* return pseudo-error code */
+ rval = E_SM_OPENTIMEOUT;
+ goto resetuid;
+ }
+ if (TimeOuts.to_fileopen > 0)
+ ev = sm_setevent(TimeOuts.to_fileopen, includetimeout, 0);
+ else
+ ev = NULL;
+
+
+ /* check for writable parent directory */
+ p = strrchr(fname, '/');
+ if (p != NULL)
+ {
+ int ret;
+
+ *p = '\0';
+ ret = safedirpath(fname, uid, gid, user,
+ sfflags|SFF_SAFEDIRPATH, 0, 0);
+ if (ret == 0)
+ {
+ /* in safe directory: relax chown & link rules */
+ safedir = true;
+ sfflags |= SFF_NOPATHCHECK;
+ }
+ else
+ {
+ if (bitnset((forwarding ?
+ DBS_FORWARDFILEINUNSAFEDIRPATH :
+ DBS_INCLUDEFILEINUNSAFEDIRPATH),
+ DontBlameSendmail))
+ sfflags |= SFF_NOPATHCHECK;
+ else if (bitnset((forwarding ?
+ DBS_FORWARDFILEINGROUPWRITABLEDIRPATH :
+ DBS_INCLUDEFILEINGROUPWRITABLEDIRPATH),
+ DontBlameSendmail) &&
+ ret == E_SM_GWDIR)
+ {
+ setbitn(DBS_GROUPWRITABLEDIRPATHSAFE,
+ DontBlameSendmail);
+ ret = safedirpath(fname, uid, gid, user,
+ sfflags|SFF_SAFEDIRPATH,
+ 0, 0);
+ clrbitn(DBS_GROUPWRITABLEDIRPATHSAFE,
+ DontBlameSendmail);
+ if (ret == 0)
+ sfflags |= SFF_NOPATHCHECK;
+ else
+ sfflags |= SFF_SAFEDIRPATH;
+ }
+ else
+ sfflags |= SFF_SAFEDIRPATH;
+ if (ret > E_PSEUDOBASE &&
+ !bitnset((forwarding ?
+ DBS_FORWARDFILEINUNSAFEDIRPATHSAFE :
+ DBS_INCLUDEFILEINUNSAFEDIRPATHSAFE),
+ DontBlameSendmail))
+ {
+ if (LogLevel > 11)
+ sm_syslog(LOG_INFO, e->e_id,
+ "%s: unsafe directory path, marked unsafe",
+ shortenstring(fname, MAXSHORTSTR));
+ ctladdr->q_flags |= QUNSAFEADDR;
+ }
+ }
+ *p = '/';
+ }
+
+ /* allow links only in unwritable directories */
+ if (!safedir &&
+ !bitnset((forwarding ?
+ DBS_LINKEDFORWARDFILEINWRITABLEDIR :
+ DBS_LINKEDINCLUDEFILEINWRITABLEDIR),
+ DontBlameSendmail))
+ sfflags |= SFF_NOLINK;
+
+ rval = safefile(fname, uid, gid, user, sfflags, S_IREAD, &st);
+ if (rval != 0)
+ {
+ /* don't use this :include: file */
+ if (tTd(27, 4))
+ sm_dprintf("include: not safe (uid=%d): %s\n",
+ (int) uid, sm_errstring(rval));
+ }
+ else if ((fp = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, fname,
+ SM_IO_RDONLY, NULL)) == NULL)
+ {
+ rval = errno;
+ if (tTd(27, 4))
+ sm_dprintf("include: open: %s\n", sm_errstring(rval));
+ }
+ else if (filechanged(fname, sm_io_getinfo(fp,SM_IO_WHAT_FD, NULL), &st))
+ {
+ rval = E_SM_FILECHANGE;
+ if (tTd(27, 4))
+ sm_dprintf("include: file changed after open\n");
+ }
+ if (ev != NULL)
+ sm_clrevent(ev);
+
+resetuid:
+
+#if HASSETREUID || USESETEUID
+ if (saveduid == 0)
+ {
+ if (uid != 0)
+ {
+# if USESETEUID
+ if (seteuid(0) < 0)
+ syserr("!seteuid(0) failure (real=%d, eff=%d)",
+ (int) getuid(), (int) geteuid());
+# else /* USESETEUID */
+ if (setreuid(-1, 0) < 0)
+ syserr("!setreuid(-1, 0) failure (real=%d, eff=%d)",
+ (int) getuid(), (int) geteuid());
+ if (setreuid(RealUid, 0) < 0)
+ syserr("!setreuid(%d, 0) failure (real=%d, eff=%d)",
+ (int) RealUid, (int) getuid(),
+ (int) geteuid());
+# endif /* USESETEUID */
+ }
+ if (setgid(savedgid) < 0)
+ syserr("!setgid(%d) failure (real=%d eff=%d)",
+ (int) savedgid, (int) getgid(),
+ (int) getegid());
+ }
+#endif /* HASSETREUID || USESETEUID */
+
+ if (tTd(27, 9))
+ sm_dprintf("include: reset uid = %d/%d\n",
+ (int) getuid(), (int) geteuid());
+
+ if (rval == E_SM_OPENTIMEOUT)
+ usrerr("451 4.4.1 open timeout on %s", fname);
+
+ if (fp == NULL)
+ return rval;
+
+ if (fstat(sm_io_getinfo(fp, SM_IO_WHAT_FD, NULL), &st) < 0)
+ {
+ rval = errno;
+ syserr("Cannot fstat %s!", fname);
+ (void) sm_io_close(fp, SM_TIME_DEFAULT);
+ return rval;
+ }
+
+ /* if path was writable, check to avoid file giveaway tricks */
+ safechown = chownsafe(sm_io_getinfo(fp, SM_IO_WHAT_FD, NULL), safedir);
+ if (tTd(27, 6))
+ sm_dprintf("include: parent of %s is %s, chown is %ssafe\n",
+ fname, safedir ? "safe" : "dangerous",
+ safechown ? "" : "un");
+
+ /* if no controlling user or coming from an alias delivery */
+ if (safechown &&
+ (ca == NULL ||
+ (ca->q_uid == DefUid && ca->q_gid == 0)))
+ {
+ ctladdr->q_uid = st.st_uid;
+ ctladdr->q_gid = st.st_gid;
+ ctladdr->q_flags |= QGOODUID;
+ }
+ if (ca != NULL && ca->q_uid == st.st_uid)
+ {
+ /* optimization -- avoid getpwuid if we already have info */
+ ctladdr->q_flags |= ca->q_flags & QBOGUSSHELL;
+ ctladdr->q_ruser = ca->q_ruser;
+ }
+ else if (!forwarding)
+ {
+ register struct passwd *pw;
+
+ pw = sm_getpwuid(st.st_uid);
+ if (pw == NULL)
+ {
+ ctladdr->q_uid = st.st_uid;
+ ctladdr->q_flags |= QBOGUSSHELL;
+ }
+ else
+ {
+ char *sh;
+
+ ctladdr->q_ruser = sm_rpool_strdup_x(e->e_rpool,
+ pw->pw_name);
+ if (safechown)
+ sh = pw->pw_shell;
+ else
+ sh = "/SENDMAIL/ANY/SHELL/";
+ if (!usershellok(pw->pw_name, sh))
+ {
+ if (LogLevel > 11)
+ sm_syslog(LOG_INFO, e->e_id,
+ "%s: user %s has bad shell %s, marked %s",
+ shortenstring(fname,
+ MAXSHORTSTR),
+ pw->pw_name, sh,
+ safechown ? "bogus" : "unsafe");
+ if (safechown)
+ ctladdr->q_flags |= QBOGUSSHELL;
+ else
+ ctladdr->q_flags |= QUNSAFEADDR;
+ }
+ }
+ }
+
+ if (bitset(EF_VRFYONLY, e->e_flags))
+ {
+ /* don't do any more now */
+ ctladdr->q_state = QS_VERIFIED;
+ e->e_nrcpts++;
+ (void) sm_io_close(fp, SM_TIME_DEFAULT);
+ return rval;
+ }
+
+ /*
+ ** Check to see if some bad guy can write this file
+ **
+ ** Group write checking could be more clever, e.g.,
+ ** guessing as to which groups are actually safe ("sys"
+ ** may be; "user" probably is not).
+ */
+
+ mode = S_IWOTH;
+ if (!bitnset((forwarding ?
+ DBS_GROUPWRITABLEFORWARDFILESAFE :
+ DBS_GROUPWRITABLEINCLUDEFILESAFE),
+ DontBlameSendmail))
+ mode |= S_IWGRP;
+
+ if (bitset(mode, st.st_mode))
+ {
+ if (tTd(27, 6))
+ sm_dprintf("include: %s is %s writable, marked unsafe\n",
+ shortenstring(fname, MAXSHORTSTR),
+ bitset(S_IWOTH, st.st_mode) ? "world"
+ : "group");
+ if (LogLevel > 11)
+ sm_syslog(LOG_INFO, e->e_id,
+ "%s: %s writable %s file, marked unsafe",
+ shortenstring(fname, MAXSHORTSTR),
+ bitset(S_IWOTH, st.st_mode) ? "world" : "group",
+ forwarding ? "forward" : ":include:");
+ ctladdr->q_flags |= QUNSAFEADDR;
+ }
+
+ /* read the file -- each line is a comma-separated list. */
+ FileName = fname;
+ LineNumber = 0;
+ ctladdr->q_flags &= ~QSELFREF;
+ nincludes = 0;
+ while (sm_io_fgets(fp, SM_TIME_DEFAULT, buf, sizeof buf) != NULL &&
+ !maxreached)
+ {
+ fixcrlf(buf, true);
+ LineNumber++;
+ if (buf[0] == '#' || buf[0] == '\0')
+ continue;
+
+ /* <sp>#@# introduces a comment anywhere */
+ /* for Japanese character sets */
+ for (p = buf; (p = strchr(++p, '#')) != NULL; )
+ {
+ if (p[1] == '@' && p[2] == '#' &&
+ isascii(p[-1]) && isspace(p[-1]) &&
+ (p[3] == '\0' || (isascii(p[3]) && isspace(p[3]))))
+ {
+ --p;
+ while (p > buf && isascii(p[-1]) &&
+ isspace(p[-1]))
+ --p;
+ p[0] = '\0';
+ break;
+ }
+ }
+ if (buf[0] == '\0')
+ continue;
+
+ e->e_to = NULL;
+ message("%s to %s",
+ forwarding ? "forwarding" : "sending", buf);
+ if (forwarding && LogLevel > 10)
+ sm_syslog(LOG_INFO, e->e_id,
+ "forward %.200s => %s",
+ oldto, shortenstring(buf, MAXSHORTSTR));
+
+ nincludes += sendtolist(buf, ctladdr, sendq, aliaslevel + 1, e);
+
+ if (forwarding &&
+ MaxForwardEntries > 0 &&
+ nincludes >= MaxForwardEntries)
+ {
+ /* just stop reading and processing further entries */
+#if 0
+ /* additional: (?) */
+ ctladdr->q_state = QS_DONTSEND;
+#endif /* 0 */
+
+ syserr("Attempt to forward to more than %d addresses (in %s)!",
+ MaxForwardEntries, fname);
+ maxreached = true;
+ }
+ }
+
+ if (sm_io_error(fp) && tTd(27, 3))
+ sm_dprintf("include: read error: %s\n", sm_errstring(errno));
+ if (nincludes > 0 && !bitset(QSELFREF, ctladdr->q_flags))
+ {
+ if (tTd(27, 5))
+ {
+ sm_dprintf("include: QS_DONTSEND ");
+ printaddr(sm_debug_file(), ctladdr, false);
+ }
+ ctladdr->q_state = QS_DONTSEND;
+ }
+
+ (void) sm_io_close(fp, SM_TIME_DEFAULT);
+ FileName = oldfilename;
+ LineNumber = oldlinenumber;
+ e->e_to = oldto;
+ return rval;
+}
+
+static void
+includetimeout(ignore)
+ int ignore;
+{
+ /*
+ ** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+ ** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+ ** DOING.
+ */
+
+ errno = ETIMEDOUT;
+ longjmp(CtxIncludeTimeout, 1);
+}
+/*
+** SENDTOARGV -- send to an argument vector.
+**
+** Parameters:
+** argv -- argument vector to send to.
+** e -- the current envelope.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** puts all addresses on the argument vector onto the
+** send queue.
+*/
+
+void
+sendtoargv(argv, e)
+ register char **argv;
+ register ENVELOPE *e;
+{
+ register char *p;
+
+ while ((p = *argv++) != NULL)
+ (void) sendtolist(p, NULLADDR, &e->e_sendqueue, 0, e);
+}
+/*
+** GETCTLADDR -- get controlling address from an address header.
+**
+** If none, get one corresponding to the effective userid.
+**
+** Parameters:
+** a -- the address to find the controller of.
+**
+** Returns:
+** the controlling address.
+*/
+
+ADDRESS *
+getctladdr(a)
+ register ADDRESS *a;
+{
+ while (a != NULL && !bitset(QGOODUID, a->q_flags))
+ a = a->q_alias;
+ return a;
+}
+/*
+** SELF_REFERENCE -- check to see if an address references itself
+**
+** The check is done through a chain of aliases. If it is part of
+** a loop, break the loop at the "best" address, that is, the one
+** that exists as a real user.
+**
+** This is to handle the case of:
+** awc: Andrew.Chang
+** Andrew.Chang: awc@mail.server
+** which is a problem only on mail.server.
+**
+** Parameters:
+** a -- the address to check.
+**
+** Returns:
+** The address that should be retained.
+*/
+
+static ADDRESS *
+self_reference(a)
+ ADDRESS *a;
+{
+ ADDRESS *b; /* top entry in self ref loop */
+ ADDRESS *c; /* entry that point to a real mail box */
+
+ if (tTd(27, 1))
+ sm_dprintf("self_reference(%s)\n", a->q_paddr);
+
+ for (b = a->q_alias; b != NULL; b = b->q_alias)
+ {
+ if (sameaddr(a, b))
+ break;
+ }
+
+ if (b == NULL)
+ {
+ if (tTd(27, 1))
+ sm_dprintf("\t... no self ref\n");
+ return NULL;
+ }
+
+ /*
+ ** Pick the first address that resolved to a real mail box
+ ** i.e has a mbdb entry. The returned value will be marked
+ ** QSELFREF in recipient(), which in turn will disable alias()
+ ** from marking it as QS_IS_DEAD(), which mean it will be used
+ ** as a deliverable address.
+ **
+ ** The 2 key thing to note here are:
+ ** 1) we are in a recursive call sequence:
+ ** alias->sendtolist->recipient->alias
+ ** 2) normally, when we return back to alias(), the address
+ ** will be marked QS_EXPANDED, since alias() assumes the
+ ** expanded form will be used instead of the current address.
+ ** This behaviour is turned off if the address is marked
+ ** QSELFREF. We set QSELFREF when we return to recipient().
+ */
+
+ c = a;
+ while (c != NULL)
+ {
+ if (tTd(27, 10))
+ sm_dprintf(" %s", c->q_user);
+ if (bitnset(M_HASPWENT, c->q_mailer->m_flags))
+ {
+ SM_MBDB_T user;
+
+ if (tTd(27, 2))
+ sm_dprintf("\t... getpwnam(%s)... ", c->q_user);
+ if (sm_mbdb_lookup(c->q_user, &user) == EX_OK)
+ {
+ if (tTd(27, 2))
+ sm_dprintf("found\n");
+
+ /* ought to cache results here */
+ if (sameaddr(b, c))
+ return b;
+ else
+ return c;
+ }
+ if (tTd(27, 2))
+ sm_dprintf("failed\n");
+ }
+ else
+ {
+ /* if local delivery, compare usernames */
+ if (bitnset(M_LOCALMAILER, c->q_mailer->m_flags) &&
+ b->q_mailer == c->q_mailer)
+ {
+ if (tTd(27, 2))
+ sm_dprintf("\t... local match (%s)\n",
+ c->q_user);
+ if (sameaddr(b, c))
+ return b;
+ else
+ return c;
+ }
+ }
+ if (tTd(27, 10))
+ sm_dprintf("\n");
+ c = c->q_alias;
+ }
+
+ if (tTd(27, 1))
+ sm_dprintf("\t... cannot break loop for \"%s\"\n", a->q_paddr);
+
+ return NULL;
+}
diff --git a/usr/src/cmd/sendmail/src/savemail.c b/usr/src/cmd/sendmail/src/savemail.c
new file mode 100644
index 0000000000..294cfa1cb7
--- /dev/null
+++ b/usr/src/cmd/sendmail/src/savemail.c
@@ -0,0 +1,1692 @@
+/*
+ * Copyright (c) 1998-2003 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sendmail.h>
+
+SM_RCSID("@(#)$Id: savemail.c,v 8.304 2004/10/06 21:36:06 ca Exp $")
+
+static void errbody __P((MCI *, ENVELOPE *, char *));
+static bool pruneroute __P((char *));
+
+/*
+** SAVEMAIL -- Save mail on error
+**
+** If mailing back errors, mail it back to the originator
+** together with an error message; otherwise, just put it in
+** dead.letter in the user's home directory (if he exists on
+** this machine).
+**
+** Parameters:
+** e -- the envelope containing the message in error.
+** sendbody -- if true, also send back the body of the
+** message; otherwise just send the header.
+**
+** Returns:
+** true if savemail panic'ed, (i.e., the data file should
+** be preserved by dropenvelope())
+**
+** Side Effects:
+** Saves the letter, by writing or mailing it back to the
+** sender, or by putting it in dead.letter in her home
+** directory.
+*/
+
+/* defines for state machine */
+#define ESM_REPORT 0 /* report to sender's terminal */
+#define ESM_MAIL 1 /* mail back to sender */
+#define ESM_QUIET 2 /* mail has already been returned */
+#define ESM_DEADLETTER 3 /* save in ~/dead.letter */
+#define ESM_POSTMASTER 4 /* return to postmaster */
+#define ESM_DEADLETTERDROP 5 /* save in DeadLetterDrop */
+#define ESM_PANIC 6 /* call loseqfile() */
+#define ESM_DONE 7 /* message is successfully delivered */
+
+bool
+savemail(e, sendbody)
+ register ENVELOPE *e;
+ bool sendbody;
+{
+ register SM_FILE_T *fp;
+ bool panic = false;
+ int state;
+ auto ADDRESS *q = NULL;
+ register char *p;
+ MCI mcibuf;
+ int flags;
+ long sff;
+ char buf[MAXLINE + 1];
+ char dlbuf[MAXPATHLEN];
+ SM_MBDB_T user;
+
+
+ if (tTd(6, 1))
+ {
+ sm_dprintf("\nsavemail, errormode = %c, id = %s, ExitStat = %d\n e_from=",
+ e->e_errormode, e->e_id == NULL ? "NONE" : e->e_id,
+ ExitStat);
+ printaddr(sm_debug_file(), &e->e_from, false);
+ }
+
+ if (e->e_id == NULL)
+ {
+ /* can't return a message with no id */
+ return panic;
+ }
+
+ /*
+ ** In the unhappy event we don't know who to return the mail
+ ** to, make someone up.
+ */
+
+ if (e->e_from.q_paddr == NULL)
+ {
+ e->e_sender = "Postmaster";
+ if (parseaddr(e->e_sender, &e->e_from,
+ RF_COPYPARSE|RF_SENDERADDR,
+ '\0', NULL, e, false) == NULL)
+ {
+ syserr("553 5.3.5 Cannot parse Postmaster!");
+ finis(true, true, EX_SOFTWARE);
+ }
+ }
+ e->e_to = NULL;
+
+ /*
+ ** Basic state machine.
+ **
+ ** This machine runs through the following states:
+ **
+ ** ESM_QUIET Errors have already been printed iff the
+ ** sender is local.
+ ** ESM_REPORT Report directly to the sender's terminal.
+ ** ESM_MAIL Mail response to the sender.
+ ** ESM_DEADLETTER Save response in ~/dead.letter.
+ ** ESM_POSTMASTER Mail response to the postmaster.
+ ** ESM_DEADLETTERDROP
+ ** If DeadLetterDrop set, save it there.
+ ** ESM_PANIC Save response anywhere possible.
+ */
+
+ /* determine starting state */
+ switch (e->e_errormode)
+ {
+ case EM_WRITE:
+ state = ESM_REPORT;
+ break;
+
+ case EM_BERKNET:
+ case EM_MAIL:
+ state = ESM_MAIL;
+ break;
+
+ case EM_PRINT:
+ case '\0':
+ state = ESM_QUIET;
+ break;
+
+ case EM_QUIET:
+ /* no need to return anything at all */
+ return panic;
+
+ default:
+ syserr("554 5.3.0 savemail: bogus errormode x%x",
+ e->e_errormode);
+ state = ESM_MAIL;
+ break;
+ }
+
+ /* if this is already an error response, send to postmaster */
+ if (bitset(EF_RESPONSE, e->e_flags))
+ {
+ if (e->e_parent != NULL &&
+ bitset(EF_RESPONSE, e->e_parent->e_flags))
+ {
+ /* got an error sending a response -- can it */
+ return panic;
+ }
+ state = ESM_POSTMASTER;
+ }
+
+ while (state != ESM_DONE)
+ {
+ if (tTd(6, 5))
+ sm_dprintf(" state %d\n", state);
+
+ switch (state)
+ {
+ case ESM_QUIET:
+ if (bitnset(M_LOCALMAILER, e->e_from.q_mailer->m_flags))
+ state = ESM_DEADLETTER;
+ else
+ state = ESM_MAIL;
+ break;
+
+ case ESM_REPORT:
+
+ /*
+ ** If the user is still logged in on the same terminal,
+ ** then write the error messages back to hir (sic).
+ */
+
+#if USE_TTYPATH
+ p = ttypath();
+#else /* USE_TTYPATH */
+ p = NULL;
+#endif /* USE_TTYPATH */
+
+ if (p == NULL || sm_io_reopen(SmFtStdio,
+ SM_TIME_DEFAULT,
+ p, SM_IO_WRONLY, NULL,
+ smioout) == NULL)
+ {
+ state = ESM_MAIL;
+ break;
+ }
+
+ expand("\201n", buf, sizeof buf, e);
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "\r\nMessage from %s...\r\n", buf);
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Errors occurred while sending mail.\r\n");
+ if (e->e_xfp != NULL)
+ {
+ (void) bfrewind(e->e_xfp);
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Transcript follows:\r\n");
+ while (sm_io_fgets(e->e_xfp, SM_TIME_DEFAULT,
+ buf, sizeof buf) != NULL &&
+ !sm_io_error(smioout))
+ (void) sm_io_fputs(smioout,
+ SM_TIME_DEFAULT,
+ buf);
+ }
+ else
+ {
+ syserr("Cannot open %s",
+ queuename(e, XSCRPT_LETTER));
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Transcript of session is unavailable.\r\n");
+ }
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Original message will be saved in dead.letter.\r\n");
+ state = ESM_DEADLETTER;
+ break;
+
+ case ESM_MAIL:
+ /*
+ ** If mailing back, do it.
+ ** Throw away all further output. Don't alias,
+ ** since this could cause loops, e.g., if joe
+ ** mails to joe@x, and for some reason the network
+ ** for @x is down, then the response gets sent to
+ ** joe@x, which gives a response, etc. Also force
+ ** the mail to be delivered even if a version of
+ ** it has already been sent to the sender.
+ **
+ ** If this is a configuration or local software
+ ** error, send to the local postmaster as well,
+ ** since the originator can't do anything
+ ** about it anyway. Note that this is a full
+ ** copy of the message (intentionally) so that
+ ** the Postmaster can forward things along.
+ */
+
+ if (ExitStat == EX_CONFIG || ExitStat == EX_SOFTWARE)
+ {
+ (void) sendtolist("postmaster", NULLADDR,
+ &e->e_errorqueue, 0, e);
+ }
+ if (!emptyaddr(&e->e_from))
+ {
+ char from[TOBUFSIZE];
+
+ if (sm_strlcpy(from, e->e_from.q_paddr,
+ sizeof from) >= sizeof from)
+ {
+ state = ESM_POSTMASTER;
+ break;
+ }
+
+ if (!DontPruneRoutes)
+ (void) pruneroute(from);
+
+ (void) sendtolist(from, NULLADDR,
+ &e->e_errorqueue, 0, e);
+ }
+
+ /*
+ ** Deliver a non-delivery report to the
+ ** Postmaster-designate (not necessarily
+ ** Postmaster). This does not include the
+ ** body of the message, for privacy reasons.
+ ** You really shouldn't need this.
+ */
+
+ e->e_flags |= EF_PM_NOTIFY;
+
+ /* check to see if there are any good addresses */
+ for (q = e->e_errorqueue; q != NULL; q = q->q_next)
+ {
+ if (QS_IS_SENDABLE(q->q_state))
+ break;
+ }
+ if (q == NULL)
+ {
+ /* this is an error-error */
+ state = ESM_POSTMASTER;
+ break;
+ }
+ if (returntosender(e->e_message, e->e_errorqueue,
+ sendbody ? RTSF_SEND_BODY
+ : RTSF_NO_BODY,
+ e) == 0)
+ {
+ state = ESM_DONE;
+ break;
+ }
+
+ /* didn't work -- return to postmaster */
+ state = ESM_POSTMASTER;
+ break;
+
+ case ESM_POSTMASTER:
+ /*
+ ** Similar to previous case, but to system postmaster.
+ */
+
+ q = NULL;
+ expand(DoubleBounceAddr, buf, sizeof buf, e);
+
+ /*
+ ** Just drop it on the floor if DoubleBounceAddr
+ ** expands to an empty string.
+ */
+
+ if (*buf == '\0')
+ {
+ state = ESM_DONE;
+ break;
+ }
+ if (sendtolist(buf, NULLADDR, &q, 0, e) <= 0)
+ {
+ syserr("553 5.3.0 cannot parse %s!", buf);
+ ExitStat = EX_SOFTWARE;
+ state = ESM_DEADLETTERDROP;
+ break;
+ }
+ flags = RTSF_PM_BOUNCE;
+ if (sendbody)
+ flags |= RTSF_SEND_BODY;
+ if (returntosender(e->e_message, q, flags, e) == 0)
+ {
+ state = ESM_DONE;
+ break;
+ }
+
+ /* didn't work -- last resort */
+ state = ESM_DEADLETTERDROP;
+ break;
+
+ case ESM_DEADLETTER:
+ /*
+ ** Save the message in dead.letter.
+ ** If we weren't mailing back, and the user is
+ ** local, we should save the message in
+ ** ~/dead.letter so that the poor person doesn't
+ ** have to type it over again -- and we all know
+ ** what poor typists UNIX users are.
+ */
+
+ p = NULL;
+ if (bitnset(M_HASPWENT, e->e_from.q_mailer->m_flags))
+ {
+ if (e->e_from.q_home != NULL)
+ p = e->e_from.q_home;
+ else if (sm_mbdb_lookup(e->e_from.q_user, &user)
+ == EX_OK &&
+ *user.mbdb_homedir != '\0')
+ p = user.mbdb_homedir;
+ }
+ if (p == NULL || e->e_dfp == NULL)
+ {
+ /* no local directory or no data file */
+ state = ESM_MAIL;
+ break;
+ }
+
+ /* we have a home directory; write dead.letter */
+ macdefine(&e->e_macro, A_TEMP, 'z', p);
+
+ /* get the sender for the UnixFromLine */
+ p = macvalue('g', e);
+ macdefine(&e->e_macro, A_PERM, 'g', e->e_sender);
+
+ expand("\201z/dead.letter", dlbuf, sizeof dlbuf, e);
+ sff = SFF_CREAT|SFF_REGONLY|SFF_RUNASREALUID;
+ if (RealUid == 0)
+ sff |= SFF_ROOTOK;
+ e->e_to = dlbuf;
+ if (writable(dlbuf, NULL, sff) &&
+ mailfile(dlbuf, FileMailer, NULL, sff, e) == EX_OK)
+ {
+ int oldverb = Verbose;
+
+ if (OpMode != MD_DAEMON && OpMode != MD_SMTP)
+ Verbose = 1;
+ if (Verbose > 0)
+ message("Saved message in %s", dlbuf);
+ Verbose = oldverb;
+ macdefine(&e->e_macro, A_PERM, 'g', p);
+ state = ESM_DONE;
+ break;
+ }
+ macdefine(&e->e_macro, A_PERM, 'g', p);
+ state = ESM_MAIL;
+ break;
+
+ case ESM_DEADLETTERDROP:
+ /*
+ ** Log the mail in DeadLetterDrop file.
+ */
+
+ if (e->e_class < 0)
+ {
+ state = ESM_DONE;
+ break;
+ }
+
+ if ((SafeFileEnv != NULL && SafeFileEnv[0] != '\0') ||
+ DeadLetterDrop == NULL ||
+ DeadLetterDrop[0] == '\0')
+ {
+ state = ESM_PANIC;
+ break;
+ }
+
+ sff = SFF_CREAT|SFF_REGONLY|SFF_ROOTOK|SFF_OPENASROOT|SFF_MUSTOWN;
+ if (!writable(DeadLetterDrop, NULL, sff) ||
+ (fp = safefopen(DeadLetterDrop, O_WRONLY|O_APPEND,
+ FileMode, sff)) == NULL)
+ {
+ state = ESM_PANIC;
+ break;
+ }
+
+ memset(&mcibuf, '\0', sizeof mcibuf);
+ mcibuf.mci_out = fp;
+ mcibuf.mci_mailer = FileMailer;
+ if (bitnset(M_7BITS, FileMailer->m_flags))
+ mcibuf.mci_flags |= MCIF_7BIT;
+
+ /* get the sender for the UnixFromLine */
+ p = macvalue('g', e);
+ macdefine(&e->e_macro, A_PERM, 'g', e->e_sender);
+
+ putfromline(&mcibuf, e);
+ (*e->e_puthdr)(&mcibuf, e->e_header, e, M87F_OUTER);
+ (*e->e_putbody)(&mcibuf, e, NULL);
+ putline("\n", &mcibuf); /* XXX EOL from FileMailer? */
+ (void) sm_io_flush(fp, SM_TIME_DEFAULT);
+ if (sm_io_error(fp) ||
+ sm_io_close(fp, SM_TIME_DEFAULT) < 0)
+ state = ESM_PANIC;
+ else
+ {
+ int oldverb = Verbose;
+
+ if (OpMode != MD_DAEMON && OpMode != MD_SMTP)
+ Verbose = 1;
+ if (Verbose > 0)
+ message("Saved message in %s",
+ DeadLetterDrop);
+ Verbose = oldverb;
+ if (LogLevel > 3)
+ sm_syslog(LOG_NOTICE, e->e_id,
+ "Saved message in %s",
+ DeadLetterDrop);
+ state = ESM_DONE;
+ }
+ macdefine(&e->e_macro, A_PERM, 'g', p);
+ break;
+
+ default:
+ syserr("554 5.3.5 savemail: unknown state %d", state);
+ /* FALLTHROUGH */
+
+ case ESM_PANIC:
+ /* leave the locked queue & transcript files around */
+ loseqfile(e, "savemail panic");
+ panic = true;
+ errno = 0;
+ syserr("554 savemail: cannot save rejected email anywhere");
+ state = ESM_DONE;
+ break;
+ }
+ }
+ return panic;
+}
+/*
+** RETURNTOSENDER -- return a message to the sender with an error.
+**
+** Parameters:
+** msg -- the explanatory message.
+** returnq -- the queue of people to send the message to.
+** flags -- flags tweaking the operation:
+** RTSF_SENDBODY -- include body of message (otherwise
+** just send the header).
+** RTSF_PMBOUNCE -- this is a postmaster bounce.
+** e -- the current envelope.
+**
+** Returns:
+** zero -- if everything went ok.
+** else -- some error.
+**
+** Side Effects:
+** Returns the current message to the sender via mail.
+*/
+
+#define MAXRETURNS 6 /* max depth of returning messages */
+#define ERRORFUDGE 1024 /* nominal size of error message text */
+
+int
+returntosender(msg, returnq, flags, e)
+ char *msg;
+ ADDRESS *returnq;
+ int flags;
+ register ENVELOPE *e;
+{
+ register ENVELOPE *ee;
+ ENVELOPE *oldcur = CurEnv;
+ ENVELOPE errenvelope;
+ static int returndepth = 0;
+ register ADDRESS *q;
+ char *p;
+ char buf[MAXNAME + 1];
+
+ if (returnq == NULL)
+ return -1;
+
+ if (msg == NULL)
+ msg = "Unable to deliver mail";
+
+ if (tTd(6, 1))
+ {
+ sm_dprintf("\n*** Return To Sender: msg=\"%s\", depth=%d, e=%p, returnq=",
+ msg, returndepth, e);
+ printaddr(sm_debug_file(), returnq, true);
+ if (tTd(6, 20))
+ {
+ sm_dprintf("Sendq=");
+ printaddr(sm_debug_file(), e->e_sendqueue, true);
+ }
+ }
+
+ if (++returndepth >= MAXRETURNS)
+ {
+ if (returndepth != MAXRETURNS)
+ syserr("554 5.3.0 returntosender: infinite recursion on %s",
+ returnq->q_paddr);
+ /* don't "unrecurse" and fake a clean exit */
+ /* returndepth--; */
+ return 0;
+ }
+
+ macdefine(&e->e_macro, A_PERM, 'g', e->e_sender);
+ macdefine(&e->e_macro, A_PERM, 'u', NULL);
+
+ /* initialize error envelope */
+ ee = newenvelope(&errenvelope, e, sm_rpool_new_x(NULL));
+ macdefine(&ee->e_macro, A_PERM, 'a', "\201b");
+ macdefine(&ee->e_macro, A_PERM, 'r', "");
+ macdefine(&ee->e_macro, A_PERM, 's', "localhost");
+ macdefine(&ee->e_macro, A_PERM, '_', "localhost");
+ clrsessenvelope(ee);
+
+ ee->e_puthdr = putheader;
+ ee->e_putbody = errbody;
+ ee->e_flags |= EF_RESPONSE|EF_METOO;
+ if (!bitset(EF_OLDSTYLE, e->e_flags))
+ ee->e_flags &= ~EF_OLDSTYLE;
+ if (bitset(EF_DONT_MIME, e->e_flags))
+ {
+ ee->e_flags |= EF_DONT_MIME;
+
+ /*
+ ** If we can't convert to MIME and we don't pass
+ ** 8-bit, we can't send the body.
+ */
+
+ if (bitset(EF_HAS8BIT, e->e_flags) &&
+ !bitset(MM_PASS8BIT, MimeMode))
+ flags &= ~RTSF_SEND_BODY;
+ }
+
+ ee->e_sendqueue = returnq;
+ ee->e_msgsize = 0;
+ if (bitset(RTSF_SEND_BODY, flags) &&
+ !bitset(PRIV_NOBODYRETN, PrivacyFlags))
+ ee->e_msgsize = ERRORFUDGE + e->e_msgsize;
+ else
+ ee->e_flags |= EF_NO_BODY_RETN;
+
+ if (!setnewqueue(ee))
+ {
+ syserr("554 5.3.0 returntosender: cannot select queue for %s",
+ returnq->q_paddr);
+ ExitStat = EX_UNAVAILABLE;
+ returndepth--;
+ return -1;
+ }
+ initsys(ee);
+
+#if NAMED_BIND
+ _res.retry = TimeOuts.res_retry[RES_TO_FIRST];
+ _res.retrans = TimeOuts.res_retrans[RES_TO_FIRST];
+#endif /* NAMED_BIND */
+ for (q = returnq; q != NULL; q = q->q_next)
+ {
+ if (QS_IS_BADADDR(q->q_state))
+ continue;
+
+ q->q_flags &= ~(QHASNOTIFY|Q_PINGFLAGS);
+ q->q_flags |= QPINGONFAILURE;
+
+ if (!QS_IS_DEAD(q->q_state))
+ ee->e_nrcpts++;
+
+ if (q->q_alias == NULL)
+ addheader("To", q->q_paddr, 0, ee);
+ }
+
+ if (LogLevel > 5)
+ {
+ if (bitset(EF_RESPONSE, e->e_flags))
+ p = "return to sender";
+ else if (bitset(EF_WARNING, e->e_flags))
+ p = "sender notify";
+ else if (bitset(RTSF_PM_BOUNCE, flags))
+ p = "postmaster notify";
+ else
+ p = "DSN";
+ sm_syslog(LOG_INFO, e->e_id, "%s: %s: %s",
+ ee->e_id, p, shortenstring(msg, MAXSHORTSTR));
+ }
+
+ if (SendMIMEErrors)
+ {
+ addheader("MIME-Version", "1.0", 0, ee);
+ (void) sm_snprintf(buf, sizeof buf, "%s.%ld/%.100s",
+ ee->e_id, (long)curtime(), MyHostName);
+ ee->e_msgboundary = sm_rpool_strdup_x(ee->e_rpool, buf);
+ (void) sm_snprintf(buf, sizeof buf,
+#if DSN
+ "multipart/report; report-type=delivery-status;\n\tboundary=\"%s\"",
+#else /* DSN */
+ "multipart/mixed; boundary=\"%s\"",
+#endif /* DSN */
+ ee->e_msgboundary);
+ addheader("Content-Type", buf, 0, ee);
+
+ p = hvalue("Content-Transfer-Encoding", e->e_header);
+ if (p != NULL && sm_strcasecmp(p, "binary") != 0)
+ p = NULL;
+ if (p == NULL && bitset(EF_HAS8BIT, e->e_flags))
+ p = "8bit";
+ if (p != NULL)
+ addheader("Content-Transfer-Encoding", p, 0, ee);
+ }
+ if (strncmp(msg, "Warning:", 8) == 0)
+ {
+ addheader("Subject", msg, 0, ee);
+ p = "warning-timeout";
+ }
+ else if (strncmp(msg, "Postmaster warning:", 19) == 0)
+ {
+ addheader("Subject", msg, 0, ee);
+ p = "postmaster-warning";
+ }
+ else if (strcmp(msg, "Return receipt") == 0)
+ {
+ addheader("Subject", msg, 0, ee);
+ p = "return-receipt";
+ }
+ else if (bitset(RTSF_PM_BOUNCE, flags))
+ {
+ (void) sm_snprintf(buf, sizeof buf,
+ "Postmaster notify: see transcript for details");
+ addheader("Subject", buf, 0, ee);
+ p = "postmaster-notification";
+ }
+ else
+ {
+ (void) sm_snprintf(buf, sizeof buf,
+ "Returned mail: see transcript for details");
+ addheader("Subject", buf, 0, ee);
+ p = "failure";
+ }
+ (void) sm_snprintf(buf, sizeof buf, "auto-generated (%s)", p);
+ addheader("Auto-Submitted", buf, 0, ee);
+
+ /* fake up an address header for the from person */
+ expand("\201n", buf, sizeof buf, e);
+ if (parseaddr(buf, &ee->e_from,
+ RF_COPYALL|RF_SENDERADDR, '\0', NULL, e, false) == NULL)
+ {
+ syserr("553 5.3.5 Can't parse myself!");
+ ExitStat = EX_SOFTWARE;
+ returndepth--;
+ return -1;
+ }
+ ee->e_from.q_flags &= ~(QHASNOTIFY|Q_PINGFLAGS);
+ ee->e_from.q_flags |= QPINGONFAILURE;
+ ee->e_sender = ee->e_from.q_paddr;
+
+ /* push state into submessage */
+ CurEnv = ee;
+ macdefine(&ee->e_macro, A_PERM, 'f', "\201n");
+ macdefine(&ee->e_macro, A_PERM, 'x', "Mail Delivery Subsystem");
+ eatheader(ee, true, true);
+
+ /* mark statistics */
+ markstats(ee, NULLADDR, STATS_NORMAL);
+
+ /* actually deliver the error message */
+ sendall(ee, SM_DELIVER);
+
+ /* restore state */
+ dropenvelope(ee, true, false);
+ sm_rpool_free(ee->e_rpool);
+ CurEnv = oldcur;
+ returndepth--;
+
+ /* check for delivery errors */
+ if (ee->e_parent == NULL ||
+ !bitset(EF_RESPONSE, ee->e_parent->e_flags))
+ return 0;
+ for (q = ee->e_sendqueue; q != NULL; q = q->q_next)
+ {
+ if (QS_IS_ATTEMPTED(q->q_state))
+ return 0;
+ }
+ return -1;
+}
+/*
+** ERRBODY -- output the body of an error message.
+**
+** Typically this is a copy of the transcript plus a copy of the
+** original offending message.
+**
+** Parameters:
+** mci -- the mailer connection information.
+** e -- the envelope we are working in.
+** separator -- any possible MIME separator (unused).
+**
+** Returns:
+** none
+**
+** Side Effects:
+** Outputs the body of an error message.
+*/
+
+/* ARGSUSED2 */
+static void
+errbody(mci, e, separator)
+ register MCI *mci;
+ register ENVELOPE *e;
+ char *separator;
+{
+ bool printheader;
+ bool sendbody;
+ bool pm_notify;
+ int save_errno;
+ register SM_FILE_T *xfile;
+ char *p;
+ register ADDRESS *q = NULL;
+ char actual[MAXLINE];
+ char buf[MAXLINE];
+
+ if (bitset(MCIF_INHEADER, mci->mci_flags))
+ {
+ putline("", mci);
+ mci->mci_flags &= ~MCIF_INHEADER;
+ }
+ if (e->e_parent == NULL)
+ {
+ syserr("errbody: null parent");
+ putline(" ----- Original message lost -----\n", mci);
+ return;
+ }
+
+ /*
+ ** Output MIME header.
+ */
+
+ if (e->e_msgboundary != NULL)
+ {
+ putline("This is a MIME-encapsulated message", mci);
+ putline("", mci);
+ (void) sm_strlcpyn(buf, sizeof buf, 2, "--", e->e_msgboundary);
+ putline(buf, mci);
+ putline("", mci);
+ }
+
+ /*
+ ** Output introductory information.
+ */
+
+ pm_notify = false;
+ p = hvalue("subject", e->e_header);
+ if (p != NULL && strncmp(p, "Postmaster ", 11) == 0)
+ pm_notify = true;
+ else
+ {
+ for (q = e->e_parent->e_sendqueue; q != NULL; q = q->q_next)
+ {
+ if (QS_IS_BADADDR(q->q_state))
+ break;
+ }
+ }
+ if (!pm_notify && q == NULL &&
+ !bitset(EF_FATALERRS|EF_SENDRECEIPT, e->e_parent->e_flags))
+ {
+ putline(" **********************************************",
+ mci);
+ putline(" ** THIS IS A WARNING MESSAGE ONLY **",
+ mci);
+ putline(" ** YOU DO NOT NEED TO RESEND YOUR MESSAGE **",
+ mci);
+ putline(" **********************************************",
+ mci);
+ putline("", mci);
+ }
+ (void) sm_snprintf(buf, sizeof buf,
+ "The original message was received at %s",
+ arpadate(ctime(&e->e_parent->e_ctime)));
+ putline(buf, mci);
+ expand("from \201_", buf, sizeof buf, e->e_parent);
+ putline(buf, mci);
+
+ /* include id in postmaster copies */
+ if (pm_notify && e->e_parent->e_id != NULL)
+ {
+ (void) sm_strlcpyn(buf, sizeof buf, 2, "with id ",
+ e->e_parent->e_id);
+ putline(buf, mci);
+ }
+ putline("", mci);
+
+ /*
+ ** Output error message header (if specified and available).
+ */
+
+ if (ErrMsgFile != NULL &&
+ !bitset(EF_SENDRECEIPT, e->e_parent->e_flags))
+ {
+ if (*ErrMsgFile == '/')
+ {
+ long sff = SFF_ROOTOK|SFF_REGONLY;
+
+ if (DontLockReadFiles)
+ sff |= SFF_NOLOCK;
+ if (!bitnset(DBS_ERRORHEADERINUNSAFEDIRPATH,
+ DontBlameSendmail))
+ sff |= SFF_SAFEDIRPATH;
+ xfile = safefopen(ErrMsgFile, O_RDONLY, 0444, sff);
+ if (xfile != NULL)
+ {
+ while (sm_io_fgets(xfile, SM_TIME_DEFAULT, buf,
+ sizeof buf) != NULL)
+ {
+ translate_dollars(buf);
+ expand(buf, buf, sizeof buf, e);
+ putline(buf, mci);
+ }
+ (void) sm_io_close(xfile, SM_TIME_DEFAULT);
+ putline("\n", mci);
+ }
+ }
+ else
+ {
+ expand(ErrMsgFile, buf, sizeof buf, e);
+ putline(buf, mci);
+ putline("", mci);
+ }
+ }
+
+ /*
+ ** Output message introduction
+ */
+
+ /* permanent fatal errors */
+ printheader = true;
+ for (q = e->e_parent->e_sendqueue; q != NULL; q = q->q_next)
+ {
+ if (!QS_IS_BADADDR(q->q_state) ||
+ !bitset(QPINGONFAILURE, q->q_flags))
+ continue;
+
+ if (printheader)
+ {
+ putline(" ----- The following addresses had permanent fatal errors -----",
+ mci);
+ printheader = false;
+ }
+
+ (void) sm_strlcpy(buf, shortenstring(q->q_paddr, MAXSHORTSTR),
+ sizeof buf);
+ putline(buf, mci);
+ if (q->q_rstatus != NULL)
+ {
+ (void) sm_snprintf(buf, sizeof buf,
+ " (reason: %s)",
+ shortenstring(exitstat(q->q_rstatus),
+ MAXSHORTSTR));
+ putline(buf, mci);
+ }
+ if (q->q_alias != NULL)
+ {
+ (void) sm_snprintf(buf, sizeof buf,
+ " (expanded from: %s)",
+ shortenstring(q->q_alias->q_paddr,
+ MAXSHORTSTR));
+ putline(buf, mci);
+ }
+ }
+ if (!printheader)
+ putline("", mci);
+
+ /* transient non-fatal errors */
+ printheader = true;
+ for (q = e->e_parent->e_sendqueue; q != NULL; q = q->q_next)
+ {
+ if (QS_IS_BADADDR(q->q_state) ||
+ !bitset(QPRIMARY, q->q_flags) ||
+ !bitset(QBYNDELAY, q->q_flags) ||
+ !bitset(QDELAYED, q->q_flags))
+ continue;
+
+ if (printheader)
+ {
+ putline(" ----- The following addresses had transient non-fatal errors -----",
+ mci);
+ printheader = false;
+ }
+
+ (void) sm_strlcpy(buf, shortenstring(q->q_paddr, MAXSHORTSTR),
+ sizeof buf);
+ putline(buf, mci);
+ if (q->q_alias != NULL)
+ {
+ (void) sm_snprintf(buf, sizeof buf,
+ " (expanded from: %s)",
+ shortenstring(q->q_alias->q_paddr,
+ MAXSHORTSTR));
+ putline(buf, mci);
+ }
+ }
+ if (!printheader)
+ putline("", mci);
+
+ /* successful delivery notifications */
+ printheader = true;
+ for (q = e->e_parent->e_sendqueue; q != NULL; q = q->q_next)
+ {
+ if (QS_IS_BADADDR(q->q_state) ||
+ !bitset(QPRIMARY, q->q_flags) ||
+ bitset(QBYNDELAY, q->q_flags) ||
+ bitset(QDELAYED, q->q_flags))
+ continue;
+ else if (bitset(QBYNRELAY, q->q_flags))
+ p = "Deliver-By notify: relayed";
+ else if (bitset(QBYTRACE, q->q_flags))
+ p = "Deliver-By trace: relayed";
+ else if (!bitset(QPINGONSUCCESS, q->q_flags))
+ continue;
+ else if (bitset(QRELAYED, q->q_flags))
+ p = "relayed to non-DSN-aware mailer";
+ else if (bitset(QDELIVERED, q->q_flags))
+ {
+ if (bitset(QEXPANDED, q->q_flags))
+ p = "successfully delivered to mailing list";
+ else
+ p = "successfully delivered to mailbox";
+ }
+ else if (bitset(QEXPANDED, q->q_flags))
+ p = "expanded by alias";
+ else
+ continue;
+
+ if (printheader)
+ {
+ putline(" ----- The following addresses had successful delivery notifications -----",
+ mci);
+ printheader = false;
+ }
+
+ (void) sm_snprintf(buf, sizeof buf, "%s (%s)",
+ shortenstring(q->q_paddr, MAXSHORTSTR), p);
+ putline(buf, mci);
+ if (q->q_alias != NULL)
+ {
+ (void) sm_snprintf(buf, sizeof buf,
+ " (expanded from: %s)",
+ shortenstring(q->q_alias->q_paddr,
+ MAXSHORTSTR));
+ putline(buf, mci);
+ }
+ }
+ if (!printheader)
+ putline("", mci);
+
+ /*
+ ** Output transcript of errors
+ */
+
+ (void) sm_io_flush(smioout, SM_TIME_DEFAULT);
+ if (e->e_parent->e_xfp == NULL)
+ {
+ putline(" ----- Transcript of session is unavailable -----\n",
+ mci);
+ }
+ else
+ {
+ printheader = true;
+ (void) bfrewind(e->e_parent->e_xfp);
+ if (e->e_xfp != NULL)
+ (void) sm_io_flush(e->e_xfp, SM_TIME_DEFAULT);
+ while (sm_io_fgets(e->e_parent->e_xfp, SM_TIME_DEFAULT, buf,
+ sizeof buf) != NULL)
+ {
+ if (printheader)
+ putline(" ----- Transcript of session follows -----\n",
+ mci);
+ printheader = false;
+ putline(buf, mci);
+ }
+ }
+ errno = 0;
+
+#if DSN
+ /*
+ ** Output machine-readable version.
+ */
+
+ if (e->e_msgboundary != NULL)
+ {
+ putline("", mci);
+ (void) sm_strlcpyn(buf, sizeof buf, 2, "--", e->e_msgboundary);
+ putline(buf, mci);
+ putline("Content-Type: message/delivery-status", mci);
+ putline("", mci);
+
+ /*
+ ** Output per-message information.
+ */
+
+ /* original envelope id from MAIL FROM: line */
+ if (e->e_parent->e_envid != NULL)
+ {
+ (void) sm_snprintf(buf, sizeof buf,
+ "Original-Envelope-Id: %.800s",
+ xuntextify(e->e_parent->e_envid));
+ putline(buf, mci);
+ }
+
+ /* Reporting-MTA: is us (required) */
+ (void) sm_snprintf(buf, sizeof buf,
+ "Reporting-MTA: dns; %.800s", MyHostName);
+ putline(buf, mci);
+
+ /* DSN-Gateway: not relevant since we are not translating */
+
+ /* Received-From-MTA: shows where we got this message from */
+ if (RealHostName != NULL)
+ {
+ /* XXX use $s for type? */
+ if (e->e_parent->e_from.q_mailer == NULL ||
+ (p = e->e_parent->e_from.q_mailer->m_mtatype) == NULL)
+ p = "dns";
+ (void) sm_snprintf(buf, sizeof buf,
+ "Received-From-MTA: %s; %.800s",
+ p, RealHostName);
+ putline(buf, mci);
+ }
+
+ /* Arrival-Date: -- when it arrived here */
+ (void) sm_strlcpyn(buf, sizeof buf, 2, "Arrival-Date: ",
+ arpadate(ctime(&e->e_parent->e_ctime)));
+ putline(buf, mci);
+
+ /* Deliver-By-Date: -- when it should have been delivered */
+ if (IS_DLVR_BY(e->e_parent))
+ {
+ time_t dbyd;
+
+ dbyd = e->e_parent->e_ctime + e->e_parent->e_deliver_by;
+ (void) sm_strlcpyn(buf, sizeof buf, 2,
+ "Deliver-By-Date: ",
+ arpadate(ctime(&dbyd)));
+ putline(buf, mci);
+ }
+
+ /*
+ ** Output per-address information.
+ */
+
+ for (q = e->e_parent->e_sendqueue; q != NULL; q = q->q_next)
+ {
+ char *action;
+
+ if (QS_IS_BADADDR(q->q_state))
+ {
+ /* RFC 1891, 6.2.6 (b) */
+ if (bitset(QHASNOTIFY, q->q_flags) &&
+ !bitset(QPINGONFAILURE, q->q_flags))
+ continue;
+ action = "failed";
+ }
+ else if (!bitset(QPRIMARY, q->q_flags))
+ continue;
+ else if (bitset(QDELIVERED, q->q_flags))
+ {
+ if (bitset(QEXPANDED, q->q_flags))
+ action = "delivered (to mailing list)";
+ else
+ action = "delivered (to mailbox)";
+ }
+ else if (bitset(QRELAYED, q->q_flags))
+ action = "relayed (to non-DSN-aware mailer)";
+ else if (bitset(QEXPANDED, q->q_flags))
+ action = "expanded (to multi-recipient alias)";
+ else if (bitset(QDELAYED, q->q_flags))
+ action = "delayed";
+ else if (bitset(QBYTRACE, q->q_flags))
+ action = "relayed (Deliver-By trace mode)";
+ else if (bitset(QBYNDELAY, q->q_flags))
+ action = "delayed (Deliver-By notify mode)";
+ else if (bitset(QBYNRELAY, q->q_flags))
+ action = "relayed (Deliver-By notify mode)";
+ else
+ continue;
+
+ putline("", mci);
+
+ /* Original-Recipient: -- passed from on high */
+ if (q->q_orcpt != NULL)
+ {
+ (void) sm_snprintf(buf, sizeof buf,
+ "Original-Recipient: %.800s",
+ q->q_orcpt);
+ putline(buf, mci);
+ }
+
+ /* Figure out actual recipient */
+ actual[0] = '\0';
+ if (q->q_user[0] != '\0')
+ {
+ if (q->q_mailer != NULL &&
+ q->q_mailer->m_addrtype != NULL)
+ p = q->q_mailer->m_addrtype;
+ else
+ p = "rfc822";
+
+ if (sm_strcasecmp(p, "rfc822") == 0 &&
+ strchr(q->q_user, '@') == NULL)
+ {
+ (void) sm_snprintf(actual,
+ sizeof actual,
+ "%s; %.700s@%.100s",
+ p, q->q_user,
+ MyHostName);
+ }
+ else
+ {
+ (void) sm_snprintf(actual,
+ sizeof actual,
+ "%s; %.800s",
+ p, q->q_user);
+ }
+ }
+
+ /* Final-Recipient: -- the name from the RCPT command */
+ if (q->q_finalrcpt == NULL)
+ {
+ /* should never happen */
+ sm_syslog(LOG_ERR, e->e_id,
+ "returntosender: q_finalrcpt is NULL");
+
+ /* try to fall back to the actual recipient */
+ if (actual[0] != '\0')
+ q->q_finalrcpt = sm_rpool_strdup_x(e->e_rpool,
+ actual);
+ }
+
+ if (q->q_finalrcpt != NULL)
+ {
+ (void) sm_snprintf(buf, sizeof buf,
+ "Final-Recipient: %s",
+ q->q_finalrcpt);
+ putline(buf, mci);
+ }
+
+ /* X-Actual-Recipient: -- the real problem address */
+ if (actual[0] != '\0' &&
+ q->q_finalrcpt != NULL &&
+#if _FFR_PRIV_NOACTUALRECIPIENT
+ !bitset(PRIV_NOACTUALRECIPIENT, PrivacyFlags) &&
+#endif /* _FFR_PRIV_NOACTUALRECIPIENT */
+ strcmp(actual, q->q_finalrcpt) != 0)
+ {
+ (void) sm_snprintf(buf, sizeof buf,
+ "X-Actual-Recipient: %s",
+ actual);
+ putline(buf, mci);
+ }
+
+ /* Action: -- what happened? */
+ (void) sm_strlcpyn(buf, sizeof buf, 2, "Action: ",
+ action);
+ putline(buf, mci);
+
+ /* Status: -- what _really_ happened? */
+ if (q->q_status != NULL)
+ p = q->q_status;
+ else if (QS_IS_BADADDR(q->q_state))
+ p = "5.0.0";
+ else if (QS_IS_QUEUEUP(q->q_state))
+ p = "4.0.0";
+ else
+ p = "2.0.0";
+ (void) sm_strlcpyn(buf, sizeof buf, 2, "Status: ", p);
+ putline(buf, mci);
+
+ /* Remote-MTA: -- who was I talking to? */
+ if (q->q_statmta != NULL)
+ {
+ if (q->q_mailer == NULL ||
+ (p = q->q_mailer->m_mtatype) == NULL)
+ p = "dns";
+ (void) sm_snprintf(buf, sizeof buf,
+ "Remote-MTA: %s; %.800s",
+ p, q->q_statmta);
+ p = &buf[strlen(buf) - 1];
+ if (*p == '.')
+ *p = '\0';
+ putline(buf, mci);
+ }
+
+ /* Diagnostic-Code: -- actual result from other end */
+ if (q->q_rstatus != NULL)
+ {
+ p = q->q_mailer->m_diagtype;
+ if (p == NULL)
+ p = "smtp";
+ (void) sm_snprintf(buf, sizeof buf,
+ "Diagnostic-Code: %s; %.800s",
+ p, q->q_rstatus);
+ putline(buf, mci);
+ }
+
+ /* Last-Attempt-Date: -- fine granularity */
+ if (q->q_statdate == (time_t) 0L)
+ q->q_statdate = curtime();
+ (void) sm_strlcpyn(buf, sizeof buf, 2,
+ "Last-Attempt-Date: ",
+ arpadate(ctime(&q->q_statdate)));
+ putline(buf, mci);
+
+ /* Will-Retry-Until: -- for delayed messages only */
+ if (QS_IS_QUEUEUP(q->q_state))
+ {
+ time_t xdate;
+
+ xdate = e->e_parent->e_ctime +
+ TimeOuts.to_q_return[e->e_parent->e_timeoutclass];
+ (void) sm_strlcpyn(buf, sizeof buf, 2,
+ "Will-Retry-Until: ",
+ arpadate(ctime(&xdate)));
+ putline(buf, mci);
+ }
+ }
+ }
+#endif /* DSN */
+
+ /*
+ ** Output text of original message
+ */
+
+ putline("", mci);
+ if (bitset(EF_HAS_DF, e->e_parent->e_flags))
+ {
+ sendbody = !bitset(EF_NO_BODY_RETN, e->e_parent->e_flags) &&
+ !bitset(EF_NO_BODY_RETN, e->e_flags);
+
+ if (e->e_msgboundary == NULL)
+ {
+ if (sendbody)
+ putline(" ----- Original message follows -----\n", mci);
+ else
+ putline(" ----- Message header follows -----\n", mci);
+ }
+ else
+ {
+ (void) sm_strlcpyn(buf, sizeof buf, 2, "--",
+ e->e_msgboundary);
+
+ putline(buf, mci);
+ (void) sm_strlcpyn(buf, sizeof buf, 2, "Content-Type: ",
+ sendbody ? "message/rfc822"
+ : "text/rfc822-headers");
+ putline(buf, mci);
+
+ p = hvalue("Content-Transfer-Encoding",
+ e->e_parent->e_header);
+ if (p != NULL && sm_strcasecmp(p, "binary") != 0)
+ p = NULL;
+ if (p == NULL &&
+ bitset(EF_HAS8BIT, e->e_parent->e_flags))
+ p = "8bit";
+ if (p != NULL)
+ {
+ (void) sm_snprintf(buf, sizeof buf,
+ "Content-Transfer-Encoding: %s",
+ p);
+ putline(buf, mci);
+ }
+ }
+ putline("", mci);
+ save_errno = errno;
+ putheader(mci, e->e_parent->e_header, e->e_parent, M87F_OUTER);
+ errno = save_errno;
+ if (sendbody)
+ putbody(mci, e->e_parent, e->e_msgboundary);
+ else if (e->e_msgboundary == NULL)
+ {
+ putline("", mci);
+ putline(" ----- Message body suppressed -----", mci);
+ }
+ }
+ else if (e->e_msgboundary == NULL)
+ {
+ putline(" ----- No message was collected -----\n", mci);
+ }
+
+ if (e->e_msgboundary != NULL)
+ {
+ putline("", mci);
+ (void) sm_strlcpyn(buf, sizeof buf, 3, "--", e->e_msgboundary,
+ "--");
+ putline(buf, mci);
+ }
+ putline("", mci);
+ (void) sm_io_flush(mci->mci_out, SM_TIME_DEFAULT);
+
+ /*
+ ** Cleanup and exit
+ */
+
+ if (errno != 0)
+ syserr("errbody: I/O error");
+}
+/*
+** SMTPTODSN -- convert SMTP to DSN status code
+**
+** Parameters:
+** smtpstat -- the smtp status code (e.g., 550).
+**
+** Returns:
+** The DSN version of the status code.
+**
+** Storage Management:
+** smtptodsn() returns a pointer to a character string literal,
+** which will remain valid forever, and thus does not need to
+** be copied. Current code relies on this property.
+*/
+
+char *
+smtptodsn(smtpstat)
+ int smtpstat;
+{
+ if (smtpstat < 0)
+ return "4.4.2";
+
+ switch (smtpstat)
+ {
+ case 450: /* Req mail action not taken: mailbox unavailable */
+ return "4.2.0";
+
+ case 451: /* Req action aborted: local error in processing */
+ return "4.3.0";
+
+ case 452: /* Req action not taken: insufficient sys storage */
+ return "4.3.1";
+
+ case 500: /* Syntax error, command unrecognized */
+ return "5.5.2";
+
+ case 501: /* Syntax error in parameters or arguments */
+ return "5.5.4";
+
+ case 502: /* Command not implemented */
+ return "5.5.1";
+
+ case 503: /* Bad sequence of commands */
+ return "5.5.1";
+
+ case 504: /* Command parameter not implemented */
+ return "5.5.4";
+
+ case 550: /* Req mail action not taken: mailbox unavailable */
+ return "5.2.0";
+
+ case 551: /* User not local; please try <...> */
+ return "5.1.6";
+
+ case 552: /* Req mail action aborted: exceeded storage alloc */
+ return "5.2.2";
+
+ case 553: /* Req action not taken: mailbox name not allowed */
+ return "5.1.0";
+
+ case 554: /* Transaction failed */
+ return "5.0.0";
+ }
+
+ if ((smtpstat / 100) == 2)
+ return "2.0.0";
+ if ((smtpstat / 100) == 4)
+ return "4.0.0";
+ return "5.0.0";
+}
+/*
+** XTEXTIFY -- take regular text and turn it into DSN-style xtext
+**
+** Parameters:
+** t -- the text to convert.
+** taboo -- additional characters that must be encoded.
+**
+** Returns:
+** The xtext-ified version of the same string.
+*/
+
+char *
+xtextify(t, taboo)
+ register char *t;
+ char *taboo;
+{
+ register char *p;
+ int l;
+ int nbogus;
+ static char *bp = NULL;
+ static int bplen = 0;
+
+ if (taboo == NULL)
+ taboo = "";
+
+ /* figure out how long this xtext will have to be */
+ nbogus = l = 0;
+ for (p = t; *p != '\0'; p++)
+ {
+ register int c = (*p & 0xff);
+
+ /* ASCII dependence here -- this is the way the spec words it */
+ if (c < '!' || c > '~' || c == '+' || c == '\\' || c == '(' ||
+ strchr(taboo, c) != NULL)
+ nbogus++;
+ l++;
+ }
+ if (nbogus < 0)
+ {
+ /* since nbogus is ssize_t and wrapped, 2 * size_t would wrap */
+ syserr("!xtextify string too long");
+ }
+ if (nbogus == 0)
+ return t;
+ l += nbogus * 2 + 1;
+
+ /* now allocate space if necessary for the new string */
+ if (l > bplen)
+ {
+ if (bp != NULL)
+ sm_free(bp); /* XXX */
+ bp = sm_pmalloc_x(l);
+ bplen = l;
+ }
+
+ /* ok, copy the text with byte expansion */
+ for (p = bp; *t != '\0'; )
+ {
+ register int c = (*t++ & 0xff);
+
+ /* ASCII dependence here -- this is the way the spec words it */
+ if (c < '!' || c > '~' || c == '+' || c == '\\' || c == '(' ||
+ strchr(taboo, c) != NULL)
+ {
+ *p++ = '+';
+ *p++ = "0123456789ABCDEF"[c >> 4];
+ *p++ = "0123456789ABCDEF"[c & 0xf];
+ }
+ else
+ *p++ = c;
+ }
+ *p = '\0';
+ return bp;
+}
+/*
+** XUNTEXTIFY -- take xtext and turn it into plain text
+**
+** Parameters:
+** t -- the xtextified text.
+**
+** Returns:
+** The decoded text. No attempt is made to deal with
+** null strings in the resulting text.
+*/
+
+char *
+xuntextify(t)
+ register char *t;
+{
+ register char *p;
+ int l;
+ static char *bp = NULL;
+ static int bplen = 0;
+
+ /* heuristic -- if no plus sign, just return the input */
+ if (strchr(t, '+') == NULL)
+ return t;
+
+ /* xtext is always longer than decoded text */
+ l = strlen(t);
+ if (l > bplen)
+ {
+ if (bp != NULL)
+ sm_free(bp); /* XXX */
+ bp = xalloc(l);
+ bplen = l;
+ }
+
+ /* ok, copy the text with byte compression */
+ for (p = bp; *t != '\0'; t++)
+ {
+ register int c = *t & 0xff;
+
+ if (c != '+')
+ {
+ *p++ = c;
+ continue;
+ }
+
+ c = *++t & 0xff;
+ if (!isascii(c) || !isxdigit(c))
+ {
+ /* error -- first digit is not hex */
+ usrerr("bogus xtext: +%c", c);
+ t--;
+ continue;
+ }
+ if (isdigit(c))
+ c -= '0';
+ else if (isupper(c))
+ c -= 'A' - 10;
+ else
+ c -= 'a' - 10;
+ *p = c << 4;
+
+ c = *++t & 0xff;
+ if (!isascii(c) || !isxdigit(c))
+ {
+ /* error -- second digit is not hex */
+ usrerr("bogus xtext: +%x%c", *p >> 4, c);
+ t--;
+ continue;
+ }
+ if (isdigit(c))
+ c -= '0';
+ else if (isupper(c))
+ c -= 'A' - 10;
+ else
+ c -= 'a' - 10;
+ *p++ |= c;
+ }
+ *p = '\0';
+ return bp;
+}
+/*
+** XTEXTOK -- check if a string is legal xtext
+**
+** Xtext is used in Delivery Status Notifications. The spec was
+** taken from RFC 1891, ``SMTP Service Extension for Delivery
+** Status Notifications''.
+**
+** Parameters:
+** s -- the string to check.
+**
+** Returns:
+** true -- if 's' is legal xtext.
+** false -- if it has any illegal characters in it.
+*/
+
+bool
+xtextok(s)
+ char *s;
+{
+ int c;
+
+ while ((c = *s++) != '\0')
+ {
+ if (c == '+')
+ {
+ c = *s++;
+ if (!isascii(c) || !isxdigit(c))
+ return false;
+ c = *s++;
+ if (!isascii(c) || !isxdigit(c))
+ return false;
+ }
+ else if (c < '!' || c > '~' || c == '=')
+ return false;
+ }
+ return true;
+}
+/*
+** PRUNEROUTE -- prune an RFC-822 source route
+**
+** Trims down a source route to the last internet-registered hop.
+** This is encouraged by RFC 1123 section 5.3.3.
+**
+** Parameters:
+** addr -- the address
+**
+** Returns:
+** true -- address was modified
+** false -- address could not be pruned
+**
+** Side Effects:
+** modifies addr in-place
+*/
+
+static bool
+pruneroute(addr)
+ char *addr;
+{
+#if NAMED_BIND
+ char *start, *at, *comma;
+ char c;
+ int braclev;
+ int rcode;
+ int i;
+ char hostbuf[BUFSIZ];
+ char *mxhosts[MAXMXHOSTS + 1];
+
+ /* check to see if this is really a route-addr */
+ if (*addr != '<' || addr[1] != '@' || addr[strlen(addr) - 1] != '>')
+ return false;
+
+ /*
+ ** Can't simply find the first ':' is the address might be in the
+ ** form: "<@[IPv6:::1]:user@host>" and the first ':' in inside
+ ** the IPv6 address.
+ */
+
+ start = addr;
+ braclev = 0;
+ while (*start != '\0')
+ {
+ if (*start == ':' && braclev <= 0)
+ break;
+ else if (*start == '[')
+ braclev++;
+ else if (*start == ']' && braclev > 0)
+ braclev--;
+ start++;
+ }
+ if (braclev > 0 || *start != ':')
+ return false;
+
+ at = strrchr(addr, '@');
+ if (at == NULL || at < start)
+ return false;
+
+ /* slice off the angle brackets */
+ i = strlen(at + 1);
+ if (i >= sizeof hostbuf)
+ return false;
+ (void) sm_strlcpy(hostbuf, at + 1, sizeof hostbuf);
+ hostbuf[i - 1] = '\0';
+
+ while (start != NULL)
+ {
+ if (getmxrr(hostbuf, mxhosts, NULL, false,
+ &rcode, true, NULL) > 0)
+ {
+ (void) sm_strlcpy(addr + 1, start + 1,
+ strlen(addr) - 1);
+ return true;
+ }
+ c = *start;
+ *start = '\0';
+ comma = strrchr(addr, ',');
+ if (comma != NULL && comma[1] == '@' &&
+ strlen(comma + 2) < sizeof hostbuf)
+ (void) sm_strlcpy(hostbuf, comma + 2, sizeof hostbuf);
+ else
+ comma = NULL;
+ *start = c;
+ start = comma;
+ }
+#endif /* NAMED_BIND */
+ return false;
+}
diff --git a/usr/src/cmd/sendmail/src/sendmail.h b/usr/src/cmd/sendmail/src/sendmail.h
new file mode 100644
index 0000000000..7d9b38424c
--- /dev/null
+++ b/usr/src/cmd/sendmail/src/sendmail.h
@@ -0,0 +1,2609 @@
+/*
+ * Copyright (c) 1998-2005 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+/*
+** SENDMAIL.H -- MTA-specific definitions for sendmail.
+*/
+
+#ifndef _SENDMAIL_H
+# define _SENDMAIL_H 1
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#ifndef MILTER
+# define MILTER 1 /* turn on MILTER by default */
+#endif /* MILTER */
+
+#ifdef _DEFINE
+# define EXTERN
+#else /* _DEFINE */
+# define EXTERN extern
+#endif /* _DEFINE */
+
+
+#include <unistd.h>
+
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <setjmp.h>
+#include <string.h>
+#include <time.h>
+# ifdef EX_OK
+# undef EX_OK /* for SVr4.2 SMP */
+# endif /* EX_OK */
+
+#include "sendmail/sendmail.h"
+
+/* profiling? */
+#if MONCONTROL
+# define SM_PROF(x) moncontrol(x)
+#else /* MONCONTROL */
+# define SM_PROF(x)
+#endif /* MONCONTROL */
+
+#ifdef _DEFINE
+# ifndef lint
+SM_UNUSED(static char SmailId[]) = "@(#)$Id: sendmail.h,v 8.993 2005/03/07 18:03:17 ca Exp $";
+# endif /* ! lint */
+#endif /* _DEFINE */
+
+#include "bf.h"
+#include "timers.h"
+#include <sm/exc.h>
+#include <sm/heap.h>
+#include <sm/debug.h>
+#include <sm/rpool.h>
+#include <sm/io.h>
+#include <sm/path.h>
+#include <sm/signal.h>
+#include <sm/clock.h>
+#include <sm/mbdb.h>
+#include <sm/errstring.h>
+#include <sm/sysexits.h>
+#include <sm/shm.h>
+
+#ifdef LOG
+# include <syslog.h>
+#endif /* LOG */
+
+
+
+# if NETINET || NETINET6 || NETUNIX || NETISO || NETNS || NETX25
+# include <sys/socket.h>
+# endif /* NETINET || NETINET6 || NETUNIX || NETISO || NETNS || NETX25 */
+# if NETUNIX
+# include <sys/un.h>
+# endif /* NETUNIX */
+# if NETINET || NETINET6
+# include <netinet/in.h>
+# endif /* NETINET || NETINET6 */
+# if NETINET6
+/*
+** There is no standard yet for IPv6 includes.
+** Specify OS specific implementation in conf.h
+*/
+# endif /* NETINET6 */
+# if NETISO
+# include <netiso/iso.h>
+# endif /* NETISO */
+# if NETNS
+# include <netns/ns.h>
+# endif /* NETNS */
+# if NETX25
+# include <netccitt/x25.h>
+# endif /* NETX25 */
+
+# if NAMED_BIND
+# include <arpa/nameser.h>
+# ifdef NOERROR
+# undef NOERROR /* avoid <sys/streams.h> conflict */
+# endif /* NOERROR */
+# include <resolv.h>
+# else /* NAMED_BIND */
+# undef SM_SET_H_ERRNO
+# define SM_SET_H_ERRNO(err)
+# endif /* NAMED_BIND */
+
+# if HESIOD
+# include <hesiod.h>
+# if !defined(HES_ER_OK) || defined(HESIOD_INTERFACES)
+# define HESIOD_INIT /* support for the new interface */
+# endif /* !defined(HES_ER_OK) || defined(HESIOD_INTERFACES) */
+# endif /* HESIOD */
+
+#if STARTTLS
+# include <openssl/ssl.h>
+# if !TLS_NO_RSA
+# define RSA_KEYLENGTH 512
+# endif /* !TLS_NO_RSA */
+#endif /* STARTTLS */
+
+#if SASL /* include the sasl include files if we have them */
+
+
+# if SASL == 2 || SASL >= 20000
+# include <sasl/sasl.h>
+# include <sasl/saslutil.h>
+# else /* SASL == 2 || SASL >= 20000 */
+# include <sasl.h>
+# include <saslutil.h>
+# endif /* SASL == 2 || SASL >= 20000 */
+# if defined(SASL_VERSION_MAJOR) && defined(SASL_VERSION_MINOR) && defined(SASL_VERSION_STEP)
+# define SASL_VERSION (SASL_VERSION_MAJOR * 10000) + (SASL_VERSION_MINOR * 100) + SASL_VERSION_STEP
+# if SASL == 1 || SASL == 2
+# undef SASL
+# define SASL SASL_VERSION
+# else /* SASL == 1 || SASL == 2 */
+# if SASL != SASL_VERSION
+ ERROR README: -DSASL (SASL) does not agree with the version of the CYRUS_SASL library (SASL_VERSION)
+ ERROR README: see README!
+# endif /* SASL != SASL_VERSION */
+# endif /* SASL == 1 || SASL == 2 */
+# else /* defined(SASL_VERSION_MAJOR) && defined(SASL_VERSION_MINOR) && defined(SASL_VERSION_STEP) */
+# if SASL == 1
+ ERROR README: please set -DSASL to the version of the CYRUS_SASL library
+ ERROR README: see README!
+# endif /* SASL == 1 */
+# endif /* defined(SASL_VERSION_MAJOR) && defined(SASL_VERSION_MINOR) && defined(SASL_VERSION_STEP) */
+#endif /* SASL */
+
+/*
+** Following are "sort of" configuration constants, but they should
+** be pretty solid on most architectures today. They have to be
+** defined after <arpa/nameser.h> because some versions of that
+** file also define them. In all cases, we can't use sizeof because
+** some systems (e.g., Crays) always treat everything as being at
+** least 64 bits.
+*/
+
+#ifndef INADDRSZ
+# define INADDRSZ 4 /* size of an IPv4 address in bytes */
+#endif /* ! INADDRSZ */
+#ifndef IN6ADDRSZ
+# define IN6ADDRSZ 16 /* size of an IPv6 address in bytes */
+#endif /* ! IN6ADDRSZ */
+#ifndef INT16SZ
+# define INT16SZ 2 /* size of a 16 bit integer in bytes */
+#endif /* ! INT16SZ */
+#ifndef INT32SZ
+# define INT32SZ 4 /* size of a 32 bit integer in bytes */
+#endif /* ! INT32SZ */
+#ifndef INADDR_LOOPBACK
+# define INADDR_LOOPBACK 0x7f000001 /* loopback address */
+#endif /* ! INADDR_LOOPBACK */
+
+/*
+** Error return from inet_addr(3), in case not defined in /usr/include.
+*/
+
+#ifndef INADDR_NONE
+# define INADDR_NONE 0xffffffff
+#endif /* ! INADDR_NONE */
+
+
+/* (f)open() modes for queue files */
+# define QF_O_EXTRA 0
+
+
+/*
+** An 'argument class' describes the storage allocation status
+** of an object pointed to by an argument to a function.
+*/
+
+typedef enum
+{
+ A_HEAP, /* the storage was allocated by malloc, and the
+ * ownership of the storage is ceded by the caller
+ * to the called function. */
+ A_TEMP, /* The storage is temporary, and is only guaranteed
+ * to be valid for the duration of the function call. */
+ A_PERM /* The storage is 'permanent': this might mean static
+ * storage, or rpool storage. */
+} ARGCLASS_T;
+
+/* forward references for prototypes */
+typedef struct envelope ENVELOPE;
+typedef struct mailer MAILER;
+typedef struct queuegrp QUEUEGRP;
+
+/*
+** Address structure.
+** Addresses are stored internally in this structure.
+*/
+
+struct address
+{
+ char *q_paddr; /* the printname for the address */
+ char *q_user; /* user name */
+ char *q_ruser; /* real user name, or NULL if q_user */
+ char *q_host; /* host name */
+ struct mailer *q_mailer; /* mailer to use */
+ unsigned long q_flags; /* status flags, see below */
+ uid_t q_uid; /* user-id of receiver (if known) */
+ gid_t q_gid; /* group-id of receiver (if known) */
+ char *q_home; /* home dir (local mailer only) */
+ char *q_fullname; /* full name if known */
+ struct address *q_next; /* chain */
+ struct address *q_alias; /* address this results from */
+ char *q_owner; /* owner of q_alias */
+ struct address *q_tchain; /* temporary use chain */
+#if PIPELINING
+ struct address *q_pchain; /* chain for pipelining */
+#endif /* PIPELINING */
+ char *q_finalrcpt; /* Final-Recipient: DSN header */
+ char *q_orcpt; /* ORCPT parameter from RCPT TO: line */
+ char *q_status; /* status code for DSNs */
+ char *q_rstatus; /* remote status message for DSNs */
+ time_t q_statdate; /* date of status messages */
+ char *q_statmta; /* MTA generating q_rstatus */
+ short q_state; /* address state, see below */
+ char *q_signature; /* MX-based sorting value */
+ int q_qgrp; /* index into queue groups */
+ int q_qdir; /* queue directory inside group */
+ char *q_message; /* error message */
+};
+
+typedef struct address ADDRESS;
+
+/* bit values for q_flags */
+#define QGOODUID 0x00000001 /* the q_uid q_gid fields are good */
+#define QPRIMARY 0x00000002 /* set from RCPT or argv */
+#define QNOTREMOTE 0x00000004 /* address not for remote forwarding */
+#define QSELFREF 0x00000008 /* this address references itself */
+#define QBOGUSSHELL 0x00000010 /* user has no valid shell listed */
+#define QUNSAFEADDR 0x00000020 /* address acquired via unsafe path */
+#define QPINGONSUCCESS 0x00000040 /* give return on successful delivery */
+#define QPINGONFAILURE 0x00000080 /* give return on failure */
+#define QPINGONDELAY 0x00000100 /* give return on message delay */
+#define QHASNOTIFY 0x00000200 /* propagate notify parameter */
+#define QRELAYED 0x00000400 /* DSN: relayed to non-DSN aware sys */
+#define QEXPANDED 0x00000800 /* DSN: undergone list expansion */
+#define QDELIVERED 0x00001000 /* DSN: successful final delivery */
+#define QDELAYED 0x00002000 /* DSN: message delayed */
+#define QALIAS 0x00004000 /* expanded alias */
+#define QBYTRACE 0x00008000 /* DeliverBy: trace */
+#define QBYNDELAY 0x00010000 /* DeliverBy: notify, delay */
+#define QBYNRELAY 0x00020000 /* DeliverBy: notify, relayed */
+#define QTHISPASS 0x40000000 /* temp: address set this pass */
+#define QRCPTOK 0x80000000 /* recipient() processed address */
+
+#define Q_PINGFLAGS (QPINGONSUCCESS|QPINGONFAILURE|QPINGONDELAY)
+
+/* values for q_state */
+#define QS_OK 0 /* address ok (for now)/not yet tried */
+#define QS_SENT 1 /* good address, delivery complete */
+#define QS_BADADDR 2 /* illegal address */
+#define QS_QUEUEUP 3 /* save address in queue */
+#define QS_RETRY 4 /* retry delivery for next MX */
+#define QS_VERIFIED 5 /* verified, but not expanded */
+
+/*
+** Notice: all of the following values are variations of QS_DONTSEND.
+** If new states are added, they must be inserted in the proper place!
+** See the macro definition of QS_IS_DEAD() down below.
+*/
+
+#define QS_DONTSEND 6 /* don't send to this address */
+#define QS_EXPANDED 7 /* expanded */
+#define QS_SENDER 8 /* message sender (MeToo) */
+#define QS_CLONED 9 /* addr cloned to split envelope */
+#define QS_DISCARDED 10 /* rcpt discarded (EF_DISCARD) */
+#define QS_REPLACED 11 /* maplocaluser()/UserDB replaced */
+#define QS_REMOVED 12 /* removed (removefromlist()) */
+#define QS_DUPLICATE 13 /* duplicate suppressed */
+#define QS_INCLUDED 14 /* :include: delivery */
+#define QS_FATALERR 15 /* fatal error, don't deliver */
+
+/* address state testing primitives */
+#define QS_IS_OK(s) ((s) == QS_OK)
+#define QS_IS_SENT(s) ((s) == QS_SENT)
+#define QS_IS_BADADDR(s) ((s) == QS_BADADDR)
+#define QS_IS_QUEUEUP(s) ((s) == QS_QUEUEUP)
+#define QS_IS_RETRY(s) ((s) == QS_RETRY)
+#define QS_IS_VERIFIED(s) ((s) == QS_VERIFIED)
+#define QS_IS_EXPANDED(s) ((s) == QS_EXPANDED)
+#define QS_IS_REMOVED(s) ((s) == QS_REMOVED)
+#define QS_IS_UNDELIVERED(s) ((s) == QS_OK || \
+ (s) == QS_QUEUEUP || \
+ (s) == QS_RETRY || \
+ (s) == QS_VERIFIED)
+#define QS_IS_UNMARKED(s) ((s) == QS_OK || \
+ (s) == QS_RETRY)
+#define QS_IS_SENDABLE(s) ((s) == QS_OK || \
+ (s) == QS_QUEUEUP || \
+ (s) == QS_RETRY)
+#define QS_IS_ATTEMPTED(s) ((s) == QS_QUEUEUP || \
+ (s) == QS_RETRY || \
+ (s) == QS_SENT)
+#define QS_IS_DEAD(s) ((s) >= QS_DONTSEND)
+
+
+#define NULLADDR ((ADDRESS *) NULL)
+
+extern ADDRESS NullAddress; /* a null (template) address [main.c] */
+
+/* functions */
+extern void cataddr __P((char **, char **, char *, int, int));
+extern char *crackaddr __P((char *, ENVELOPE *));
+extern bool emptyaddr __P((ADDRESS *));
+extern ADDRESS *getctladdr __P((ADDRESS *));
+extern int include __P((char *, bool, ADDRESS *, ADDRESS **, int, ENVELOPE *));
+extern bool invalidaddr __P((char *, char *, bool));
+extern ADDRESS *parseaddr __P((char *, ADDRESS *, int, int, char **,
+ ENVELOPE *, bool));
+extern char **prescan __P((char *, int, char[], int, char **, unsigned char *, bool));
+extern void printaddr __P((SM_FILE_T *, ADDRESS *, bool));
+extern ADDRESS *recipient __P((ADDRESS *, ADDRESS **, int, ENVELOPE *));
+extern char *remotename __P((char *, MAILER *, int, int *, ENVELOPE *));
+extern int rewrite __P((char **, int, int, ENVELOPE *, int));
+extern bool sameaddr __P((ADDRESS *, ADDRESS *));
+extern int sendtolist __P((char *, ADDRESS *, ADDRESS **, int, ENVELOPE *));
+#if MILTER
+extern int removefromlist __P((char *, ADDRESS **, ENVELOPE *));
+#endif /* MILTER */
+extern void setsender __P((char *, ENVELOPE *, char **, int, bool));
+
+/* macro to simplify the common call to rewrite() */
+#define REWRITE(pvp, rs, env) rewrite(pvp, rs, 0, env, MAXATOM)
+
+/*
+** Mailer definition structure.
+** Every mailer known to the system is declared in this
+** structure. It defines the pathname of the mailer, some
+** flags associated with it, and the argument vector to
+** pass to it. The flags are defined in conf.c
+**
+** The argument vector is expanded before actual use. All
+** words except the first are passed through the macro
+** processor.
+*/
+
+struct mailer
+{
+ char *m_name; /* symbolic name of this mailer */
+ char *m_mailer; /* pathname of the mailer to use */
+ char *m_mtatype; /* type of this MTA */
+ char *m_addrtype; /* type for addresses */
+ char *m_diagtype; /* type for diagnostics */
+ BITMAP256 m_flags; /* status flags, see below */
+ short m_mno; /* mailer number internally */
+ short m_nice; /* niceness to run at (mostly for prog) */
+ char **m_argv; /* template argument vector */
+ short m_sh_rwset; /* rewrite set: sender header addresses */
+ short m_se_rwset; /* rewrite set: sender envelope addresses */
+ short m_rh_rwset; /* rewrite set: recipient header addresses */
+ short m_re_rwset; /* rewrite set: recipient envelope addresses */
+ char *m_eol; /* end of line string */
+ long m_maxsize; /* size limit on message to this mailer */
+ int m_linelimit; /* max # characters per line */
+ int m_maxdeliveries; /* max deliveries per mailer connection */
+ char *m_execdir; /* directory to chdir to before execv */
+ char *m_rootdir; /* directory to chroot to before execv */
+ uid_t m_uid; /* UID to run as */
+ gid_t m_gid; /* GID to run as */
+ char *m_defcharset; /* default character set */
+ time_t m_wait; /* timeout to wait for end */
+ int m_maxrcpt; /* max recipients per envelope client-side */
+ short m_qgrp; /* queue group for this mailer */
+};
+
+/* bits for m_flags */
+#define M_ESMTP 'a' /* run Extended SMTP */
+#define M_ALIASABLE 'A' /* user can be LHS of an alias */
+#define M_BLANKEND 'b' /* ensure blank line at end of message */
+#define M_STRIPBACKSL 'B' /* strip all leading backslashes from user */
+#define M_NOCOMMENT 'c' /* don't include comment part of address */
+#define M_CANONICAL 'C' /* make addresses canonical "u@dom" */
+#define M_NOBRACKET 'd' /* never angle bracket envelope route-addrs */
+ /* 'D' CF: include Date: */
+#define M_EXPENSIVE 'e' /* it costs to use this mailer.... */
+#define M_ESCFROM 'E' /* escape From lines to >From */
+#define M_FOPT 'f' /* mailer takes picky -f flag */
+ /* 'F' CF: include From: or Resent-From: */
+#define M_NO_NULL_FROM 'g' /* sender of errors should be $g */
+#define M_HST_UPPER 'h' /* preserve host case distinction */
+#define M_PREHEAD 'H' /* MAIL11V3: preview headers */
+#define M_UDBENVELOPE 'i' /* do udbsender rewriting on envelope */
+#define M_INTERNAL 'I' /* SMTP to another sendmail site */
+#define M_UDBRECIPIENT 'j' /* do udbsender rewriting on recipient lines */
+#define M_NOLOOPCHECK 'k' /* don't check for loops in HELO command */
+#define M_CHUNKING 'K' /* CHUNKING: reserved for future use */
+#define M_LOCALMAILER 'l' /* delivery is to this host */
+#define M_LIMITS 'L' /* must enforce SMTP line limits */
+#define M_MUSER 'm' /* can handle multiple users at once */
+ /* 'M' CF: include Message-Id: */
+#define M_NHDR 'n' /* don't insert From line */
+#define M_MANYSTATUS 'N' /* MAIL11V3: DATA returns multi-status */
+#define M_RUNASRCPT 'o' /* always run mailer as recipient */
+#define M_FROMPATH 'p' /* use reverse-path in MAIL FROM: */
+ /* 'P' CF: include Return-Path: */
+#define M_VRFY250 'q' /* VRFY command returns 250 instead of 252 */
+#define M_ROPT 'r' /* mailer takes picky -r flag */
+#define M_SECURE_PORT 'R' /* try to send on a reserved TCP port */
+#define M_STRIPQ 's' /* strip quote chars from user/host */
+#define M_SPECIFIC_UID 'S' /* run as specific uid/gid */
+#define M_USR_UPPER 'u' /* preserve user case distinction */
+#define M_UGLYUUCP 'U' /* this wants an ugly UUCP from line */
+#define M_CONTENT_LEN 'v' /* add Content-Length: header (SVr4) */
+ /* 'V' UIUC: !-relativize all addresses */
+#define M_HASPWENT 'w' /* check for /etc/passwd entry */
+#define M_NOHOSTSTAT 'W' /* ignore long term host status information */
+ /* 'x' CF: include Full-Name: */
+#define M_XDOT 'X' /* use hidden-dot algorithm */
+#define M_LMTP 'z' /* run Local Mail Transport Protocol */
+#define M_DIALDELAY 'Z' /* apply dial delay sleeptime */
+#define M_NOMX '0' /* turn off MX lookups */
+#define M_NONULLS '1' /* don't send null bytes */
+#define M_FSMTP '2' /* force SMTP (no ESMTP even if offered) */
+#define M_EBCDIC '3' /* extend Q-P encoding for EBCDIC */
+#define M_TRYRULESET5 '5' /* use ruleset 5 after local aliasing */
+#define M_7BITHDRS '6' /* strip headers to 7 bits even in 8 bit path */
+#define M_7BITS '7' /* use 7-bit path */
+#define M_8BITS '8' /* force "just send 8" behaviour */
+#define M_MAKE8BIT '9' /* convert 7 -> 8 bit if appropriate */
+#define M_CHECKINCLUDE ':' /* check for :include: files */
+#define M_CHECKPROG '|' /* check for |program addresses */
+#define M_CHECKFILE '/' /* check for /file addresses */
+#define M_CHECKUDB '@' /* user can be user database key */
+#define M_CHECKHDIR '~' /* SGI: check for valid home directory */
+#define M_HOLD '%' /* Hold delivery until ETRN/-qI/-qR/-qS */
+#define M_PLUS '+' /* Reserved: Used in mc for adding new flags */
+#define M_MINUS '-' /* Reserved: Used in mc for removing flags */
+
+/* functions */
+extern void initerrmailers __P((void));
+extern void makemailer __P((char *));
+extern void makequeue __P((char *, bool));
+extern void runqueueevent __P((int));
+#if _FFR_QUEUE_RUN_PARANOIA
+extern bool checkqueuerunner __P((void));
+#endif /* _FFR_QUEUE_RUN_PARANOIA */
+
+EXTERN MAILER *FileMailer; /* ptr to *file* mailer */
+EXTERN MAILER *InclMailer; /* ptr to *include* mailer */
+EXTERN MAILER *LocalMailer; /* ptr to local mailer */
+EXTERN MAILER *ProgMailer; /* ptr to program mailer */
+EXTERN MAILER *Mailer[MAXMAILERS + 1];
+
+/*
+** Queue group definition structure.
+** Every queue group known to the system is declared in this structure.
+** It defines the basic pathname of the queue group, some flags
+** associated with it, and the argument vector to pass to it.
+*/
+
+struct qpaths_s
+{
+ char *qp_name; /* name of queue dir, relative path */
+ short qp_subdirs; /* use subdirs? */
+ short qp_fsysidx; /* file system index of this directory */
+# if SM_CONF_SHM
+ int qp_idx; /* index into array for queue information */
+# endif /* SM_CONF_SHM */
+};
+
+typedef struct qpaths_s QPATHS;
+
+struct queuegrp
+{
+ char *qg_name; /* symbolic name of this queue group */
+
+ /*
+ ** For now this is the same across all queue groups.
+ ** Otherwise we have to play around with chdir().
+ */
+
+ char *qg_qdir; /* common component of queue directory */
+ short qg_index; /* queue number internally, index in Queue[] */
+ int qg_maxqrun; /* max # of jobs in 1 queuerun */
+ int qg_numqueues; /* number of queues in this queue */
+
+ /*
+ ** qg_queueintvl == 0 denotes that no individual value is used.
+ ** Whatever accesses this must deal with "<= 0" as
+ ** "not set, use appropriate default".
+ */
+
+ time_t qg_queueintvl; /* interval for queue runs */
+ QPATHS *qg_qpaths; /* list of queue directories */
+ BITMAP256 qg_flags; /* status flags, see below */
+ short qg_nice; /* niceness for queue run */
+ int qg_wgrp; /* Assigned to this work group */
+ int qg_maxlist; /* max items in work queue for this group */
+ int qg_curnum; /* current number of queue for queue runs */
+ int qg_maxrcpt; /* max recipients per envelope, 0==no limit */
+
+ time_t qg_nextrun; /* time for next queue runs */
+#if _FFR_QUEUE_GROUP_SORTORDER
+ short qg_sortorder; /* how do we sort this queuerun */
+#endif /* _FFR_QUEUE_GROUP_SORTORDER */
+#if 0
+ long qg_wkrcptfact; /* multiplier for # recipients -> priority */
+ long qg_qfactor; /* slope of queue function */
+ bool qg_doqueuerun; /* XXX flag is it time to do a queuerun */
+#endif /* 0 */
+};
+
+/* bits for qg_flags (XXX: unused as of now) */
+#define QD_DEFINED ((char) 1) /* queue group has been defined */
+#define QD_FORK 'f' /* fork queue runs */
+
+extern void filesys_update __P((void));
+#if _FFR_ANY_FREE_FS
+extern bool filesys_free __P((long));
+#endif /* _FFR_ANY_FREE_FS */
+
+#if SASL
+/*
+** SASL
+*/
+
+/* lines in authinfo file or index into SASL_AI_T */
+# define SASL_WRONG (-1)
+# define SASL_USER 0 /* authorization id (user) */
+# define SASL_AUTHID 1 /* authentication id */
+# define SASL_PASSWORD 2 /* password fuer authid */
+# define SASL_DEFREALM 3 /* realm to use */
+# define SASL_MECHLIST 4 /* list of mechanisms to try */
+# define SASL_ID_REALM 5 /* authid@defrealm */
+
+/*
+** Current mechanism; this is just used to convey information between
+** invocation of SASL callback functions.
+** It must be last in the list, because it's not allocated by us
+** and hence we don't free() it.
+*/
+# define SASL_MECH 6
+# define SASL_ENTRIES 7 /* number of entries in array */
+
+# define SASL_USER_BIT (1 << SASL_USER)
+# define SASL_AUTHID_BIT (1 << SASL_AUTHID)
+# define SASL_PASSWORD_BIT (1 << SASL_PASSWORD)
+# define SASL_DEFREALM_BIT (1 << SASL_DEFREALM)
+# define SASL_MECHLIST_BIT (1 << SASL_MECHLIST)
+
+/* authenticated? */
+# define SASL_NOT_AUTH 0 /* not authenticated */
+# define SASL_PROC_AUTH 1 /* in process of authenticating */
+# define SASL_IS_AUTH 2 /* authenticated */
+
+/* SASL options */
+# define SASL_AUTH_AUTH 0x1000 /* use auth= only if authenticated */
+# if SASL >= 20101
+# define SASL_SEC_MASK SASL_SEC_MAXIMUM /* mask for SASL_SEC_* values: sasl.h */
+# else /* SASL >= 20101 */
+# define SASL_SEC_MASK 0x0fff /* mask for SASL_SEC_* values: sasl.h */
+# if (SASL_SEC_NOPLAINTEXT & SASL_SEC_MASK) == 0 || \
+ (SASL_SEC_NOACTIVE & SASL_SEC_MASK) == 0 || \
+ (SASL_SEC_NODICTIONARY & SASL_SEC_MASK) == 0 || \
+ (SASL_SEC_FORWARD_SECRECY & SASL_SEC_MASK) == 0 || \
+ (SASL_SEC_NOANONYMOUS & SASL_SEC_MASK) == 0 || \
+ (SASL_SEC_PASS_CREDENTIALS & SASL_SEC_MASK) == 0
+ERROR: change SASL_SEC_MASK_ notify sendmail.org!
+# endif /* SASL_SEC_NOPLAINTEXT & SASL_SEC_MASK) == 0 ... */
+# endif /* SASL >= 20101 */
+# define MAXOUTLEN 8192 /* length of output buffer */
+
+/* functions */
+extern char *intersect __P((char *, char *, SM_RPOOL_T *));
+extern char *iteminlist __P((char *, char *, char *));
+# if SASL >= 20000
+extern int proxy_policy __P((sasl_conn_t *, void *, const char *, unsigned, const char *, unsigned, const char *, unsigned, struct propctx *));
+extern int safesaslfile __P((void *, const char *, sasl_verify_type_t));
+# else /* SASL >= 20000 */
+extern int proxy_policy __P((void *, const char *, const char *, const char **, const char **));
+# if SASL > 10515
+extern int safesaslfile __P((void *, char *, int));
+# else /* SASL > 10515 */
+extern int safesaslfile __P((void *, char *));
+# endif /* SASL > 10515 */
+# endif /* SASL >= 20000 */
+extern void stop_sasl_client __P((void));
+
+/* structure to store authinfo */
+typedef char *SASL_AI_T[SASL_ENTRIES];
+
+EXTERN char *AuthMechanisms; /* AUTH mechanisms */
+EXTERN char *AuthRealm; /* AUTH realm */
+EXTERN char *SASLInfo; /* file with AUTH info */
+EXTERN int SASLOpts; /* options for SASL */
+EXTERN int MaxSLBits; /* max. encryption bits for SASL */
+#endif /* SASL */
+
+/*
+** Structure to store macros.
+*/
+typedef struct
+{
+ SM_RPOOL_T *mac_rpool; /* resource pool */
+ BITMAP256 mac_allocated; /* storage has been alloc()? */
+ char *mac_table[MAXMACROID + 1]; /* macros */
+} MACROS_T;
+
+EXTERN MACROS_T GlobalMacros;
+
+/*
+** Information about currently open connections to mailers, or to
+** hosts that we have looked up recently.
+*/
+
+#define MCI struct mailer_con_info
+
+MCI
+{
+ unsigned long mci_flags; /* flag bits, see below */
+ short mci_errno; /* error number on last connection */
+ short mci_herrno; /* h_errno from last DNS lookup */
+ short mci_exitstat; /* exit status from last connection */
+ short mci_state; /* SMTP state */
+ int mci_deliveries; /* delivery attempts for connection */
+ long mci_maxsize; /* max size this server will accept */
+ SM_FILE_T *mci_in; /* input side of connection */
+ SM_FILE_T *mci_out; /* output side of connection */
+ pid_t mci_pid; /* process id of subordinate proc */
+ char *mci_phase; /* SMTP phase string */
+ struct mailer *mci_mailer; /* ptr to the mailer for this conn */
+ char *mci_host; /* host name */
+ char *mci_status; /* DSN status to be copied to addrs */
+ char *mci_rstatus; /* SMTP status to be copied to addrs */
+ time_t mci_lastuse; /* last usage time */
+ SM_FILE_T *mci_statfile; /* long term status file */
+ char *mci_heloname; /* name to use as HELO arg */
+ long mci_min_by; /* minimum DELIVERBY */
+ bool mci_retryrcpt; /* tempfail for at least one rcpt */
+ char *mci_tolist; /* list of valid recipients */
+ SM_RPOOL_T *mci_rpool; /* resource pool */
+#if PIPELINING
+ int mci_okrcpts; /* number of valid recipients */
+ ADDRESS *mci_nextaddr; /* next address for pipelined status */
+#endif /* PIPELINING */
+#if SASL
+ SASL_AI_T mci_sai; /* authentication info */
+ bool mci_sasl_auth; /* authenticated? */
+ int mci_sasl_string_len;
+ char *mci_sasl_string; /* sasl reply string */
+ char *mci_saslcap; /* SASL list of mechanisms */
+ sasl_conn_t *mci_conn; /* SASL connection */
+#endif /* SASL */
+#if STARTTLS
+ SSL *mci_ssl; /* SSL connection */
+#endif /* STARTTLS */
+ MACROS_T mci_macro; /* macro definitions */
+};
+
+
+/* flag bits */
+#define MCIF_VALID 0x00000001 /* this entry is valid */
+/* 0x00000002 unused, was MCIF_TEMP */
+#define MCIF_CACHED 0x00000004 /* currently in open cache */
+#define MCIF_ESMTP 0x00000008 /* this host speaks ESMTP */
+#define MCIF_EXPN 0x00000010 /* EXPN command supported */
+#define MCIF_SIZE 0x00000020 /* SIZE option supported */
+#define MCIF_8BITMIME 0x00000040 /* BODY=8BITMIME supported */
+#define MCIF_7BIT 0x00000080 /* strip this message to 7 bits */
+/* 0x00000100 unused, was MCIF_MULTSTAT: MAIL11V3: handles MULT status */
+#define MCIF_INHEADER 0x00000200 /* currently outputing header */
+#define MCIF_CVT8TO7 0x00000400 /* convert from 8 to 7 bits */
+#define MCIF_DSN 0x00000800 /* DSN extension supported */
+#define MCIF_8BITOK 0x00001000 /* OK to send 8 bit characters */
+#define MCIF_CVT7TO8 0x00002000 /* convert from 7 to 8 bits */
+#define MCIF_INMIME 0x00004000 /* currently reading MIME header */
+#define MCIF_AUTH 0x00008000 /* AUTH= supported */
+#define MCIF_AUTHACT 0x00010000 /* SASL (AUTH) active */
+#define MCIF_ENHSTAT 0x00020000 /* ENHANCEDSTATUSCODES supported */
+#define MCIF_PIPELINED 0x00040000 /* PIPELINING supported */
+#define MCIF_VERB 0x00080000 /* VERB supported */
+#if STARTTLS
+#define MCIF_TLS 0x00100000 /* STARTTLS supported */
+#define MCIF_TLSACT 0x00200000 /* STARTTLS active */
+#define MCIF_EXTENS (MCIF_EXPN | MCIF_SIZE | MCIF_8BITMIME | MCIF_DSN | MCIF_8BITOK | MCIF_AUTH | MCIF_ENHSTAT | MCIF_TLS)
+#else /* STARTTLS */
+#define MCIF_EXTENS (MCIF_EXPN | MCIF_SIZE | MCIF_8BITMIME | MCIF_DSN | MCIF_8BITOK | MCIF_AUTH | MCIF_ENHSTAT)
+#endif /* STARTTLS */
+#define MCIF_DLVR_BY 0x00400000 /* DELIVERBY */
+#if _FFR_IGNORE_EXT_ON_HELO
+# define MCIF_HELO 0x00800000 /* we used HELO: ignore extensions */
+#endif /* _FFR_IGNORE_EXT_ON_HELO */
+#define MCIF_ONLY_EHLO 0x10000000 /* use only EHLO in smtpinit */
+
+/* states */
+#define MCIS_CLOSED 0 /* no traffic on this connection */
+#define MCIS_OPENING 1 /* sending initial protocol */
+#define MCIS_OPEN 2 /* open, initial protocol sent */
+#define MCIS_MAIL 3 /* MAIL command sent */
+#define MCIS_RCPT 4 /* RCPT commands being sent */
+#define MCIS_DATA 5 /* DATA command sent */
+#define MCIS_QUITING 6 /* running quit protocol */
+#define MCIS_SSD 7 /* service shutting down */
+#define MCIS_ERROR 8 /* I/O error on connection */
+
+/* functions */
+extern void mci_cache __P((MCI *));
+extern void mci_close __P((MCI *, char *where));
+extern void mci_dump __P((SM_FILE_T *, MCI *, bool));
+extern void mci_dump_all __P((SM_FILE_T *, bool));
+extern void mci_flush __P((bool, MCI *));
+extern MCI *mci_get __P((char *, MAILER *));
+extern int mci_lock_host __P((MCI *));
+extern bool mci_match __P((char *, MAILER *));
+extern int mci_print_persistent __P((char *, char *));
+extern int mci_purge_persistent __P((char *, char *));
+extern MCI **mci_scan __P((MCI *));
+extern void mci_setstat __P((MCI *, int, char *, char *));
+extern void mci_store_persistent __P((MCI *));
+extern int mci_traverse_persistent __P((int (*)(), char *));
+extern void mci_unlock_host __P((MCI *));
+
+EXTERN int MaxMciCache; /* maximum entries in MCI cache */
+EXTERN time_t MciCacheTimeout; /* maximum idle time on connections */
+EXTERN time_t MciInfoTimeout; /* how long 'til we retry down hosts */
+
+/*
+** Header structure.
+** This structure is used internally to store header items.
+*/
+
+struct header
+{
+ char *h_field; /* the name of the field */
+ char *h_value; /* the value of that field */
+ struct header *h_link; /* the next header */
+ unsigned char h_macro; /* include header if macro defined */
+ unsigned long h_flags; /* status bits, see below */
+ BITMAP256 h_mflags; /* m_flags bits needed */
+};
+
+typedef struct header HDR;
+
+/*
+** Header information structure.
+** Defined in conf.c, this struct declares the header fields
+** that have some magic meaning.
+*/
+
+struct hdrinfo
+{
+ char *hi_field; /* the name of the field */
+ unsigned long hi_flags; /* status bits, see below */
+ char *hi_ruleset; /* validity check ruleset */
+};
+
+extern struct hdrinfo HdrInfo[];
+
+/* bits for h_flags and hi_flags */
+#define H_EOH 0x00000001 /* field terminates header */
+#define H_RCPT 0x00000002 /* contains recipient addresses */
+#define H_DEFAULT 0x00000004 /* if another value is found, drop this */
+#define H_RESENT 0x00000008 /* this address is a "Resent-..." address */
+#define H_CHECK 0x00000010 /* check h_mflags against m_flags */
+#define H_ACHECK 0x00000020 /* ditto, but always (not just default) */
+#define H_FORCE 0x00000040 /* force this field, even if default */
+#define H_TRACE 0x00000080 /* this field contains trace information */
+#define H_FROM 0x00000100 /* this is a from-type field */
+#define H_VALID 0x00000200 /* this field has a validated value */
+#define H_RECEIPTTO 0x00000400 /* field has return receipt info */
+#define H_ERRORSTO 0x00000800 /* field has error address info */
+#define H_CTE 0x00001000 /* field is a content-transfer-encoding */
+#define H_CTYPE 0x00002000 /* this is a content-type field */
+#define H_BCC 0x00004000 /* Bcc: header: strip value or delete */
+#define H_ENCODABLE 0x00008000 /* field can be RFC 1522 encoded */
+#define H_STRIPCOMM 0x00010000 /* header check: strip comments */
+#define H_BINDLATE 0x00020000 /* only expand macros at deliver */
+#define H_USER 0x00040000 /* header came from the user/SMTP */
+
+/* bits for chompheader() */
+#define CHHDR_DEF 0x0001 /* default header */
+#define CHHDR_CHECK 0x0002 /* call ruleset for header */
+#define CHHDR_USER 0x0004 /* header from user */
+#define CHHDR_QUEUE 0x0008 /* header from queue file */
+
+/* functions */
+extern void addheader __P((char *, char *, int, ENVELOPE *));
+extern unsigned long chompheader __P((char *, int, HDR **, ENVELOPE *));
+extern void commaize __P((HDR *, char *, bool, MCI *, ENVELOPE *));
+extern HDR *copyheader __P((HDR *, SM_RPOOL_T *));
+extern void eatheader __P((ENVELOPE *, bool, bool));
+extern char *hvalue __P((char *, HDR *));
+extern void insheader __P((int, char *, char *, int, ENVELOPE *));
+extern bool isheader __P((char *));
+extern void putfromline __P((MCI *, ENVELOPE *));
+extern void setupheaders __P((void));
+
+/*
+** Performance monitoring
+*/
+
+#define TIMERS struct sm_timers
+
+TIMERS
+{
+ TIMER ti_overall; /* the whole process */
+};
+
+
+#define PUSHTIMER(l, t) { if (tTd(98, l)) pushtimer(&t); }
+#define POPTIMER(l, t) { if (tTd(98, l)) poptimer(&t); }
+
+/*
+** Envelope structure.
+** This structure defines the message itself. There is usually
+** only one of these -- for the message that we originally read
+** and which is our primary interest -- but other envelopes can
+** be generated during processing. For example, error messages
+** will have their own envelope.
+*/
+
+struct envelope
+{
+ HDR *e_header; /* head of header list */
+ long e_msgpriority; /* adjusted priority of this message */
+ time_t e_ctime; /* time message appeared in the queue */
+ char *e_to; /* (list of) target person(s) */
+ ADDRESS e_from; /* the person it is from */
+ char *e_sender; /* e_from.q_paddr w comments stripped */
+ char **e_fromdomain; /* the domain part of the sender */
+ ADDRESS *e_sendqueue; /* list of message recipients */
+ ADDRESS *e_errorqueue; /* the queue for error responses */
+
+ /*
+ ** Overflow detection is based on < 0, so don't change this
+ ** to unsigned. We don't use unsigned and == ULONG_MAX because
+ ** some libc's don't have strtoul(), see mail_esmtp_args().
+ */
+
+ long e_msgsize; /* size of the message in bytes */
+ char *e_msgid; /* message id (for logging) */
+ unsigned long e_flags; /* flags, see below */
+ int e_nrcpts; /* number of recipients */
+ short e_class; /* msg class (priority, junk, etc.) */
+ short e_hopcount; /* number of times processed */
+ short e_nsent; /* number of sends since checkpoint */
+ short e_sendmode; /* message send mode */
+ short e_errormode; /* error return mode */
+ short e_timeoutclass; /* message timeout class */
+ void (*e_puthdr)__P((MCI *, HDR *, ENVELOPE *, int));
+ /* function to put header of message */
+ void (*e_putbody)__P((MCI *, ENVELOPE *, char *));
+ /* function to put body of message */
+ ENVELOPE *e_parent; /* the message this one encloses */
+ ENVELOPE *e_sibling; /* the next envelope of interest */
+ char *e_bodytype; /* type of message body */
+ SM_FILE_T *e_dfp; /* data file */
+ char *e_id; /* code for this entry in queue */
+ int e_qgrp; /* queue group (index into queues) */
+ int e_qdir; /* index into queue directories */
+ int e_dfqgrp; /* data file queue group index */
+ int e_dfqdir; /* data file queue directory index */
+ int e_xfqgrp; /* queue group (index into queues) */
+ int e_xfqdir; /* index into queue directories (xf) */
+ SM_FILE_T *e_xfp; /* transcript file */
+ SM_FILE_T *e_lockfp; /* the lock file for this message */
+ char *e_message; /* error message; readonly; NULL, or
+ * static storage, or allocated from
+ * e_rpool */
+ char *e_statmsg; /* stat msg (changes per delivery).
+ * readonly. NULL or allocated from
+ * e_rpool. */
+ char *e_quarmsg; /* why envelope is quarantined */
+ char e_qfletter; /* queue file letter on disk */
+ char *e_msgboundary; /* MIME-style message part boundary */
+ char *e_origrcpt; /* original recipient (one only) */
+ char *e_envid; /* envelope id from MAIL FROM: line */
+ char *e_status; /* DSN status for this message */
+ time_t e_dtime; /* time of last delivery attempt */
+ int e_ntries; /* number of delivery attempts */
+ dev_t e_dfdev; /* data file device (crash recovery) */
+ ino_t e_dfino; /* data file inode (crash recovery) */
+ MACROS_T e_macro; /* macro definitions */
+ MCI *e_mci; /* connection info */
+ char *e_auth_param; /* readonly; NULL or static storage or
+ * allocated from e_rpool */
+ TIMERS e_timers; /* per job timers */
+ long e_deliver_by; /* deliver by */
+ int e_dlvr_flag; /* deliver by flag */
+ SM_RPOOL_T *e_rpool; /* resource pool for this envelope */
+};
+
+/* values for e_flags */
+#define EF_OLDSTYLE 0x00000001L /* use spaces (not commas) in hdrs */
+#define EF_INQUEUE 0x00000002L /* this message is fully queued */
+#define EF_NO_BODY_RETN 0x00000004L /* omit message body on error */
+#define EF_CLRQUEUE 0x00000008L /* disk copy is no longer needed */
+#define EF_SENDRECEIPT 0x00000010L /* send a return receipt */
+#define EF_FATALERRS 0x00000020L /* fatal errors occurred */
+#define EF_DELETE_BCC 0x00000040L /* delete Bcc: headers entirely */
+#define EF_RESPONSE 0x00000080L /* this is an error or return receipt */
+#define EF_RESENT 0x00000100L /* this message is being forwarded */
+#define EF_VRFYONLY 0x00000200L /* verify only (don't expand aliases) */
+#define EF_WARNING 0x00000400L /* warning message has been sent */
+#define EF_QUEUERUN 0x00000800L /* this envelope is from queue */
+#define EF_GLOBALERRS 0x00001000L /* treat errors as global */
+#define EF_PM_NOTIFY 0x00002000L /* send return mail to postmaster */
+#define EF_METOO 0x00004000L /* send to me too */
+#define EF_LOGSENDER 0x00008000L /* need to log the sender */
+#define EF_NORECEIPT 0x00010000L /* suppress all return-receipts */
+#define EF_HAS8BIT 0x00020000L /* at least one 8-bit char in body */
+#define EF_NL_NOT_EOL 0x00040000L /* don't accept raw NL as EOLine */
+#define EF_CRLF_NOT_EOL 0x00080000L /* don't accept CR-LF as EOLine */
+#define EF_RET_PARAM 0x00100000L /* RCPT command had RET argument */
+#define EF_HAS_DF 0x00200000L /* set when data file is instantiated */
+#define EF_IS_MIME 0x00400000L /* really is a MIME message */
+#define EF_DONT_MIME 0x00800000L /* never MIME this message */
+#define EF_DISCARD 0x01000000L /* discard the message */
+#define EF_TOOBIG 0x02000000L /* message is too big */
+#define EF_SPLIT 0x04000000L /* envelope has been split */
+#define EF_UNSAFE 0x08000000L /* unsafe: read from untrusted source */
+
+#define DLVR_NOTIFY 0x01
+#define DLVR_RETURN 0x02
+#define DLVR_TRACE 0x10
+#define IS_DLVR_NOTIFY(e) (((e)->e_dlvr_flag & DLVR_NOTIFY) != 0)
+#define IS_DLVR_RETURN(e) (((e)->e_dlvr_flag & DLVR_RETURN) != 0)
+#define IS_DLVR_TRACE(e) (((e)->e_dlvr_flag & DLVR_TRACE) != 0)
+#define IS_DLVR_BY(e) ((e)->e_dlvr_flag != 0)
+
+#define BODYTYPE_NONE (0)
+#define BODYTYPE_7BIT (1)
+#define BODYTYPE_8BITMIME (2)
+#define BODYTYPE_ILLEGAL (-1)
+#define BODYTYPE_VALID(b) ((b) == BODYTYPE_7BIT || (b) == BODYTYPE_8BITMIME)
+
+extern ENVELOPE BlankEnvelope;
+
+/* functions */
+extern void clearenvelope __P((ENVELOPE *, bool, SM_RPOOL_T *));
+extern void dropenvelope __P((ENVELOPE *, bool, bool));
+extern ENVELOPE *newenvelope __P((ENVELOPE *, ENVELOPE *, SM_RPOOL_T *));
+extern void clrsessenvelope __P((ENVELOPE *));
+extern void printenvflags __P((ENVELOPE *));
+extern void putbody __P((MCI *, ENVELOPE *, char *));
+extern void putheader __P((MCI *, HDR *, ENVELOPE *, int));
+
+/*
+** Message priority classes.
+**
+** The message class is read directly from the Priority: header
+** field in the message.
+**
+** CurEnv->e_msgpriority is the number of bytes in the message plus
+** the creation time (so that jobs ``tend'' to be ordered correctly),
+** adjusted by the message class, the number of recipients, and the
+** amount of time the message has been sitting around. This number
+** is used to order the queue. Higher values mean LOWER priority.
+**
+** Each priority class point is worth WkClassFact priority points;
+** each recipient is worth WkRecipFact priority points. Each time
+** we reprocess a message the priority is adjusted by WkTimeFact.
+** WkTimeFact should normally decrease the priority so that jobs
+** that have historically failed will be run later; thanks go to
+** Jay Lepreau at Utah for pointing out the error in my thinking.
+**
+** The "class" is this number, unadjusted by the age or size of
+** this message. Classes with negative representations will have
+** error messages thrown away if they are not local.
+*/
+
+struct priority
+{
+ char *pri_name; /* external name of priority */
+ int pri_val; /* internal value for same */
+};
+
+EXTERN int NumPriorities; /* pointer into Priorities */
+EXTERN struct priority Priorities[MAXPRIORITIES];
+
+/*
+** Rewrite rules.
+*/
+
+struct rewrite
+{
+ char **r_lhs; /* pattern match */
+ char **r_rhs; /* substitution value */
+ struct rewrite *r_next;/* next in chain */
+ int r_line; /* rule line in sendmail.cf */
+};
+
+/*
+** Special characters in rewriting rules.
+** These are used internally only.
+** The COND* rules are actually used in macros rather than in
+** rewriting rules, but are given here because they
+** cannot conflict.
+*/
+
+/* left hand side items */
+#define MATCHZANY ((unsigned char)0220) /* match zero or more tokens */
+#define MATCHANY ((unsigned char)0221) /* match one or more tokens */
+#define MATCHONE ((unsigned char)0222) /* match exactly one token */
+#define MATCHCLASS ((unsigned char)0223) /* match one token in a class */
+#define MATCHNCLASS ((unsigned char)0224) /* match anything not in class */
+#define MATCHREPL ((unsigned char)0225) /* replacement on RHS for above */
+
+/* right hand side items */
+#define CANONNET ((unsigned char)0226) /* canonical net, next token */
+#define CANONHOST ((unsigned char)0227) /* canonical host, next token */
+#define CANONUSER ((unsigned char)0230) /* canonical user, next N tokens */
+#define CALLSUBR ((unsigned char)0231) /* call another rewriting set */
+
+/* conditionals in macros */
+#define CONDIF ((unsigned char)0232) /* conditional if-then */
+#define CONDELSE ((unsigned char)0233) /* conditional else */
+#define CONDFI ((unsigned char)0234) /* conditional fi */
+
+/* bracket characters for host name lookup */
+#define HOSTBEGIN ((unsigned char)0235) /* hostname lookup begin */
+#define HOSTEND ((unsigned char)0236) /* hostname lookup end */
+
+/* bracket characters for generalized lookup */
+#define LOOKUPBEGIN ((unsigned char)0205) /* generalized lookup begin */
+#define LOOKUPEND ((unsigned char)0206) /* generalized lookup end */
+
+/* macro substitution character */
+#define MACROEXPAND ((unsigned char)0201) /* macro expansion */
+#define MACRODEXPAND ((unsigned char)0202) /* deferred macro expansion */
+
+/* to make the code clearer */
+#define MATCHZERO CANONHOST
+
+#define MAXMATCH 9 /* max params per rewrite */
+#define MAX_MAP_ARGS 10 /* max arguments for map */
+
+/* external <==> internal mapping table */
+struct metamac
+{
+ char metaname; /* external code (after $) */
+ unsigned char metaval; /* internal code (as above) */
+};
+
+/* values for macros with external names only */
+#define MID_OPMODE 0202 /* operation mode */
+
+/* functions */
+#if SM_HEAP_CHECK
+extern void
+macdefine_tagged __P((
+ MACROS_T *_mac,
+ ARGCLASS_T _vclass,
+ int _id,
+ char *_value,
+ char *_file,
+ int _line,
+ int _group));
+# define macdefine(mac,c,id,v) \
+ macdefine_tagged(mac,c,id,v,__FILE__,__LINE__,sm_heap_group())
+#else /* SM_HEAP_CHECK */
+extern void
+macdefine __P((
+ MACROS_T *_mac,
+ ARGCLASS_T _vclass,
+ int _id,
+ char *_value));
+# define macdefine_tagged(mac,c,id,v,file,line,grp) macdefine(mac,c,id,v)
+#endif /* SM_HEAP_CHECK */
+extern void macset __P((MACROS_T *, int, char *));
+#define macget(mac, i) (mac)->mac_table[i]
+extern void expand __P((char *, char *, size_t, ENVELOPE *));
+extern int macid_parse __P((char *, char **));
+#define macid(name) macid_parse(name, NULL)
+extern char *macname __P((int));
+extern char *macvalue __P((int, ENVELOPE *));
+extern int rscheck __P((char *, char *, char *, ENVELOPE *, int, int, char *, char *));
+extern int rscap __P((char *, char *, char *, ENVELOPE *, char ***, char *, int));
+extern void setclass __P((int, char *));
+extern int strtorwset __P((char *, char **, int));
+extern void translate_dollars __P((char *));
+extern bool wordinclass __P((char *, int));
+
+/*
+** Name canonification short circuit.
+**
+** If the name server for a host is down, the process of trying to
+** canonify the name can hang. This is similar to (but alas, not
+** identical to) looking up the name for delivery. This stab type
+** caches the result of the name server lookup so we don't hang
+** multiple times.
+*/
+
+#define NAMECANON struct _namecanon
+
+NAMECANON
+{
+ short nc_errno; /* cached errno */
+ short nc_herrno; /* cached h_errno */
+ short nc_stat; /* cached exit status code */
+ short nc_flags; /* flag bits */
+ char *nc_cname; /* the canonical name */
+ time_t nc_exp; /* entry expires at */
+};
+
+/* values for nc_flags */
+#define NCF_VALID 0x0001 /* entry valid */
+
+/* hostsignature structure */
+
+struct hostsig_t
+{
+ char *hs_sig; /* hostsignature */
+ time_t hs_exp; /* entry expires at */
+};
+
+typedef struct hostsig_t HOSTSIG_T;
+
+/* functions */
+extern bool getcanonname __P((char *, int, bool, int *));
+extern int getmxrr __P((char *, char **, unsigned short *, bool, int *, bool, int *));
+extern char *hostsignature __P((MAILER *, char *));
+extern int getfallbackmxrr __P((char *));
+
+/*
+** Mapping functions
+**
+** These allow arbitrary mappings in the config file. The idea
+** (albeit not the implementation) comes from IDA sendmail.
+*/
+
+#define MAPCLASS struct _mapclass
+#define MAP struct _map
+#define MAXMAPACTIONS 5 /* size of map_actions array */
+
+
+/*
+** An actual map.
+*/
+
+MAP
+{
+ MAPCLASS *map_class; /* the class of this map */
+ MAPCLASS *map_orgclass; /* the original class of this map */
+ char *map_mname; /* name of this map */
+ long map_mflags; /* flags, see below */
+ char *map_file; /* the (nominal) filename */
+ ARBPTR_T map_db1; /* the open database ptr */
+ ARBPTR_T map_db2; /* an "extra" database pointer */
+ char *map_keycolnm; /* key column name */
+ char *map_valcolnm; /* value column name */
+ unsigned char map_keycolno; /* key column number */
+ unsigned char map_valcolno; /* value column number */
+ char map_coldelim; /* column delimiter */
+ char map_spacesub; /* spacesub */
+ char *map_app; /* to append to successful matches */
+ char *map_tapp; /* to append to "tempfail" matches */
+ char *map_domain; /* the (nominal) NIS domain */
+ char *map_rebuild; /* program to run to do auto-rebuild */
+ time_t map_mtime; /* last database modification time */
+ time_t map_timeout; /* timeout for map accesses */
+ int map_retry; /* # of retries for map accesses */
+ pid_t map_pid; /* PID of process which opened map */
+ int map_lockfd; /* auxiliary lock file descriptor */
+ short map_specificity; /* specificity of aliases */
+ MAP *map_stack[MAXMAPSTACK]; /* list for stacked maps */
+ short map_return[MAXMAPACTIONS]; /* return bitmaps for stacked maps */
+};
+
+
+/* bit values for map_mflags */
+#define MF_VALID 0x00000001 /* this entry is valid */
+#define MF_INCLNULL 0x00000002 /* include null byte in key */
+#define MF_OPTIONAL 0x00000004 /* don't complain if map not found */
+#define MF_NOFOLDCASE 0x00000008 /* don't fold case in keys */
+#define MF_MATCHONLY 0x00000010 /* don't use the map value */
+#define MF_OPEN 0x00000020 /* this entry is open */
+#define MF_WRITABLE 0x00000040 /* open for writing */
+#define MF_ALIAS 0x00000080 /* this is an alias file */
+#define MF_TRY0NULL 0x00000100 /* try with no null byte */
+#define MF_TRY1NULL 0x00000200 /* try with the null byte */
+#define MF_LOCKED 0x00000400 /* this map is currently locked */
+#define MF_ALIASWAIT 0x00000800 /* alias map in aliaswait state */
+#define MF_IMPL_HASH 0x00001000 /* implicit: underlying hash database */
+#define MF_IMPL_NDBM 0x00002000 /* implicit: underlying NDBM database */
+/* 0x00004000 */
+#define MF_APPEND 0x00008000 /* append new entry on rebuild */
+#define MF_KEEPQUOTES 0x00010000 /* don't dequote key before lookup */
+#define MF_NODEFER 0x00020000 /* don't defer if map lookup fails */
+#define MF_REGEX_NOT 0x00040000 /* regular expression negation */
+#define MF_DEFER 0x00080000 /* don't lookup map in defer mode */
+#define MF_SINGLEMATCH 0x00100000 /* successful only if match one key */
+/* 0x00200000 available for use */
+#define MF_FILECLASS 0x00400000 /* this is a file class map */
+#define MF_OPENBOGUS 0x00800000 /* open failed, don't call map_close */
+#define MF_CLOSING 0x01000000 /* map is being closed */
+
+#define DYNOPENMAP(map) if (!bitset(MF_OPEN, (map)->map_mflags)) \
+ { \
+ if (!openmap(map)) \
+ return NULL; \
+ }
+
+
+/* indices for map_actions */
+#define MA_NOTFOUND 0 /* member map returned "not found" */
+#define MA_UNAVAIL 1 /* member map is not available */
+#define MA_TRYAGAIN 2 /* member map returns temp failure */
+
+/* macros to handle MapTempFail */
+#define BIT_IS_MTP 0x01 /* temp.failure occurred */
+#define BIT_ASK_MTP 0x02 /* do we care about MapTempFail? */
+#define RESET_MAPTEMPFAIL MapTempFail = 0
+#define INIT_MAPTEMPFAIL MapTempFail = BIT_ASK_MTP
+#define SET_MAPTEMPFAIL MapTempFail |= BIT_IS_MTP
+#define IS_MAPTEMPFAIL bitset(BIT_IS_MTP, MapTempFail)
+#define ASK_MAPTEMPFAIL bitset(BIT_ASK_MTP, MapTempFail)
+
+/*
+** The class of a map -- essentially the functions to call
+*/
+
+MAPCLASS
+{
+ char *map_cname; /* name of this map class */
+ char *map_ext; /* extension for database file */
+ short map_cflags; /* flag bits, see below */
+ bool (*map_parse)__P((MAP *, char *));
+ /* argument parsing function */
+ char *(*map_lookup)__P((MAP *, char *, char **, int *));
+ /* lookup function */
+ void (*map_store)__P((MAP *, char *, char *));
+ /* store function */
+ bool (*map_open)__P((MAP *, int));
+ /* open function */
+ void (*map_close)__P((MAP *));
+ /* close function */
+};
+
+/* bit values for map_cflags */
+#define MCF_ALIASOK 0x0001 /* can be used for aliases */
+#define MCF_ALIASONLY 0x0002 /* usable only for aliases */
+#define MCF_REBUILDABLE 0x0004 /* can rebuild alias files */
+#define MCF_OPTFILE 0x0008 /* file name is optional */
+#define MCF_NOTPERSIST 0x0010 /* don't keep map open all the time */
+
+/* functions */
+extern void closemaps __P((bool));
+extern bool impl_map_open __P((MAP *, int));
+extern void initmaps __P((void));
+extern MAP *makemapentry __P((char *));
+extern void maplocaluser __P((ADDRESS *, ADDRESS **, int, ENVELOPE *));
+extern char *map_rewrite __P((MAP *, const char *, size_t, char **));
+#if NETINFO
+extern char *ni_propval __P((char *, char *, char *, char *, int));
+#endif /* NETINFO */
+extern bool openmap __P((MAP *));
+#if USERDB
+extern void _udbx_close __P((void));
+extern int udbexpand __P((ADDRESS *, ADDRESS **, int, ENVELOPE *));
+extern char *udbsender __P((char *, SM_RPOOL_T *));
+#endif /* USERDB */
+
+/*
+** LDAP related items
+*/
+#if LDAPMAP
+/* struct defining LDAP Auth Methods */
+struct lamvalues
+{
+ char *lam_name; /* name of LDAP auth method */
+ int lam_code; /* numeric code */
+};
+
+/* struct defining LDAP Alias Dereferencing */
+struct ladvalues
+{
+ char *lad_name; /* name of LDAP alias dereferencing method */
+ int lad_code; /* numeric code */
+};
+
+/* struct defining LDAP Search Scope */
+struct lssvalues
+{
+ char *lss_name; /* name of LDAP search scope */
+ int lss_code; /* numeric code */
+};
+
+/* functions */
+extern bool ldapmap_parseargs __P((MAP *, char *));
+extern void ldapmap_set_defaults __P((char *));
+#endif /* LDAPMAP */
+
+/*
+** PH related items
+*/
+
+#if PH_MAP
+
+# include <phclient.h>
+
+struct ph_map_struct
+{
+ char *ph_servers; /* list of ph servers */
+ char *ph_field_list; /* list of fields to search for match */
+ PH *ph; /* PH server handle */
+ int ph_fastclose; /* send "quit" command on close */
+ time_t ph_timeout; /* timeout interval */
+};
+typedef struct ph_map_struct PH_MAP_STRUCT;
+
+#endif /* PH_MAP */
+
+/*
+** Regular UNIX sockaddrs are too small to handle ISO addresses, so
+** we are forced to declare a supertype here.
+*/
+
+#if NETINET || NETINET6 || NETUNIX || NETISO || NETNS || NETX25
+union bigsockaddr
+{
+ struct sockaddr sa; /* general version */
+# if NETUNIX
+ struct sockaddr_un sunix; /* UNIX family */
+# endif /* NETUNIX */
+# if NETINET
+ struct sockaddr_in sin; /* INET family */
+# endif /* NETINET */
+# if NETINET6
+ struct sockaddr_in6 sin6; /* INET/IPv6 */
+# endif /* NETINET6 */
+# if NETISO
+ struct sockaddr_iso siso; /* ISO family */
+# endif /* NETISO */
+# if NETNS
+ struct sockaddr_ns sns; /* XNS family */
+# endif /* NETNS */
+# if NETX25
+ struct sockaddr_x25 sx25; /* X.25 family */
+# endif /* NETX25 */
+};
+
+# define SOCKADDR union bigsockaddr
+
+/* functions */
+extern char *anynet_ntoa __P((SOCKADDR *));
+# if NETINET6
+extern char *anynet_ntop __P((struct in6_addr *, char *, size_t));
+extern int anynet_pton __P((int, const char *, void *));
+# endif /* NETINET6 */
+extern char *hostnamebyanyaddr __P((SOCKADDR *));
+extern char *validate_connection __P((SOCKADDR *, char *, ENVELOPE *));
+# if SASL >= 20000
+extern bool iptostring __P((SOCKADDR *, SOCKADDR_LEN_T, char *, unsigned));
+# endif /* SASL >= 20000 */
+
+#endif /* NETINET || NETINET6 || NETUNIX || NETISO || NETNS || NETX25 */
+
+/*
+** Process List (proclist)
+*/
+
+#define NO_PID ((pid_t) 0)
+#ifndef PROC_LIST_SEG
+# define PROC_LIST_SEG 32 /* number of pids to alloc at a time */
+#endif /* ! PROC_LIST_SEG */
+
+/* process types */
+#define PROC_NONE 0
+#define PROC_DAEMON 1
+#define PROC_DAEMON_CHILD 2
+#define PROC_QUEUE 3
+#define PROC_QUEUE_CHILD 3
+#define PROC_CONTROL 4
+#define PROC_CONTROL_CHILD 5
+
+/* functions */
+extern void proc_list_add __P((pid_t, char *, int, int, int, SOCKADDR *));
+extern void proc_list_clear __P((void));
+extern void proc_list_display __P((SM_FILE_T *, char *));
+extern void proc_list_drop __P((pid_t, int, int *));
+extern void proc_list_probe __P((void));
+extern void proc_list_set __P((pid_t, char *));
+extern void proc_list_signal __P((int, int));
+
+/*
+** Symbol table definitions
+*/
+
+struct symtab
+{
+ char *s_name; /* name to be entered */
+ short s_symtype; /* general type (see below) */
+ struct symtab *s_next; /* pointer to next in chain */
+ union
+ {
+ BITMAP256 sv_class; /* bit-map of word classes */
+ ADDRESS *sv_addr; /* pointer to address header */
+ MAILER *sv_mailer; /* pointer to mailer */
+ char *sv_alias; /* alias */
+ MAPCLASS sv_mapclass; /* mapping function class */
+ MAP sv_map; /* mapping function */
+ HOSTSIG_T sv_hostsig; /* host signature */
+ MCI sv_mci; /* mailer connection info */
+ NAMECANON sv_namecanon; /* canonical name cache */
+ int sv_macro; /* macro name => id mapping */
+ int sv_ruleset; /* ruleset index */
+ struct hdrinfo sv_header; /* header metainfo */
+ char *sv_service[MAXMAPSTACK]; /* service switch */
+#if LDAPMAP
+ MAP *sv_lmap; /* Maps for LDAP connection */
+#endif /* LDAPMAP */
+#if SOCKETMAP
+ MAP *sv_socketmap; /* Maps for SOCKET connection */
+#endif /* SOCKETMAP */
+#if MILTER
+ struct milter *sv_milter; /* milter filter name */
+#endif /* MILTER */
+ QUEUEGRP *sv_queue; /* pointer to queue */
+ } s_value;
+};
+
+typedef struct symtab STAB;
+
+/* symbol types */
+#define ST_UNDEF 0 /* undefined type */
+#define ST_CLASS 1 /* class map */
+#define ST_ADDRESS 2 /* an address in parsed format */
+#define ST_MAILER 3 /* a mailer header */
+#define ST_ALIAS 4 /* an alias */
+#define ST_MAPCLASS 5 /* mapping function class */
+#define ST_MAP 6 /* mapping function */
+#define ST_HOSTSIG 7 /* host signature */
+#define ST_NAMECANON 8 /* cached canonical name */
+#define ST_MACRO 9 /* macro name to id mapping */
+#define ST_RULESET 10 /* ruleset index */
+#define ST_SERVICE 11 /* service switch entry */
+#define ST_HEADER 12 /* special header flags */
+#if LDAPMAP
+# define ST_LMAP 13 /* List head of maps for LDAP connection */
+#endif /* LDAPMAP */
+#if MILTER
+# define ST_MILTER 14 /* milter filter */
+#endif /* MILTER */
+#define ST_QUEUE 15 /* a queue entry */
+
+#if SOCKETMAP
+# define ST_SOCKETMAP 16 /* List head of maps for SOCKET connection */
+#endif /* SOCKETMAP */
+
+/* This entry must be last */
+#define ST_MCI 17 /* mailer connection info (offset) */
+
+#define s_class s_value.sv_class
+#define s_address s_value.sv_addr
+#define s_mailer s_value.sv_mailer
+#define s_alias s_value.sv_alias
+#define s_mci s_value.sv_mci
+#define s_mapclass s_value.sv_mapclass
+#define s_hostsig s_value.sv_hostsig
+#define s_map s_value.sv_map
+#define s_namecanon s_value.sv_namecanon
+#define s_macro s_value.sv_macro
+#define s_ruleset s_value.sv_ruleset
+#define s_service s_value.sv_service
+#define s_header s_value.sv_header
+#if LDAPMAP
+# define s_lmap s_value.sv_lmap
+#endif /* LDAPMAP */
+#if SOCKETMAP
+# define s_socketmap s_value.sv_socketmap
+#endif /* SOCKETMAP */
+#if MILTER
+# define s_milter s_value.sv_milter
+#endif /* MILTER */
+#define s_quegrp s_value.sv_queue
+
+/* opcodes to stab */
+#define ST_FIND 0 /* find entry */
+#define ST_ENTER 1 /* enter if not there */
+
+/* functions */
+extern STAB *stab __P((char *, int, int));
+extern void stabapply __P((void (*)(STAB *, int), int));
+
+/*
+** Operation, send, error, and MIME modes
+**
+** The operation mode describes the basic operation of sendmail.
+** This can be set from the command line, and is "send mail" by
+** default.
+**
+** The send mode tells how to send mail. It can be set in the
+** configuration file. Its setting determines how quickly the
+** mail will be delivered versus the load on your system. If the
+** -v (verbose) flag is given, it will be forced to SM_DELIVER
+** mode.
+**
+** The error mode tells how to return errors.
+*/
+
+#define MD_DELIVER 'm' /* be a mail sender */
+#define MD_SMTP 's' /* run SMTP on standard input */
+#define MD_ARPAFTP 'a' /* obsolete ARPANET mode (Grey Book) */
+#define MD_DAEMON 'd' /* run as a daemon */
+#define MD_FGDAEMON 'D' /* run daemon in foreground */
+#define MD_VERIFY 'v' /* verify: don't collect or deliver */
+#define MD_TEST 't' /* test mode: resolve addrs only */
+#define MD_INITALIAS 'i' /* initialize alias database */
+#define MD_PRINT 'p' /* print the queue */
+#define MD_PRINTNQE 'P' /* print number of entries in queue */
+#define MD_FREEZE 'z' /* freeze the configuration file */
+#define MD_HOSTSTAT 'h' /* print persistent host stat info */
+#define MD_PURGESTAT 'H' /* purge persistent host stat info */
+#define MD_QUEUERUN 'q' /* queue run */
+
+/* Note: see also include/sendmail/pathnames.h: GET_CLIENT_CF */
+
+/* values for e_sendmode -- send modes */
+#define SM_DELIVER 'i' /* interactive delivery */
+#define SM_FORK 'b' /* deliver in background */
+#define SM_QUEUE 'q' /* queue, don't deliver */
+#define SM_DEFER 'd' /* defer map lookups as well as queue */
+#define SM_VERIFY 'v' /* verify only (used internally) */
+
+#define WILL_BE_QUEUED(m) ((m) == SM_QUEUE || (m) == SM_DEFER)
+
+/* used only as a parameter to sendall */
+#define SM_DEFAULT '\0' /* unspecified, use SendMode */
+
+/* functions */
+extern void set_delivery_mode __P((int, ENVELOPE *));
+
+/* values for e_errormode -- error handling modes */
+#define EM_PRINT 'p' /* print errors */
+#define EM_MAIL 'm' /* mail back errors */
+#define EM_WRITE 'w' /* write back errors */
+#define EM_BERKNET 'e' /* special berknet processing */
+#define EM_QUIET 'q' /* don't print messages (stat only) */
+
+
+/* bit values for MimeMode */
+#define MM_CVTMIME 0x0001 /* convert 8 to 7 bit MIME */
+#define MM_PASS8BIT 0x0002 /* just send 8 bit data blind */
+#define MM_MIME8BIT 0x0004 /* convert 8-bit data to MIME */
+
+
+/* how to handle messages without any recipient addresses */
+#define NRA_NO_ACTION 0 /* just leave it as is */
+#define NRA_ADD_TO 1 /* add To: header */
+#define NRA_ADD_APPARENTLY_TO 2 /* add Apparently-To: header */
+#define NRA_ADD_BCC 3 /* add empty Bcc: header */
+#define NRA_ADD_TO_UNDISCLOSED 4 /* add To: undisclosed:; header */
+
+
+/* flags to putxline */
+#define PXLF_NOTHINGSPECIAL 0 /* no special mapping */
+#define PXLF_MAPFROM 0x0001 /* map From_ to >From_ */
+#define PXLF_STRIP8BIT 0x0002 /* strip 8th bit */
+#define PXLF_HEADER 0x0004 /* map newlines in headers */
+#define PXLF_NOADDEOL 0x0008 /* if EOL not present, don't add one */
+
+/*
+** Privacy flags
+** These are bit values for the PrivacyFlags word.
+*/
+
+#define PRIV_PUBLIC 0 /* what have I got to hide? */
+#define PRIV_NEEDMAILHELO 0x00000001 /* insist on HELO for MAIL */
+#define PRIV_NEEDEXPNHELO 0x00000002 /* insist on HELO for EXPN */
+#define PRIV_NEEDVRFYHELO 0x00000004 /* insist on HELO for VRFY */
+#define PRIV_NOEXPN 0x00000008 /* disallow EXPN command */
+#define PRIV_NOVRFY 0x00000010 /* disallow VRFY command */
+#define PRIV_AUTHWARNINGS 0x00000020 /* flag possible auth probs */
+#define PRIV_NOVERB 0x00000040 /* disallow VERB command */
+#define PRIV_RESTRICTMAILQ 0x00010000 /* restrict mailq command */
+#define PRIV_RESTRICTQRUN 0x00020000 /* restrict queue run */
+#define PRIV_RESTRICTEXPAND 0x00040000 /* restrict alias/forward expansion */
+#define PRIV_NOETRN 0x00080000 /* disallow ETRN command */
+#define PRIV_NOBODYRETN 0x00100000 /* do not return bodies on bounces */
+#define PRIV_NORECEIPTS 0x00200000 /* disallow return receipts */
+#if _FFR_PRIV_NOACTUALRECIPIENT
+# define PRIV_NOACTUALRECIPIENT 0x00400000 /* no X-Actual-Recipient in DSNs */
+#endif /* _FFR_PRIV_NOACTUALRECIPIENT */
+
+/* don't give no info, anyway, anyhow */
+#define PRIV_GOAWAY 0x0000ffff
+
+/* struct defining such things */
+struct prival
+{
+ char *pv_name; /* name of privacy flag */
+ unsigned long pv_flag; /* numeric level */
+};
+
+EXTERN unsigned long PrivacyFlags; /* privacy flags */
+
+
+/*
+** Flags passed to remotename, parseaddr, allocaddr, and buildaddr.
+*/
+
+#define RF_SENDERADDR 0x001 /* this is a sender address */
+#define RF_HEADERADDR 0x002 /* this is a header address */
+#define RF_CANONICAL 0x004 /* strip comment information */
+#define RF_ADDDOMAIN 0x008 /* OK to do domain extension */
+#define RF_COPYPARSE 0x010 /* copy parsed user & host */
+#define RF_COPYPADDR 0x020 /* copy print address */
+#define RF_COPYALL (RF_COPYPARSE|RF_COPYPADDR)
+#define RF_COPYNONE 0
+
+/*
+** Flags passed to rscheck
+*/
+
+#define RSF_RMCOMM 0x0001 /* strip comments */
+#define RSF_UNSTRUCTURED 0x0002 /* unstructured, ignore syntax errors */
+#define RSF_COUNT 0x0004 /* count rejections (statistics)? */
+
+/*
+** Flags passed to mime8to7 and putheader.
+*/
+
+#define M87F_OUTER 0 /* outer context */
+#define M87F_NO8BIT 0x0001 /* can't have 8-bit in this section */
+#define M87F_DIGEST 0x0002 /* processing multipart/digest */
+#define M87F_NO8TO7 0x0004 /* don't do 8->7 bit conversions */
+
+/* functions */
+extern void mime7to8 __P((MCI *, HDR *, ENVELOPE *));
+extern int mime8to7 __P((MCI *, HDR *, ENVELOPE *, char **, int));
+
+/*
+** Flags passed to returntosender.
+*/
+
+#define RTSF_NO_BODY 0 /* send headers only */
+#define RTSF_SEND_BODY 0x0001 /* include body of message in return */
+#define RTSF_PM_BOUNCE 0x0002 /* this is a postmaster bounce */
+
+/* functions */
+extern int returntosender __P((char *, ADDRESS *, int, ENVELOPE *));
+
+/*
+** Mail Filters (milter)
+*/
+
+/*
+** 32-bit type used by milter
+** (needed by libmilter even if MILTER isn't defined)
+*/
+
+typedef SM_INT32 mi_int32;
+
+#if MILTER
+# define SMFTO_WRITE 0 /* Timeout for sending information */
+# define SMFTO_READ 1 /* Timeout waiting for a response */
+# define SMFTO_EOM 2 /* Timeout for ACK/NAK to EOM */
+# define SMFTO_CONNECT 3 /* Timeout for connect() */
+
+# define SMFTO_NUM_TO 4 /* Total number of timeouts */
+
+struct milter
+{
+ char *mf_name; /* filter name */
+ BITMAP256 mf_flags; /* MTA flags */
+ mi_int32 mf_fvers; /* filter version */
+ mi_int32 mf_fflags; /* filter flags */
+ mi_int32 mf_pflags; /* protocol flags */
+ char *mf_conn; /* connection info */
+ int mf_sock; /* connected socket */
+ char mf_state; /* state of filter */
+ time_t mf_timeout[SMFTO_NUM_TO]; /* timeouts */
+};
+
+/* MTA flags */
+# define SMF_REJECT 'R' /* Reject connection on filter fail */
+# define SMF_TEMPFAIL 'T' /* tempfail connection on failure */
+# define SMF_TEMPDROP '4' /* 421 connection on failure */
+
+/* states */
+# define SMFS_CLOSED 'C' /* closed for all further actions */
+# define SMFS_OPEN 'O' /* connected to remote milter filter */
+# define SMFS_INMSG 'M' /* currently servicing a message */
+# define SMFS_DONE 'D' /* done with current message */
+# define SMFS_CLOSABLE 'Q' /* done with current connection */
+# define SMFS_ERROR 'E' /* error state */
+# define SMFS_READY 'R' /* ready for action */
+
+EXTERN struct milter *InputFilters[MAXFILTERS];
+EXTERN char *InputFilterList;
+EXTERN int MilterLogLevel;
+
+/* functions */
+extern void setup_daemon_milters __P((void));
+#endif /* MILTER */
+
+/*
+** Vendor codes
+**
+** Vendors can customize sendmail to add special behaviour,
+** generally for back compatibility. Ideally, this should
+** be set up in the .cf file using the "V" command. However,
+** it's quite reasonable for some vendors to want the default
+** be their old version; this can be set using
+** -DVENDOR_DEFAULT=VENDOR_xxx
+** in the Makefile.
+**
+** Vendors should apply to sendmail@sendmail.org for
+** unique vendor codes.
+*/
+
+#define VENDOR_BERKELEY 1 /* Berkeley-native configuration file */
+#define VENDOR_SUN 2 /* Sun-native configuration file */
+#define VENDOR_HP 3 /* Hewlett-Packard specific config syntax */
+#define VENDOR_IBM 4 /* IBM specific config syntax */
+#define VENDOR_SENDMAIL 5 /* Sendmail, Inc. specific config syntax */
+#define VENDOR_DEC 6 /* Compaq, DEC, Digital */
+
+/* prototypes for vendor-specific hook routines */
+extern void vendor_daemon_setup __P((ENVELOPE *));
+extern void vendor_set_uid __P((UID_T));
+
+
+/*
+** Terminal escape codes.
+**
+** To make debugging output clearer.
+*/
+
+struct termescape
+{
+ char *te_rv_on; /* turn reverse-video on */
+ char *te_rv_off; /* turn reverse-video off */
+};
+
+/*
+** Additional definitions
+*/
+
+/*
+** d_flags, see daemon.c
+** general rule: lower case: required, upper case: No
+*/
+
+#define D_AUTHREQ 'a' /* authentication required */
+#define D_BINDIF 'b' /* use if_addr for outgoing connection */
+#define D_CANONREQ 'c' /* canonification required (cf) */
+#define D_IFNHELO 'h' /* use if name for HELO */
+#define D_FQMAIL 'f' /* fq sender address required (cf) */
+#define D_FQRCPT 'r' /* fq recipient address required (cf) */
+#define D_SMTPS 's' /* SMTP over SSL (smtps) */
+#define D_UNQUALOK 'u' /* unqualified address is ok (cf) */
+#define D_NOAUTH 'A' /* no AUTH */
+#define D_NOCANON 'C' /* no canonification (cf) */
+#define D_NOETRN 'E' /* no ETRN (MSA) */
+#define D_NOTLS 'S' /* don't use STARTTLS */
+#define D_ETRNONLY ((char)0x01) /* allow only ETRN (disk low) */
+#define D_OPTIONAL 'O' /* optional socket */
+#define D_DISABLE ((char)0x02) /* optional socket disabled */
+#define D_ISSET ((char)0x03) /* this client struct is set */
+
+#if STARTTLS
+/*
+** TLS
+*/
+
+/* what to do in the TLS initialization */
+#define TLS_I_NONE 0x00000000 /* no requirements... */
+#define TLS_I_CERT_EX 0x00000001 /* cert must exist */
+#define TLS_I_CERT_UNR 0x00000002 /* cert must be g/o unreadable */
+#define TLS_I_KEY_EX 0x00000004 /* key must exist */
+#define TLS_I_KEY_UNR 0x00000008 /* key must be g/o unreadable */
+#define TLS_I_CERTP_EX 0x00000010 /* CA cert path must exist */
+#define TLS_I_CERTP_UNR 0x00000020 /* CA cert path must be g/o unreadable */
+#define TLS_I_CERTF_EX 0x00000040 /* CA cert file must exist */
+#define TLS_I_CERTF_UNR 0x00000080 /* CA cert file must be g/o unreadable */
+#define TLS_I_RSA_TMP 0x00000100 /* RSA TMP must be generated */
+#define TLS_I_USE_KEY 0x00000200 /* private key must usable */
+#define TLS_I_USE_CERT 0x00000400 /* certificate must be usable */
+#define TLS_I_VRFY_PATH 0x00000800 /* load verify path must succeed */
+#define TLS_I_VRFY_LOC 0x00001000 /* load verify default must succeed */
+#define TLS_I_CACHE 0x00002000 /* require cache */
+#define TLS_I_TRY_DH 0x00004000 /* try DH certificate */
+#define TLS_I_REQ_DH 0x00008000 /* require DH certificate */
+#define TLS_I_DHPAR_EX 0x00010000 /* require DH parameters */
+#define TLS_I_DHPAR_UNR 0x00020000 /* DH param. must be g/o unreadable */
+#define TLS_I_DH512 0x00040000 /* generate 512bit DH param */
+#define TLS_I_DH1024 0x00080000 /* generate 1024bit DH param */
+#define TLS_I_DH2048 0x00100000 /* generate 2048bit DH param */
+#define TLS_I_NO_VRFY 0x00200000 /* do not require authentication */
+#define TLS_I_KEY_OUNR 0x00400000 /* Key must be other unreadable */
+#define TLS_I_CRLF_EX 0x00800000 /* CRL file must exist */
+#define TLS_I_CRLF_UNR 0x01000000 /* CRL file must be g/o unreadable */
+
+/* require server cert */
+#define TLS_I_SRV_CERT (TLS_I_CERT_EX | TLS_I_KEY_EX | \
+ TLS_I_KEY_UNR | TLS_I_KEY_OUNR | \
+ TLS_I_CERTP_EX | TLS_I_CERTF_EX | \
+ TLS_I_USE_KEY | TLS_I_USE_CERT)
+
+/* server requirements */
+#define TLS_I_SRV (TLS_I_SRV_CERT | TLS_I_RSA_TMP | TLS_I_VRFY_PATH | \
+ TLS_I_VRFY_LOC | TLS_I_TRY_DH | TLS_I_DH512)
+
+/* client requirements */
+#define TLS_I_CLT (TLS_I_KEY_UNR | TLS_I_KEY_OUNR)
+
+#define TLS_AUTH_OK 0
+#define TLS_AUTH_NO 1
+#define TLS_AUTH_FAIL (-1)
+
+/* functions */
+extern bool init_tls_library __P((void));
+extern bool inittls __P((SSL_CTX **, unsigned long, bool, char *, char *, char *, char *, char *));
+extern bool initclttls __P((bool));
+extern void setclttls __P((bool));
+extern bool initsrvtls __P((bool));
+extern int tls_get_info __P((SSL *, bool, char *, MACROS_T *, bool));
+extern int endtls __P((SSL *, char *));
+extern void tlslogerr __P((char *));
+
+
+EXTERN char *CACertPath; /* path to CA certificates (dir. with hashes) */
+EXTERN char *CACertFile; /* file with CA certificate */
+EXTERN char *CltCertFile; /* file with client certificate */
+EXTERN char *CltKeyFile; /* file with client private key */
+# if _FFR_TLS_1
+EXTERN char *CipherList; /* list of ciphers */
+EXTERN char *DHParams5; /* file with DH parameters (512) */
+# endif /* _FFR_TLS_1 */
+EXTERN char *DHParams; /* file with DH parameters */
+EXTERN char *RandFile; /* source of random data */
+EXTERN char *SrvCertFile; /* file with server certificate */
+EXTERN char *SrvKeyFile; /* file with server private key */
+EXTERN char *CRLFile; /* file CRLs */
+#if _FFR_CRLPATH
+EXTERN char *CRLPath; /* path to CRLs (dir. with hashes) */
+#endif /* _FFR_CRLPATH */
+EXTERN unsigned long TLS_Srv_Opts; /* TLS server options */
+#endif /* STARTTLS */
+
+/*
+** Queue related items
+*/
+
+/* queue file names */
+#define ANYQFL_LETTER '?'
+#define QUARQF_LETTER 'h'
+#define DATAFL_LETTER 'd'
+#define XSCRPT_LETTER 'x'
+#define NORMQF_LETTER 'q'
+#define NEWQFL_LETTER 't'
+
+# define TEMPQF_LETTER 'T'
+# define LOSEQF_LETTER 'Q'
+
+/* queue sort order */
+#define QSO_BYPRIORITY 0 /* sort by message priority */
+#define QSO_BYHOST 1 /* sort by first host name */
+#define QSO_BYTIME 2 /* sort by submission time */
+#define QSO_BYFILENAME 3 /* sort by file name only */
+#define QSO_RANDOM 4 /* sort in random order */
+#define QSO_BYMODTIME 5 /* sort by modification time */
+#define QSO_NONE 6 /* do not sort */
+#if _FFR_RHS
+# define QSO_BYSHUFFLE 7 /* sort by shuffled host name */
+#endif /* _FFR_RHS */
+
+#define NOQGRP (-1) /* no queue group (yet) */
+#define ENVQGRP (-2) /* use queue group of envelope */
+#define NOAQGRP (-3) /* no queue group in addr (yet) */
+#define ISVALIDQGRP(x) ((x) >= 0) /* valid queue group? */
+#define NOQDIR (-1) /* no queue directory (yet) */
+#define ENVQDIR (-2) /* use queue directory of envelope */
+#define NOAQDIR (-3) /* no queue directory in addr (yet) */
+#define ISVALIDQDIR(x) ((x) >= 0) /* valid queue directory? */
+#define RS_QUEUEGROUP "queuegroup" /* ruleset for queue group selection */
+
+#define NOW ((time_t) (-1)) /* queue return: now */
+
+/* SuperSafe values */
+#define SAFE_NO 0 /* no fsync(): don't use... */
+#define SAFE_INTERACTIVE 1 /* limit fsync() in -odi */
+#define SAFE_REALLY 2 /* always fsync() */
+#define SAFE_REALLY_POSTMILTER 3 /* fsync() if milter says OK */
+
+/* QueueMode bits */
+#define QM_NORMAL ' '
+#define QM_QUARANTINE 'Q'
+#define QM_LOST 'L'
+
+/* Queue Run Limitations */
+struct queue_char
+{
+ char *queue_match; /* string to match */
+ bool queue_negate; /* or not match, if set */
+ struct queue_char *queue_next;
+};
+
+/* run_work_group() flags */
+#define RWG_NONE 0x0000
+#define RWG_FORK 0x0001
+#define RWG_VERBOSE 0x0002
+#define RWG_PERSISTENT 0x0004
+#define RWG_FORCE 0x0008
+#define RWG_RUNALL 0x0010
+
+typedef struct queue_char QUEUE_CHAR;
+
+EXTERN int volatile CurRunners; /* current number of runner children */
+EXTERN int MaxQueueRun; /* maximum number of jobs in one queue run */
+EXTERN int MaxQueueChildren; /* max # of forked queue children */
+EXTERN int MaxRunnersPerQueue; /* max # proc's active in queue group */
+EXTERN int NiceQueueRun; /* nice queue runs to this value */
+EXTERN int NumQueue; /* number of queue groups */
+EXTERN int QueueFileMode; /* mode on files in mail queue */
+EXTERN int QueueMode; /* which queue items to act upon */
+EXTERN int QueueSortOrder; /* queue sorting order algorithm */
+EXTERN time_t MinQueueAge; /* min delivery interval */
+EXTERN time_t QueueIntvl; /* intervals between running the queue */
+EXTERN char *QueueDir; /* location of queue directory */
+EXTERN QUEUE_CHAR *QueueLimitId; /* limit queue run to id */
+EXTERN QUEUE_CHAR *QueueLimitQuarantine; /* limit queue run to quarantine reason */
+EXTERN QUEUE_CHAR *QueueLimitRecipient; /* limit queue run to rcpt */
+EXTERN QUEUE_CHAR *QueueLimitSender; /* limit queue run to sender */
+EXTERN QUEUEGRP *Queue[MAXQUEUEGROUPS + 1]; /* queue groups */
+
+/* functions */
+extern void assign_queueid __P((ENVELOPE *));
+extern ADDRESS *copyqueue __P((ADDRESS *, SM_RPOOL_T *));
+extern void cleanup_queues __P((void));
+extern bool doqueuerun __P((void));
+extern void initsys __P((ENVELOPE *));
+extern void loseqfile __P((ENVELOPE *, char *));
+extern int name2qid __P((char *));
+extern char *qid_printname __P((ENVELOPE *));
+extern char *qid_printqueue __P((int, int));
+extern void quarantine_queue __P((char *, int));
+extern char *queuename __P((ENVELOPE *, int));
+extern void queueup __P((ENVELOPE *, bool, bool));
+extern bool runqueue __P((bool, bool, bool, bool));
+extern bool run_work_group __P((int, int));
+extern void set_def_queueval __P((QUEUEGRP *, bool));
+extern void setup_queues __P((bool));
+extern bool setnewqueue __P((ENVELOPE *));
+extern bool shouldqueue __P((long, time_t));
+extern void sync_queue_time __P((void));
+extern void init_qid_alg __P((void));
+extern int print_single_queue __P((int, int));
+#if REQUIRES_DIR_FSYNC
+# define SYNC_DIR(path, panic) sync_dir(path, panic)
+extern void sync_dir __P((char *, bool));
+#else /* REQUIRES_DIR_FSYNC */
+# define SYNC_DIR(path, panic) ((void) 0)
+#endif /* REQUIRES_DIR_FSYNC */
+
+/*
+** Timeouts
+**
+** Indicated values are the MINIMUM per RFC 1123 section 5.3.2.
+*/
+
+EXTERN struct
+{
+ /* RFC 1123-specified timeouts [minimum value] */
+ time_t to_initial; /* initial greeting timeout [5m] */
+ time_t to_mail; /* MAIL command [5m] */
+ time_t to_rcpt; /* RCPT command [5m] */
+ time_t to_datainit; /* DATA initiation [2m] */
+ time_t to_datablock; /* DATA block [3m] */
+ time_t to_datafinal; /* DATA completion [10m] */
+ time_t to_nextcommand; /* next command [5m] */
+ /* following timeouts are not mentioned in RFC 1123 */
+ time_t to_iconnect; /* initial connection timeout (first try) */
+ time_t to_connect; /* initial connection timeout (later tries) */
+ time_t to_aconnect; /* all connections timeout (MX and A records) */
+ time_t to_rset; /* RSET command */
+ time_t to_helo; /* HELO command */
+ time_t to_quit; /* QUIT command */
+ time_t to_miscshort; /* misc short commands (NOOP, VERB, etc) */
+ time_t to_ident; /* IDENT protocol requests */
+ time_t to_fileopen; /* opening :include: and .forward files */
+ time_t to_control; /* process a control socket command */
+ time_t to_lhlo; /* LMTP: LHLO command */
+#if SASL
+ time_t to_auth; /* AUTH dialogue [10m] */
+#endif /* SASL */
+#if STARTTLS
+ time_t to_starttls; /* STARTTLS dialogue [10m] */
+#endif /* STARTTLS */
+ /* following are per message */
+ time_t to_q_return[MAXTOCLASS]; /* queue return timeouts */
+ time_t to_q_warning[MAXTOCLASS]; /* queue warning timeouts */
+ time_t res_retrans[MAXRESTOTYPES]; /* resolver retransmit */
+ int res_retry[MAXRESTOTYPES]; /* resolver retry */
+} TimeOuts;
+
+/* timeout classes for return and warning timeouts */
+#define TOC_NORMAL 0 /* normal delivery */
+#define TOC_URGENT 1 /* urgent delivery */
+#define TOC_NONURGENT 2 /* non-urgent delivery */
+#define TOC_DSN 3 /* DSN delivery */
+
+/* resolver timeout specifiers */
+#define RES_TO_FIRST 0 /* first attempt */
+#define RES_TO_NORMAL 1 /* subsequent attempts */
+#define RES_TO_DEFAULT 2 /* default value */
+
+/* functions */
+extern void inittimeouts __P((char *, bool));
+
+/*
+** Interface probing
+*/
+
+#define DPI_PROBENONE 0 /* Don't probe any interfaces */
+#define DPI_PROBEALL 1 /* Probe all interfaces */
+#define DPI_SKIPLOOPBACK 2 /* Don't probe loopback interfaces */
+
+/*
+** Trace information
+*/
+
+/* macros for debugging flags */
+#define tTd(flag, level) (tTdvect[flag] >= (unsigned char)level)
+#define tTdlevel(flag) (tTdvect[flag])
+
+/* variables */
+extern unsigned char tTdvect[100]; /* trace vector */
+
+/*
+** Miscellaneous information.
+*/
+
+/*
+** The "no queue id" queue id for sm_syslog
+*/
+
+#define NOQID "*~*"
+
+/* use id or NOQID (to avoid NOQUEUE in logfile) */
+#define E_ID(id) ((id) == NULL ? NOQID : (id))
+
+#define CURHOSTNAME (CurHostName == NULL ? "local" : CurHostName)
+
+/*
+** Some in-line functions
+*/
+
+/* set exit status */
+#define setstat(s) { \
+ if (ExitStat == EX_OK || ExitStat == EX_TEMPFAIL) \
+ ExitStat = s; \
+ }
+
+
+#define STRUCTCOPY(s, d) d = s
+
+/* free a pointer if it isn't NULL and set it to NULL */
+#define SM_FREE_CLR(p) \
+ if ((p) != NULL) \
+ { \
+ sm_free(p); \
+ (p) = NULL; \
+ } \
+ else
+
+/*
+** Update a permanent string variable with a new value.
+** The old value is freed, the new value is strdup'ed.
+**
+** We use sm_pstrdup_x to duplicate the string because it raises
+** an exception on error, and because it allocates "permanent storage"
+** which is not expected to be freed before process exit.
+** The latter is important for memory leak analysis.
+**
+** If an exception occurs while strdup'ing the new value,
+** then the variable remains set to the old value.
+** That's why the strdup must occur before we free the old value.
+**
+** The macro uses a do loop so that this idiom will work:
+** if (...)
+** PSTRSET(var, val1);
+** else
+** PSTRSET(var, val2);
+*/
+#define PSTRSET(var, val) \
+ do \
+ { \
+ char *_newval = sm_pstrdup_x(val); \
+ if (var != NULL) \
+ sm_free(var); \
+ var = _newval; \
+ } while (0)
+
+#define _CHECK_RESTART \
+ do \
+ { \
+ if (ShutdownRequest != NULL) \
+ shutdown_daemon(); \
+ else if (RestartRequest != NULL) \
+ restart_daemon(); \
+ else if (RestartWorkGroup) \
+ restart_marked_work_groups(); \
+ } while (0)
+
+# define CHECK_RESTART _CHECK_RESTART
+
+/* reply types (text in SmtpMsgBuffer) */
+#define XS_DEFAULT 0
+#define XS_STARTTLS 1
+#define XS_AUTH 2
+
+/*
+** Global variables.
+*/
+
+EXTERN bool AllowBogusHELO; /* allow syntax errors on HELO command */
+EXTERN bool CheckAliases; /* parse addresses during newaliases */
+EXTERN bool ColonOkInAddr; /* single colon legal in address */
+#if !defined(_USE_SUN_NSSWITCH_) && !defined(_USE_DEC_SVC_CONF_)
+EXTERN bool ConfigFileRead; /* configuration file has been read */
+#endif /* !defined(_USE_SUN_NSSWITCH_) && !defined(_USE_DEC_SVC_CONF_) */
+EXTERN bool volatile DataProgress; /* have we sent anything since last check */
+EXTERN bool DisConnected; /* running with OutChannel redirect to transcript file */
+EXTERN bool DontExpandCnames; /* do not $[...$] expand CNAMEs */
+EXTERN bool DontInitGroups; /* avoid initgroups() because of NIS cost */
+EXTERN bool DontLockReadFiles; /* don't read lock support files */
+EXTERN bool DontPruneRoutes; /* don't prune source routes */
+EXTERN bool ForkQueueRuns; /* fork for each job when running the queue */
+EXTERN bool FromFlag; /* if set, "From" person is explicit */
+EXTERN bool GrabTo; /* if set, get recipients from msg */
+EXTERN bool HasEightBits; /* has at least one eight bit input byte */
+EXTERN bool HasWildcardMX; /* don't use MX records when canonifying */
+EXTERN bool HoldErrs; /* only output errors to transcript */
+EXTERN bool IgnoreHostStatus; /* ignore long term host status files */
+EXTERN bool IgnrDot; /* don't let dot end messages */
+EXTERN bool LogUsrErrs; /* syslog user errors (e.g., SMTP RCPT cmd) */
+EXTERN bool MatchGecos; /* look for user names in gecos field */
+EXTERN bool MeToo; /* send to the sender also */
+EXTERN bool NoAlias; /* suppress aliasing */
+EXTERN bool NoConnect; /* don't connect to non-local mailers */
+EXTERN bool OnlyOneError; /* .... or only want to give one SMTP reply */
+EXTERN bool QuickAbort; /* .... but only if we want a quick abort */
+#if REQUIRES_DIR_FSYNC
+EXTERN bool RequiresDirfsync; /* requires fsync() for directory */
+#endif /* REQUIRES_DIR_FSYNC */
+EXTERN bool volatile RestartWorkGroup; /* daemon needs to restart some work groups */
+EXTERN bool RrtImpliesDsn; /* turn Return-Receipt-To: into DSN */
+EXTERN bool SaveFrom; /* save leading "From" lines */
+EXTERN bool SendMIMEErrors; /* send error messages in MIME format */
+EXTERN bool SevenBitInput; /* force 7-bit data on input */
+EXTERN bool SingleLineFromHeader; /* force From: header to be one line */
+EXTERN bool SingleThreadDelivery; /* single thread hosts on delivery */
+#if _FFR_SOFT_BOUNCE
+EXTERN bool SoftBounce; /* replace 5xy by 4xy (for testing) */
+#endif /* _FFR_SOFT_BOUNCE */
+EXTERN bool volatile StopRequest; /* stop sending output */
+EXTERN bool SuprErrs; /* set if we are suppressing errors */
+EXTERN bool TryNullMXList; /* if we are the best MX, try host directly */
+EXTERN bool UseMSP; /* mail submission: group writable queue ok? */
+EXTERN bool WorkAroundBrokenAAAA; /* some nameservers return SERVFAIL on AAAA queries */
+EXTERN bool UseErrorsTo; /* use Errors-To: header (back compat) */
+EXTERN bool UseNameServer; /* using DNS -- interpret h_errno & MX RRs */
+EXTERN char InetMode; /* default network for daemon mode */
+EXTERN char OpMode; /* operation mode, see below */
+EXTERN char SpaceSub; /* substitution for <lwsp> */
+EXTERN int BadRcptThrottle; /* Throttle rejected RCPTs per SMTP message */
+EXTERN int CheckpointInterval; /* queue file checkpoint interval */
+EXTERN int ConfigLevel; /* config file level */
+EXTERN int ConnRateThrottle; /* throttle for SMTP connection rate */
+EXTERN int volatile CurChildren; /* current number of daemonic children */
+EXTERN int CurrentLA; /* current load average */
+EXTERN int DefaultNotify; /* default DSN notification flags */
+EXTERN int DelayLA; /* load average to delay connections */
+EXTERN int DontProbeInterfaces; /* don't probe interfaces for names */
+EXTERN int Errors; /* set if errors (local to single pass) */
+EXTERN int ExitStat; /* exit status code */
+EXTERN int FastSplit; /* fast initial splitting of envelopes */
+EXTERN int FileMode; /* mode on files */
+EXTERN int LineNumber; /* line number in current input */
+EXTERN int LogLevel; /* level of logging to perform */
+EXTERN int MaxAliasRecursion; /* maximum depth of alias recursion */
+EXTERN int MaxChildren; /* maximum number of daemonic children */
+EXTERN int MaxForwardEntries; /* maximum number of forward entries */
+EXTERN int MaxHeadersLength; /* max length of headers */
+EXTERN int MaxHopCount; /* max # of hops until bounce */
+EXTERN int MaxMacroRecursion; /* maximum depth of macro recursion */
+EXTERN int MaxMimeFieldLength; /* maximum MIME field length */
+EXTERN int MaxMimeHeaderLength; /* maximum MIME header length */
+
+EXTERN int MaxRcptPerMsg; /* max recipients per SMTP message */
+EXTERN int MaxRuleRecursion; /* maximum depth of ruleset recursion */
+EXTERN int MimeMode; /* MIME processing mode */
+EXTERN int NoRecipientAction;
+
+#if SM_CONF_SHM
+EXTERN int Numfilesys; /* number of queue file systems */
+EXTERN int *PNumFileSys;
+# define NumFileSys (*PNumFileSys)
+# else /* SM_CONF_SHM */
+EXTERN int NumFileSys; /* number of queue file systems */
+# endif /* SM_CONF_SHM */
+
+EXTERN int QueueLA; /* load average starting forced queueing */
+EXTERN int RefuseLA; /* load average refusing connections */
+EXTERN time_t RejectLogInterval; /* time btwn log msgs while refusing */
+EXTERN int SuperSafe; /* be extra careful, even if expensive */
+EXTERN int VendorCode; /* vendor-specific operation enhancements */
+EXTERN int Verbose; /* set if blow-by-blow desired */
+EXTERN gid_t DefGid; /* default gid to run as */
+EXTERN gid_t RealGid; /* real gid of caller */
+EXTERN gid_t RunAsGid; /* GID to become for bulk of run */
+EXTERN gid_t EffGid; /* effective gid */
+#if SM_CONF_SHM
+EXTERN key_t ShmKey; /* shared memory key */
+# if _FFR_SELECT_SHM
+EXTERN char *ShmKeyFile; /* shared memory key file */
+# endif /* _FFR_SELECT_SHM */
+#endif /* SM_CONF_SHM */
+EXTERN pid_t CurrentPid; /* current process id */
+EXTERN pid_t DaemonPid; /* process id of daemon */
+EXTERN pid_t PidFilePid; /* daemon/queue runner who wrote pid file */
+EXTERN uid_t DefUid; /* default uid to run as */
+EXTERN uid_t RealUid; /* real uid of caller */
+EXTERN uid_t RunAsUid; /* UID to become for bulk of run */
+EXTERN uid_t TrustedUid; /* uid of trusted user for files and startup */
+EXTERN size_t DataFileBufferSize; /* size of buf for in-core data file */
+EXTERN time_t DeliverByMin; /* deliver by minimum time */
+EXTERN time_t DialDelay; /* delay between dial-on-demand tries */
+EXTERN time_t SafeAlias; /* interval to wait until @:@ in alias file */
+EXTERN time_t ServiceCacheMaxAge; /* refresh interval for cache */
+EXTERN size_t XscriptFileBufferSize; /* size of buf for in-core transcript file */
+EXTERN MODE_T OldUmask; /* umask when sendmail starts up */
+EXTERN long MaxMessageSize; /* advertised max size we will accept */
+EXTERN long MinBlocksFree; /* min # of blocks free on queue fs */
+EXTERN long QueueFactor; /* slope of queue function */
+EXTERN long WkClassFact; /* multiplier for message class -> priority */
+EXTERN long WkRecipFact; /* multiplier for # of recipients -> priority */
+EXTERN long WkTimeFact; /* priority offset each time this job is run */
+EXTERN char *ControlSocketName; /* control socket filename [control.c] */
+EXTERN char *CurHostName; /* current host we are dealing with */
+EXTERN char *DeadLetterDrop; /* path to dead letter office */
+EXTERN char *DefUser; /* default user to run as (from DefUid) */
+EXTERN char *DefaultCharSet; /* default character set for MIME */
+EXTERN char *DoubleBounceAddr; /* where to send double bounces */
+EXTERN char *ErrMsgFile; /* file to prepend to all error messages */
+EXTERN char *FallbackMX; /* fall back MX host */
+EXTERN char *FallbackSmartHost; /* fall back smart host */
+EXTERN char *FileName; /* name to print on error messages */
+EXTERN char *ForwardPath; /* path to search for .forward files */
+#if _FFR_HELONAME
+EXTERN char *HeloName; /* hostname to announce in HELO */
+#endif /* _FFR_HELONAME */
+EXTERN char *HelpFile; /* location of SMTP help file */
+EXTERN char *HostStatDir; /* location of host status information */
+EXTERN char *HostsFile; /* path to /etc/hosts file */
+extern char *Mbdb; /* mailbox database type */
+EXTERN char *MustQuoteChars; /* quote these characters in phrases */
+EXTERN char *MyHostName; /* name of this host for SMTP messages */
+EXTERN char *OperatorChars; /* operators (old $o macro) */
+EXTERN char *PidFile; /* location of proc id file [conf.c] */
+EXTERN char *PostMasterCopy; /* address to get errs cc's */
+EXTERN char *ProcTitlePrefix; /* process title prefix */
+EXTERN char *RealHostName; /* name of host we are talking to */
+EXTERN char *RealUserName; /* real user name of caller */
+EXTERN char *volatile RestartRequest;/* a sendmail restart has been requested */
+EXTERN char *RunAsUserName; /* user to become for bulk of run */
+EXTERN char *SafeFileEnv; /* chroot location for file delivery */
+EXTERN char *ServiceSwitchFile; /* backup service switch */
+EXTERN char *volatile ShutdownRequest;/* a sendmail shutdown has been requested */
+EXTERN char *SmtpGreeting; /* SMTP greeting message (old $e macro) */
+EXTERN char *SmtpPhase; /* current phase in SMTP processing */
+EXTERN char SmtpError[MAXLINE]; /* save failure error messages */
+EXTERN char *StatFile; /* location of statistics summary */
+EXTERN char *TimeZoneSpec; /* override time zone specification */
+EXTERN char *UdbSpec; /* user database source spec */
+EXTERN char *UnixFromLine; /* UNIX From_ line (old $l macro) */
+EXTERN char **ExternalEnviron; /* saved user (input) environment */
+EXTERN char **SaveArgv; /* argument vector for re-execing */
+EXTERN BITMAP256 DontBlameSendmail; /* DontBlameSendmail bits */
+EXTERN SM_FILE_T *InChannel; /* input connection */
+EXTERN SM_FILE_T *OutChannel; /* output connection */
+EXTERN SM_FILE_T *TrafficLogFile; /* file in which to log all traffic */
+#if HESIOD
+EXTERN void *HesiodContext;
+#endif /* HESIOD */
+EXTERN ENVELOPE *CurEnv; /* envelope currently being processed */
+EXTERN char *RuleSetNames[MAXRWSETS]; /* ruleset number to name */
+EXTERN char *UserEnviron[MAXUSERENVIRON + 1];
+EXTERN struct rewrite *RewriteRules[MAXRWSETS];
+EXTERN struct termescape TermEscape; /* terminal escape codes */
+EXTERN SOCKADDR ConnectOnlyTo; /* override connection address (for testing) */
+EXTERN SOCKADDR RealHostAddr; /* address of host we are talking to */
+extern const SM_EXC_TYPE_T EtypeQuickAbort; /* type of a QuickAbort exception */
+
+
+EXTERN int ConnectionRateWindowSize;
+
+/*
+** Declarations of useful functions
+*/
+
+/* Transcript file */
+extern void closexscript __P((ENVELOPE *));
+extern void openxscript __P((ENVELOPE *));
+
+/* error related */
+extern void buffer_errors __P((void));
+extern void flush_errors __P((bool));
+extern void PRINTFLIKE(1, 2) message __P((const char *, ...));
+extern void PRINTFLIKE(1, 2) nmessage __P((const char *, ...));
+extern void PRINTFLIKE(1, 2) syserr __P((const char *, ...));
+extern void PRINTFLIKE(2, 3) usrerrenh __P((char *, const char *, ...));
+extern void PRINTFLIKE(1, 2) usrerr __P((const char *, ...));
+extern int isenhsc __P((const char *, int));
+extern int extenhsc __P((const char *, int, char *));
+
+/* alias file */
+extern void alias __P((ADDRESS *, ADDRESS **, int, ENVELOPE *));
+extern bool aliaswait __P((MAP *, char *, bool));
+extern void forward __P((ADDRESS *, ADDRESS **, int, ENVELOPE *));
+extern void readaliases __P((MAP *, SM_FILE_T *, bool, bool));
+extern bool rebuildaliases __P((MAP *, bool));
+extern void setalias __P((char *));
+
+/* logging */
+extern void logdelivery __P((MAILER *, MCI *, char *, const char *, ADDRESS *, time_t, ENVELOPE *));
+extern void logsender __P((ENVELOPE *, char *));
+extern void PRINTFLIKE(3, 4) sm_syslog __P((int, const char *, const char *, ...));
+
+/* SMTP */
+extern void giveresponse __P((int, char *, MAILER *, MCI *, ADDRESS *, time_t, ENVELOPE *, ADDRESS *));
+extern int reply __P((MAILER *, MCI *, ENVELOPE *, time_t, void (*)__P((char *, bool, MAILER *, MCI *, ENVELOPE *)), char **, int));
+extern void smtp __P((char *volatile, BITMAP256, ENVELOPE *volatile));
+#if SASL
+extern int smtpauth __P((MAILER *, MCI *, ENVELOPE *));
+#endif /* SASL */
+extern int smtpdata __P((MAILER *, MCI *, ENVELOPE *, ADDRESS *, time_t));
+extern int smtpgetstat __P((MAILER *, MCI *, ENVELOPE *));
+extern int smtpmailfrom __P((MAILER *, MCI *, ENVELOPE *));
+extern void smtpmessage __P((char *, MAILER *, MCI *, ...));
+extern void smtpinit __P((MAILER *, MCI *, ENVELOPE *, bool));
+extern char *smtptodsn __P((int));
+extern int smtpprobe __P((MCI *));
+extern void smtpquit __P((MAILER *, MCI *, ENVELOPE *));
+extern int smtprcpt __P((ADDRESS *, MAILER *, MCI *, ENVELOPE *, ADDRESS *, time_t));
+extern void smtprset __P((MAILER *, MCI *, ENVELOPE *));
+
+#define ISSMTPCODE(c) (isascii(c[0]) && isdigit(c[0]) && \
+ isascii(c[1]) && isdigit(c[1]) && \
+ isascii(c[2]) && isdigit(c[2]))
+#define ISSMTPREPLY(c) (ISSMTPCODE(c) && \
+ (c[3] == ' ' || c[3] == '-' || c[3] == '\0'))
+
+/* delivery */
+extern pid_t dowork __P((int, int, char *, bool, bool, ENVELOPE *));
+extern pid_t doworklist __P((ENVELOPE *, bool, bool));
+extern int endmailer __P((MCI *, ENVELOPE *, char **));
+extern int mailfile __P((char *volatile, MAILER *volatile, ADDRESS *, volatile long, ENVELOPE *));
+extern void sendall __P((ENVELOPE *, int));
+
+/* stats */
+#define STATS_NORMAL 'n'
+#define STATS_QUARANTINE 'q'
+#define STATS_REJECT 'r'
+#define STATS_CONNECT 'c'
+
+extern void markstats __P((ENVELOPE *, ADDRESS *, int));
+extern void clearstats __P((void));
+extern void poststats __P((char *));
+
+/* control socket */
+extern void closecontrolsocket __P((bool));
+extern void clrcontrol __P((void));
+extern void control_command __P((int, ENVELOPE *));
+extern int opencontrolsocket __P((void));
+
+#if MILTER
+/* milter functions */
+extern void milter_config __P((char *, struct milter **, int));
+extern void milter_setup __P((char *));
+extern void milter_set_option __P((char *, char *, bool));
+extern bool milter_can_delrcpts __P((void));
+extern bool milter_init __P((ENVELOPE *, char *));
+extern void milter_quit __P((ENVELOPE *));
+extern void milter_abort __P((ENVELOPE *));
+extern char *milter_connect __P((char *, SOCKADDR, ENVELOPE *, char *));
+extern char *milter_helo __P((char *, ENVELOPE *, char *));
+extern char *milter_envfrom __P((char **, ENVELOPE *, char *));
+extern char *milter_data_cmd __P((ENVELOPE *, char *));
+extern char *milter_envrcpt __P((char **, ENVELOPE *, char *));
+extern char *milter_data __P((ENVELOPE *, char *));
+extern char *milter_unknown __P((char *, ENVELOPE *, char *));
+#endif /* MILTER */
+
+extern char *addquotes __P((char *, SM_RPOOL_T *));
+extern char *arpadate __P((char *));
+extern bool atobool __P((char *));
+extern int atooct __P((char *));
+extern void auth_warning __P((ENVELOPE *, const char *, ...));
+extern int blocksignal __P((int));
+extern bool bitintersect __P((BITMAP256, BITMAP256));
+extern bool bitzerop __P((BITMAP256));
+extern int check_bodytype __P((char *));
+extern void buildfname __P((char *, char *, char *, int));
+extern bool chkclientmodifiers __P((int));
+extern bool chkdaemonmodifiers __P((int));
+extern int checkcompat __P((ADDRESS *, ENVELOPE *));
+#ifdef XDEBUG
+extern void checkfd012 __P((char *));
+extern void checkfdopen __P((int, char *));
+#endif /* XDEBUG */
+extern void checkfds __P((char *));
+extern bool chownsafe __P((int, bool));
+extern void cleanstrcpy __P((char *, char *, int));
+#if SM_CONF_SHM
+extern void cleanup_shm __P((bool));
+#endif /* SM_CONF_SHM */
+extern void close_sendmail_pid __P((void));
+extern void clrdaemon __P((void));
+extern void collect __P((SM_FILE_T *, bool, HDR **, ENVELOPE *, bool));
+extern bool connection_rate_check __P((SOCKADDR *, ENVELOPE *));
+extern time_t convtime __P((char *, int));
+extern char **copyplist __P((char **, bool, SM_RPOOL_T *));
+extern void copy_class __P((int, int));
+extern int count_open_connections __P((SOCKADDR *));
+extern time_t curtime __P((void));
+extern char *defcharset __P((ENVELOPE *));
+extern char *denlstring __P((char *, bool, bool));
+extern void dferror __P((SM_FILE_T *volatile, char *, ENVELOPE *));
+extern void disconnect __P((int, ENVELOPE *));
+#if _FFR_CONTROL_MSTAT
+extern void disk_status __P((SM_FILE_T *, char *));
+#endif /* _FFR_CONTROL_MSTAT */
+extern bool dns_getcanonname __P((char *, int, bool, int *, int *));
+extern pid_t dofork __P((void));
+extern int drop_privileges __P((bool));
+extern int dsntoexitstat __P((char *));
+extern void dumpfd __P((int, bool, bool));
+extern void dumpstate __P((char *));
+extern bool enoughdiskspace __P((long, ENVELOPE *));
+extern char *exitstat __P((char *));
+extern void fatal_error __P((SM_EXC_T *));
+extern char *fgetfolded __P((char *, int, SM_FILE_T *));
+extern void fill_fd __P((int, char *));
+extern char *find_character __P((char *, int));
+extern int finduser __P((char *, bool *, SM_MBDB_T *));
+extern void finis __P((bool, bool, volatile int));
+extern void fixcrlf __P((char *, bool));
+extern long freediskspace __P((char *, long *));
+#if NETINET6 && NEEDSGETIPNODE
+extern void freehostent __P((struct hostent *));
+#endif /* NETINET6 && NEEDSGETIPNODE */
+extern char *get_column __P((char *, int, int, char *, int));
+extern char *getauthinfo __P((int, bool *));
+extern int getdtsize __P((void));
+extern int getla __P((void));
+extern char *getmodifiers __P((char *, BITMAP256));
+extern BITMAP256 *getrequests __P((ENVELOPE *));
+extern char *getvendor __P((int));
+extern void help __P((char *, ENVELOPE *));
+extern void init_md __P((int, char **));
+extern void initdaemon __P((void));
+extern void inithostmaps __P((void));
+extern void initmacros __P((ENVELOPE *));
+extern void initsetproctitle __P((int, char **, char **));
+extern void init_vendor_macros __P((ENVELOPE *));
+extern SIGFUNC_DECL intsig __P((int));
+extern bool isloopback __P((SOCKADDR sa));
+extern void load_if_names __P((void));
+extern bool lockfile __P((int, char *, char *, int));
+extern void log_sendmail_pid __P((ENVELOPE *));
+extern void logundelrcpts __P((ENVELOPE *, char *, int, bool));
+extern char lower __P((int));
+extern void makelower __P((char *));
+extern int makeconnection_ds __P((char *, MCI *));
+extern int makeconnection __P((char *, volatile unsigned int, MCI *, ENVELOPE *, time_t));
+extern void makeworkgroups __P((void));
+extern void markfailure __P((ENVELOPE *, ADDRESS *, MCI *, int, bool));
+extern void mark_work_group_restart __P((int, int));
+extern char * munchstring __P((char *, char **, int));
+extern struct hostent *myhostname __P((char *, int));
+extern char *newstr __P((const char *));
+#if NISPLUS
+extern char *nisplus_default_domain __P((void)); /* extern for Sun */
+#endif /* NISPLUS */
+extern bool path_is_dir __P((char *, bool));
+extern int pickqdir __P((QUEUEGRP *qg, long fsize, ENVELOPE *e));
+extern char *pintvl __P((time_t, bool));
+extern void printav __P((SM_FILE_T *, char **));
+extern void printmailer __P((SM_FILE_T *, MAILER *));
+extern void printnqe __P((SM_FILE_T *, char *));
+extern void printopenfds __P((bool));
+extern void printqueue __P((void));
+extern void printrules __P((void));
+extern pid_t prog_open __P((char **, int *, ENVELOPE *));
+extern void putline __P((char *, MCI *));
+extern void putxline __P((char *, size_t, MCI *, int));
+extern void queueup_macros __P((int, SM_FILE_T *, ENVELOPE *));
+extern void readcf __P((char *, bool, ENVELOPE *));
+extern SIGFUNC_DECL reapchild __P((int));
+extern int releasesignal __P((int));
+extern void resetlimits __P((void));
+extern void restart_daemon __P((void));
+extern void restart_marked_work_groups __P((void));
+extern bool rfc822_string __P((char *));
+extern bool savemail __P((ENVELOPE *, bool));
+extern void seed_random __P((void));
+extern void sendtoargv __P((char **, ENVELOPE *));
+extern void setclientoptions __P((char *));
+extern bool setdaemonoptions __P((char *));
+extern void setdefaults __P((ENVELOPE *));
+extern void setdefuser __P((void));
+extern bool setvendor __P((char *));
+extern void set_op_mode __P((int));
+extern void setoption __P((int, char *, bool, bool, ENVELOPE *));
+extern sigfunc_t setsignal __P((int, sigfunc_t));
+extern void setuserenv __P((const char *, const char *));
+extern void settime __P((ENVELOPE *));
+extern char *sfgets __P((char *, int, SM_FILE_T *, time_t, char *));
+extern char *shortenstring __P((const char *, size_t));
+extern char *shorten_hostname __P((char []));
+extern bool shorten_rfc822_string __P((char *, size_t));
+extern void shutdown_daemon __P((void));
+extern void sm_closefrom __P((int lowest, int highest));
+extern void sm_close_on_exec __P((int lowest, int highest));
+extern struct hostent *sm_gethostbyname __P((char *, int));
+extern struct hostent *sm_gethostbyaddr __P((char *, int, int));
+extern void sm_getla __P((void));
+extern struct passwd *sm_getpwnam __P((char *));
+extern struct passwd *sm_getpwuid __P((UID_T));
+extern void sm_setproctitle __P((bool, ENVELOPE *, const char *, ...));
+extern pid_t sm_wait __P((int *));
+extern bool split_by_recipient __P((ENVELOPE *e));
+extern void stop_sendmail __P((void));
+extern char *str2prt __P((char *));
+extern void stripbackslash __P((char *));
+extern bool strreplnonprt __P((char *, int));
+extern bool strcontainedin __P((bool, char *, char *));
+extern int switch_map_find __P((char *, char *[], short []));
+extern bool transienterror __P((int));
+#if _FFR_BESTMX_BETTER_TRUNCATION || _FFR_DNSMAP_MULTI
+extern void truncate_at_delim __P((char *, size_t, int));
+#endif /* _FFR_BESTMX_BETTER_TRUNCATION || _FFR_DNSMAP_MULTI */
+extern void tTflag __P((char *));
+extern void tTsetup __P((unsigned char *, unsigned int, char *));
+extern SIGFUNC_DECL tick __P((int));
+extern char *ttypath __P((void));
+extern void unlockqueue __P((ENVELOPE *));
+#if !HASUNSETENV
+extern void unsetenv __P((char *));
+#endif /* !HASUNSETENV */
+
+/* update file system information: +/- some blocks */
+#if SM_CONF_SHM
+extern void upd_qs __P((ENVELOPE *, bool, bool, char *));
+# define updfs(e, count, space, where) upd_qs(e, count, space, where)
+#else /* SM_CONF_SHM */
+# define updfs(e, count, space, where)
+# define upd_qs(e, count, space, where)
+#endif /* SM_CONF_SHM */
+
+extern char *username __P((void));
+extern bool usershellok __P((char *, char *));
+extern void vendor_post_defaults __P((ENVELOPE *));
+extern void vendor_pre_defaults __P((ENVELOPE *));
+extern int waitfor __P((pid_t));
+extern bool writable __P((char *, ADDRESS *, long));
+#if SM_HEAP_CHECK
+# define xalloc(size) xalloc_tagged(size, __FILE__, __LINE__)
+extern char *xalloc_tagged __P((int, char*, int));
+#else /* SM_HEAP_CHECK */
+extern char *xalloc __P((int));
+#endif /* SM_HEAP_CHECK */
+extern void xputs __P((SM_FILE_T *, const char *));
+extern char *xtextify __P((char *, char *));
+extern bool xtextok __P((char *));
+extern int xunlink __P((char *));
+extern char *xuntextify __P((char *));
+
+
+#endif /* ! _SENDMAIL_H */
diff --git a/usr/src/cmd/sendmail/src/sfsasl.c b/usr/src/cmd/sendmail/src/sfsasl.c
new file mode 100644
index 0000000000..fb4ad2ade6
--- /dev/null
+++ b/usr/src/cmd/sendmail/src/sfsasl.c
@@ -0,0 +1,799 @@
+/*
+ * Copyright (c) 1999-2004 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: sfsasl.c,v 8.101 2004/12/15 22:45:55 ca Exp $")
+#include <stdlib.h>
+#include <sendmail.h>
+#include <errno.h>
+
+/* allow to disable error handling code just in case... */
+#ifndef DEAL_WITH_ERROR_SSL
+# define DEAL_WITH_ERROR_SSL 1
+#endif /* ! DEAL_WITH_ERROR_SSL */
+
+#if SASL
+# include "sfsasl.h"
+
+/* Structure used by the "sasl" file type */
+struct sasl_obj
+{
+ SM_FILE_T *fp;
+ sasl_conn_t *conn;
+};
+
+struct sasl_info
+{
+ SM_FILE_T *fp;
+ sasl_conn_t *conn;
+};
+
+/*
+** SASL_GETINFO - returns requested information about a "sasl" file
+** descriptor.
+**
+** Parameters:
+** fp -- the file descriptor
+** what -- the type of information requested
+** valp -- the thang to return the information in
+**
+** Returns:
+** -1 for unknown requests
+** >=0 on success with valp filled in (if possible).
+*/
+
+static int sasl_getinfo __P((SM_FILE_T *, int, void *));
+
+static int
+sasl_getinfo(fp, what, valp)
+ SM_FILE_T *fp;
+ int what;
+ void *valp;
+{
+ struct sasl_obj *so = (struct sasl_obj *) fp->f_cookie;
+
+ switch (what)
+ {
+ case SM_IO_WHAT_FD:
+ if (so->fp == NULL)
+ return -1;
+ return so->fp->f_file; /* for stdio fileno() compatability */
+
+ case SM_IO_IS_READABLE:
+ if (so->fp == NULL)
+ return 0;
+
+ /* get info from underlying file */
+ return sm_io_getinfo(so->fp, what, valp);
+
+ default:
+ return -1;
+ }
+}
+
+/*
+** SASL_OPEN -- creates the sasl specific information for opening a
+** file of the sasl type.
+**
+** Parameters:
+** fp -- the file pointer associated with the new open
+** info -- contains the sasl connection information pointer and
+** the original SM_FILE_T that holds the open
+** flags -- ignored
+** rpool -- ignored
+**
+** Returns:
+** 0 on success
+*/
+
+static int sasl_open __P((SM_FILE_T *, const void *, int, const void *));
+
+/* ARGSUSED2 */
+static int
+sasl_open(fp, info, flags, rpool)
+ SM_FILE_T *fp;
+ const void *info;
+ int flags;
+ const void *rpool;
+{
+ struct sasl_obj *so;
+ struct sasl_info *si = (struct sasl_info *) info;
+
+ so = (struct sasl_obj *) sm_malloc(sizeof(struct sasl_obj));
+ if (so == NULL)
+ {
+ errno = ENOMEM;
+ return -1;
+ }
+ so->fp = si->fp;
+ so->conn = si->conn;
+
+ /*
+ ** The underlying 'fp' is set to SM_IO_NOW so that the entire
+ ** encoded string is written in one chunk. Otherwise there is
+ ** the possibility that it may appear illegal, bogus or
+ ** mangled to the other side of the connection.
+ ** We will read or write through 'fp' since it is the opaque
+ ** connection for the communications. We need to treat it this
+ ** way in case the encoded string is to be sent down a TLS
+ ** connection rather than, say, sm_io's stdio.
+ */
+
+ (void) sm_io_setvbuf(so->fp, SM_TIME_DEFAULT, NULL, SM_IO_NOW, 0);
+ fp->f_cookie = so;
+ return 0;
+}
+
+/*
+** SASL_CLOSE -- close the sasl specific parts of the sasl file pointer
+**
+** Parameters:
+** fp -- the file pointer to close
+**
+** Returns:
+** 0 on success
+*/
+
+static int sasl_close __P((SM_FILE_T *));
+
+static int
+sasl_close(fp)
+ SM_FILE_T *fp;
+{
+ struct sasl_obj *so;
+
+ so = (struct sasl_obj *) fp->f_cookie;
+ if (so == NULL)
+ return 0;
+ if (so->fp != NULL)
+ {
+ sm_io_close(so->fp, SM_TIME_DEFAULT);
+ so->fp = NULL;
+ }
+ sm_free(so);
+ so = NULL;
+ return 0;
+}
+
+/* how to deallocate a buffer allocated by SASL */
+extern void sm_sasl_free __P((void *));
+# define SASL_DEALLOC(b) sm_sasl_free(b)
+
+/*
+** SASL_READ -- read encrypted information and decrypt it for the caller
+**
+** Parameters:
+** fp -- the file pointer
+** buf -- the location to place the decrypted information
+** size -- the number of bytes to read after decryption
+**
+** Results:
+** -1 on error
+** otherwise the number of bytes read
+*/
+
+static ssize_t sasl_read __P((SM_FILE_T *, char *, size_t));
+
+static ssize_t
+sasl_read(fp, buf, size)
+ SM_FILE_T *fp;
+ char *buf;
+ size_t size;
+{
+ int result;
+ ssize_t len;
+# if SASL >= 20000
+ static const char *outbuf = NULL;
+# else /* SASL >= 20000 */
+ static char *outbuf = NULL;
+# endif /* SASL >= 20000 */
+ static unsigned int outlen = 0;
+ static unsigned int offset = 0;
+ struct sasl_obj *so = (struct sasl_obj *) fp->f_cookie;
+
+ /*
+ ** sasl_decode() may require more data than a single read() returns.
+ ** Hence we have to put a loop around the decoding.
+ ** This also requires that we may have to split up the returned
+ ** data since it might be larger than the allowed size.
+ ** Therefore we use a static pointer and return portions of it
+ ** if necessary.
+ ** XXX Note: This function is not thread-safe nor can it be used
+ ** on more than one file. A correct implementation would store
+ ** this data in fp->f_cookie.
+ */
+
+# if SASL >= 20000
+ while (outlen == 0)
+# else /* SASL >= 20000 */
+ while (outbuf == NULL && outlen == 0)
+# endif /* SASL >= 20000 */
+ {
+ len = sm_io_read(so->fp, SM_TIME_DEFAULT, buf, size);
+ if (len <= 0)
+ return len;
+ result = sasl_decode(so->conn, buf,
+ (unsigned int) len, &outbuf, &outlen);
+ if (result != SASL_OK)
+ {
+ outbuf = NULL;
+ offset = 0;
+ outlen = 0;
+ return -1;
+ }
+ }
+
+ if (outbuf == NULL)
+ {
+ /* be paranoid: outbuf == NULL but outlen != 0 */
+ syserr("@sasl_read failure: outbuf == NULL but outlen != 0");
+ /* NOTREACHED */
+ }
+ if (outlen - offset > size)
+ {
+ /* return another part of the buffer */
+ (void) memcpy(buf, outbuf + offset, size);
+ offset += size;
+ len = size;
+ }
+ else
+ {
+ /* return the rest of the buffer */
+ len = outlen - offset;
+ (void) memcpy(buf, outbuf + offset, (size_t) len);
+# if SASL < 20000
+ SASL_DEALLOC(outbuf);
+# endif /* SASL < 20000 */
+ outbuf = NULL;
+ offset = 0;
+ outlen = 0;
+ }
+ return len;
+}
+
+/*
+** SASL_WRITE -- write information out after encrypting it
+**
+** Parameters:
+** fp -- the file pointer
+** buf -- holds the data to be encrypted and written
+** size -- the number of bytes to have encrypted and written
+**
+** Returns:
+** -1 on error
+** otherwise number of bytes written
+*/
+
+static ssize_t sasl_write __P((SM_FILE_T *, const char *, size_t));
+
+static ssize_t
+sasl_write(fp, buf, size)
+ SM_FILE_T *fp;
+ const char *buf;
+ size_t size;
+{
+ int result;
+# if SASL >= 20000
+ const char *outbuf;
+# else /* SASL >= 20000 */
+ char *outbuf;
+# endif /* SASL >= 20000 */
+ unsigned int outlen, *maxencode;
+ size_t ret = 0, total = 0;
+ struct sasl_obj *so = (struct sasl_obj *) fp->f_cookie;
+
+ /*
+ ** Fetch the maximum input buffer size for sasl_encode().
+ ** This can be less than the size set in attemptauth()
+ ** due to a negotation with the other side, e.g.,
+ ** Cyrus IMAP lmtp program sets maxbuf=4096,
+ ** digestmd5 substracts 25 and hence we'll get 4071
+ ** instead of 8192 (MAXOUTLEN).
+ ** Hack (for now): simply reduce the size, callers are (must be)
+ ** able to deal with that and invoke sasl_write() again with
+ ** the rest of the data.
+ ** Note: it would be better to store this value in the context
+ ** after the negotiation.
+ */
+
+ result = sasl_getprop(so->conn, SASL_MAXOUTBUF,
+ (const void **) &maxencode);
+ if (result == SASL_OK && size > *maxencode && *maxencode > 0)
+ size = *maxencode;
+
+ result = sasl_encode(so->conn, buf,
+ (unsigned int) size, &outbuf, &outlen);
+
+ if (result != SASL_OK)
+ return -1;
+
+ if (outbuf != NULL)
+ {
+ while (outlen > 0)
+ {
+ /* XXX result == 0? */
+ ret = sm_io_write(so->fp, SM_TIME_DEFAULT,
+ &outbuf[total], outlen);
+ if (ret <= 0)
+ return ret;
+ outlen -= ret;
+ total += ret;
+ }
+# if SASL < 20000
+ SASL_DEALLOC(outbuf);
+# endif /* SASL < 20000 */
+ }
+ return size;
+}
+
+/*
+** SFDCSASL -- create sasl file type and open in and out file pointers
+** for sendmail to read from and write to.
+**
+** Parameters:
+** fin -- the sm_io file encrypted data to be read from
+** fout -- the sm_io file encrypted data to be writen to
+** conn -- the sasl connection pointer
+**
+** Returns:
+** -1 on error
+** 0 on success
+**
+** Side effects:
+** The arguments "fin" and "fout" are replaced with the new
+** SM_FILE_T pointers.
+*/
+
+int
+sfdcsasl(fin, fout, conn)
+ SM_FILE_T **fin;
+ SM_FILE_T **fout;
+ sasl_conn_t *conn;
+{
+ SM_FILE_T *newin, *newout;
+ SM_FILE_T SM_IO_SET_TYPE(sasl_vector, "sasl", sasl_open, sasl_close,
+ sasl_read, sasl_write, NULL, sasl_getinfo, NULL,
+ SM_TIME_FOREVER);
+ struct sasl_info info;
+
+ if (conn == NULL)
+ {
+ /* no need to do anything */
+ return 0;
+ }
+
+ SM_IO_INIT_TYPE(sasl_vector, "sasl", sasl_open, sasl_close,
+ sasl_read, sasl_write, NULL, sasl_getinfo, NULL,
+ SM_TIME_FOREVER);
+ info.fp = *fin;
+ info.conn = conn;
+ newin = sm_io_open(&sasl_vector, SM_TIME_DEFAULT, &info,
+ SM_IO_RDONLY_B, NULL);
+
+ if (newin == NULL)
+ return -1;
+
+ info.fp = *fout;
+ info.conn = conn;
+ newout = sm_io_open(&sasl_vector, SM_TIME_DEFAULT, &info,
+ SM_IO_WRONLY_B, NULL);
+
+ if (newout == NULL)
+ {
+ (void) sm_io_close(newin, SM_TIME_DEFAULT);
+ return -1;
+ }
+ sm_io_automode(newin, newout);
+
+ *fin = newin;
+ *fout = newout;
+ return 0;
+}
+#endif /* SASL */
+
+#if STARTTLS
+# include "sfsasl.h"
+# include <openssl/err.h>
+
+/* Structure used by the "tls" file type */
+struct tls_obj
+{
+ SM_FILE_T *fp;
+ SSL *con;
+};
+
+struct tls_info
+{
+ SM_FILE_T *fp;
+ SSL *con;
+};
+
+/*
+** TLS_GETINFO - returns requested information about a "tls" file
+** descriptor.
+**
+** Parameters:
+** fp -- the file descriptor
+** what -- the type of information requested
+** valp -- the thang to return the information in (unused)
+**
+** Returns:
+** -1 for unknown requests
+** >=0 on success with valp filled in (if possible).
+*/
+
+static int tls_getinfo __P((SM_FILE_T *, int, void *));
+
+/* ARGSUSED2 */
+static int
+tls_getinfo(fp, what, valp)
+ SM_FILE_T *fp;
+ int what;
+ void *valp;
+{
+ struct tls_obj *so = (struct tls_obj *) fp->f_cookie;
+
+ switch (what)
+ {
+ case SM_IO_WHAT_FD:
+ if (so->fp == NULL)
+ return -1;
+ return so->fp->f_file; /* for stdio fileno() compatability */
+
+ case SM_IO_IS_READABLE:
+ return SSL_pending(so->con) > 0;
+
+ default:
+ return -1;
+ }
+}
+
+/*
+** TLS_OPEN -- creates the tls specific information for opening a
+** file of the tls type.
+**
+** Parameters:
+** fp -- the file pointer associated with the new open
+** info -- the sm_io file pointer holding the open and the
+** TLS encryption connection to be read from or written to
+** flags -- ignored
+** rpool -- ignored
+**
+** Returns:
+** 0 on success
+*/
+
+static int tls_open __P((SM_FILE_T *, const void *, int, const void *));
+
+/* ARGSUSED2 */
+static int
+tls_open(fp, info, flags, rpool)
+ SM_FILE_T *fp;
+ const void *info;
+ int flags;
+ const void *rpool;
+{
+ struct tls_obj *so;
+ struct tls_info *ti = (struct tls_info *) info;
+
+ so = (struct tls_obj *) sm_malloc(sizeof(struct tls_obj));
+ if (so == NULL)
+ {
+ errno = ENOMEM;
+ return -1;
+ }
+ so->fp = ti->fp;
+ so->con = ti->con;
+
+ /*
+ ** We try to get the "raw" file descriptor that TLS uses to
+ ** do the actual read/write with. This is to allow us control
+ ** over the file descriptor being a blocking or non-blocking type.
+ ** Under the covers TLS handles the change and this allows us
+ ** to do timeouts with sm_io.
+ */
+
+ fp->f_file = sm_io_getinfo(so->fp, SM_IO_WHAT_FD, NULL);
+ (void) sm_io_setvbuf(so->fp, SM_TIME_DEFAULT, NULL, SM_IO_NOW, 0);
+ fp->f_cookie = so;
+ return 0;
+}
+
+/*
+** TLS_CLOSE -- close the tls specific parts of the tls file pointer
+**
+** Parameters:
+** fp -- the file pointer to close
+**
+** Returns:
+** 0 on success
+*/
+
+static int tls_close __P((SM_FILE_T *));
+
+static int
+tls_close(fp)
+ SM_FILE_T *fp;
+{
+ struct tls_obj *so;
+
+ so = (struct tls_obj *) fp->f_cookie;
+ if (so == NULL)
+ return 0;
+ if (so->fp != NULL)
+ {
+ sm_io_close(so->fp, SM_TIME_DEFAULT);
+ so->fp = NULL;
+ }
+ sm_free(so);
+ so = NULL;
+ return 0;
+}
+
+/* maximum number of retries for TLS related I/O due to handshakes */
+# define MAX_TLS_IOS 4
+
+/*
+** TLS_READ -- read secured information for the caller
+**
+** Parameters:
+** fp -- the file pointer
+** buf -- the location to place the data
+** size -- the number of bytes to read from connection
+**
+** Results:
+** -1 on error
+** otherwise the number of bytes read
+*/
+
+static ssize_t tls_read __P((SM_FILE_T *, char *, size_t));
+
+static ssize_t
+tls_read(fp, buf, size)
+ SM_FILE_T *fp;
+ char *buf;
+ size_t size;
+{
+ int r;
+ static int again = MAX_TLS_IOS;
+ struct tls_obj *so = (struct tls_obj *) fp->f_cookie;
+ char *err;
+
+ r = SSL_read(so->con, (char *) buf, size);
+
+ if (r > 0)
+ {
+ again = MAX_TLS_IOS;
+ return r;
+ }
+
+ err = NULL;
+ switch (SSL_get_error(so->con, r))
+ {
+ case SSL_ERROR_NONE:
+ case SSL_ERROR_ZERO_RETURN:
+ again = MAX_TLS_IOS;
+ break;
+ case SSL_ERROR_WANT_WRITE:
+ if (--again <= 0)
+ err = "read W BLOCK";
+ else
+ errno = EAGAIN;
+ break;
+ case SSL_ERROR_WANT_READ:
+ if (--again <= 0)
+ err = "read R BLOCK";
+ else
+ errno = EAGAIN;
+ break;
+ case SSL_ERROR_WANT_X509_LOOKUP:
+ err = "write X BLOCK";
+ break;
+ case SSL_ERROR_SYSCALL:
+ if (r == 0 && errno == 0) /* out of protocol EOF found */
+ break;
+ err = "syscall error";
+/*
+ get_last_socket_error());
+*/
+ break;
+ case SSL_ERROR_SSL:
+#if DEAL_WITH_ERROR_SSL
+ if (r == 0 && errno == 0) /* out of protocol EOF found */
+ break;
+#endif /* DEAL_WITH_ERROR_SSL */
+ err = "generic SSL error";
+ if (LogLevel > 9)
+ tlslogerr("read");
+
+#if DEAL_WITH_ERROR_SSL
+ /* avoid repeated calls? */
+ if (r == 0)
+ r = -1;
+#endif /* DEAL_WITH_ERROR_SSL */
+ break;
+ }
+ if (err != NULL)
+ {
+ int save_errno;
+
+ save_errno = (errno == 0) ? EIO : errno;
+ again = MAX_TLS_IOS;
+ if (LogLevel > 9)
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS: read error=%s (%d), errno=%d, get_error=%s",
+ err, r, errno,
+ ERR_error_string(ERR_get_error(), NULL));
+ else if (LogLevel > 7)
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS: read error=%s (%d)", err, r);
+ errno = save_errno;
+ }
+ return r;
+}
+
+/*
+** TLS_WRITE -- write information out through secure connection
+**
+** Parameters:
+** fp -- the file pointer
+** buf -- holds the data to be securely written
+** size -- the number of bytes to write
+**
+** Returns:
+** -1 on error
+** otherwise number of bytes written
+*/
+
+static ssize_t tls_write __P((SM_FILE_T *, const char *, size_t));
+
+static ssize_t
+tls_write(fp, buf, size)
+ SM_FILE_T *fp;
+ const char *buf;
+ size_t size;
+{
+ int r;
+ static int again = MAX_TLS_IOS;
+ struct tls_obj *so = (struct tls_obj *) fp->f_cookie;
+ char *err;
+
+ r = SSL_write(so->con, (char *) buf, size);
+
+ if (r > 0)
+ {
+ again = MAX_TLS_IOS;
+ return r;
+ }
+ err = NULL;
+ switch (SSL_get_error(so->con, r))
+ {
+ case SSL_ERROR_NONE:
+ case SSL_ERROR_ZERO_RETURN:
+ again = MAX_TLS_IOS;
+ break;
+ case SSL_ERROR_WANT_WRITE:
+ if (--again <= 0)
+ err = "write W BLOCK";
+ else
+ errno = EAGAIN;
+ break;
+ case SSL_ERROR_WANT_READ:
+ if (--again <= 0)
+ err = "write R BLOCK";
+ else
+ errno = EAGAIN;
+ break;
+ case SSL_ERROR_WANT_X509_LOOKUP:
+ err = "write X BLOCK";
+ break;
+ case SSL_ERROR_SYSCALL:
+ if (r == 0 && errno == 0) /* out of protocol EOF found */
+ break;
+ err = "syscall error";
+/*
+ get_last_socket_error());
+*/
+ break;
+ case SSL_ERROR_SSL:
+ err = "generic SSL error";
+/*
+ ERR_GET_REASON(ERR_peek_error()));
+*/
+ if (LogLevel > 9)
+ tlslogerr("write");
+
+#if DEAL_WITH_ERROR_SSL
+ /* avoid repeated calls? */
+ if (r == 0)
+ r = -1;
+#endif /* DEAL_WITH_ERROR_SSL */
+ break;
+ }
+ if (err != NULL)
+ {
+ int save_errno;
+
+ save_errno = (errno == 0) ? EIO : errno;
+ again = MAX_TLS_IOS;
+ if (LogLevel > 9)
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS: write error=%s (%d), errno=%d, get_error=%s",
+ err, r, errno,
+ ERR_error_string(ERR_get_error(), NULL));
+ else if (LogLevel > 7)
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS: write error=%s (%d)", err, r);
+ errno = save_errno;
+ }
+ return r;
+}
+
+/*
+** SFDCTLS -- create tls file type and open in and out file pointers
+** for sendmail to read from and write to.
+**
+** Parameters:
+** fin -- data input source being replaced
+** fout -- data output source being replaced
+** con -- the tls connection pointer
+**
+** Returns:
+** -1 on error
+** 0 on success
+**
+** Side effects:
+** The arguments "fin" and "fout" are replaced with the new
+** SM_FILE_T pointers.
+** The original "fin" and "fout" are preserved in the tls file
+** type but are not actually used because of the design of TLS.
+*/
+
+int
+sfdctls(fin, fout, con)
+ SM_FILE_T **fin;
+ SM_FILE_T **fout;
+ SSL *con;
+{
+ SM_FILE_T *tlsin, *tlsout;
+ SM_FILE_T SM_IO_SET_TYPE(tls_vector, "tls", tls_open, tls_close,
+ tls_read, tls_write, NULL, tls_getinfo, NULL,
+ SM_TIME_FOREVER);
+ struct tls_info info;
+
+ SM_ASSERT(con != NULL);
+
+ SM_IO_INIT_TYPE(tls_vector, "tls", tls_open, tls_close,
+ tls_read, tls_write, NULL, tls_getinfo, NULL,
+ SM_TIME_FOREVER);
+ info.fp = *fin;
+ info.con = con;
+ tlsin = sm_io_open(&tls_vector, SM_TIME_DEFAULT, &info, SM_IO_RDONLY_B,
+ NULL);
+ if (tlsin == NULL)
+ return -1;
+
+ info.fp = *fout;
+ tlsout = sm_io_open(&tls_vector, SM_TIME_DEFAULT, &info, SM_IO_WRONLY_B,
+ NULL);
+ if (tlsout == NULL)
+ {
+ (void) sm_io_close(tlsin, SM_TIME_DEFAULT);
+ return -1;
+ }
+ sm_io_automode(tlsin, tlsout);
+
+ *fin = tlsin;
+ *fout = tlsout;
+ return 0;
+}
+#endif /* STARTTLS */
diff --git a/usr/src/cmd/sendmail/src/sfsasl.h b/usr/src/cmd/sendmail/src/sfsasl.h
new file mode 100644
index 0000000000..a2472dbc09
--- /dev/null
+++ b/usr/src/cmd/sendmail/src/sfsasl.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 1999, 2000 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ * $Id: sfsasl.h,v 8.17 2000/09/19 21:30:49 ca Exp $"
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#ifndef SFSASL_H
+# define SFSASL_H
+
+#if SASL
+extern int sfdcsasl __P((SM_FILE_T **, SM_FILE_T **, sasl_conn_t *));
+#endif /* SASL */
+
+# if STARTTLS
+extern int sfdctls __P((SM_FILE_T **, SM_FILE_T **, SSL *));
+# endif /* STARTTLS */
+
+#endif /* ! SFSASL_H */
diff --git a/usr/src/cmd/sendmail/src/sm_os.h b/usr/src/cmd/sendmail/src/sm_os.h
new file mode 100644
index 0000000000..4c28daf335
--- /dev/null
+++ b/usr/src/cmd/sendmail/src/sm_os.h
@@ -0,0 +1,8 @@
+/*
+ * Copyright (c) 2001 by Sun Microsystems, Inc.
+ * All rights reserved.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include "../include/sm/os/sm_os_sunos.h"
diff --git a/usr/src/cmd/sendmail/src/sm_resolve.c b/usr/src/cmd/sendmail/src/sm_resolve.c
new file mode 100644
index 0000000000..a7e86f4e0e
--- /dev/null
+++ b/usr/src/cmd/sendmail/src/sm_resolve.c
@@ -0,0 +1,452 @@
+/*
+ * Copyright (c) 2000-2004 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+ * Copyright (c) 1995, 1996, 1997, 1998, 1999 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sendmail.h>
+#if DNSMAP
+# if NAMED_BIND
+# include "sm_resolve.h"
+
+SM_RCSID("$Id: sm_resolve.c,v 8.33 2004/08/04 21:17:57 ca Exp $")
+
+static struct stot
+{
+ const char *st_name;
+ int st_type;
+} stot[] =
+{
+# if NETINET
+ { "A", T_A },
+# endif /* NETINET */
+# if NETINET6
+ { "AAAA", T_AAAA },
+# endif /* NETINET6 */
+ { "NS", T_NS },
+ { "CNAME", T_CNAME },
+ { "PTR", T_PTR },
+ { "MX", T_MX },
+ { "TXT", T_TXT },
+ { "AFSDB", T_AFSDB },
+ { "SRV", T_SRV },
+ { NULL, 0 }
+};
+
+static DNS_REPLY_T *parse_dns_reply __P((unsigned char *, int));
+
+/*
+** DNS_STRING_TO_TYPE -- convert resource record name into type
+**
+** Parameters:
+** name -- name of resource record type
+**
+** Returns:
+** type if succeeded.
+** -1 otherwise.
+*/
+
+int
+dns_string_to_type(name)
+ const char *name;
+{
+ struct stot *p = stot;
+
+ for (p = stot; p->st_name != NULL; p++)
+ if (sm_strcasecmp(name, p->st_name) == 0)
+ return p->st_type;
+ return -1;
+}
+
+/*
+** DNS_TYPE_TO_STRING -- convert resource record type into name
+**
+** Parameters:
+** type -- resource record type
+**
+** Returns:
+** name if succeeded.
+** NULL otherwise.
+*/
+
+const char *
+dns_type_to_string(type)
+ int type;
+{
+ struct stot *p = stot;
+
+ for (p = stot; p->st_name != NULL; p++)
+ if (type == p->st_type)
+ return p->st_name;
+ return NULL;
+}
+
+/*
+** DNS_FREE_DATA -- free all components of a DNS_REPLY_T
+**
+** Parameters:
+** r -- pointer to DNS_REPLY_T
+**
+** Returns:
+** none.
+*/
+
+void
+dns_free_data(r)
+ DNS_REPLY_T *r;
+{
+ RESOURCE_RECORD_T *rr;
+
+ if (r->dns_r_q.dns_q_domain != NULL)
+ sm_free(r->dns_r_q.dns_q_domain);
+ for (rr = r->dns_r_head; rr != NULL; )
+ {
+ RESOURCE_RECORD_T *tmp = rr;
+
+ if (rr->rr_domain != NULL)
+ sm_free(rr->rr_domain);
+ if (rr->rr_u.rr_data != NULL)
+ sm_free(rr->rr_u.rr_data);
+ rr = rr->rr_next;
+ sm_free(tmp);
+ }
+ sm_free(r);
+}
+
+/*
+** PARSE_DNS_REPLY -- parse DNS reply data.
+**
+** Parameters:
+** data -- pointer to dns data
+** len -- len of data
+**
+** Returns:
+** pointer to DNS_REPLY_T if succeeded.
+** NULL otherwise.
+*/
+
+static DNS_REPLY_T *
+parse_dns_reply(data, len)
+ unsigned char *data;
+ int len;
+{
+ unsigned char *p;
+ int status;
+ size_t l;
+ char host[MAXHOSTNAMELEN];
+ DNS_REPLY_T *r;
+ RESOURCE_RECORD_T **rr;
+
+ r = (DNS_REPLY_T *) sm_malloc(sizeof(*r));
+ if (r == NULL)
+ return NULL;
+ memset(r, 0, sizeof(*r));
+
+ p = data;
+
+ /* doesn't work on Crays? */
+ memcpy(&r->dns_r_h, p, sizeof(r->dns_r_h));
+ p += sizeof(r->dns_r_h);
+ status = dn_expand(data, data + len, p, host, sizeof host);
+ if (status < 0)
+ {
+ dns_free_data(r);
+ return NULL;
+ }
+ r->dns_r_q.dns_q_domain = sm_strdup(host);
+ if (r->dns_r_q.dns_q_domain == NULL)
+ {
+ dns_free_data(r);
+ return NULL;
+ }
+ p += status;
+ GETSHORT(r->dns_r_q.dns_q_type, p);
+ GETSHORT(r->dns_r_q.dns_q_class, p);
+ rr = &r->dns_r_head;
+ while (p < data + len)
+ {
+ int type, class, ttl, size, txtlen;
+
+ status = dn_expand(data, data + len, p, host, sizeof host);
+ if (status < 0)
+ {
+ dns_free_data(r);
+ return NULL;
+ }
+ p += status;
+ GETSHORT(type, p);
+ GETSHORT(class, p);
+ GETLONG(ttl, p);
+ GETSHORT(size, p);
+ if (p + size > data + len)
+ {
+ /*
+ ** announced size of data exceeds length of
+ ** data paket: someone is cheating.
+ */
+
+ if (LogLevel > 5)
+ sm_syslog(LOG_WARNING, NOQID,
+ "ERROR: DNS RDLENGTH=%d > data len=%d",
+ size, len - (p - data));
+ dns_free_data(r);
+ return NULL;
+ }
+ *rr = (RESOURCE_RECORD_T *) sm_malloc(sizeof(**rr));
+ if (*rr == NULL)
+ {
+ dns_free_data(r);
+ return NULL;
+ }
+ memset(*rr, 0, sizeof(**rr));
+ (*rr)->rr_domain = sm_strdup(host);
+ if ((*rr)->rr_domain == NULL)
+ {
+ dns_free_data(r);
+ return NULL;
+ }
+ (*rr)->rr_type = type;
+ (*rr)->rr_class = class;
+ (*rr)->rr_ttl = ttl;
+ (*rr)->rr_size = size;
+ switch (type)
+ {
+ case T_NS:
+ case T_CNAME:
+ case T_PTR:
+ status = dn_expand(data, data + len, p, host,
+ sizeof host);
+ if (status < 0)
+ {
+ dns_free_data(r);
+ return NULL;
+ }
+ (*rr)->rr_u.rr_txt = sm_strdup(host);
+ if ((*rr)->rr_u.rr_txt == NULL)
+ {
+ dns_free_data(r);
+ return NULL;
+ }
+ break;
+
+ case T_MX:
+ case T_AFSDB:
+ status = dn_expand(data, data + len, p + 2, host,
+ sizeof host);
+ if (status < 0)
+ {
+ dns_free_data(r);
+ return NULL;
+ }
+ l = strlen(host) + 1;
+ (*rr)->rr_u.rr_mx = (MX_RECORD_T *)
+ sm_malloc(sizeof(*((*rr)->rr_u.rr_mx)) + l);
+ if ((*rr)->rr_u.rr_mx == NULL)
+ {
+ dns_free_data(r);
+ return NULL;
+ }
+ (*rr)->rr_u.rr_mx->mx_r_preference = (p[0] << 8) | p[1];
+ (void) sm_strlcpy((*rr)->rr_u.rr_mx->mx_r_domain,
+ host, l);
+ break;
+
+ case T_SRV:
+ status = dn_expand(data, data + len, p + 6, host,
+ sizeof host);
+ if (status < 0)
+ {
+ dns_free_data(r);
+ return NULL;
+ }
+ l = strlen(host) + 1;
+ (*rr)->rr_u.rr_srv = (SRV_RECORDT_T*)
+ sm_malloc(sizeof(*((*rr)->rr_u.rr_srv)) + l);
+ if ((*rr)->rr_u.rr_srv == NULL)
+ {
+ dns_free_data(r);
+ return NULL;
+ }
+ (*rr)->rr_u.rr_srv->srv_r_priority = (p[0] << 8) | p[1];
+ (*rr)->rr_u.rr_srv->srv_r_weight = (p[2] << 8) | p[3];
+ (*rr)->rr_u.rr_srv->srv_r_port = (p[4] << 8) | p[5];
+ (void) sm_strlcpy((*rr)->rr_u.rr_srv->srv_r_target,
+ host, l);
+ break;
+
+ case T_TXT:
+
+ /*
+ ** The TXT record contains the length as
+ ** leading byte, hence the value is restricted
+ ** to 255, which is less than the maximum value
+ ** of RDLENGTH (size). Nevertheless, txtlen
+ ** must be less than size because the latter
+ ** specifies the length of the entire TXT
+ ** record.
+ */
+
+ txtlen = *p;
+ if (txtlen >= size)
+ {
+ if (LogLevel > 5)
+ sm_syslog(LOG_WARNING, NOQID,
+ "ERROR: DNS TXT record size=%d <= text len=%d",
+ size, txtlen);
+ dns_free_data(r);
+ return NULL;
+ }
+ (*rr)->rr_u.rr_txt = (char *) sm_malloc(txtlen + 1);
+ if ((*rr)->rr_u.rr_txt == NULL)
+ {
+ dns_free_data(r);
+ return NULL;
+ }
+ (void) sm_strlcpy((*rr)->rr_u.rr_txt, (char*) p + 1,
+ txtlen + 1);
+ break;
+
+ default:
+ (*rr)->rr_u.rr_data = (unsigned char*) sm_malloc(size);
+ if ((*rr)->rr_u.rr_data == NULL)
+ {
+ dns_free_data(r);
+ return NULL;
+ }
+ (void) memcpy((*rr)->rr_u.rr_data, p, size);
+ break;
+ }
+ p += size;
+ rr = &(*rr)->rr_next;
+ }
+ *rr = NULL;
+ return r;
+}
+
+/*
+** DNS_LOOKUP_INT -- perform dns map lookup (internal helper routine)
+**
+** Parameters:
+** domain -- name to lookup
+** rr_class -- resource record class
+** rr_type -- resource record type
+** retrans -- retransmission timeout
+** retry -- number of retries
+**
+** Returns:
+** result of lookup if succeeded.
+** NULL otherwise.
+*/
+
+DNS_REPLY_T *
+dns_lookup_int(domain, rr_class, rr_type, retrans, retry)
+ const char *domain;
+ int rr_class;
+ int rr_type;
+ time_t retrans;
+ int retry;
+{
+ int len;
+ unsigned long old_options = 0;
+ time_t save_retrans = 0;
+ int save_retry = 0;
+ DNS_REPLY_T *r = NULL;
+ unsigned char reply[1024];
+
+ if (tTd(8, 16))
+ {
+ old_options = _res.options;
+ _res.options |= RES_DEBUG;
+ sm_dprintf("dns_lookup(%s, %d, %s)\n", domain,
+ rr_class, dns_type_to_string(rr_type));
+ }
+ if (retrans > 0)
+ {
+ save_retrans = _res.retrans;
+ _res.retrans = retrans;
+ }
+ if (retry > 0)
+ {
+ save_retry = _res.retry;
+ _res.retry = retry;
+ }
+ errno = 0;
+ SM_SET_H_ERRNO(0);
+ len = res_search(domain, rr_class, rr_type, reply, sizeof reply);
+ if (tTd(8, 16))
+ {
+ _res.options = old_options;
+ sm_dprintf("dns_lookup(%s, %d, %s) --> %d\n",
+ domain, rr_class, dns_type_to_string(rr_type), len);
+ }
+ if (len >= 0)
+ r = parse_dns_reply(reply, len);
+ if (retrans > 0)
+ _res.retrans = save_retrans;
+ if (retry > 0)
+ _res.retry = save_retry;
+ return r;
+}
+
+# if 0
+DNS_REPLY_T *
+dns_lookup(domain, type_name, retrans, retry)
+ const char *domain;
+ const char *type_name;
+ time_t retrans;
+ int retry;
+{
+ int type;
+
+ type = dns_string_to_type(type_name);
+ if (type == -1)
+ {
+ if (tTd(8, 16))
+ sm_dprintf("dns_lookup: unknown resource type: `%s'\n",
+ type_name);
+ return NULL;
+ }
+ return dns_lookup_int(domain, C_IN, type, retrans, retry);
+}
+# endif /* 0 */
+# endif /* NAMED_BIND */
+#endif /* DNSMAP */
diff --git a/usr/src/cmd/sendmail/src/sm_resolve.h b/usr/src/cmd/sendmail/src/sm_resolve.h
new file mode 100644
index 0000000000..a2f68f3132
--- /dev/null
+++ b/usr/src/cmd/sendmail/src/sm_resolve.h
@@ -0,0 +1,144 @@
+/*
+ * Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+ * Copyright (c) 1995, 1996, 1997, 1998, 1999 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/* $Id: sm_resolve.h,v 8.8 2001/09/01 00:06:02 gshapiro Exp $ */
+
+#if DNSMAP
+# ifndef __ROKEN_RESOLVE_H__
+# define __ROKEN_RESOLVE_H__
+
+/* We use these, but they are not always present in <arpa/nameser.h> */
+
+# ifndef T_TXT
+# define T_TXT 16
+# endif /* ! T_TXT */
+# ifndef T_AFSDB
+# define T_AFSDB 18
+# endif /* ! T_AFSDB */
+# ifndef T_SRV
+# define T_SRV 33
+# endif /* ! T_SRV */
+# ifndef T_NAPTR
+# define T_NAPTR 35
+# endif /* ! T_NAPTR */
+
+typedef struct
+{
+ char *dns_q_domain;
+ unsigned int dns_q_type;
+ unsigned int dns_q_class;
+} DNS_QUERY_T;
+
+typedef struct
+{
+ unsigned int mx_r_preference;
+ char mx_r_domain[1];
+} MX_RECORD_T;
+
+typedef struct
+{
+ unsigned int srv_r_priority;
+ unsigned int srv_r_weight;
+ unsigned int srv_r_port;
+ char srv_r_target[1];
+} SRV_RECORDT_T;
+
+
+typedef struct resource_record RESOURCE_RECORD_T;
+
+struct resource_record
+{
+ char *rr_domain;
+ unsigned int rr_type;
+ unsigned int rr_class;
+ unsigned int rr_ttl;
+ unsigned int rr_size;
+ union
+ {
+ void *rr_data;
+ MX_RECORD_T *rr_mx;
+ MX_RECORD_T *rr_afsdb; /* mx and afsdb are identical */
+ SRV_RECORDT_T *rr_srv;
+# if NETINET
+ struct in_addr *rr_a;
+# endif /* NETINET */
+# if NETINET6
+ struct in6_addr *rr_aaaa;
+# endif /* NETINET6 */
+ char *rr_txt;
+ } rr_u;
+ RESOURCE_RECORD_T *rr_next;
+};
+
+# if !defined(T_A) && !defined(T_AAAA)
+/* XXX if <arpa/nameser.h> isn't included */
+typedef int HEADER; /* will never be used */
+# endif /* !defined(T_A) && !defined(T_AAAA) */
+
+typedef struct
+{
+ HEADER dns_r_h;
+ DNS_QUERY_T dns_r_q;
+ RESOURCE_RECORD_T *dns_r_head;
+} DNS_REPLY_T;
+
+
+extern void dns_free_data __P((DNS_REPLY_T *));
+extern int dns_string_to_type __P((const char *));
+extern const char *dns_type_to_string __P((int));
+extern DNS_REPLY_T *dns_lookup_int __P((const char *,
+ int,
+ int,
+ time_t,
+ int));
+# if 0
+extern DNS_REPLY_T *dns_lookup __P((const char *domain,
+ const char *type_name,
+ time_t retrans,
+ int retry));
+# endif /* 0 */
+
+# endif /* ! __ROKEN_RESOLVE_H__ */
+#endif /* DNSMAP */
diff --git a/usr/src/cmd/sendmail/src/srvrsmtp.c b/usr/src/cmd/sendmail/src/srvrsmtp.c
new file mode 100644
index 0000000000..01eed13e29
--- /dev/null
+++ b/usr/src/cmd/sendmail/src/srvrsmtp.c
@@ -0,0 +1,4688 @@
+/*
+ * Copyright (c) 1998-2005 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sendmail.h>
+#if MILTER
+# include <libmilter/mfapi.h>
+# include <libmilter/mfdef.h>
+#endif /* MILTER */
+
+SM_RCSID("@(#)$Id: srvrsmtp.c,v 8.906 2005/03/16 00:36:09 ca Exp $")
+
+#include <sys/time.h>
+#include <sm/fdset.h>
+
+#if SASL || STARTTLS
+# include "sfsasl.h"
+#endif /* SASL || STARTTLS */
+#if SASL
+# define ENC64LEN(l) (((l) + 2) * 4 / 3 + 1)
+static int saslmechs __P((sasl_conn_t *, char **));
+#endif /* SASL */
+#if STARTTLS
+# include <sysexits.h>
+
+static SSL_CTX *srv_ctx = NULL; /* TLS server context */
+static SSL *srv_ssl = NULL; /* per connection context */
+
+static bool tls_ok_srv = false;
+
+extern void tls_set_verify __P((SSL_CTX *, SSL *, bool));
+# define TLS_VERIFY_CLIENT() tls_set_verify(srv_ctx, srv_ssl, \
+ bitset(SRV_VRFY_CLT, features))
+#endif /* STARTTLS */
+
+/* server features */
+#define SRV_NONE 0x0000 /* none... */
+#define SRV_OFFER_TLS 0x0001 /* offer STARTTLS */
+#define SRV_VRFY_CLT 0x0002 /* request a cert */
+#define SRV_OFFER_AUTH 0x0004 /* offer AUTH */
+#define SRV_OFFER_ETRN 0x0008 /* offer ETRN */
+#define SRV_OFFER_VRFY 0x0010 /* offer VRFY (not yet used) */
+#define SRV_OFFER_EXPN 0x0020 /* offer EXPN */
+#define SRV_OFFER_VERB 0x0040 /* offer VERB */
+#define SRV_OFFER_DSN 0x0080 /* offer DSN */
+#if PIPELINING
+# define SRV_OFFER_PIPE 0x0100 /* offer PIPELINING */
+# if _FFR_NO_PIPE
+# define SRV_NO_PIPE 0x0200 /* disable PIPELINING, sleep if used */
+# endif /* _FFR_NO_PIPE */
+#endif /* PIPELINING */
+#define SRV_REQ_AUTH 0x0400 /* require AUTH */
+#define SRV_REQ_SEC 0x0800 /* require security - equiv to AuthOptions=p */
+#define SRV_TMP_FAIL 0x1000 /* ruleset caused a temporary failure */
+
+static unsigned int srvfeatures __P((ENVELOPE *, char *, unsigned int));
+
+#define STOP_ATTACK ((time_t) -1)
+static time_t checksmtpattack __P((volatile unsigned int *, unsigned int,
+ bool, char *, ENVELOPE *));
+static void mail_esmtp_args __P((char *, char *, ENVELOPE *));
+static void printvrfyaddr __P((ADDRESS *, bool, bool));
+static void rcpt_esmtp_args __P((ADDRESS *, char *, char *, ENVELOPE *));
+static char *skipword __P((char *volatile, char *));
+static void setup_smtpd_io __P((void));
+
+#if SASL
+# if SASL >= 20000
+static int reset_saslconn __P((sasl_conn_t **_conn, char *_hostname,
+ char *_remoteip, char *_localip,
+ char *_auth_id, sasl_ssf_t *_ext_ssf));
+
+# define RESET_SASLCONN \
+ do \
+ { \
+ result = reset_saslconn(&conn, AuthRealm, remoteip, \
+ localip, auth_id, &ext_ssf); \
+ if (result != SASL_OK) \
+ sasl_ok = false; \
+ } while (0)
+
+# else /* SASL >= 20000 */
+static int reset_saslconn __P((sasl_conn_t **_conn, char *_hostname,
+ struct sockaddr_in *_saddr_r,
+ struct sockaddr_in *_saddr_l,
+ sasl_external_properties_t *_ext_ssf));
+# define RESET_SASLCONN \
+ do \
+ { \
+ result = reset_saslconn(&conn, AuthRealm, &saddr_r, \
+ &saddr_l, &ext_ssf); \
+ if (result != SASL_OK) \
+ sasl_ok = false; \
+ } while (0)
+
+# endif /* SASL >= 20000 */
+#endif /* SASL */
+
+extern ENVELOPE BlankEnvelope;
+
+#define NBADRCPTS \
+ do \
+ { \
+ char buf[16]; \
+ (void) sm_snprintf(buf, sizeof buf, "%d", \
+ BadRcptThrottle > 0 && n_badrcpts > BadRcptThrottle \
+ ? n_badrcpts - 1 : n_badrcpts); \
+ macdefine(&e->e_macro, A_TEMP, macid("{nbadrcpts}"), buf); \
+ } while (0)
+
+#define SKIP_SPACE(s) while (isascii(*s) && isspace(*s)) \
+ (s)++
+
+/*
+** SMTP -- run the SMTP protocol.
+**
+** Parameters:
+** nullserver -- if non-NULL, rejection message for
+** (almost) all SMTP commands.
+** d_flags -- daemon flags
+** e -- the envelope.
+**
+** Returns:
+** never.
+**
+** Side Effects:
+** Reads commands from the input channel and processes them.
+*/
+
+/*
+** Notice: The smtp server doesn't have a session context like the client
+** side has (mci). Therefore some data (session oriented) is allocated
+** or assigned to the "wrong" structure (esp. STARTTLS, AUTH).
+** This should be fixed in a successor version.
+*/
+
+struct cmd
+{
+ char *cmd_name; /* command name */
+ int cmd_code; /* internal code, see below */
+};
+
+/* values for cmd_code */
+#define CMDERROR 0 /* bad command */
+#define CMDMAIL 1 /* mail -- designate sender */
+#define CMDRCPT 2 /* rcpt -- designate recipient */
+#define CMDDATA 3 /* data -- send message text */
+#define CMDRSET 4 /* rset -- reset state */
+#define CMDVRFY 5 /* vrfy -- verify address */
+#define CMDEXPN 6 /* expn -- expand address */
+#define CMDNOOP 7 /* noop -- do nothing */
+#define CMDQUIT 8 /* quit -- close connection and die */
+#define CMDHELO 9 /* helo -- be polite */
+#define CMDHELP 10 /* help -- give usage info */
+#define CMDEHLO 11 /* ehlo -- extended helo (RFC 1425) */
+#define CMDETRN 12 /* etrn -- flush queue */
+#if SASL
+# define CMDAUTH 13 /* auth -- SASL authenticate */
+#endif /* SASL */
+#if STARTTLS
+# define CMDSTLS 14 /* STARTTLS -- start TLS session */
+#endif /* STARTTLS */
+/* non-standard commands */
+#define CMDVERB 17 /* verb -- go into verbose mode */
+/* unimplemented commands from RFC 821 */
+#define CMDUNIMPL 19 /* unimplemented rfc821 commands */
+/* use this to catch and log "door handle" attempts on your system */
+#define CMDLOGBOGUS 23 /* bogus command that should be logged */
+/* debugging-only commands, only enabled if SMTPDEBUG is defined */
+#define CMDDBGQSHOW 24 /* showq -- show send queue */
+#define CMDDBGDEBUG 25 /* debug -- set debug mode */
+
+/*
+** Note: If you change this list, remember to update 'helpfile'
+*/
+
+static struct cmd CmdTab[] =
+{
+ { "mail", CMDMAIL },
+ { "rcpt", CMDRCPT },
+ { "data", CMDDATA },
+ { "rset", CMDRSET },
+ { "vrfy", CMDVRFY },
+ { "expn", CMDEXPN },
+ { "help", CMDHELP },
+ { "noop", CMDNOOP },
+ { "quit", CMDQUIT },
+ { "helo", CMDHELO },
+ { "ehlo", CMDEHLO },
+ { "etrn", CMDETRN },
+ { "verb", CMDVERB },
+ { "send", CMDUNIMPL },
+ { "saml", CMDUNIMPL },
+ { "soml", CMDUNIMPL },
+ { "turn", CMDUNIMPL },
+#if SASL
+ { "auth", CMDAUTH, },
+#endif /* SASL */
+#if STARTTLS
+ { "starttls", CMDSTLS, },
+#endif /* STARTTLS */
+ /* remaining commands are here only to trap and log attempts to use them */
+ { "showq", CMDDBGQSHOW },
+ { "debug", CMDDBGDEBUG },
+ { "wiz", CMDLOGBOGUS },
+
+ { NULL, CMDERROR }
+};
+
+static char *CurSmtpClient; /* who's at the other end of channel */
+
+#ifndef MAXBADCOMMANDS
+# define MAXBADCOMMANDS 25 /* maximum number of bad commands */
+#endif /* ! MAXBADCOMMANDS */
+#ifndef MAXNOOPCOMMANDS
+# define MAXNOOPCOMMANDS 20 /* max "noise" commands before slowdown */
+#endif /* ! MAXNOOPCOMMANDS */
+#ifndef MAXHELOCOMMANDS
+# define MAXHELOCOMMANDS 3 /* max HELO/EHLO commands before slowdown */
+#endif /* ! MAXHELOCOMMANDS */
+#ifndef MAXVRFYCOMMANDS
+# define MAXVRFYCOMMANDS 6 /* max VRFY/EXPN commands before slowdown */
+#endif /* ! MAXVRFYCOMMANDS */
+#ifndef MAXETRNCOMMANDS
+# define MAXETRNCOMMANDS 8 /* max ETRN commands before slowdown */
+#endif /* ! MAXETRNCOMMANDS */
+#ifndef MAXTIMEOUT
+# define MAXTIMEOUT (4 * 60) /* max timeout for bad commands */
+#endif /* ! MAXTIMEOUT */
+
+/*
+** Maximum shift value to compute timeout for bad commands.
+** This introduces an upper limit of 2^MAXSHIFT for the timeout.
+*/
+
+#ifndef MAXSHIFT
+# define MAXSHIFT 8
+#endif /* ! MAXSHIFT */
+#if MAXSHIFT > 31
+ ERROR _MAXSHIFT > 31 is invalid
+#endif /* MAXSHIFT */
+
+
+#if MAXBADCOMMANDS > 0
+# define STOP_IF_ATTACK(r) do \
+ { \
+ if ((r) == STOP_ATTACK) \
+ goto stopattack; \
+ } while (0)
+
+#else /* MAXBADCOMMANDS > 0 */
+# define STOP_IF_ATTACK(r) r
+#endif /* MAXBADCOMMANDS > 0 */
+
+
+#if SM_HEAP_CHECK
+static SM_DEBUG_T DebugLeakSmtp = SM_DEBUG_INITIALIZER("leak_smtp",
+ "@(#)$Debug: leak_smtp - trace memory leaks during SMTP processing $");
+#endif /* SM_HEAP_CHECK */
+
+typedef struct
+{
+ bool sm_gotmail; /* mail command received */
+ unsigned int sm_nrcpts; /* number of successful RCPT commands */
+ bool sm_discard;
+#if MILTER
+ bool sm_milterize;
+ bool sm_milterlist; /* any filters in the list? */
+#endif /* MILTER */
+ char *sm_quarmsg; /* carry quarantining across messages */
+} SMTP_T;
+
+static bool smtp_data __P((SMTP_T *, ENVELOPE *));
+
+#define MSG_TEMPFAIL "451 4.3.2 Please try again later"
+
+#if MILTER
+# define MILTER_ABORT(e) milter_abort((e))
+
+# define MILTER_REPLY(str) \
+ { \
+ int savelogusrerrs = LogUsrErrs; \
+ \
+ switch (state) \
+ { \
+ case SMFIR_REPLYCODE: \
+ if (MilterLogLevel > 3) \
+ { \
+ sm_syslog(LOG_INFO, e->e_id, \
+ "Milter: %s=%s, reject=%s", \
+ str, addr, response); \
+ LogUsrErrs = false; \
+ } \
+ if (strncmp(response, "421 ", 4) == 0) \
+ { \
+ bool tsave = QuickAbort; \
+ \
+ QuickAbort = false; \
+ usrerr(response); \
+ QuickAbort = tsave; \
+ e->e_sendqueue = NULL; \
+ goto doquit; \
+ } \
+ else \
+ usrerr(response); \
+ break; \
+ \
+ case SMFIR_REJECT: \
+ if (MilterLogLevel > 3) \
+ { \
+ sm_syslog(LOG_INFO, e->e_id, \
+ "Milter: %s=%s, reject=550 5.7.1 Command rejected", \
+ str, addr); \
+ LogUsrErrs = false; \
+ } \
+ usrerr("550 5.7.1 Command rejected"); \
+ break; \
+ \
+ case SMFIR_DISCARD: \
+ if (MilterLogLevel > 3) \
+ sm_syslog(LOG_INFO, e->e_id, \
+ "Milter: %s=%s, discard", \
+ str, addr); \
+ e->e_flags |= EF_DISCARD; \
+ break; \
+ \
+ case SMFIR_TEMPFAIL: \
+ if (MilterLogLevel > 3) \
+ { \
+ sm_syslog(LOG_INFO, e->e_id, \
+ "Milter: %s=%s, reject=%s", \
+ str, addr, MSG_TEMPFAIL); \
+ LogUsrErrs = false; \
+ } \
+ usrerr(MSG_TEMPFAIL); \
+ break; \
+ } \
+ LogUsrErrs = savelogusrerrs; \
+ if (response != NULL) \
+ sm_free(response); /* XXX */ \
+ }
+
+#else /* MILTER */
+# define MILTER_ABORT(e)
+#endif /* MILTER */
+
+/* clear all SMTP state (for HELO/EHLO/RSET) */
+#define CLEAR_STATE(cmd) \
+do \
+{ \
+ /* abort milter filters */ \
+ MILTER_ABORT(e); \
+ \
+ if (smtp.sm_nrcpts > 0) \
+ { \
+ logundelrcpts(e, cmd, 10, false); \
+ smtp.sm_nrcpts = 0; \
+ macdefine(&e->e_macro, A_PERM, \
+ macid("{nrcpts}"), "0"); \
+ } \
+ \
+ e->e_sendqueue = NULL; \
+ e->e_flags |= EF_CLRQUEUE; \
+ \
+ if (LogLevel > 4 && bitset(EF_LOGSENDER, e->e_flags)) \
+ logsender(e, NULL); \
+ e->e_flags &= ~EF_LOGSENDER; \
+ \
+ /* clean up a bit */ \
+ smtp.sm_gotmail = false; \
+ SuprErrs = true; \
+ dropenvelope(e, true, false); \
+ sm_rpool_free(e->e_rpool); \
+ e = newenvelope(e, CurEnv, sm_rpool_new_x(NULL)); \
+ CurEnv = e; \
+ \
+ /* put back discard bit */ \
+ if (smtp.sm_discard) \
+ e->e_flags |= EF_DISCARD; \
+ \
+ /* restore connection quarantining */ \
+ if (smtp.sm_quarmsg == NULL) \
+ { \
+ e->e_quarmsg = NULL; \
+ macdefine(&e->e_macro, A_PERM, \
+ macid("{quarantine}"), ""); \
+ } \
+ else \
+ { \
+ e->e_quarmsg = sm_rpool_strdup_x(e->e_rpool, \
+ smtp.sm_quarmsg); \
+ macdefine(&e->e_macro, A_PERM, macid("{quarantine}"), \
+ e->e_quarmsg); \
+ } \
+} while (0)
+
+/* sleep to flatten out connection load */
+#define MIN_DELAY_LOG 15 /* wait before logging this again */
+
+/* is it worth setting the process title for 1s? */
+#define DELAY_CONN(cmd) \
+ if (DelayLA > 0 && (CurrentLA = getla()) >= DelayLA) \
+ { \
+ time_t dnow; \
+ \
+ sm_setproctitle(true, e, \
+ "%s: %s: delaying %s: load average: %d", \
+ qid_printname(e), CurSmtpClient, \
+ cmd, DelayLA); \
+ if (LogLevel > 8 && (dnow = curtime()) > log_delay) \
+ { \
+ sm_syslog(LOG_INFO, e->e_id, \
+ "delaying=%s, load average=%d >= %d", \
+ cmd, CurrentLA, DelayLA); \
+ log_delay = dnow + MIN_DELAY_LOG; \
+ } \
+ (void) sleep(1); \
+ sm_setproctitle(true, e, "%s %s: %.80s", \
+ qid_printname(e), CurSmtpClient, inp); \
+ }
+
+
+void
+smtp(nullserver, d_flags, e)
+ char *volatile nullserver;
+ BITMAP256 d_flags;
+ register ENVELOPE *volatile e;
+{
+ register char *volatile p;
+ register struct cmd *volatile c = NULL;
+ char *cmd;
+ auto ADDRESS *vrfyqueue;
+ ADDRESS *a;
+ volatile bool gothello; /* helo command received */
+ bool vrfy; /* set if this is a vrfy command */
+ char *volatile protocol; /* sending protocol */
+ char *volatile sendinghost; /* sending hostname */
+ char *volatile peerhostname; /* name of SMTP peer or "localhost" */
+ auto char *delimptr;
+ char *id;
+ volatile unsigned int n_badcmds = 0; /* count of bad commands */
+ volatile unsigned int n_badrcpts = 0; /* number of rejected RCPT */
+ volatile unsigned int n_verifies = 0; /* count of VRFY/EXPN */
+ volatile unsigned int n_etrn = 0; /* count of ETRN */
+ volatile unsigned int n_noop = 0; /* count of NOOP/VERB/etc */
+ volatile unsigned int n_helo = 0; /* count of HELO/EHLO */
+ volatile int save_sevenbitinput;
+ bool ok;
+#if _FFR_BLOCK_PROXIES
+ volatile bool first;
+#endif /* _FFR_BLOCK_PROXIES */
+ volatile bool tempfail = false;
+ volatile time_t wt; /* timeout after too many commands */
+ volatile time_t previous; /* time after checksmtpattack() */
+ volatile bool lognullconnection = true;
+ register char *q;
+ SMTP_T smtp;
+ char *addr;
+ char *greetcode = "220";
+ char *hostname; /* my hostname ($j) */
+ QUEUE_CHAR *new;
+ int argno;
+ char *args[MAXSMTPARGS];
+ char inp[MAXLINE];
+ char cmdbuf[MAXLINE];
+#if SASL
+ sasl_conn_t *conn;
+ volatile bool sasl_ok;
+ volatile unsigned int n_auth = 0; /* count of AUTH commands */
+ bool ismore;
+ int result;
+ volatile int authenticating;
+ char *user;
+ char *in, *out2;
+# if SASL >= 20000
+ char *auth_id;
+ const char *out;
+ sasl_ssf_t ext_ssf;
+ char localip[60], remoteip[60];
+# else /* SASL >= 20000 */
+ char *out;
+ const char *errstr;
+ sasl_external_properties_t ext_ssf;
+ struct sockaddr_in saddr_l;
+ struct sockaddr_in saddr_r;
+# endif /* SASL >= 20000 */
+ sasl_security_properties_t ssp;
+ sasl_ssf_t *ssf;
+ unsigned int inlen, out2len;
+ unsigned int outlen;
+ char *volatile auth_type;
+ char *mechlist;
+ volatile unsigned int n_mechs;
+ unsigned int len;
+#endif /* SASL */
+ int r;
+#if STARTTLS
+ int fdfl;
+ int rfd, wfd;
+ volatile bool tls_active = false;
+ volatile bool smtps = bitnset(D_SMTPS, d_flags);
+ bool saveQuickAbort;
+ bool saveSuprErrs;
+ time_t tlsstart;
+#endif /* STARTTLS */
+ volatile unsigned int features;
+#if PIPELINING
+# if _FFR_NO_PIPE
+ int np_log = 0;
+# endif /* _FFR_NO_PIPE */
+#endif /* PIPELINING */
+ volatile time_t log_delay = (time_t) 0;
+
+ save_sevenbitinput = SevenBitInput;
+ smtp.sm_nrcpts = 0;
+#if MILTER
+ smtp.sm_milterize = (nullserver == NULL);
+ smtp.sm_milterlist = false;
+#endif /* MILTER */
+
+ /* setup I/O fd correctly for the SMTP server */
+ setup_smtpd_io();
+
+#if SM_HEAP_CHECK
+ if (sm_debug_active(&DebugLeakSmtp, 1))
+ {
+ sm_heap_newgroup();
+ sm_dprintf("smtp() heap group #%d\n", sm_heap_group());
+ }
+#endif /* SM_HEAP_CHECK */
+
+ /* XXX the rpool should be set when e is initialized in main() */
+ e->e_rpool = sm_rpool_new_x(NULL);
+ e->e_macro.mac_rpool = e->e_rpool;
+
+ settime(e);
+ sm_getla();
+ peerhostname = RealHostName;
+ if (peerhostname == NULL)
+ peerhostname = "localhost";
+ CurHostName = peerhostname;
+ CurSmtpClient = macvalue('_', e);
+ if (CurSmtpClient == NULL)
+ CurSmtpClient = CurHostName;
+
+ /* check_relay may have set discard bit, save for later */
+ smtp.sm_discard = bitset(EF_DISCARD, e->e_flags);
+
+#if PIPELINING
+ /* auto-flush output when reading input */
+ (void) sm_io_autoflush(InChannel, OutChannel);
+#endif /* PIPELINING */
+
+ sm_setproctitle(true, e, "server %s startup", CurSmtpClient);
+
+ /* Set default features for server. */
+ features = ((bitset(PRIV_NOETRN, PrivacyFlags) ||
+ bitnset(D_NOETRN, d_flags)) ? SRV_NONE : SRV_OFFER_ETRN)
+ | (bitnset(D_AUTHREQ, d_flags) ? SRV_REQ_AUTH : SRV_NONE)
+ | (bitset(PRIV_NOEXPN, PrivacyFlags) ? SRV_NONE
+ : (SRV_OFFER_EXPN
+ | (bitset(PRIV_NOVERB, PrivacyFlags)
+ ? SRV_NONE : SRV_OFFER_VERB)))
+ | (bitset(PRIV_NORECEIPTS, PrivacyFlags) ? SRV_NONE
+ : SRV_OFFER_DSN)
+#if SASL
+ | (bitnset(D_NOAUTH, d_flags) ? SRV_NONE : SRV_OFFER_AUTH)
+ | (bitset(SASL_SEC_NOPLAINTEXT, SASLOpts) ? SRV_REQ_SEC
+ : SRV_NONE)
+#endif /* SASL */
+#if PIPELINING
+ | SRV_OFFER_PIPE
+#endif /* PIPELINING */
+#if STARTTLS
+ | (bitnset(D_NOTLS, d_flags) ? SRV_NONE : SRV_OFFER_TLS)
+ | (bitset(TLS_I_NO_VRFY, TLS_Srv_Opts) ? SRV_NONE
+ : SRV_VRFY_CLT)
+#endif /* STARTTLS */
+ ;
+ if (nullserver == NULL)
+ {
+ features = srvfeatures(e, CurSmtpClient, features);
+ if (bitset(SRV_TMP_FAIL, features))
+ {
+ if (LogLevel > 4)
+ sm_syslog(LOG_ERR, NOQID,
+ "ERROR: srv_features=tempfail, relay=%.100s, access temporarily disabled",
+ CurSmtpClient);
+ nullserver = "450 4.3.0 Please try again later.";
+ }
+ else
+ {
+#if PIPELINING
+# if _FFR_NO_PIPE
+ if (bitset(SRV_NO_PIPE, features))
+ {
+ /* for consistency */
+ features &= ~SRV_OFFER_PIPE;
+ }
+# endif /* _FFR_NO_PIPE */
+#endif /* PIPELINING */
+#if SASL
+ if (bitset(SRV_REQ_SEC, features))
+ SASLOpts |= SASL_SEC_NOPLAINTEXT;
+ else
+ SASLOpts &= ~SASL_SEC_NOPLAINTEXT;
+#endif /* SASL */
+ }
+ }
+ else if (strncmp(nullserver, "421 ", 4) == 0)
+ {
+ message(nullserver);
+ goto doquit;
+ }
+
+ hostname = macvalue('j', e);
+#if SASL
+ if (AuthRealm == NULL)
+ AuthRealm = hostname;
+ sasl_ok = bitset(SRV_OFFER_AUTH, features);
+ n_mechs = 0;
+ authenticating = SASL_NOT_AUTH;
+
+ /* SASL server new connection */
+ if (sasl_ok)
+ {
+# if SASL >= 20000
+ result = sasl_server_new("smtp", AuthRealm, NULL, NULL, NULL,
+ NULL, 0, &conn);
+# elif SASL > 10505
+ /* use empty realm: only works in SASL > 1.5.5 */
+ result = sasl_server_new("smtp", AuthRealm, "", NULL, 0, &conn);
+# else /* SASL >= 20000 */
+ /* use no realm -> realm is set to hostname by SASL lib */
+ result = sasl_server_new("smtp", AuthRealm, NULL, NULL, 0,
+ &conn);
+# endif /* SASL >= 20000 */
+ sasl_ok = result == SASL_OK;
+ if (!sasl_ok)
+ {
+ if (LogLevel > 9)
+ sm_syslog(LOG_WARNING, NOQID,
+ "AUTH error: sasl_server_new failed=%d",
+ result);
+ }
+ }
+ if (sasl_ok)
+ {
+ /*
+ ** SASL set properties for sasl
+ ** set local/remote IP
+ ** XXX Cyrus SASL v1 only supports IPv4
+ **
+ ** XXX where exactly are these used/required?
+ ** Kerberos_v4
+ */
+
+# if SASL >= 20000
+ localip[0] = remoteip[0] = '\0';
+# if NETINET || NETINET6
+ in = macvalue(macid("{daemon_family}"), e);
+ if (in != NULL && (
+# if NETINET6
+ strcmp(in, "inet6") == 0 ||
+# endif /* NETINET6 */
+ strcmp(in, "inet") == 0))
+ {
+ SOCKADDR_LEN_T addrsize;
+ SOCKADDR saddr_l;
+ SOCKADDR saddr_r;
+
+ addrsize = sizeof(saddr_r);
+ if (getpeername(sm_io_getinfo(InChannel, SM_IO_WHAT_FD,
+ NULL),
+ (struct sockaddr *) &saddr_r,
+ &addrsize) == 0)
+ {
+ if (iptostring(&saddr_r, addrsize,
+ remoteip, sizeof remoteip))
+ {
+ sasl_setprop(conn, SASL_IPREMOTEPORT,
+ remoteip);
+ }
+ addrsize = sizeof(saddr_l);
+ if (getsockname(sm_io_getinfo(InChannel,
+ SM_IO_WHAT_FD,
+ NULL),
+ (struct sockaddr *) &saddr_l,
+ &addrsize) == 0)
+ {
+ if (iptostring(&saddr_l, addrsize,
+ localip,
+ sizeof localip))
+ {
+ sasl_setprop(conn,
+ SASL_IPLOCALPORT,
+ localip);
+ }
+ }
+ }
+ }
+# endif /* NETINET || NETINET6 */
+# else /* SASL >= 20000 */
+# if NETINET
+ in = macvalue(macid("{daemon_family}"), e);
+ if (in != NULL && strcmp(in, "inet") == 0)
+ {
+ SOCKADDR_LEN_T addrsize;
+
+ addrsize = sizeof(struct sockaddr_in);
+ if (getpeername(sm_io_getinfo(InChannel, SM_IO_WHAT_FD,
+ NULL),
+ (struct sockaddr *)&saddr_r,
+ &addrsize) == 0)
+ {
+ sasl_setprop(conn, SASL_IP_REMOTE, &saddr_r);
+ addrsize = sizeof(struct sockaddr_in);
+ if (getsockname(sm_io_getinfo(InChannel,
+ SM_IO_WHAT_FD,
+ NULL),
+ (struct sockaddr *)&saddr_l,
+ &addrsize) == 0)
+ sasl_setprop(conn, SASL_IP_LOCAL,
+ &saddr_l);
+ }
+ }
+# endif /* NETINET */
+# endif /* SASL >= 20000 */
+
+ auth_type = NULL;
+ mechlist = NULL;
+ user = NULL;
+# if 0
+ macdefine(&BlankEnvelope.e_macro, A_PERM,
+ macid("{auth_author}"), NULL);
+# endif /* 0 */
+
+ /* set properties */
+ (void) memset(&ssp, '\0', sizeof ssp);
+
+ /* XXX should these be options settable via .cf ? */
+ /* ssp.min_ssf = 0; is default due to memset() */
+ {
+ ssp.max_ssf = MaxSLBits;
+ ssp.maxbufsize = MAXOUTLEN;
+ }
+ ssp.security_flags = SASLOpts & SASL_SEC_MASK;
+ sasl_ok = sasl_setprop(conn, SASL_SEC_PROPS, &ssp) == SASL_OK;
+
+ if (sasl_ok)
+ {
+ /*
+ ** external security strength factor;
+ ** currently we have none so zero
+ */
+
+# if SASL >= 20000
+ ext_ssf = 0;
+ auth_id = NULL;
+ sasl_ok = ((sasl_setprop(conn, SASL_SSF_EXTERNAL,
+ &ext_ssf) == SASL_OK) &&
+ (sasl_setprop(conn, SASL_AUTH_EXTERNAL,
+ auth_id) == SASL_OK));
+# else /* SASL >= 20000 */
+ ext_ssf.ssf = 0;
+ ext_ssf.auth_id = NULL;
+ sasl_ok = sasl_setprop(conn, SASL_SSF_EXTERNAL,
+ &ext_ssf) == SASL_OK;
+# endif /* SASL >= 20000 */
+ }
+ if (sasl_ok)
+ n_mechs = saslmechs(conn, &mechlist);
+ }
+#endif /* SASL */
+
+#if STARTTLS
+#endif /* STARTTLS */
+
+#if MILTER
+ if (smtp.sm_milterize)
+ {
+ char state;
+
+ /* initialize mail filter connection */
+ smtp.sm_milterlist = milter_init(e, &state);
+ switch (state)
+ {
+ case SMFIR_REJECT:
+ if (MilterLogLevel > 3)
+ sm_syslog(LOG_INFO, e->e_id,
+ "Milter: initialization failed, rejecting commands");
+ greetcode = "554";
+ nullserver = "Command rejected";
+ smtp.sm_milterize = false;
+ break;
+
+ case SMFIR_TEMPFAIL:
+ if (MilterLogLevel > 3)
+ sm_syslog(LOG_INFO, e->e_id,
+ "Milter: initialization failed, temp failing commands");
+ tempfail = true;
+ smtp.sm_milterize = false;
+ break;
+ }
+ }
+
+ if (smtp.sm_milterlist && smtp.sm_milterize &&
+ !bitset(EF_DISCARD, e->e_flags))
+ {
+ char state;
+ char *response;
+
+ response = milter_connect(peerhostname, RealHostAddr,
+ e, &state);
+ switch (state)
+ {
+ case SMFIR_REPLYCODE: /* REPLYCODE shouldn't happen */
+ case SMFIR_REJECT:
+ if (MilterLogLevel > 3)
+ sm_syslog(LOG_INFO, e->e_id,
+ "Milter: connect: host=%s, addr=%s, rejecting commands",
+ peerhostname,
+ anynet_ntoa(&RealHostAddr));
+ greetcode = "554";
+ nullserver = "Command rejected";
+ smtp.sm_milterize = false;
+ break;
+
+ case SMFIR_TEMPFAIL:
+ if (MilterLogLevel > 3)
+ sm_syslog(LOG_INFO, e->e_id,
+ "Milter: connect: host=%s, addr=%s, temp failing commands",
+ peerhostname,
+ anynet_ntoa(&RealHostAddr));
+ tempfail = true;
+ smtp.sm_milterize = false;
+ break;
+
+ case SMFIR_SHUTDOWN:
+ if (MilterLogLevel > 3)
+ sm_syslog(LOG_INFO, e->e_id,
+ "Milter: connect: host=%s, addr=%s, shutdown",
+ peerhostname,
+ anynet_ntoa(&RealHostAddr));
+ tempfail = true;
+ smtp.sm_milterize = false;
+ message("421 4.7.0 %s closing connection",
+ MyHostName);
+
+ /* arrange to ignore send list */
+ e->e_sendqueue = NULL;
+ goto doquit;
+ }
+ if (response != NULL)
+ sm_free(response); /* XXX */
+ }
+#endif /* MILTER */
+
+ /*
+ ** Broken proxies and SMTP slammers
+ ** push data without waiting, catch them
+ */
+
+ if (
+#if STARTTLS
+ !smtps &&
+#endif /* STARTTLS */
+ *greetcode == '2')
+ {
+ time_t msecs = 0;
+ char **pvp;
+ char pvpbuf[PSBUFSIZE];
+
+ /* Ask the rulesets how long to pause */
+ pvp = NULL;
+ r = rscap("greet_pause", peerhostname,
+ anynet_ntoa(&RealHostAddr), e,
+ &pvp, pvpbuf, sizeof(pvpbuf));
+ if (r == EX_OK && pvp != NULL && pvp[0] != NULL &&
+ (pvp[0][0] & 0377) == CANONNET && pvp[1] != NULL)
+ {
+ msecs = strtol(pvp[1], NULL, 10);
+ }
+
+ if (msecs > 0)
+ {
+ int fd;
+ fd_set readfds;
+ struct timeval timeout;
+
+ /* pause for a moment */
+ timeout.tv_sec = msecs / 1000;
+ timeout.tv_usec = (msecs % 1000) * 1000;
+
+ /* Obey RFC 2821: 4.3.5.2: 220 timeout of 5 minutes */
+ if (timeout.tv_sec >= 300)
+ {
+ timeout.tv_sec = 300;
+ timeout.tv_usec = 0;
+ }
+
+ /* check if data is on the socket during the pause */
+ fd = sm_io_getinfo(InChannel, SM_IO_WHAT_FD, NULL);
+ FD_ZERO(&readfds);
+ SM_FD_SET(fd, &readfds);
+ if (select(fd + 1, FDSET_CAST &readfds,
+ NULL, NULL, &timeout) > 0 &&
+ FD_ISSET(fd, &readfds))
+ {
+ greetcode = "554";
+ nullserver = "Command rejected";
+ sm_syslog(LOG_INFO, e->e_id,
+ "rejecting commands from %s [%s] due to pre-greeting traffic",
+ peerhostname,
+ anynet_ntoa(&RealHostAddr));
+ }
+ }
+ }
+
+#if STARTTLS
+ /* If this an smtps connection, start TLS now */
+ if (smtps)
+ {
+ Errors = 0;
+ goto starttls;
+ }
+
+ greeting:
+
+#endif /* STARTTLS */
+
+ /* output the first line, inserting "ESMTP" as second word */
+ if (*greetcode == '5')
+ (void) sm_snprintf(inp, sizeof inp, "%s not accepting messages",
+ hostname);
+ else
+ expand(SmtpGreeting, inp, sizeof inp, e);
+
+ p = strchr(inp, '\n');
+ if (p != NULL)
+ *p++ = '\0';
+ id = strchr(inp, ' ');
+ if (id == NULL)
+ id = &inp[strlen(inp)];
+ if (p == NULL)
+ (void) sm_snprintf(cmdbuf, sizeof cmdbuf,
+ "%s %%.*s ESMTP%%s", greetcode);
+ else
+ (void) sm_snprintf(cmdbuf, sizeof cmdbuf,
+ "%s-%%.*s ESMTP%%s", greetcode);
+ message(cmdbuf, (int) (id - inp), inp, id);
+
+ /* output remaining lines */
+ while ((id = p) != NULL && (p = strchr(id, '\n')) != NULL)
+ {
+ *p++ = '\0';
+ if (isascii(*id) && isspace(*id))
+ id++;
+ (void) sm_strlcpyn(cmdbuf, sizeof cmdbuf, 2, greetcode, "-%s");
+ message(cmdbuf, id);
+ }
+ if (id != NULL)
+ {
+ if (isascii(*id) && isspace(*id))
+ id++;
+ (void) sm_strlcpyn(cmdbuf, sizeof cmdbuf, 2, greetcode, " %s");
+ message(cmdbuf, id);
+ }
+
+ protocol = NULL;
+ sendinghost = macvalue('s', e);
+
+ /* If quarantining by a connect/ehlo action, save between messages */
+ if (e->e_quarmsg == NULL)
+ smtp.sm_quarmsg = NULL;
+ else
+ smtp.sm_quarmsg = newstr(e->e_quarmsg);
+
+ /* sendinghost's storage must outlive the current envelope */
+ if (sendinghost != NULL)
+ sendinghost = sm_strdup_x(sendinghost);
+#if _FFR_BLOCK_PROXIES
+ first = true;
+#endif /* _FFR_BLOCK_PROXIES */
+ gothello = false;
+ smtp.sm_gotmail = false;
+ for (;;)
+ {
+ SM_TRY
+ {
+ QuickAbort = false;
+ HoldErrs = false;
+ SuprErrs = false;
+ LogUsrErrs = false;
+ OnlyOneError = true;
+ e->e_flags &= ~(EF_VRFYONLY|EF_GLOBALERRS);
+
+ /* setup for the read */
+ e->e_to = NULL;
+ Errors = 0;
+ FileName = NULL;
+ (void) sm_io_flush(smioout, SM_TIME_DEFAULT);
+
+ /* read the input line */
+ SmtpPhase = "server cmd read";
+ sm_setproctitle(true, e, "server %s cmd read", CurSmtpClient);
+#if SASL
+ /*
+ ** XXX SMTP AUTH requires accepting any length,
+ ** at least for challenge/response
+ */
+#endif /* SASL */
+
+ /* handle errors */
+ if (sm_io_error(OutChannel) ||
+ (p = sfgets(inp, sizeof inp, InChannel,
+ TimeOuts.to_nextcommand, SmtpPhase)) == NULL)
+ {
+ char *d;
+
+ d = macvalue(macid("{daemon_name}"), e);
+ if (d == NULL)
+ d = "stdin";
+ /* end of file, just die */
+ disconnect(1, e);
+
+#if MILTER
+ /* close out milter filters */
+ milter_quit(e);
+#endif /* MILTER */
+
+ message("421 4.4.1 %s Lost input channel from %s",
+ MyHostName, CurSmtpClient);
+ if (LogLevel > (smtp.sm_gotmail ? 1 : 19))
+ sm_syslog(LOG_NOTICE, e->e_id,
+ "lost input channel from %s to %s after %s",
+ CurSmtpClient, d,
+ (c == NULL || c->cmd_name == NULL) ? "startup" : c->cmd_name);
+ /*
+ ** If have not accepted mail (DATA), do not bounce
+ ** bad addresses back to sender.
+ */
+
+ if (bitset(EF_CLRQUEUE, e->e_flags))
+ e->e_sendqueue = NULL;
+ goto doquit;
+ }
+
+#if _FFR_BLOCK_PROXIES
+ if (first)
+ {
+ size_t inplen, cmdlen;
+ int idx;
+ char *http_cmd;
+ static char *http_cmds[] = { "GET", "POST",
+ "CONNECT", "USER", NULL };
+
+ inplen = strlen(inp);
+ for (idx = 0; (http_cmd = http_cmds[idx]) != NULL;
+ idx++)
+ {
+ cmdlen = strlen(http_cmd);
+ if (cmdlen < inplen &&
+ sm_strncasecmp(inp, http_cmd, cmdlen) == 0 &&
+ isascii(inp[cmdlen]) && isspace(inp[cmdlen]))
+ {
+ /* Open proxy, drop it */
+ message("421 4.7.0 %s Rejecting open proxy %s",
+ MyHostName, CurSmtpClient);
+ sm_syslog(LOG_INFO, e->e_id,
+ "%s: probable open proxy: command=%.40s",
+ CurSmtpClient, inp);
+ goto doquit;
+ }
+ }
+ first = false;
+ }
+#endif /* _FFR_BLOCK_PROXIES */
+
+ /* clean up end of line */
+ fixcrlf(inp, true);
+
+#if PIPELINING
+# if _FFR_NO_PIPE
+ /*
+ ** if there is more input and pipelining is disabled:
+ ** delay ... (and maybe discard the input?)
+ ** XXX this doesn't really work, at least in tests using
+ ** telnet SM_IO_IS_READABLE only returns 1 if there were
+ ** more than 2 input lines available.
+ */
+
+ if (bitset(SRV_NO_PIPE, features) &&
+ sm_io_getinfo(InChannel, SM_IO_IS_READABLE, NULL) > 0)
+ {
+ if (++np_log < 3)
+ sm_syslog(LOG_INFO, NOQID,
+ "unauthorized PIPELINING, sleeping");
+ sleep(1);
+ }
+
+# endif /* _FFR_NO_PIPE */
+#endif /* PIPELINING */
+
+#if SASL
+ if (authenticating == SASL_PROC_AUTH)
+ {
+# if 0
+ if (*inp == '\0')
+ {
+ authenticating = SASL_NOT_AUTH;
+ message("501 5.5.2 missing input");
+ RESET_SASLCONN;
+ continue;
+ }
+# endif /* 0 */
+ if (*inp == '*' && *(inp + 1) == '\0')
+ {
+ authenticating = SASL_NOT_AUTH;
+
+ /* rfc 2254 4. */
+ message("501 5.0.0 AUTH aborted");
+ RESET_SASLCONN;
+ continue;
+ }
+
+ /* could this be shorter? XXX */
+# if SASL >= 20000
+ in = xalloc(strlen(inp) + 1);
+ result = sasl_decode64(inp, strlen(inp), in,
+ strlen(inp), &inlen);
+# else /* SASL >= 20000 */
+ out = xalloc(strlen(inp));
+ result = sasl_decode64(inp, strlen(inp), out, &outlen);
+# endif /* SASL >= 20000 */
+ if (result != SASL_OK)
+ {
+ authenticating = SASL_NOT_AUTH;
+
+ /* rfc 2254 4. */
+ message("501 5.5.4 cannot decode AUTH parameter %s",
+ inp);
+# if SASL >= 20000
+ sm_free(in);
+# endif /* SASL >= 20000 */
+ RESET_SASLCONN;
+ continue;
+ }
+
+# if SASL >= 20000
+ result = sasl_server_step(conn, in, inlen,
+ &out, &outlen);
+ sm_free(in);
+# else /* SASL >= 20000 */
+ result = sasl_server_step(conn, out, outlen,
+ &out, &outlen, &errstr);
+# endif /* SASL >= 20000 */
+
+ /* get an OK if we're done */
+ if (result == SASL_OK)
+ {
+ authenticated:
+ message("235 2.0.0 OK Authenticated");
+ authenticating = SASL_IS_AUTH;
+ macdefine(&BlankEnvelope.e_macro, A_TEMP,
+ macid("{auth_type}"), auth_type);
+
+# if SASL >= 20000
+ user = macvalue(macid("{auth_authen}"), e);
+
+ /* get security strength (features) */
+ result = sasl_getprop(conn, SASL_SSF,
+ (const void **) &ssf);
+# else /* SASL >= 20000 */
+ result = sasl_getprop(conn, SASL_USERNAME,
+ (void **)&user);
+ if (result != SASL_OK)
+ {
+ user = "";
+ macdefine(&BlankEnvelope.e_macro,
+ A_PERM,
+ macid("{auth_authen}"), NULL);
+ }
+ else
+ {
+ macdefine(&BlankEnvelope.e_macro,
+ A_TEMP,
+ macid("{auth_authen}"),
+ xtextify(user, "<>\")"));
+ }
+
+# if 0
+ /* get realm? */
+ sasl_getprop(conn, SASL_REALM, (void **) &data);
+# endif /* 0 */
+
+ /* get security strength (features) */
+ result = sasl_getprop(conn, SASL_SSF,
+ (void **) &ssf);
+# endif /* SASL >= 20000 */
+ if (result != SASL_OK)
+ {
+ macdefine(&BlankEnvelope.e_macro,
+ A_PERM,
+ macid("{auth_ssf}"), "0");
+ ssf = NULL;
+ }
+ else
+ {
+ char pbuf[8];
+
+ (void) sm_snprintf(pbuf, sizeof pbuf,
+ "%u", *ssf);
+ macdefine(&BlankEnvelope.e_macro,
+ A_TEMP,
+ macid("{auth_ssf}"), pbuf);
+ if (tTd(95, 8))
+ sm_dprintf("AUTH auth_ssf: %u\n",
+ *ssf);
+ }
+
+ /*
+ ** Only switch to encrypted connection
+ ** if a security layer has been negotiated
+ */
+
+ if (ssf != NULL && *ssf > 0)
+ {
+ /*
+ ** Convert I/O layer to use SASL.
+ ** If the call fails, the connection
+ ** is aborted.
+ */
+
+ if (sfdcsasl(&InChannel, &OutChannel,
+ conn) == 0)
+ {
+ /* restart dialogue */
+ n_helo = 0;
+# if PIPELINING
+ (void) sm_io_autoflush(InChannel,
+ OutChannel);
+# endif /* PIPELINING */
+ }
+ else
+ syserr("503 5.3.3 SASL TLS failed");
+ }
+
+ /* NULL pointer ok since it's our function */
+ if (LogLevel > 8)
+ sm_syslog(LOG_INFO, NOQID,
+ "AUTH=server, relay=%s, authid=%.128s, mech=%.16s, bits=%d",
+ CurSmtpClient,
+ shortenstring(user, 128),
+ auth_type, *ssf);
+ }
+ else if (result == SASL_CONTINUE)
+ {
+ len = ENC64LEN(outlen);
+ out2 = xalloc(len);
+ result = sasl_encode64(out, outlen, out2, len,
+ &out2len);
+ if (result != SASL_OK)
+ {
+ /* correct code? XXX */
+ /* 454 Temp. authentication failure */
+ message("454 4.5.4 Internal error: unable to encode64");
+ if (LogLevel > 5)
+ sm_syslog(LOG_WARNING, e->e_id,
+ "AUTH encode64 error [%d for \"%s\"]",
+ result, out);
+ /* start over? */
+ authenticating = SASL_NOT_AUTH;
+ }
+ else
+ {
+ message("334 %s", out2);
+ if (tTd(95, 2))
+ sm_dprintf("AUTH continue: msg='%s' len=%u\n",
+ out2, out2len);
+ }
+# if SASL >= 20000
+ sm_free(out2);
+# endif /* SASL >= 20000 */
+ }
+ else
+ {
+ /* not SASL_OK or SASL_CONT */
+ message("535 5.7.0 authentication failed");
+ if (LogLevel > 9)
+ sm_syslog(LOG_WARNING, e->e_id,
+ "AUTH failure (%s): %s (%d) %s",
+ auth_type,
+ sasl_errstring(result, NULL,
+ NULL),
+ result,
+# if SASL >= 20000
+ sasl_errdetail(conn));
+# else /* SASL >= 20000 */
+ errstr == NULL ? "" : errstr);
+# endif /* SASL >= 20000 */
+ RESET_SASLCONN;
+ authenticating = SASL_NOT_AUTH;
+ }
+ }
+ else
+ {
+ /* don't want to do any of this if authenticating */
+#endif /* SASL */
+
+ /* echo command to transcript */
+ if (e->e_xfp != NULL)
+ (void) sm_io_fprintf(e->e_xfp, SM_TIME_DEFAULT,
+ "<<< %s\n", inp);
+
+ if (LogLevel > 14)
+ sm_syslog(LOG_INFO, e->e_id, "<-- %s", inp);
+
+ /* break off command */
+ for (p = inp; isascii(*p) && isspace(*p); p++)
+ continue;
+ cmd = cmdbuf;
+ while (*p != '\0' &&
+ !(isascii(*p) && isspace(*p)) &&
+ cmd < &cmdbuf[sizeof cmdbuf - 2])
+ *cmd++ = *p++;
+ *cmd = '\0';
+
+ /* throw away leading whitespace */
+ SKIP_SPACE(p);
+
+ /* decode command */
+ for (c = CmdTab; c->cmd_name != NULL; c++)
+ {
+ if (sm_strcasecmp(c->cmd_name, cmdbuf) == 0)
+ break;
+ }
+
+ /* reset errors */
+ errno = 0;
+
+ /* check whether a "non-null" command has been used */
+ switch (c->cmd_code)
+ {
+#if SASL
+ case CMDAUTH:
+ /* avoid information leak; take first two words? */
+ q = "AUTH";
+ break;
+#endif /* SASL */
+
+ case CMDMAIL:
+ case CMDEXPN:
+ case CMDVRFY:
+ case CMDETRN:
+ lognullconnection = false;
+ /* FALLTHROUGH */
+ default:
+ q = inp;
+ break;
+ }
+
+ if (e->e_id == NULL)
+ sm_setproctitle(true, e, "%s: %.80s",
+ CurSmtpClient, q);
+ else
+ sm_setproctitle(true, e, "%s %s: %.80s",
+ qid_printname(e),
+ CurSmtpClient, q);
+
+ /*
+ ** Process command.
+ **
+ ** If we are running as a null server, return 550
+ ** to almost everything.
+ */
+
+ if (nullserver != NULL || bitnset(D_ETRNONLY, d_flags))
+ {
+ switch (c->cmd_code)
+ {
+ case CMDQUIT:
+ case CMDHELO:
+ case CMDEHLO:
+ case CMDNOOP:
+ case CMDRSET:
+ case CMDERROR:
+ /* process normally */
+ break;
+
+ case CMDETRN:
+ if (bitnset(D_ETRNONLY, d_flags) &&
+ nullserver == NULL)
+ break;
+ DELAY_CONN("ETRN");
+ /* FALLTHROUGH */
+
+ default:
+#if MAXBADCOMMANDS > 0
+ /* theoretically this could overflow */
+ if (nullserver != NULL &&
+ ++n_badcmds > MAXBADCOMMANDS)
+ {
+ message("421 4.7.0 %s Too many bad commands; closing connection",
+ MyHostName);
+
+ /* arrange to ignore send list */
+ e->e_sendqueue = NULL;
+ goto doquit;
+ }
+#endif /* MAXBADCOMMANDS > 0 */
+ if (nullserver != NULL)
+ {
+ if (ISSMTPREPLY(nullserver))
+ usrerr(nullserver);
+ else
+ usrerr("550 5.0.0 %s",
+ nullserver);
+ }
+ else
+ usrerr("452 4.4.5 Insufficient disk space; try again later");
+ continue;
+ }
+ }
+
+ switch (c->cmd_code)
+ {
+#if SASL
+ case CMDAUTH: /* sasl */
+ DELAY_CONN("AUTH");
+ if (!sasl_ok || n_mechs <= 0)
+ {
+ message("503 5.3.3 AUTH not available");
+ break;
+ }
+ if (authenticating == SASL_IS_AUTH)
+ {
+ message("503 5.5.0 Already Authenticated");
+ break;
+ }
+ if (smtp.sm_gotmail)
+ {
+ message("503 5.5.0 AUTH not permitted during a mail transaction");
+ break;
+ }
+ if (tempfail)
+ {
+ if (LogLevel > 9)
+ sm_syslog(LOG_INFO, e->e_id,
+ "SMTP AUTH command (%.100s) from %s tempfailed (due to previous checks)",
+ p, CurSmtpClient);
+ usrerr("454 4.3.0 Please try again later");
+ break;
+ }
+
+ ismore = false;
+
+ /* crude way to avoid crack attempts */
+ STOP_IF_ATTACK(checksmtpattack(&n_auth, n_mechs + 1,
+ true, "AUTH", e));
+
+ /* make sure mechanism (p) is a valid string */
+ for (q = p; *q != '\0' && isascii(*q); q++)
+ {
+ if (isspace(*q))
+ {
+ *q = '\0';
+ while (*++q != '\0' &&
+ isascii(*q) && isspace(*q))
+ continue;
+ *(q - 1) = '\0';
+ ismore = (*q != '\0');
+ break;
+ }
+ }
+
+ if (*p == '\0')
+ {
+ message("501 5.5.2 AUTH mechanism must be specified");
+ break;
+ }
+
+ /* check whether mechanism is available */
+ if (iteminlist(p, mechlist, " ") == NULL)
+ {
+ message("504 5.3.3 AUTH mechanism %.32s not available",
+ p);
+ break;
+ }
+
+ if (ismore)
+ {
+ /* could this be shorter? XXX */
+# if SASL >= 20000
+ in = xalloc(strlen(q) + 1);
+ result = sasl_decode64(q, strlen(q), in,
+ strlen(q), &inlen);
+# else /* SASL >= 20000 */
+ in = sm_rpool_malloc(e->e_rpool, strlen(q));
+ result = sasl_decode64(q, strlen(q), in,
+ &inlen);
+# endif /* SASL >= 20000 */
+ if (result != SASL_OK)
+ {
+ message("501 5.5.4 cannot BASE64 decode '%s'",
+ q);
+ if (LogLevel > 5)
+ sm_syslog(LOG_WARNING, e->e_id,
+ "AUTH decode64 error [%d for \"%s\"]",
+ result, q);
+ /* start over? */
+ authenticating = SASL_NOT_AUTH;
+# if SASL >= 20000
+ sm_free(in);
+# endif /* SASL >= 20000 */
+ in = NULL;
+ inlen = 0;
+ break;
+ }
+ }
+ else
+ {
+ in = NULL;
+ inlen = 0;
+ }
+
+ /* see if that auth type exists */
+# if SASL >= 20000
+ result = sasl_server_start(conn, p, in, inlen,
+ &out, &outlen);
+ if (in != NULL)
+ sm_free(in);
+# else /* SASL >= 20000 */
+ result = sasl_server_start(conn, p, in, inlen,
+ &out, &outlen, &errstr);
+# endif /* SASL >= 20000 */
+
+ if (result != SASL_OK && result != SASL_CONTINUE)
+ {
+ message("535 5.7.0 authentication failed");
+ if (LogLevel > 9)
+ sm_syslog(LOG_ERR, e->e_id,
+ "AUTH failure (%s): %s (%d) %s",
+ p,
+ sasl_errstring(result, NULL,
+ NULL),
+ result,
+# if SASL >= 20000
+ sasl_errdetail(conn));
+# else /* SASL >= 20000 */
+ errstr);
+# endif /* SASL >= 20000 */
+ RESET_SASLCONN;
+ break;
+ }
+ auth_type = newstr(p);
+
+ if (result == SASL_OK)
+ {
+ /* ugly, but same code */
+ goto authenticated;
+ /* authenticated by the initial response */
+ }
+
+ /* len is at least 2 */
+ len = ENC64LEN(outlen);
+ out2 = xalloc(len);
+ result = sasl_encode64(out, outlen, out2, len,
+ &out2len);
+
+ if (result != SASL_OK)
+ {
+ message("454 4.5.4 Temporary authentication failure");
+ if (LogLevel > 5)
+ sm_syslog(LOG_WARNING, e->e_id,
+ "AUTH encode64 error [%d for \"%s\"]",
+ result, out);
+
+ /* start over? */
+ authenticating = SASL_NOT_AUTH;
+ RESET_SASLCONN;
+ }
+ else
+ {
+ message("334 %s", out2);
+ authenticating = SASL_PROC_AUTH;
+ }
+# if SASL >= 20000
+ sm_free(out2);
+# endif /* SASL >= 20000 */
+ break;
+#endif /* SASL */
+
+#if STARTTLS
+ case CMDSTLS: /* starttls */
+ DELAY_CONN("STARTTLS");
+ if (*p != '\0')
+ {
+ message("501 5.5.2 Syntax error (no parameters allowed)");
+ break;
+ }
+ if (!bitset(SRV_OFFER_TLS, features))
+ {
+ message("503 5.5.0 TLS not available");
+ break;
+ }
+ if (!tls_ok_srv)
+ {
+ message("454 4.3.3 TLS not available after start");
+ break;
+ }
+ if (smtp.sm_gotmail)
+ {
+ message("503 5.5.0 TLS not permitted during a mail transaction");
+ break;
+ }
+ if (tempfail)
+ {
+ if (LogLevel > 9)
+ sm_syslog(LOG_INFO, e->e_id,
+ "SMTP STARTTLS command (%.100s) from %s tempfailed (due to previous checks)",
+ p, CurSmtpClient);
+ usrerr("454 4.7.0 Please try again later");
+ break;
+ }
+ starttls:
+# if TLS_NO_RSA
+ /*
+ ** XXX do we need a temp key ?
+ */
+# else /* TLS_NO_RSA */
+# endif /* TLS_NO_RSA */
+
+# if TLS_VRFY_PER_CTX
+ /*
+ ** Note: this sets the verification globally
+ ** (per SSL_CTX)
+ ** it's ok since it applies only to one transaction
+ */
+
+ TLS_VERIFY_CLIENT();
+# endif /* TLS_VRFY_PER_CTX */
+
+ if (srv_ssl != NULL)
+ SSL_clear(srv_ssl);
+ else if ((srv_ssl = SSL_new(srv_ctx)) == NULL)
+ {
+ message("454 4.3.3 TLS not available: error generating SSL handle");
+ if (LogLevel > 8)
+ tlslogerr("server");
+ goto tls_done;
+ }
+
+# if !TLS_VRFY_PER_CTX
+ /*
+ ** this could be used if it were possible to set
+ ** verification per SSL (connection)
+ ** not just per SSL_CTX (global)
+ */
+
+ TLS_VERIFY_CLIENT();
+# endif /* !TLS_VRFY_PER_CTX */
+
+ rfd = sm_io_getinfo(InChannel, SM_IO_WHAT_FD, NULL);
+ wfd = sm_io_getinfo(OutChannel, SM_IO_WHAT_FD, NULL);
+
+ if (rfd < 0 || wfd < 0 ||
+ SSL_set_rfd(srv_ssl, rfd) <= 0 ||
+ SSL_set_wfd(srv_ssl, wfd) <= 0)
+ {
+ message("454 4.3.3 TLS not available: error set fd");
+ SSL_free(srv_ssl);
+ srv_ssl = NULL;
+ goto tls_done;
+ }
+ if (!smtps)
+ message("220 2.0.0 Ready to start TLS");
+# if PIPELINING
+ (void) sm_io_flush(OutChannel, SM_TIME_DEFAULT);
+# endif /* PIPELINING */
+
+ SSL_set_accept_state(srv_ssl);
+
+# define SSL_ACC(s) SSL_accept(s)
+
+ tlsstart = curtime();
+ fdfl = fcntl(rfd, F_GETFL);
+ if (fdfl != -1)
+ fcntl(rfd, F_SETFL, fdfl|O_NONBLOCK);
+ ssl_retry:
+ if ((r = SSL_ACC(srv_ssl)) <= 0)
+ {
+ int i;
+ bool timedout;
+ time_t left;
+ time_t now = curtime();
+ struct timeval tv;
+
+ /* what to do in this case? */
+ i = SSL_get_error(srv_ssl, r);
+
+ /*
+ ** For SSL_ERROR_WANT_{READ,WRITE}:
+ ** There is no SSL record available yet
+ ** or there is only a partial SSL record
+ ** removed from the network (socket) buffer
+ ** into the SSL buffer. The SSL_accept will
+ ** only succeed when a full SSL record is
+ ** available (assuming a "real" error
+ ** doesn't happen). To handle when a "real"
+ ** error does happen the select is set for
+ ** exceptions too.
+ ** The connection may be re-negotiated
+ ** during this time so both read and write
+ ** "want errors" need to be handled.
+ ** A select() exception loops back so that
+ ** a proper SSL error message can be gotten.
+ */
+
+ left = TimeOuts.to_starttls - (now - tlsstart);
+ timedout = left <= 0;
+ if (!timedout)
+ {
+ tv.tv_sec = left;
+ tv.tv_usec = 0;
+ }
+
+ if (!timedout && FD_SETSIZE > 0 &&
+ (rfd >= FD_SETSIZE ||
+ (i == SSL_ERROR_WANT_WRITE &&
+ wfd >= FD_SETSIZE)))
+ {
+ if (LogLevel > 5)
+ {
+ sm_syslog(LOG_ERR, NOQID,
+ "STARTTLS=server, error: fd %d/%d too large",
+ rfd, wfd);
+ if (LogLevel > 8)
+ tlslogerr("server");
+ }
+ goto tlsfail;
+ }
+
+ /* XXX what about SSL_pending() ? */
+ if (!timedout && i == SSL_ERROR_WANT_READ)
+ {
+ fd_set ssl_maskr, ssl_maskx;
+
+ FD_ZERO(&ssl_maskr);
+ FD_SET(rfd, &ssl_maskr);
+ FD_ZERO(&ssl_maskx);
+ FD_SET(rfd, &ssl_maskx);
+ if (select(rfd + 1, &ssl_maskr, NULL,
+ &ssl_maskx, &tv) > 0)
+ goto ssl_retry;
+ }
+ if (!timedout && i == SSL_ERROR_WANT_WRITE)
+ {
+ fd_set ssl_maskw, ssl_maskx;
+
+ FD_ZERO(&ssl_maskw);
+ FD_SET(wfd, &ssl_maskw);
+ FD_ZERO(&ssl_maskx);
+ FD_SET(rfd, &ssl_maskx);
+ if (select(wfd + 1, NULL, &ssl_maskw,
+ &ssl_maskx, &tv) > 0)
+ goto ssl_retry;
+ }
+ if (LogLevel > 5)
+ {
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS=server, error: accept failed=%d, SSL_error=%d, timedout=%d, errno=%d",
+ r, i, (int) timedout, errno);
+ if (LogLevel > 8)
+ tlslogerr("server");
+ }
+tlsfail:
+ tls_ok_srv = false;
+ SSL_free(srv_ssl);
+ srv_ssl = NULL;
+
+ /*
+ ** according to the next draft of
+ ** RFC 2487 the connection should be dropped
+ */
+
+ /* arrange to ignore any current send list */
+ e->e_sendqueue = NULL;
+ goto doquit;
+ }
+
+ if (fdfl != -1)
+ fcntl(rfd, F_SETFL, fdfl);
+
+ /* ignore return code for now, it's in {verify} */
+ (void) tls_get_info(srv_ssl, true,
+ CurSmtpClient,
+ &BlankEnvelope.e_macro,
+ bitset(SRV_VRFY_CLT, features));
+
+ /*
+ ** call Stls_client to find out whether
+ ** to accept the connection from the client
+ */
+
+ saveQuickAbort = QuickAbort;
+ saveSuprErrs = SuprErrs;
+ SuprErrs = true;
+ QuickAbort = false;
+ if (rscheck("tls_client",
+ macvalue(macid("{verify}"), e),
+ "STARTTLS", e,
+ RSF_RMCOMM|RSF_COUNT,
+ 5, NULL, NOQID) != EX_OK ||
+ Errors > 0)
+ {
+ extern char MsgBuf[];
+
+ if (MsgBuf[0] != '\0' && ISSMTPREPLY(MsgBuf))
+ nullserver = newstr(MsgBuf);
+ else
+ nullserver = "503 5.7.0 Authentication required.";
+ }
+ QuickAbort = saveQuickAbort;
+ SuprErrs = saveSuprErrs;
+
+ tls_ok_srv = false; /* don't offer STARTTLS again */
+ n_helo = 0;
+# if SASL
+ if (sasl_ok)
+ {
+ int cipher_bits;
+ bool verified;
+ char *s, *v, *c;
+
+ s = macvalue(macid("{cipher_bits}"), e);
+ v = macvalue(macid("{verify}"), e);
+ c = macvalue(macid("{cert_subject}"), e);
+ verified = (v != NULL && strcmp(v, "OK") == 0);
+ if (s != NULL && (cipher_bits = atoi(s)) > 0)
+ {
+# if SASL >= 20000
+ ext_ssf = cipher_bits;
+ auth_id = verified ? c : NULL;
+ sasl_ok = ((sasl_setprop(conn,
+ SASL_SSF_EXTERNAL,
+ &ext_ssf) == SASL_OK) &&
+ (sasl_setprop(conn,
+ SASL_AUTH_EXTERNAL,
+ auth_id) == SASL_OK));
+# else /* SASL >= 20000 */
+ ext_ssf.ssf = cipher_bits;
+ ext_ssf.auth_id = verified ? c : NULL;
+ sasl_ok = sasl_setprop(conn,
+ SASL_SSF_EXTERNAL,
+ &ext_ssf) == SASL_OK;
+# endif /* SASL >= 20000 */
+ mechlist = NULL;
+ if (sasl_ok)
+ n_mechs = saslmechs(conn,
+ &mechlist);
+ }
+ }
+# endif /* SASL */
+
+ /* switch to secure connection */
+ if (sfdctls(&InChannel, &OutChannel, srv_ssl) == 0)
+ {
+ tls_active = true;
+# if PIPELINING
+ (void) sm_io_autoflush(InChannel, OutChannel);
+# endif /* PIPELINING */
+ }
+ else
+ {
+ /*
+ ** XXX this is an internal error
+ ** how to deal with it?
+ ** we can't generate an error message
+ ** since the other side switched to an
+ ** encrypted layer, but we could not...
+ ** just "hang up"?
+ */
+
+ nullserver = "454 4.3.3 TLS not available: can't switch to encrypted layer";
+ syserr("STARTTLS: can't switch to encrypted layer");
+ }
+ tls_done:
+ if (smtps)
+ {
+ if (tls_active)
+ goto greeting;
+ else
+ goto doquit;
+ }
+ break;
+#endif /* STARTTLS */
+
+ case CMDHELO: /* hello -- introduce yourself */
+ case CMDEHLO: /* extended hello */
+ DELAY_CONN("EHLO");
+ if (c->cmd_code == CMDEHLO)
+ {
+ protocol = "ESMTP";
+ SmtpPhase = "server EHLO";
+ }
+ else
+ {
+ protocol = "SMTP";
+ SmtpPhase = "server HELO";
+ }
+
+ /* avoid denial-of-service */
+ STOP_IF_ATTACK(checksmtpattack(&n_helo, MAXHELOCOMMANDS,
+ true, "HELO/EHLO", e));
+
+#if 0
+ /* RFC2821 4.1.4 allows duplicate HELO/EHLO */
+ /* check for duplicate HELO/EHLO per RFC 1651 4.2 */
+ if (gothello)
+ {
+ usrerr("503 %s Duplicate HELO/EHLO",
+ MyHostName);
+ break;
+ }
+#endif /* 0 */
+
+ /* check for valid domain name (re 1123 5.2.5) */
+ if (*p == '\0' && !AllowBogusHELO)
+ {
+ usrerr("501 %s requires domain address",
+ cmdbuf);
+ break;
+ }
+
+ /* check for long domain name (hides Received: info) */
+ if (strlen(p) > MAXNAME)
+ {
+ usrerr("501 Invalid domain name");
+ if (LogLevel > 9)
+ sm_syslog(LOG_INFO, CurEnv->e_id,
+ "invalid domain name (too long) from %s",
+ CurSmtpClient);
+ break;
+ }
+
+ ok = true;
+ for (q = p; *q != '\0'; q++)
+ {
+ if (!isascii(*q))
+ break;
+ if (isalnum(*q))
+ continue;
+ if (isspace(*q))
+ {
+ *q = '\0';
+
+ /* only complain if strict check */
+ ok = AllowBogusHELO;
+
+ /* allow trailing whitespace */
+ while (!ok && *++q != '\0' &&
+ isspace(*q))
+ ;
+ if (*q == '\0')
+ ok = true;
+ break;
+ }
+ if (strchr("[].-_#:", *q) == NULL)
+ break;
+ }
+
+ if (*q == '\0' && ok)
+ {
+ q = "pleased to meet you";
+ sendinghost = sm_strdup_x(p);
+ }
+ else if (!AllowBogusHELO)
+ {
+ usrerr("501 Invalid domain name");
+ if (LogLevel > 9)
+ sm_syslog(LOG_INFO, CurEnv->e_id,
+ "invalid domain name (%s) from %.100s",
+ p, CurSmtpClient);
+ break;
+ }
+ else
+ {
+ q = "accepting invalid domain name";
+ }
+
+ if (gothello)
+ {
+ CLEAR_STATE(cmdbuf);
+ }
+
+#if MILTER
+ if (smtp.sm_milterlist && smtp.sm_milterize &&
+ !bitset(EF_DISCARD, e->e_flags))
+ {
+ char state;
+ char *response;
+
+ response = milter_helo(p, e, &state);
+ switch (state)
+ {
+ case SMFIR_REPLYCODE:
+ if (MilterLogLevel > 3)
+ sm_syslog(LOG_INFO, e->e_id,
+ "Milter: helo=%s, reject=%s",
+ p, response);
+ nullserver = newstr(response);
+ smtp.sm_milterize = false;
+ break;
+
+ case SMFIR_REJECT:
+ if (MilterLogLevel > 3)
+ sm_syslog(LOG_INFO, e->e_id,
+ "Milter: helo=%s, reject=Command rejected",
+ p);
+ nullserver = "Command rejected";
+ smtp.sm_milterize = false;
+ break;
+
+ case SMFIR_TEMPFAIL:
+ if (MilterLogLevel > 3)
+ sm_syslog(LOG_INFO, e->e_id,
+ "Milter: helo=%s, reject=%s",
+ p, MSG_TEMPFAIL);
+ tempfail = true;
+ smtp.sm_milterize = false;
+ break;
+ }
+ if (response != NULL)
+ sm_free(response);
+
+ /*
+ ** If quarantining by a connect/ehlo action,
+ ** save between messages
+ */
+
+ if (smtp.sm_quarmsg == NULL &&
+ e->e_quarmsg != NULL)
+ smtp.sm_quarmsg = newstr(e->e_quarmsg);
+ }
+#endif /* MILTER */
+ gothello = true;
+
+ /* print HELO response message */
+ if (c->cmd_code != CMDEHLO)
+ {
+ message("250 %s Hello %s, %s",
+ MyHostName, CurSmtpClient, q);
+ break;
+ }
+
+ message("250-%s Hello %s, %s",
+ MyHostName, CurSmtpClient, q);
+
+ /* offer ENHSC even for nullserver */
+ if (nullserver != NULL)
+ {
+ message("250 ENHANCEDSTATUSCODES");
+ break;
+ }
+
+ /*
+ ** print EHLO features list
+ **
+ ** Note: If you change this list,
+ ** remember to update 'helpfile'
+ */
+
+ message("250-ENHANCEDSTATUSCODES");
+#if PIPELINING
+ if (bitset(SRV_OFFER_PIPE, features))
+ message("250-PIPELINING");
+#endif /* PIPELINING */
+ if (bitset(SRV_OFFER_EXPN, features))
+ {
+ message("250-EXPN");
+ if (bitset(SRV_OFFER_VERB, features))
+ message("250-VERB");
+ }
+#if MIME8TO7
+ message("250-8BITMIME");
+#endif /* MIME8TO7 */
+ if (MaxMessageSize > 0)
+ message("250-SIZE %ld", MaxMessageSize);
+ else
+ message("250-SIZE");
+#if DSN
+ if (SendMIMEErrors && bitset(SRV_OFFER_DSN, features))
+ message("250-DSN");
+#endif /* DSN */
+ if (bitset(SRV_OFFER_ETRN, features))
+ message("250-ETRN");
+#if SASL
+ if (sasl_ok && mechlist != NULL && *mechlist != '\0')
+ message("250-AUTH %s", mechlist);
+#endif /* SASL */
+#if STARTTLS
+ if (tls_ok_srv &&
+ bitset(SRV_OFFER_TLS, features))
+ message("250-STARTTLS");
+#endif /* STARTTLS */
+ if (DeliverByMin > 0)
+ message("250-DELIVERBY %ld",
+ (long) DeliverByMin);
+ else if (DeliverByMin == 0)
+ message("250-DELIVERBY");
+
+ /* < 0: no deliver-by */
+
+ message("250 HELP");
+ break;
+
+ case CMDMAIL: /* mail -- designate sender */
+ SmtpPhase = "server MAIL";
+ DELAY_CONN("MAIL");
+
+ /* check for validity of this command */
+ if (!gothello && bitset(PRIV_NEEDMAILHELO, PrivacyFlags))
+ {
+ usrerr("503 5.0.0 Polite people say HELO first");
+ break;
+ }
+ if (smtp.sm_gotmail)
+ {
+ usrerr("503 5.5.0 Sender already specified");
+ break;
+ }
+#if SASL
+ if (bitset(SRV_REQ_AUTH, features) &&
+ authenticating != SASL_IS_AUTH)
+ {
+ usrerr("530 5.7.0 Authentication required");
+ break;
+ }
+#endif /* SASL */
+
+ p = skipword(p, "from");
+ if (p == NULL)
+ break;
+ if (tempfail)
+ {
+ if (LogLevel > 9)
+ sm_syslog(LOG_INFO, e->e_id,
+ "SMTP MAIL command (%.100s) from %s tempfailed (due to previous checks)",
+ p, CurSmtpClient);
+ usrerr(MSG_TEMPFAIL);
+ break;
+ }
+
+ /* make sure we know who the sending host is */
+ if (sendinghost == NULL)
+ sendinghost = peerhostname;
+
+
+#if SM_HEAP_CHECK
+ if (sm_debug_active(&DebugLeakSmtp, 1))
+ {
+ sm_heap_newgroup();
+ sm_dprintf("smtp() heap group #%d\n",
+ sm_heap_group());
+ }
+#endif /* SM_HEAP_CHECK */
+
+ if (Errors > 0)
+ goto undo_no_pm;
+ if (!gothello)
+ {
+ auth_warning(e, "%s didn't use HELO protocol",
+ CurSmtpClient);
+ }
+#ifdef PICKY_HELO_CHECK
+ if (sm_strcasecmp(sendinghost, peerhostname) != 0 &&
+ (sm_strcasecmp(peerhostname, "localhost") != 0 ||
+ sm_strcasecmp(sendinghost, MyHostName) != 0))
+ {
+ auth_warning(e, "Host %s claimed to be %s",
+ CurSmtpClient, sendinghost);
+ }
+#endif /* PICKY_HELO_CHECK */
+
+ if (protocol == NULL)
+ protocol = "SMTP";
+ macdefine(&e->e_macro, A_PERM, 'r', protocol);
+ macdefine(&e->e_macro, A_PERM, 's', sendinghost);
+
+ if (Errors > 0)
+ goto undo_no_pm;
+ smtp.sm_nrcpts = 0;
+ n_badrcpts = 0;
+ macdefine(&e->e_macro, A_PERM, macid("{ntries}"), "0");
+ macdefine(&e->e_macro, A_PERM, macid("{nrcpts}"), "0");
+ macdefine(&e->e_macro, A_PERM, macid("{nbadrcpts}"),
+ "0");
+ e->e_flags |= EF_CLRQUEUE;
+ sm_setproctitle(true, e, "%s %s: %.80s",
+ qid_printname(e),
+ CurSmtpClient, inp);
+
+ /* do the processing */
+ SM_TRY
+ {
+ extern char *FullName;
+
+ QuickAbort = true;
+ SM_FREE_CLR(FullName);
+
+ /* must parse sender first */
+ delimptr = NULL;
+ setsender(p, e, &delimptr, ' ', false);
+ if (delimptr != NULL && *delimptr != '\0')
+ *delimptr++ = '\0';
+ if (Errors > 0)
+ sm_exc_raisenew_x(&EtypeQuickAbort, 1);
+
+ /* Successfully set e_from, allow logging */
+ e->e_flags |= EF_LOGSENDER;
+
+ /* put resulting triple from parseaddr() into macros */
+ if (e->e_from.q_mailer != NULL)
+ macdefine(&e->e_macro, A_PERM,
+ macid("{mail_mailer}"),
+ e->e_from.q_mailer->m_name);
+ else
+ macdefine(&e->e_macro, A_PERM,
+ macid("{mail_mailer}"), NULL);
+ if (e->e_from.q_host != NULL)
+ macdefine(&e->e_macro, A_PERM,
+ macid("{mail_host}"),
+ e->e_from.q_host);
+ else
+ macdefine(&e->e_macro, A_PERM,
+ macid("{mail_host}"), "localhost");
+ if (e->e_from.q_user != NULL)
+ macdefine(&e->e_macro, A_PERM,
+ macid("{mail_addr}"),
+ e->e_from.q_user);
+ else
+ macdefine(&e->e_macro, A_PERM,
+ macid("{mail_addr}"), NULL);
+ if (Errors > 0)
+ sm_exc_raisenew_x(&EtypeQuickAbort, 1);
+
+ /* check for possible spoofing */
+ if (RealUid != 0 && OpMode == MD_SMTP &&
+ !wordinclass(RealUserName, 't') &&
+ (!bitnset(M_LOCALMAILER,
+ e->e_from.q_mailer->m_flags) ||
+ strcmp(e->e_from.q_user, RealUserName) != 0))
+ {
+ auth_warning(e, "%s owned process doing -bs",
+ RealUserName);
+ }
+
+ /* reset to default value */
+ SevenBitInput = save_sevenbitinput;
+
+ /* now parse ESMTP arguments */
+ e->e_msgsize = 0;
+ addr = p;
+ argno = 0;
+ args[argno++] = p;
+ p = delimptr;
+ while (p != NULL && *p != '\0')
+ {
+ char *kp;
+ char *vp = NULL;
+ char *equal = NULL;
+
+ /* locate the beginning of the keyword */
+ SKIP_SPACE(p);
+ if (*p == '\0')
+ break;
+ kp = p;
+
+ /* skip to the value portion */
+ while ((isascii(*p) && isalnum(*p)) || *p == '-')
+ p++;
+ if (*p == '=')
+ {
+ equal = p;
+ *p++ = '\0';
+ vp = p;
+
+ /* skip to the end of the value */
+ while (*p != '\0' && *p != ' ' &&
+ !(isascii(*p) && iscntrl(*p)) &&
+ *p != '=')
+ p++;
+ }
+
+ if (*p != '\0')
+ *p++ = '\0';
+
+ if (tTd(19, 1))
+ sm_dprintf("MAIL: got arg %s=\"%s\"\n", kp,
+ vp == NULL ? "<null>" : vp);
+
+ mail_esmtp_args(kp, vp, e);
+ if (equal != NULL)
+ *equal = '=';
+ args[argno++] = kp;
+ if (argno >= MAXSMTPARGS - 1)
+ usrerr("501 5.5.4 Too many parameters");
+ if (Errors > 0)
+ sm_exc_raisenew_x(&EtypeQuickAbort, 1);
+ }
+ args[argno] = NULL;
+ if (Errors > 0)
+ sm_exc_raisenew_x(&EtypeQuickAbort, 1);
+
+#if SASL
+# if _FFR_AUTH_PASSING
+ /* set the default AUTH= if the sender didn't */
+ if (e->e_auth_param == NULL)
+ {
+ /* XXX only do this for an MSA? */
+ e->e_auth_param = macvalue(macid("{auth_authen}"),
+ e);
+ if (e->e_auth_param == NULL)
+ e->e_auth_param = "<>";
+
+ /*
+ ** XXX should we invoke Strust_auth now?
+ ** authorizing as the client that just
+ ** authenticated, so we'll trust implicitly
+ */
+ }
+# endif /* _FFR_AUTH_PASSING */
+#endif /* SASL */
+
+ /* do config file checking of the sender */
+ macdefine(&e->e_macro, A_PERM,
+ macid("{addr_type}"), "e s");
+#if _FFR_MAIL_MACRO
+ /* make the "real" sender address available */
+ macdefine(&e->e_macro, A_TEMP, macid("{mail_from}"),
+ e->e_from.q_paddr);
+#endif /* _FFR_MAIL_MACRO */
+ if (rscheck("check_mail", addr,
+ NULL, e, RSF_RMCOMM|RSF_COUNT, 3,
+ NULL, e->e_id) != EX_OK ||
+ Errors > 0)
+ sm_exc_raisenew_x(&EtypeQuickAbort, 1);
+ macdefine(&e->e_macro, A_PERM,
+ macid("{addr_type}"), NULL);
+
+ if (MaxMessageSize > 0 &&
+ (e->e_msgsize > MaxMessageSize ||
+ e->e_msgsize < 0))
+ {
+ usrerr("552 5.2.3 Message size exceeds fixed maximum message size (%ld)",
+ MaxMessageSize);
+ sm_exc_raisenew_x(&EtypeQuickAbort, 1);
+ }
+
+ /*
+ ** XXX always check whether there is at least one fs
+ ** with enough space?
+ ** However, this may not help much: the queue group
+ ** selection may later on select a FS that hasn't
+ ** enough space.
+ */
+
+ if ((NumFileSys == 1 || NumQueue == 1) &&
+ !enoughdiskspace(e->e_msgsize, e)
+#if _FFR_ANY_FREE_FS
+ && !filesys_free(e->e_msgsize)
+#endif /* _FFR_ANY_FREE_FS */
+ )
+ {
+ /*
+ ** We perform this test again when the
+ ** queue directory is selected, in collect.
+ */
+
+ usrerr("452 4.4.5 Insufficient disk space; try again later");
+ sm_exc_raisenew_x(&EtypeQuickAbort, 1);
+ }
+ if (Errors > 0)
+ sm_exc_raisenew_x(&EtypeQuickAbort, 1);
+
+ LogUsrErrs = true;
+#if MILTER
+ if (smtp.sm_milterlist && smtp.sm_milterize &&
+ !bitset(EF_DISCARD, e->e_flags))
+ {
+ char state;
+ char *response;
+
+ response = milter_envfrom(args, e, &state);
+ MILTER_REPLY("from");
+ }
+#endif /* MILTER */
+ if (Errors > 0)
+ sm_exc_raisenew_x(&EtypeQuickAbort, 1);
+
+ message("250 2.1.0 Sender ok");
+ smtp.sm_gotmail = true;
+ }
+ SM_EXCEPT(exc, "[!F]*")
+ {
+ /*
+ ** An error occurred while processing a MAIL command.
+ ** Jump to the common error handling code.
+ */
+
+ sm_exc_free(exc);
+ goto undo_no_pm;
+ }
+ SM_END_TRY
+ break;
+
+ undo_no_pm:
+ e->e_flags &= ~EF_PM_NOTIFY;
+ undo:
+ break;
+
+ case CMDRCPT: /* rcpt -- designate recipient */
+ DELAY_CONN("RCPT");
+ if (BadRcptThrottle > 0 &&
+ n_badrcpts >= BadRcptThrottle)
+ {
+ if (LogLevel > 5 &&
+ n_badrcpts == BadRcptThrottle)
+ {
+ sm_syslog(LOG_INFO, e->e_id,
+ "%s: Possible SMTP RCPT flood, throttling.",
+ CurSmtpClient);
+
+ /* To avoid duplicated message */
+ n_badrcpts++;
+ }
+ NBADRCPTS;
+
+ /*
+ ** Don't use exponential backoff for now.
+ ** Some servers will open more connections
+ ** and actually overload the receiver even
+ ** more.
+ */
+
+ (void) sleep(1);
+ }
+ if (!smtp.sm_gotmail)
+ {
+ usrerr("503 5.0.0 Need MAIL before RCPT");
+ break;
+ }
+ SmtpPhase = "server RCPT";
+ SM_TRY
+ {
+ QuickAbort = true;
+ LogUsrErrs = true;
+
+ /* limit flooding of our machine */
+ if (MaxRcptPerMsg > 0 &&
+ smtp.sm_nrcpts >= MaxRcptPerMsg)
+ {
+ /* sleep(1); / * slow down? */
+ usrerr("452 4.5.3 Too many recipients");
+ goto rcpt_done;
+ }
+
+ if (e->e_sendmode != SM_DELIVER)
+ e->e_flags |= EF_VRFYONLY;
+
+#if MILTER
+ /*
+ ** If the filter will be deleting recipients,
+ ** don't expand them at RCPT time (in the call
+ ** to recipient()). If they are expanded, it
+ ** is impossible for removefromlist() to figure
+ ** out the expanded members of the original
+ ** recipient and mark them as QS_DONTSEND.
+ */
+
+ if (milter_can_delrcpts())
+ e->e_flags |= EF_VRFYONLY;
+#endif /* MILTER */
+
+ p = skipword(p, "to");
+ if (p == NULL)
+ goto rcpt_done;
+ macdefine(&e->e_macro, A_PERM,
+ macid("{addr_type}"), "e r");
+ a = parseaddr(p, NULLADDR, RF_COPYALL, ' ', &delimptr,
+ e, true);
+ macdefine(&e->e_macro, A_PERM,
+ macid("{addr_type}"), NULL);
+ if (Errors > 0)
+ goto rcpt_done;
+ if (a == NULL)
+ {
+ usrerr("501 5.0.0 Missing recipient");
+ goto rcpt_done;
+ }
+
+ if (delimptr != NULL && *delimptr != '\0')
+ *delimptr++ = '\0';
+
+ /* put resulting triple from parseaddr() into macros */
+ if (a->q_mailer != NULL)
+ macdefine(&e->e_macro, A_PERM,
+ macid("{rcpt_mailer}"),
+ a->q_mailer->m_name);
+ else
+ macdefine(&e->e_macro, A_PERM,
+ macid("{rcpt_mailer}"), NULL);
+ if (a->q_host != NULL)
+ macdefine(&e->e_macro, A_PERM,
+ macid("{rcpt_host}"), a->q_host);
+ else
+ macdefine(&e->e_macro, A_PERM,
+ macid("{rcpt_host}"), "localhost");
+ if (a->q_user != NULL)
+ macdefine(&e->e_macro, A_PERM,
+ macid("{rcpt_addr}"), a->q_user);
+ else
+ macdefine(&e->e_macro, A_PERM,
+ macid("{rcpt_addr}"), NULL);
+ if (Errors > 0)
+ goto rcpt_done;
+
+ /* now parse ESMTP arguments */
+ addr = p;
+ argno = 0;
+ args[argno++] = p;
+ p = delimptr;
+ while (p != NULL && *p != '\0')
+ {
+ char *kp;
+ char *vp = NULL;
+ char *equal = NULL;
+
+ /* locate the beginning of the keyword */
+ SKIP_SPACE(p);
+ if (*p == '\0')
+ break;
+ kp = p;
+
+ /* skip to the value portion */
+ while ((isascii(*p) && isalnum(*p)) || *p == '-')
+ p++;
+ if (*p == '=')
+ {
+ equal = p;
+ *p++ = '\0';
+ vp = p;
+
+ /* skip to the end of the value */
+ while (*p != '\0' && *p != ' ' &&
+ !(isascii(*p) && iscntrl(*p)) &&
+ *p != '=')
+ p++;
+ }
+
+ if (*p != '\0')
+ *p++ = '\0';
+
+ if (tTd(19, 1))
+ sm_dprintf("RCPT: got arg %s=\"%s\"\n", kp,
+ vp == NULL ? "<null>" : vp);
+
+ rcpt_esmtp_args(a, kp, vp, e);
+ if (equal != NULL)
+ *equal = '=';
+ args[argno++] = kp;
+ if (argno >= MAXSMTPARGS - 1)
+ usrerr("501 5.5.4 Too many parameters");
+ if (Errors > 0)
+ break;
+ }
+ args[argno] = NULL;
+ if (Errors > 0)
+ goto rcpt_done;
+
+ /* do config file checking of the recipient */
+ macdefine(&e->e_macro, A_PERM,
+ macid("{addr_type}"), "e r");
+ if (rscheck("check_rcpt", addr,
+ NULL, e, RSF_RMCOMM|RSF_COUNT, 3,
+ NULL, e->e_id) != EX_OK ||
+ Errors > 0)
+ goto rcpt_done;
+ macdefine(&e->e_macro, A_PERM,
+ macid("{addr_type}"), NULL);
+
+ /* If discarding, don't bother to verify user */
+ if (bitset(EF_DISCARD, e->e_flags))
+ a->q_state = QS_VERIFIED;
+
+#if MILTER
+ if (smtp.sm_milterlist && smtp.sm_milterize &&
+ !bitset(EF_DISCARD, e->e_flags))
+ {
+ char state;
+ char *response;
+
+ response = milter_envrcpt(args, e, &state);
+ MILTER_REPLY("to");
+ }
+#endif /* MILTER */
+
+ macdefine(&e->e_macro, A_PERM,
+ macid("{rcpt_mailer}"), NULL);
+ macdefine(&e->e_macro, A_PERM,
+ macid("{rcpt_host}"), NULL);
+ macdefine(&e->e_macro, A_PERM,
+ macid("{rcpt_addr}"), NULL);
+ macdefine(&e->e_macro, A_PERM,
+ macid("{dsn_notify}"), NULL);
+ if (Errors > 0)
+ goto rcpt_done;
+
+ /* save in recipient list after ESMTP mods */
+ a = recipient(a, &e->e_sendqueue, 0, e);
+ if (Errors > 0)
+ goto rcpt_done;
+
+ /* no errors during parsing, but might be a duplicate */
+ e->e_to = a->q_paddr;
+ if (!QS_IS_BADADDR(a->q_state))
+ {
+ if (smtp.sm_nrcpts == 0)
+ initsys(e);
+ message("250 2.1.5 Recipient ok%s",
+ QS_IS_QUEUEUP(a->q_state) ?
+ " (will queue)" : "");
+ smtp.sm_nrcpts++;
+ }
+ else
+ {
+ /* punt -- should keep message in ADDRESS.... */
+ usrerr("550 5.1.1 Addressee unknown");
+ }
+ rcpt_done:
+ if (Errors > 0)
+ {
+ ++n_badrcpts;
+ NBADRCPTS;
+ }
+ }
+ SM_EXCEPT(exc, "[!F]*")
+ {
+ /* An exception occurred while processing RCPT */
+ e->e_flags &= ~(EF_FATALERRS|EF_PM_NOTIFY);
+ ++n_badrcpts;
+ NBADRCPTS;
+ }
+ SM_END_TRY
+ break;
+
+ case CMDDATA: /* data -- text of mail */
+ DELAY_CONN("DATA");
+ if (!smtp_data(&smtp, e))
+ goto doquit;
+ break;
+
+ case CMDRSET: /* rset -- reset state */
+ if (tTd(94, 100))
+ message("451 4.0.0 Test failure");
+ else
+ message("250 2.0.0 Reset state");
+ CLEAR_STATE(cmdbuf);
+ break;
+
+ case CMDVRFY: /* vrfy -- verify address */
+ case CMDEXPN: /* expn -- expand address */
+ vrfy = c->cmd_code == CMDVRFY;
+ DELAY_CONN(vrfy ? "VRFY" : "EXPN");
+ if (tempfail)
+ {
+ if (LogLevel > 9)
+ sm_syslog(LOG_INFO, e->e_id,
+ "SMTP %s command (%.100s) from %s tempfailed (due to previous checks)",
+ vrfy ? "VRFY" : "EXPN",
+ p, CurSmtpClient);
+
+ /* RFC 821 doesn't allow 4xy reply code */
+ usrerr("550 5.7.1 Please try again later");
+ break;
+ }
+ wt = checksmtpattack(&n_verifies, MAXVRFYCOMMANDS,
+ false, vrfy ? "VRFY" : "EXPN", e);
+ STOP_IF_ATTACK(wt);
+ previous = curtime();
+ if ((vrfy && bitset(PRIV_NOVRFY, PrivacyFlags)) ||
+ (!vrfy && !bitset(SRV_OFFER_EXPN, features)))
+ {
+ if (vrfy)
+ message("252 2.5.2 Cannot VRFY user; try RCPT to attempt delivery (or try finger)");
+ else
+ message("502 5.7.0 Sorry, we do not allow this operation");
+ if (LogLevel > 5)
+ sm_syslog(LOG_INFO, e->e_id,
+ "%s: %s [rejected]",
+ CurSmtpClient,
+ shortenstring(inp, MAXSHORTSTR));
+ break;
+ }
+ else if (!gothello &&
+ bitset(vrfy ? PRIV_NEEDVRFYHELO : PRIV_NEEDEXPNHELO,
+ PrivacyFlags))
+ {
+ usrerr("503 5.0.0 I demand that you introduce yourself first");
+ break;
+ }
+ if (Errors > 0)
+ break;
+ if (LogLevel > 5)
+ sm_syslog(LOG_INFO, e->e_id, "%s: %s",
+ CurSmtpClient,
+ shortenstring(inp, MAXSHORTSTR));
+ SM_TRY
+ {
+ QuickAbort = true;
+ vrfyqueue = NULL;
+ if (vrfy)
+ e->e_flags |= EF_VRFYONLY;
+ while (*p != '\0' && isascii(*p) && isspace(*p))
+ p++;
+ if (*p == '\0')
+ {
+ usrerr("501 5.5.2 Argument required");
+ }
+ else
+ {
+ /* do config file checking of the address */
+ if (rscheck(vrfy ? "check_vrfy" : "check_expn",
+ p, NULL, e, RSF_RMCOMM,
+ 3, NULL, NOQID) != EX_OK ||
+ Errors > 0)
+ sm_exc_raisenew_x(&EtypeQuickAbort, 1);
+ (void) sendtolist(p, NULLADDR, &vrfyqueue, 0, e);
+ }
+ if (wt > 0)
+ {
+ time_t t;
+
+ t = wt - (curtime() - previous);
+ if (t > 0)
+ (void) sleep(t);
+ }
+ if (Errors > 0)
+ sm_exc_raisenew_x(&EtypeQuickAbort, 1);
+ if (vrfyqueue == NULL)
+ {
+ usrerr("554 5.5.2 Nothing to %s", vrfy ? "VRFY" : "EXPN");
+ }
+ while (vrfyqueue != NULL)
+ {
+ if (!QS_IS_UNDELIVERED(vrfyqueue->q_state))
+ {
+ vrfyqueue = vrfyqueue->q_next;
+ continue;
+ }
+
+ /* see if there is more in the vrfy list */
+ a = vrfyqueue;
+ while ((a = a->q_next) != NULL &&
+ (!QS_IS_UNDELIVERED(a->q_state)))
+ continue;
+ printvrfyaddr(vrfyqueue, a == NULL, vrfy);
+ vrfyqueue = a;
+ }
+ }
+ SM_EXCEPT(exc, "[!F]*")
+ {
+ /*
+ ** An exception occurred while processing VRFY/EXPN
+ */
+
+ sm_exc_free(exc);
+ goto undo;
+ }
+ SM_END_TRY
+ break;
+
+ case CMDETRN: /* etrn -- force queue flush */
+ DELAY_CONN("ETRN");
+
+ /* Don't leak queue information via debug flags */
+ if (!bitset(SRV_OFFER_ETRN, features) || UseMSP ||
+ (RealUid != 0 && RealUid != TrustedUid &&
+ OpMode == MD_SMTP))
+ {
+ /* different message for MSA ? */
+ message("502 5.7.0 Sorry, we do not allow this operation");
+ if (LogLevel > 5)
+ sm_syslog(LOG_INFO, e->e_id,
+ "%s: %s [rejected]",
+ CurSmtpClient,
+ shortenstring(inp, MAXSHORTSTR));
+ break;
+ }
+ if (tempfail)
+ {
+ if (LogLevel > 9)
+ sm_syslog(LOG_INFO, e->e_id,
+ "SMTP ETRN command (%.100s) from %s tempfailed (due to previous checks)",
+ p, CurSmtpClient);
+ usrerr(MSG_TEMPFAIL);
+ break;
+ }
+
+ if (strlen(p) <= 0)
+ {
+ usrerr("500 5.5.2 Parameter required");
+ break;
+ }
+
+ /* crude way to avoid denial-of-service attacks */
+ STOP_IF_ATTACK(checksmtpattack(&n_etrn, MAXETRNCOMMANDS,
+ true, "ETRN", e));
+
+ /*
+ ** Do config file checking of the parameter.
+ ** Even though we have srv_features now, we still
+ ** need this ruleset because the former is called
+ ** when the connection has been established, while
+ ** this ruleset is called when the command is
+ ** actually issued and therefore has all information
+ ** available to make a decision.
+ */
+
+ if (rscheck("check_etrn", p, NULL, e,
+ RSF_RMCOMM, 3, NULL, NOQID) != EX_OK ||
+ Errors > 0)
+ break;
+
+ if (LogLevel > 5)
+ sm_syslog(LOG_INFO, e->e_id,
+ "%s: ETRN %s", CurSmtpClient,
+ shortenstring(p, MAXSHORTSTR));
+
+ id = p;
+ if (*id == '#')
+ {
+ int i, qgrp;
+
+ id++;
+ qgrp = name2qid(id);
+ if (!ISVALIDQGRP(qgrp))
+ {
+ usrerr("459 4.5.4 Queue %s unknown",
+ id);
+ break;
+ }
+ for (i = 0; i < NumQueue && Queue[i] != NULL;
+ i++)
+ Queue[i]->qg_nextrun = (time_t) -1;
+ Queue[qgrp]->qg_nextrun = 0;
+ ok = run_work_group(Queue[qgrp]->qg_wgrp,
+ RWG_FORK|RWG_FORCE);
+ if (ok && Errors == 0)
+ message("250 2.0.0 Queuing for queue group %s started", id);
+ break;
+ }
+
+ if (*id == '@')
+ id++;
+ else
+ *--id = '@';
+
+ new = (QUEUE_CHAR *) sm_malloc(sizeof(QUEUE_CHAR));
+ if (new == NULL)
+ {
+ syserr("500 5.5.0 ETRN out of memory");
+ break;
+ }
+ new->queue_match = id;
+ new->queue_negate = false;
+ new->queue_next = NULL;
+ QueueLimitRecipient = new;
+ ok = runqueue(true, false, false, true);
+ sm_free(QueueLimitRecipient); /* XXX */
+ QueueLimitRecipient = NULL;
+ if (ok && Errors == 0)
+ message("250 2.0.0 Queuing for node %s started", p);
+ break;
+
+ case CMDHELP: /* help -- give user info */
+ DELAY_CONN("HELP");
+ help(p, e);
+ break;
+
+ case CMDNOOP: /* noop -- do nothing */
+ DELAY_CONN("NOOP");
+ STOP_IF_ATTACK(checksmtpattack(&n_noop, MAXNOOPCOMMANDS,
+ true, "NOOP", e));
+ message("250 2.0.0 OK");
+ break;
+
+ case CMDQUIT: /* quit -- leave mail */
+ message("221 2.0.0 %s closing connection", MyHostName);
+#if PIPELINING
+ (void) sm_io_flush(OutChannel, SM_TIME_DEFAULT);
+#endif /* PIPELINING */
+
+ if (smtp.sm_nrcpts > 0)
+ logundelrcpts(e, "aborted by sender", 9, false);
+
+ /* arrange to ignore any current send list */
+ e->e_sendqueue = NULL;
+
+#if STARTTLS
+ /* shutdown TLS connection */
+ if (tls_active)
+ {
+ (void) endtls(srv_ssl, "server");
+ tls_active = false;
+ }
+#endif /* STARTTLS */
+#if SASL
+ if (authenticating == SASL_IS_AUTH)
+ {
+ sasl_dispose(&conn);
+ authenticating = SASL_NOT_AUTH;
+ /* XXX sasl_done(); this is a child */
+ }
+#endif /* SASL */
+
+doquit:
+ /* avoid future 050 messages */
+ disconnect(1, e);
+
+#if MILTER
+ /* close out milter filters */
+ milter_quit(e);
+#endif /* MILTER */
+
+ if (LogLevel > 4 && bitset(EF_LOGSENDER, e->e_flags))
+ logsender(e, NULL);
+ e->e_flags &= ~EF_LOGSENDER;
+
+ if (lognullconnection && LogLevel > 5 &&
+ nullserver == NULL)
+ {
+ char *d;
+
+ d = macvalue(macid("{daemon_name}"), e);
+ if (d == NULL)
+ d = "stdin";
+
+ /*
+ ** even though this id is "bogus", it makes
+ ** it simpler to "grep" related events, e.g.,
+ ** timeouts for the same connection.
+ */
+
+ sm_syslog(LOG_INFO, e->e_id,
+ "%s did not issue MAIL/EXPN/VRFY/ETRN during connection to %s",
+ CurSmtpClient, d);
+ }
+ if (tTd(93, 100))
+ {
+ /* return to handle next connection */
+ return;
+ }
+ finis(true, true, ExitStat);
+ /* NOTREACHED */
+
+ case CMDVERB: /* set verbose mode */
+ DELAY_CONN("VERB");
+ if (!bitset(SRV_OFFER_EXPN, features) ||
+ !bitset(SRV_OFFER_VERB, features))
+ {
+ /* this would give out the same info */
+ message("502 5.7.0 Verbose unavailable");
+ break;
+ }
+ STOP_IF_ATTACK(checksmtpattack(&n_noop, MAXNOOPCOMMANDS,
+ true, "VERB", e));
+ Verbose = 1;
+ set_delivery_mode(SM_DELIVER, e);
+ message("250 2.0.0 Verbose mode");
+ break;
+
+#if SMTPDEBUG
+ case CMDDBGQSHOW: /* show queues */
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Send Queue=");
+ printaddr(smioout, e->e_sendqueue, true);
+ break;
+
+ case CMDDBGDEBUG: /* set debug mode */
+ tTsetup(tTdvect, sizeof tTdvect, "0-99.1");
+ tTflag(p);
+ message("200 2.0.0 Debug set");
+ break;
+
+#else /* SMTPDEBUG */
+ case CMDDBGQSHOW: /* show queues */
+ case CMDDBGDEBUG: /* set debug mode */
+#endif /* SMTPDEBUG */
+ case CMDLOGBOGUS: /* bogus command */
+ DELAY_CONN("Bogus");
+ if (LogLevel > 0)
+ sm_syslog(LOG_CRIT, e->e_id,
+ "\"%s\" command from %s (%.100s)",
+ c->cmd_name, CurSmtpClient,
+ anynet_ntoa(&RealHostAddr));
+ /* FALLTHROUGH */
+
+ case CMDERROR: /* unknown command */
+#if MAXBADCOMMANDS > 0
+ if (++n_badcmds > MAXBADCOMMANDS)
+ {
+ stopattack:
+ message("421 4.7.0 %s Too many bad commands; closing connection",
+ MyHostName);
+
+ /* arrange to ignore any current send list */
+ e->e_sendqueue = NULL;
+ goto doquit;
+ }
+#endif /* MAXBADCOMMANDS > 0 */
+
+#if MILTER && SMFI_VERSION > 2
+ if (smtp.sm_milterlist && smtp.sm_milterize &&
+ !bitset(EF_DISCARD, e->e_flags))
+ {
+ char state;
+ char *response;
+
+ if (MilterLogLevel > 9)
+ sm_syslog(LOG_INFO, e->e_id,
+ "Sending \"%s\" to Milter", inp);
+ response = milter_unknown(inp, e, &state);
+ MILTER_REPLY("unknown");
+ if (state == SMFIR_REPLYCODE ||
+ state == SMFIR_REJECT ||
+ state == SMFIR_TEMPFAIL)
+ {
+ /* MILTER_REPLY already gave an error */
+ break;
+ }
+ }
+#endif /* MILTER && SMFI_VERSION > 2 */
+
+ usrerr("500 5.5.1 Command unrecognized: \"%s\"",
+ shortenstring(inp, MAXSHORTSTR));
+ break;
+
+ case CMDUNIMPL:
+ DELAY_CONN("Unimpl");
+ usrerr("502 5.5.1 Command not implemented: \"%s\"",
+ shortenstring(inp, MAXSHORTSTR));
+ break;
+
+ default:
+ DELAY_CONN("default");
+ errno = 0;
+ syserr("500 5.5.0 smtp: unknown code %d", c->cmd_code);
+ break;
+ }
+#if SASL
+ }
+#endif /* SASL */
+ }
+ SM_EXCEPT(exc, "[!F]*")
+ {
+ /*
+ ** The only possible exception is "E:mta.quickabort".
+ ** There is nothing to do except fall through and loop.
+ */
+ }
+ SM_END_TRY
+ }
+}
+/*
+** SMTP_DATA -- implement the SMTP DATA command.
+**
+** Parameters:
+** smtp -- status of SMTP connection.
+** e -- envelope.
+**
+** Returns:
+** true iff SMTP session can continue.
+**
+** Side Effects:
+** possibly sends message.
+*/
+
+static bool
+smtp_data(smtp, e)
+ SMTP_T *smtp;
+ ENVELOPE *e;
+{
+#if MILTER
+ bool milteraccept;
+#endif /* MILTER */
+ bool aborting;
+ bool doublequeue;
+ ADDRESS *a;
+ ENVELOPE *ee;
+ char *id;
+ char *oldid;
+ char buf[32];
+
+ SmtpPhase = "server DATA";
+ if (!smtp->sm_gotmail)
+ {
+ usrerr("503 5.0.0 Need MAIL command");
+ return true;
+ }
+ else if (smtp->sm_nrcpts <= 0)
+ {
+ usrerr("503 5.0.0 Need RCPT (recipient)");
+ return true;
+ }
+ (void) sm_snprintf(buf, sizeof buf, "%u", smtp->sm_nrcpts);
+ if (rscheck("check_data", buf, NULL, e,
+ RSF_RMCOMM|RSF_UNSTRUCTURED|RSF_COUNT, 3, NULL,
+ e->e_id) != EX_OK)
+ return true;
+
+#if MILTER && SMFI_VERSION > 3
+ if (smtp->sm_milterlist && smtp->sm_milterize &&
+ !bitset(EF_DISCARD, e->e_flags))
+ {
+ char state;
+ char *response;
+ int savelogusrerrs = LogUsrErrs;
+
+ response = milter_data_cmd(e, &state);
+ switch (state)
+ {
+ case SMFIR_REPLYCODE:
+ if (MilterLogLevel > 3)
+ {
+ sm_syslog(LOG_INFO, e->e_id,
+ "Milter: cmd=data, reject=%s",
+ response);
+ LogUsrErrs = false;
+ }
+ usrerr(response);
+ if (strncmp(response, "421 ", 4) == 0)
+ {
+ e->e_sendqueue = NULL;
+ return false;
+ }
+ return true;
+
+ case SMFIR_REJECT:
+ if (MilterLogLevel > 3)
+ {
+ sm_syslog(LOG_INFO, e->e_id,
+ "Milter: cmd=data, reject=550 5.7.1 Command rejected");
+ LogUsrErrs = false;
+ }
+ usrerr("550 5.7.1 Command rejected");
+ return true;
+
+ case SMFIR_DISCARD:
+ if (MilterLogLevel > 3)
+ sm_syslog(LOG_INFO, e->e_id,
+ "Milter: cmd=data, discard");
+ e->e_flags |= EF_DISCARD;
+ break;
+
+ case SMFIR_TEMPFAIL:
+ if (MilterLogLevel > 3)
+ {
+ sm_syslog(LOG_INFO, e->e_id,
+ "Milter: cmd=data, reject=%s",
+ MSG_TEMPFAIL);
+ LogUsrErrs = false;
+ }
+ usrerr(MSG_TEMPFAIL);
+ return true;
+ }
+ LogUsrErrs = savelogusrerrs;
+ if (response != NULL)
+ sm_free(response); /* XXX */
+ }
+#endif /* MILTER && SMFI_VERSION > 3 */
+
+ /* put back discard bit */
+ if (smtp->sm_discard)
+ e->e_flags |= EF_DISCARD;
+
+ /* check to see if we need to re-expand aliases */
+ /* also reset QS_BADADDR on already-diagnosted addrs */
+ doublequeue = false;
+ for (a = e->e_sendqueue; a != NULL; a = a->q_next)
+ {
+ if (QS_IS_VERIFIED(a->q_state) &&
+ !bitset(EF_DISCARD, e->e_flags))
+ {
+ /* need to re-expand aliases */
+ doublequeue = true;
+ }
+ if (QS_IS_BADADDR(a->q_state))
+ {
+ /* make this "go away" */
+ a->q_state = QS_DONTSEND;
+ }
+ }
+
+ /* collect the text of the message */
+ SmtpPhase = "collect";
+ buffer_errors();
+
+ collect(InChannel, true, NULL, e, true);
+
+ /* redefine message size */
+ (void) sm_snprintf(buf, sizeof buf, "%ld", e->e_msgsize);
+ macdefine(&e->e_macro, A_TEMP, macid("{msg_size}"), buf);
+
+#if _FFR_CHECK_EOM
+ /* rscheck() will set Errors or EF_DISCARD if it trips */
+ (void) rscheck("check_eom", buf, NULL, e, RSF_UNSTRUCTURED|RSF_COUNT,
+ 3, NULL, e->e_id);
+#endif /* _FFR_CHECK_EOM */
+
+#if MILTER
+ milteraccept = true;
+ if (smtp->sm_milterlist && smtp->sm_milterize &&
+ Errors <= 0 &&
+ !bitset(EF_DISCARD, e->e_flags))
+ {
+ char state;
+ char *response;
+
+ response = milter_data(e, &state);
+ switch (state)
+ {
+ case SMFIR_REPLYCODE:
+ if (MilterLogLevel > 3)
+ sm_syslog(LOG_INFO, e->e_id,
+ "Milter: data, reject=%s",
+ response);
+ milteraccept = false;
+ usrerr(response);
+ break;
+
+ case SMFIR_REJECT:
+ milteraccept = false;
+ if (MilterLogLevel > 3)
+ sm_syslog(LOG_INFO, e->e_id,
+ "Milter: data, reject=554 5.7.1 Command rejected");
+ usrerr("554 5.7.1 Command rejected");
+ break;
+
+ case SMFIR_DISCARD:
+ if (MilterLogLevel > 3)
+ sm_syslog(LOG_INFO, e->e_id,
+ "Milter: data, discard");
+ milteraccept = false;
+ e->e_flags |= EF_DISCARD;
+ break;
+
+ case SMFIR_TEMPFAIL:
+ if (MilterLogLevel > 3)
+ sm_syslog(LOG_INFO, e->e_id,
+ "Milter: data, reject=%s",
+ MSG_TEMPFAIL);
+ milteraccept = false;
+ usrerr(MSG_TEMPFAIL);
+ break;
+ }
+ if (response != NULL)
+ sm_free(response);
+ }
+
+ /* Milter may have changed message size */
+ (void) sm_snprintf(buf, sizeof buf, "%ld", e->e_msgsize);
+ macdefine(&e->e_macro, A_TEMP, macid("{msg_size}"), buf);
+
+ /* abort message filters that didn't get the body & log msg is OK */
+ if (smtp->sm_milterlist && smtp->sm_milterize)
+ {
+ milter_abort(e);
+ if (milteraccept && MilterLogLevel > 9)
+ sm_syslog(LOG_INFO, e->e_id, "Milter accept: message");
+ }
+
+ /*
+ ** If SuperSafe is SAFE_REALLY_POSTMILTER, and we don't have milter or
+ ** milter accepted message, sync it now
+ **
+ ** XXX This is almost a copy of the code in collect(): put it into
+ ** a function that is called from both places?
+ */
+
+ if (milteraccept && SuperSafe == SAFE_REALLY_POSTMILTER)
+ {
+ int afd;
+ SM_FILE_T *volatile df;
+ char *dfname;
+
+ df = e->e_dfp;
+ dfname = queuename(e, DATAFL_LETTER);
+ if (sm_io_setinfo(df, SM_BF_COMMIT, NULL) < 0
+ && errno != EINVAL)
+ {
+ int save_errno;
+
+ save_errno = errno;
+ if (save_errno == EEXIST)
+ {
+ struct stat st;
+ int dfd;
+
+ if (stat(dfname, &st) < 0)
+ st.st_size = -1;
+ errno = EEXIST;
+ syserr("@collect: bfcommit(%s): already on disk, size=%ld",
+ dfname, (long) st.st_size);
+ dfd = sm_io_getinfo(df, SM_IO_WHAT_FD, NULL);
+ if (dfd >= 0)
+ dumpfd(dfd, true, true);
+ }
+ errno = save_errno;
+ dferror(df, "bfcommit", e);
+ flush_errors(true);
+ finis(save_errno != EEXIST, true, ExitStat);
+ }
+ else if ((afd = sm_io_getinfo(df, SM_IO_WHAT_FD, NULL)) < 0)
+ {
+ dferror(df, "sm_io_getinfo", e);
+ flush_errors(true);
+ finis(true, true, ExitStat);
+ /* NOTREACHED */
+ }
+ else if (fsync(afd) < 0)
+ {
+ dferror(df, "fsync", e);
+ flush_errors(true);
+ finis(true, true, ExitStat);
+ /* NOTREACHED */
+ }
+ else if (sm_io_close(df, SM_TIME_DEFAULT) < 0)
+ {
+ dferror(df, "sm_io_close", e);
+ flush_errors(true);
+ finis(true, true, ExitStat);
+ /* NOTREACHED */
+ }
+
+ /* Now reopen the df file */
+ e->e_dfp = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, dfname,
+ SM_IO_RDONLY, NULL);
+ if (e->e_dfp == NULL)
+ {
+ /* we haven't acked receipt yet, so just chuck this */
+ syserr("@Cannot reopen %s", dfname);
+ finis(true, true, ExitStat);
+ /* NOTREACHED */
+ }
+ }
+#endif /* MILTER */
+
+ /* Check if quarantining stats should be updated */
+ if (e->e_quarmsg != NULL)
+ markstats(e, NULL, STATS_QUARANTINE);
+
+ /*
+ ** If a header/body check (header checks or milter)
+ ** set EF_DISCARD, don't queueup the message --
+ ** that would lose the EF_DISCARD bit and deliver
+ ** the message.
+ */
+
+ if (bitset(EF_DISCARD, e->e_flags))
+ doublequeue = false;
+
+ aborting = Errors > 0;
+ if (!(aborting || bitset(EF_DISCARD, e->e_flags)) &&
+ (QueueMode == QM_QUARANTINE || e->e_quarmsg == NULL) &&
+ !split_by_recipient(e))
+ aborting = bitset(EF_FATALERRS, e->e_flags);
+
+ if (aborting)
+ {
+ /* Log who the mail would have gone to */
+ logundelrcpts(e, e->e_message, 8, false);
+ flush_errors(true);
+ buffer_errors();
+ goto abortmessage;
+ }
+
+ /* from now on, we have to operate silently */
+ buffer_errors();
+
+#if 0
+ /*
+ ** Clear message, it may contain an error from the SMTP dialogue.
+ ** This error must not show up in the queue.
+ ** Some error message should show up, e.g., alias database
+ ** not available, but others shouldn't, e.g., from check_rcpt.
+ */
+
+ e->e_message = NULL;
+#endif /* 0 */
+
+ /*
+ ** Arrange to send to everyone.
+ ** If sending to multiple people, mail back
+ ** errors rather than reporting directly.
+ ** In any case, don't mail back errors for
+ ** anything that has happened up to
+ ** now (the other end will do this).
+ ** Truncate our transcript -- the mail has gotten
+ ** to us successfully, and if we have
+ ** to mail this back, it will be easier
+ ** on the reader.
+ ** Then send to everyone.
+ ** Finally give a reply code. If an error has
+ ** already been given, don't mail a
+ ** message back.
+ ** We goose error returns by clearing error bit.
+ */
+
+ SmtpPhase = "delivery";
+ (void) sm_io_setinfo(e->e_xfp, SM_BF_TRUNCATE, NULL);
+ id = e->e_id;
+
+#if NAMED_BIND
+ _res.retry = TimeOuts.res_retry[RES_TO_FIRST];
+ _res.retrans = TimeOuts.res_retrans[RES_TO_FIRST];
+#endif /* NAMED_BIND */
+
+ for (ee = e; ee != NULL; ee = ee->e_sibling)
+ {
+ /* make sure we actually do delivery */
+ ee->e_flags &= ~EF_CLRQUEUE;
+
+ /* from now on, operate silently */
+ ee->e_errormode = EM_MAIL;
+
+ if (doublequeue)
+ {
+ /* make sure it is in the queue */
+ queueup(ee, false, true);
+ }
+ else
+ {
+ /* send to all recipients */
+ sendall(ee, SM_DEFAULT);
+ }
+ ee->e_to = NULL;
+ }
+
+ /* put back id for SMTP logging in putoutmsg() */
+ oldid = CurEnv->e_id;
+ CurEnv->e_id = id;
+
+ /* issue success message */
+ message("250 2.0.0 %s Message accepted for delivery", id);
+ CurEnv->e_id = oldid;
+
+ /* if we just queued, poke it */
+ if (doublequeue)
+ {
+ bool anything_to_send = false;
+
+ sm_getla();
+ for (ee = e; ee != NULL; ee = ee->e_sibling)
+ {
+ if (WILL_BE_QUEUED(ee->e_sendmode))
+ continue;
+ if (shouldqueue(ee->e_msgpriority, ee->e_ctime))
+ {
+ ee->e_sendmode = SM_QUEUE;
+ continue;
+ }
+ else if (QueueMode != QM_QUARANTINE &&
+ ee->e_quarmsg != NULL)
+ {
+ ee->e_sendmode = SM_QUEUE;
+ continue;
+ }
+ anything_to_send = true;
+
+ /* close all the queue files */
+ closexscript(ee);
+ if (ee->e_dfp != NULL)
+ {
+ (void) sm_io_close(ee->e_dfp, SM_TIME_DEFAULT);
+ ee->e_dfp = NULL;
+ }
+ unlockqueue(ee);
+ }
+ if (anything_to_send)
+ {
+#if PIPELINING
+ /*
+ ** XXX if we don't do this, we get 250 twice
+ ** because it is also flushed in the child.
+ */
+
+ (void) sm_io_flush(OutChannel, SM_TIME_DEFAULT);
+#endif /* PIPELINING */
+ (void) doworklist(e, true, true);
+ }
+ }
+
+ abortmessage:
+ if (LogLevel > 4 && bitset(EF_LOGSENDER, e->e_flags))
+ logsender(e, NULL);
+ e->e_flags &= ~EF_LOGSENDER;
+
+ /* clean up a bit */
+ smtp->sm_gotmail = false;
+
+ /*
+ ** Call dropenvelope if and only if the envelope is *not*
+ ** being processed by the child process forked by doworklist().
+ */
+
+ if (aborting || bitset(EF_DISCARD, e->e_flags))
+ dropenvelope(e, true, false);
+ else
+ {
+ for (ee = e; ee != NULL; ee = ee->e_sibling)
+ {
+ if (!doublequeue &&
+ QueueMode != QM_QUARANTINE &&
+ ee->e_quarmsg != NULL)
+ {
+ dropenvelope(ee, true, false);
+ continue;
+ }
+ if (WILL_BE_QUEUED(ee->e_sendmode))
+ dropenvelope(ee, true, false);
+ }
+ }
+ sm_rpool_free(e->e_rpool);
+
+ /*
+ ** At this point, e == &MainEnvelope, but if we did splitting,
+ ** then CurEnv may point to an envelope structure that was just
+ ** freed with the rpool. So reset CurEnv *before* calling
+ ** newenvelope.
+ */
+
+ CurEnv = e;
+ newenvelope(e, e, sm_rpool_new_x(NULL));
+ e->e_flags = BlankEnvelope.e_flags;
+
+ /* restore connection quarantining */
+ if (smtp->sm_quarmsg == NULL)
+ {
+ e->e_quarmsg = NULL;
+ macdefine(&e->e_macro, A_PERM, macid("{quarantine}"), "");
+ }
+ else
+ {
+ e->e_quarmsg = sm_rpool_strdup_x(e->e_rpool, smtp->sm_quarmsg);
+ macdefine(&e->e_macro, A_PERM,
+ macid("{quarantine}"), e->e_quarmsg);
+ }
+ return true;
+}
+/*
+** LOGUNDELRCPTS -- log undelivered (or all) recipients.
+**
+** Parameters:
+** e -- envelope.
+** msg -- message for Stat=
+** level -- log level.
+** all -- log all recipients.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** logs undelivered (or all) recipients
+*/
+
+void
+logundelrcpts(e, msg, level, all)
+ ENVELOPE *e;
+ char *msg;
+ int level;
+ bool all;
+{
+ ADDRESS *a;
+
+ if (LogLevel <= level || msg == NULL || *msg == '\0')
+ return;
+
+ /* Clear $h so relay= doesn't get mislogged by logdelivery() */
+ macdefine(&e->e_macro, A_PERM, 'h', NULL);
+
+ /* Log who the mail would have gone to */
+ for (a = e->e_sendqueue; a != NULL; a = a->q_next)
+ {
+ if (!QS_IS_UNDELIVERED(a->q_state) && !all)
+ continue;
+ e->e_to = a->q_paddr;
+ logdelivery(NULL, NULL, a->q_status, msg, NULL,
+ (time_t) 0, e);
+ }
+ e->e_to = NULL;
+}
+/*
+** CHECKSMTPATTACK -- check for denial-of-service attack by repetition
+**
+** Parameters:
+** pcounter -- pointer to a counter for this command.
+** maxcount -- maximum value for this counter before we
+** slow down.
+** waitnow -- sleep now (in this routine)?
+** cname -- command name for logging.
+** e -- the current envelope.
+**
+** Returns:
+** time to wait,
+** STOP_ATTACK if twice as many commands as allowed and
+** MaxChildren > 0.
+**
+** Side Effects:
+** Slows down if we seem to be under attack.
+*/
+
+static time_t
+checksmtpattack(pcounter, maxcount, waitnow, cname, e)
+ volatile unsigned int *pcounter;
+ unsigned int maxcount;
+ bool waitnow;
+ char *cname;
+ ENVELOPE *e;
+{
+ if (maxcount <= 0) /* no limit */
+ return (time_t) 0;
+
+ if (++(*pcounter) >= maxcount)
+ {
+ unsigned int shift;
+ time_t s;
+
+ if (*pcounter == maxcount && LogLevel > 5)
+ {
+ sm_syslog(LOG_INFO, e->e_id,
+ "%s: possible SMTP attack: command=%.40s, count=%u",
+ CurSmtpClient, cname, *pcounter);
+ }
+ shift = *pcounter - maxcount;
+ s = 1 << shift;
+ if (shift > MAXSHIFT || s >= MAXTIMEOUT || s <= 0)
+ s = MAXTIMEOUT;
+
+#define IS_ATTACK(s) ((MaxChildren > 0 && *pcounter >= maxcount * 2) \
+ ? STOP_ATTACK : (time_t) s)
+
+ /* sleep at least 1 second before returning */
+ (void) sleep(*pcounter / maxcount);
+ s -= *pcounter / maxcount;
+ if (s >= MAXTIMEOUT || s < 0)
+ s = MAXTIMEOUT;
+ if (waitnow && s > 0)
+ {
+ (void) sleep(s);
+ return IS_ATTACK(0);
+ }
+ return IS_ATTACK(s);
+ }
+ return (time_t) 0;
+}
+/*
+** SETUP_SMTPD_IO -- setup I/O fd correctly for the SMTP server
+**
+** Parameters:
+** none.
+**
+** Returns:
+** nothing.
+**
+** Side Effects:
+** may change I/O fd.
+*/
+
+static void
+setup_smtpd_io()
+{
+ int inchfd, outchfd, outfd;
+
+ inchfd = sm_io_getinfo(InChannel, SM_IO_WHAT_FD, NULL);
+ outchfd = sm_io_getinfo(OutChannel, SM_IO_WHAT_FD, NULL);
+ outfd = sm_io_getinfo(smioout, SM_IO_WHAT_FD, NULL);
+ if (outchfd != outfd)
+ {
+ /* arrange for debugging output to go to remote host */
+ (void) dup2(outchfd, outfd);
+ }
+
+ /*
+ ** if InChannel and OutChannel are stdin/stdout
+ ** and connected to ttys
+ ** and fcntl(STDIN, F_SETFL, O_NONBLOCKING) also changes STDOUT,
+ ** then "chain" them together.
+ */
+
+ if (inchfd == STDIN_FILENO && outchfd == STDOUT_FILENO &&
+ isatty(inchfd) && isatty(outchfd))
+ {
+ int inmode, outmode;
+
+ inmode = fcntl(inchfd, F_GETFL, 0);
+ if (inmode == -1)
+ {
+ if (LogLevel > 11)
+ sm_syslog(LOG_INFO, NOQID,
+ "fcntl(inchfd, F_GETFL) failed: %s",
+ sm_errstring(errno));
+ return;
+ }
+ outmode = fcntl(outchfd, F_GETFL, 0);
+ if (outmode == -1)
+ {
+ if (LogLevel > 11)
+ sm_syslog(LOG_INFO, NOQID,
+ "fcntl(outchfd, F_GETFL) failed: %s",
+ sm_errstring(errno));
+ return;
+ }
+ if (bitset(O_NONBLOCK, inmode) ||
+ bitset(O_NONBLOCK, outmode) ||
+ fcntl(inchfd, F_SETFL, inmode | O_NONBLOCK) == -1)
+ return;
+ outmode = fcntl(outchfd, F_GETFL, 0);
+ if (outmode != -1 && bitset(O_NONBLOCK, outmode))
+ {
+ /* changing InChannel also changes OutChannel */
+ sm_io_automode(OutChannel, InChannel);
+ if (tTd(97, 4) && LogLevel > 9)
+ sm_syslog(LOG_INFO, NOQID,
+ "set automode for I (%d)/O (%d) in SMTP server",
+ inchfd, outchfd);
+ }
+
+ /* undo change of inchfd */
+ (void) fcntl(inchfd, F_SETFL, inmode);
+ }
+}
+/*
+** SKIPWORD -- skip a fixed word.
+**
+** Parameters:
+** p -- place to start looking.
+** w -- word to skip.
+**
+** Returns:
+** p following w.
+** NULL on error.
+**
+** Side Effects:
+** clobbers the p data area.
+*/
+
+static char *
+skipword(p, w)
+ register char *volatile p;
+ char *w;
+{
+ register char *q;
+ char *firstp = p;
+
+ /* find beginning of word */
+ SKIP_SPACE(p);
+ q = p;
+
+ /* find end of word */
+ while (*p != '\0' && *p != ':' && !(isascii(*p) && isspace(*p)))
+ p++;
+ while (isascii(*p) && isspace(*p))
+ *p++ = '\0';
+ if (*p != ':')
+ {
+ syntax:
+ usrerr("501 5.5.2 Syntax error in parameters scanning \"%s\"",
+ shortenstring(firstp, MAXSHORTSTR));
+ return NULL;
+ }
+ *p++ = '\0';
+ SKIP_SPACE(p);
+
+ if (*p == '\0')
+ goto syntax;
+
+ /* see if the input word matches desired word */
+ if (sm_strcasecmp(q, w))
+ goto syntax;
+
+ return p;
+}
+/*
+** MAIL_ESMTP_ARGS -- process ESMTP arguments from MAIL line
+**
+** Parameters:
+** kp -- the parameter key.
+** vp -- the value of that parameter.
+** e -- the envelope.
+**
+** Returns:
+** none.
+*/
+
+static void
+mail_esmtp_args(kp, vp, e)
+ char *kp;
+ char *vp;
+ ENVELOPE *e;
+{
+ if (sm_strcasecmp(kp, "size") == 0)
+ {
+ if (vp == NULL)
+ {
+ usrerr("501 5.5.2 SIZE requires a value");
+ /* NOTREACHED */
+ }
+ macdefine(&e->e_macro, A_TEMP, macid("{msg_size}"), vp);
+ errno = 0;
+ e->e_msgsize = strtol(vp, (char **) NULL, 10);
+ if (e->e_msgsize == LONG_MAX && errno == ERANGE)
+ {
+ usrerr("552 5.2.3 Message size exceeds maximum value");
+ /* NOTREACHED */
+ }
+ if (e->e_msgsize < 0)
+ {
+ usrerr("552 5.2.3 Message size invalid");
+ /* NOTREACHED */
+ }
+ }
+ else if (sm_strcasecmp(kp, "body") == 0)
+ {
+ if (vp == NULL)
+ {
+ usrerr("501 5.5.2 BODY requires a value");
+ /* NOTREACHED */
+ }
+ else if (sm_strcasecmp(vp, "8bitmime") == 0)
+ {
+ SevenBitInput = false;
+ }
+ else if (sm_strcasecmp(vp, "7bit") == 0)
+ {
+ SevenBitInput = true;
+ }
+ else
+ {
+ usrerr("501 5.5.4 Unknown BODY type %s", vp);
+ /* NOTREACHED */
+ }
+ e->e_bodytype = sm_rpool_strdup_x(e->e_rpool, vp);
+ }
+ else if (sm_strcasecmp(kp, "envid") == 0)
+ {
+ if (bitset(PRIV_NORECEIPTS, PrivacyFlags))
+ {
+ usrerr("504 5.7.0 Sorry, ENVID not supported, we do not allow DSN");
+ /* NOTREACHED */
+ }
+ if (vp == NULL)
+ {
+ usrerr("501 5.5.2 ENVID requires a value");
+ /* NOTREACHED */
+ }
+ if (!xtextok(vp))
+ {
+ usrerr("501 5.5.4 Syntax error in ENVID parameter value");
+ /* NOTREACHED */
+ }
+ if (e->e_envid != NULL)
+ {
+ usrerr("501 5.5.0 Duplicate ENVID parameter");
+ /* NOTREACHED */
+ }
+ e->e_envid = sm_rpool_strdup_x(e->e_rpool, vp);
+ macdefine(&e->e_macro, A_PERM,
+ macid("{dsn_envid}"), e->e_envid);
+ }
+ else if (sm_strcasecmp(kp, "ret") == 0)
+ {
+ if (bitset(PRIV_NORECEIPTS, PrivacyFlags))
+ {
+ usrerr("504 5.7.0 Sorry, RET not supported, we do not allow DSN");
+ /* NOTREACHED */
+ }
+ if (vp == NULL)
+ {
+ usrerr("501 5.5.2 RET requires a value");
+ /* NOTREACHED */
+ }
+ if (bitset(EF_RET_PARAM, e->e_flags))
+ {
+ usrerr("501 5.5.0 Duplicate RET parameter");
+ /* NOTREACHED */
+ }
+ e->e_flags |= EF_RET_PARAM;
+ if (sm_strcasecmp(vp, "hdrs") == 0)
+ e->e_flags |= EF_NO_BODY_RETN;
+ else if (sm_strcasecmp(vp, "full") != 0)
+ {
+ usrerr("501 5.5.2 Bad argument \"%s\" to RET", vp);
+ /* NOTREACHED */
+ }
+ macdefine(&e->e_macro, A_TEMP, macid("{dsn_ret}"), vp);
+ }
+#if SASL
+ else if (sm_strcasecmp(kp, "auth") == 0)
+ {
+ int len;
+ char *q;
+ char *auth_param; /* the value of the AUTH=x */
+ bool saveQuickAbort = QuickAbort;
+ bool saveSuprErrs = SuprErrs;
+ bool saveExitStat = ExitStat;
+
+ if (vp == NULL)
+ {
+ usrerr("501 5.5.2 AUTH= requires a value");
+ /* NOTREACHED */
+ }
+ if (e->e_auth_param != NULL)
+ {
+ usrerr("501 5.5.0 Duplicate AUTH parameter");
+ /* NOTREACHED */
+ }
+ if ((q = strchr(vp, ' ')) != NULL)
+ len = q - vp + 1;
+ else
+ len = strlen(vp) + 1;
+ auth_param = xalloc(len);
+ (void) sm_strlcpy(auth_param, vp, len);
+ if (!xtextok(auth_param))
+ {
+ usrerr("501 5.5.4 Syntax error in AUTH parameter value");
+ /* just a warning? */
+ /* NOTREACHED */
+ }
+
+ /* XXX define this always or only if trusted? */
+ macdefine(&e->e_macro, A_TEMP, macid("{auth_author}"),
+ auth_param);
+
+ /*
+ ** call Strust_auth to find out whether
+ ** auth_param is acceptable (trusted)
+ ** we shouldn't trust it if not authenticated
+ ** (required by RFC, leave it to ruleset?)
+ */
+
+ SuprErrs = true;
+ QuickAbort = false;
+ if (strcmp(auth_param, "<>") != 0 &&
+ (rscheck("trust_auth", auth_param, NULL, e, RSF_RMCOMM,
+ 9, NULL, NOQID) != EX_OK || Errors > 0))
+ {
+ if (tTd(95, 8))
+ {
+ q = e->e_auth_param;
+ sm_dprintf("auth=\"%.100s\" not trusted user=\"%.100s\"\n",
+ auth_param, (q == NULL) ? "" : q);
+ }
+
+ /* not trusted */
+ e->e_auth_param = "<>";
+# if _FFR_AUTH_PASSING
+ macdefine(&BlankEnvelope.e_macro, A_PERM,
+ macid("{auth_author}"), NULL);
+# endif /* _FFR_AUTH_PASSING */
+ }
+ else
+ {
+ if (tTd(95, 8))
+ sm_dprintf("auth=\"%.100s\" trusted\n", auth_param);
+ e->e_auth_param = sm_rpool_strdup_x(e->e_rpool,
+ auth_param);
+ }
+ sm_free(auth_param); /* XXX */
+
+ /* reset values */
+ Errors = 0;
+ QuickAbort = saveQuickAbort;
+ SuprErrs = saveSuprErrs;
+ ExitStat = saveExitStat;
+ }
+#endif /* SASL */
+#define PRTCHAR(c) ((isascii(c) && isprint(c)) ? (c) : '?')
+
+ /*
+ ** "by" is only accepted if DeliverByMin >= 0.
+ ** We maybe could add this to the list of server_features.
+ */
+
+ else if (sm_strcasecmp(kp, "by") == 0 && DeliverByMin >= 0)
+ {
+ char *s;
+
+ if (vp == NULL)
+ {
+ usrerr("501 5.5.2 BY= requires a value");
+ /* NOTREACHED */
+ }
+ errno = 0;
+ e->e_deliver_by = strtol(vp, &s, 10);
+ if (e->e_deliver_by == LONG_MIN ||
+ e->e_deliver_by == LONG_MAX ||
+ e->e_deliver_by > 999999999l ||
+ e->e_deliver_by < -999999999l)
+ {
+ usrerr("501 5.5.2 BY=%s out of range", vp);
+ /* NOTREACHED */
+ }
+ if (s == NULL || *s != ';')
+ {
+ usrerr("501 5.5.2 BY= missing ';'");
+ /* NOTREACHED */
+ }
+ e->e_dlvr_flag = 0;
+ ++s; /* XXX: spaces allowed? */
+ SKIP_SPACE(s);
+ switch (tolower(*s))
+ {
+ case 'n':
+ e->e_dlvr_flag = DLVR_NOTIFY;
+ break;
+ case 'r':
+ e->e_dlvr_flag = DLVR_RETURN;
+ if (e->e_deliver_by <= 0)
+ {
+ usrerr("501 5.5.4 mode R requires BY time > 0");
+ /* NOTREACHED */
+ }
+ if (DeliverByMin > 0 && e->e_deliver_by > 0 &&
+ e->e_deliver_by < DeliverByMin)
+ {
+ usrerr("555 5.5.2 time %ld less than %ld",
+ e->e_deliver_by, (long) DeliverByMin);
+ /* NOTREACHED */
+ }
+ break;
+ default:
+ usrerr("501 5.5.2 illegal by-mode '%c'", PRTCHAR(*s));
+ /* NOTREACHED */
+ }
+ ++s; /* XXX: spaces allowed? */
+ SKIP_SPACE(s);
+ switch (tolower(*s))
+ {
+ case 't':
+ e->e_dlvr_flag |= DLVR_TRACE;
+ break;
+ case '\0':
+ break;
+ default:
+ usrerr("501 5.5.2 illegal by-trace '%c'", PRTCHAR(*s));
+ /* NOTREACHED */
+ }
+
+ /* XXX: check whether more characters follow? */
+ }
+ else
+ {
+ usrerr("555 5.5.4 %s parameter unrecognized", kp);
+ /* NOTREACHED */
+ }
+}
+/*
+** RCPT_ESMTP_ARGS -- process ESMTP arguments from RCPT line
+**
+** Parameters:
+** a -- the address corresponding to the To: parameter.
+** kp -- the parameter key.
+** vp -- the value of that parameter.
+** e -- the envelope.
+**
+** Returns:
+** none.
+*/
+
+static void
+rcpt_esmtp_args(a, kp, vp, e)
+ ADDRESS *a;
+ char *kp;
+ char *vp;
+ ENVELOPE *e;
+{
+ if (sm_strcasecmp(kp, "notify") == 0)
+ {
+ char *p;
+
+ if (bitset(PRIV_NORECEIPTS, PrivacyFlags))
+ {
+ usrerr("504 5.7.0 Sorry, NOTIFY not supported, we do not allow DSN");
+ /* NOTREACHED */
+ }
+ if (vp == NULL)
+ {
+ usrerr("501 5.5.2 NOTIFY requires a value");
+ /* NOTREACHED */
+ }
+ a->q_flags &= ~(QPINGONSUCCESS|QPINGONFAILURE|QPINGONDELAY);
+ a->q_flags |= QHASNOTIFY;
+ macdefine(&e->e_macro, A_TEMP, macid("{dsn_notify}"), vp);
+
+ if (sm_strcasecmp(vp, "never") == 0)
+ return;
+ for (p = vp; p != NULL; vp = p)
+ {
+ char *s;
+
+ s = p = strchr(p, ',');
+ if (p != NULL)
+ *p++ = '\0';
+ if (sm_strcasecmp(vp, "success") == 0)
+ a->q_flags |= QPINGONSUCCESS;
+ else if (sm_strcasecmp(vp, "failure") == 0)
+ a->q_flags |= QPINGONFAILURE;
+ else if (sm_strcasecmp(vp, "delay") == 0)
+ a->q_flags |= QPINGONDELAY;
+ else
+ {
+ usrerr("501 5.5.4 Bad argument \"%s\" to NOTIFY",
+ vp);
+ /* NOTREACHED */
+ }
+ if (s != NULL)
+ *s = ',';
+ }
+ }
+ else if (sm_strcasecmp(kp, "orcpt") == 0)
+ {
+ if (bitset(PRIV_NORECEIPTS, PrivacyFlags))
+ {
+ usrerr("504 5.7.0 Sorry, ORCPT not supported, we do not allow DSN");
+ /* NOTREACHED */
+ }
+ if (vp == NULL)
+ {
+ usrerr("501 5.5.2 ORCPT requires a value");
+ /* NOTREACHED */
+ }
+ if (strchr(vp, ';') == NULL || !xtextok(vp))
+ {
+ usrerr("501 5.5.4 Syntax error in ORCPT parameter value");
+ /* NOTREACHED */
+ }
+ if (a->q_orcpt != NULL)
+ {
+ usrerr("501 5.5.0 Duplicate ORCPT parameter");
+ /* NOTREACHED */
+ }
+ a->q_orcpt = sm_rpool_strdup_x(e->e_rpool, vp);
+ }
+ else
+ {
+ usrerr("555 5.5.4 %s parameter unrecognized", kp);
+ /* NOTREACHED */
+ }
+}
+/*
+** PRINTVRFYADDR -- print an entry in the verify queue
+**
+** Parameters:
+** a -- the address to print.
+** last -- set if this is the last one.
+** vrfy -- set if this is a VRFY command.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Prints the appropriate 250 codes.
+*/
+#define OFFF (3 + 1 + 5 + 1) /* offset in fmt: SMTP reply + enh. code */
+
+static void
+printvrfyaddr(a, last, vrfy)
+ register ADDRESS *a;
+ bool last;
+ bool vrfy;
+{
+ char fmtbuf[30];
+
+ if (vrfy && a->q_mailer != NULL &&
+ !bitnset(M_VRFY250, a->q_mailer->m_flags))
+ (void) sm_strlcpy(fmtbuf, "252", sizeof fmtbuf);
+ else
+ (void) sm_strlcpy(fmtbuf, "250", sizeof fmtbuf);
+ fmtbuf[3] = last ? ' ' : '-';
+ (void) sm_strlcpy(&fmtbuf[4], "2.1.5 ", sizeof fmtbuf - 4);
+ if (a->q_fullname == NULL)
+ {
+ if ((a->q_mailer == NULL ||
+ a->q_mailer->m_addrtype == NULL ||
+ sm_strcasecmp(a->q_mailer->m_addrtype, "rfc822") == 0) &&
+ strchr(a->q_user, '@') == NULL)
+ (void) sm_strlcpy(&fmtbuf[OFFF], "<%s@%s>",
+ sizeof fmtbuf - OFFF);
+ else
+ (void) sm_strlcpy(&fmtbuf[OFFF], "<%s>",
+ sizeof fmtbuf - OFFF);
+ message(fmtbuf, a->q_user, MyHostName);
+ }
+ else
+ {
+ if ((a->q_mailer == NULL ||
+ a->q_mailer->m_addrtype == NULL ||
+ sm_strcasecmp(a->q_mailer->m_addrtype, "rfc822") == 0) &&
+ strchr(a->q_user, '@') == NULL)
+ (void) sm_strlcpy(&fmtbuf[OFFF], "%s <%s@%s>",
+ sizeof fmtbuf - OFFF);
+ else
+ (void) sm_strlcpy(&fmtbuf[OFFF], "%s <%s>",
+ sizeof fmtbuf - OFFF);
+ message(fmtbuf, a->q_fullname, a->q_user, MyHostName);
+ }
+}
+
+#if SASL
+/*
+** SASLMECHS -- get list of possible AUTH mechanisms
+**
+** Parameters:
+** conn -- SASL connection info.
+** mechlist -- output parameter for list of mechanisms.
+**
+** Returns:
+** number of mechs.
+*/
+
+static int
+saslmechs(conn, mechlist)
+ sasl_conn_t *conn;
+ char **mechlist;
+{
+ int len, num, result;
+
+ /* "user" is currently unused */
+# if SASL >= 20000
+ result = sasl_listmech(conn, NULL,
+ "", " ", "", (const char **) mechlist,
+ (unsigned int *)&len, &num);
+# else /* SASL >= 20000 */
+ result = sasl_listmech(conn, "user", /* XXX */
+ "", " ", "", mechlist,
+ (unsigned int *)&len, (unsigned int *)&num);
+# endif /* SASL >= 20000 */
+ if (result != SASL_OK)
+ {
+ if (LogLevel > 9)
+ sm_syslog(LOG_WARNING, NOQID,
+ "AUTH error: listmech=%d, num=%d",
+ result, num);
+ num = 0;
+ }
+ if (num > 0)
+ {
+ if (LogLevel > 11)
+ sm_syslog(LOG_INFO, NOQID,
+ "AUTH: available mech=%s, allowed mech=%s",
+ *mechlist, AuthMechanisms);
+ *mechlist = intersect(AuthMechanisms, *mechlist, NULL);
+ }
+ else
+ {
+ *mechlist = NULL; /* be paranoid... */
+ if (result == SASL_OK && LogLevel > 9)
+ sm_syslog(LOG_WARNING, NOQID,
+ "AUTH warning: no mechanisms");
+ }
+ return num;
+}
+
+# if SASL >= 20000
+/*
+** PROXY_POLICY -- define proxy policy for AUTH
+**
+** Parameters:
+** conn -- unused.
+** context -- unused.
+** requested_user -- authorization identity.
+** rlen -- authorization identity length.
+** auth_identity -- authentication identity.
+** alen -- authentication identity length.
+** def_realm -- default user realm.
+** urlen -- user realm length.
+** propctx -- unused.
+**
+** Returns:
+** ok?
+**
+** Side Effects:
+** sets {auth_authen} macro.
+*/
+
+int
+proxy_policy(conn, context, requested_user, rlen, auth_identity, alen,
+ def_realm, urlen, propctx)
+ sasl_conn_t *conn;
+ void *context;
+ const char *requested_user;
+ unsigned rlen;
+ const char *auth_identity;
+ unsigned alen;
+ const char *def_realm;
+ unsigned urlen;
+ struct propctx *propctx;
+{
+ if (auth_identity == NULL)
+ return SASL_FAIL;
+
+ macdefine(&BlankEnvelope.e_macro, A_TEMP,
+ macid("{auth_authen}"), (char *) auth_identity);
+
+ return SASL_OK;
+}
+# else /* SASL >= 20000 */
+
+/*
+** PROXY_POLICY -- define proxy policy for AUTH
+**
+** Parameters:
+** context -- unused.
+** auth_identity -- authentication identity.
+** requested_user -- authorization identity.
+** user -- allowed user (output).
+** errstr -- possible error string (output).
+**
+** Returns:
+** ok?
+*/
+
+int
+proxy_policy(context, auth_identity, requested_user, user, errstr)
+ void *context;
+ const char *auth_identity;
+ const char *requested_user;
+ const char **user;
+ const char **errstr;
+{
+ if (user == NULL || auth_identity == NULL)
+ return SASL_FAIL;
+ *user = newstr(auth_identity);
+ return SASL_OK;
+}
+# endif /* SASL >= 20000 */
+#endif /* SASL */
+
+#if STARTTLS
+/*
+** INITSRVTLS -- initialize server side TLS
+**
+** Parameters:
+** tls_ok -- should tls initialization be done?
+**
+** Returns:
+** succeeded?
+**
+** Side Effects:
+** sets tls_ok_srv which is a static variable in this module.
+** Do NOT remove assignments to it!
+*/
+
+bool
+initsrvtls(tls_ok)
+ bool tls_ok;
+{
+ if (!tls_ok)
+ return false;
+
+ /* do NOT remove assignment */
+ tls_ok_srv = inittls(&srv_ctx, TLS_Srv_Opts, true, SrvCertFile,
+ SrvKeyFile, CACertPath, CACertFile, DHParams);
+ return tls_ok_srv;
+}
+#endif /* STARTTLS */
+/*
+** SRVFEATURES -- get features for SMTP server
+**
+** Parameters:
+** e -- envelope (should be session context).
+** clientname -- name of client.
+** features -- default features for this invocation.
+**
+** Returns:
+** server features.
+*/
+
+/* table with options: it uses just one character, how about strings? */
+static struct
+{
+ char srvf_opt;
+ unsigned int srvf_flag;
+} srv_feat_table[] =
+{
+ { 'A', SRV_OFFER_AUTH },
+ { 'B', SRV_OFFER_VERB },
+ { 'C', SRV_REQ_SEC },
+ { 'D', SRV_OFFER_DSN },
+ { 'E', SRV_OFFER_ETRN },
+ { 'L', SRV_REQ_AUTH },
+#if PIPELINING
+# if _FFR_NO_PIPE
+ { 'N', SRV_NO_PIPE },
+# endif /* _FFR_NO_PIPE */
+ { 'P', SRV_OFFER_PIPE },
+#endif /* PIPELINING */
+ { 'R', SRV_VRFY_CLT }, /* same as V; not documented */
+ { 'S', SRV_OFFER_TLS },
+/* { 'T', SRV_TMP_FAIL }, */
+ { 'V', SRV_VRFY_CLT },
+ { 'X', SRV_OFFER_EXPN },
+/* { 'Y', SRV_OFFER_VRFY }, */
+ { '\0', SRV_NONE }
+};
+
+static unsigned int
+srvfeatures(e, clientname, features)
+ ENVELOPE *e;
+ char *clientname;
+ unsigned int features;
+{
+ int r, i, j;
+ char **pvp, c, opt;
+ char pvpbuf[PSBUFSIZE];
+
+ pvp = NULL;
+ r = rscap("srv_features", clientname, "", e, &pvp, pvpbuf,
+ sizeof(pvpbuf));
+ if (r != EX_OK)
+ return features;
+ if (pvp == NULL || pvp[0] == NULL || (pvp[0][0] & 0377) != CANONNET)
+ return features;
+ if (pvp[1] != NULL && sm_strncasecmp(pvp[1], "temp", 4) == 0)
+ return SRV_TMP_FAIL;
+
+ /*
+ ** General rule (see sendmail.h, d_flags):
+ ** lower case: required/offered, upper case: Not required/available
+ **
+ ** Since we can change some features per daemon, we have both
+ ** cases here: turn on/off a feature.
+ */
+
+ for (i = 1; pvp[i] != NULL; i++)
+ {
+ c = pvp[i][0];
+ j = 0;
+ for (;;)
+ {
+ if ((opt = srv_feat_table[j].srvf_opt) == '\0')
+ {
+ if (LogLevel > 9)
+ sm_syslog(LOG_WARNING, e->e_id,
+ "srvfeatures: unknown feature %s",
+ pvp[i]);
+ break;
+ }
+ if (c == opt)
+ {
+ features &= ~(srv_feat_table[j].srvf_flag);
+ break;
+ }
+ if (c == tolower(opt))
+ {
+ features |= srv_feat_table[j].srvf_flag;
+ break;
+ }
+ ++j;
+ }
+ }
+ return features;
+}
+
+/*
+** HELP -- implement the HELP command.
+**
+** Parameters:
+** topic -- the topic we want help for.
+** e -- envelope.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** outputs the help file to message output.
+*/
+#define HELPVSTR "#vers "
+#define HELPVERSION 2
+
+void
+help(topic, e)
+ char *topic;
+ ENVELOPE *e;
+{
+ register SM_FILE_T *hf;
+ register char *p;
+ int len;
+ bool noinfo;
+ bool first = true;
+ long sff = SFF_OPENASROOT|SFF_REGONLY;
+ char buf[MAXLINE];
+ char inp[MAXLINE];
+ static int foundvers = -1;
+ extern char Version[];
+
+ if (DontLockReadFiles)
+ sff |= SFF_NOLOCK;
+ if (!bitnset(DBS_HELPFILEINUNSAFEDIRPATH, DontBlameSendmail))
+ sff |= SFF_SAFEDIRPATH;
+
+ if (HelpFile == NULL ||
+ (hf = safefopen(HelpFile, O_RDONLY, 0444, sff)) == NULL)
+ {
+ /* no help */
+ errno = 0;
+ message("502 5.3.0 Sendmail %s -- HELP not implemented",
+ Version);
+ return;
+ }
+
+ if (topic == NULL || *topic == '\0')
+ {
+ topic = "smtp";
+ noinfo = false;
+ }
+ else
+ {
+ makelower(topic);
+ noinfo = true;
+ }
+
+ len = strlen(topic);
+
+ while (sm_io_fgets(hf, SM_TIME_DEFAULT, buf, sizeof buf) != NULL)
+ {
+ if (buf[0] == '#')
+ {
+ if (foundvers < 0 &&
+ strncmp(buf, HELPVSTR, strlen(HELPVSTR)) == 0)
+ {
+ int h;
+
+ if (sm_io_sscanf(buf + strlen(HELPVSTR), "%d",
+ &h) == 1)
+ foundvers = h;
+ }
+ continue;
+ }
+ if (strncmp(buf, topic, len) == 0)
+ {
+ if (first)
+ {
+ first = false;
+
+ /* print version if no/old vers# in file */
+ if (foundvers < 2 && !noinfo)
+ message("214-2.0.0 This is Sendmail version %s", Version);
+ }
+ p = strpbrk(buf, " \t");
+ if (p == NULL)
+ p = buf + strlen(buf) - 1;
+ else
+ p++;
+ fixcrlf(p, true);
+ if (foundvers >= 2)
+ {
+ translate_dollars(p);
+ expand(p, inp, sizeof inp, e);
+ p = inp;
+ }
+ message("214-2.0.0 %s", p);
+ noinfo = false;
+ }
+ }
+
+ if (noinfo)
+ message("504 5.3.0 HELP topic \"%.10s\" unknown", topic);
+ else
+ message("214 2.0.0 End of HELP info");
+
+ if (foundvers != 0 && foundvers < HELPVERSION)
+ {
+ if (LogLevel > 1)
+ sm_syslog(LOG_WARNING, e->e_id,
+ "%s too old (require version %d)",
+ HelpFile, HELPVERSION);
+
+ /* avoid log next time */
+ foundvers = 0;
+ }
+
+ (void) sm_io_close(hf, SM_TIME_DEFAULT);
+}
+
+#if SASL
+/*
+** RESET_SASLCONN -- reset SASL connection data
+**
+** Parameters:
+** conn -- SASL connection context
+** hostname -- host name
+** various connection data
+**
+** Returns:
+** SASL result
+*/
+
+static int
+reset_saslconn(sasl_conn_t **conn, char *hostname,
+# if SASL >= 20000
+ char *remoteip, char *localip,
+ char *auth_id, sasl_ssf_t * ext_ssf)
+# else /* SASL >= 20000 */
+ struct sockaddr_in *saddr_r, struct sockaddr_in *saddr_l,
+ sasl_external_properties_t * ext_ssf)
+# endif /* SASL >= 20000 */
+{
+ int result;
+
+ sasl_dispose(conn);
+# if SASL >= 20000
+ result = sasl_server_new("smtp", hostname, NULL, NULL, NULL,
+ NULL, 0, conn);
+# elif SASL > 10505
+ /* use empty realm: only works in SASL > 1.5.5 */
+ result = sasl_server_new("smtp", hostname, "", NULL, 0, conn);
+# else /* SASL >= 20000 */
+ /* use no realm -> realm is set to hostname by SASL lib */
+ result = sasl_server_new("smtp", hostname, NULL, NULL, 0,
+ conn);
+# endif /* SASL >= 20000 */
+ if (result != SASL_OK)
+ return result;
+
+# if SASL >= 20000
+# if NETINET || NETINET6
+ if (remoteip != NULL && *remoteip != '\0')
+ result = sasl_setprop(*conn, SASL_IPREMOTEPORT, remoteip);
+ if (result != SASL_OK)
+ return result;
+
+ if (localip != NULL && *localip != '\0')
+ result = sasl_setprop(*conn, SASL_IPLOCALPORT, localip);
+ if (result != SASL_OK)
+ return result;
+# endif /* NETINET || NETINET6 */
+
+ result = sasl_setprop(*conn, SASL_SSF_EXTERNAL, ext_ssf);
+ if (result != SASL_OK)
+ return result;
+
+ result = sasl_setprop(*conn, SASL_AUTH_EXTERNAL, auth_id);
+ if (result != SASL_OK)
+ return result;
+# else /* SASL >= 20000 */
+# if NETINET
+ if (saddr_r != NULL)
+ result = sasl_setprop(*conn, SASL_IP_REMOTE, saddr_r);
+ if (result != SASL_OK)
+ return result;
+
+ if (saddr_l != NULL)
+ result = sasl_setprop(*conn, SASL_IP_LOCAL, saddr_l);
+ if (result != SASL_OK)
+ return result;
+# endif /* NETINET */
+
+ result = sasl_setprop(*conn, SASL_SSF_EXTERNAL, ext_ssf);
+ if (result != SASL_OK)
+ return result;
+# endif /* SASL >= 20000 */
+ return SASL_OK;
+}
+#endif /* SASL */
diff --git a/usr/src/cmd/sendmail/src/stab.c b/usr/src/cmd/sendmail/src/stab.c
new file mode 100644
index 0000000000..511eb30f0c
--- /dev/null
+++ b/usr/src/cmd/sendmail/src/stab.c
@@ -0,0 +1,477 @@
+/*
+ * Copyright (c) 1998-2001, 2003 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sendmail.h>
+
+SM_RCSID("@(#)$Id: stab.c,v 8.88 2003/05/21 15:36:30 ca Exp $")
+
+/*
+** STAB -- manage the symbol table
+**
+** Parameters:
+** name -- the name to be looked up or inserted.
+** type -- the type of symbol.
+** op -- what to do:
+** ST_ENTER -- enter the name if not already present.
+** ST_FIND -- find it only.
+**
+** Returns:
+** pointer to a STAB entry for this name.
+** NULL if not found and not entered.
+**
+** Side Effects:
+** can update the symbol table.
+*/
+
+#define STABSIZE 2003
+#define SM_LOWER(c) ((isascii(c) && isupper(c)) ? tolower(c) : (c))
+
+static STAB *SymTab[STABSIZE];
+
+STAB *
+stab(name, type, op)
+ char *name;
+ int type;
+ int op;
+{
+ register STAB *s;
+ register STAB **ps;
+ register int hfunc;
+ register char *p;
+ int len;
+
+ if (tTd(36, 5))
+ sm_dprintf("STAB: %s %d ", name, type);
+
+ /*
+ ** Compute the hashing function
+ */
+
+ hfunc = type;
+ for (p = name; *p != '\0'; p++)
+ hfunc = ((hfunc << 1) ^ (SM_LOWER(*p) & 0377)) % STABSIZE;
+
+ if (tTd(36, 9))
+ sm_dprintf("(hfunc=%d) ", hfunc);
+
+ ps = &SymTab[hfunc];
+ if (type == ST_MACRO || type == ST_RULESET)
+ {
+ while ((s = *ps) != NULL &&
+ (s->s_symtype != type || strcmp(name, s->s_name)))
+ ps = &s->s_next;
+ }
+ else
+ {
+ while ((s = *ps) != NULL &&
+ (s->s_symtype != type || sm_strcasecmp(name, s->s_name)))
+ ps = &s->s_next;
+ }
+
+ /*
+ ** Dispose of the entry.
+ */
+
+ if (s != NULL || op == ST_FIND)
+ {
+ if (tTd(36, 5))
+ {
+ if (s == NULL)
+ sm_dprintf("not found\n");
+ else
+ {
+ long *lp = (long *) s->s_class;
+
+ sm_dprintf("type %d val %lx %lx %lx %lx\n",
+ s->s_symtype, lp[0], lp[1], lp[2], lp[3]);
+ }
+ }
+ return s;
+ }
+
+ /*
+ ** Make a new entry and link it in.
+ */
+
+ if (tTd(36, 5))
+ sm_dprintf("entered\n");
+
+ /* determine size of new entry */
+ switch (type)
+ {
+ case ST_CLASS:
+ len = sizeof s->s_class;
+ break;
+
+ case ST_ADDRESS:
+ len = sizeof s->s_address;
+ break;
+
+ case ST_MAILER:
+ len = sizeof s->s_mailer;
+ break;
+
+ case ST_ALIAS:
+ len = sizeof s->s_alias;
+ break;
+
+ case ST_MAPCLASS:
+ len = sizeof s->s_mapclass;
+ break;
+
+ case ST_MAP:
+ len = sizeof s->s_map;
+ break;
+
+ case ST_HOSTSIG:
+ len = sizeof s->s_hostsig;
+ break;
+
+ case ST_NAMECANON:
+ len = sizeof s->s_namecanon;
+ break;
+
+ case ST_MACRO:
+ len = sizeof s->s_macro;
+ break;
+
+ case ST_RULESET:
+ len = sizeof s->s_ruleset;
+ break;
+
+ case ST_HEADER:
+ len = sizeof s->s_header;
+ break;
+
+ case ST_SERVICE:
+ len = sizeof s->s_service;
+ break;
+
+#if LDAPMAP
+ case ST_LMAP:
+ len = sizeof s->s_lmap;
+ break;
+#endif /* LDAPMAP */
+
+#if MILTER
+ case ST_MILTER:
+ len = sizeof s->s_milter;
+ break;
+#endif /* MILTER */
+
+ case ST_QUEUE:
+ len = sizeof s->s_quegrp;
+ break;
+
+#if SOCKETMAP
+ case ST_SOCKETMAP:
+ len = sizeof s->s_socketmap;
+ break;
+#endif /* SOCKETMAP */
+
+ default:
+ /*
+ ** Each mailer has its own MCI stab entry:
+ **
+ ** s = stab(host, ST_MCI + m->m_mno, ST_ENTER);
+ **
+ ** Therefore, anything ST_MCI or larger is an s_mci.
+ */
+
+ if (type >= ST_MCI)
+ len = sizeof s->s_mci;
+ else
+ {
+ syserr("stab: unknown symbol type %d", type);
+ len = sizeof s->s_value;
+ }
+ break;
+ }
+ len += sizeof *s - sizeof s->s_value;
+
+ if (tTd(36, 15))
+ sm_dprintf("size of stab entry: %d\n", len);
+
+ /* make new entry */
+ s = (STAB *) sm_pmalloc_x(len);
+ memset((char *) s, '\0', len);
+ s->s_name = sm_pstrdup_x(name);
+ s->s_symtype = type;
+
+ /* link it in */
+ *ps = s;
+
+ /* set a default value for rulesets */
+ if (type == ST_RULESET)
+ s->s_ruleset = -1;
+
+ return s;
+}
+/*
+** STABAPPLY -- apply function to all stab entries
+**
+** Parameters:
+** func -- the function to apply. It will be given two
+** parameters (the stab entry and the arg).
+** arg -- an arbitrary argument, passed to func.
+**
+** Returns:
+** none.
+*/
+
+void
+stabapply(func, arg)
+ void (*func)__P((STAB *, int));
+ int arg;
+{
+ register STAB **shead;
+ register STAB *s;
+
+ for (shead = SymTab; shead < &SymTab[STABSIZE]; shead++)
+ {
+ for (s = *shead; s != NULL; s = s->s_next)
+ {
+ if (tTd(36, 90))
+ sm_dprintf("stabapply: trying %d/%s\n",
+ s->s_symtype, s->s_name);
+ func(s, arg);
+ }
+ }
+}
+/*
+** QUEUEUP_MACROS -- queueup the macros in a class
+**
+** Write the macros listed in the specified class into the
+** file referenced by qfp.
+**
+** Parameters:
+** class -- class ID.
+** qfp -- file pointer to the queue file.
+** e -- the envelope.
+**
+** Returns:
+** none.
+*/
+
+void
+queueup_macros(class, qfp, e)
+ int class;
+ SM_FILE_T *qfp;
+ ENVELOPE *e;
+{
+ register STAB **shead;
+ register STAB *s;
+
+ if (e == NULL)
+ return;
+
+ class = bitidx(class);
+ for (shead = SymTab; shead < &SymTab[STABSIZE]; shead++)
+ {
+ for (s = *shead; s != NULL; s = s->s_next)
+ {
+ int m;
+ char *p;
+
+ if (s->s_symtype == ST_CLASS &&
+ bitnset(bitidx(class), s->s_class) &&
+ (m = macid(s->s_name)) != 0 &&
+ (p = macvalue(m, e)) != NULL)
+ {
+ (void) sm_io_fprintf(qfp, SM_TIME_DEFAULT,
+ "$%s%s\n",
+ s->s_name,
+ denlstring(p, true,
+ false));
+ }
+ }
+ }
+}
+/*
+** COPY_CLASS -- copy class members from one class to another
+**
+** Parameters:
+** src -- source class.
+** dst -- destination class.
+**
+** Returns:
+** none.
+*/
+
+void
+copy_class(src, dst)
+ int src;
+ int dst;
+{
+ register STAB **shead;
+ register STAB *s;
+
+ src = bitidx(src);
+ dst = bitidx(dst);
+ for (shead = SymTab; shead < &SymTab[STABSIZE]; shead++)
+ {
+ for (s = *shead; s != NULL; s = s->s_next)
+ {
+ if (s->s_symtype == ST_CLASS &&
+ bitnset(src, s->s_class))
+ setbitn(dst, s->s_class);
+ }
+ }
+}
+
+/*
+** RMEXPSTAB -- remove expired entries from SymTab.
+**
+** These entries need to be removed in long-running processes,
+** e.g., persistent queue runners, to avoid consuming memory.
+**
+** XXX It might be useful to restrict the maximum TTL to avoid
+** caching data very long.
+**
+** Parameters:
+** none.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** can remove entries from the symbol table.
+*/
+
+#define SM_STAB_FREE(x) \
+ do \
+ { \
+ char *o = (x); \
+ (x) = NULL; \
+ if (o != NULL) \
+ sm_free(o); \
+ } while (0)
+
+void
+rmexpstab()
+{
+ int i;
+ STAB *s, *p, *f;
+ time_t now;
+
+ now = curtime();
+ for (i = 0; i < STABSIZE; i++)
+ {
+ p = NULL;
+ s = SymTab[i];
+ while (s != NULL)
+ {
+ switch (s->s_symtype)
+ {
+ case ST_HOSTSIG:
+ if (s->s_hostsig.hs_exp >= now)
+ goto next; /* not expired */
+ SM_STAB_FREE(s->s_hostsig.hs_sig); /* XXX */
+ break;
+
+ case ST_NAMECANON:
+ if (s->s_namecanon.nc_exp >= now)
+ goto next; /* not expired */
+ SM_STAB_FREE(s->s_namecanon.nc_cname); /* XXX */
+ break;
+
+ default:
+ if (s->s_symtype >= ST_MCI)
+ {
+ /* call mci_uncache? */
+ SM_STAB_FREE(s->s_mci.mci_status);
+ SM_STAB_FREE(s->s_mci.mci_rstatus);
+ SM_STAB_FREE(s->s_mci.mci_heloname);
+#if 0
+ /* not dynamically allocated */
+ SM_STAB_FREE(s->s_mci.mci_host);
+ SM_STAB_FREE(s->s_mci.mci_tolist);
+#endif /* 0 */
+#if SASL
+ /* should always by NULL */
+ SM_STAB_FREE(s->s_mci.mci_sasl_string);
+#endif /* SASL */
+ if (s->s_mci.mci_rpool != NULL)
+ {
+ sm_rpool_free(s->s_mci.mci_rpool);
+ s->s_mci.mci_macro.mac_rpool = NULL;
+ s->s_mci.mci_rpool = NULL;
+ }
+ break;
+ }
+ next:
+ p = s;
+ s = s->s_next;
+ continue;
+ }
+
+ /* remove entry */
+ SM_STAB_FREE(s->s_name); /* XXX */
+ f = s;
+ s = s->s_next;
+ sm_free(f); /* XXX */
+ if (p == NULL)
+ SymTab[i] = s;
+ else
+ p->s_next = s;
+ }
+ }
+}
+
+#if SM_HEAP_CHECK
+/*
+** DUMPSTAB -- dump symbol table.
+**
+** For debugging.
+*/
+
+#define MAXSTTYPES (ST_MCI + 1)
+
+void
+dumpstab()
+{
+ int i, t, total, types[MAXSTTYPES];
+ STAB *s;
+ static int prevt[MAXSTTYPES], prev = 0;
+
+ total = 0;
+ for (i = 0; i < MAXSTTYPES; i++)
+ types[i] = 0;
+ for (i = 0; i < STABSIZE; i++)
+ {
+ s = SymTab[i];
+ while (s != NULL)
+ {
+ ++total;
+ t = s->s_symtype;
+ if (t > MAXSTTYPES - 1)
+ t = MAXSTTYPES - 1;
+ types[t]++;
+ s = s->s_next;
+ }
+ }
+ sm_syslog(LOG_INFO, NOQID, "stab: total=%d (%d)", total, total - prev);
+ prev = total;
+ for (i = 0; i < MAXSTTYPES; i++)
+ {
+ if (types[i] != 0)
+ {
+ sm_syslog(LOG_INFO, NOQID, "stab: type[%2d]=%2d (%d)",
+ i, types[i], types[i] - prevt[i]);
+ }
+ prevt[i] = types[i];
+ }
+}
+#endif /* SM_HEAP_CHECK */
diff --git a/usr/src/cmd/sendmail/src/stats.c b/usr/src/cmd/sendmail/src/stats.c
new file mode 100644
index 0000000000..bbe80aef8a
--- /dev/null
+++ b/usr/src/cmd/sendmail/src/stats.c
@@ -0,0 +1,200 @@
+/*
+ * Copyright (c) 1998-2002 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sendmail.h>
+
+SM_RCSID("@(#)$Id: stats.c,v 8.56 2002/06/27 22:47:37 gshapiro Exp $")
+
+#include <sendmail/mailstats.h>
+
+static struct statistics Stat;
+
+static bool GotStats = false; /* set when we have stats to merge */
+
+/* See http://physics.nist.gov/cuu/Units/binary.html */
+#define ONE_K 1000 /* one thousand (twenty-four?) */
+#define KBYTES(x) (((x) + (ONE_K - 1)) / ONE_K)
+/*
+** MARKSTATS -- mark statistics
+**
+** Parameters:
+** e -- the envelope.
+** to -- to address.
+** type -- type of stats this represents.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** changes static Stat structure
+*/
+
+void
+markstats(e, to, type)
+ register ENVELOPE *e;
+ register ADDRESS *to;
+ int type;
+{
+ switch (type)
+ {
+ case STATS_QUARANTINE:
+ if (e->e_from.q_mailer != NULL)
+ Stat.stat_nq[e->e_from.q_mailer->m_mno]++;
+ break;
+
+ case STATS_REJECT:
+ if (e->e_from.q_mailer != NULL)
+ {
+ if (bitset(EF_DISCARD, e->e_flags))
+ Stat.stat_nd[e->e_from.q_mailer->m_mno]++;
+ else
+ Stat.stat_nr[e->e_from.q_mailer->m_mno]++;
+ }
+ Stat.stat_cr++;
+ break;
+
+ case STATS_CONNECT:
+ if (to == NULL)
+ Stat.stat_cf++;
+ else
+ Stat.stat_ct++;
+ break;
+
+ case STATS_NORMAL:
+ if (to == NULL)
+ {
+ if (e->e_from.q_mailer != NULL)
+ {
+ Stat.stat_nf[e->e_from.q_mailer->m_mno]++;
+ Stat.stat_bf[e->e_from.q_mailer->m_mno] +=
+ KBYTES(e->e_msgsize);
+ }
+ }
+ else
+ {
+ Stat.stat_nt[to->q_mailer->m_mno]++;
+ Stat.stat_bt[to->q_mailer->m_mno] += KBYTES(e->e_msgsize);
+ }
+ break;
+
+ default:
+ /* Silently ignore bogus call */
+ return;
+ }
+
+
+ GotStats = true;
+}
+/*
+** CLEARSTATS -- clear statistics structure
+**
+** Parameters:
+** none.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** clears the Stat structure.
+*/
+
+void
+clearstats()
+{
+ /* clear the structure to avoid future disappointment */
+ memset(&Stat, '\0', sizeof Stat);
+ GotStats = false;
+}
+/*
+** POSTSTATS -- post statistics in the statistics file
+**
+** Parameters:
+** sfile -- the name of the statistics file.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** merges the Stat structure with the sfile file.
+*/
+
+void
+poststats(sfile)
+ char *sfile;
+{
+ int fd;
+ static bool entered = false;
+ long sff = SFF_REGONLY|SFF_OPENASROOT;
+ struct statistics stats;
+ extern off_t lseek();
+
+ if (sfile == NULL || *sfile == '\0' || !GotStats || entered)
+ return;
+ entered = true;
+
+ (void) time(&Stat.stat_itime);
+ Stat.stat_size = sizeof Stat;
+ Stat.stat_magic = STAT_MAGIC;
+ Stat.stat_version = STAT_VERSION;
+
+ if (!bitnset(DBS_WRITESTATSTOSYMLINK, DontBlameSendmail))
+ sff |= SFF_NOSLINK;
+ if (!bitnset(DBS_WRITESTATSTOHARDLINK, DontBlameSendmail))
+ sff |= SFF_NOHLINK;
+
+ fd = safeopen(sfile, O_RDWR, 0600, sff);
+ if (fd < 0)
+ {
+ if (LogLevel > 12)
+ sm_syslog(LOG_INFO, NOQID, "poststats: %s: %s",
+ sfile, sm_errstring(errno));
+ errno = 0;
+ entered = false;
+ return;
+ }
+ if (read(fd, (char *) &stats, sizeof stats) == sizeof stats &&
+ stats.stat_size == sizeof stats &&
+ stats.stat_magic == Stat.stat_magic &&
+ stats.stat_version == Stat.stat_version)
+ {
+ /* merge current statistics into statfile */
+ register int i;
+
+ for (i = 0; i < MAXMAILERS; i++)
+ {
+ stats.stat_nf[i] += Stat.stat_nf[i];
+ stats.stat_bf[i] += Stat.stat_bf[i];
+ stats.stat_nt[i] += Stat.stat_nt[i];
+ stats.stat_bt[i] += Stat.stat_bt[i];
+ stats.stat_nr[i] += Stat.stat_nr[i];
+ stats.stat_nd[i] += Stat.stat_nd[i];
+ stats.stat_nq[i] += Stat.stat_nq[i];
+ }
+ stats.stat_cr += Stat.stat_cr;
+ stats.stat_ct += Stat.stat_ct;
+ stats.stat_cf += Stat.stat_cf;
+ }
+ else
+ memmove((char *) &stats, (char *) &Stat, sizeof stats);
+
+ /* write out results */
+ (void) lseek(fd, (off_t) 0, 0);
+ (void) write(fd, (char *) &stats, sizeof stats);
+ (void) close(fd);
+
+ /* clear the structure to avoid future disappointment */
+ clearstats();
+ entered = false;
+}
diff --git a/usr/src/cmd/sendmail/src/statusd_shm.h b/usr/src/cmd/sendmail/src/statusd_shm.h
new file mode 100644
index 0000000000..005249f6e7
--- /dev/null
+++ b/usr/src/cmd/sendmail/src/statusd_shm.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 1999-2000 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ * $Id: statusd_shm.h,v 8.7 2000/09/17 17:30:06 gshapiro Exp $
+ *
+ * Contributed by Exactis.com, Inc.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+** The shared memory part of statusd.
+**
+** Attach to STATUSD_SHM_KEY and update the counter appropriate
+** for your type of service.
+**
+*/
+
+#define STATUSD_MAGIC 110946
+#define STATUSD_SHM_KEY (key_t)(13)
+#define STATUSD_LONGS (2)
+
+typedef struct
+{
+ unsigned long magic;
+ unsigned long ul[STATUSD_LONGS];
+} STATUSD_SHM;
+
+/*
+** Offsets into ul[]. The appropriate program
+** increments these as appropriate.
+*/
+
+#define STATUSD_COOKIE (0) /* reregister cookie */
+
+/* sendmail */
+#define STATUSD_SM_NSENDMAIL (1) /* how many running */
+
+extern void shmtick __P((int, int));
+
diff --git a/usr/src/cmd/sendmail/src/sun_compat.c b/usr/src/cmd/sendmail/src/sun_compat.c
new file mode 100644
index 0000000000..18b3e8d9d4
--- /dev/null
+++ b/usr/src/cmd/sendmail/src/sun_compat.c
@@ -0,0 +1,92 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+/*
+ * Copyright 1994 - 2004 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#ifndef lint
+static char id[] = "%W% (Sun) %G%";
+#endif /* not lint */
+
+#include "sendmail.h"
+
+extern int getdomainname();
+
+void
+init_md_sun()
+{
+ struct stat sbuf;
+
+ /* Check for large file descriptor */
+ if (fstat(fileno(stdin), &sbuf) < 0)
+ {
+ if (errno == EOVERFLOW)
+ {
+ perror("stdin");
+ exit(EX_NOINPUT);
+ }
+ }
+}
+
+
+#ifdef SUN_INIT_DOMAIN
+/* this is mainly for backward compatibility in Sun environment */
+char *
+sun_init_domain()
+{
+ /*
+ * Get the domain name from the kernel.
+ * If it does not start with a leading dot, then remove
+ * the first component. Since leading dots are funny Unix
+ * files, we treat a leading "+" the same as a leading dot.
+ * Finally, force there to be at least one dot in the domain name
+ * (i.e. top-level domains are not allowed, like "com", must be
+ * something like "sun.com").
+ */
+ char buf[MAXNAME];
+ char *period, *autodomain;
+
+ if (getdomainname(buf, sizeof buf) < 0)
+ return NULL;
+
+ if (strlen(buf) == 0)
+ return NULL;
+
+ if (tTd(0, 20))
+ printf("domainname = %s\n", buf);
+
+ if (buf[0] == '+')
+ buf[0] = '.';
+ period = strchr(buf, '.');
+ if (period == NULL)
+ autodomain = buf;
+ else
+ autodomain = period+1;
+ if (strchr(autodomain, '.') == NULL)
+ return newstr(buf);
+ else
+ return newstr(autodomain);
+}
+#endif /* SUN_INIT_DOMAIN */
diff --git a/usr/src/cmd/sendmail/src/sysexits.c b/usr/src/cmd/sendmail/src/sysexits.c
new file mode 100644
index 0000000000..2cc94495ef
--- /dev/null
+++ b/usr/src/cmd/sendmail/src/sysexits.c
@@ -0,0 +1,169 @@
+/*
+ * Copyright (c) 1998-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sendmail.h>
+
+SM_RCSID("@(#)$Id: sysexits.c,v 8.33.4.1 2002/09/09 02:42:37 gshapiro Exp $")
+
+/*
+** DSNTOEXITSTAT -- convert DSN-style error code to EX_ style.
+**
+** Parameters:
+** dsncode -- the text of the DSN-style code.
+**
+** Returns:
+** The corresponding exit status.
+*/
+
+int
+dsntoexitstat(dsncode)
+ char *dsncode;
+{
+ int code2, code3;
+
+ /* first the easy cases.... */
+ if (*dsncode == '2')
+ return EX_OK;
+ if (*dsncode == '4')
+ return EX_TEMPFAIL;
+
+ /* reject other illegal values */
+ if (*dsncode != '5')
+ return EX_CONFIG;
+
+ /* now decode the other two field parts */
+ if (*++dsncode == '.')
+ dsncode++;
+ code2 = atoi(dsncode);
+ while (*dsncode != '\0' && *dsncode != '.')
+ dsncode++;
+ if (*dsncode != '\0')
+ dsncode++;
+ code3 = atoi(dsncode);
+
+ /* and do a nested switch to work them out */
+ switch (code2)
+ {
+ case 0: /* Other or Undefined status */
+ return EX_UNAVAILABLE;
+
+ case 1: /* Address Status */
+ switch (code3)
+ {
+ case 0: /* Other Address Status */
+ return EX_DATAERR;
+
+ case 1: /* Bad destination mailbox address */
+ case 6: /* Mailbox has moved, No forwarding address */
+ return EX_NOUSER;
+
+ case 2: /* Bad destination system address */
+ case 8: /* Bad senders system address */
+ return EX_NOHOST;
+
+ case 3: /* Bad destination mailbox address syntax */
+ case 7: /* Bad senders mailbox address syntax */
+ return EX_USAGE;
+
+ case 4: /* Destination mailbox address ambiguous */
+ return EX_UNAVAILABLE;
+
+ case 5: /* Destination address valid */
+ /* According to RFC1893, this can't happen */
+ return EX_CONFIG;
+ }
+ break;
+
+ case 2: /* Mailbox Status */
+ switch (code3)
+ {
+ case 0: /* Other or Undefined mailbox status */
+ case 1: /* Mailbox disabled, not accepting messages */
+ case 2: /* Mailbox full */
+ case 4: /* Mailing list expansion problem */
+ return EX_UNAVAILABLE;
+
+ case 3: /* Message length exceeds administrative lim */
+ return EX_DATAERR;
+ }
+ break;
+
+ case 3: /* System Status */
+ return EX_OSERR;
+
+ case 4: /* Network and Routing Status */
+ switch (code3)
+ {
+ case 0: /* Other or undefined network or routing stat */
+ return EX_IOERR;
+
+ case 1: /* No answer from host */
+ case 3: /* Routing server failure */
+ case 5: /* Network congestion */
+ return EX_TEMPFAIL;
+
+ case 2: /* Bad connection */
+ return EX_IOERR;
+
+ case 4: /* Unable to route */
+ return EX_PROTOCOL;
+
+ case 6: /* Routing loop detected */
+ return EX_CONFIG;
+
+ case 7: /* Delivery time expired */
+ return EX_UNAVAILABLE;
+ }
+ break;
+
+ case 5: /* Protocol Status */
+ return EX_PROTOCOL;
+
+ case 6: /* Message Content or Media Status */
+ return EX_UNAVAILABLE;
+
+ case 7: /* Security Status */
+ return EX_DATAERR;
+ }
+ return EX_UNAVAILABLE;
+}
+/*
+** EXITSTAT -- convert EX_ value to error text.
+**
+** Parameters:
+** excode -- rstatus which might consists of an EX_* value.
+**
+** Returns:
+** The corresponding error text or the original string.
+*/
+
+char *
+exitstat(excode)
+ char *excode;
+{
+ char *c;
+ int i;
+ char *exitmsg;
+
+ if (excode == NULL || *excode == '\0')
+ return excode;
+ i = (int) strtol(excode, &c, 10);
+ if (*c != '\0')
+ return excode;
+ exitmsg = sm_sysexitmsg(i);
+ if (exitmsg != NULL)
+ return exitmsg;
+ return excode;
+}
diff --git a/usr/src/cmd/sendmail/src/sysexits.h b/usr/src/cmd/sendmail/src/sysexits.h
new file mode 100644
index 0000000000..23fd3e68f5
--- /dev/null
+++ b/usr/src/cmd/sendmail/src/sysexits.h
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 1987, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ * $Id: sysexits.h,v 8.5 2000/11/26 02:13:20 ca Exp $
+ * @(#)sysexits.h 8.1 (Berkeley) 6/2/93
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#ifndef _SYSEXITS_H_
+# define _SYSEXITS_H_
+
+/*
+** SYSEXITS.H -- Exit status codes for system programs.
+**
+** This include file attempts to categorize possible error
+** exit statuses for system programs, notably delivermail
+** and the Berkeley network.
+**
+** Error numbers begin at EX__BASE to reduce the possibility of
+** clashing with other exit statuses that random programs may
+** already return. The meaning of the codes is approximately
+** as follows:
+**
+** EX_USAGE -- The command was used incorrectly, e.g., with
+** the wrong number of arguments, a bad flag, a bad
+** syntax in a parameter, or whatever.
+** EX_DATAERR -- The input data was incorrect in some way.
+** This should only be used for user's data & not
+** system files.
+** EX_NOINPUT -- An input file (not a system file) did not
+** exist or was not readable. This could also include
+** errors like "No message" to a mailer (if it cared
+** to catch it).
+** EX_NOUSER -- The user specified did not exist. This might
+** be used for mail addresses or remote logins.
+** EX_NOHOST -- The host specified did not exist. This is used
+** in mail addresses or network requests.
+** EX_UNAVAILABLE -- A service is unavailable. This can occur
+** if a support program or file does not exist. This
+** can also be used as a catchall message when something
+** you wanted to do doesn't work, but you don't know
+** why.
+** EX_SOFTWARE -- An internal software error has been detected.
+** This should be limited to non-operating system related
+** errors as possible.
+** EX_OSERR -- An operating system error has been detected.
+** This is intended to be used for such things as "cannot
+** fork", "cannot create pipe", or the like. It includes
+** things like getuid returning a user that does not
+** exist in the passwd file.
+** EX_OSFILE -- Some system file (e.g., /etc/passwd, /etc/utmp,
+** etc.) does not exist, cannot be opened, or has some
+** sort of error (e.g., syntax error).
+** EX_CANTCREAT -- A (user specified) output file cannot be
+** created.
+** EX_IOERR -- An error occurred while doing I/O on some file.
+** EX_TEMPFAIL -- temporary failure, indicating something that
+** is not really an error. In sendmail, this means
+** that a mailer (e.g.) could not create a connection,
+** and the request should be reattempted later.
+** EX_PROTOCOL -- the remote system returned something that
+** was "not possible" during a protocol exchange.
+** EX_NOPERM -- You did not have sufficient permission to
+** perform the operation. This is not intended for
+** file system problems, which should use NOINPUT or
+** CANTCREAT, but rather for higher level permissions.
+*/
+
+# define EX_OK 0 /* successful termination */
+
+# define EX__BASE 64 /* base value for error messages */
+
+# define EX_USAGE 64 /* command line usage error */
+# define EX_DATAERR 65 /* data format error */
+# define EX_NOINPUT 66 /* cannot open input */
+# define EX_NOUSER 67 /* addressee unknown */
+# define EX_NOHOST 68 /* host name unknown */
+# define EX_UNAVAILABLE 69 /* service unavailable */
+# define EX_SOFTWARE 70 /* internal software error */
+# define EX_OSERR 71 /* system error (e.g., can't fork) */
+# define EX_OSFILE 72 /* critical OS file missing */
+# define EX_CANTCREAT 73 /* can't create (user) output file */
+# define EX_IOERR 74 /* input/output error */
+# define EX_TEMPFAIL 75 /* temp failure; user is invited to retry */
+# define EX_PROTOCOL 76 /* remote error in protocol */
+# define EX_NOPERM 77 /* permission denied */
+# define EX_CONFIG 78 /* configuration error */
+
+# define EX__MAX 78 /* maximum listed value */
+
+#endif /* ! _SYSEXITS_H_ */
diff --git a/usr/src/cmd/sendmail/src/timers.h b/usr/src/cmd/sendmail/src/timers.h
new file mode 100644
index 0000000000..1eeddf0785
--- /dev/null
+++ b/usr/src/cmd/sendmail/src/timers.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 1999-2000 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ * $Id: timers.h,v 8.6 2001/04/03 01:53:18 gshapiro Exp $
+ *
+ * Contributed by Exactis.com, Inc.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#ifndef TIMERS_H
+#define TIMERS_H 1
+
+#define MAXTIMERSTACK 20 /* maximum timer depth */
+
+#define TIMER struct _timer
+
+TIMER
+{
+ long ti_wall_sec; /* wall clock seconds */
+ long ti_wall_usec; /* ... microseconds */
+ long ti_cpu_sec; /* cpu time seconds */
+ long ti_cpu_usec; /* ... microseconds */
+};
+
+extern void pushtimer __P((TIMER *));
+extern void poptimer __P((TIMER *));
+extern char *strtimer __P((TIMER *));
+#endif /* ! TIMERS_H */
diff --git a/usr/src/cmd/sendmail/src/tls.c b/usr/src/cmd/sendmail/src/tls.c
new file mode 100644
index 0000000000..7ade7a3c58
--- /dev/null
+++ b/usr/src/cmd/sendmail/src/tls.c
@@ -0,0 +1,1613 @@
+/*
+ * Copyright (c) 2000-2005 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sendmail.h>
+
+SM_RCSID("@(#)$Id: tls.c,v 8.97 2005/03/08 22:20:52 ca Exp $")
+
+#if STARTTLS
+# include <openssl/err.h>
+# include <openssl/bio.h>
+# include <openssl/pem.h>
+# ifndef HASURANDOMDEV
+# include <openssl/rand.h>
+# endif /* ! HASURANDOMDEV */
+# if !TLS_NO_RSA
+static RSA *rsa_tmp = NULL; /* temporary RSA key */
+static RSA *tmp_rsa_key __P((SSL *, int, int));
+# endif /* !TLS_NO_RSA */
+# if !defined(OPENSSL_VERSION_NUMBER) || OPENSSL_VERSION_NUMBER < 0x00907000L
+static int tls_verify_cb __P((X509_STORE_CTX *));
+# else /* !defined() || OPENSSL_VERSION_NUMBER < 0x00907000L */
+static int tls_verify_cb __P((X509_STORE_CTX *, void *));
+# endif /* !defined() || OPENSSL_VERSION_NUMBER < 0x00907000L */
+
+# if OPENSSL_VERSION_NUMBER > 0x00907000L
+static int x509_verify_cb __P((int, X509_STORE_CTX *));
+# endif /* OPENSSL_VERSION_NUMBER > 0x00907000L */
+
+# if !defined(OPENSSL_VERSION_NUMBER) || OPENSSL_VERSION_NUMBER < 0x00907000L
+# define CONST097
+# else /* !defined() || OPENSSL_VERSION_NUMBER < 0x00907000L */
+# define CONST097 const
+# endif /* !defined() || OPENSSL_VERSION_NUMBER < 0x00907000L */
+static void apps_ssl_info_cb __P((CONST097 SSL *, int , int));
+static bool tls_ok_f __P((char *, char *, int));
+static bool tls_safe_f __P((char *, long, bool));
+static int tls_verify_log __P((int, X509_STORE_CTX *, char *));
+
+# if !NO_DH
+static DH *get_dh512 __P((void));
+
+static unsigned char dh512_p[] =
+{
+ 0xDA,0x58,0x3C,0x16,0xD9,0x85,0x22,0x89,0xD0,0xE4,0xAF,0x75,
+ 0x6F,0x4C,0xCA,0x92,0xDD,0x4B,0xE5,0x33,0xB8,0x04,0xFB,0x0F,
+ 0xED,0x94,0xEF,0x9C,0x8A,0x44,0x03,0xED,0x57,0x46,0x50,0xD3,
+ 0x69,0x99,0xDB,0x29,0xD7,0x76,0x27,0x6B,0xA2,0xD3,0xD4,0x12,
+ 0xE2,0x18,0xF4,0xDD,0x1E,0x08,0x4C,0xF6,0xD8,0x00,0x3E,0x7C,
+ 0x47,0x74,0xE8,0x33
+};
+static unsigned char dh512_g[] =
+{
+ 0x02
+};
+
+static DH *
+get_dh512()
+{
+ DH *dh = NULL;
+
+ if ((dh = DH_new()) == NULL)
+ return NULL;
+ dh->p = BN_bin2bn(dh512_p, sizeof(dh512_p), NULL);
+ dh->g = BN_bin2bn(dh512_g, sizeof(dh512_g), NULL);
+ if ((dh->p == NULL) || (dh->g == NULL))
+ return NULL;
+ return dh;
+}
+# endif /* !NO_DH */
+
+
+/*
+** TLS_RAND_INIT -- initialize STARTTLS random generator
+**
+** Parameters:
+** randfile -- name of file with random data
+** logl -- loglevel
+**
+** Returns:
+** success/failure
+**
+** Side Effects:
+** initializes PRNG for tls library.
+*/
+
+# define MIN_RAND_BYTES 128 /* 1024 bits */
+
+# define RF_OK 0 /* randfile OK */
+# define RF_MISS 1 /* randfile == NULL || *randfile == '\0' */
+# define RF_UNKNOWN 2 /* unknown prefix for randfile */
+
+# define RI_NONE 0 /* no init yet */
+# define RI_SUCCESS 1 /* init was successful */
+# define RI_FAIL 2 /* init failed */
+
+static bool tls_rand_init __P((char *, int));
+
+static bool
+tls_rand_init(randfile, logl)
+ char *randfile;
+ int logl;
+{
+# ifndef HASURANDOMDEV
+ /* not required if /dev/urandom exists, OpenSSL does it internally */
+
+ bool ok;
+ int randdef;
+ static int done = RI_NONE;
+
+ /*
+ ** initialize PRNG
+ */
+
+ /* did we try this before? if yes: return old value */
+ if (done != RI_NONE)
+ return done == RI_SUCCESS;
+
+ /* set default values */
+ ok = false;
+ done = RI_FAIL;
+ randdef = (randfile == NULL || *randfile == '\0') ? RF_MISS : RF_OK;
+# if EGD
+ if (randdef == RF_OK && sm_strncasecmp(randfile, "egd:", 4) == 0)
+ {
+ randfile += 4;
+ if (RAND_egd(randfile) < 0)
+ {
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS: RAND_egd(%s) failed: random number generator not seeded",
+ randfile);
+ }
+ else
+ ok = true;
+ }
+ else
+# endif /* EGD */
+ if (randdef == RF_OK && sm_strncasecmp(randfile, "file:", 5) == 0)
+ {
+ int fd;
+ long sff;
+ struct stat st;
+
+ randfile += 5;
+ sff = SFF_SAFEDIRPATH | SFF_NOWLINK
+ | SFF_NOGWFILES | SFF_NOWWFILES
+ | SFF_NOGRFILES | SFF_NOWRFILES
+ | SFF_MUSTOWN | SFF_ROOTOK | SFF_OPENASROOT;
+ if (DontLockReadFiles)
+ sff |= SFF_NOLOCK;
+ if ((fd = safeopen(randfile, O_RDONLY, 0, sff)) >= 0)
+ {
+ if (fstat(fd, &st) < 0)
+ {
+ if (LogLevel > logl)
+ sm_syslog(LOG_ERR, NOQID,
+ "STARTTLS: can't fstat(%s)",
+ randfile);
+ }
+ else
+ {
+ bool use, problem;
+
+ use = true;
+ problem = false;
+
+ /* max. age of file: 10 minutes */
+ if (st.st_mtime + 600 < curtime())
+ {
+ use = bitnset(DBS_INSUFFICIENTENTROPY,
+ DontBlameSendmail);
+ problem = true;
+ if (LogLevel > logl)
+ sm_syslog(LOG_ERR, NOQID,
+ "STARTTLS: RandFile %s too old: %s",
+ randfile,
+ use ? "unsafe" :
+ "unusable");
+ }
+ if (use && st.st_size < MIN_RAND_BYTES)
+ {
+ use = bitnset(DBS_INSUFFICIENTENTROPY,
+ DontBlameSendmail);
+ problem = true;
+ if (LogLevel > logl)
+ sm_syslog(LOG_ERR, NOQID,
+ "STARTTLS: size(%s) < %d: %s",
+ randfile,
+ MIN_RAND_BYTES,
+ use ? "unsafe" :
+ "unusable");
+ }
+ if (use)
+ ok = RAND_load_file(randfile, -1) >=
+ MIN_RAND_BYTES;
+ if (use && !ok)
+ {
+ if (LogLevel > logl)
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS: RAND_load_file(%s) failed: random number generator not seeded",
+ randfile);
+ }
+ if (problem)
+ ok = false;
+ }
+ if (ok || bitnset(DBS_INSUFFICIENTENTROPY,
+ DontBlameSendmail))
+ {
+ /* add this even if fstat() failed */
+ RAND_seed((void *) &st, sizeof st);
+ }
+ (void) close(fd);
+ }
+ else
+ {
+ if (LogLevel > logl)
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS: Warning: safeopen(%s) failed",
+ randfile);
+ }
+ }
+ else if (randdef == RF_OK)
+ {
+ if (LogLevel > logl)
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS: Error: no proper random file definition %s",
+ randfile);
+ randdef = RF_UNKNOWN;
+ }
+ if (randdef == RF_MISS)
+ {
+ if (LogLevel > logl)
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS: Error: missing random file definition");
+ }
+ if (!ok && bitnset(DBS_INSUFFICIENTENTROPY, DontBlameSendmail))
+ {
+ int i;
+ long r;
+ unsigned char buf[MIN_RAND_BYTES];
+
+ /* assert((MIN_RAND_BYTES % sizeof(long)) == 0); */
+ for (i = 0; i <= sizeof(buf) - sizeof(long); i += sizeof(long))
+ {
+ r = get_random();
+ (void) memcpy(buf + i, (void *) &r, sizeof(long));
+ }
+ RAND_seed(buf, sizeof buf);
+ if (LogLevel > logl)
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS: Warning: random number generator not properly seeded");
+ ok = true;
+ }
+ done = ok ? RI_SUCCESS : RI_FAIL;
+ return ok;
+# else /* ! HASURANDOMDEV */
+ return true;
+# endif /* ! HASURANDOMDEV */
+}
+/*
+** INIT_TLS_LIBRARY -- Calls functions which setup TLS library for global use.
+**
+** Parameters:
+** none.
+**
+** Returns:
+** succeeded?
+*/
+
+bool
+init_tls_library()
+{
+ /* basic TLS initialization, ignore result for now */
+ SSL_library_init();
+ SSL_load_error_strings();
+# if 0
+ /* this is currently a macro for SSL_library_init */
+ SSLeay_add_ssl_algorithms();
+# endif /* 0 */
+
+ return tls_rand_init(RandFile, 7);
+}
+/*
+** TLS_SET_VERIFY -- request client certificate?
+**
+** Parameters:
+** ctx -- TLS context
+** ssl -- TLS structure
+** vrfy -- require certificate?
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Sets verification state for TLS
+**
+# if TLS_VRFY_PER_CTX
+** Notice:
+** This is per TLS context, not per TLS structure;
+** the former is global, the latter per connection.
+** It would be nice to do this per connection, but this
+** doesn't work in the current TLS libraries :-(
+# endif * TLS_VRFY_PER_CTX *
+*/
+
+void
+tls_set_verify(ctx, ssl, vrfy)
+ SSL_CTX *ctx;
+ SSL *ssl;
+ bool vrfy;
+{
+# if !TLS_VRFY_PER_CTX
+ SSL_set_verify(ssl, vrfy ? SSL_VERIFY_PEER : SSL_VERIFY_NONE, NULL);
+# else /* !TLS_VRFY_PER_CTX */
+ SSL_CTX_set_verify(ctx, vrfy ? SSL_VERIFY_PEER : SSL_VERIFY_NONE,
+ NULL);
+# endif /* !TLS_VRFY_PER_CTX */
+}
+
+/*
+** status in initialization
+** these flags keep track of the status of the initialization
+** i.e., whether a file exists (_EX) and whether it can be used (_OK)
+** [due to permissions]
+*/
+
+# define TLS_S_NONE 0x00000000 /* none yet */
+# define TLS_S_CERT_EX 0x00000001 /* cert file exists */
+# define TLS_S_CERT_OK 0x00000002 /* cert file is ok */
+# define TLS_S_KEY_EX 0x00000004 /* key file exists */
+# define TLS_S_KEY_OK 0x00000008 /* key file is ok */
+# define TLS_S_CERTP_EX 0x00000010 /* CA cert path exists */
+# define TLS_S_CERTP_OK 0x00000020 /* CA cert path is ok */
+# define TLS_S_CERTF_EX 0x00000040 /* CA cert file exists */
+# define TLS_S_CERTF_OK 0x00000080 /* CA cert file is ok */
+# define TLS_S_CRLF_EX 0x00000100 /* CRL file exists */
+# define TLS_S_CRLF_OK 0x00000200 /* CRL file is ok */
+
+# if _FFR_TLS_1
+# define TLS_S_CERT2_EX 0x00001000 /* 2nd cert file exists */
+# define TLS_S_CERT2_OK 0x00002000 /* 2nd cert file is ok */
+# define TLS_S_KEY2_EX 0x00004000 /* 2nd key file exists */
+# define TLS_S_KEY2_OK 0x00008000 /* 2nd key file is ok */
+# endif /* _FFR_TLS_1 */
+
+# define TLS_S_DH_OK 0x00200000 /* DH cert is ok */
+# define TLS_S_DHPAR_EX 0x00400000 /* DH param file exists */
+# define TLS_S_DHPAR_OK 0x00800000 /* DH param file is ok to use */
+
+/* Type of variable */
+# define TLS_T_OTHER 0
+# define TLS_T_SRV 1
+# define TLS_T_CLT 2
+
+/*
+** TLS_OK_F -- can var be an absolute filename?
+**
+** Parameters:
+** var -- filename
+** fn -- what is the filename used for?
+** type -- type of variable
+**
+** Returns:
+** ok?
+*/
+
+static bool
+tls_ok_f(var, fn, type)
+ char *var;
+ char *fn;
+ int type;
+{
+ /* must be absolute pathname */
+ if (var != NULL && *var == '/')
+ return true;
+ if (LogLevel > 12)
+ sm_syslog(LOG_WARNING, NOQID, "STARTTLS: %s%s missing",
+ type == TLS_T_SRV ? "Server" :
+ (type == TLS_T_CLT ? "Client" : ""), fn);
+ return false;
+}
+/*
+** TLS_SAFE_F -- is a file safe to use?
+**
+** Parameters:
+** var -- filename
+** sff -- flags for safefile()
+** srv -- server side?
+**
+** Returns:
+** ok?
+*/
+
+static bool
+tls_safe_f(var, sff, srv)
+ char *var;
+ long sff;
+ bool srv;
+{
+ int ret;
+
+ if ((ret = safefile(var, RunAsUid, RunAsGid, RunAsUserName, sff,
+ S_IRUSR, NULL)) == 0)
+ return true;
+ if (LogLevel > 7)
+ sm_syslog(LOG_WARNING, NOQID, "STARTTLS=%s: file %s unsafe: %s",
+ srv ? "server" : "client", var, sm_errstring(ret));
+ return false;
+}
+
+/*
+** TLS_OK_F -- macro to simplify calls to tls_ok_f
+**
+** Parameters:
+** var -- filename
+** fn -- what is the filename used for?
+** req -- is the file required?
+** st -- status bit to set if ok
+** type -- type of variable
+**
+** Side Effects:
+** uses r, ok; may change ok and status.
+**
+*/
+
+# define TLS_OK_F(var, fn, req, st, type) if (ok) \
+ { \
+ r = tls_ok_f(var, fn, type); \
+ if (r) \
+ status |= st; \
+ else if (req) \
+ ok = false; \
+ }
+
+/*
+** TLS_UNR -- macro to return whether a file should be unreadable
+**
+** Parameters:
+** bit -- flag to test
+** req -- flags
+**
+** Returns:
+** 0/SFF_NORFILES
+*/
+# define TLS_UNR(bit, req) (bitset(bit, req) ? SFF_NORFILES : 0)
+# define TLS_OUNR(bit, req) (bitset(bit, req) ? SFF_NOWRFILES : 0)
+# define TLS_KEYSFF(req) \
+ (bitnset(DBS_GROUPREADABLEKEYFILE, DontBlameSendmail) ? \
+ TLS_OUNR(TLS_I_KEY_OUNR, req) : \
+ TLS_UNR(TLS_I_KEY_UNR, req))
+
+/*
+** TLS_SAFE_F -- macro to simplify calls to tls_safe_f
+**
+** Parameters:
+** var -- filename
+** sff -- flags for safefile()
+** req -- is the file required?
+** ex -- does the file exist?
+** st -- status bit to set if ok
+** srv -- server side?
+**
+** Side Effects:
+** uses r, ok, ex; may change ok and status.
+**
+*/
+
+# define TLS_SAFE_F(var, sff, req, ex, st, srv) if (ex && ok) \
+ { \
+ r = tls_safe_f(var, sff, srv); \
+ if (r) \
+ status |= st; \
+ else if (req) \
+ ok = false; \
+ }
+
+/*
+** INITTLS -- initialize TLS
+**
+** Parameters:
+** ctx -- pointer to context
+** req -- requirements for initialization (see sendmail.h)
+** srv -- server side?
+** certfile -- filename of certificate
+** keyfile -- filename of private key
+** cacertpath -- path to CAs
+** cacertfile -- file with CA(s)
+** dhparam -- parameters for DH
+**
+** Returns:
+** succeeded?
+*/
+
+bool
+inittls(ctx, req, srv, certfile, keyfile, cacertpath, cacertfile, dhparam)
+ SSL_CTX **ctx;
+ unsigned long req;
+ bool srv;
+ char *certfile, *keyfile, *cacertpath, *cacertfile, *dhparam;
+{
+# if !NO_DH
+ static DH *dh = NULL;
+# endif /* !NO_DH */
+ int r;
+ bool ok;
+ long sff, status;
+ char *who;
+# if _FFR_TLS_1
+ char *cf2, *kf2;
+# endif /* _FFR_TLS_1 */
+# if SM_CONF_SHM
+ extern int ShmId;
+# endif /* SM_CONF_SHM */
+# if OPENSSL_VERSION_NUMBER > 0x00907000L
+ BIO *crl_file;
+ X509_CRL *crl;
+ X509_STORE *store;
+# endif /* OPENSSL_VERSION_NUMBER > 0x00907000L */
+
+ status = TLS_S_NONE;
+ who = srv ? "server" : "client";
+ if (ctx == NULL)
+ syserr("STARTTLS=%s, inittls: ctx == NULL", who);
+
+ /* already initialized? (we could re-init...) */
+ if (*ctx != NULL)
+ return true;
+ ok = true;
+
+# if _FFR_TLS_1
+ /*
+ ** look for a second filename: it must be separated by a ','
+ ** no blanks allowed (they won't be skipped).
+ ** we change a global variable here! this change will be undone
+ ** before return from the function but only if it returns true.
+ ** this isn't a problem since in a failure case this function
+ ** won't be called again with the same (overwritten) values.
+ ** otherwise each return must be replaced with a goto endinittls.
+ */
+
+ cf2 = NULL;
+ kf2 = NULL;
+ if (certfile != NULL && (cf2 = strchr(certfile, ',')) != NULL)
+ {
+ *cf2++ = '\0';
+ if (keyfile != NULL && (kf2 = strchr(keyfile, ',')) != NULL)
+ *kf2++ = '\0';
+ }
+# endif /* _FFR_TLS_1 */
+
+ /*
+ ** Check whether files/paths are defined
+ */
+
+ TLS_OK_F(certfile, "CertFile", bitset(TLS_I_CERT_EX, req),
+ TLS_S_CERT_EX, srv ? TLS_T_SRV : TLS_T_CLT);
+ TLS_OK_F(keyfile, "KeyFile", bitset(TLS_I_KEY_EX, req),
+ TLS_S_KEY_EX, srv ? TLS_T_SRV : TLS_T_CLT);
+ TLS_OK_F(cacertpath, "CACertPath", bitset(TLS_I_CERTP_EX, req),
+ TLS_S_CERTP_EX, TLS_T_OTHER);
+ TLS_OK_F(cacertfile, "CACertFile", bitset(TLS_I_CERTF_EX, req),
+ TLS_S_CERTF_EX, TLS_T_OTHER);
+
+# if OPENSSL_VERSION_NUMBER > 0x00907000L
+ TLS_OK_F(CRLFile, "CRLFile", bitset(TLS_I_CRLF_EX, req),
+ TLS_S_CRLF_EX, TLS_T_OTHER);
+# endif /* OPENSSL_VERSION_NUMBER > 0x00907000L */
+
+# if _FFR_TLS_1
+ /*
+ ** if the second file is specified it must exist
+ ** XXX: it is possible here to define only one of those files
+ */
+
+ if (cf2 != NULL)
+ {
+ TLS_OK_F(cf2, "CertFile", bitset(TLS_I_CERT_EX, req),
+ TLS_S_CERT2_EX, srv ? TLS_T_SRV : TLS_T_CLT);
+ }
+ if (kf2 != NULL)
+ {
+ TLS_OK_F(kf2, "KeyFile", bitset(TLS_I_KEY_EX, req),
+ TLS_S_KEY2_EX, srv ? TLS_T_SRV : TLS_T_CLT);
+ }
+# endif /* _FFR_TLS_1 */
+
+ /*
+ ** valid values for dhparam are (only the first char is checked)
+ ** none no parameters: don't use DH
+ ** 512 generate 512 bit parameters (fixed)
+ ** 1024 generate 1024 bit parameters
+ ** /file/name read parameters from /file/name
+ ** default is: 1024 for server, 512 for client (OK? XXX)
+ */
+
+ if (bitset(TLS_I_TRY_DH, req))
+ {
+ if (dhparam != NULL)
+ {
+ char c = *dhparam;
+
+ if (c == '1')
+ req |= TLS_I_DH1024;
+ else if (c == '5')
+ req |= TLS_I_DH512;
+ else if (c != 'n' && c != 'N' && c != '/')
+ {
+ if (LogLevel > 12)
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS=%s, error: illegal value '%s' for DHParam",
+ who, dhparam);
+ dhparam = NULL;
+ }
+ }
+ if (dhparam == NULL)
+ dhparam = srv ? "1" : "5";
+ else if (*dhparam == '/')
+ {
+ TLS_OK_F(dhparam, "DHParameters",
+ bitset(TLS_I_DHPAR_EX, req),
+ TLS_S_DHPAR_EX, TLS_T_OTHER);
+ }
+ }
+ if (!ok)
+ return ok;
+
+ /* certfile etc. must be "safe". */
+ sff = SFF_REGONLY | SFF_SAFEDIRPATH | SFF_NOWLINK
+ | SFF_NOGWFILES | SFF_NOWWFILES
+ | SFF_MUSTOWN | SFF_ROOTOK | SFF_OPENASROOT;
+ if (DontLockReadFiles)
+ sff |= SFF_NOLOCK;
+
+ TLS_SAFE_F(certfile, sff | TLS_UNR(TLS_I_CERT_UNR, req),
+ bitset(TLS_I_CERT_EX, req),
+ bitset(TLS_S_CERT_EX, status), TLS_S_CERT_OK, srv);
+ TLS_SAFE_F(keyfile, sff | TLS_KEYSFF(req),
+ bitset(TLS_I_KEY_EX, req),
+ bitset(TLS_S_KEY_EX, status), TLS_S_KEY_OK, srv);
+ TLS_SAFE_F(cacertfile, sff | TLS_UNR(TLS_I_CERTF_UNR, req),
+ bitset(TLS_I_CERTF_EX, req),
+ bitset(TLS_S_CERTF_EX, status), TLS_S_CERTF_OK, srv);
+ TLS_SAFE_F(dhparam, sff | TLS_UNR(TLS_I_DHPAR_UNR, req),
+ bitset(TLS_I_DHPAR_EX, req),
+ bitset(TLS_S_DHPAR_EX, status), TLS_S_DHPAR_OK, srv);
+# if OPENSSL_VERSION_NUMBER > 0x00907000L
+ TLS_SAFE_F(CRLFile, sff | TLS_UNR(TLS_I_CRLF_UNR, req),
+ bitset(TLS_I_CRLF_EX, req),
+ bitset(TLS_S_CRLF_EX, status), TLS_S_CRLF_OK, srv);
+# endif /* OPENSSL_VERSION_NUMBER > 0x00907000L */
+ if (!ok)
+ return ok;
+# if _FFR_TLS_1
+ if (cf2 != NULL)
+ {
+ TLS_SAFE_F(cf2, sff | TLS_UNR(TLS_I_CERT_UNR, req),
+ bitset(TLS_I_CERT_EX, req),
+ bitset(TLS_S_CERT2_EX, status), TLS_S_CERT2_OK, srv);
+ }
+ if (kf2 != NULL)
+ {
+ TLS_SAFE_F(kf2, sff | TLS_KEYSFF(req),
+ bitset(TLS_I_KEY_EX, req),
+ bitset(TLS_S_KEY2_EX, status), TLS_S_KEY2_OK, srv);
+ }
+# endif /* _FFR_TLS_1 */
+
+ /* create a method and a new context */
+ if ((*ctx = SSL_CTX_new(srv ? SSLv23_server_method() :
+ SSLv23_client_method())) == NULL)
+ {
+ if (LogLevel > 7)
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS=%s, error: SSL_CTX_new(SSLv23_%s_method()) failed",
+ who, who);
+ if (LogLevel > 9)
+ tlslogerr(who);
+ return false;
+ }
+
+# if OPENSSL_VERSION_NUMBER > 0x00907000L
+ if (CRLFile != NULL)
+ {
+ /* get a pointer to the current certificate validation store */
+ store = SSL_CTX_get_cert_store(*ctx); /* does not fail */
+ crl_file = BIO_new(BIO_s_file_internal());
+ if (crl_file != NULL)
+ {
+ if (BIO_read_filename(crl_file, CRLFile) >= 0)
+ {
+ crl = PEM_read_bio_X509_CRL(crl_file, NULL,
+ NULL, NULL);
+ BIO_free(crl_file);
+ X509_STORE_add_crl(store, crl);
+ X509_CRL_free(crl);
+ X509_STORE_set_flags(store,
+ X509_V_FLAG_CRL_CHECK|X509_V_FLAG_CRL_CHECK_ALL);
+ X509_STORE_set_verify_cb_func(store,
+ x509_verify_cb);
+ }
+ else
+ {
+ if (LogLevel > 9)
+ {
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS=%s, error: PEM_read_bio_X509_CRL(%s)=failed",
+ who, CRLFile);
+ }
+
+ /* avoid memory leaks */
+ BIO_free(crl_file);
+ return false;
+ }
+
+ }
+ else if (LogLevel > 9)
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS=%s, error: BIO_new=failed", who);
+ }
+# if _FFR_CRLPATH
+ if (CRLPath != NULL)
+ {
+ X509_LOOKUP *lookup;
+
+ lookup = X509_STORE_add_lookup(store, X509_LOOKUP_hash_dir());
+ if (lookup == NULL)
+ {
+ if (LogLevel > 9)
+ {
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS=%s, error: X509_STORE_add_lookup(hash)=failed",
+ who, CRLFile);
+ }
+ return false;
+ }
+ X509_LOOKUP_add_dir(lookup, CRLPath, X509_FILETYPE_PEM);
+ X509_STORE_set_flags(store,
+ X509_V_FLAG_CRL_CHECK|X509_V_FLAG_CRL_CHECK_ALL);
+ }
+# endif /* _FFR_CRLPATH */
+# endif /* OPENSSL_VERSION_NUMBER > 0x00907000L */
+
+# if TLS_NO_RSA
+ /* turn off backward compatibility, required for no-rsa */
+ SSL_CTX_set_options(*ctx, SSL_OP_NO_SSLv2);
+# endif /* TLS_NO_RSA */
+
+
+# if !TLS_NO_RSA
+ /*
+ ** Create a temporary RSA key
+ ** XXX Maybe we shouldn't create this always (even though it
+ ** is only at startup).
+ ** It is a time-consuming operation and it is not always necessary.
+ ** maybe we should do it only on demand...
+ */
+
+ if (bitset(TLS_I_RSA_TMP, req)
+# if SM_CONF_SHM
+ && ShmId != SM_SHM_NO_ID &&
+ (rsa_tmp = RSA_generate_key(RSA_KEYLENGTH, RSA_F4, NULL,
+ NULL)) == NULL
+# else /* SM_CONF_SHM */
+ && 0 /* no shared memory: no need to generate key now */
+# endif /* SM_CONF_SHM */
+ )
+ {
+ if (LogLevel > 7)
+ {
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS=%s, error: RSA_generate_key failed",
+ who);
+ if (LogLevel > 9)
+ tlslogerr(who);
+ }
+ return false;
+ }
+# endif /* !TLS_NO_RSA */
+
+ /*
+ ** load private key
+ ** XXX change this for DSA-only version
+ */
+
+ if (bitset(TLS_S_KEY_OK, status) &&
+ SSL_CTX_use_PrivateKey_file(*ctx, keyfile,
+ SSL_FILETYPE_PEM) <= 0)
+ {
+ if (LogLevel > 7)
+ {
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS=%s, error: SSL_CTX_use_PrivateKey_file(%s) failed",
+ who, keyfile);
+ if (LogLevel > 9)
+ tlslogerr(who);
+ }
+ if (bitset(TLS_I_USE_KEY, req))
+ return false;
+ }
+
+ /* get the certificate file */
+ if (bitset(TLS_S_CERT_OK, status) &&
+ SSL_CTX_use_certificate_file(*ctx, certfile,
+ SSL_FILETYPE_PEM) <= 0)
+ {
+ if (LogLevel > 7)
+ {
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS=%s, error: SSL_CTX_use_certificate_file(%s) failed",
+ who, certfile);
+ if (LogLevel > 9)
+ tlslogerr(who);
+ }
+ if (bitset(TLS_I_USE_CERT, req))
+ return false;
+ }
+
+ /* check the private key */
+ if (bitset(TLS_S_KEY_OK, status) &&
+ (r = SSL_CTX_check_private_key(*ctx)) <= 0)
+ {
+ /* Private key does not match the certificate public key */
+ if (LogLevel > 5)
+ {
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS=%s, error: SSL_CTX_check_private_key failed(%s): %d",
+ who, keyfile, r);
+ if (LogLevel > 9)
+ tlslogerr(who);
+ }
+ if (bitset(TLS_I_USE_KEY, req))
+ return false;
+ }
+
+# if _FFR_TLS_1
+ /* XXX this code is pretty much duplicated from above! */
+
+ /* load private key */
+ if (bitset(TLS_S_KEY2_OK, status) &&
+ SSL_CTX_use_PrivateKey_file(*ctx, kf2, SSL_FILETYPE_PEM) <= 0)
+ {
+ if (LogLevel > 7)
+ {
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS=%s, error: SSL_CTX_use_PrivateKey_file(%s) failed",
+ who, kf2);
+ if (LogLevel > 9)
+ tlslogerr(who);
+ }
+ }
+
+ /* get the certificate file */
+ if (bitset(TLS_S_CERT2_OK, status) &&
+ SSL_CTX_use_certificate_file(*ctx, cf2, SSL_FILETYPE_PEM) <= 0)
+ {
+ if (LogLevel > 7)
+ {
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS=%s, error: SSL_CTX_use_certificate_file(%s) failed",
+ who, cf2);
+ if (LogLevel > 9)
+ tlslogerr(who);
+ }
+ }
+
+ /* also check the private key */
+ if (bitset(TLS_S_KEY2_OK, status) &&
+ (r = SSL_CTX_check_private_key(*ctx)) <= 0)
+ {
+ /* Private key does not match the certificate public key */
+ if (LogLevel > 5)
+ {
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS=%s, error: SSL_CTX_check_private_key 2 failed: %d",
+ who, r);
+ if (LogLevel > 9)
+ tlslogerr(who);
+ }
+ }
+# endif /* _FFR_TLS_1 */
+
+ /* SSL_CTX_set_quiet_shutdown(*ctx, 1); violation of standard? */
+ SSL_CTX_set_options(*ctx, SSL_OP_ALL); /* XXX bug compatibility? */
+
+# if !NO_DH
+ /* Diffie-Hellman initialization */
+ if (bitset(TLS_I_TRY_DH, req))
+ {
+ if (bitset(TLS_S_DHPAR_OK, status))
+ {
+ BIO *bio;
+
+ if ((bio = BIO_new_file(dhparam, "r")) != NULL)
+ {
+ dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL);
+ BIO_free(bio);
+ if (dh == NULL && LogLevel > 7)
+ {
+ unsigned long err;
+
+ err = ERR_get_error();
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS=%s, error: cannot read DH parameters(%s): %s",
+ who, dhparam,
+ ERR_error_string(err, NULL));
+ if (LogLevel > 9)
+ tlslogerr(who);
+ }
+ }
+ else
+ {
+ if (LogLevel > 5)
+ {
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS=%s, error: BIO_new_file(%s) failed",
+ who, dhparam);
+ if (LogLevel > 9)
+ tlslogerr(who);
+ }
+ }
+ }
+ if (dh == NULL && bitset(TLS_I_DH1024, req))
+ {
+ DSA *dsa;
+
+ /* this takes a while! (7-130s on a 450MHz AMD K6-2) */
+ dsa = DSA_generate_parameters(1024, NULL, 0, NULL,
+ NULL, 0, NULL);
+ dh = DSA_dup_DH(dsa);
+ DSA_free(dsa);
+ }
+ else
+ if (dh == NULL && bitset(TLS_I_DH512, req))
+ dh = get_dh512();
+
+ if (dh == NULL)
+ {
+ if (LogLevel > 9)
+ {
+ unsigned long err;
+
+ err = ERR_get_error();
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS=%s, error: cannot read or set DH parameters(%s): %s",
+ who, dhparam,
+ ERR_error_string(err, NULL));
+ }
+ if (bitset(TLS_I_REQ_DH, req))
+ return false;
+ }
+ else
+ {
+ SSL_CTX_set_tmp_dh(*ctx, dh);
+
+ /* important to avoid small subgroup attacks */
+ SSL_CTX_set_options(*ctx, SSL_OP_SINGLE_DH_USE);
+ if (LogLevel > 13)
+ sm_syslog(LOG_INFO, NOQID,
+ "STARTTLS=%s, Diffie-Hellman init, key=%d bit (%c)",
+ who, 8 * DH_size(dh), *dhparam);
+ DH_free(dh);
+ }
+ }
+# endif /* !NO_DH */
+
+
+ /* XXX do we need this cache here? */
+ if (bitset(TLS_I_CACHE, req))
+ SSL_CTX_sess_set_cache_size(*ctx, 128);
+ /* timeout? SSL_CTX_set_timeout(*ctx, TimeOut...); */
+
+ /* load certificate locations and default CA paths */
+ if (bitset(TLS_S_CERTP_EX, status) && bitset(TLS_S_CERTF_EX, status))
+ {
+ if ((r = SSL_CTX_load_verify_locations(*ctx, cacertfile,
+ cacertpath)) == 1)
+ {
+# if !TLS_NO_RSA
+ if (bitset(TLS_I_RSA_TMP, req))
+ SSL_CTX_set_tmp_rsa_callback(*ctx, tmp_rsa_key);
+# endif /* !TLS_NO_RSA */
+
+ /*
+ ** We have to install our own verify callback:
+ ** SSL_VERIFY_PEER requests a client cert but even
+ ** though *FAIL_IF* isn't set, the connection
+ ** will be aborted if the client presents a cert
+ ** that is not "liked" (can't be verified?) by
+ ** the TLS library :-(
+ */
+
+ /*
+ ** XXX currently we could call tls_set_verify()
+ ** but we hope that that function will later on
+ ** only set the mode per connection.
+ */
+ SSL_CTX_set_verify(*ctx,
+ bitset(TLS_I_NO_VRFY, req) ? SSL_VERIFY_NONE
+ : SSL_VERIFY_PEER,
+ NULL);
+
+ /* install verify callback */
+ SSL_CTX_set_cert_verify_callback(*ctx, tls_verify_cb,
+ NULL);
+ SSL_CTX_set_client_CA_list(*ctx,
+ SSL_load_client_CA_file(cacertfile));
+ }
+ else
+ {
+ /*
+ ** can't load CA data; do we care?
+ ** the data is necessary to authenticate the client,
+ ** which in turn would be necessary
+ ** if we want to allow relaying based on it.
+ */
+ if (LogLevel > 5)
+ {
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS=%s, error: load verify locs %s, %s failed: %d",
+ who, cacertpath, cacertfile, r);
+ if (LogLevel > 9)
+ tlslogerr(who);
+ }
+ if (bitset(TLS_I_VRFY_LOC, req))
+ return false;
+ }
+ }
+
+ /* XXX: make this dependent on an option? */
+ if (tTd(96, 9))
+ SSL_CTX_set_info_callback(*ctx, apps_ssl_info_cb);
+
+# if _FFR_TLS_1
+ /* install our own cipher list */
+ if (CipherList != NULL && *CipherList != '\0')
+ {
+ if (SSL_CTX_set_cipher_list(*ctx, CipherList) <= 0)
+ {
+ if (LogLevel > 7)
+ {
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS=%s, error: SSL_CTX_set_cipher_list(%s) failed, list ignored",
+ who, CipherList);
+
+ if (LogLevel > 9)
+ tlslogerr(who);
+ }
+ /* failure if setting to this list is required? */
+ }
+ }
+# endif /* _FFR_TLS_1 */
+ if (LogLevel > 12)
+ sm_syslog(LOG_INFO, NOQID, "STARTTLS=%s, init=%d", who, ok);
+
+# if _FFR_TLS_1
+# if 0
+ /*
+ ** this label is required if we want to have a "clean" exit
+ ** see the comments above at the initialization of cf2
+ */
+
+ endinittls:
+# endif /* 0 */
+
+ /* undo damage to global variables */
+ if (cf2 != NULL)
+ *--cf2 = ',';
+ if (kf2 != NULL)
+ *--kf2 = ',';
+# endif /* _FFR_TLS_1 */
+
+ return ok;
+}
+/*
+** TLS_GET_INFO -- get information about TLS connection
+**
+** Parameters:
+** ssl -- TLS connection structure
+** srv -- server or client
+** host -- hostname of other side
+** mac -- macro storage
+** certreq -- did we ask for a cert?
+**
+** Returns:
+** result of authentication.
+**
+** Side Effects:
+** sets macros: {cipher}, {tls_version}, {verify},
+** {cipher_bits}, {alg_bits}, {cert}, {cert_subject},
+** {cert_issuer}, {cn_subject}, {cn_issuer}
+*/
+
+int
+tls_get_info(ssl, srv, host, mac, certreq)
+ SSL *ssl;
+ bool srv;
+ char *host;
+ MACROS_T *mac;
+ bool certreq;
+{
+ SSL_CIPHER *c;
+ int b, r;
+ long verifyok;
+ char *s, *who;
+ char bitstr[16];
+ X509 *cert;
+
+ c = SSL_get_current_cipher(ssl);
+
+ /* cast is just workaround for compiler warning */
+ macdefine(mac, A_TEMP, macid("{cipher}"),
+ (char *) SSL_CIPHER_get_name(c));
+ b = SSL_CIPHER_get_bits(c, &r);
+ (void) sm_snprintf(bitstr, sizeof bitstr, "%d", b);
+ macdefine(mac, A_TEMP, macid("{cipher_bits}"), bitstr);
+ (void) sm_snprintf(bitstr, sizeof bitstr, "%d", r);
+ macdefine(mac, A_TEMP, macid("{alg_bits}"), bitstr);
+ s = SSL_CIPHER_get_version(c);
+ if (s == NULL)
+ s = "UNKNOWN";
+ macdefine(mac, A_TEMP, macid("{tls_version}"), s);
+
+ who = srv ? "server" : "client";
+ cert = SSL_get_peer_certificate(ssl);
+ verifyok = SSL_get_verify_result(ssl);
+ if (LogLevel > 14)
+ sm_syslog(LOG_INFO, NOQID,
+ "STARTTLS=%s, get_verify: %ld get_peer: 0x%lx",
+ who, verifyok, (unsigned long) cert);
+ if (cert != NULL)
+ {
+ unsigned int n;
+ unsigned char md[EVP_MAX_MD_SIZE];
+ char buf[MAXNAME];
+
+ X509_NAME_oneline(X509_get_subject_name(cert),
+ buf, sizeof buf);
+ macdefine(mac, A_TEMP, macid("{cert_subject}"),
+ xtextify(buf, "<>\")"));
+ X509_NAME_oneline(X509_get_issuer_name(cert),
+ buf, sizeof buf);
+ macdefine(mac, A_TEMP, macid("{cert_issuer}"),
+ xtextify(buf, "<>\")"));
+ X509_NAME_get_text_by_NID(X509_get_subject_name(cert),
+ NID_commonName, buf, sizeof buf);
+ macdefine(mac, A_TEMP, macid("{cn_subject}"),
+ xtextify(buf, "<>\")"));
+ X509_NAME_get_text_by_NID(X509_get_issuer_name(cert),
+ NID_commonName, buf, sizeof buf);
+ macdefine(mac, A_TEMP, macid("{cn_issuer}"),
+ xtextify(buf, "<>\")"));
+ n = 0;
+ if (X509_digest(cert, EVP_md5(), md, &n) != 0 && n > 0)
+ {
+ char md5h[EVP_MAX_MD_SIZE * 3];
+ static const char hexcodes[] = "0123456789ABCDEF";
+
+ SM_ASSERT((n * 3) + 2 < sizeof(md5h));
+ for (r = 0; r < (int) n; r++)
+ {
+ md5h[r * 3] = hexcodes[(md[r] & 0xf0) >> 4];
+ md5h[(r * 3) + 1] = hexcodes[(md[r] & 0x0f)];
+ md5h[(r * 3) + 2] = ':';
+ }
+ md5h[(n * 3) - 1] = '\0';
+ macdefine(mac, A_TEMP, macid("{cert_md5}"), md5h);
+ }
+ else
+ macdefine(mac, A_TEMP, macid("{cert_md5}"), "");
+ }
+ else
+ {
+ macdefine(mac, A_PERM, macid("{cert_subject}"), "");
+ macdefine(mac, A_PERM, macid("{cert_issuer}"), "");
+ macdefine(mac, A_PERM, macid("{cn_subject}"), "");
+ macdefine(mac, A_PERM, macid("{cn_issuer}"), "");
+ macdefine(mac, A_TEMP, macid("{cert_md5}"), "");
+ }
+ switch (verifyok)
+ {
+ case X509_V_OK:
+ if (cert != NULL)
+ {
+ s = "OK";
+ r = TLS_AUTH_OK;
+ }
+ else
+ {
+ s = certreq ? "NO" : "NOT",
+ r = TLS_AUTH_NO;
+ }
+ break;
+ default:
+ s = "FAIL";
+ r = TLS_AUTH_FAIL;
+ break;
+ }
+ macdefine(mac, A_PERM, macid("{verify}"), s);
+ if (cert != NULL)
+ X509_free(cert);
+
+ /* do some logging */
+ if (LogLevel > 8)
+ {
+ char *vers, *s1, *s2, *cbits, *algbits;
+
+ vers = macget(mac, macid("{tls_version}"));
+ cbits = macget(mac, macid("{cipher_bits}"));
+ algbits = macget(mac, macid("{alg_bits}"));
+ s1 = macget(mac, macid("{verify}"));
+ s2 = macget(mac, macid("{cipher}"));
+
+ /* XXX: maybe cut off ident info? */
+ sm_syslog(LOG_INFO, NOQID,
+ "STARTTLS=%s, relay=%.100s, version=%.16s, verify=%.16s, cipher=%.64s, bits=%.6s/%.6s",
+ who,
+ host == NULL ? "local" : host,
+ vers, s1, s2, /* sm_snprintf() can deal with NULL */
+ algbits == NULL ? "0" : algbits,
+ cbits == NULL ? "0" : cbits);
+ if (LogLevel > 11)
+ {
+ /*
+ ** Maybe run xuntextify on the strings?
+ ** That is easier to read but makes it maybe a bit
+ ** more complicated to figure out the right values
+ ** for the access map...
+ */
+
+ s1 = macget(mac, macid("{cert_subject}"));
+ s2 = macget(mac, macid("{cert_issuer}"));
+ sm_syslog(LOG_INFO, NOQID,
+ "STARTTLS=%s, cert-subject=%.256s, cert-issuer=%.256s, verifymsg=%s",
+ who, s1, s2,
+ X509_verify_cert_error_string(verifyok));
+ }
+ }
+ return r;
+}
+/*
+** ENDTLS -- shutdown secure connection
+**
+** Parameters:
+** ssl -- SSL connection information.
+** side -- server/client (for logging).
+**
+** Returns:
+** success? (EX_* code)
+*/
+
+int
+endtls(ssl, side)
+ SSL *ssl;
+ char *side;
+{
+ int ret = EX_OK;
+
+ if (ssl != NULL)
+ {
+ int r;
+
+ if ((r = SSL_shutdown(ssl)) < 0)
+ {
+ if (LogLevel > 11)
+ {
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS=%s, SSL_shutdown failed: %d",
+ side, r);
+ tlslogerr(side);
+ }
+ ret = EX_SOFTWARE;
+ }
+# if !defined(OPENSSL_VERSION_NUMBER) || OPENSSL_VERSION_NUMBER > 0x0090602fL
+
+ /*
+ ** Bug in OpenSSL (at least up to 0.9.6b):
+ ** From: Lutz.Jaenicke@aet.TU-Cottbus.DE
+ ** Message-ID: <20010723152244.A13122@serv01.aet.tu-cottbus.de>
+ ** To: openssl-users@openssl.org
+ ** Subject: Re: SSL_shutdown() woes (fwd)
+ **
+ ** The side sending the shutdown alert first will
+ ** not care about the answer of the peer but will
+ ** immediately return with a return value of "0"
+ ** (ssl/s3_lib.c:ssl3_shutdown()). SSL_get_error will evaluate
+ ** the value of "0" and as the shutdown alert of the peer was
+ ** not received (actually, the program did not even wait for
+ ** the answer), an SSL_ERROR_SYSCALL is flagged, because this
+ ** is the default rule in case everything else does not apply.
+ **
+ ** For your server the problem is different, because it
+ ** receives the shutdown first (setting SSL_RECEIVED_SHUTDOWN),
+ ** then sends its response (SSL_SENT_SHUTDOWN), so for the
+ ** server the shutdown was successfull.
+ **
+ ** As is by know, you would have to call SSL_shutdown() once
+ ** and ignore an SSL_ERROR_SYSCALL returned. Then call
+ ** SSL_shutdown() again to actually get the server's response.
+ **
+ ** In the last discussion, Bodo Moeller concluded that a
+ ** rewrite of the shutdown code would be necessary, but
+ ** probably with another API, as the change would not be
+ ** compatible to the way it is now. Things do not become
+ ** easier as other programs do not follow the shutdown
+ ** guidelines anyway, so that a lot error conditions and
+ ** compitibility issues would have to be caught.
+ **
+ ** For now the recommondation is to ignore the error message.
+ */
+
+ else if (r == 0)
+ {
+ if (LogLevel > 15)
+ {
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS=%s, SSL_shutdown not done",
+ side);
+ tlslogerr(side);
+ }
+ ret = EX_SOFTWARE;
+ }
+# endif /* !defined(OPENSSL_VERSION_NUMBER) || OPENSSL_VERSION_NUMBER > 0x0090602fL */
+ SSL_free(ssl);
+ ssl = NULL;
+ }
+ return ret;
+}
+
+# if !TLS_NO_RSA
+/*
+** TMP_RSA_KEY -- return temporary RSA key
+**
+** Parameters:
+** s -- TLS connection structure
+** export --
+** keylength --
+**
+** Returns:
+** temporary RSA key.
+*/
+
+# ifndef MAX_RSA_TMP_CNT
+# define MAX_RSA_TMP_CNT 1000 /* XXX better value? */
+# endif /* ! MAX_RSA_TMP_CNT */
+
+/* ARGUSED0 */
+static RSA *
+tmp_rsa_key(s, export, keylength)
+ SSL *s;
+ int export;
+ int keylength;
+{
+# if SM_CONF_SHM
+ extern int ShmId;
+ extern int *PRSATmpCnt;
+
+ if (ShmId != SM_SHM_NO_ID && rsa_tmp != NULL &&
+ ++(*PRSATmpCnt) < MAX_RSA_TMP_CNT)
+ return rsa_tmp;
+# endif /* SM_CONF_SHM */
+
+ if (rsa_tmp != NULL)
+ RSA_free(rsa_tmp);
+ rsa_tmp = RSA_generate_key(RSA_KEYLENGTH, RSA_F4, NULL, NULL);
+ if (rsa_tmp == NULL)
+ {
+ if (LogLevel > 0)
+ sm_syslog(LOG_ERR, NOQID,
+ "STARTTLS=server, tmp_rsa_key: RSA_generate_key failed!");
+ }
+ else
+ {
+# if SM_CONF_SHM
+# if 0
+ /*
+ ** XXX we can't (yet) share the new key...
+ ** The RSA structure contains pointers hence it can't be
+ ** easily kept in shared memory. It must be transformed
+ ** into a continous memory region first, then stored,
+ ** and later read out again (each time re-transformed).
+ */
+
+ if (ShmId != SM_SHM_NO_ID)
+ *PRSATmpCnt = 0;
+# endif /* 0 */
+# endif /* SM_CONF_SHM */
+ if (LogLevel > 9)
+ sm_syslog(LOG_ERR, NOQID,
+ "STARTTLS=server, tmp_rsa_key: new temp RSA key");
+ }
+ return rsa_tmp;
+}
+# endif /* !TLS_NO_RSA */
+/*
+** APPS_SSL_INFO_CB -- info callback for TLS connections
+**
+** Parameters:
+** s -- TLS connection structure
+** where -- state in handshake
+** ret -- return code of last operation
+**
+** Returns:
+** none.
+*/
+
+static void
+apps_ssl_info_cb(s, where, ret)
+ CONST097 SSL *s;
+ int where;
+ int ret;
+{
+ int w;
+ char *str;
+ BIO *bio_err = NULL;
+
+ if (LogLevel > 14)
+ sm_syslog(LOG_INFO, NOQID,
+ "STARTTLS: info_callback where=0x%x, ret=%d",
+ where, ret);
+
+ w = where & ~SSL_ST_MASK;
+ if (bio_err == NULL)
+ bio_err = BIO_new_fp(stderr, BIO_NOCLOSE);
+
+ if (bitset(SSL_ST_CONNECT, w))
+ str = "SSL_connect";
+ else if (bitset(SSL_ST_ACCEPT, w))
+ str = "SSL_accept";
+ else
+ str = "undefined";
+
+ if (bitset(SSL_CB_LOOP, where))
+ {
+ if (LogLevel > 12)
+ sm_syslog(LOG_NOTICE, NOQID,
+ "STARTTLS: %s:%s",
+ str, SSL_state_string_long(s));
+ }
+ else if (bitset(SSL_CB_ALERT, where))
+ {
+ str = bitset(SSL_CB_READ, where) ? "read" : "write";
+ if (LogLevel > 12)
+ sm_syslog(LOG_NOTICE, NOQID,
+ "STARTTLS: SSL3 alert %s:%s:%s",
+ str, SSL_alert_type_string_long(ret),
+ SSL_alert_desc_string_long(ret));
+ }
+ else if (bitset(SSL_CB_EXIT, where))
+ {
+ if (ret == 0)
+ {
+ if (LogLevel > 7)
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS: %s:failed in %s",
+ str, SSL_state_string_long(s));
+ }
+ else if (ret < 0)
+ {
+ if (LogLevel > 7)
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS: %s:error in %s",
+ str, SSL_state_string_long(s));
+ }
+ }
+}
+/*
+** TLS_VERIFY_LOG -- log verify error for TLS certificates
+**
+** Parameters:
+** ok -- verify ok?
+** ctx -- x509 context
+**
+** Returns:
+** 0 -- fatal error
+** 1 -- ok
+*/
+
+static int
+tls_verify_log(ok, ctx, name)
+ int ok;
+ X509_STORE_CTX *ctx;
+ char *name;
+{
+ SSL *ssl;
+ X509 *cert;
+ int reason, depth;
+ char buf[512];
+
+ cert = X509_STORE_CTX_get_current_cert(ctx);
+ reason = X509_STORE_CTX_get_error(ctx);
+ depth = X509_STORE_CTX_get_error_depth(ctx);
+ ssl = (SSL *) X509_STORE_CTX_get_ex_data(ctx,
+ SSL_get_ex_data_X509_STORE_CTX_idx());
+
+ if (ssl == NULL)
+ {
+ /* internal error */
+ sm_syslog(LOG_ERR, NOQID,
+ "STARTTLS: internal error: tls_verify_cb: ssl == NULL");
+ return 0;
+ }
+
+ X509_NAME_oneline(X509_get_subject_name(cert), buf, sizeof buf);
+ sm_syslog(LOG_INFO, NOQID,
+ "STARTTLS: %s cert verify: depth=%d %s, state=%d, reason=%s",
+ name, depth, buf, ok, X509_verify_cert_error_string(reason));
+ return 1;
+}
+
+/*
+** TLS_VERIFY_CB -- verify callback for TLS certificates
+**
+** Parameters:
+** ctx -- x509 context
+**
+** Returns:
+** accept connection?
+** currently: always yes.
+*/
+
+static int
+# if !defined(OPENSSL_VERSION_NUMBER) || OPENSSL_VERSION_NUMBER < 0x00907000L
+tls_verify_cb(ctx)
+ X509_STORE_CTX *ctx;
+# else /* !defined() || OPENSSL_VERSION_NUMBER < 0x00907000L */
+tls_verify_cb(ctx, unused)
+ X509_STORE_CTX *ctx;
+ void *unused;
+# endif /* !defined() || OPENSSL_VERSION_NUMBER < 0x00907000L */
+{
+ int ok;
+
+ ok = X509_verify_cert(ctx);
+ if (ok == 0)
+ {
+ if (LogLevel > 13)
+ return tls_verify_log(ok, ctx, "TLS");
+ return 1; /* override it */
+ }
+ return ok;
+}
+/*
+** TLSLOGERR -- log the errors from the TLS error stack
+**
+** Parameters:
+** who -- server/client (for logging).
+**
+** Returns:
+** none.
+*/
+
+void
+tlslogerr(who)
+ char *who;
+{
+ unsigned long l;
+ int line, flags;
+ unsigned long es;
+ char *file, *data;
+ char buf[256];
+# define CP (const char **)
+
+ es = CRYPTO_thread_id();
+ while ((l = ERR_get_error_line_data(CP &file, &line, CP &data, &flags))
+ != 0)
+ {
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS=%s: %lu:%s:%s:%d:%s", who, es,
+ ERR_error_string(l, buf),
+ file, line,
+ bitset(ERR_TXT_STRING, flags) ? data : "");
+ }
+}
+
+# if OPENSSL_VERSION_NUMBER > 0x00907000L
+/*
+** X509_VERIFY_CB -- verify callback
+**
+** Parameters:
+** ctx -- x509 context
+**
+** Returns:
+** accept connection?
+** currently: always yes.
+*/
+
+static int
+x509_verify_cb(ok, ctx)
+ int ok;
+ X509_STORE_CTX *ctx;
+{
+ if (ok == 0)
+ {
+ if (LogLevel > 13)
+ tls_verify_log(ok, ctx, "x509");
+ if (ctx->error == X509_V_ERR_UNABLE_TO_GET_CRL)
+ {
+ ctx->error = 0;
+ return 1; /* override it */
+ }
+ }
+ return ok;
+}
+# endif /* OPENSSL_VERSION_NUMBER > 0x00907000L */
+#endif /* STARTTLS */
diff --git a/usr/src/cmd/sendmail/src/trace.c b/usr/src/cmd/sendmail/src/trace.c
new file mode 100644
index 0000000000..ddcdd92cd5
--- /dev/null
+++ b/usr/src/cmd/sendmail/src/trace.c
@@ -0,0 +1,226 @@
+/*
+ * Copyright (c) 1998-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sendmail.h>
+#include <sm/debug.h>
+#include <sm/string.h>
+
+SM_RCSID("@(#)$Id: trace.c,v 8.37.4.1 2002/12/05 17:28:05 ca Exp $")
+
+static char *tTnewflag __P((char *));
+static char *tToldflag __P((char *));
+
+/*
+** TtSETUP -- set up for trace package.
+**
+** Parameters:
+** vect -- pointer to trace vector.
+** size -- number of flags in trace vector.
+** defflags -- flags to set if no value given.
+**
+** Returns:
+** none
+**
+** Side Effects:
+** environment is set up.
+*/
+
+static unsigned char *tTvect;
+static unsigned int tTsize;
+static char *DefFlags;
+
+void
+tTsetup(vect, size, defflags)
+ unsigned char *vect;
+ unsigned int size;
+ char *defflags;
+{
+ tTvect = vect;
+ tTsize = size;
+ DefFlags = defflags;
+}
+
+/*
+** tToldflag -- process an old style trace flag
+**
+** Parameters:
+** s -- points to a [\0, \t] terminated string,
+** and the initial character is a digit.
+**
+** Returns:
+** pointer to terminating [\0, \t] character
+**
+** Side Effects:
+** modifies tTvect
+*/
+
+static char *
+tToldflag(s)
+ register char *s;
+{
+ unsigned int first, last;
+ register unsigned int i;
+
+ /* find first flag to set */
+ i = 0;
+ while (isascii(*s) && isdigit(*s) && i < tTsize)
+ i = i * 10 + (*s++ - '0');
+
+ /*
+ ** skip over rest of a too large number
+ ** Maybe we should complain if out-of-bounds values are used.
+ */
+
+ while (isascii(*s) && isdigit(*s) && i >= tTsize)
+ s++;
+ first = i;
+
+ /* find last flag to set */
+ if (*s == '-')
+ {
+ i = 0;
+ while (isascii(*++s) && isdigit(*s) && i < tTsize)
+ i = i * 10 + (*s - '0');
+
+ /* skip over rest of a too large number */
+ while (isascii(*s) && isdigit(*s) && i >= tTsize)
+ s++;
+ }
+ last = i;
+
+ /* find the level to set it to */
+ i = 1;
+ if (*s == '.')
+ {
+ i = 0;
+ while (isascii(*++s) && isdigit(*s))
+ i = i * 10 + (*s - '0');
+ }
+
+ /* clean up args */
+ if (first >= tTsize)
+ first = tTsize - 1;
+ if (last >= tTsize)
+ last = tTsize - 1;
+
+ /* set the flags */
+ while (first <= last)
+ tTvect[first++] = (unsigned char) i;
+
+ /* skip trailing junk */
+ while (*s != '\0' && *s != ',' && *s != ' ' && *s != '\t')
+ ++s;
+
+ return s;
+}
+
+/*
+** tTnewflag -- process a new style trace flag
+**
+** Parameters:
+** s -- Points to a non-empty [\0, \t] terminated string,
+** of which the initial character is not a digit.
+**
+** Returns:
+** pointer to terminating [\0, \t] character
+**
+** Side Effects:
+** adds trace flag to libsm debug database
+*/
+
+static char *
+tTnewflag(s)
+ register char *s;
+{
+ char *pat, *endpat;
+ int level;
+
+ pat = s;
+ while (*s != '\0' && *s != ',' && *s != ' ' && *s != '\t' && *s != '.')
+ ++s;
+ endpat = s;
+ if (*s == '.')
+ {
+ ++s;
+ level = 0;
+ while (isascii(*s) && isdigit(*s))
+ {
+ level = level * 10 + (*s - '0');
+ ++s;
+ }
+ if (level < 0)
+ level = 0;
+ }
+ else
+ {
+ level = 1;
+ }
+
+ sm_debug_addsetting_x(sm_strndup_x(pat, endpat - pat), level);
+
+ /* skip trailing junk */
+ while (*s != '\0' && *s != ',' && *s != ' ' && *s != '\t')
+ ++s;
+
+ return s;
+}
+
+/*
+** TtFLAG -- process an external trace flag list.
+**
+** Parameters:
+** s -- the trace flag.
+**
+** The syntax of a trace flag list is as follows:
+**
+** <flags> ::= <flag> | <flags> "," <flag>
+** <flag> ::= <categories> | <categories> "." <level>
+** <categories> ::= <int> | <int> "-" <int> | <pattern>
+** <pattern> ::= <an sh glob pattern matching a C identifier>
+**
+** White space is ignored before and after a flag.
+** However, note that we skip over anything we don't
+** understand, rather than report an error.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** sets/clears old-style trace flags.
+** registers new-style trace flags with the libsm debug package.
+*/
+
+void
+tTflag(s)
+ register char *s;
+{
+ if (s == NULL || *s == '\0')
+ s = DefFlags;
+
+ for (;;)
+ {
+ if (*s == '\0')
+ return;
+ if (*s == ',' || *s == ' ' || *s == '\t')
+ {
+ ++s;
+ continue;
+ }
+ if (isascii(*s) && isdigit(*s))
+ s = tToldflag(s);
+ else
+ s = tTnewflag(s);
+ }
+}
diff --git a/usr/src/cmd/sendmail/src/udb.c b/usr/src/cmd/sendmail/src/udb.c
new file mode 100644
index 0000000000..0b7feb87cb
--- /dev/null
+++ b/usr/src/cmd/sendmail/src/udb.c
@@ -0,0 +1,1314 @@
+/*
+ * Copyright (c) 1998-2003 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sendmail.h>
+
+#if USERDB
+SM_RCSID("@(#)$Id: udb.c,v 8.160 2003/04/03 16:32:46 ca Exp $ (with USERDB)")
+#else /* USERDB */
+SM_RCSID("@(#)$Id: udb.c,v 8.160 2003/04/03 16:32:46 ca Exp $ (without USERDB)")
+#endif /* USERDB */
+
+#if USERDB
+
+# if NEWDB
+# include "sm/bdb.h"
+# else /* NEWDB */
+# define DBT struct _data_base_thang_
+DBT
+{
+ void *data; /* pointer to data */
+ size_t size; /* length of data */
+};
+# endif /* NEWDB */
+
+/*
+** UDB.C -- interface between sendmail and Berkeley User Data Base.
+**
+** This depends on the 4.4BSD db package.
+*/
+
+
+struct udbent
+{
+ char *udb_spec; /* string version of spec */
+ int udb_type; /* type of entry */
+ pid_t udb_pid; /* PID of process which opened db */
+ char *udb_default; /* default host for outgoing mail */
+ union
+ {
+# if NETINET || NETINET6
+ /* type UE_REMOTE -- do remote call for lookup */
+ struct
+ {
+ SOCKADDR _udb_addr; /* address */
+ int _udb_timeout; /* timeout */
+ } udb_remote;
+# define udb_addr udb_u.udb_remote._udb_addr
+# define udb_timeout udb_u.udb_remote._udb_timeout
+# endif /* NETINET || NETINET6 */
+
+ /* type UE_FORWARD -- forward message to remote */
+ struct
+ {
+ char *_udb_fwdhost; /* name of forward host */
+ } udb_forward;
+# define udb_fwdhost udb_u.udb_forward._udb_fwdhost
+
+# if NEWDB
+ /* type UE_FETCH -- lookup in local database */
+ struct
+ {
+ char *_udb_dbname; /* pathname of database */
+ DB *_udb_dbp; /* open database ptr */
+ } udb_lookup;
+# define udb_dbname udb_u.udb_lookup._udb_dbname
+# define udb_dbp udb_u.udb_lookup._udb_dbp
+# endif /* NEWDB */
+ } udb_u;
+};
+
+# define UDB_EOLIST 0 /* end of list */
+# define UDB_SKIP 1 /* skip this entry */
+# define UDB_REMOTE 2 /* look up in remote database */
+# define UDB_DBFETCH 3 /* look up in local database */
+# define UDB_FORWARD 4 /* forward to remote host */
+# define UDB_HESIOD 5 /* look up via hesiod */
+
+# define MAXUDBENT 10 /* maximum number of UDB entries */
+
+
+struct udb_option
+{
+ char *udbo_name;
+ char *udbo_val;
+};
+
+# if HESIOD
+static int hes_udb_get __P((DBT *, DBT *));
+# endif /* HESIOD */
+static char *udbmatch __P((char *, char *, SM_RPOOL_T *));
+static int _udbx_init __P((ENVELOPE *));
+static int _udb_parsespec __P((char *, struct udb_option [], int));
+
+/*
+** UDBEXPAND -- look up user in database and expand
+**
+** Parameters:
+** a -- address to expand.
+** sendq -- pointer to head of sendq to put the expansions in.
+** aliaslevel -- the current alias nesting depth.
+** e -- the current envelope.
+**
+** Returns:
+** EX_TEMPFAIL -- if something "odd" happened -- probably due
+** to accessing a file on an NFS server that is down.
+** EX_OK -- otherwise.
+**
+** Side Effects:
+** Modifies sendq.
+*/
+
+static struct udbent UdbEnts[MAXUDBENT + 1];
+static bool UdbInitialized = false;
+
+int
+udbexpand(a, sendq, aliaslevel, e)
+ register ADDRESS *a;
+ ADDRESS **sendq;
+ int aliaslevel;
+ register ENVELOPE *e;
+{
+ int i;
+ DBT key;
+ DBT info;
+ bool breakout;
+ register struct udbent *up;
+ int keylen;
+ int naddrs;
+ char *user;
+ char keybuf[MAXKEY];
+
+ memset(&key, '\0', sizeof key);
+ memset(&info, '\0', sizeof info);
+
+ if (tTd(28, 1))
+ sm_dprintf("udbexpand(%s)\n", a->q_paddr);
+
+ /* make certain we are supposed to send to this address */
+ if (!QS_IS_SENDABLE(a->q_state))
+ return EX_OK;
+ e->e_to = a->q_paddr;
+
+ /* on first call, locate the database */
+ if (!UdbInitialized)
+ {
+ if (_udbx_init(e) == EX_TEMPFAIL)
+ return EX_TEMPFAIL;
+ }
+
+ /* short circuit the process if no chance of a match */
+ if (UdbSpec == NULL || UdbSpec[0] == '\0')
+ return EX_OK;
+
+ /* extract user to do userdb matching on */
+ user = a->q_user;
+
+ /* short circuit name begins with '\\' since it can't possibly match */
+ /* (might want to treat this as unquoted instead) */
+ if (user[0] == '\\')
+ return EX_OK;
+
+ /* if name begins with a colon, it indicates our metadata */
+ if (user[0] == ':')
+ return EX_OK;
+
+ keylen = sm_strlcpyn(keybuf, sizeof keybuf, 2, user, ":maildrop");
+
+ /* if name is too long, assume it won't match */
+ if (keylen >= sizeof keybuf)
+ return EX_OK;
+
+ /* build actual database key */
+
+ breakout = false;
+ for (up = UdbEnts; !breakout; up++)
+ {
+ int usersize;
+ int userleft;
+ char userbuf[MEMCHUNKSIZE];
+# if HESIOD && HES_GETMAILHOST
+ char pobuf[MAXNAME];
+# endif /* HESIOD && HES_GETMAILHOST */
+# if defined(NEWDB) && DB_VERSION_MAJOR > 1
+ DBC *dbc = NULL;
+# endif /* defined(NEWDB) && DB_VERSION_MAJOR > 1 */
+
+ user = userbuf;
+ userbuf[0] = '\0';
+ usersize = sizeof userbuf;
+ userleft = sizeof userbuf - 1;
+
+ /*
+ ** Select action based on entry type.
+ **
+ ** On dropping out of this switch, "class" should
+ ** explain the type of the data, and "user" should
+ ** contain the user information.
+ */
+
+ switch (up->udb_type)
+ {
+# if NEWDB
+ case UDB_DBFETCH:
+ key.data = keybuf;
+ key.size = keylen;
+ if (tTd(28, 80))
+ sm_dprintf("udbexpand: trying %s (%d) via db\n",
+ keybuf, keylen);
+# if DB_VERSION_MAJOR < 2
+ i = (*up->udb_dbp->seq)(up->udb_dbp, &key, &info, R_CURSOR);
+# else /* DB_VERSION_MAJOR < 2 */
+ i = 0;
+ if (dbc == NULL &&
+# if DB_VERSION_MAJOR > 2 || DB_VERSION_MINOR >= 6
+ (errno = (*up->udb_dbp->cursor)(up->udb_dbp,
+ NULL, &dbc, 0)) != 0)
+# else /* DB_VERSION_MAJOR > 2 || DB_VERSION_MINOR >= 6 */
+ (errno = (*up->udb_dbp->cursor)(up->udb_dbp,
+ NULL, &dbc)) != 0)
+# endif /* DB_VERSION_MAJOR > 2 || DB_VERSION_MINOR >= 6 */
+ i = -1;
+ if (i != 0 || dbc == NULL ||
+ (errno = dbc->c_get(dbc, &key,
+ &info, DB_SET)) != 0)
+ i = 1;
+# endif /* DB_VERSION_MAJOR < 2 */
+ if (i > 0 || info.size <= 0)
+ {
+ if (tTd(28, 2))
+ sm_dprintf("udbexpand: no match on %s (%d)\n",
+ keybuf, keylen);
+# if DB_VERSION_MAJOR > 1
+ if (dbc != NULL)
+ {
+ (void) dbc->c_close(dbc);
+ dbc = NULL;
+ }
+# endif /* DB_VERSION_MAJOR > 1 */
+ break;
+ }
+ if (tTd(28, 80))
+ sm_dprintf("udbexpand: match %.*s: %.*s\n",
+ (int) key.size, (char *) key.data,
+ (int) info.size, (char *) info.data);
+
+ a->q_flags &= ~QSELFREF;
+ while (i == 0 && key.size == keylen &&
+ memcmp(key.data, keybuf, keylen) == 0)
+ {
+ char *p;
+
+ if (bitset(EF_VRFYONLY, e->e_flags))
+ {
+ a->q_state = QS_VERIFIED;
+# if DB_VERSION_MAJOR > 1
+ if (dbc != NULL)
+ {
+ (void) dbc->c_close(dbc);
+ dbc = NULL;
+ }
+# endif /* DB_VERSION_MAJOR > 1 */
+ return EX_OK;
+ }
+
+ breakout = true;
+ if (info.size >= userleft - 1)
+ {
+ char *nuser;
+ int size = MEMCHUNKSIZE;
+
+ if (info.size > MEMCHUNKSIZE)
+ size = info.size;
+ nuser = sm_malloc_x(usersize + size);
+
+ memmove(nuser, user, usersize);
+ if (user != userbuf)
+ sm_free(user); /* XXX */
+ user = nuser;
+ usersize += size;
+ userleft += size;
+ }
+ p = &user[strlen(user)];
+ if (p != user)
+ {
+ *p++ = ',';
+ userleft--;
+ }
+ memmove(p, info.data, info.size);
+ p[info.size] = '\0';
+ userleft -= info.size;
+
+ /* get the next record */
+# if DB_VERSION_MAJOR < 2
+ i = (*up->udb_dbp->seq)(up->udb_dbp, &key, &info, R_NEXT);
+# else /* DB_VERSION_MAJOR < 2 */
+ i = 0;
+ if ((errno = dbc->c_get(dbc, &key,
+ &info, DB_NEXT)) != 0)
+ i = 1;
+# endif /* DB_VERSION_MAJOR < 2 */
+ }
+
+# if DB_VERSION_MAJOR > 1
+ if (dbc != NULL)
+ {
+ (void) dbc->c_close(dbc);
+ dbc = NULL;
+ }
+# endif /* DB_VERSION_MAJOR > 1 */
+
+ /* if nothing ever matched, try next database */
+ if (!breakout)
+ break;
+
+ message("expanded to %s", user);
+ if (LogLevel > 10)
+ sm_syslog(LOG_INFO, e->e_id,
+ "expand %.100s => %s",
+ e->e_to,
+ shortenstring(user, MAXSHORTSTR));
+ naddrs = sendtolist(user, a, sendq, aliaslevel + 1, e);
+ if (naddrs > 0 && !bitset(QSELFREF, a->q_flags))
+ {
+ if (tTd(28, 5))
+ {
+ sm_dprintf("udbexpand: QS_EXPANDED ");
+ printaddr(sm_debug_file(), a, false);
+ }
+ a->q_state = QS_EXPANDED;
+ }
+ if (i < 0)
+ {
+ syserr("udbexpand: db-get %.*s stat %d",
+ (int) key.size, (char *) key.data, i);
+ return EX_TEMPFAIL;
+ }
+
+ /*
+ ** If this address has a -request address, reflect
+ ** it into the envelope.
+ */
+
+ memset(&key, '\0', sizeof key);
+ memset(&info, '\0', sizeof info);
+ (void) sm_strlcpyn(keybuf, sizeof keybuf, 2, a->q_user,
+ ":mailsender");
+ keylen = strlen(keybuf);
+ key.data = keybuf;
+ key.size = keylen;
+
+# if DB_VERSION_MAJOR < 2
+ i = (*up->udb_dbp->get)(up->udb_dbp, &key, &info, 0);
+# else /* DB_VERSION_MAJOR < 2 */
+ i = errno = (*up->udb_dbp->get)(up->udb_dbp, NULL,
+ &key, &info, 0);
+# endif /* DB_VERSION_MAJOR < 2 */
+ if (i != 0 || info.size <= 0)
+ break;
+ a->q_owner = sm_rpool_malloc_x(e->e_rpool,
+ info.size + 1);
+ memmove(a->q_owner, info.data, info.size);
+ a->q_owner[info.size] = '\0';
+
+ /* announce delivery; NORECEIPT bit set later */
+ if (e->e_xfp != NULL)
+ {
+ (void) sm_io_fprintf(e->e_xfp, SM_TIME_DEFAULT,
+ "Message delivered to mailing list %s\n",
+ a->q_paddr);
+ }
+ e->e_flags |= EF_SENDRECEIPT;
+ a->q_flags |= QDELIVERED|QEXPANDED;
+ break;
+# endif /* NEWDB */
+
+# if HESIOD
+ case UDB_HESIOD:
+ key.data = keybuf;
+ key.size = keylen;
+ if (tTd(28, 80))
+ sm_dprintf("udbexpand: trying %s (%d) via hesiod\n",
+ keybuf, keylen);
+ /* look up the key via hesiod */
+ i = hes_udb_get(&key, &info);
+ if (i < 0)
+ {
+ syserr("udbexpand: hesiod-get %.*s stat %d",
+ (int) key.size, (char *) key.data, i);
+ return EX_TEMPFAIL;
+ }
+ else if (i > 0 || info.size <= 0)
+ {
+# if HES_GETMAILHOST
+ struct hes_postoffice *hp;
+# endif /* HES_GETMAILHOST */
+
+ if (tTd(28, 2))
+ sm_dprintf("udbexpand: no match on %s (%d)\n",
+ (char *) keybuf, (int) keylen);
+# if HES_GETMAILHOST
+ if (tTd(28, 8))
+ sm_dprintf(" ... trying hes_getmailhost(%s)\n",
+ a->q_user);
+ hp = hes_getmailhost(a->q_user);
+ if (hp == NULL)
+ {
+ if (hes_error() == HES_ER_NET)
+ {
+ syserr("udbexpand: hesiod-getmail %s stat %d",
+ a->q_user, hes_error());
+ return EX_TEMPFAIL;
+ }
+ if (tTd(28, 2))
+ sm_dprintf("hes_getmailhost(%s): %d\n",
+ a->q_user, hes_error());
+ break;
+ }
+ if (strlen(hp->po_name) + strlen(hp->po_host) >
+ sizeof pobuf - 2)
+ {
+ if (tTd(28, 2))
+ sm_dprintf("hes_getmailhost(%s): expansion too long: %.30s@%.30s\n",
+ a->q_user,
+ hp->po_name,
+ hp->po_host);
+ break;
+ }
+ info.data = pobuf;
+ (void) sm_snprintf(pobuf, sizeof pobuf,
+ "%s@%s", hp->po_name, hp->po_host);
+ info.size = strlen(info.data);
+# else /* HES_GETMAILHOST */
+ break;
+# endif /* HES_GETMAILHOST */
+ }
+ if (tTd(28, 80))
+ sm_dprintf("udbexpand: match %.*s: %.*s\n",
+ (int) key.size, (char *) key.data,
+ (int) info.size, (char *) info.data);
+ a->q_flags &= ~QSELFREF;
+
+ if (bitset(EF_VRFYONLY, e->e_flags))
+ {
+ a->q_state = QS_VERIFIED;
+ return EX_OK;
+ }
+
+ breakout = true;
+ if (info.size >= usersize)
+ user = sm_malloc_x(info.size + 1);
+ memmove(user, info.data, info.size);
+ user[info.size] = '\0';
+
+ message("hesioded to %s", user);
+ if (LogLevel > 10)
+ sm_syslog(LOG_INFO, e->e_id,
+ "hesiod %.100s => %s",
+ e->e_to,
+ shortenstring(user, MAXSHORTSTR));
+ naddrs = sendtolist(user, a, sendq, aliaslevel + 1, e);
+
+ if (naddrs > 0 && !bitset(QSELFREF, a->q_flags))
+ {
+ if (tTd(28, 5))
+ {
+ sm_dprintf("udbexpand: QS_EXPANDED ");
+ printaddr(sm_debug_file(), a, false);
+ }
+ a->q_state = QS_EXPANDED;
+ }
+
+ /*
+ ** If this address has a -request address, reflect
+ ** it into the envelope.
+ */
+
+ (void) sm_strlcpyn(keybuf, sizeof keybuf, 2, a->q_user,
+ ":mailsender");
+ keylen = strlen(keybuf);
+ key.data = keybuf;
+ key.size = keylen;
+ i = hes_udb_get(&key, &info);
+ if (i != 0 || info.size <= 0)
+ break;
+ a->q_owner = sm_rpool_malloc_x(e->e_rpool,
+ info.size + 1);
+ memmove(a->q_owner, info.data, info.size);
+ a->q_owner[info.size] = '\0';
+ break;
+# endif /* HESIOD */
+
+ case UDB_REMOTE:
+ /* not yet implemented */
+ break;
+
+ case UDB_FORWARD:
+ if (bitset(EF_VRFYONLY, e->e_flags))
+ {
+ a->q_state = QS_VERIFIED;
+ return EX_OK;
+ }
+ i = strlen(up->udb_fwdhost) + strlen(a->q_user) + 1;
+ if (i >= usersize)
+ {
+ usersize = i + 1;
+ user = sm_malloc_x(usersize);
+ }
+ (void) sm_strlcpyn(user, usersize, 3,
+ a->q_user, "@", up->udb_fwdhost);
+ message("expanded to %s", user);
+ a->q_flags &= ~QSELFREF;
+ naddrs = sendtolist(user, a, sendq, aliaslevel + 1, e);
+ if (naddrs > 0 && !bitset(QSELFREF, a->q_flags))
+ {
+ if (tTd(28, 5))
+ {
+ sm_dprintf("udbexpand: QS_EXPANDED ");
+ printaddr(sm_debug_file(), a, false);
+ }
+ a->q_state = QS_EXPANDED;
+ }
+ breakout = true;
+ break;
+
+ case UDB_EOLIST:
+ breakout = true;
+ break;
+
+ default:
+ /* unknown entry type */
+ break;
+ }
+ /* XXX if an exception occurs, there is a storage leak */
+ if (user != userbuf)
+ sm_free(user); /* XXX */
+ }
+ return EX_OK;
+}
+/*
+** UDBSENDER -- return canonical external name of sender, given local name
+**
+** Parameters:
+** sender -- the name of the sender on the local machine.
+** rpool -- resource pool from which to allocate result
+**
+** Returns:
+** The external name for this sender, if derivable from the
+** database. Storage allocated from rpool.
+** NULL -- if nothing is changed from the database.
+**
+** Side Effects:
+** none.
+*/
+
+char *
+udbsender(sender, rpool)
+ char *sender;
+ SM_RPOOL_T *rpool;
+{
+ return udbmatch(sender, "mailname", rpool);
+}
+/*
+** UDBMATCH -- match user in field, return result of lookup.
+**
+** Parameters:
+** user -- the name of the user.
+** field -- the field to lookup.
+** rpool -- resource pool from which to allocate result
+**
+** Returns:
+** The external name for this sender, if derivable from the
+** database. Storage allocated from rpool.
+** NULL -- if nothing is changed from the database.
+**
+** Side Effects:
+** none.
+*/
+
+static char *
+udbmatch(user, field, rpool)
+ char *user;
+ char *field;
+ SM_RPOOL_T *rpool;
+{
+ register char *p;
+ register struct udbent *up;
+ int i;
+ int keylen;
+ DBT key, info;
+ char keybuf[MAXKEY];
+
+ if (tTd(28, 1))
+ sm_dprintf("udbmatch(%s, %s)\n", user, field);
+
+ if (!UdbInitialized)
+ {
+ if (_udbx_init(CurEnv) == EX_TEMPFAIL)
+ return NULL;
+ }
+
+ /* short circuit if no spec */
+ if (UdbSpec == NULL || UdbSpec[0] == '\0')
+ return NULL;
+
+ /* short circuit name begins with '\\' since it can't possibly match */
+ if (user[0] == '\\')
+ return NULL;
+
+ /* long names can never match and are a pain to deal with */
+ i = strlen(field);
+ if (i < sizeof "maildrop")
+ i = sizeof "maildrop";
+ if ((strlen(user) + i) > sizeof keybuf - 4)
+ return NULL;
+
+ /* names beginning with colons indicate metadata */
+ if (user[0] == ':')
+ return NULL;
+
+ /* build database key */
+ (void) sm_strlcpyn(keybuf, sizeof keybuf, 3, user, ":", field);
+ keylen = strlen(keybuf);
+
+ for (up = UdbEnts; up->udb_type != UDB_EOLIST; up++)
+ {
+ /*
+ ** Select action based on entry type.
+ */
+
+ switch (up->udb_type)
+ {
+# if NEWDB
+ case UDB_DBFETCH:
+ memset(&key, '\0', sizeof key);
+ memset(&info, '\0', sizeof info);
+ key.data = keybuf;
+ key.size = keylen;
+# if DB_VERSION_MAJOR < 2
+ i = (*up->udb_dbp->get)(up->udb_dbp, &key, &info, 0);
+# else /* DB_VERSION_MAJOR < 2 */
+ i = errno = (*up->udb_dbp->get)(up->udb_dbp, NULL,
+ &key, &info, 0);
+# endif /* DB_VERSION_MAJOR < 2 */
+ if (i != 0 || info.size <= 0)
+ {
+ if (tTd(28, 2))
+ sm_dprintf("udbmatch: no match on %s (%d) via db\n",
+ keybuf, keylen);
+ continue;
+ }
+
+ p = sm_rpool_malloc_x(rpool, info.size + 1);
+ memmove(p, info.data, info.size);
+ p[info.size] = '\0';
+ if (tTd(28, 1))
+ sm_dprintf("udbmatch ==> %s\n", p);
+ return p;
+# endif /* NEWDB */
+
+# if HESIOD
+ case UDB_HESIOD:
+ key.data = keybuf;
+ key.size = keylen;
+ i = hes_udb_get(&key, &info);
+ if (i != 0 || info.size <= 0)
+ {
+ if (tTd(28, 2))
+ sm_dprintf("udbmatch: no match on %s (%d) via hesiod\n",
+ keybuf, keylen);
+ continue;
+ }
+
+ p = sm_rpool_malloc_x(rpool, info.size + 1);
+ memmove(p, info.data, info.size);
+ p[info.size] = '\0';
+ if (tTd(28, 1))
+ sm_dprintf("udbmatch ==> %s\n", p);
+ return p;
+# endif /* HESIOD */
+ }
+ }
+
+ if (strcmp(field, "mailname") != 0)
+ return NULL;
+
+ /*
+ ** Nothing yet. Search again for a default case. But only
+ ** use it if we also have a forward (:maildrop) pointer already
+ ** in the database.
+ */
+
+ /* build database key */
+ (void) sm_strlcpyn(keybuf, sizeof keybuf, 2, user, ":maildrop");
+ keylen = strlen(keybuf);
+
+ for (up = UdbEnts; up->udb_type != UDB_EOLIST; up++)
+ {
+ switch (up->udb_type)
+ {
+# if NEWDB
+ case UDB_DBFETCH:
+ /* get the default case for this database */
+ if (up->udb_default == NULL)
+ {
+ memset(&key, '\0', sizeof key);
+ memset(&info, '\0', sizeof info);
+ key.data = ":default:mailname";
+ key.size = strlen(key.data);
+# if DB_VERSION_MAJOR < 2
+ i = (*up->udb_dbp->get)(up->udb_dbp,
+ &key, &info, 0);
+# else /* DB_VERSION_MAJOR < 2 */
+ i = errno = (*up->udb_dbp->get)(up->udb_dbp,
+ NULL, &key,
+ &info, 0);
+# endif /* DB_VERSION_MAJOR < 2 */
+ if (i != 0 || info.size <= 0)
+ {
+ /* no default case */
+ up->udb_default = "";
+ continue;
+ }
+
+ /* save the default case */
+ up->udb_default = sm_pmalloc_x(info.size + 1);
+ memmove(up->udb_default, info.data, info.size);
+ up->udb_default[info.size] = '\0';
+ }
+ else if (up->udb_default[0] == '\0')
+ continue;
+
+ /* we have a default case -- verify user:maildrop */
+ memset(&key, '\0', sizeof key);
+ memset(&info, '\0', sizeof info);
+ key.data = keybuf;
+ key.size = keylen;
+# if DB_VERSION_MAJOR < 2
+ i = (*up->udb_dbp->get)(up->udb_dbp, &key, &info, 0);
+# else /* DB_VERSION_MAJOR < 2 */
+ i = errno = (*up->udb_dbp->get)(up->udb_dbp, NULL,
+ &key, &info, 0);
+# endif /* DB_VERSION_MAJOR < 2 */
+ if (i != 0 || info.size <= 0)
+ {
+ /* nope -- no aliasing for this user */
+ continue;
+ }
+
+ /* they exist -- build the actual address */
+ i = strlen(user) + strlen(up->udb_default) + 2;
+ p = sm_rpool_malloc_x(rpool, i);
+ (void) sm_strlcpyn(p, i, 3, user, "@", up->udb_default);
+ if (tTd(28, 1))
+ sm_dprintf("udbmatch ==> %s\n", p);
+ return p;
+# endif /* NEWDB */
+
+# if HESIOD
+ case UDB_HESIOD:
+ /* get the default case for this database */
+ if (up->udb_default == NULL)
+ {
+ key.data = ":default:mailname";
+ key.size = strlen(key.data);
+ i = hes_udb_get(&key, &info);
+
+ if (i != 0 || info.size <= 0)
+ {
+ /* no default case */
+ up->udb_default = "";
+ continue;
+ }
+
+ /* save the default case */
+ up->udb_default = sm_pmalloc_x(info.size + 1);
+ memmove(up->udb_default, info.data, info.size);
+ up->udb_default[info.size] = '\0';
+ }
+ else if (up->udb_default[0] == '\0')
+ continue;
+
+ /* we have a default case -- verify user:maildrop */
+ key.data = keybuf;
+ key.size = keylen;
+ i = hes_udb_get(&key, &info);
+ if (i != 0 || info.size <= 0)
+ {
+ /* nope -- no aliasing for this user */
+ continue;
+ }
+
+ /* they exist -- build the actual address */
+ i = strlen(user) + strlen(up->udb_default) + 2;
+ p = sm_rpool_malloc_x(rpool, i);
+ (void) sm_strlcpyn(p, i, 3, user, "@", up->udb_default);
+ if (tTd(28, 1))
+ sm_dprintf("udbmatch ==> %s\n", p);
+ return p;
+ break;
+# endif /* HESIOD */
+ }
+ }
+
+ /* still nothing.... too bad */
+ return NULL;
+}
+/*
+** UDB_MAP_LOOKUP -- look up arbitrary entry in user database map
+**
+** Parameters:
+** map -- the map being queried.
+** name -- the name to look up.
+** av -- arguments to the map lookup.
+** statp -- to get any error status.
+**
+** Returns:
+** NULL if name not found in map.
+** The rewritten name otherwise.
+*/
+
+/* ARGSUSED3 */
+char *
+udb_map_lookup(map, name, av, statp)
+ MAP *map;
+ char *name;
+ char **av;
+ int *statp;
+{
+ char *val;
+ char *key;
+ char *SM_NONVOLATILE result = NULL;
+ char keybuf[MAXNAME + 1];
+
+ if (tTd(28, 20) || tTd(38, 20))
+ sm_dprintf("udb_map_lookup(%s, %s)\n", map->map_mname, name);
+
+ if (bitset(MF_NOFOLDCASE, map->map_mflags))
+ {
+ key = name;
+ }
+ else
+ {
+ int keysize = strlen(name);
+
+ if (keysize > sizeof keybuf - 1)
+ keysize = sizeof keybuf - 1;
+ memmove(keybuf, name, keysize);
+ keybuf[keysize] = '\0';
+ makelower(keybuf);
+ key = keybuf;
+ }
+ val = udbmatch(key, map->map_file, NULL);
+ if (val == NULL)
+ return NULL;
+ SM_TRY
+ if (bitset(MF_MATCHONLY, map->map_mflags))
+ result = map_rewrite(map, name, strlen(name), NULL);
+ else
+ result = map_rewrite(map, val, strlen(val), av);
+ SM_FINALLY
+ sm_free(val);
+ SM_END_TRY
+ return result;
+}
+/*
+** _UDBX_INIT -- parse the UDB specification, opening any valid entries.
+**
+** Parameters:
+** e -- the current envelope.
+**
+** Returns:
+** EX_TEMPFAIL -- if it appeared it couldn't get hold of a
+** database due to a host being down or some similar
+** (recoverable) situation.
+** EX_OK -- otherwise.
+**
+** Side Effects:
+** Fills in the UdbEnts structure from UdbSpec.
+*/
+
+# define MAXUDBOPTS 27
+
+static int
+_udbx_init(e)
+ ENVELOPE *e;
+{
+ int ents = 0;
+ register char *p;
+ register struct udbent *up;
+
+ if (UdbInitialized)
+ return EX_OK;
+
+# ifdef UDB_DEFAULT_SPEC
+ if (UdbSpec == NULL)
+ UdbSpec = UDB_DEFAULT_SPEC;
+# endif /* UDB_DEFAULT_SPEC */
+
+ p = UdbSpec;
+ up = UdbEnts;
+ while (p != NULL)
+ {
+ char *spec;
+ int l;
+ struct udb_option opts[MAXUDBOPTS + 1];
+
+ while (*p == ' ' || *p == '\t' || *p == ',')
+ p++;
+ if (*p == '\0')
+ break;
+ spec = p;
+ p = strchr(p, ',');
+ if (p != NULL)
+ *p++ = '\0';
+
+ if (ents >= MAXUDBENT)
+ {
+ syserr("Maximum number of UDB entries exceeded");
+ break;
+ }
+
+ /* extract options */
+ (void) _udb_parsespec(spec, opts, MAXUDBOPTS);
+
+ /*
+ ** Decode database specification.
+ **
+ ** In the sendmail tradition, the leading character
+ ** defines the semantics of the rest of the entry.
+ **
+ ** @hostname -- forward email to the indicated host.
+ ** This should be the last in the list,
+ ** since it always matches the input.
+ ** /dbname -- search the named database on the local
+ ** host using the Berkeley db package.
+ ** Hesiod -- search the named database with BIND
+ ** using the MIT Hesiod package.
+ */
+
+ switch (*spec)
+ {
+ case '@': /* forward to remote host */
+ up->udb_type = UDB_FORWARD;
+ up->udb_pid = CurrentPid;
+ up->udb_fwdhost = spec + 1;
+ ents++;
+ up++;
+ break;
+
+# if HESIOD
+ case 'h': /* use hesiod */
+ case 'H':
+ if (sm_strcasecmp(spec, "hesiod") != 0)
+ goto badspec;
+ up->udb_type = UDB_HESIOD;
+ up->udb_pid = CurrentPid;
+ ents++;
+ up++;
+ break;
+# endif /* HESIOD */
+
+# if NEWDB
+ case '/': /* look up remote name */
+ l = strlen(spec);
+ if (l > 3 && strcmp(&spec[l - 3], ".db") == 0)
+ {
+ up->udb_dbname = spec;
+ }
+ else
+ {
+ up->udb_dbname = sm_pmalloc_x(l + 4);
+ (void) sm_strlcpyn(up->udb_dbname, l + 4, 2,
+ spec, ".db");
+ }
+ errno = 0;
+# if DB_VERSION_MAJOR < 2
+ up->udb_dbp = dbopen(up->udb_dbname, O_RDONLY,
+ 0644, DB_BTREE, NULL);
+# else /* DB_VERSION_MAJOR < 2 */
+ {
+ int flags = DB_RDONLY;
+# if DB_VERSION_MAJOR > 2
+ int ret;
+# endif /* DB_VERSION_MAJOR > 2 */
+
+ SM_DB_FLAG_ADD(flags);
+ up->udb_dbp = NULL;
+# if DB_VERSION_MAJOR > 2
+ ret = db_create(&up->udb_dbp, NULL, 0);
+ if (ret != 0)
+ {
+ (void) up->udb_dbp->close(up->udb_dbp,
+ 0);
+ up->udb_dbp = NULL;
+ }
+ else
+ {
+ ret = up->udb_dbp->open(up->udb_dbp,
+ DBTXN
+ up->udb_dbname,
+ NULL,
+ DB_BTREE,
+ flags,
+ 0644);
+ if (ret != 0)
+ {
+#ifdef DB_OLD_VERSION
+ if (ret == DB_OLD_VERSION)
+ ret = EINVAL;
+#endif /* DB_OLD_VERSION */
+ (void) up->udb_dbp->close(up->udb_dbp, 0);
+ up->udb_dbp = NULL;
+ }
+ }
+ errno = ret;
+# else /* DB_VERSION_MAJOR > 2 */
+ errno = db_open(up->udb_dbname, DB_BTREE,
+ flags, 0644, NULL,
+ NULL, &up->udb_dbp);
+# endif /* DB_VERSION_MAJOR > 2 */
+ }
+# endif /* DB_VERSION_MAJOR < 2 */
+ if (up->udb_dbp == NULL)
+ {
+ if (tTd(28, 1))
+ {
+ int save_errno = errno;
+
+# if DB_VERSION_MAJOR < 2
+ sm_dprintf("dbopen(%s): %s\n",
+# else /* DB_VERSION_MAJOR < 2 */
+ sm_dprintf("db_open(%s): %s\n",
+# endif /* DB_VERSION_MAJOR < 2 */
+ up->udb_dbname,
+ sm_errstring(errno));
+ errno = save_errno;
+ }
+ if (errno != ENOENT && errno != EACCES)
+ {
+ if (LogLevel > 2)
+ sm_syslog(LOG_ERR, e->e_id,
+# if DB_VERSION_MAJOR < 2
+ "dbopen(%s): %s",
+# else /* DB_VERSION_MAJOR < 2 */
+ "db_open(%s): %s",
+# endif /* DB_VERSION_MAJOR < 2 */
+ up->udb_dbname,
+ sm_errstring(errno));
+ up->udb_type = UDB_EOLIST;
+ if (up->udb_dbname != spec)
+ sm_free(up->udb_dbname); /* XXX */
+ goto tempfail;
+ }
+ if (up->udb_dbname != spec)
+ sm_free(up->udb_dbname); /* XXX */
+ break;
+ }
+ if (tTd(28, 1))
+ {
+# if DB_VERSION_MAJOR < 2
+ sm_dprintf("_udbx_init: dbopen(%s)\n",
+# else /* DB_VERSION_MAJOR < 2 */
+ sm_dprintf("_udbx_init: db_open(%s)\n",
+# endif /* DB_VERSION_MAJOR < 2 */
+ up->udb_dbname);
+ }
+ up->udb_type = UDB_DBFETCH;
+ up->udb_pid = CurrentPid;
+ ents++;
+ up++;
+ break;
+# endif /* NEWDB */
+
+ default:
+# if HESIOD
+badspec:
+# endif /* HESIOD */
+ syserr("Unknown UDB spec %s", spec);
+ break;
+ }
+ }
+ up->udb_type = UDB_EOLIST;
+
+ if (tTd(28, 4))
+ {
+ for (up = UdbEnts; up->udb_type != UDB_EOLIST; up++)
+ {
+ switch (up->udb_type)
+ {
+ case UDB_REMOTE:
+ sm_dprintf("REMOTE: addr %s, timeo %d\n",
+ anynet_ntoa((SOCKADDR *) &up->udb_addr),
+ up->udb_timeout);
+ break;
+
+ case UDB_DBFETCH:
+# if NEWDB
+ sm_dprintf("FETCH: file %s\n",
+ up->udb_dbname);
+# else /* NEWDB */
+ sm_dprintf("FETCH\n");
+# endif /* NEWDB */
+ break;
+
+ case UDB_FORWARD:
+ sm_dprintf("FORWARD: host %s\n",
+ up->udb_fwdhost);
+ break;
+
+ case UDB_HESIOD:
+ sm_dprintf("HESIOD\n");
+ break;
+
+ default:
+ sm_dprintf("UNKNOWN\n");
+ break;
+ }
+ }
+ }
+
+ UdbInitialized = true;
+ errno = 0;
+ return EX_OK;
+
+ /*
+ ** On temporary failure, back out anything we've already done
+ */
+
+ tempfail:
+# if NEWDB
+ for (up = UdbEnts; up->udb_type != UDB_EOLIST; up++)
+ {
+ if (up->udb_type == UDB_DBFETCH)
+ {
+# if DB_VERSION_MAJOR < 2
+ (*up->udb_dbp->close)(up->udb_dbp);
+# else /* DB_VERSION_MAJOR < 2 */
+ errno = (*up->udb_dbp->close)(up->udb_dbp, 0);
+# endif /* DB_VERSION_MAJOR < 2 */
+ if (tTd(28, 1))
+ sm_dprintf("_udbx_init: db->close(%s)\n",
+ up->udb_dbname);
+ }
+ }
+# endif /* NEWDB */
+ return EX_TEMPFAIL;
+}
+
+static int
+_udb_parsespec(udbspec, opt, maxopts)
+ char *udbspec;
+ struct udb_option opt[];
+ int maxopts;
+{
+ register char *spec;
+ register char *spec_end;
+ register int optnum;
+
+ spec_end = strchr(udbspec, ':');
+ for (optnum = 0; optnum < maxopts && (spec = spec_end) != NULL; optnum++)
+ {
+ register char *p;
+
+ while (isascii(*spec) && isspace(*spec))
+ spec++;
+ spec_end = strchr(spec, ':');
+ if (spec_end != NULL)
+ *spec_end++ = '\0';
+
+ opt[optnum].udbo_name = spec;
+ opt[optnum].udbo_val = NULL;
+ p = strchr(spec, '=');
+ if (p != NULL)
+ opt[optnum].udbo_val = ++p;
+ }
+ return optnum;
+}
+/*
+** _UDBX_CLOSE -- close all file based UDB entries.
+**
+** Parameters:
+** none
+**
+** Returns:
+** none
+*/
+void
+_udbx_close()
+{
+ struct udbent *up;
+
+ if (!UdbInitialized)
+ return;
+
+ for (up = UdbEnts; up->udb_type != UDB_EOLIST; up++)
+ {
+ if (up->udb_pid != CurrentPid)
+ continue;
+
+# if NEWDB
+ if (up->udb_type == UDB_DBFETCH)
+ {
+# if DB_VERSION_MAJOR < 2
+ (*up->udb_dbp->close)(up->udb_dbp);
+# else /* DB_VERSION_MAJOR < 2 */
+ errno = (*up->udb_dbp->close)(up->udb_dbp, 0);
+# endif /* DB_VERSION_MAJOR < 2 */
+ }
+ if (tTd(28, 1))
+ sm_dprintf("_udbx_init: db->close(%s)\n",
+ up->udb_dbname);
+# endif /* NEWDB */
+ }
+}
+
+# if HESIOD
+
+static int
+hes_udb_get(key, info)
+ DBT *key;
+ DBT *info;
+{
+ char *name, *type;
+ char **hp;
+ char kbuf[MAXKEY + 1];
+
+ if (sm_strlcpy(kbuf, key->data, sizeof kbuf) >= sizeof kbuf)
+ return 0;
+ name = kbuf;
+ type = strrchr(name, ':');
+ if (type == NULL)
+ return 1;
+ *type++ = '\0';
+ if (strchr(name, '@') != NULL)
+ return 1;
+
+ if (tTd(28, 1))
+ sm_dprintf("hes_udb_get(%s, %s)\n", name, type);
+
+ /* make the hesiod query */
+# ifdef HESIOD_INIT
+ if (HesiodContext == NULL && hesiod_init(&HesiodContext) != 0)
+ return -1;
+ hp = hesiod_resolve(HesiodContext, name, type);
+# else /* HESIOD_INIT */
+ hp = hes_resolve(name, type);
+# endif /* HESIOD_INIT */
+ *--type = ':';
+# ifdef HESIOD_INIT
+ if (hp == NULL)
+ return 1;
+ if (*hp == NULL)
+ {
+ hesiod_free_list(HesiodContext, hp);
+ if (errno == ECONNREFUSED || errno == EMSGSIZE)
+ return -1;
+ return 1;
+ }
+# else /* HESIOD_INIT */
+ if (hp == NULL || hp[0] == NULL)
+ {
+ /* network problem or timeout */
+ if (hes_error() == HES_ER_NET)
+ return -1;
+
+ return 1;
+ }
+# endif /* HESIOD_INIT */
+ else
+ {
+ /*
+ ** If there are multiple matches, just return the
+ ** first one.
+ **
+ ** XXX These should really be returned; for example,
+ ** XXX it is legal for :maildrop to be multi-valued.
+ */
+
+ info->data = hp[0];
+ info->size = (size_t) strlen(info->data);
+ }
+
+ if (tTd(28, 80))
+ sm_dprintf("hes_udb_get => %s\n", *hp);
+
+ return 0;
+}
+# endif /* HESIOD */
+
+#else /* USERDB */
+
+int
+udbexpand(a, sendq, aliaslevel, e)
+ ADDRESS *a;
+ ADDRESS **sendq;
+ int aliaslevel;
+ ENVELOPE *e;
+{
+ return EX_OK;
+}
+
+#endif /* USERDB */
diff --git a/usr/src/cmd/sendmail/src/usersmtp.c b/usr/src/cmd/sendmail/src/usersmtp.c
new file mode 100644
index 0000000000..923c89d519
--- /dev/null
+++ b/usr/src/cmd/sendmail/src/usersmtp.c
@@ -0,0 +1,3356 @@
+/*
+ * Copyright (c) 1998-2005 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sendmail.h>
+
+SM_RCSID("@(#)$Id: usersmtp.c,v 8.463 2005/03/16 00:36:09 ca Exp $")
+
+#include <sysexits.h>
+
+
+static void datatimeout __P((int));
+static void esmtp_check __P((char *, bool, MAILER *, MCI *, ENVELOPE *));
+static void helo_options __P((char *, bool, MAILER *, MCI *, ENVELOPE *));
+static int smtprcptstat __P((ADDRESS *, MAILER *, MCI *, ENVELOPE *));
+
+#if SASL
+extern void *sm_sasl_malloc __P((unsigned long));
+extern void sm_sasl_free __P((void *));
+#endif /* SASL */
+
+/*
+** USERSMTP -- run SMTP protocol from the user end.
+**
+** This protocol is described in RFC821.
+*/
+
+#define REPLYTYPE(r) ((r) / 100) /* first digit of reply code */
+#define REPLYCLASS(r) (((r) / 10) % 10) /* second digit of reply code */
+#define SMTPCLOSING 421 /* "Service Shutting Down" */
+
+#define ENHSCN(e, d) ((e) == NULL ? (d) : (e))
+
+#define ENHSCN_RPOOL(e, d, rpool) \
+ ((e) == NULL ? (d) : sm_rpool_strdup_x(rpool, e))
+
+static char SmtpMsgBuffer[MAXLINE]; /* buffer for commands */
+static char SmtpReplyBuffer[MAXLINE]; /* buffer for replies */
+static bool SmtpNeedIntro; /* need "while talking" in transcript */
+/*
+** SMTPINIT -- initialize SMTP.
+**
+** Opens the connection and sends the initial protocol.
+**
+** Parameters:
+** m -- mailer to create connection to.
+** mci -- the mailer connection info.
+** e -- the envelope.
+** onlyhelo -- send only helo command?
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** creates connection and sends initial protocol.
+*/
+
+void
+smtpinit(m, mci, e, onlyhelo)
+ MAILER *m;
+ register MCI *mci;
+ ENVELOPE *e;
+ bool onlyhelo;
+{
+ register int r;
+ int state;
+ register char *p;
+ register char *hn;
+ char *enhsc;
+
+ enhsc = NULL;
+ if (tTd(18, 1))
+ {
+ sm_dprintf("smtpinit ");
+ mci_dump(sm_debug_file(), mci, false);
+ }
+
+ /*
+ ** Open the connection to the mailer.
+ */
+
+ SmtpError[0] = '\0';
+ SmtpMsgBuffer[0] = '\0';
+ CurHostName = mci->mci_host; /* XXX UGLY XXX */
+ if (CurHostName == NULL)
+ CurHostName = MyHostName;
+ SmtpNeedIntro = true;
+ state = mci->mci_state;
+ switch (state)
+ {
+ case MCIS_MAIL:
+ case MCIS_RCPT:
+ case MCIS_DATA:
+ /* need to clear old information */
+ smtprset(m, mci, e);
+ /* FALLTHROUGH */
+
+ case MCIS_OPEN:
+ if (!onlyhelo)
+ return;
+ break;
+
+ case MCIS_ERROR:
+ case MCIS_QUITING:
+ case MCIS_SSD:
+ /* shouldn't happen */
+ smtpquit(m, mci, e);
+ /* FALLTHROUGH */
+
+ case MCIS_CLOSED:
+ syserr("451 4.4.0 smtpinit: state CLOSED (was %d)", state);
+ return;
+
+ case MCIS_OPENING:
+ break;
+ }
+ if (onlyhelo)
+ goto helo;
+
+ mci->mci_state = MCIS_OPENING;
+ clrsessenvelope(e);
+
+ /*
+ ** Get the greeting message.
+ ** This should appear spontaneously. Give it five minutes to
+ ** happen.
+ */
+
+ SmtpPhase = mci->mci_phase = "client greeting";
+ sm_setproctitle(true, e, "%s %s: %s",
+ qid_printname(e), CurHostName, mci->mci_phase);
+ r = reply(m, mci, e, TimeOuts.to_initial, esmtp_check, NULL,
+ XS_DEFAULT);
+ if (r < 0)
+ goto tempfail1;
+ if (REPLYTYPE(r) == 4)
+ goto tempfail2;
+ if (REPLYTYPE(r) != 2)
+ goto unavailable;
+
+ /*
+ ** Send the HELO command.
+ ** My mother taught me to always introduce myself.
+ */
+
+helo:
+ if (bitnset(M_ESMTP, m->m_flags) || bitnset(M_LMTP, m->m_flags))
+ mci->mci_flags |= MCIF_ESMTP;
+ hn = mci->mci_heloname ? mci->mci_heloname : MyHostName;
+
+tryhelo:
+#if _FFR_IGNORE_EXT_ON_HELO
+ mci->mci_flags &= ~MCIF_HELO;
+#endif /* _FFR_IGNORE_EXT_ON_HELO */
+ if (bitnset(M_LMTP, m->m_flags))
+ {
+ smtpmessage("LHLO %s", m, mci, hn);
+ SmtpPhase = mci->mci_phase = "client LHLO";
+ }
+ else if (bitset(MCIF_ESMTP, mci->mci_flags) &&
+ !bitnset(M_FSMTP, m->m_flags))
+ {
+ smtpmessage("EHLO %s", m, mci, hn);
+ SmtpPhase = mci->mci_phase = "client EHLO";
+ }
+ else
+ {
+ smtpmessage("HELO %s", m, mci, hn);
+ SmtpPhase = mci->mci_phase = "client HELO";
+#if _FFR_IGNORE_EXT_ON_HELO
+ mci->mci_flags |= MCIF_HELO;
+#endif /* _FFR_IGNORE_EXT_ON_HELO */
+ }
+ sm_setproctitle(true, e, "%s %s: %s", qid_printname(e),
+ CurHostName, mci->mci_phase);
+ r = reply(m, mci, e,
+ bitnset(M_LMTP, m->m_flags) ? TimeOuts.to_lhlo
+ : TimeOuts.to_helo,
+ helo_options, NULL, XS_DEFAULT);
+ if (r < 0)
+ goto tempfail1;
+ else if (REPLYTYPE(r) == 5)
+ {
+ if (bitset(MCIF_ESMTP, mci->mci_flags) &&
+ !bitnset(M_LMTP, m->m_flags))
+ {
+ /* try old SMTP instead */
+ mci->mci_flags &= ~MCIF_ESMTP;
+ goto tryhelo;
+ }
+ goto unavailable;
+ }
+ else if (REPLYTYPE(r) != 2)
+ goto tempfail2;
+
+ /*
+ ** Check to see if we actually ended up talking to ourself.
+ ** This means we didn't know about an alias or MX, or we managed
+ ** to connect to an echo server.
+ */
+
+ p = strchr(&SmtpReplyBuffer[4], ' ');
+ if (p != NULL)
+ *p = '\0';
+ if (!bitnset(M_NOLOOPCHECK, m->m_flags) &&
+ !bitnset(M_LMTP, m->m_flags) &&
+ sm_strcasecmp(&SmtpReplyBuffer[4], MyHostName) == 0)
+ {
+ syserr("553 5.3.5 %s config error: mail loops back to me (MX problem?)",
+ CurHostName);
+ mci_setstat(mci, EX_CONFIG, "5.3.5",
+ "553 5.3.5 system config error");
+ mci->mci_errno = 0;
+ smtpquit(m, mci, e);
+ return;
+ }
+
+ /*
+ ** If this is expected to be another sendmail, send some internal
+ ** commands.
+ ** If we're running as MSP, "propagate" -v flag if possible.
+ */
+
+ if ((UseMSP && Verbose && bitset(MCIF_VERB, mci->mci_flags))
+# if !_FFR_DEPRECATE_MAILER_FLAG_I
+ || bitnset(M_INTERNAL, m->m_flags)
+# endif /* !_FFR_DEPRECATE_MAILER_FLAG_I */
+ )
+ {
+ /* tell it to be verbose */
+ smtpmessage("VERB", m, mci);
+ r = reply(m, mci, e, TimeOuts.to_miscshort, NULL, &enhsc,
+ XS_DEFAULT);
+ if (r < 0)
+ goto tempfail1;
+ }
+
+ if (mci->mci_state != MCIS_CLOSED)
+ {
+ mci->mci_state = MCIS_OPEN;
+ return;
+ }
+
+ /* got a 421 error code during startup */
+
+ tempfail1:
+ mci_setstat(mci, EX_TEMPFAIL, ENHSCN(enhsc, "4.4.2"), NULL);
+ if (mci->mci_state != MCIS_CLOSED)
+ smtpquit(m, mci, e);
+ return;
+
+ tempfail2:
+ /* XXX should use code from other end iff ENHANCEDSTATUSCODES */
+ mci_setstat(mci, EX_TEMPFAIL, ENHSCN(enhsc, "4.5.0"),
+ SmtpReplyBuffer);
+ if (mci->mci_state != MCIS_CLOSED)
+ smtpquit(m, mci, e);
+ return;
+
+ unavailable:
+ mci_setstat(mci, EX_UNAVAILABLE, "5.5.0", SmtpReplyBuffer);
+ smtpquit(m, mci, e);
+ return;
+}
+/*
+** ESMTP_CHECK -- check to see if this implementation likes ESMTP protocol
+**
+** Parameters:
+** line -- the response line.
+** firstline -- set if this is the first line of the reply.
+** m -- the mailer.
+** mci -- the mailer connection info.
+** e -- the envelope.
+**
+** Returns:
+** none.
+*/
+
+static void
+esmtp_check(line, firstline, m, mci, e)
+ char *line;
+ bool firstline;
+ MAILER *m;
+ register MCI *mci;
+ ENVELOPE *e;
+{
+ if (strstr(line, "ESMTP") != NULL)
+ mci->mci_flags |= MCIF_ESMTP;
+
+ /*
+ ** Dirty hack below. Quoting the author:
+ ** This was a response to people who wanted SMTP transmission to be
+ ** just-send-8 by default. Essentially, you could put this tag into
+ ** your greeting message to behave as though the F=8 flag was set on
+ ** the mailer.
+ */
+
+ if (strstr(line, "8BIT-OK") != NULL)
+ mci->mci_flags |= MCIF_8BITOK;
+}
+
+#if SASL
+/* specify prototype so compiler can check calls */
+static char *str_union __P((char *, char *, SM_RPOOL_T *));
+
+/*
+** STR_UNION -- create the union of two lists
+**
+** Parameters:
+** s1, s2 -- lists of items (separated by single blanks).
+** rpool -- resource pool from which result is allocated.
+**
+** Returns:
+** the union of both lists.
+*/
+
+static char *
+str_union(s1, s2, rpool)
+ char *s1, *s2;
+ SM_RPOOL_T *rpool;
+{
+ char *hr, *h1, *h, *res;
+ int l1, l2, rl;
+
+ if (s1 == NULL || *s1 == '\0')
+ return s2;
+ if (s2 == NULL || *s2 == '\0')
+ return s1;
+ l1 = strlen(s1);
+ l2 = strlen(s2);
+ rl = l1 + l2;
+ res = (char *) sm_rpool_malloc(rpool, rl + 2);
+ if (res == NULL)
+ {
+ if (l1 > l2)
+ return s1;
+ return s2;
+ }
+ (void) sm_strlcpy(res, s1, rl);
+ hr = res + l1;
+ h1 = s2;
+ h = s2;
+
+ /* walk through s2 */
+ while (h != NULL && *h1 != '\0')
+ {
+ /* is there something after the current word? */
+ if ((h = strchr(h1, ' ')) != NULL)
+ *h = '\0';
+ l1 = strlen(h1);
+
+ /* does the current word appear in s1 ? */
+ if (iteminlist(h1, s1, " ") == NULL)
+ {
+ /* add space as delimiter */
+ *hr++ = ' ';
+
+ /* copy the item */
+ memcpy(hr, h1, l1);
+
+ /* advance pointer in result list */
+ hr += l1;
+ *hr = '\0';
+ }
+ if (h != NULL)
+ {
+ /* there are more items */
+ *h = ' ';
+ h1 = h + 1;
+ }
+ }
+ return res;
+}
+#endif /* SASL */
+
+/*
+** HELO_OPTIONS -- process the options on a HELO line.
+**
+** Parameters:
+** line -- the response line.
+** firstline -- set if this is the first line of the reply.
+** m -- the mailer.
+** mci -- the mailer connection info.
+** e -- the envelope (unused).
+**
+** Returns:
+** none.
+*/
+
+static void
+helo_options(line, firstline, m, mci, e)
+ char *line;
+ bool firstline;
+ MAILER *m;
+ register MCI *mci;
+ ENVELOPE *e;
+{
+ register char *p;
+#if _FFR_IGNORE_EXT_ON_HELO
+ static bool logged = false;
+#endif /* _FFR_IGNORE_EXT_ON_HELO */
+
+ if (firstline)
+ {
+#if SASL
+ mci->mci_saslcap = NULL;
+#endif /* SASL */
+#if _FFR_IGNORE_EXT_ON_HELO
+ logged = false;
+#endif /* _FFR_IGNORE_EXT_ON_HELO */
+ return;
+ }
+#if _FFR_IGNORE_EXT_ON_HELO
+ else if (bitset(MCIF_HELO, mci->mci_flags))
+ {
+ if (LogLevel > 8 && !logged)
+ {
+ sm_syslog(LOG_WARNING, NOQID,
+ "server=%s [%s] returned extensions despite HELO command",
+ macvalue(macid("{server_name}"), e),
+ macvalue(macid("{server_addr}"), e));
+ logged = true;
+ }
+ return;
+ }
+#endif /* _FFR_IGNORE_EXT_ON_HELO */
+
+ if (strlen(line) < 5)
+ return;
+ line += 4;
+ p = strpbrk(line, " =");
+ if (p != NULL)
+ *p++ = '\0';
+ if (sm_strcasecmp(line, "size") == 0)
+ {
+ mci->mci_flags |= MCIF_SIZE;
+ if (p != NULL)
+ mci->mci_maxsize = atol(p);
+ }
+ else if (sm_strcasecmp(line, "8bitmime") == 0)
+ {
+ mci->mci_flags |= MCIF_8BITMIME;
+ mci->mci_flags &= ~MCIF_7BIT;
+ }
+ else if (sm_strcasecmp(line, "expn") == 0)
+ mci->mci_flags |= MCIF_EXPN;
+ else if (sm_strcasecmp(line, "dsn") == 0)
+ mci->mci_flags |= MCIF_DSN;
+ else if (sm_strcasecmp(line, "enhancedstatuscodes") == 0)
+ mci->mci_flags |= MCIF_ENHSTAT;
+ else if (sm_strcasecmp(line, "pipelining") == 0)
+ mci->mci_flags |= MCIF_PIPELINED;
+ else if (sm_strcasecmp(line, "verb") == 0)
+ mci->mci_flags |= MCIF_VERB;
+#if STARTTLS
+ else if (sm_strcasecmp(line, "starttls") == 0)
+ mci->mci_flags |= MCIF_TLS;
+#endif /* STARTTLS */
+ else if (sm_strcasecmp(line, "deliverby") == 0)
+ {
+ mci->mci_flags |= MCIF_DLVR_BY;
+ if (p != NULL)
+ mci->mci_min_by = atol(p);
+ }
+#if SASL
+ else if (sm_strcasecmp(line, "auth") == 0)
+ {
+ if (p != NULL && *p != '\0')
+ {
+ if (mci->mci_saslcap != NULL)
+ {
+ /*
+ ** Create the union with previous auth
+ ** offerings because we recognize "auth "
+ ** and "auth=" (old format).
+ */
+
+ mci->mci_saslcap = str_union(mci->mci_saslcap,
+ p, mci->mci_rpool);
+ mci->mci_flags |= MCIF_AUTH;
+ }
+ else
+ {
+ int l;
+
+ l = strlen(p) + 1;
+ mci->mci_saslcap = (char *)
+ sm_rpool_malloc(mci->mci_rpool, l);
+ if (mci->mci_saslcap != NULL)
+ {
+ (void) sm_strlcpy(mci->mci_saslcap, p,
+ l);
+ mci->mci_flags |= MCIF_AUTH;
+ }
+ }
+ }
+ }
+#endif /* SASL */
+}
+#if SASL
+
+static int getsimple __P((void *, int, const char **, unsigned *));
+static int getsecret __P((sasl_conn_t *, void *, int, sasl_secret_t **));
+static int saslgetrealm __P((void *, int, const char **, const char **));
+static int readauth __P((char *, bool, SASL_AI_T *m, SM_RPOOL_T *));
+static int getauth __P((MCI *, ENVELOPE *, SASL_AI_T *));
+static char *removemech __P((char *, char *, SM_RPOOL_T *));
+static int attemptauth __P((MAILER *, MCI *, ENVELOPE *, SASL_AI_T *));
+
+static sasl_callback_t callbacks[] =
+{
+ { SASL_CB_GETREALM, &saslgetrealm, NULL },
+#define CB_GETREALM_IDX 0
+ { SASL_CB_PASS, &getsecret, NULL },
+#define CB_PASS_IDX 1
+ { SASL_CB_USER, &getsimple, NULL },
+#define CB_USER_IDX 2
+ { SASL_CB_AUTHNAME, &getsimple, NULL },
+#define CB_AUTHNAME_IDX 3
+ { SASL_CB_VERIFYFILE, &safesaslfile, NULL },
+#define CB_SAFESASL_IDX 4
+ { SASL_CB_LIST_END, NULL, NULL }
+};
+
+/*
+** INIT_SASL_CLIENT -- initialize client side of Cyrus-SASL
+**
+** Parameters:
+** none.
+**
+** Returns:
+** SASL_OK -- if successful.
+** SASL error code -- otherwise.
+**
+** Side Effects:
+** checks/sets sasl_clt_init.
+*/
+
+static bool sasl_clt_init = false;
+
+static int
+init_sasl_client()
+{
+ int result;
+
+ if (sasl_clt_init)
+ return SASL_OK;
+ result = sasl_client_init(callbacks);
+
+ /* should we retry later again or just remember that it failed? */
+ if (result == SASL_OK)
+ sasl_clt_init = true;
+ return result;
+}
+/*
+** STOP_SASL_CLIENT -- shutdown client side of Cyrus-SASL
+**
+** Parameters:
+** none.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** checks/sets sasl_clt_init.
+*/
+
+void
+stop_sasl_client()
+{
+ if (!sasl_clt_init)
+ return;
+ sasl_clt_init = false;
+ sasl_done();
+}
+/*
+** GETSASLDATA -- process the challenges from the SASL protocol
+**
+** This gets the relevant sasl response data out of the reply
+** from the server.
+**
+** Parameters:
+** line -- the response line.
+** firstline -- set if this is the first line of the reply.
+** m -- the mailer.
+** mci -- the mailer connection info.
+** e -- the envelope (unused).
+**
+** Returns:
+** none.
+*/
+
+static void getsasldata __P((char *, bool, MAILER *, MCI *, ENVELOPE *));
+
+static void
+getsasldata(line, firstline, m, mci, e)
+ char *line;
+ bool firstline;
+ MAILER *m;
+ register MCI *mci;
+ ENVELOPE *e;
+{
+ int len;
+ int result;
+# if SASL < 20000
+ char *out;
+# endif /* SASL < 20000 */
+
+ /* if not a continue we don't care about it */
+ len = strlen(line);
+ if ((len <= 4) ||
+ (line[0] != '3') ||
+ !isascii(line[1]) || !isdigit(line[1]) ||
+ !isascii(line[2]) || !isdigit(line[2]))
+ {
+ SM_FREE_CLR(mci->mci_sasl_string);
+ return;
+ }
+
+ /* forget about "334 " */
+ line += 4;
+ len -= 4;
+# if SASL >= 20000
+ /* XXX put this into a macro/function? It's duplicated below */
+ if (mci->mci_sasl_string != NULL)
+ {
+ if (mci->mci_sasl_string_len <= len)
+ {
+ sm_free(mci->mci_sasl_string); /* XXX */
+ mci->mci_sasl_string = xalloc(len + 1);
+ }
+ }
+ else
+ mci->mci_sasl_string = xalloc(len + 1);
+
+ result = sasl_decode64(line, len, mci->mci_sasl_string, len + 1,
+ (unsigned int *) &mci->mci_sasl_string_len);
+ if (result != SASL_OK)
+ {
+ mci->mci_sasl_string_len = 0;
+ *mci->mci_sasl_string = '\0';
+ }
+# else /* SASL >= 20000 */
+ out = (char *) sm_rpool_malloc_x(mci->mci_rpool, len + 1);
+ result = sasl_decode64(line, len, out, (unsigned int *) &len);
+ if (result != SASL_OK)
+ {
+ len = 0;
+ *out = '\0';
+ }
+
+ /*
+ ** mci_sasl_string is "shared" with Cyrus-SASL library; hence
+ ** it can't be in an rpool unless we use the same memory
+ ** management mechanism (with same rpool!) for Cyrus SASL.
+ */
+
+ if (mci->mci_sasl_string != NULL)
+ {
+ if (mci->mci_sasl_string_len <= len)
+ {
+ sm_free(mci->mci_sasl_string); /* XXX */
+ mci->mci_sasl_string = xalloc(len + 1);
+ }
+ }
+ else
+ mci->mci_sasl_string = xalloc(len + 1);
+
+ memcpy(mci->mci_sasl_string, out, len);
+ mci->mci_sasl_string[len] = '\0';
+ mci->mci_sasl_string_len = len;
+# endif /* SASL >= 20000 */
+ return;
+}
+/*
+** READAUTH -- read auth values from a file
+**
+** Parameters:
+** filename -- name of file to read.
+** safe -- if set, this is a safe read.
+** sai -- where to store auth_info.
+** rpool -- resource pool for sai.
+**
+** Returns:
+** EX_OK -- data succesfully read.
+** EX_UNAVAILABLE -- no valid filename.
+** EX_TEMPFAIL -- temporary failure.
+*/
+
+static char *sasl_info_name[] =
+{
+ "user id",
+ "authentication id",
+ "password",
+ "realm",
+ "mechlist"
+};
+static int
+readauth(filename, safe, sai, rpool)
+ char *filename;
+ bool safe;
+ SASL_AI_T *sai;
+ SM_RPOOL_T *rpool;
+{
+ SM_FILE_T *f;
+ long sff;
+ pid_t pid;
+ int lc;
+ char *s;
+ char buf[MAXLINE];
+
+ if (filename == NULL || filename[0] == '\0')
+ return EX_UNAVAILABLE;
+
+#if !_FFR_ALLOW_SASLINFO
+ /*
+ ** make sure we don't use a program that is not
+ ** accesible to the user who specified a different authinfo file.
+ ** However, currently we don't pass this info (authinfo file
+ ** specified by user) around, so we just turn off program access.
+ */
+
+ if (filename[0] == '|')
+ {
+ auto int fd;
+ int i;
+ char *p;
+ char *argv[MAXPV + 1];
+
+ i = 0;
+ for (p = strtok(&filename[1], " \t"); p != NULL;
+ p = strtok(NULL, " \t"))
+ {
+ if (i >= MAXPV)
+ break;
+ argv[i++] = p;
+ }
+ argv[i] = NULL;
+ pid = prog_open(argv, &fd, CurEnv);
+ if (pid < 0)
+ f = NULL;
+ else
+ f = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT,
+ (void *) &fd, SM_IO_RDONLY, NULL);
+ }
+ else
+#endif /* !_FFR_ALLOW_SASLINFO */
+ {
+ pid = -1;
+ sff = SFF_REGONLY|SFF_SAFEDIRPATH|SFF_NOWLINK
+ |SFF_NOGWFILES|SFF_NOWWFILES|SFF_NOWRFILES;
+# if _FFR_GROUPREADABLEAUTHINFOFILE
+ if (!bitnset(DBS_GROUPREADABLEAUTHINFOFILE, DontBlameSendmail))
+# endif /* _FFR_GROUPREADABLEAUTHINFOFILE */
+ sff |= SFF_NOGRFILES;
+ if (DontLockReadFiles)
+ sff |= SFF_NOLOCK;
+
+#if _FFR_ALLOW_SASLINFO
+ /*
+ ** XXX: make sure we don't read or open files that are not
+ ** accesible to the user who specified a different authinfo
+ ** file.
+ */
+
+ sff |= SFF_MUSTOWN;
+#else /* _FFR_ALLOW_SASLINFO */
+ if (safe)
+ sff |= SFF_OPENASROOT;
+#endif /* _FFR_ALLOW_SASLINFO */
+
+ f = safefopen(filename, O_RDONLY, 0, sff);
+ }
+ if (f == NULL)
+ {
+ if (LogLevel > 5)
+ sm_syslog(LOG_ERR, NOQID,
+ "AUTH=client, error: can't open %s: %s",
+ filename, sm_errstring(errno));
+ return EX_TEMPFAIL;
+ }
+
+ lc = 0;
+ while (lc <= SASL_MECHLIST &&
+ sm_io_fgets(f, SM_TIME_DEFAULT, buf, sizeof buf) != NULL)
+ {
+ if (buf[0] != '#')
+ {
+ (*sai)[lc] = sm_rpool_strdup_x(rpool, buf);
+ if ((s = strchr((*sai)[lc], '\n')) != NULL)
+ *s = '\0';
+ lc++;
+ }
+ }
+
+ (void) sm_io_close(f, SM_TIME_DEFAULT);
+ if (pid > 0)
+ (void) waitfor(pid);
+ if (lc < SASL_PASSWORD)
+ {
+ if (LogLevel > 8)
+ sm_syslog(LOG_ERR, NOQID,
+ "AUTH=client, error: can't read %s from %s",
+ sasl_info_name[lc + 1], filename);
+ return EX_TEMPFAIL;
+ }
+ return EX_OK;
+}
+
+/*
+** GETAUTH -- get authinfo from ruleset call
+**
+** {server_name}, {server_addr} must be set
+**
+** Parameters:
+** mci -- the mailer connection structure.
+** e -- the envelope (including the sender to specify).
+** sai -- pointer to authinfo (result).
+**
+** Returns:
+** EX_OK -- ruleset was succesfully called, data may not
+** be available, sai must be checked.
+** EX_UNAVAILABLE -- ruleset unavailable (or failed).
+** EX_TEMPFAIL -- temporary failure (from ruleset).
+**
+** Side Effects:
+** Fills in sai if successful.
+*/
+
+static int
+getauth(mci, e, sai)
+ MCI *mci;
+ ENVELOPE *e;
+ SASL_AI_T *sai;
+{
+ int i, r, l, got, ret;
+ char **pvp;
+ char pvpbuf[PSBUFSIZE];
+
+ r = rscap("authinfo", macvalue(macid("{server_name}"), e),
+ macvalue(macid("{server_addr}"), e), e,
+ &pvp, pvpbuf, sizeof(pvpbuf));
+
+ if (r != EX_OK)
+ return EX_UNAVAILABLE;
+
+ /* other than expected return value: ok (i.e., no auth) */
+ if (pvp == NULL || pvp[0] == NULL || (pvp[0][0] & 0377) != CANONNET)
+ return EX_OK;
+ if (pvp[1] != NULL && sm_strncasecmp(pvp[1], "temp", 4) == 0)
+ return EX_TEMPFAIL;
+
+ /*
+ ** parse the data, put it into sai
+ ** format: "TDstring" (including the '"' !)
+ ** where T is a tag: 'U', ...
+ ** D is a delimiter: ':' or '='
+ */
+
+ ret = EX_OK; /* default return value */
+ i = 0;
+ got = 0;
+ while (i < SASL_ENTRIES)
+ {
+ if (pvp[i + 1] == NULL)
+ break;
+ if (pvp[i + 1][0] != '"')
+ break;
+ switch (pvp[i + 1][1])
+ {
+ case 'U':
+ case 'u':
+ r = SASL_USER;
+ break;
+ case 'I':
+ case 'i':
+ r = SASL_AUTHID;
+ break;
+ case 'P':
+ case 'p':
+ r = SASL_PASSWORD;
+ break;
+ case 'R':
+ case 'r':
+ r = SASL_DEFREALM;
+ break;
+ case 'M':
+ case 'm':
+ r = SASL_MECHLIST;
+ break;
+ default:
+ goto fail;
+ }
+ l = strlen(pvp[i + 1]);
+
+ /* check syntax */
+ if (l <= 3 || pvp[i + 1][l - 1] != '"')
+ goto fail;
+
+ /* remove closing quote */
+ pvp[i + 1][l - 1] = '\0';
+
+ /* remove "TD and " */
+ l -= 4;
+ (*sai)[r] = (char *) sm_rpool_malloc(mci->mci_rpool, l + 1);
+ if ((*sai)[r] == NULL)
+ goto tempfail;
+ if (pvp[i + 1][2] == ':')
+ {
+ /* ':text' (just copy) */
+ (void) sm_strlcpy((*sai)[r], pvp[i + 1] + 3, l + 1);
+ got |= 1 << r;
+ }
+ else if (pvp[i + 1][2] == '=')
+ {
+ unsigned int len;
+
+ /* '=base64' (decode) */
+# if SASL >= 20000
+ ret = sasl_decode64(pvp[i + 1] + 3,
+ (unsigned int) l, (*sai)[r],
+ (unsigned int) l + 1, &len);
+# else /* SASL >= 20000 */
+ ret = sasl_decode64(pvp[i + 1] + 3,
+ (unsigned int) l, (*sai)[r], &len);
+# endif /* SASL >= 20000 */
+ if (ret != SASL_OK)
+ goto fail;
+ got |= 1 << r;
+ }
+ else
+ goto fail;
+ if (tTd(95, 5))
+ sm_syslog(LOG_DEBUG, NOQID, "getauth %s=%s",
+ sasl_info_name[r], (*sai)[r]);
+ ++i;
+ }
+
+ /* did we get the expected data? */
+ /* XXX: EXTERNAL mechanism only requires (and only uses) SASL_USER */
+ if (!(bitset(SASL_USER_BIT|SASL_AUTHID_BIT, got) &&
+ bitset(SASL_PASSWORD_BIT, got)))
+ goto fail;
+
+ /* no authid? copy uid */
+ if (!bitset(SASL_AUTHID_BIT, got))
+ {
+ l = strlen((*sai)[SASL_USER]) + 1;
+ (*sai)[SASL_AUTHID] = (char *) sm_rpool_malloc(mci->mci_rpool,
+ l + 1);
+ if ((*sai)[SASL_AUTHID] == NULL)
+ goto tempfail;
+ (void) sm_strlcpy((*sai)[SASL_AUTHID], (*sai)[SASL_USER], l);
+ }
+
+ /* no uid? copy authid */
+ if (!bitset(SASL_USER_BIT, got))
+ {
+ l = strlen((*sai)[SASL_AUTHID]) + 1;
+ (*sai)[SASL_USER] = (char *) sm_rpool_malloc(mci->mci_rpool,
+ l + 1);
+ if ((*sai)[SASL_USER] == NULL)
+ goto tempfail;
+ (void) sm_strlcpy((*sai)[SASL_USER], (*sai)[SASL_AUTHID], l);
+ }
+ return EX_OK;
+
+ tempfail:
+ ret = EX_TEMPFAIL;
+ fail:
+ if (LogLevel > 8)
+ sm_syslog(LOG_WARNING, NOQID,
+ "AUTH=client, relay=%.64s [%.16s], authinfo %sfailed",
+ macvalue(macid("{server_name}"), e),
+ macvalue(macid("{server_addr}"), e),
+ ret == EX_TEMPFAIL ? "temp" : "");
+ for (i = 0; i <= SASL_MECHLIST; i++)
+ (*sai)[i] = NULL; /* just clear; rpool */
+ return ret;
+}
+
+# if SASL >= 20000
+/*
+** GETSIMPLE -- callback to get userid or authid
+**
+** Parameters:
+** context -- sai
+** id -- what to do
+** result -- (pointer to) result
+** len -- (pointer to) length of result
+**
+** Returns:
+** OK/failure values
+*/
+
+static int
+getsimple(context, id, result, len)
+ void *context;
+ int id;
+ const char **result;
+ unsigned *len;
+{
+ SASL_AI_T *sai;
+
+ if (result == NULL || context == NULL)
+ return SASL_BADPARAM;
+ sai = (SASL_AI_T *) context;
+
+ switch (id)
+ {
+ case SASL_CB_USER:
+ *result = (*sai)[SASL_USER];
+ if (tTd(95, 5))
+ sm_syslog(LOG_DEBUG, NOQID, "AUTH username '%s'",
+ *result);
+ if (len != NULL)
+ *len = *result != NULL ? strlen(*result) : 0;
+ break;
+
+ case SASL_CB_AUTHNAME:
+ *result = (*sai)[SASL_AUTHID];
+ if (tTd(95, 5))
+ sm_syslog(LOG_DEBUG, NOQID, "AUTH authid '%s'",
+ *result);
+ if (len != NULL)
+ *len = *result != NULL ? strlen(*result) : 0;
+ break;
+
+ case SASL_CB_LANGUAGE:
+ *result = NULL;
+ if (len != NULL)
+ *len = 0;
+ break;
+
+ default:
+ return SASL_BADPARAM;
+ }
+ return SASL_OK;
+}
+/*
+** GETSECRET -- callback to get password
+**
+** Parameters:
+** conn -- connection information
+** context -- sai
+** id -- what to do
+** psecret -- (pointer to) result
+**
+** Returns:
+** OK/failure values
+*/
+
+static int
+getsecret(conn, context, id, psecret)
+ sasl_conn_t *conn;
+ SM_UNUSED(void *context);
+ int id;
+ sasl_secret_t **psecret;
+{
+ int len;
+ char *authpass;
+ MCI *mci;
+
+ if (conn == NULL || psecret == NULL || id != SASL_CB_PASS)
+ return SASL_BADPARAM;
+
+ mci = (MCI *) context;
+ authpass = mci->mci_sai[SASL_PASSWORD];
+ len = strlen(authpass);
+
+ /*
+ ** use an rpool because we are responsible for free()ing the secret,
+ ** but we can't free() it until after the auth completes
+ */
+
+ *psecret = (sasl_secret_t *) sm_rpool_malloc(mci->mci_rpool,
+ sizeof(sasl_secret_t) +
+ len + 1);
+ if (*psecret == NULL)
+ return SASL_FAIL;
+ (void) sm_strlcpy((char *) (*psecret)->data, authpass, len + 1);
+ (*psecret)->len = (unsigned long) len;
+ return SASL_OK;
+}
+# else /* SASL >= 20000 */
+/*
+** GETSIMPLE -- callback to get userid or authid
+**
+** Parameters:
+** context -- sai
+** id -- what to do
+** result -- (pointer to) result
+** len -- (pointer to) length of result
+**
+** Returns:
+** OK/failure values
+*/
+
+static int
+getsimple(context, id, result, len)
+ void *context;
+ int id;
+ const char **result;
+ unsigned *len;
+{
+ char *h, *s;
+# if SASL > 10509
+ bool addrealm;
+# endif /* SASL > 10509 */
+ size_t l;
+ SASL_AI_T *sai;
+ char *authid = NULL;
+
+ if (result == NULL || context == NULL)
+ return SASL_BADPARAM;
+ sai = (SASL_AI_T *) context;
+
+ /*
+ ** Unfortunately it is not clear whether this routine should
+ ** return a copy of a string or just a pointer to a string.
+ ** The Cyrus-SASL plugins treat these return values differently, e.g.,
+ ** plugins/cram.c free()s authid, plugings/digestmd5.c does not.
+ ** The best solution to this problem is to fix Cyrus-SASL, but it
+ ** seems there is nobody who creates patches... Hello CMU!?
+ ** The second best solution is to have flags that tell this routine
+ ** whether to return an malloc()ed copy.
+ ** The next best solution is to always return an malloc()ed copy,
+ ** and suffer from some memory leak, which is ugly for persistent
+ ** queue runners.
+ ** For now we go with the last solution...
+ ** We can't use rpools (which would avoid this particular problem)
+ ** as explained in sasl.c.
+ */
+
+ switch (id)
+ {
+ case SASL_CB_USER:
+ l = strlen((*sai)[SASL_USER]) + 1;
+ s = sm_sasl_malloc(l);
+ if (s == NULL)
+ {
+ if (len != NULL)
+ *len = 0;
+ *result = NULL;
+ return SASL_NOMEM;
+ }
+ (void) sm_strlcpy(s, (*sai)[SASL_USER], l);
+ *result = s;
+ if (tTd(95, 5))
+ sm_syslog(LOG_DEBUG, NOQID, "AUTH username '%s'",
+ *result);
+ if (len != NULL)
+ *len = *result != NULL ? strlen(*result) : 0;
+ break;
+
+ case SASL_CB_AUTHNAME:
+ h = (*sai)[SASL_AUTHID];
+# if SASL > 10509
+ /* XXX maybe other mechanisms too?! */
+ addrealm = (*sai)[SASL_MECH] != NULL &&
+ sm_strcasecmp((*sai)[SASL_MECH], "CRAM-MD5") == 0;
+
+ /*
+ ** Add realm to authentication id unless authid contains
+ ** '@' (i.e., a realm) or the default realm is empty.
+ */
+
+ if (addrealm && h != NULL && strchr(h, '@') == NULL)
+ {
+ /* has this been done before? */
+ if ((*sai)[SASL_ID_REALM] == NULL)
+ {
+ char *realm;
+
+ realm = (*sai)[SASL_DEFREALM];
+
+ /* do not add an empty realm */
+ if (*realm == '\0')
+ {
+ authid = h;
+ (*sai)[SASL_ID_REALM] = NULL;
+ }
+ else
+ {
+ l = strlen(h) + strlen(realm) + 2;
+
+ /* should use rpool, but from where? */
+ authid = sm_sasl_malloc(l);
+ if (authid != NULL)
+ {
+ (void) sm_snprintf(authid, l,
+ "%s@%s",
+ h, realm);
+ (*sai)[SASL_ID_REALM] = authid;
+ }
+ else
+ {
+ authid = h;
+ (*sai)[SASL_ID_REALM] = NULL;
+ }
+ }
+ }
+ else
+ authid = (*sai)[SASL_ID_REALM];
+ }
+ else
+# endif /* SASL > 10509 */
+ authid = h;
+ l = strlen(authid) + 1;
+ s = sm_sasl_malloc(l);
+ if (s == NULL)
+ {
+ if (len != NULL)
+ *len = 0;
+ *result = NULL;
+ return SASL_NOMEM;
+ }
+ (void) sm_strlcpy(s, authid, l);
+ *result = s;
+ if (tTd(95, 5))
+ sm_syslog(LOG_DEBUG, NOQID, "AUTH authid '%s'",
+ *result);
+ if (len != NULL)
+ *len = authid ? strlen(authid) : 0;
+ break;
+
+ case SASL_CB_LANGUAGE:
+ *result = NULL;
+ if (len != NULL)
+ *len = 0;
+ break;
+
+ default:
+ return SASL_BADPARAM;
+ }
+ return SASL_OK;
+}
+/*
+** GETSECRET -- callback to get password
+**
+** Parameters:
+** conn -- connection information
+** context -- sai
+** id -- what to do
+** psecret -- (pointer to) result
+**
+** Returns:
+** OK/failure values
+*/
+
+static int
+getsecret(conn, context, id, psecret)
+ sasl_conn_t *conn;
+ SM_UNUSED(void *context);
+ int id;
+ sasl_secret_t **psecret;
+{
+ int len;
+ char *authpass;
+ SASL_AI_T *sai;
+
+ if (conn == NULL || psecret == NULL || id != SASL_CB_PASS)
+ return SASL_BADPARAM;
+
+ sai = (SASL_AI_T *) context;
+ authpass = (*sai)[SASL_PASSWORD];
+ len = strlen(authpass);
+ *psecret = (sasl_secret_t *) sm_sasl_malloc(sizeof(sasl_secret_t) +
+ len + 1);
+ if (*psecret == NULL)
+ return SASL_FAIL;
+ (void) sm_strlcpy((*psecret)->data, authpass, len + 1);
+ (*psecret)->len = (unsigned long) len;
+ return SASL_OK;
+}
+# endif /* SASL >= 20000 */
+
+/*
+** SAFESASLFILE -- callback for sasl: is file safe?
+**
+** Parameters:
+** context -- pointer to context between invocations (unused)
+** file -- name of file to check
+** type -- type of file to check
+**
+** Returns:
+** SASL_OK -- file can be used
+** SASL_CONTINUE -- don't use file
+** SASL_FAIL -- failure (not used here)
+**
+*/
+
+int
+#if SASL > 10515
+safesaslfile(context, file, type)
+#else /* SASL > 10515 */
+safesaslfile(context, file)
+#endif /* SASL > 10515 */
+ void *context;
+# if SASL >= 20000
+ const char *file;
+# else /* SASL >= 20000 */
+ char *file;
+# endif /* SASL >= 20000 */
+#if SASL > 10515
+# if SASL >= 20000
+ sasl_verify_type_t type;
+# else /* SASL >= 20000 */
+ int type;
+# endif /* SASL >= 20000 */
+#endif /* SASL > 10515 */
+{
+ long sff;
+ int r;
+#if SASL <= 10515
+ size_t len;
+#endif /* SASL <= 10515 */
+ char *p;
+
+ if (file == NULL || *file == '\0')
+ return SASL_OK;
+ sff = SFF_SAFEDIRPATH|SFF_NOWLINK|SFF_NOWWFILES|SFF_ROOTOK;
+#if SASL <= 10515
+ if ((p = strrchr(file, '/')) == NULL)
+ p = file;
+ else
+ ++p;
+
+ /* everything beside libs and .conf files must not be readable */
+ len = strlen(p);
+ if ((len <= 3 || strncmp(p, "lib", 3) != 0) &&
+ (len <= 5 || strncmp(p + len - 5, ".conf", 5) != 0))
+ {
+ if (!bitnset(DBS_GROUPREADABLESASLDBFILE, DontBlameSendmail))
+ sff |= SFF_NORFILES;
+ if (!bitnset(DBS_GROUPWRITABLESASLDBFILE, DontBlameSendmail))
+ sff |= SFF_NOGWFILES;
+ }
+#else /* SASL <= 10515 */
+ /* files containing passwords should be not readable */
+ if (type == SASL_VRFY_PASSWD)
+ {
+ if (bitnset(DBS_GROUPREADABLESASLDBFILE, DontBlameSendmail))
+ sff |= SFF_NOWRFILES;
+ else
+ sff |= SFF_NORFILES;
+ if (!bitnset(DBS_GROUPWRITABLESASLDBFILE, DontBlameSendmail))
+ sff |= SFF_NOGWFILES;
+ }
+#endif /* SASL <= 10515 */
+
+ p = (char *) file;
+ if ((r = safefile(p, RunAsUid, RunAsGid, RunAsUserName, sff,
+ S_IRUSR, NULL)) == 0)
+ return SASL_OK;
+ if (LogLevel > (r != ENOENT ? 8 : 10))
+ sm_syslog(LOG_WARNING, NOQID, "error: safesasl(%s) failed: %s",
+ p, sm_errstring(r));
+ return SASL_CONTINUE;
+}
+
+/*
+** SASLGETREALM -- return the realm for SASL
+**
+** return the realm for the client
+**
+** Parameters:
+** context -- context shared between invocations
+** availrealms -- list of available realms
+** {realm, realm, ...}
+** result -- pointer to result
+**
+** Returns:
+** failure/success
+*/
+
+static int
+saslgetrealm(context, id, availrealms, result)
+ void *context;
+ int id;
+ const char **availrealms;
+ const char **result;
+{
+ char *r;
+ SASL_AI_T *sai;
+
+ sai = (SASL_AI_T *) context;
+ if (sai == NULL)
+ return SASL_FAIL;
+ r = (*sai)[SASL_DEFREALM];
+
+ if (LogLevel > 12)
+ sm_syslog(LOG_INFO, NOQID,
+ "AUTH=client, realm=%s, available realms=%s",
+ r == NULL ? "<No Realm>" : r,
+ (availrealms == NULL || *availrealms == NULL)
+ ? "<No Realms>" : *availrealms);
+
+ /* check whether context is in list */
+ if (availrealms != NULL && *availrealms != NULL)
+ {
+ if (iteminlist(context, (char *)(*availrealms + 1), " ,}") ==
+ NULL)
+ {
+ if (LogLevel > 8)
+ sm_syslog(LOG_ERR, NOQID,
+ "AUTH=client, realm=%s not in list=%s",
+ r, *availrealms);
+ return SASL_FAIL;
+ }
+ }
+ *result = r;
+ return SASL_OK;
+}
+/*
+** ITEMINLIST -- does item appear in list?
+**
+** Check whether item appears in list (which must be separated by a
+** character in delim) as a "word", i.e. it must appear at the begin
+** of the list or after a space, and it must end with a space or the
+** end of the list.
+**
+** Parameters:
+** item -- item to search.
+** list -- list of items.
+** delim -- list of delimiters.
+**
+** Returns:
+** pointer to occurrence (NULL if not found).
+*/
+
+char *
+iteminlist(item, list, delim)
+ char *item;
+ char *list;
+ char *delim;
+{
+ char *s;
+ int len;
+
+ if (list == NULL || *list == '\0')
+ return NULL;
+ if (item == NULL || *item == '\0')
+ return NULL;
+ s = list;
+ len = strlen(item);
+ while (s != NULL && *s != '\0')
+ {
+ if (sm_strncasecmp(s, item, len) == 0 &&
+ (s[len] == '\0' || strchr(delim, s[len]) != NULL))
+ return s;
+ s = strpbrk(s, delim);
+ if (s != NULL)
+ while (*++s == ' ')
+ continue;
+ }
+ return NULL;
+}
+/*
+** REMOVEMECH -- remove item [rem] from list [list]
+**
+** Parameters:
+** rem -- item to remove
+** list -- list of items
+** rpool -- resource pool from which result is allocated.
+**
+** Returns:
+** pointer to new list (NULL in case of error).
+*/
+
+static char *
+removemech(rem, list, rpool)
+ char *rem;
+ char *list;
+ SM_RPOOL_T *rpool;
+{
+ char *ret;
+ char *needle;
+ int len;
+
+ if (list == NULL)
+ return NULL;
+ if (rem == NULL || *rem == '\0')
+ {
+ /* take out what? */
+ return NULL;
+ }
+
+ /* find the item in the list */
+ if ((needle = iteminlist(rem, list, " ")) == NULL)
+ {
+ /* not in there: return original */
+ return list;
+ }
+
+ /* length of string without rem */
+ len = strlen(list) - strlen(rem);
+ if (len <= 0)
+ {
+ ret = (char *) sm_rpool_malloc_x(rpool, 1);
+ *ret = '\0';
+ return ret;
+ }
+ ret = (char *) sm_rpool_malloc_x(rpool, len);
+ memset(ret, '\0', len);
+
+ /* copy from start to removed item */
+ memcpy(ret, list, needle - list);
+
+ /* length of rest of string past removed item */
+ len = strlen(needle) - strlen(rem) - 1;
+ if (len > 0)
+ {
+ /* not last item -- copy into string */
+ memcpy(ret + (needle - list),
+ list + (needle - list) + strlen(rem) + 1,
+ len);
+ }
+ else
+ ret[(needle - list) - 1] = '\0';
+ return ret;
+}
+/*
+** ATTEMPTAUTH -- try to AUTHenticate using one mechanism
+**
+** Parameters:
+** m -- the mailer.
+** mci -- the mailer connection structure.
+** e -- the envelope (including the sender to specify).
+** sai - sasl authinfo
+**
+** Returns:
+** EX_OK -- authentication was successful.
+** EX_NOPERM -- authentication failed.
+** EX_IOERR -- authentication dialogue failed (I/O problem?).
+** EX_TEMPFAIL -- temporary failure.
+**
+*/
+
+static int
+attemptauth(m, mci, e, sai)
+ MAILER *m;
+ MCI *mci;
+ ENVELOPE *e;
+ SASL_AI_T *sai;
+{
+ int saslresult, smtpresult;
+# if SASL >= 20000
+ sasl_ssf_t ssf;
+ const char *auth_id;
+ const char *out;
+# else /* SASL >= 20000 */
+ sasl_external_properties_t ssf;
+ char *out;
+# endif /* SASL >= 20000 */
+ unsigned int outlen;
+ sasl_interact_t *client_interact = NULL;
+ char *mechusing;
+ sasl_security_properties_t ssp;
+ char in64[MAXOUTLEN];
+#if NETINET || (NETINET6 && SASL >= 20000)
+ extern SOCKADDR CurHostAddr;
+#endif /* NETINET || (NETINET6 && SASL >= 20000) */
+
+ /* no mechanism selected (yet) */
+ (*sai)[SASL_MECH] = NULL;
+
+ /* dispose old connection */
+ if (mci->mci_conn != NULL)
+ sasl_dispose(&(mci->mci_conn));
+
+ /* make a new client sasl connection */
+# if SASL >= 20000
+ saslresult = sasl_client_new(bitnset(M_LMTP, m->m_flags) ? "lmtp"
+ : "smtp",
+ CurHostName, NULL, NULL, NULL, 0,
+ &mci->mci_conn);
+# else /* SASL >= 20000 */
+ saslresult = sasl_client_new(bitnset(M_LMTP, m->m_flags) ? "lmtp"
+ : "smtp",
+ CurHostName, NULL, 0, &mci->mci_conn);
+# endif /* SASL >= 20000 */
+ if (saslresult != SASL_OK)
+ return EX_TEMPFAIL;
+
+ /* set properties */
+ (void) memset(&ssp, '\0', sizeof ssp);
+
+ /* XXX should these be options settable via .cf ? */
+ {
+ ssp.max_ssf = MaxSLBits;
+ ssp.maxbufsize = MAXOUTLEN;
+# if 0
+ ssp.security_flags = SASL_SEC_NOPLAINTEXT;
+# endif /* 0 */
+ }
+ saslresult = sasl_setprop(mci->mci_conn, SASL_SEC_PROPS, &ssp);
+ if (saslresult != SASL_OK)
+ return EX_TEMPFAIL;
+
+# if SASL >= 20000
+ /* external security strength factor, authentication id */
+ ssf = 0;
+ auth_id = NULL;
+# if STARTTLS
+ out = macvalue(macid("{cert_subject}"), e);
+ if (out != NULL && *out != '\0')
+ auth_id = out;
+ out = macvalue(macid("{cipher_bits}"), e);
+ if (out != NULL && *out != '\0')
+ ssf = atoi(out);
+# endif /* STARTTLS */
+ saslresult = sasl_setprop(mci->mci_conn, SASL_SSF_EXTERNAL, &ssf);
+ if (saslresult != SASL_OK)
+ return EX_TEMPFAIL;
+ saslresult = sasl_setprop(mci->mci_conn, SASL_AUTH_EXTERNAL, auth_id);
+ if (saslresult != SASL_OK)
+ return EX_TEMPFAIL;
+
+# if NETINET || NETINET6
+ /* set local/remote ipv4 addresses */
+ if (mci->mci_out != NULL && (
+# if NETINET6
+ CurHostAddr.sa.sa_family == AF_INET6 ||
+# endif /* NETINET6 */
+ CurHostAddr.sa.sa_family == AF_INET))
+ {
+ SOCKADDR_LEN_T addrsize;
+ SOCKADDR saddr_l;
+ char localip[60], remoteip[60];
+
+ switch (CurHostAddr.sa.sa_family)
+ {
+ case AF_INET:
+ addrsize = sizeof(struct sockaddr_in);
+ break;
+# if NETINET6
+ case AF_INET6:
+ addrsize = sizeof(struct sockaddr_in6);
+ break;
+# endif /* NETINET6 */
+ default:
+ break;
+ }
+ if (iptostring(&CurHostAddr, addrsize,
+ remoteip, sizeof remoteip))
+ {
+ if (sasl_setprop(mci->mci_conn, SASL_IPREMOTEPORT,
+ remoteip) != SASL_OK)
+ return EX_TEMPFAIL;
+ }
+ addrsize = sizeof(saddr_l);
+ if (getsockname(sm_io_getinfo(mci->mci_out, SM_IO_WHAT_FD,
+ NULL),
+ (struct sockaddr *) &saddr_l, &addrsize) == 0)
+ {
+ if (iptostring(&saddr_l, addrsize,
+ localip, sizeof localip))
+ {
+ if (sasl_setprop(mci->mci_conn,
+ SASL_IPLOCALPORT,
+ localip) != SASL_OK)
+ return EX_TEMPFAIL;
+ }
+ }
+ }
+# endif /* NETINET || NETINET6 */
+
+ /* start client side of sasl */
+ saslresult = sasl_client_start(mci->mci_conn, mci->mci_saslcap,
+ &client_interact,
+ &out, &outlen,
+ (const char **) &mechusing);
+# else /* SASL >= 20000 */
+ /* external security strength factor, authentication id */
+ ssf.ssf = 0;
+ ssf.auth_id = NULL;
+# if STARTTLS
+ out = macvalue(macid("{cert_subject}"), e);
+ if (out != NULL && *out != '\0')
+ ssf.auth_id = out;
+ out = macvalue(macid("{cipher_bits}"), e);
+ if (out != NULL && *out != '\0')
+ ssf.ssf = atoi(out);
+# endif /* STARTTLS */
+ saslresult = sasl_setprop(mci->mci_conn, SASL_SSF_EXTERNAL, &ssf);
+ if (saslresult != SASL_OK)
+ return EX_TEMPFAIL;
+
+# if NETINET
+ /* set local/remote ipv4 addresses */
+ if (mci->mci_out != NULL && CurHostAddr.sa.sa_family == AF_INET)
+ {
+ SOCKADDR_LEN_T addrsize;
+ struct sockaddr_in saddr_l;
+
+ if (sasl_setprop(mci->mci_conn, SASL_IP_REMOTE,
+ (struct sockaddr_in *) &CurHostAddr)
+ != SASL_OK)
+ return EX_TEMPFAIL;
+ addrsize = sizeof(struct sockaddr_in);
+ if (getsockname(sm_io_getinfo(mci->mci_out, SM_IO_WHAT_FD,
+ NULL),
+ (struct sockaddr *) &saddr_l, &addrsize) == 0)
+ {
+ if (sasl_setprop(mci->mci_conn, SASL_IP_LOCAL,
+ &saddr_l) != SASL_OK)
+ return EX_TEMPFAIL;
+ }
+ }
+# endif /* NETINET */
+
+ /* start client side of sasl */
+ saslresult = sasl_client_start(mci->mci_conn, mci->mci_saslcap,
+ NULL, &client_interact,
+ &out, &outlen,
+ (const char **) &mechusing);
+# endif /* SASL >= 20000 */
+
+ if (saslresult != SASL_OK && saslresult != SASL_CONTINUE)
+ {
+ if (saslresult == SASL_NOMECH && LogLevel > 8)
+ {
+ sm_syslog(LOG_NOTICE, e->e_id,
+ "AUTH=client, available mechanisms do not fulfill requirements");
+ }
+ return EX_TEMPFAIL;
+ }
+
+ /* just point current mechanism to the data in the sasl library */
+ (*sai)[SASL_MECH] = mechusing;
+
+ /* send the info across the wire */
+ if (out == NULL
+ /* login and digest-md5 up to 1.5.28 set out="" */
+ || (outlen == 0 &&
+ (sm_strcasecmp(mechusing, "LOGIN") == 0 ||
+ sm_strcasecmp(mechusing, "DIGEST-MD5") == 0))
+ )
+ {
+ /* no initial response */
+ smtpmessage("AUTH %s", m, mci, mechusing);
+ }
+ else if (outlen == 0)
+ {
+ /*
+ ** zero-length initial response, per RFC 2554 4.:
+ ** "Unlike a zero-length client answer to a 334 reply, a zero-
+ ** length initial response is sent as a single equals sign"
+ */
+
+ smtpmessage("AUTH %s =", m, mci, mechusing);
+ }
+ else
+ {
+ saslresult = sasl_encode64(out, outlen, in64, MAXOUTLEN, NULL);
+ if (saslresult != SASL_OK) /* internal error */
+ {
+ if (LogLevel > 8)
+ sm_syslog(LOG_ERR, e->e_id,
+ "encode64 for AUTH failed");
+ return EX_TEMPFAIL;
+ }
+ smtpmessage("AUTH %s %s", m, mci, mechusing, in64);
+ }
+# if SASL < 20000
+ sm_sasl_free(out); /* XXX only if no rpool is used */
+# endif /* SASL < 20000 */
+
+ /* get the reply */
+ smtpresult = reply(m, mci, e, TimeOuts.to_auth, getsasldata, NULL,
+ XS_AUTH);
+
+ for (;;)
+ {
+ /* check return code from server */
+ if (smtpresult == 235)
+ {
+ macdefine(&mci->mci_macro, A_TEMP, macid("{auth_type}"),
+ mechusing);
+ return EX_OK;
+ }
+ if (smtpresult == -1)
+ return EX_IOERR;
+ if (REPLYTYPE(smtpresult) == 5)
+ return EX_NOPERM; /* ugly, but ... */
+ if (REPLYTYPE(smtpresult) != 3)
+ {
+ /* should we fail deliberately, see RFC 2554 4. ? */
+ /* smtpmessage("*", m, mci); */
+ return EX_TEMPFAIL;
+ }
+
+ saslresult = sasl_client_step(mci->mci_conn,
+ mci->mci_sasl_string,
+ mci->mci_sasl_string_len,
+ &client_interact,
+ &out, &outlen);
+
+ if (saslresult != SASL_OK && saslresult != SASL_CONTINUE)
+ {
+ if (tTd(95, 5))
+ sm_dprintf("AUTH FAIL=%s (%d)\n",
+ sasl_errstring(saslresult, NULL, NULL),
+ saslresult);
+
+ /* fail deliberately, see RFC 2554 4. */
+ smtpmessage("*", m, mci);
+
+ /*
+ ** but we should only fail for this authentication
+ ** mechanism; how to do that?
+ */
+
+ smtpresult = reply(m, mci, e, TimeOuts.to_auth,
+ getsasldata, NULL, XS_AUTH);
+ return EX_NOPERM;
+ }
+
+ if (outlen > 0)
+ {
+ saslresult = sasl_encode64(out, outlen, in64,
+ MAXOUTLEN, NULL);
+ if (saslresult != SASL_OK)
+ {
+ /* give an error reply to the other side! */
+ smtpmessage("*", m, mci);
+ return EX_TEMPFAIL;
+ }
+ }
+ else
+ in64[0] = '\0';
+# if SASL < 20000
+ sm_sasl_free(out); /* XXX only if no rpool is used */
+# endif /* SASL < 20000 */
+ smtpmessage("%s", m, mci, in64);
+ smtpresult = reply(m, mci, e, TimeOuts.to_auth,
+ getsasldata, NULL, XS_AUTH);
+ }
+ /* NOTREACHED */
+}
+/*
+** SMTPAUTH -- try to AUTHenticate
+**
+** This will try mechanisms in the order the sasl library decided until:
+** - there are no more mechanisms
+** - a mechanism succeeds
+** - the sasl library fails initializing
+**
+** Parameters:
+** m -- the mailer.
+** mci -- the mailer connection info.
+** e -- the envelope.
+**
+** Returns:
+** EX_OK -- authentication was successful
+** EX_UNAVAILABLE -- authentication not possible, e.g.,
+** no data available.
+** EX_NOPERM -- authentication failed.
+** EX_TEMPFAIL -- temporary failure.
+**
+** Notice: AuthInfo is used for all connections, hence we must
+** return EX_TEMPFAIL only if we really want to retry, i.e.,
+** iff getauth() tempfailed or getauth() was used and
+** authentication tempfailed.
+*/
+
+int
+smtpauth(m, mci, e)
+ MAILER *m;
+ MCI *mci;
+ ENVELOPE *e;
+{
+ int result;
+ int i;
+ bool usedgetauth;
+
+ mci->mci_sasl_auth = false;
+ for (i = 0; i < SASL_MECH ; i++)
+ mci->mci_sai[i] = NULL;
+
+ result = getauth(mci, e, &(mci->mci_sai));
+ if (result == EX_TEMPFAIL)
+ return result;
+ usedgetauth = true;
+
+ /* no data available: don't try to authenticate */
+ if (result == EX_OK && mci->mci_sai[SASL_AUTHID] == NULL)
+ return result;
+ if (result != EX_OK)
+ {
+ if (SASLInfo == NULL)
+ return EX_UNAVAILABLE;
+
+ /* read authinfo from file */
+ result = readauth(SASLInfo, true, &(mci->mci_sai),
+ mci->mci_rpool);
+ if (result != EX_OK)
+ return result;
+ usedgetauth = false;
+ }
+
+ /* check whether sufficient data is available */
+ if (mci->mci_sai[SASL_PASSWORD] == NULL ||
+ *(mci->mci_sai)[SASL_PASSWORD] == '\0')
+ return EX_UNAVAILABLE;
+ if ((mci->mci_sai[SASL_AUTHID] == NULL ||
+ *(mci->mci_sai)[SASL_AUTHID] == '\0') &&
+ (mci->mci_sai[SASL_USER] == NULL ||
+ *(mci->mci_sai)[SASL_USER] == '\0'))
+ return EX_UNAVAILABLE;
+
+ /* set the context for the callback function to sai */
+# if SASL >= 20000
+ callbacks[CB_PASS_IDX].context = (void *) mci;
+# else /* SASL >= 20000 */
+ callbacks[CB_PASS_IDX].context = (void *) &mci->mci_sai;
+# endif /* SASL >= 20000 */
+ callbacks[CB_USER_IDX].context = (void *) &mci->mci_sai;
+ callbacks[CB_AUTHNAME_IDX].context = (void *) &mci->mci_sai;
+ callbacks[CB_GETREALM_IDX].context = (void *) &mci->mci_sai;
+#if 0
+ callbacks[CB_SAFESASL_IDX].context = (void *) &mci->mci_sai;
+#endif /* 0 */
+
+ /* set default value for realm */
+ if ((mci->mci_sai)[SASL_DEFREALM] == NULL)
+ (mci->mci_sai)[SASL_DEFREALM] = sm_rpool_strdup_x(e->e_rpool,
+ macvalue('j', CurEnv));
+
+ /* set default value for list of mechanism to use */
+ if ((mci->mci_sai)[SASL_MECHLIST] == NULL ||
+ *(mci->mci_sai)[SASL_MECHLIST] == '\0')
+ (mci->mci_sai)[SASL_MECHLIST] = AuthMechanisms;
+
+ /* create list of mechanisms to try */
+ mci->mci_saslcap = intersect((mci->mci_sai)[SASL_MECHLIST],
+ mci->mci_saslcap, mci->mci_rpool);
+
+ /* initialize sasl client library */
+ result = init_sasl_client();
+ if (result != SASL_OK)
+ return usedgetauth ? EX_TEMPFAIL : EX_UNAVAILABLE;
+ do
+ {
+ result = attemptauth(m, mci, e, &(mci->mci_sai));
+ if (result == EX_OK)
+ mci->mci_sasl_auth = true;
+ else if (result == EX_TEMPFAIL || result == EX_NOPERM)
+ {
+ mci->mci_saslcap = removemech((mci->mci_sai)[SASL_MECH],
+ mci->mci_saslcap,
+ mci->mci_rpool);
+ if (mci->mci_saslcap == NULL ||
+ *(mci->mci_saslcap) == '\0')
+ return usedgetauth ? result
+ : EX_UNAVAILABLE;
+ }
+ else
+ return result;
+ } while (result != EX_OK);
+ return result;
+}
+#endif /* SASL */
+
+/*
+** SMTPMAILFROM -- send MAIL command
+**
+** Parameters:
+** m -- the mailer.
+** mci -- the mailer connection structure.
+** e -- the envelope (including the sender to specify).
+*/
+
+int
+smtpmailfrom(m, mci, e)
+ MAILER *m;
+ MCI *mci;
+ ENVELOPE *e;
+{
+ int r;
+ char *bufp;
+ char *bodytype;
+ char *enhsc;
+ char buf[MAXNAME + 1];
+ char optbuf[MAXLINE];
+
+ if (tTd(18, 2))
+ sm_dprintf("smtpmailfrom: CurHost=%s\n", CurHostName);
+ enhsc = NULL;
+
+ /*
+ ** Check if connection is gone, if so
+ ** it's a tempfail and we use mci_errno
+ ** for the reason.
+ */
+
+ if (mci->mci_state == MCIS_CLOSED)
+ {
+ errno = mci->mci_errno;
+ return EX_TEMPFAIL;
+ }
+
+ /* set up appropriate options to include */
+ if (bitset(MCIF_SIZE, mci->mci_flags) && e->e_msgsize > 0)
+ {
+ (void) sm_snprintf(optbuf, sizeof optbuf, " SIZE=%ld",
+ e->e_msgsize);
+ bufp = &optbuf[strlen(optbuf)];
+ }
+ else
+ {
+ optbuf[0] = '\0';
+ bufp = optbuf;
+ }
+
+ bodytype = e->e_bodytype;
+ if (bitset(MCIF_8BITMIME, mci->mci_flags))
+ {
+ if (bodytype == NULL &&
+ bitset(MM_MIME8BIT, MimeMode) &&
+ bitset(EF_HAS8BIT, e->e_flags) &&
+ !bitset(EF_DONT_MIME, e->e_flags) &&
+ !bitnset(M_8BITS, m->m_flags))
+ bodytype = "8BITMIME";
+ if (bodytype != NULL &&
+ SPACELEFT(optbuf, bufp) > strlen(bodytype) + 7)
+ {
+ (void) sm_snprintf(bufp, SPACELEFT(optbuf, bufp),
+ " BODY=%s", bodytype);
+ bufp += strlen(bufp);
+ }
+ }
+ else if (bitnset(M_8BITS, m->m_flags) ||
+ !bitset(EF_HAS8BIT, e->e_flags) ||
+ bitset(MCIF_8BITOK, mci->mci_flags))
+ {
+ /* EMPTY */
+ /* just pass it through */
+ }
+#if MIME8TO7
+ else if (bitset(MM_CVTMIME, MimeMode) &&
+ !bitset(EF_DONT_MIME, e->e_flags) &&
+ (!bitset(MM_PASS8BIT, MimeMode) ||
+ bitset(EF_IS_MIME, e->e_flags)))
+ {
+ /* must convert from 8bit MIME format to 7bit encoded */
+ mci->mci_flags |= MCIF_CVT8TO7;
+ }
+#endif /* MIME8TO7 */
+ else if (!bitset(MM_PASS8BIT, MimeMode))
+ {
+ /* cannot just send a 8-bit version */
+ extern char MsgBuf[];
+
+ usrerrenh("5.6.3", "%s does not support 8BITMIME", CurHostName);
+ mci_setstat(mci, EX_NOTSTICKY, "5.6.3", MsgBuf);
+ return EX_DATAERR;
+ }
+
+ if (bitset(MCIF_DSN, mci->mci_flags))
+ {
+ if (e->e_envid != NULL &&
+ SPACELEFT(optbuf, bufp) > strlen(e->e_envid) + 7)
+ {
+ (void) sm_snprintf(bufp, SPACELEFT(optbuf, bufp),
+ " ENVID=%s", e->e_envid);
+ bufp += strlen(bufp);
+ }
+
+ /* RET= parameter */
+ if (bitset(EF_RET_PARAM, e->e_flags) &&
+ SPACELEFT(optbuf, bufp) > 9)
+ {
+ (void) sm_snprintf(bufp, SPACELEFT(optbuf, bufp),
+ " RET=%s",
+ bitset(EF_NO_BODY_RETN, e->e_flags) ?
+ "HDRS" : "FULL");
+ bufp += strlen(bufp);
+ }
+ }
+
+ if (bitset(MCIF_AUTH, mci->mci_flags) && e->e_auth_param != NULL &&
+ SPACELEFT(optbuf, bufp) > strlen(e->e_auth_param) + 7
+#if SASL
+ && (!bitset(SASL_AUTH_AUTH, SASLOpts) || mci->mci_sasl_auth)
+#endif /* SASL */
+ )
+ {
+ (void) sm_snprintf(bufp, SPACELEFT(optbuf, bufp),
+ " AUTH=%s", e->e_auth_param);
+ bufp += strlen(bufp);
+ }
+
+ /*
+ ** 17 is the max length required, we could use log() to compute
+ ** the exact length (and check IS_DLVR_TRACE())
+ */
+
+ if (bitset(MCIF_DLVR_BY, mci->mci_flags) &&
+ IS_DLVR_BY(e) && SPACELEFT(optbuf, bufp) > 17)
+ {
+ long dby;
+
+ /*
+ ** Avoid problems with delays (for R) since the check
+ ** in deliver() whether min-deliver-time is sufficient.
+ ** Alternatively we could pass the computed time to this
+ ** function.
+ */
+
+ dby = e->e_deliver_by - (curtime() - e->e_ctime);
+ if (dby <= 0 && IS_DLVR_RETURN(e))
+ dby = mci->mci_min_by <= 0 ? 1 : mci->mci_min_by;
+ (void) sm_snprintf(bufp, SPACELEFT(optbuf, bufp),
+ " BY=%ld;%c%s",
+ dby,
+ IS_DLVR_RETURN(e) ? 'R' : 'N',
+ IS_DLVR_TRACE(e) ? "T" : "");
+ bufp += strlen(bufp);
+ }
+
+ /*
+ ** Send the MAIL command.
+ ** Designates the sender.
+ */
+
+ mci->mci_state = MCIS_MAIL;
+
+ if (bitset(EF_RESPONSE, e->e_flags) &&
+ !bitnset(M_NO_NULL_FROM, m->m_flags))
+ buf[0] = '\0';
+ else
+ expand("\201g", buf, sizeof buf, e);
+ if (buf[0] == '<')
+ {
+ /* strip off <angle brackets> (put back on below) */
+ bufp = &buf[strlen(buf) - 1];
+ if (*bufp == '>')
+ *bufp = '\0';
+ bufp = &buf[1];
+ }
+ else
+ bufp = buf;
+ if (bitnset(M_LOCALMAILER, e->e_from.q_mailer->m_flags) ||
+ !bitnset(M_FROMPATH, m->m_flags))
+ {
+ smtpmessage("MAIL From:<%s>%s", m, mci, bufp, optbuf);
+ }
+ else
+ {
+ smtpmessage("MAIL From:<@%s%c%s>%s", m, mci, MyHostName,
+ *bufp == '@' ? ',' : ':', bufp, optbuf);
+ }
+ SmtpPhase = mci->mci_phase = "client MAIL";
+ sm_setproctitle(true, e, "%s %s: %s", qid_printname(e),
+ CurHostName, mci->mci_phase);
+ r = reply(m, mci, e, TimeOuts.to_mail, NULL, &enhsc, XS_DEFAULT);
+ if (r < 0)
+ {
+ /* communications failure */
+ mci_setstat(mci, EX_TEMPFAIL, "4.4.2", NULL);
+ return EX_TEMPFAIL;
+ }
+ else if (r == SMTPCLOSING)
+ {
+ /* service shutting down: handled by reply() */
+ return EX_TEMPFAIL;
+ }
+ else if (REPLYTYPE(r) == 4)
+ {
+ mci_setstat(mci, EX_NOTSTICKY, ENHSCN(enhsc, smtptodsn(r)),
+ SmtpReplyBuffer);
+ return EX_TEMPFAIL;
+ }
+ else if (REPLYTYPE(r) == 2)
+ {
+ return EX_OK;
+ }
+ else if (r == 501)
+ {
+ /* syntax error in arguments */
+ mci_setstat(mci, EX_NOTSTICKY, ENHSCN(enhsc, "5.5.2"),
+ SmtpReplyBuffer);
+ return EX_DATAERR;
+ }
+ else if (r == 553)
+ {
+ /* mailbox name not allowed */
+ mci_setstat(mci, EX_NOTSTICKY, ENHSCN(enhsc, "5.1.3"),
+ SmtpReplyBuffer);
+ return EX_DATAERR;
+ }
+ else if (r == 552)
+ {
+ /* exceeded storage allocation */
+ mci_setstat(mci, EX_NOTSTICKY, ENHSCN(enhsc, "5.3.4"),
+ SmtpReplyBuffer);
+ if (bitset(MCIF_SIZE, mci->mci_flags))
+ e->e_flags |= EF_NO_BODY_RETN;
+ return EX_UNAVAILABLE;
+ }
+ else if (REPLYTYPE(r) == 5)
+ {
+ /* unknown error */
+ mci_setstat(mci, EX_NOTSTICKY, ENHSCN(enhsc, "5.0.0"),
+ SmtpReplyBuffer);
+ return EX_UNAVAILABLE;
+ }
+
+ if (LogLevel > 1)
+ {
+ sm_syslog(LOG_CRIT, e->e_id,
+ "%.100s: SMTP MAIL protocol error: %s",
+ CurHostName,
+ shortenstring(SmtpReplyBuffer, 403));
+ }
+
+ /* protocol error -- close up */
+ mci_setstat(mci, EX_PROTOCOL, ENHSCN(enhsc, "5.5.1"),
+ SmtpReplyBuffer);
+ smtpquit(m, mci, e);
+ return EX_PROTOCOL;
+}
+/*
+** SMTPRCPT -- designate recipient.
+**
+** Parameters:
+** to -- address of recipient.
+** m -- the mailer we are sending to.
+** mci -- the connection info for this transaction.
+** e -- the envelope for this transaction.
+**
+** Returns:
+** exit status corresponding to recipient status.
+**
+** Side Effects:
+** Sends the mail via SMTP.
+*/
+
+int
+smtprcpt(to, m, mci, e, ctladdr, xstart)
+ ADDRESS *to;
+ register MAILER *m;
+ MCI *mci;
+ ENVELOPE *e;
+ ADDRESS *ctladdr;
+ time_t xstart;
+{
+ char *bufp;
+ char optbuf[MAXLINE];
+
+#if PIPELINING
+ /*
+ ** If there is status waiting from the other end, read it.
+ ** This should normally happen because of SMTP pipelining.
+ */
+
+ while (mci->mci_nextaddr != NULL &&
+ sm_io_getinfo(mci->mci_in, SM_IO_IS_READABLE, NULL) > 0)
+ {
+ int r;
+
+ r = smtprcptstat(mci->mci_nextaddr, m, mci, e);
+ if (r != EX_OK)
+ {
+ markfailure(e, mci->mci_nextaddr, mci, r, false);
+ giveresponse(r, mci->mci_nextaddr->q_status, m, mci,
+ ctladdr, xstart, e, to);
+ }
+ mci->mci_nextaddr = mci->mci_nextaddr->q_pchain;
+ }
+#endif /* PIPELINING */
+
+ /*
+ ** Check if connection is gone, if so
+ ** it's a tempfail and we use mci_errno
+ ** for the reason.
+ */
+
+ if (mci->mci_state == MCIS_CLOSED)
+ {
+ errno = mci->mci_errno;
+ return EX_TEMPFAIL;
+ }
+
+ optbuf[0] = '\0';
+ bufp = optbuf;
+
+ /*
+ ** Warning: in the following it is assumed that the free space
+ ** in bufp is sizeof optbuf
+ */
+
+ if (bitset(MCIF_DSN, mci->mci_flags))
+ {
+ if (IS_DLVR_NOTIFY(e) &&
+ !bitset(MCIF_DLVR_BY, mci->mci_flags))
+ {
+ /* RFC 2852: 4.1.4.2 */
+ if (!bitset(QHASNOTIFY, to->q_flags))
+ to->q_flags |= QPINGONFAILURE|QPINGONDELAY|QHASNOTIFY;
+ else if (bitset(QPINGONSUCCESS, to->q_flags) ||
+ bitset(QPINGONFAILURE, to->q_flags) ||
+ bitset(QPINGONDELAY, to->q_flags))
+ to->q_flags |= QPINGONDELAY;
+ }
+
+ /* NOTIFY= parameter */
+ if (bitset(QHASNOTIFY, to->q_flags) &&
+ bitset(QPRIMARY, to->q_flags) &&
+ !bitnset(M_LOCALMAILER, m->m_flags))
+ {
+ bool firstone = true;
+
+ (void) sm_strlcat(bufp, " NOTIFY=", sizeof optbuf);
+ if (bitset(QPINGONSUCCESS, to->q_flags))
+ {
+ (void) sm_strlcat(bufp, "SUCCESS", sizeof optbuf);
+ firstone = false;
+ }
+ if (bitset(QPINGONFAILURE, to->q_flags))
+ {
+ if (!firstone)
+ (void) sm_strlcat(bufp, ",",
+ sizeof optbuf);
+ (void) sm_strlcat(bufp, "FAILURE", sizeof optbuf);
+ firstone = false;
+ }
+ if (bitset(QPINGONDELAY, to->q_flags))
+ {
+ if (!firstone)
+ (void) sm_strlcat(bufp, ",",
+ sizeof optbuf);
+ (void) sm_strlcat(bufp, "DELAY", sizeof optbuf);
+ firstone = false;
+ }
+ if (firstone)
+ (void) sm_strlcat(bufp, "NEVER", sizeof optbuf);
+ bufp += strlen(bufp);
+ }
+
+ /* ORCPT= parameter */
+ if (to->q_orcpt != NULL &&
+ SPACELEFT(optbuf, bufp) > strlen(to->q_orcpt) + 7)
+ {
+ (void) sm_snprintf(bufp, SPACELEFT(optbuf, bufp),
+ " ORCPT=%s", to->q_orcpt);
+ bufp += strlen(bufp);
+ }
+ }
+
+ smtpmessage("RCPT To:<%s>%s", m, mci, to->q_user, optbuf);
+ mci->mci_state = MCIS_RCPT;
+
+ SmtpPhase = mci->mci_phase = "client RCPT";
+ sm_setproctitle(true, e, "%s %s: %s", qid_printname(e),
+ CurHostName, mci->mci_phase);
+
+#if PIPELINING
+ /*
+ ** If running SMTP pipelining, we will pick up status later
+ */
+
+ if (bitset(MCIF_PIPELINED, mci->mci_flags))
+ return EX_OK;
+#endif /* PIPELINING */
+
+ return smtprcptstat(to, m, mci, e);
+}
+/*
+** SMTPRCPTSTAT -- get recipient status
+**
+** This is only called during SMTP pipelining
+**
+** Parameters:
+** to -- address of recipient.
+** m -- mailer being sent to.
+** mci -- the mailer connection information.
+** e -- the envelope for this message.
+**
+** Returns:
+** EX_* -- protocol status
+*/
+
+static int
+smtprcptstat(to, m, mci, e)
+ ADDRESS *to;
+ MAILER *m;
+ register MCI *mci;
+ register ENVELOPE *e;
+{
+ int r;
+ int save_errno;
+ char *enhsc;
+
+ /*
+ ** Check if connection is gone, if so
+ ** it's a tempfail and we use mci_errno
+ ** for the reason.
+ */
+
+ if (mci->mci_state == MCIS_CLOSED)
+ {
+ errno = mci->mci_errno;
+ return EX_TEMPFAIL;
+ }
+
+ enhsc = NULL;
+ r = reply(m, mci, e, TimeOuts.to_rcpt, NULL, &enhsc, XS_DEFAULT);
+ save_errno = errno;
+ to->q_rstatus = sm_rpool_strdup_x(e->e_rpool, SmtpReplyBuffer);
+ to->q_status = ENHSCN_RPOOL(enhsc, smtptodsn(r), e->e_rpool);
+ if (!bitnset(M_LMTP, m->m_flags))
+ to->q_statmta = mci->mci_host;
+ if (r < 0 || REPLYTYPE(r) == 4)
+ {
+ mci->mci_retryrcpt = true;
+ errno = save_errno;
+ return EX_TEMPFAIL;
+ }
+ else if (REPLYTYPE(r) == 2)
+ {
+ char *t;
+
+ if ((t = mci->mci_tolist) != NULL)
+ {
+ char *p;
+
+ *t++ = ',';
+ for (p = to->q_paddr; *p != '\0'; *t++ = *p++)
+ continue;
+ *t = '\0';
+ mci->mci_tolist = t;
+ }
+#if PIPELINING
+ mci->mci_okrcpts++;
+#endif /* PIPELINING */
+ return EX_OK;
+ }
+ else if (r == 550)
+ {
+ to->q_status = ENHSCN_RPOOL(enhsc, "5.1.1", e->e_rpool);
+ return EX_NOUSER;
+ }
+ else if (r == 551)
+ {
+ to->q_status = ENHSCN_RPOOL(enhsc, "5.1.6", e->e_rpool);
+ return EX_NOUSER;
+ }
+ else if (r == 553)
+ {
+ to->q_status = ENHSCN_RPOOL(enhsc, "5.1.3", e->e_rpool);
+ return EX_NOUSER;
+ }
+ else if (REPLYTYPE(r) == 5)
+ {
+ return EX_UNAVAILABLE;
+ }
+
+ if (LogLevel > 1)
+ {
+ sm_syslog(LOG_CRIT, e->e_id,
+ "%.100s: SMTP RCPT protocol error: %s",
+ CurHostName,
+ shortenstring(SmtpReplyBuffer, 403));
+ }
+
+ mci_setstat(mci, EX_PROTOCOL, ENHSCN(enhsc, "5.5.1"),
+ SmtpReplyBuffer);
+ return EX_PROTOCOL;
+}
+/*
+** SMTPDATA -- send the data and clean up the transaction.
+**
+** Parameters:
+** m -- mailer being sent to.
+** mci -- the mailer connection information.
+** e -- the envelope for this message.
+**
+** Returns:
+** exit status corresponding to DATA command.
+*/
+
+static jmp_buf CtxDataTimeout;
+static SM_EVENT *volatile DataTimeout = NULL;
+
+int
+smtpdata(m, mci, e, ctladdr, xstart)
+ MAILER *m;
+ register MCI *mci;
+ register ENVELOPE *e;
+ ADDRESS *ctladdr;
+ time_t xstart;
+{
+ register int r;
+ int rstat;
+ int xstat;
+ time_t timeout;
+ char *enhsc;
+
+ /*
+ ** Check if connection is gone, if so
+ ** it's a tempfail and we use mci_errno
+ ** for the reason.
+ */
+
+ if (mci->mci_state == MCIS_CLOSED)
+ {
+ errno = mci->mci_errno;
+ return EX_TEMPFAIL;
+ }
+
+ enhsc = NULL;
+
+ /*
+ ** Send the data.
+ ** First send the command and check that it is ok.
+ ** Then send the data (if there are valid recipients).
+ ** Follow it up with a dot to terminate.
+ ** Finally get the results of the transaction.
+ */
+
+ /* send the command and check ok to proceed */
+ smtpmessage("DATA", m, mci);
+
+#if PIPELINING
+ if (mci->mci_nextaddr != NULL)
+ {
+ char *oldto = e->e_to;
+
+ /* pick up any pending RCPT responses for SMTP pipelining */
+ while (mci->mci_nextaddr != NULL)
+ {
+ int r;
+
+ e->e_to = mci->mci_nextaddr->q_paddr;
+ r = smtprcptstat(mci->mci_nextaddr, m, mci, e);
+ if (r != EX_OK)
+ {
+ markfailure(e, mci->mci_nextaddr, mci, r,
+ false);
+ giveresponse(r, mci->mci_nextaddr->q_status, m,
+ mci, ctladdr, xstart, e,
+ mci->mci_nextaddr);
+ if (r == EX_TEMPFAIL)
+ mci->mci_nextaddr->q_state = QS_RETRY;
+ }
+ mci->mci_nextaddr = mci->mci_nextaddr->q_pchain;
+ }
+ e->e_to = oldto;
+
+ /*
+ ** Connection might be closed in response to a RCPT command,
+ ** i.e., the server responded with 421. In that case (at
+ ** least) one RCPT has a temporary failure, hence we don't
+ ** need to check mci_okrcpts (as it is done below) to figure
+ ** out which error to return.
+ */
+
+ if (mci->mci_state == MCIS_CLOSED)
+ {
+ errno = mci->mci_errno;
+ return EX_TEMPFAIL;
+ }
+ }
+#endif /* PIPELINING */
+
+ /* now proceed with DATA phase */
+ SmtpPhase = mci->mci_phase = "client DATA 354";
+ mci->mci_state = MCIS_DATA;
+ sm_setproctitle(true, e, "%s %s: %s",
+ qid_printname(e), CurHostName, mci->mci_phase);
+ r = reply(m, mci, e, TimeOuts.to_datainit, NULL, &enhsc, XS_DEFAULT);
+ if (r < 0 || REPLYTYPE(r) == 4)
+ {
+ if (r >= 0)
+ smtpquit(m, mci, e);
+ errno = mci->mci_errno;
+ return EX_TEMPFAIL;
+ }
+ else if (REPLYTYPE(r) == 5)
+ {
+ smtprset(m, mci, e);
+#if PIPELINING
+ if (mci->mci_okrcpts <= 0)
+ return mci->mci_retryrcpt ? EX_TEMPFAIL
+ : EX_UNAVAILABLE;
+#endif /* PIPELINING */
+ return EX_UNAVAILABLE;
+ }
+ else if (REPLYTYPE(r) != 3)
+ {
+ if (LogLevel > 1)
+ {
+ sm_syslog(LOG_CRIT, e->e_id,
+ "%.100s: SMTP DATA-1 protocol error: %s",
+ CurHostName,
+ shortenstring(SmtpReplyBuffer, 403));
+ }
+ smtprset(m, mci, e);
+ mci_setstat(mci, EX_PROTOCOL, ENHSCN(enhsc, "5.5.1"),
+ SmtpReplyBuffer);
+#if PIPELINING
+ if (mci->mci_okrcpts <= 0)
+ return mci->mci_retryrcpt ? EX_TEMPFAIL
+ : EX_PROTOCOL;
+#endif /* PIPELINING */
+ return EX_PROTOCOL;
+ }
+
+#if PIPELINING
+ if (mci->mci_okrcpts > 0)
+ {
+#endif /* PIPELINING */
+
+ /*
+ ** Set timeout around data writes. Make it at least large
+ ** enough for DNS timeouts on all recipients plus some fudge
+ ** factor. The main thing is that it should not be infinite.
+ */
+
+ if (setjmp(CtxDataTimeout) != 0)
+ {
+ mci->mci_errno = errno;
+ mci->mci_state = MCIS_ERROR;
+ mci_setstat(mci, EX_TEMPFAIL, "4.4.2", NULL);
+
+ /*
+ ** If putbody() couldn't finish due to a timeout,
+ ** rewind it here in the timeout handler. See
+ ** comments at the end of putbody() for reasoning.
+ */
+
+ if (e->e_dfp != NULL)
+ (void) bfrewind(e->e_dfp);
+
+ errno = mci->mci_errno;
+ syserr("451 4.4.1 timeout writing message to %s", CurHostName);
+ smtpquit(m, mci, e);
+ return EX_TEMPFAIL;
+ }
+
+ if (tTd(18, 101))
+ {
+ /* simulate a DATA timeout */
+ timeout = 1;
+ }
+ else
+ timeout = DATA_PROGRESS_TIMEOUT;
+
+ DataTimeout = sm_setevent(timeout, datatimeout, 0);
+
+
+ /*
+ ** Output the actual message.
+ */
+
+ (*e->e_puthdr)(mci, e->e_header, e, M87F_OUTER);
+
+ if (tTd(18, 101))
+ {
+ /* simulate a DATA timeout */
+ (void) sleep(2);
+ }
+
+ (*e->e_putbody)(mci, e, NULL);
+
+ /*
+ ** Cleanup after sending message.
+ */
+
+ if (DataTimeout != NULL)
+ sm_clrevent(DataTimeout);
+
+#if PIPELINING
+ }
+#endif /* PIPELINING */
+
+#if _FFR_CATCH_BROKEN_MTAS
+ if (sm_io_getinfo(mci->mci_in, SM_IO_IS_READABLE, NULL) > 0)
+ {
+ /* terminate the message */
+ (void) sm_io_fprintf(mci->mci_out, SM_TIME_DEFAULT, ".%s",
+ m->m_eol);
+ if (TrafficLogFile != NULL)
+ (void) sm_io_fprintf(TrafficLogFile, SM_TIME_DEFAULT,
+ "%05d >>> .\n", (int) CurrentPid);
+ if (Verbose)
+ nmessage(">>> .");
+
+ sm_syslog(LOG_CRIT, e->e_id,
+ "%.100s: SMTP DATA-1 protocol error: remote server returned response before final dot",
+ CurHostName);
+ mci->mci_errno = EIO;
+ mci->mci_state = MCIS_ERROR;
+ mci_setstat(mci, EX_PROTOCOL, "5.5.0", NULL);
+ smtpquit(m, mci, e);
+ return EX_PROTOCOL;
+ }
+#endif /* _FFR_CATCH_BROKEN_MTAS */
+
+ if (sm_io_error(mci->mci_out))
+ {
+ /* error during processing -- don't send the dot */
+ mci->mci_errno = EIO;
+ mci->mci_state = MCIS_ERROR;
+ mci_setstat(mci, EX_IOERR, "4.4.2", NULL);
+ smtpquit(m, mci, e);
+ return EX_IOERR;
+ }
+
+ /* terminate the message */
+ (void) sm_io_fprintf(mci->mci_out, SM_TIME_DEFAULT, ".%s", m->m_eol);
+ if (TrafficLogFile != NULL)
+ (void) sm_io_fprintf(TrafficLogFile, SM_TIME_DEFAULT,
+ "%05d >>> .\n", (int) CurrentPid);
+ if (Verbose)
+ nmessage(">>> .");
+
+ /* check for the results of the transaction */
+ SmtpPhase = mci->mci_phase = "client DATA status";
+ sm_setproctitle(true, e, "%s %s: %s", qid_printname(e),
+ CurHostName, mci->mci_phase);
+ if (bitnset(M_LMTP, m->m_flags))
+ return EX_OK;
+ r = reply(m, mci, e, TimeOuts.to_datafinal, NULL, &enhsc, XS_DEFAULT);
+ if (r < 0)
+ return EX_TEMPFAIL;
+ if (mci->mci_state == MCIS_DATA)
+ mci->mci_state = MCIS_OPEN;
+ xstat = EX_NOTSTICKY;
+ if (r == 452)
+ rstat = EX_TEMPFAIL;
+ else if (REPLYTYPE(r) == 4)
+ rstat = xstat = EX_TEMPFAIL;
+ else if (REPLYTYPE(r) == 2)
+ rstat = xstat = EX_OK;
+ else if (REPLYCLASS(r) != 5)
+ rstat = xstat = EX_PROTOCOL;
+ else if (REPLYTYPE(r) == 5)
+ rstat = EX_UNAVAILABLE;
+ else
+ rstat = EX_PROTOCOL;
+ mci_setstat(mci, xstat, ENHSCN(enhsc, smtptodsn(r)),
+ SmtpReplyBuffer);
+ if (bitset(MCIF_ENHSTAT, mci->mci_flags) &&
+ (r = isenhsc(SmtpReplyBuffer + 4, ' ')) > 0)
+ r += 5;
+ else
+ r = 4;
+ e->e_statmsg = sm_rpool_strdup_x(e->e_rpool, &SmtpReplyBuffer[r]);
+ SmtpPhase = mci->mci_phase = "idle";
+ sm_setproctitle(true, e, "%s: %s", CurHostName, mci->mci_phase);
+ if (rstat != EX_PROTOCOL)
+ return rstat;
+ if (LogLevel > 1)
+ {
+ sm_syslog(LOG_CRIT, e->e_id,
+ "%.100s: SMTP DATA-2 protocol error: %s",
+ CurHostName,
+ shortenstring(SmtpReplyBuffer, 403));
+ }
+ return rstat;
+}
+
+static void
+datatimeout(ignore)
+ int ignore;
+{
+ int save_errno = errno;
+
+ /*
+ ** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+ ** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+ ** DOING.
+ */
+
+ if (DataProgress)
+ {
+ time_t timeout;
+
+ /* check back again later */
+ if (tTd(18, 101))
+ {
+ /* simulate a DATA timeout */
+ timeout = 1;
+ }
+ else
+ timeout = DATA_PROGRESS_TIMEOUT;
+
+ /* reset the timeout */
+ DataTimeout = sm_sigsafe_setevent(timeout, datatimeout, 0);
+ DataProgress = false;
+ }
+ else
+ {
+ /* event is done */
+ DataTimeout = NULL;
+ }
+
+ /* if no progress was made or problem resetting event, die now */
+ if (DataTimeout == NULL)
+ {
+ errno = ETIMEDOUT;
+ longjmp(CtxDataTimeout, 1);
+ }
+ errno = save_errno;
+}
+/*
+** SMTPGETSTAT -- get status code from DATA in LMTP
+**
+** Parameters:
+** m -- the mailer to which we are sending the message.
+** mci -- the mailer connection structure.
+** e -- the current envelope.
+**
+** Returns:
+** The exit status corresponding to the reply code.
+*/
+
+int
+smtpgetstat(m, mci, e)
+ MAILER *m;
+ MCI *mci;
+ ENVELOPE *e;
+{
+ int r;
+ int off;
+ int status, xstat;
+ char *enhsc;
+
+ enhsc = NULL;
+
+ /* check for the results of the transaction */
+ r = reply(m, mci, e, TimeOuts.to_datafinal, NULL, &enhsc, XS_DEFAULT);
+ if (r < 0)
+ return EX_TEMPFAIL;
+ xstat = EX_NOTSTICKY;
+ if (REPLYTYPE(r) == 4)
+ status = EX_TEMPFAIL;
+ else if (REPLYTYPE(r) == 2)
+ status = xstat = EX_OK;
+ else if (REPLYCLASS(r) != 5)
+ status = xstat = EX_PROTOCOL;
+ else if (REPLYTYPE(r) == 5)
+ status = EX_UNAVAILABLE;
+ else
+ status = EX_PROTOCOL;
+ if (bitset(MCIF_ENHSTAT, mci->mci_flags) &&
+ (off = isenhsc(SmtpReplyBuffer + 4, ' ')) > 0)
+ off += 5;
+ else
+ off = 4;
+ e->e_statmsg = sm_rpool_strdup_x(e->e_rpool, &SmtpReplyBuffer[off]);
+ mci_setstat(mci, xstat, ENHSCN(enhsc, smtptodsn(r)), SmtpReplyBuffer);
+ if (LogLevel > 1 && status == EX_PROTOCOL)
+ {
+ sm_syslog(LOG_CRIT, e->e_id,
+ "%.100s: SMTP DATA-3 protocol error: %s",
+ CurHostName,
+ shortenstring(SmtpReplyBuffer, 403));
+ }
+ return status;
+}
+/*
+** SMTPQUIT -- close the SMTP connection.
+**
+** Parameters:
+** m -- a pointer to the mailer.
+** mci -- the mailer connection information.
+** e -- the current envelope.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** sends the final protocol and closes the connection.
+*/
+
+void
+smtpquit(m, mci, e)
+ register MAILER *m;
+ register MCI *mci;
+ ENVELOPE *e;
+{
+ bool oldSuprErrs = SuprErrs;
+ int rcode;
+ char *oldcurhost;
+
+ if (mci->mci_state == MCIS_CLOSED)
+ {
+ mci_close(mci, "smtpquit:1");
+ return;
+ }
+
+ oldcurhost = CurHostName;
+ CurHostName = mci->mci_host; /* XXX UGLY XXX */
+ if (CurHostName == NULL)
+ CurHostName = MyHostName;
+
+#if PIPELINING
+ mci->mci_okrcpts = 0;
+#endif /* PIPELINING */
+
+ /*
+ ** Suppress errors here -- we may be processing a different
+ ** job when we do the quit connection, and we don't want the
+ ** new job to be penalized for something that isn't it's
+ ** problem.
+ */
+
+ SuprErrs = true;
+
+ /* send the quit message if we haven't gotten I/O error */
+ if (mci->mci_state != MCIS_ERROR &&
+ mci->mci_state != MCIS_QUITING)
+ {
+ SmtpPhase = "client QUIT";
+ mci->mci_state = MCIS_QUITING;
+ smtpmessage("QUIT", m, mci);
+ (void) reply(m, mci, e, TimeOuts.to_quit, NULL, NULL,
+ XS_DEFAULT);
+ SuprErrs = oldSuprErrs;
+ if (mci->mci_state == MCIS_CLOSED)
+ goto end;
+ }
+
+ /* now actually close the connection and pick up the zombie */
+ rcode = endmailer(mci, e, NULL);
+ if (rcode != EX_OK)
+ {
+ char *mailer = NULL;
+
+ if (mci->mci_mailer != NULL &&
+ mci->mci_mailer->m_name != NULL)
+ mailer = mci->mci_mailer->m_name;
+
+ /* look for naughty mailers */
+ sm_syslog(LOG_ERR, e->e_id,
+ "smtpquit: mailer%s%s exited with exit value %d",
+ mailer == NULL ? "" : " ",
+ mailer == NULL ? "" : mailer,
+ rcode);
+ }
+
+ SuprErrs = oldSuprErrs;
+
+ end:
+ CurHostName = oldcurhost;
+ return;
+}
+/*
+** SMTPRSET -- send a RSET (reset) command
+**
+** Parameters:
+** m -- a pointer to the mailer.
+** mci -- the mailer connection information.
+** e -- the current envelope.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** closes the connection if there is no reply to RSET.
+*/
+
+void
+smtprset(m, mci, e)
+ register MAILER *m;
+ register MCI *mci;
+ ENVELOPE *e;
+{
+ int r;
+
+ CurHostName = mci->mci_host; /* XXX UGLY XXX */
+ if (CurHostName == NULL)
+ CurHostName = MyHostName;
+
+#if PIPELINING
+ mci->mci_okrcpts = 0;
+#endif /* PIPELINING */
+
+ /*
+ ** Check if connection is gone, if so
+ ** it's a tempfail and we use mci_errno
+ ** for the reason.
+ */
+
+ if (mci->mci_state == MCIS_CLOSED)
+ {
+ errno = mci->mci_errno;
+ return;
+ }
+
+ SmtpPhase = "client RSET";
+ smtpmessage("RSET", m, mci);
+ r = reply(m, mci, e, TimeOuts.to_rset, NULL, NULL, XS_DEFAULT);
+ if (r < 0)
+ return;
+
+ /*
+ ** Any response is deemed to be acceptable.
+ ** The standard does not state the proper action
+ ** to take when a value other than 250 is received.
+ **
+ ** However, if 421 is returned for the RSET, leave
+ ** mci_state alone (MCIS_SSD can be set in reply()
+ ** and MCIS_CLOSED can be set in smtpquit() if
+ ** reply() gets a 421 and calls smtpquit()).
+ */
+
+ if (mci->mci_state != MCIS_SSD && mci->mci_state != MCIS_CLOSED)
+ mci->mci_state = MCIS_OPEN;
+}
+/*
+** SMTPPROBE -- check the connection state
+**
+** Parameters:
+** mci -- the mailer connection information.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** closes the connection if there is no reply to RSET.
+*/
+
+int
+smtpprobe(mci)
+ register MCI *mci;
+{
+ int r;
+ MAILER *m = mci->mci_mailer;
+ ENVELOPE *e;
+ extern ENVELOPE BlankEnvelope;
+
+ CurHostName = mci->mci_host; /* XXX UGLY XXX */
+ if (CurHostName == NULL)
+ CurHostName = MyHostName;
+
+ e = &BlankEnvelope;
+ SmtpPhase = "client probe";
+ smtpmessage("RSET", m, mci);
+ r = reply(m, mci, e, TimeOuts.to_miscshort, NULL, NULL, XS_DEFAULT);
+ if (REPLYTYPE(r) != 2)
+ smtpquit(m, mci, e);
+ return r;
+}
+/*
+** REPLY -- read arpanet reply
+**
+** Parameters:
+** m -- the mailer we are reading the reply from.
+** mci -- the mailer connection info structure.
+** e -- the current envelope.
+** timeout -- the timeout for reads.
+** pfunc -- processing function called on each line of response.
+** If null, no special processing is done.
+** enhstat -- optional, returns enhanced error code string (if set)
+** rtype -- type of SmtpMsgBuffer: does it contains secret data?
+**
+** Returns:
+** reply code it reads.
+**
+** Side Effects:
+** flushes the mail file.
+*/
+
+int
+reply(m, mci, e, timeout, pfunc, enhstat, rtype)
+ MAILER *m;
+ MCI *mci;
+ ENVELOPE *e;
+ time_t timeout;
+ void (*pfunc) __P((char *, bool, MAILER *, MCI *, ENVELOPE *));
+ char **enhstat;
+ int rtype;
+{
+ register char *bufp;
+ register int r;
+ bool firstline = true;
+ char junkbuf[MAXLINE];
+ static char enhstatcode[ENHSCLEN];
+ int save_errno;
+
+ /*
+ ** Flush the output before reading response.
+ **
+ ** For SMTP pipelining, it would be better if we didn't do
+ ** this if there was already data waiting to be read. But
+ ** to do it properly means pushing it to the I/O library,
+ ** since it really needs to be done below the buffer layer.
+ */
+
+ if (mci->mci_out != NULL)
+ (void) sm_io_flush(mci->mci_out, SM_TIME_DEFAULT);
+
+ if (tTd(18, 1))
+ sm_dprintf("reply\n");
+
+ /*
+ ** Read the input line, being careful not to hang.
+ */
+
+ bufp = SmtpReplyBuffer;
+ for (;;)
+ {
+ register char *p;
+
+ /* actually do the read */
+ if (e->e_xfp != NULL) /* for debugging */
+ (void) sm_io_flush(e->e_xfp, SM_TIME_DEFAULT);
+
+ /* if we are in the process of closing just give the code */
+ if (mci->mci_state == MCIS_CLOSED)
+ return SMTPCLOSING;
+
+ /* don't try to read from a non-existent fd */
+ if (mci->mci_in == NULL)
+ {
+ if (mci->mci_errno == 0)
+ mci->mci_errno = EBADF;
+
+ /* errors on QUIT should be ignored */
+ if (strncmp(SmtpMsgBuffer, "QUIT", 4) == 0)
+ {
+ errno = mci->mci_errno;
+ mci_close(mci, "reply:1");
+ return -1;
+ }
+ mci->mci_state = MCIS_ERROR;
+ smtpquit(m, mci, e);
+ errno = mci->mci_errno;
+ return -1;
+ }
+
+ if (mci->mci_out != NULL)
+ (void) sm_io_flush(mci->mci_out, SM_TIME_DEFAULT);
+
+ /* get the line from the other side */
+ p = sfgets(bufp, MAXLINE, mci->mci_in, timeout, SmtpPhase);
+ save_errno = errno;
+ mci->mci_lastuse = curtime();
+
+ if (p == NULL)
+ {
+ bool oldholderrs;
+ extern char MsgBuf[];
+
+ /* errors on QUIT should be ignored */
+ if (strncmp(SmtpMsgBuffer, "QUIT", 4) == 0)
+ {
+ mci_close(mci, "reply:2");
+ return -1;
+ }
+
+ /* if the remote end closed early, fake an error */
+ errno = save_errno;
+ if (errno == 0)
+ {
+ (void) sm_snprintf(SmtpReplyBuffer,
+ sizeof SmtpReplyBuffer,
+ "421 4.4.1 Connection reset by %s",
+ CURHOSTNAME);
+#ifdef ECONNRESET
+ errno = ECONNRESET;
+#else /* ECONNRESET */
+ errno = EPIPE;
+#endif /* ECONNRESET */
+ }
+
+ mci->mci_errno = errno;
+ oldholderrs = HoldErrs;
+ HoldErrs = true;
+ usrerr("451 4.4.1 reply: read error from %s",
+ CURHOSTNAME);
+ mci_setstat(mci, EX_TEMPFAIL, "4.4.2", MsgBuf);
+
+ /* if debugging, pause so we can see state */
+ if (tTd(18, 100))
+ (void) pause();
+ mci->mci_state = MCIS_ERROR;
+ smtpquit(m, mci, e);
+#if XDEBUG
+ {
+ char wbuf[MAXLINE];
+
+ p = wbuf;
+ if (e->e_to != NULL)
+ {
+ (void) sm_snprintf(p,
+ SPACELEFT(wbuf, p),
+ "%s... ",
+ shortenstring(e->e_to, MAXSHORTSTR));
+ p += strlen(p);
+ }
+ (void) sm_snprintf(p, SPACELEFT(wbuf, p),
+ "reply(%.100s) during %s",
+ CURHOSTNAME, SmtpPhase);
+ checkfd012(wbuf);
+ }
+#endif /* XDEBUG */
+ HoldErrs = oldholderrs;
+ errno = save_errno;
+ return -1;
+ }
+ fixcrlf(bufp, true);
+
+ /* EHLO failure is not a real error */
+ if (e->e_xfp != NULL && (bufp[0] == '4' ||
+ (bufp[0] == '5' && strncmp(SmtpMsgBuffer, "EHLO", 4) != 0)))
+ {
+ /* serious error -- log the previous command */
+ if (SmtpNeedIntro)
+ {
+ /* inform user who we are chatting with */
+ (void) sm_io_fprintf(CurEnv->e_xfp,
+ SM_TIME_DEFAULT,
+ "... while talking to %s:\n",
+ CURHOSTNAME);
+ SmtpNeedIntro = false;
+ }
+ if (SmtpMsgBuffer[0] != '\0')
+ {
+ (void) sm_io_fprintf(e->e_xfp,
+ SM_TIME_DEFAULT,
+ ">>> %s\n",
+ (rtype == XS_STARTTLS)
+ ? "STARTTLS dialogue"
+ : ((rtype == XS_AUTH)
+ ? "AUTH dialogue"
+ : SmtpMsgBuffer));
+ SmtpMsgBuffer[0] = '\0';
+ }
+
+ /* now log the message as from the other side */
+ (void) sm_io_fprintf(e->e_xfp, SM_TIME_DEFAULT,
+ "<<< %s\n", bufp);
+ }
+
+ /* display the input for verbose mode */
+ if (Verbose)
+ nmessage("050 %s", bufp);
+
+ /* ignore improperly formatted input */
+ if (!ISSMTPREPLY(bufp))
+ continue;
+
+ if (bitset(MCIF_ENHSTAT, mci->mci_flags) &&
+ enhstat != NULL &&
+ extenhsc(bufp + 4, ' ', enhstatcode) > 0)
+ *enhstat = enhstatcode;
+
+ /* process the line */
+ if (pfunc != NULL)
+ (*pfunc)(bufp, firstline, m, mci, e);
+
+ firstline = false;
+
+ /* decode the reply code */
+ r = atoi(bufp);
+
+ /* extra semantics: 0xx codes are "informational" */
+ if (r < 100)
+ continue;
+
+ /* if no continuation lines, return this line */
+ if (bufp[3] != '-')
+ break;
+
+ /* first line of real reply -- ignore rest */
+ bufp = junkbuf;
+ }
+
+ /*
+ ** Now look at SmtpReplyBuffer -- only care about the first
+ ** line of the response from here on out.
+ */
+
+ /* save temporary failure messages for posterity */
+ if (SmtpReplyBuffer[0] == '4')
+ (void) sm_strlcpy(SmtpError, SmtpReplyBuffer, sizeof SmtpError);
+
+ /* reply code 421 is "Service Shutting Down" */
+ if (r == SMTPCLOSING && mci->mci_state != MCIS_SSD &&
+ mci->mci_state != MCIS_QUITING)
+ {
+ /* send the quit protocol */
+ mci->mci_state = MCIS_SSD;
+ smtpquit(m, mci, e);
+ }
+
+ return r;
+}
+/*
+** SMTPMESSAGE -- send message to server
+**
+** Parameters:
+** f -- format
+** m -- the mailer to control formatting.
+** a, b, c -- parameters
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** writes message to mci->mci_out.
+*/
+
+/*VARARGS1*/
+void
+#ifdef __STDC__
+smtpmessage(char *f, MAILER *m, MCI *mci, ...)
+#else /* __STDC__ */
+smtpmessage(f, m, mci, va_alist)
+ char *f;
+ MAILER *m;
+ MCI *mci;
+ va_dcl
+#endif /* __STDC__ */
+{
+ SM_VA_LOCAL_DECL
+
+ SM_VA_START(ap, mci);
+ (void) sm_vsnprintf(SmtpMsgBuffer, sizeof SmtpMsgBuffer, f, ap);
+ SM_VA_END(ap);
+
+ if (tTd(18, 1) || Verbose)
+ nmessage(">>> %s", SmtpMsgBuffer);
+ if (TrafficLogFile != NULL)
+ (void) sm_io_fprintf(TrafficLogFile, SM_TIME_DEFAULT,
+ "%05d >>> %s\n", (int) CurrentPid,
+ SmtpMsgBuffer);
+ if (mci->mci_out != NULL)
+ {
+ (void) sm_io_fprintf(mci->mci_out, SM_TIME_DEFAULT, "%s%s",
+ SmtpMsgBuffer, m == NULL ? "\r\n"
+ : m->m_eol);
+ }
+ else if (tTd(18, 1))
+ {
+ sm_dprintf("smtpmessage: NULL mci_out\n");
+ }
+}
diff --git a/usr/src/cmd/sendmail/src/util.c b/usr/src/cmd/sendmail/src/util.c
new file mode 100644
index 0000000000..55ba12159c
--- /dev/null
+++ b/usr/src/cmd/sendmail/src/util.c
@@ -0,0 +1,2882 @@
+/*
+ * Copyright (c) 1998-2004 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sendmail.h>
+
+SM_RCSID("@(#)$Id: util.c,v 8.383 2004/08/02 18:50:59 ca Exp $")
+
+#include <sysexits.h>
+#include <sm/xtrap.h>
+
+/*
+** NEWSTR -- Create a copy of a C string
+**
+** Parameters:
+** s -- the string to copy.
+**
+** Returns:
+** pointer to newly allocated string.
+*/
+
+char *
+newstr(s)
+ const char *s;
+{
+ size_t l;
+ char *n;
+
+ l = strlen(s);
+ SM_ASSERT(l + 1 > l);
+ n = xalloc(l + 1);
+ sm_strlcpy(n, s, l + 1);
+ return n;
+}
+
+/*
+** ADDQUOTES -- Adds quotes & quote bits to a string.
+**
+** Runs through a string and adds backslashes and quote bits.
+**
+** Parameters:
+** s -- the string to modify.
+** rpool -- resource pool from which to allocate result
+**
+** Returns:
+** pointer to quoted string.
+*/
+
+char *
+addquotes(s, rpool)
+ char *s;
+ SM_RPOOL_T *rpool;
+{
+ int len = 0;
+ char c;
+ char *p = s, *q, *r;
+
+ if (s == NULL)
+ return NULL;
+
+ /* Find length of quoted string */
+ while ((c = *p++) != '\0')
+ {
+ len++;
+ if (c == '\\' || c == '"')
+ len++;
+ }
+
+ q = r = sm_rpool_malloc_x(rpool, len + 3);
+ p = s;
+
+ /* add leading quote */
+ *q++ = '"';
+ while ((c = *p++) != '\0')
+ {
+ /* quote \ or " */
+ if (c == '\\' || c == '"')
+ *q++ = '\\';
+ *q++ = c;
+ }
+ *q++ = '"';
+ *q = '\0';
+ return r;
+}
+
+/*
+** STRIPBACKSLASH -- Strip all leading backslashes from a string, provided
+** the following character is alpha-numerical.
+**
+** This is done in place.
+**
+** Parameters:
+** s -- the string to strip.
+**
+** Returns:
+** none.
+*/
+
+void
+stripbackslash(s)
+ char *s;
+{
+ char *p, *q, c;
+
+ if (s == NULL || *s == '\0')
+ return;
+ p = q = s;
+ while (*p == '\\' && (p[1] == '\\' || (isascii(p[1]) && isalnum(p[1]))))
+ p++;
+ do
+ {
+ c = *q++ = *p++;
+ } while (c != '\0');
+}
+
+/*
+** RFC822_STRING -- Checks string for proper RFC822 string quoting.
+**
+** Runs through a string and verifies RFC822 special characters
+** are only found inside comments, quoted strings, or backslash
+** escaped. Also verified balanced quotes and parenthesis.
+**
+** Parameters:
+** s -- the string to modify.
+**
+** Returns:
+** true iff the string is RFC822 compliant, false otherwise.
+*/
+
+bool
+rfc822_string(s)
+ char *s;
+{
+ bool quoted = false;
+ int commentlev = 0;
+ char *c = s;
+
+ if (s == NULL)
+ return false;
+
+ while (*c != '\0')
+ {
+ /* escaped character */
+ if (*c == '\\')
+ {
+ c++;
+ if (*c == '\0')
+ return false;
+ }
+ else if (commentlev == 0 && *c == '"')
+ quoted = !quoted;
+ else if (!quoted)
+ {
+ if (*c == ')')
+ {
+ /* unbalanced ')' */
+ if (commentlev == 0)
+ return false;
+ else
+ commentlev--;
+ }
+ else if (*c == '(')
+ commentlev++;
+ else if (commentlev == 0 &&
+ strchr(MustQuoteChars, *c) != NULL)
+ return false;
+ }
+ c++;
+ }
+
+ /* unbalanced '"' or '(' */
+ return !quoted && commentlev == 0;
+}
+/*
+** SHORTEN_RFC822_STRING -- Truncate and rebalance an RFC822 string
+**
+** Arbitrarily shorten (in place) an RFC822 string and rebalance
+** comments and quotes.
+**
+** Parameters:
+** string -- the string to shorten
+** length -- the maximum size, 0 if no maximum
+**
+** Returns:
+** true if string is changed, false otherwise
+**
+** Side Effects:
+** Changes string in place, possibly resulting
+** in a shorter string.
+*/
+
+bool
+shorten_rfc822_string(string, length)
+ char *string;
+ size_t length;
+{
+ bool backslash = false;
+ bool modified = false;
+ bool quoted = false;
+ size_t slen;
+ int parencount = 0;
+ char *ptr = string;
+
+ /*
+ ** If have to rebalance an already short enough string,
+ ** need to do it within allocated space.
+ */
+
+ slen = strlen(string);
+ if (length == 0 || slen < length)
+ length = slen;
+
+ while (*ptr != '\0')
+ {
+ if (backslash)
+ {
+ backslash = false;
+ goto increment;
+ }
+
+ if (*ptr == '\\')
+ backslash = true;
+ else if (*ptr == '(')
+ {
+ if (!quoted)
+ parencount++;
+ }
+ else if (*ptr == ')')
+ {
+ if (--parencount < 0)
+ parencount = 0;
+ }
+
+ /* Inside a comment, quotes don't matter */
+ if (parencount <= 0 && *ptr == '"')
+ quoted = !quoted;
+
+increment:
+ /* Check for sufficient space for next character */
+ if (length - (ptr - string) <= (size_t) ((backslash ? 1 : 0) +
+ parencount +
+ (quoted ? 1 : 0)))
+ {
+ /* Not enough, backtrack */
+ if (*ptr == '\\')
+ backslash = false;
+ else if (*ptr == '(' && !quoted)
+ parencount--;
+ else if (*ptr == '"' && parencount == 0)
+ quoted = false;
+ break;
+ }
+ ptr++;
+ }
+
+ /* Rebalance */
+ while (parencount-- > 0)
+ {
+ if (*ptr != ')')
+ {
+ modified = true;
+ *ptr = ')';
+ }
+ ptr++;
+ }
+ if (quoted)
+ {
+ if (*ptr != '"')
+ {
+ modified = true;
+ *ptr = '"';
+ }
+ ptr++;
+ }
+ if (*ptr != '\0')
+ {
+ modified = true;
+ *ptr = '\0';
+ }
+ return modified;
+}
+/*
+** FIND_CHARACTER -- find an unquoted character in an RFC822 string
+**
+** Find an unquoted, non-commented character in an RFC822
+** string and return a pointer to its location in the
+** string.
+**
+** Parameters:
+** string -- the string to search
+** character -- the character to find
+**
+** Returns:
+** pointer to the character, or
+** a pointer to the end of the line if character is not found
+*/
+
+char *
+find_character(string, character)
+ char *string;
+ int character;
+{
+ bool backslash = false;
+ bool quoted = false;
+ int parencount = 0;
+
+ while (string != NULL && *string != '\0')
+ {
+ if (backslash)
+ {
+ backslash = false;
+ if (!quoted && character == '\\' && *string == '\\')
+ break;
+ string++;
+ continue;
+ }
+ switch (*string)
+ {
+ case '\\':
+ backslash = true;
+ break;
+
+ case '(':
+ if (!quoted)
+ parencount++;
+ break;
+
+ case ')':
+ if (--parencount < 0)
+ parencount = 0;
+ break;
+ }
+
+ /* Inside a comment, nothing matters */
+ if (parencount > 0)
+ {
+ string++;
+ continue;
+ }
+
+ if (*string == '"')
+ quoted = !quoted;
+ else if (*string == character && !quoted)
+ break;
+ string++;
+ }
+
+ /* Return pointer to the character */
+ return string;
+}
+
+/*
+** CHECK_BODYTYPE -- check bodytype parameter
+**
+** Parameters:
+** bodytype -- bodytype parameter
+**
+** Returns:
+** BODYTYPE_* according to parameter
+**
+*/
+
+int
+check_bodytype(bodytype)
+ char *bodytype;
+{
+ /* check body type for legality */
+ if (bodytype == NULL)
+ return BODYTYPE_NONE;
+ if (sm_strcasecmp(bodytype, "7BIT") == 0)
+ return BODYTYPE_7BIT;
+ if (sm_strcasecmp(bodytype, "8BITMIME") == 0)
+ return BODYTYPE_8BITMIME;
+ return BODYTYPE_ILLEGAL;
+}
+
+#if _FFR_BESTMX_BETTER_TRUNCATION || _FFR_DNSMAP_MULTI
+/*
+** TRUNCATE_AT_DELIM -- truncate string at a delimiter and append "..."
+**
+** Parameters:
+** str -- string to truncate
+** len -- maximum length (including '\0') (0 for unlimited)
+** delim -- delimiter character
+**
+** Returns:
+** None.
+*/
+
+void
+truncate_at_delim(str, len, delim)
+ char *str;
+ size_t len;
+ int delim;
+{
+ char *p;
+
+ if (str == NULL || len == 0 || strlen(str) < len)
+ return;
+
+ *(str + len - 1) = '\0';
+ while ((p = strrchr(str, delim)) != NULL)
+ {
+ *p = '\0';
+ if (p - str + 4 < len)
+ {
+ *p++ = (char) delim;
+ *p = '\0';
+ (void) sm_strlcat(str, "...", len);
+ return;
+ }
+ }
+
+ /* Couldn't find a place to append "..." */
+ if (len > 3)
+ (void) sm_strlcpy(str, "...", len);
+ else
+ str[0] = '\0';
+}
+#endif /* _FFR_BESTMX_BETTER_TRUNCATION || _FFR_DNSMAP_MULTI */
+/*
+** XALLOC -- Allocate memory, raise an exception on error
+**
+** Parameters:
+** sz -- size of area to allocate.
+**
+** Returns:
+** pointer to data region.
+**
+** Exceptions:
+** SmHeapOutOfMemory (F:sm.heap) -- cannot allocate memory
+**
+** Side Effects:
+** Memory is allocated.
+*/
+
+char *
+#if SM_HEAP_CHECK
+xalloc_tagged(sz, file, line)
+ register int sz;
+ char *file;
+ int line;
+#else /* SM_HEAP_CHECK */
+xalloc(sz)
+ register int sz;
+#endif /* SM_HEAP_CHECK */
+{
+ register char *p;
+
+ /* some systems can't handle size zero mallocs */
+ if (sz <= 0)
+ sz = 1;
+
+ /* scaffolding for testing error handling code */
+ sm_xtrap_raise_x(&SmHeapOutOfMemory);
+
+ p = sm_malloc_tagged((unsigned) sz, file, line, sm_heap_group());
+ if (p == NULL)
+ {
+ sm_exc_raise_x(&SmHeapOutOfMemory);
+ }
+ return p;
+}
+/*
+** COPYPLIST -- copy list of pointers.
+**
+** This routine is the equivalent of strdup for lists of
+** pointers.
+**
+** Parameters:
+** list -- list of pointers to copy.
+** Must be NULL terminated.
+** copycont -- if true, copy the contents of the vector
+** (which must be a string) also.
+** rpool -- resource pool from which to allocate storage,
+** or NULL
+**
+** Returns:
+** a copy of 'list'.
+*/
+
+char **
+copyplist(list, copycont, rpool)
+ char **list;
+ bool copycont;
+ SM_RPOOL_T *rpool;
+{
+ register char **vp;
+ register char **newvp;
+
+ for (vp = list; *vp != NULL; vp++)
+ continue;
+
+ vp++;
+
+ newvp = (char **) sm_rpool_malloc_x(rpool, (vp - list) * sizeof *vp);
+ memmove((char *) newvp, (char *) list, (int) (vp - list) * sizeof *vp);
+
+ if (copycont)
+ {
+ for (vp = newvp; *vp != NULL; vp++)
+ *vp = sm_rpool_strdup_x(rpool, *vp);
+ }
+
+ return newvp;
+}
+/*
+** COPYQUEUE -- copy address queue.
+**
+** This routine is the equivalent of strdup for address queues;
+** addresses marked as QS_IS_DEAD() aren't copied
+**
+** Parameters:
+** addr -- list of address structures to copy.
+** rpool -- resource pool from which to allocate storage
+**
+** Returns:
+** a copy of 'addr'.
+*/
+
+ADDRESS *
+copyqueue(addr, rpool)
+ ADDRESS *addr;
+ SM_RPOOL_T *rpool;
+{
+ register ADDRESS *newaddr;
+ ADDRESS *ret;
+ register ADDRESS **tail = &ret;
+
+ while (addr != NULL)
+ {
+ if (!QS_IS_DEAD(addr->q_state))
+ {
+ newaddr = (ADDRESS *) sm_rpool_malloc_x(rpool,
+ sizeof *newaddr);
+ STRUCTCOPY(*addr, *newaddr);
+ *tail = newaddr;
+ tail = &newaddr->q_next;
+ }
+ addr = addr->q_next;
+ }
+ *tail = NULL;
+
+ return ret;
+}
+/*
+** LOG_SENDMAIL_PID -- record sendmail pid and command line.
+**
+** Parameters:
+** e -- the current envelope.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** writes pidfile, logs command line.
+** keeps file open and locked to prevent overwrite of active file
+*/
+
+static SM_FILE_T *Pidf = NULL;
+
+void
+log_sendmail_pid(e)
+ ENVELOPE *e;
+{
+ long sff;
+ char pidpath[MAXPATHLEN];
+ extern char *CommandLineArgs;
+
+ /* write the pid to the log file for posterity */
+ sff = SFF_NOLINK|SFF_ROOTOK|SFF_REGONLY|SFF_CREAT|SFF_NBLOCK;
+ if (TrustedUid != 0 && RealUid == TrustedUid)
+ sff |= SFF_OPENASROOT;
+ expand(PidFile, pidpath, sizeof pidpath, e);
+ Pidf = safefopen(pidpath, O_WRONLY|O_TRUNC, FileMode, sff);
+ if (Pidf == NULL)
+ {
+ if (errno == EWOULDBLOCK)
+ sm_syslog(LOG_ERR, NOQID,
+ "unable to write pid to %s: file in use by another process",
+ pidpath);
+ else
+ sm_syslog(LOG_ERR, NOQID,
+ "unable to write pid to %s: %s",
+ pidpath, sm_errstring(errno));
+ }
+ else
+ {
+ PidFilePid = getpid();
+
+ /* write the process id on line 1 */
+ (void) sm_io_fprintf(Pidf, SM_TIME_DEFAULT, "%ld\n",
+ (long) PidFilePid);
+
+ /* line 2 contains all command line flags */
+ (void) sm_io_fprintf(Pidf, SM_TIME_DEFAULT, "%s\n",
+ CommandLineArgs);
+
+ /* flush */
+ (void) sm_io_flush(Pidf, SM_TIME_DEFAULT);
+
+ /*
+ ** Leave pid file open until process ends
+ ** so it's not overwritten by another
+ ** process.
+ */
+ }
+ if (LogLevel > 9)
+ sm_syslog(LOG_INFO, NOQID, "started as: %s", CommandLineArgs);
+}
+
+/*
+** CLOSE_SENDMAIL_PID -- close sendmail pid file
+**
+** Parameters:
+** none.
+**
+** Returns:
+** none.
+*/
+
+void
+close_sendmail_pid()
+{
+ if (Pidf == NULL)
+ return;
+
+ (void) sm_io_close(Pidf, SM_TIME_DEFAULT);
+ Pidf = NULL;
+}
+
+/*
+** SET_DELIVERY_MODE -- set and record the delivery mode
+**
+** Parameters:
+** mode -- delivery mode
+** e -- the current envelope.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** sets {deliveryMode} macro
+*/
+
+void
+set_delivery_mode(mode, e)
+ int mode;
+ ENVELOPE *e;
+{
+ char buf[2];
+
+ e->e_sendmode = (char) mode;
+ buf[0] = (char) mode;
+ buf[1] = '\0';
+ macdefine(&e->e_macro, A_TEMP, macid("{deliveryMode}"), buf);
+}
+/*
+** SET_OP_MODE -- set and record the op mode
+**
+** Parameters:
+** mode -- op mode
+** e -- the current envelope.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** sets {opMode} macro
+*/
+
+void
+set_op_mode(mode)
+ int mode;
+{
+ char buf[2];
+ extern ENVELOPE BlankEnvelope;
+
+ OpMode = (char) mode;
+ buf[0] = (char) mode;
+ buf[1] = '\0';
+ macdefine(&BlankEnvelope.e_macro, A_TEMP, MID_OPMODE, buf);
+}
+/*
+** PRINTAV -- print argument vector.
+**
+** Parameters:
+** fp -- output file pointer.
+** av -- argument vector.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** prints av.
+*/
+
+void
+printav(fp, av)
+ SM_FILE_T *fp;
+ register char **av;
+{
+ while (*av != NULL)
+ {
+ if (tTd(0, 44))
+ sm_dprintf("\n\t%08lx=", (unsigned long) *av);
+ else
+ (void) sm_io_putc(fp, SM_TIME_DEFAULT, ' ');
+ xputs(fp, *av++);
+ }
+ (void) sm_io_putc(fp, SM_TIME_DEFAULT, '\n');
+}
+/*
+** XPUTS -- put string doing control escapes.
+**
+** Parameters:
+** fp -- output file pointer.
+** s -- string to put.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** output to stdout
+*/
+
+void
+xputs(fp, s)
+ SM_FILE_T *fp;
+ register const char *s;
+{
+ register int c;
+ register struct metamac *mp;
+ bool shiftout = false;
+ extern struct metamac MetaMacros[];
+ static SM_DEBUG_T DebugANSI = SM_DEBUG_INITIALIZER("ANSI",
+ "@(#)$Debug: ANSI - enable reverse video in debug output $");
+
+ /*
+ ** TermEscape is set here, rather than in main(),
+ ** because ANSI mode can be turned on or off at any time
+ ** if we are in -bt rule testing mode.
+ */
+
+ if (sm_debug_unknown(&DebugANSI))
+ {
+ if (sm_debug_active(&DebugANSI, 1))
+ {
+ TermEscape.te_rv_on = "\033[7m";
+ TermEscape.te_rv_off = "\033[0m";
+ }
+ else
+ {
+ TermEscape.te_rv_on = "";
+ TermEscape.te_rv_off = "";
+ }
+ }
+
+ if (s == NULL)
+ {
+ (void) sm_io_fprintf(fp, SM_TIME_DEFAULT, "%s<null>%s",
+ TermEscape.te_rv_on, TermEscape.te_rv_off);
+ return;
+ }
+ while ((c = (*s++ & 0377)) != '\0')
+ {
+ if (shiftout)
+ {
+ (void) sm_io_fprintf(fp, SM_TIME_DEFAULT, "%s",
+ TermEscape.te_rv_off);
+ shiftout = false;
+ }
+ if (!isascii(c))
+ {
+ if (c == MATCHREPL)
+ {
+ (void) sm_io_fprintf(fp, SM_TIME_DEFAULT,
+ "%s$",
+ TermEscape.te_rv_on);
+ shiftout = true;
+ if (*s == '\0')
+ continue;
+ c = *s++ & 0377;
+ goto printchar;
+ }
+ if (c == MACROEXPAND || c == MACRODEXPAND)
+ {
+ (void) sm_io_fprintf(fp, SM_TIME_DEFAULT,
+ "%s$",
+ TermEscape.te_rv_on);
+ if (c == MACRODEXPAND)
+ (void) sm_io_putc(fp,
+ SM_TIME_DEFAULT, '&');
+ shiftout = true;
+ if (*s == '\0')
+ continue;
+ if (strchr("=~&?", *s) != NULL)
+ (void) sm_io_putc(fp,
+ SM_TIME_DEFAULT,
+ *s++);
+ if (bitset(0200, *s))
+ (void) sm_io_fprintf(fp,
+ SM_TIME_DEFAULT,
+ "{%s}",
+ macname(bitidx(*s++)));
+ else
+ (void) sm_io_fprintf(fp,
+ SM_TIME_DEFAULT,
+ "%c",
+ *s++);
+ continue;
+ }
+ for (mp = MetaMacros; mp->metaname != '\0'; mp++)
+ {
+ if (bitidx(mp->metaval) == c)
+ {
+ (void) sm_io_fprintf(fp,
+ SM_TIME_DEFAULT,
+ "%s$%c",
+ TermEscape.te_rv_on,
+ mp->metaname);
+ shiftout = true;
+ break;
+ }
+ }
+ if (c == MATCHCLASS || c == MATCHNCLASS)
+ {
+ if (bitset(0200, *s))
+ (void) sm_io_fprintf(fp,
+ SM_TIME_DEFAULT,
+ "{%s}",
+ macname(bitidx(*s++)));
+ else if (*s != '\0')
+ (void) sm_io_fprintf(fp,
+ SM_TIME_DEFAULT,
+ "%c",
+ *s++);
+ }
+ if (mp->metaname != '\0')
+ continue;
+
+ /* unrecognized meta character */
+ (void) sm_io_fprintf(fp, SM_TIME_DEFAULT, "%sM-",
+ TermEscape.te_rv_on);
+ shiftout = true;
+ c &= 0177;
+ }
+ printchar:
+ if (isprint(c))
+ {
+ (void) sm_io_putc(fp, SM_TIME_DEFAULT, c);
+ continue;
+ }
+
+ /* wasn't a meta-macro -- find another way to print it */
+ switch (c)
+ {
+ case '\n':
+ c = 'n';
+ break;
+
+ case '\r':
+ c = 'r';
+ break;
+
+ case '\t':
+ c = 't';
+ break;
+ }
+ if (!shiftout)
+ {
+ (void) sm_io_fprintf(fp, SM_TIME_DEFAULT, "%s",
+ TermEscape.te_rv_on);
+ shiftout = true;
+ }
+ if (isprint(c))
+ {
+ (void) sm_io_putc(fp, SM_TIME_DEFAULT, '\\');
+ (void) sm_io_putc(fp, SM_TIME_DEFAULT, c);
+ }
+ else
+ {
+ (void) sm_io_putc(fp, SM_TIME_DEFAULT, '^');
+ (void) sm_io_putc(fp, SM_TIME_DEFAULT, c ^ 0100);
+ }
+ }
+ if (shiftout)
+ (void) sm_io_fprintf(fp, SM_TIME_DEFAULT, "%s",
+ TermEscape.te_rv_off);
+ (void) sm_io_flush(fp, SM_TIME_DEFAULT);
+}
+/*
+** MAKELOWER -- Translate a line into lower case
+**
+** Parameters:
+** p -- the string to translate. If NULL, return is
+** immediate.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** String pointed to by p is translated to lower case.
+*/
+
+void
+makelower(p)
+ register char *p;
+{
+ register char c;
+
+ if (p == NULL)
+ return;
+ for (; (c = *p) != '\0'; p++)
+ if (isascii(c) && isupper(c))
+ *p = tolower(c);
+}
+/*
+** FIXCRLF -- fix <CR><LF> in line.
+**
+** Looks for the <CR><LF> combination and turns it into the
+** UNIX canonical <NL> character. It only takes one line,
+** i.e., it is assumed that the first <NL> found is the end
+** of the line.
+**
+** Parameters:
+** line -- the line to fix.
+** stripnl -- if true, strip the newline also.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** line is changed in place.
+*/
+
+void
+fixcrlf(line, stripnl)
+ char *line;
+ bool stripnl;
+{
+ register char *p;
+
+ p = strchr(line, '\n');
+ if (p == NULL)
+ return;
+ if (p > line && p[-1] == '\r')
+ p--;
+ if (!stripnl)
+ *p++ = '\n';
+ *p = '\0';
+}
+/*
+** PUTLINE -- put a line like fputs obeying SMTP conventions
+**
+** This routine always guarantees outputing a newline (or CRLF,
+** as appropriate) at the end of the string.
+**
+** Parameters:
+** l -- line to put.
+** mci -- the mailer connection information.
+**
+** Returns:
+** none
+**
+** Side Effects:
+** output of l to mci->mci_out.
+*/
+
+void
+putline(l, mci)
+ register char *l;
+ register MCI *mci;
+{
+ putxline(l, strlen(l), mci, PXLF_MAPFROM);
+}
+/*
+** PUTXLINE -- putline with flags bits.
+**
+** This routine always guarantees outputing a newline (or CRLF,
+** as appropriate) at the end of the string.
+**
+** Parameters:
+** l -- line to put.
+** len -- the length of the line.
+** mci -- the mailer connection information.
+** pxflags -- flag bits:
+** PXLF_MAPFROM -- map From_ to >From_.
+** PXLF_STRIP8BIT -- strip 8th bit.
+** PXLF_HEADER -- map bare newline in header to newline space.
+** PXLF_NOADDEOL -- don't add an EOL if one wasn't present.
+**
+** Returns:
+** none
+**
+** Side Effects:
+** output of l to mci->mci_out.
+*/
+
+void
+putxline(l, len, mci, pxflags)
+ register char *l;
+ size_t len;
+ register MCI *mci;
+ int pxflags;
+{
+ bool dead = false;
+ register char *p, *end;
+ int slop = 0;
+
+ /* strip out 0200 bits -- these can look like TELNET protocol */
+ if (bitset(MCIF_7BIT, mci->mci_flags) ||
+ bitset(PXLF_STRIP8BIT, pxflags))
+ {
+ register char svchar;
+
+ for (p = l; (svchar = *p) != '\0'; ++p)
+ if (bitset(0200, svchar))
+ *p = svchar &~ 0200;
+ }
+
+ end = l + len;
+ do
+ {
+ bool noeol = false;
+
+ /* find the end of the line */
+ p = memchr(l, '\n', end - l);
+ if (p == NULL)
+ {
+ p = end;
+ noeol = true;
+ }
+
+ if (TrafficLogFile != NULL)
+ (void) sm_io_fprintf(TrafficLogFile, SM_TIME_DEFAULT,
+ "%05d >>> ", (int) CurrentPid);
+
+ /* check for line overflow */
+ while (mci->mci_mailer->m_linelimit > 0 &&
+ (p - l + slop) > mci->mci_mailer->m_linelimit)
+ {
+ char *l_base = l;
+ register char *q = &l[mci->mci_mailer->m_linelimit - slop - 1];
+
+ if (l[0] == '.' && slop == 0 &&
+ bitnset(M_XDOT, mci->mci_mailer->m_flags))
+ {
+ if (sm_io_putc(mci->mci_out, SM_TIME_DEFAULT,
+ '.') == SM_IO_EOF)
+ dead = true;
+ else
+ {
+ /* record progress for DATA timeout */
+ DataProgress = true;
+ }
+ if (TrafficLogFile != NULL)
+ (void) sm_io_putc(TrafficLogFile,
+ SM_TIME_DEFAULT, '.');
+ }
+ else if (l[0] == 'F' && slop == 0 &&
+ bitset(PXLF_MAPFROM, pxflags) &&
+ strncmp(l, "From ", 5) == 0 &&
+ bitnset(M_ESCFROM, mci->mci_mailer->m_flags))
+ {
+ if (sm_io_putc(mci->mci_out, SM_TIME_DEFAULT,
+ '>') == SM_IO_EOF)
+ dead = true;
+ else
+ {
+ /* record progress for DATA timeout */
+ DataProgress = true;
+ }
+ if (TrafficLogFile != NULL)
+ (void) sm_io_putc(TrafficLogFile,
+ SM_TIME_DEFAULT,
+ '>');
+ }
+ if (dead)
+ break;
+
+ while (l < q)
+ {
+ if (sm_io_putc(mci->mci_out, SM_TIME_DEFAULT,
+ (unsigned char) *l++) == SM_IO_EOF)
+ {
+ dead = true;
+ break;
+ }
+ else
+ {
+ /* record progress for DATA timeout */
+ DataProgress = true;
+ }
+ }
+ if (dead)
+ break;
+
+ if (sm_io_putc(mci->mci_out, SM_TIME_DEFAULT, '!') ==
+ SM_IO_EOF ||
+ sm_io_fputs(mci->mci_out, SM_TIME_DEFAULT,
+ mci->mci_mailer->m_eol) ==
+ SM_IO_EOF ||
+ sm_io_putc(mci->mci_out, SM_TIME_DEFAULT, ' ') ==
+ SM_IO_EOF)
+ {
+ dead = true;
+ break;
+ }
+ else
+ {
+ /* record progress for DATA timeout */
+ DataProgress = true;
+ }
+ if (TrafficLogFile != NULL)
+ {
+ for (l = l_base; l < q; l++)
+ (void) sm_io_putc(TrafficLogFile,
+ SM_TIME_DEFAULT,
+ (unsigned char)*l);
+ (void) sm_io_fprintf(TrafficLogFile,
+ SM_TIME_DEFAULT,
+ "!\n%05d >>> ",
+ (int) CurrentPid);
+ }
+ slop = 1;
+ }
+
+ if (dead)
+ break;
+
+ /* output last part */
+ if (l[0] == '.' && slop == 0 &&
+ bitnset(M_XDOT, mci->mci_mailer->m_flags))
+ {
+ if (sm_io_putc(mci->mci_out, SM_TIME_DEFAULT, '.') ==
+ SM_IO_EOF)
+ break;
+ else
+ {
+ /* record progress for DATA timeout */
+ DataProgress = true;
+ }
+ if (TrafficLogFile != NULL)
+ (void) sm_io_putc(TrafficLogFile,
+ SM_TIME_DEFAULT, '.');
+ }
+ else if (l[0] == 'F' && slop == 0 &&
+ bitset(PXLF_MAPFROM, pxflags) &&
+ strncmp(l, "From ", 5) == 0 &&
+ bitnset(M_ESCFROM, mci->mci_mailer->m_flags))
+ {
+ if (sm_io_putc(mci->mci_out, SM_TIME_DEFAULT, '>') ==
+ SM_IO_EOF)
+ break;
+ else
+ {
+ /* record progress for DATA timeout */
+ DataProgress = true;
+ }
+ if (TrafficLogFile != NULL)
+ (void) sm_io_putc(TrafficLogFile,
+ SM_TIME_DEFAULT, '>');
+ }
+ for ( ; l < p; ++l)
+ {
+ if (TrafficLogFile != NULL)
+ (void) sm_io_putc(TrafficLogFile,
+ SM_TIME_DEFAULT,
+ (unsigned char)*l);
+ if (sm_io_putc(mci->mci_out, SM_TIME_DEFAULT,
+ (unsigned char) *l) == SM_IO_EOF)
+ {
+ dead = true;
+ break;
+ }
+ else
+ {
+ /* record progress for DATA timeout */
+ DataProgress = true;
+ }
+ }
+ if (dead)
+ break;
+
+ if (TrafficLogFile != NULL)
+ (void) sm_io_putc(TrafficLogFile, SM_TIME_DEFAULT,
+ '\n');
+ if ((!bitset(PXLF_NOADDEOL, pxflags) || !noeol) &&
+ sm_io_fputs(mci->mci_out, SM_TIME_DEFAULT,
+ mci->mci_mailer->m_eol) == SM_IO_EOF)
+ break;
+ else
+ {
+ /* record progress for DATA timeout */
+ DataProgress = true;
+ }
+ if (l < end && *l == '\n')
+ {
+ if (*++l != ' ' && *l != '\t' && *l != '\0' &&
+ bitset(PXLF_HEADER, pxflags))
+ {
+ if (sm_io_putc(mci->mci_out, SM_TIME_DEFAULT,
+ ' ') == SM_IO_EOF)
+ break;
+ else
+ {
+ /* record progress for DATA timeout */
+ DataProgress = true;
+ }
+
+ if (TrafficLogFile != NULL)
+ (void) sm_io_putc(TrafficLogFile,
+ SM_TIME_DEFAULT, ' ');
+ }
+ }
+
+ /* record progress for DATA timeout */
+ DataProgress = true;
+ } while (l < end);
+}
+/*
+** XUNLINK -- unlink a file, doing logging as appropriate.
+**
+** Parameters:
+** f -- name of file to unlink.
+**
+** Returns:
+** return value of unlink()
+**
+** Side Effects:
+** f is unlinked.
+*/
+
+int
+xunlink(f)
+ char *f;
+{
+ register int i;
+ int save_errno;
+
+ if (LogLevel > 98)
+ sm_syslog(LOG_DEBUG, CurEnv->e_id, "unlink %s", f);
+
+ i = unlink(f);
+ save_errno = errno;
+ if (i < 0 && LogLevel > 97)
+ sm_syslog(LOG_DEBUG, CurEnv->e_id, "%s: unlink-fail %d",
+ f, errno);
+ if (i >= 0)
+ SYNC_DIR(f, false);
+ errno = save_errno;
+ return i;
+}
+/*
+** SFGETS -- "safe" fgets -- times out and ignores random interrupts.
+**
+** Parameters:
+** buf -- place to put the input line.
+** siz -- size of buf.
+** fp -- file to read from.
+** timeout -- the timeout before error occurs.
+** during -- what we are trying to read (for error messages).
+**
+** Returns:
+** NULL on error (including timeout). This may also leave
+** buf containing a null string.
+** buf otherwise.
+*/
+
+
+char *
+sfgets(buf, siz, fp, timeout, during)
+ char *buf;
+ int siz;
+ SM_FILE_T *fp;
+ time_t timeout;
+ char *during;
+{
+ register char *p;
+ int save_errno;
+ int io_timeout;
+
+ SM_REQUIRE(siz > 0);
+ SM_REQUIRE(buf != NULL);
+
+ if (fp == NULL)
+ {
+ buf[0] = '\0';
+ errno = EBADF;
+ return NULL;
+ }
+
+ /* try to read */
+ p = NULL;
+ errno = 0;
+
+ /* convert the timeout to sm_io notation */
+ io_timeout = (timeout <= 0) ? SM_TIME_DEFAULT : timeout * 1000;
+ while (!sm_io_eof(fp) && !sm_io_error(fp))
+ {
+ errno = 0;
+ p = sm_io_fgets(fp, io_timeout, buf, siz);
+ if (p == NULL && errno == EAGAIN)
+ {
+ /* The sm_io_fgets() call timedout */
+ if (LogLevel > 1)
+ sm_syslog(LOG_NOTICE, CurEnv->e_id,
+ "timeout waiting for input from %.100s during %s",
+ CURHOSTNAME,
+ during);
+ buf[0] = '\0';
+#if XDEBUG
+ checkfd012(during);
+#endif /* XDEBUG */
+ if (TrafficLogFile != NULL)
+ (void) sm_io_fprintf(TrafficLogFile,
+ SM_TIME_DEFAULT,
+ "%05d <<< [TIMEOUT]\n",
+ (int) CurrentPid);
+ errno = ETIMEDOUT;
+ return NULL;
+ }
+ if (p != NULL || errno != EINTR)
+ break;
+ (void) sm_io_clearerr(fp);
+ }
+ save_errno = errno;
+
+ /* clean up the books and exit */
+ LineNumber++;
+ if (p == NULL)
+ {
+ buf[0] = '\0';
+ if (TrafficLogFile != NULL)
+ (void) sm_io_fprintf(TrafficLogFile, SM_TIME_DEFAULT,
+ "%05d <<< [EOF]\n",
+ (int) CurrentPid);
+ errno = save_errno;
+ return NULL;
+ }
+ if (TrafficLogFile != NULL)
+ (void) sm_io_fprintf(TrafficLogFile, SM_TIME_DEFAULT,
+ "%05d <<< %s", (int) CurrentPid, buf);
+ if (SevenBitInput)
+ {
+ for (p = buf; *p != '\0'; p++)
+ *p &= ~0200;
+ }
+ else if (!HasEightBits)
+ {
+ for (p = buf; *p != '\0'; p++)
+ {
+ if (bitset(0200, *p))
+ {
+ HasEightBits = true;
+ break;
+ }
+ }
+ }
+ return buf;
+}
+/*
+** FGETFOLDED -- like fgets, but knows about folded lines.
+**
+** Parameters:
+** buf -- place to put result.
+** n -- bytes available.
+** f -- file to read from.
+**
+** Returns:
+** input line(s) on success, NULL on error or SM_IO_EOF.
+** This will normally be buf -- unless the line is too
+** long, when it will be sm_malloc_x()ed.
+**
+** Side Effects:
+** buf gets lines from f, with continuation lines (lines
+** with leading white space) appended. CRLF's are mapped
+** into single newlines. Any trailing NL is stripped.
+*/
+
+char *
+fgetfolded(buf, n, f)
+ char *buf;
+ register int n;
+ SM_FILE_T *f;
+{
+ register char *p = buf;
+ char *bp = buf;
+ register int i;
+
+ SM_REQUIRE(n > 0);
+ SM_REQUIRE(buf != NULL);
+ if (f == NULL)
+ {
+ buf[0] = '\0';
+ errno = EBADF;
+ return NULL;
+ }
+
+ n--;
+ while ((i = sm_io_getc(f, SM_TIME_DEFAULT)) != SM_IO_EOF)
+ {
+ if (i == '\r')
+ {
+ i = sm_io_getc(f, SM_TIME_DEFAULT);
+ if (i != '\n')
+ {
+ if (i != SM_IO_EOF)
+ (void) sm_io_ungetc(f, SM_TIME_DEFAULT,
+ i);
+ i = '\r';
+ }
+ }
+ if (--n <= 0)
+ {
+ /* allocate new space */
+ char *nbp;
+ int nn;
+
+ nn = (p - bp);
+ if (nn < MEMCHUNKSIZE)
+ nn *= 2;
+ else
+ nn += MEMCHUNKSIZE;
+ nbp = sm_malloc_x(nn);
+ memmove(nbp, bp, p - bp);
+ p = &nbp[p - bp];
+ if (bp != buf)
+ sm_free(bp);
+ bp = nbp;
+ n = nn - (p - bp);
+ }
+ *p++ = i;
+ if (i == '\n')
+ {
+ LineNumber++;
+ i = sm_io_getc(f, SM_TIME_DEFAULT);
+ if (i != SM_IO_EOF)
+ (void) sm_io_ungetc(f, SM_TIME_DEFAULT, i);
+ if (i != ' ' && i != '\t')
+ break;
+ }
+ }
+ if (p == bp)
+ return NULL;
+ if (p[-1] == '\n')
+ p--;
+ *p = '\0';
+ return bp;
+}
+/*
+** CURTIME -- return current time.
+**
+** Parameters:
+** none.
+**
+** Returns:
+** the current time.
+*/
+
+time_t
+curtime()
+{
+ auto time_t t;
+
+ (void) time(&t);
+ return t;
+}
+/*
+** ATOBOOL -- convert a string representation to boolean.
+**
+** Defaults to false
+**
+** Parameters:
+** s -- string to convert. Takes "tTyY", empty, and NULL as true,
+** others as false.
+**
+** Returns:
+** A boolean representation of the string.
+*/
+
+bool
+atobool(s)
+ register char *s;
+{
+ if (s == NULL || *s == '\0' || strchr("tTyY", *s) != NULL)
+ return true;
+ return false;
+}
+/*
+** ATOOCT -- convert a string representation to octal.
+**
+** Parameters:
+** s -- string to convert.
+**
+** Returns:
+** An integer representing the string interpreted as an
+** octal number.
+*/
+
+int
+atooct(s)
+ register char *s;
+{
+ register int i = 0;
+
+ while (*s >= '0' && *s <= '7')
+ i = (i << 3) | (*s++ - '0');
+ return i;
+}
+/*
+** BITINTERSECT -- tell if two bitmaps intersect
+**
+** Parameters:
+** a, b -- the bitmaps in question
+**
+** Returns:
+** true if they have a non-null intersection
+** false otherwise
+*/
+
+bool
+bitintersect(a, b)
+ BITMAP256 a;
+ BITMAP256 b;
+{
+ int i;
+
+ for (i = BITMAPBYTES / sizeof (int); --i >= 0; )
+ {
+ if ((a[i] & b[i]) != 0)
+ return true;
+ }
+ return false;
+}
+/*
+** BITZEROP -- tell if a bitmap is all zero
+**
+** Parameters:
+** map -- the bit map to check
+**
+** Returns:
+** true if map is all zero.
+** false if there are any bits set in map.
+*/
+
+bool
+bitzerop(map)
+ BITMAP256 map;
+{
+ int i;
+
+ for (i = BITMAPBYTES / sizeof (int); --i >= 0; )
+ {
+ if (map[i] != 0)
+ return false;
+ }
+ return true;
+}
+/*
+** STRCONTAINEDIN -- tell if one string is contained in another
+**
+** Parameters:
+** icase -- ignore case?
+** a -- possible substring.
+** b -- possible superstring.
+**
+** Returns:
+** true if a is contained in b (case insensitive).
+** false otherwise.
+*/
+
+bool
+strcontainedin(icase, a, b)
+ bool icase;
+ register char *a;
+ register char *b;
+{
+ int la;
+ int lb;
+ int c;
+
+ la = strlen(a);
+ lb = strlen(b);
+ c = *a;
+ if (icase && isascii(c) && isupper(c))
+ c = tolower(c);
+ for (; lb-- >= la; b++)
+ {
+ if (icase)
+ {
+ if (*b != c &&
+ isascii(*b) && isupper(*b) && tolower(*b) != c)
+ continue;
+ if (sm_strncasecmp(a, b, la) == 0)
+ return true;
+ }
+ else
+ {
+ if (*b != c)
+ continue;
+ if (strncmp(a, b, la) == 0)
+ return true;
+ }
+ }
+ return false;
+}
+/*
+** CHECKFD012 -- check low numbered file descriptors
+**
+** File descriptors 0, 1, and 2 should be open at all times.
+** This routine verifies that, and fixes it if not true.
+**
+** Parameters:
+** where -- a tag printed if the assertion failed
+**
+** Returns:
+** none
+*/
+
+void
+checkfd012(where)
+ char *where;
+{
+#if XDEBUG
+ register int i;
+
+ for (i = 0; i < 3; i++)
+ fill_fd(i, where);
+#endif /* XDEBUG */
+}
+/*
+** CHECKFDOPEN -- make sure file descriptor is open -- for extended debugging
+**
+** Parameters:
+** fd -- file descriptor to check.
+** where -- tag to print on failure.
+**
+** Returns:
+** none.
+*/
+
+void
+checkfdopen(fd, where)
+ int fd;
+ char *where;
+{
+#if XDEBUG
+ struct stat st;
+
+ if (fstat(fd, &st) < 0 && errno == EBADF)
+ {
+ syserr("checkfdopen(%d): %s not open as expected!", fd, where);
+ printopenfds(true);
+ }
+#endif /* XDEBUG */
+}
+/*
+** CHECKFDS -- check for new or missing file descriptors
+**
+** Parameters:
+** where -- tag for printing. If null, take a base line.
+**
+** Returns:
+** none
+**
+** Side Effects:
+** If where is set, shows changes since the last call.
+*/
+
+void
+checkfds(where)
+ char *where;
+{
+ int maxfd;
+ register int fd;
+ bool printhdr = true;
+ int save_errno = errno;
+ static BITMAP256 baseline;
+ extern int DtableSize;
+
+ if (DtableSize > BITMAPBITS)
+ maxfd = BITMAPBITS;
+ else
+ maxfd = DtableSize;
+ if (where == NULL)
+ clrbitmap(baseline);
+
+ for (fd = 0; fd < maxfd; fd++)
+ {
+ struct stat stbuf;
+
+ if (fstat(fd, &stbuf) < 0 && errno != EOPNOTSUPP)
+ {
+ if (!bitnset(fd, baseline))
+ continue;
+ clrbitn(fd, baseline);
+ }
+ else if (!bitnset(fd, baseline))
+ setbitn(fd, baseline);
+ else
+ continue;
+
+ /* file state has changed */
+ if (where == NULL)
+ continue;
+ if (printhdr)
+ {
+ sm_syslog(LOG_DEBUG, CurEnv->e_id,
+ "%s: changed fds:",
+ where);
+ printhdr = false;
+ }
+ dumpfd(fd, true, true);
+ }
+ errno = save_errno;
+}
+/*
+** PRINTOPENFDS -- print the open file descriptors (for debugging)
+**
+** Parameters:
+** logit -- if set, send output to syslog; otherwise
+** print for debugging.
+**
+** Returns:
+** none.
+*/
+
+#if NETINET || NETINET6
+# include <arpa/inet.h>
+#endif /* NETINET || NETINET6 */
+
+void
+printopenfds(logit)
+ bool logit;
+{
+ register int fd;
+ extern int DtableSize;
+
+ for (fd = 0; fd < DtableSize; fd++)
+ dumpfd(fd, false, logit);
+}
+/*
+** DUMPFD -- dump a file descriptor
+**
+** Parameters:
+** fd -- the file descriptor to dump.
+** printclosed -- if set, print a notification even if
+** it is closed; otherwise print nothing.
+** logit -- if set, use sm_syslog instead of sm_dprintf()
+**
+** Returns:
+** none.
+*/
+
+void
+dumpfd(fd, printclosed, logit)
+ int fd;
+ bool printclosed;
+ bool logit;
+{
+ register char *p;
+ char *hp;
+#ifdef S_IFSOCK
+ SOCKADDR sa;
+#endif /* S_IFSOCK */
+ auto SOCKADDR_LEN_T slen;
+ int i;
+#if STAT64 > 0
+ struct stat64 st;
+#else /* STAT64 > 0 */
+ struct stat st;
+#endif /* STAT64 > 0 */
+ char buf[200];
+
+ p = buf;
+ (void) sm_snprintf(p, SPACELEFT(buf, p), "%3d: ", fd);
+ p += strlen(p);
+
+ if (
+#if STAT64 > 0
+ fstat64(fd, &st)
+#else /* STAT64 > 0 */
+ fstat(fd, &st)
+#endif /* STAT64 > 0 */
+ < 0)
+ {
+ if (errno != EBADF)
+ {
+ (void) sm_snprintf(p, SPACELEFT(buf, p),
+ "CANNOT STAT (%s)",
+ sm_errstring(errno));
+ goto printit;
+ }
+ else if (printclosed)
+ {
+ (void) sm_snprintf(p, SPACELEFT(buf, p), "CLOSED");
+ goto printit;
+ }
+ return;
+ }
+
+ i = fcntl(fd, F_GETFL, 0);
+ if (i != -1)
+ {
+ (void) sm_snprintf(p, SPACELEFT(buf, p), "fl=0x%x, ", i);
+ p += strlen(p);
+ }
+
+ (void) sm_snprintf(p, SPACELEFT(buf, p), "mode=%o: ",
+ (int) st.st_mode);
+ p += strlen(p);
+ switch (st.st_mode & S_IFMT)
+ {
+#ifdef S_IFSOCK
+ case S_IFSOCK:
+ (void) sm_snprintf(p, SPACELEFT(buf, p), "SOCK ");
+ p += strlen(p);
+ memset(&sa, '\0', sizeof sa);
+ slen = sizeof sa;
+ if (getsockname(fd, &sa.sa, &slen) < 0)
+ (void) sm_snprintf(p, SPACELEFT(buf, p), "(%s)",
+ sm_errstring(errno));
+ else
+ {
+ hp = hostnamebyanyaddr(&sa);
+ if (hp == NULL)
+ {
+ /* EMPTY */
+ /* do nothing */
+ }
+# if NETINET
+ else if (sa.sa.sa_family == AF_INET)
+ (void) sm_snprintf(p, SPACELEFT(buf, p),
+ "%s/%d", hp, ntohs(sa.sin.sin_port));
+# endif /* NETINET */
+# if NETINET6
+ else if (sa.sa.sa_family == AF_INET6)
+ (void) sm_snprintf(p, SPACELEFT(buf, p),
+ "%s/%d", hp, ntohs(sa.sin6.sin6_port));
+# endif /* NETINET6 */
+ else
+ (void) sm_snprintf(p, SPACELEFT(buf, p),
+ "%s", hp);
+ }
+ p += strlen(p);
+ (void) sm_snprintf(p, SPACELEFT(buf, p), "->");
+ p += strlen(p);
+ slen = sizeof sa;
+ if (getpeername(fd, &sa.sa, &slen) < 0)
+ (void) sm_snprintf(p, SPACELEFT(buf, p), "(%s)",
+ sm_errstring(errno));
+ else
+ {
+ hp = hostnamebyanyaddr(&sa);
+ if (hp == NULL)
+ {
+ /* EMPTY */
+ /* do nothing */
+ }
+# if NETINET
+ else if (sa.sa.sa_family == AF_INET)
+ (void) sm_snprintf(p, SPACELEFT(buf, p),
+ "%s/%d", hp, ntohs(sa.sin.sin_port));
+# endif /* NETINET */
+# if NETINET6
+ else if (sa.sa.sa_family == AF_INET6)
+ (void) sm_snprintf(p, SPACELEFT(buf, p),
+ "%s/%d", hp, ntohs(sa.sin6.sin6_port));
+# endif /* NETINET6 */
+ else
+ (void) sm_snprintf(p, SPACELEFT(buf, p),
+ "%s", hp);
+ }
+ break;
+#endif /* S_IFSOCK */
+
+ case S_IFCHR:
+ (void) sm_snprintf(p, SPACELEFT(buf, p), "CHR: ");
+ p += strlen(p);
+ goto defprint;
+
+#ifdef S_IFBLK
+ case S_IFBLK:
+ (void) sm_snprintf(p, SPACELEFT(buf, p), "BLK: ");
+ p += strlen(p);
+ goto defprint;
+#endif /* S_IFBLK */
+
+#if defined(S_IFIFO) && (!defined(S_IFSOCK) || S_IFIFO != S_IFSOCK)
+ case S_IFIFO:
+ (void) sm_snprintf(p, SPACELEFT(buf, p), "FIFO: ");
+ p += strlen(p);
+ goto defprint;
+#endif /* defined(S_IFIFO) && (!defined(S_IFSOCK) || S_IFIFO != S_IFSOCK) */
+
+#ifdef S_IFDIR
+ case S_IFDIR:
+ (void) sm_snprintf(p, SPACELEFT(buf, p), "DIR: ");
+ p += strlen(p);
+ goto defprint;
+#endif /* S_IFDIR */
+
+#ifdef S_IFLNK
+ case S_IFLNK:
+ (void) sm_snprintf(p, SPACELEFT(buf, p), "LNK: ");
+ p += strlen(p);
+ goto defprint;
+#endif /* S_IFLNK */
+
+ default:
+defprint:
+ (void) sm_snprintf(p, SPACELEFT(buf, p),
+ "dev=%d/%d, ino=%llu, nlink=%d, u/gid=%d/%d, ",
+ major(st.st_dev), minor(st.st_dev),
+ (ULONGLONG_T) st.st_ino,
+ (int) st.st_nlink, (int) st.st_uid,
+ (int) st.st_gid);
+ p += strlen(p);
+ (void) sm_snprintf(p, SPACELEFT(buf, p), "size=%llu",
+ (ULONGLONG_T) st.st_size);
+ break;
+ }
+
+printit:
+ if (logit)
+ sm_syslog(LOG_DEBUG, CurEnv ? CurEnv->e_id : NULL,
+ "%.800s", buf);
+ else
+ sm_dprintf("%s\n", buf);
+}
+/*
+** SHORTEN_HOSTNAME -- strip local domain information off of hostname.
+**
+** Parameters:
+** host -- the host to shorten (stripped in place).
+**
+** Returns:
+** place where string was truncated, NULL if not truncated.
+*/
+
+char *
+shorten_hostname(host)
+ char host[];
+{
+ register char *p;
+ char *mydom;
+ int i;
+ bool canon = false;
+
+ /* strip off final dot */
+ i = strlen(host);
+ p = &host[(i == 0) ? 0 : i - 1];
+ if (*p == '.')
+ {
+ *p = '\0';
+ canon = true;
+ }
+
+ /* see if there is any domain at all -- if not, we are done */
+ p = strchr(host, '.');
+ if (p == NULL)
+ return NULL;
+
+ /* yes, we have a domain -- see if it looks like us */
+ mydom = macvalue('m', CurEnv);
+ if (mydom == NULL)
+ mydom = "";
+ i = strlen(++p);
+ if ((canon ? sm_strcasecmp(p, mydom)
+ : sm_strncasecmp(p, mydom, i)) == 0 &&
+ (mydom[i] == '.' || mydom[i] == '\0'))
+ {
+ *--p = '\0';
+ return p;
+ }
+ return NULL;
+}
+/*
+** PROG_OPEN -- open a program for reading
+**
+** Parameters:
+** argv -- the argument list.
+** pfd -- pointer to a place to store the file descriptor.
+** e -- the current envelope.
+**
+** Returns:
+** pid of the process -- -1 if it failed.
+*/
+
+pid_t
+prog_open(argv, pfd, e)
+ char **argv;
+ int *pfd;
+ ENVELOPE *e;
+{
+ pid_t pid;
+ int save_errno;
+ int sff;
+ int ret;
+ int fdv[2];
+ char *p, *q;
+ char buf[MAXPATHLEN];
+ extern int DtableSize;
+
+ if (pipe(fdv) < 0)
+ {
+ syserr("%s: cannot create pipe for stdout", argv[0]);
+ return -1;
+ }
+ pid = fork();
+ if (pid < 0)
+ {
+ syserr("%s: cannot fork", argv[0]);
+ (void) close(fdv[0]);
+ (void) close(fdv[1]);
+ return -1;
+ }
+ if (pid > 0)
+ {
+ /* parent */
+ (void) close(fdv[1]);
+ *pfd = fdv[0];
+ return pid;
+ }
+
+ /* Reset global flags */
+ RestartRequest = NULL;
+ RestartWorkGroup = false;
+ ShutdownRequest = NULL;
+ PendingSignal = 0;
+ CurrentPid = getpid();
+
+ /*
+ ** Initialize exception stack and default exception
+ ** handler for child process.
+ */
+
+ sm_exc_newthread(fatal_error);
+
+ /* child -- close stdin */
+ (void) close(0);
+
+ /* stdout goes back to parent */
+ (void) close(fdv[0]);
+ if (dup2(fdv[1], 1) < 0)
+ {
+ syserr("%s: cannot dup2 for stdout", argv[0]);
+ _exit(EX_OSERR);
+ }
+ (void) close(fdv[1]);
+
+ /* stderr goes to transcript if available */
+ if (e->e_xfp != NULL)
+ {
+ int xfd;
+
+ xfd = sm_io_getinfo(e->e_xfp, SM_IO_WHAT_FD, NULL);
+ if (xfd >= 0 && dup2(xfd, 2) < 0)
+ {
+ syserr("%s: cannot dup2 for stderr", argv[0]);
+ _exit(EX_OSERR);
+ }
+ }
+
+ /* this process has no right to the queue file */
+ if (e->e_lockfp != NULL)
+ (void) close(sm_io_getinfo(e->e_lockfp, SM_IO_WHAT_FD, NULL));
+
+ /* chroot to the program mailer directory, if defined */
+ if (ProgMailer != NULL && ProgMailer->m_rootdir != NULL)
+ {
+ expand(ProgMailer->m_rootdir, buf, sizeof buf, e);
+ if (chroot(buf) < 0)
+ {
+ syserr("prog_open: cannot chroot(%s)", buf);
+ exit(EX_TEMPFAIL);
+ }
+ if (chdir("/") < 0)
+ {
+ syserr("prog_open: cannot chdir(/)");
+ exit(EX_TEMPFAIL);
+ }
+ }
+
+ /* run as default user */
+ endpwent();
+ sm_mbdb_terminate();
+ if (setgid(DefGid) < 0 && geteuid() == 0)
+ {
+ syserr("prog_open: setgid(%ld) failed", (long) DefGid);
+ exit(EX_TEMPFAIL);
+ }
+ if (setuid(DefUid) < 0 && geteuid() == 0)
+ {
+ syserr("prog_open: setuid(%ld) failed", (long) DefUid);
+ exit(EX_TEMPFAIL);
+ }
+
+ /* run in some directory */
+ if (ProgMailer != NULL)
+ p = ProgMailer->m_execdir;
+ else
+ p = NULL;
+ for (; p != NULL; p = q)
+ {
+ q = strchr(p, ':');
+ if (q != NULL)
+ *q = '\0';
+ expand(p, buf, sizeof buf, e);
+ if (q != NULL)
+ *q++ = ':';
+ if (buf[0] != '\0' && chdir(buf) >= 0)
+ break;
+ }
+ if (p == NULL)
+ {
+ /* backup directories */
+ if (chdir("/tmp") < 0)
+ (void) chdir("/");
+ }
+
+ /* Check safety of program to be run */
+ sff = SFF_ROOTOK|SFF_EXECOK;
+ if (!bitnset(DBS_RUNWRITABLEPROGRAM, DontBlameSendmail))
+ sff |= SFF_NOGWFILES|SFF_NOWWFILES;
+ if (bitnset(DBS_RUNPROGRAMINUNSAFEDIRPATH, DontBlameSendmail))
+ sff |= SFF_NOPATHCHECK;
+ else
+ sff |= SFF_SAFEDIRPATH;
+ ret = safefile(argv[0], DefUid, DefGid, DefUser, sff, 0, NULL);
+ if (ret != 0)
+ sm_syslog(LOG_INFO, e->e_id,
+ "Warning: prog_open: program %s unsafe: %s",
+ argv[0], sm_errstring(ret));
+
+ /* arrange for all the files to be closed */
+ sm_close_on_exec(STDERR_FILENO + 1, DtableSize);
+
+ /* now exec the process */
+ (void) execve(argv[0], (ARGV_T) argv, (ARGV_T) UserEnviron);
+
+ /* woops! failed */
+ save_errno = errno;
+ syserr("%s: cannot exec", argv[0]);
+ if (transienterror(save_errno))
+ _exit(EX_OSERR);
+ _exit(EX_CONFIG);
+ return -1; /* avoid compiler warning on IRIX */
+}
+/*
+** GET_COLUMN -- look up a Column in a line buffer
+**
+** Parameters:
+** line -- the raw text line to search.
+** col -- the column number to fetch.
+** delim -- the delimiter between columns. If null,
+** use white space.
+** buf -- the output buffer.
+** buflen -- the length of buf.
+**
+** Returns:
+** buf if successful.
+** NULL otherwise.
+*/
+
+char *
+get_column(line, col, delim, buf, buflen)
+ char line[];
+ int col;
+ int delim;
+ char buf[];
+ int buflen;
+{
+ char *p;
+ char *begin, *end;
+ int i;
+ char delimbuf[4];
+
+ if ((char) delim == '\0')
+ (void) sm_strlcpy(delimbuf, "\n\t ", sizeof delimbuf);
+ else
+ {
+ delimbuf[0] = (char) delim;
+ delimbuf[1] = '\0';
+ }
+
+ p = line;
+ if (*p == '\0')
+ return NULL; /* line empty */
+ if (*p == (char) delim && col == 0)
+ return NULL; /* first column empty */
+
+ begin = line;
+
+ if (col == 0 && (char) delim == '\0')
+ {
+ while (*begin != '\0' && isascii(*begin) && isspace(*begin))
+ begin++;
+ }
+
+ for (i = 0; i < col; i++)
+ {
+ if ((begin = strpbrk(begin, delimbuf)) == NULL)
+ return NULL; /* no such column */
+ begin++;
+ if ((char) delim == '\0')
+ {
+ while (*begin != '\0' && isascii(*begin) && isspace(*begin))
+ begin++;
+ }
+ }
+
+ end = strpbrk(begin, delimbuf);
+ if (end == NULL)
+ i = strlen(begin);
+ else
+ i = end - begin;
+ if (i >= buflen)
+ i = buflen - 1;
+ (void) sm_strlcpy(buf, begin, i + 1);
+ return buf;
+}
+/*
+** CLEANSTRCPY -- copy string keeping out bogus characters
+**
+** Parameters:
+** t -- "to" string.
+** f -- "from" string.
+** l -- length of space available in "to" string.
+**
+** Returns:
+** none.
+*/
+
+void
+cleanstrcpy(t, f, l)
+ register char *t;
+ register char *f;
+ int l;
+{
+ /* check for newlines and log if necessary */
+ (void) denlstring(f, true, true);
+
+ if (l <= 0)
+ syserr("!cleanstrcpy: length == 0");
+
+ l--;
+ while (l > 0 && *f != '\0')
+ {
+ if (isascii(*f) &&
+ (isalnum(*f) || strchr("!#$%&'*+-./^_`{|}~", *f) != NULL))
+ {
+ l--;
+ *t++ = *f;
+ }
+ f++;
+ }
+ *t = '\0';
+}
+/*
+** DENLSTRING -- convert newlines in a string to spaces
+**
+** Parameters:
+** s -- the input string
+** strict -- if set, don't permit continuation lines.
+** logattacks -- if set, log attempted attacks.
+**
+** Returns:
+** A pointer to a version of the string with newlines
+** mapped to spaces. This should be copied.
+*/
+
+char *
+denlstring(s, strict, logattacks)
+ char *s;
+ bool strict;
+ bool logattacks;
+{
+ register char *p;
+ int l;
+ static char *bp = NULL;
+ static int bl = 0;
+
+ p = s;
+ while ((p = strchr(p, '\n')) != NULL)
+ if (strict || (*++p != ' ' && *p != '\t'))
+ break;
+ if (p == NULL)
+ return s;
+
+ l = strlen(s) + 1;
+ if (bl < l)
+ {
+ /* allocate more space */
+ char *nbp = sm_pmalloc_x(l);
+
+ if (bp != NULL)
+ sm_free(bp);
+ bp = nbp;
+ bl = l;
+ }
+ (void) sm_strlcpy(bp, s, l);
+ for (p = bp; (p = strchr(p, '\n')) != NULL; )
+ *p++ = ' ';
+
+ if (logattacks)
+ {
+ sm_syslog(LOG_NOTICE, CurEnv->e_id,
+ "POSSIBLE ATTACK from %.100s: newline in string \"%s\"",
+ RealHostName == NULL ? "[UNKNOWN]" : RealHostName,
+ shortenstring(bp, MAXSHORTSTR));
+ }
+
+ return bp;
+}
+
+/*
+** STRREPLNONPRT -- replace "unprintable" characters in a string with subst
+**
+** Parameters:
+** s -- string to manipulate (in place)
+** subst -- character to use as replacement
+**
+** Returns:
+** true iff string did not contain "unprintable" characters
+*/
+
+bool
+strreplnonprt(s, c)
+ char *s;
+ int c;
+{
+ bool ok;
+
+ ok = true;
+ if (s == NULL)
+ return ok;
+ while (*s != '\0')
+ {
+ if (!(isascii(*s) && isprint(*s)))
+ {
+ *s = c;
+ ok = false;
+ }
+ ++s;
+ }
+ return ok;
+}
+
+/*
+** STR2PRT -- convert "unprintable" characters in a string to \oct
+**
+** Parameters:
+** s -- string to convert
+**
+** Returns:
+** converted string.
+** This is a static local buffer, string must be copied
+** before this function is called again!
+*/
+
+char *
+str2prt(s)
+ char *s;
+{
+ int l;
+ char c, *h;
+ bool ok;
+ static int len = 0;
+ static char *buf = NULL;
+
+ if (s == NULL)
+ return NULL;
+ ok = true;
+ for (h = s, l = 1; *h != '\0'; h++, l++)
+ {
+ if (*h == '\\')
+ {
+ ++l;
+ ok = false;
+ }
+ else if (!(isascii(*h) && isprint(*h)))
+ {
+ l += 3;
+ ok = false;
+ }
+ }
+ if (ok)
+ return s;
+ if (l > len)
+ {
+ char *nbuf = sm_pmalloc_x(l);
+
+ if (buf != NULL)
+ sm_free(buf);
+ len = l;
+ buf = nbuf;
+ }
+ for (h = buf; *s != '\0' && l > 0; s++, l--)
+ {
+ c = *s;
+ if (isascii(c) && isprint(c) && c != '\\')
+ {
+ *h++ = c;
+ }
+ else
+ {
+ *h++ = '\\';
+ --l;
+ switch (c)
+ {
+ case '\\':
+ *h++ = '\\';
+ break;
+ case '\t':
+ *h++ = 't';
+ break;
+ case '\n':
+ *h++ = 'n';
+ break;
+ case '\r':
+ *h++ = 'r';
+ break;
+ default:
+ (void) sm_snprintf(h, l, "%03o",
+ (unsigned int)((unsigned char) c));
+
+ /*
+ ** XXX since l is unsigned this may
+ ** wrap around if the calculation is screwed
+ ** up...
+ */
+
+ l -= 2;
+ h += 3;
+ break;
+ }
+ }
+ }
+ *h = '\0';
+ buf[len - 1] = '\0';
+ return buf;
+}
+/*
+** PATH_IS_DIR -- check to see if file exists and is a directory.
+**
+** There are some additional checks for security violations in
+** here. This routine is intended to be used for the host status
+** support.
+**
+** Parameters:
+** pathname -- pathname to check for directory-ness.
+** createflag -- if set, create directory if needed.
+**
+** Returns:
+** true -- if the indicated pathname is a directory
+** false -- otherwise
+*/
+
+bool
+path_is_dir(pathname, createflag)
+ char *pathname;
+ bool createflag;
+{
+ struct stat statbuf;
+
+#if HASLSTAT
+ if (lstat(pathname, &statbuf) < 0)
+#else /* HASLSTAT */
+ if (stat(pathname, &statbuf) < 0)
+#endif /* HASLSTAT */
+ {
+ if (errno != ENOENT || !createflag)
+ return false;
+ if (mkdir(pathname, 0755) < 0)
+ return false;
+ return true;
+ }
+ if (!S_ISDIR(statbuf.st_mode))
+ {
+ errno = ENOTDIR;
+ return false;
+ }
+
+ /* security: don't allow writable directories */
+ if (bitset(S_IWGRP|S_IWOTH, statbuf.st_mode))
+ {
+ errno = EACCES;
+ return false;
+ }
+ return true;
+}
+/*
+** PROC_LIST_ADD -- add process id to list of our children
+**
+** Parameters:
+** pid -- pid to add to list.
+** task -- task of pid.
+** type -- type of process.
+** count -- number of processes.
+** other -- other information for this type.
+**
+** Returns:
+** none
+**
+** Side Effects:
+** May increase CurChildren. May grow ProcList.
+*/
+
+typedef struct procs PROCS_T;
+
+struct procs
+{
+ pid_t proc_pid;
+ char *proc_task;
+ int proc_type;
+ int proc_count;
+ int proc_other;
+ SOCKADDR proc_hostaddr;
+};
+
+static PROCS_T *volatile ProcListVec = NULL;
+static int ProcListSize = 0;
+
+void
+proc_list_add(pid, task, type, count, other, hostaddr)
+ pid_t pid;
+ char *task;
+ int type;
+ int count;
+ int other;
+ SOCKADDR *hostaddr;
+{
+ int i;
+
+ for (i = 0; i < ProcListSize; i++)
+ {
+ if (ProcListVec[i].proc_pid == NO_PID)
+ break;
+ }
+ if (i >= ProcListSize)
+ {
+ /* probe the existing vector to avoid growing infinitely */
+ proc_list_probe();
+
+ /* now scan again */
+ for (i = 0; i < ProcListSize; i++)
+ {
+ if (ProcListVec[i].proc_pid == NO_PID)
+ break;
+ }
+ }
+ if (i >= ProcListSize)
+ {
+ /* grow process list */
+ PROCS_T *npv;
+
+ SM_ASSERT(ProcListSize < INT_MAX - PROC_LIST_SEG);
+ npv = (PROCS_T *) sm_pmalloc_x((sizeof *npv) *
+ (ProcListSize + PROC_LIST_SEG));
+ if (ProcListSize > 0)
+ {
+ memmove(npv, ProcListVec,
+ ProcListSize * sizeof (PROCS_T));
+ sm_free(ProcListVec);
+ }
+
+ /* XXX just use memset() to initialize this part? */
+ for (i = ProcListSize; i < ProcListSize + PROC_LIST_SEG; i++)
+ {
+ npv[i].proc_pid = NO_PID;
+ npv[i].proc_task = NULL;
+ npv[i].proc_type = PROC_NONE;
+ }
+ i = ProcListSize;
+ ProcListSize += PROC_LIST_SEG;
+ ProcListVec = npv;
+ }
+ ProcListVec[i].proc_pid = pid;
+ PSTRSET(ProcListVec[i].proc_task, task);
+ ProcListVec[i].proc_type = type;
+ ProcListVec[i].proc_count = count;
+ ProcListVec[i].proc_other = other;
+ if (hostaddr != NULL)
+ ProcListVec[i].proc_hostaddr = *hostaddr;
+ else
+ memset(&ProcListVec[i].proc_hostaddr, 0,
+ sizeof(ProcListVec[i].proc_hostaddr));
+
+ /* if process adding itself, it's not a child */
+ if (pid != CurrentPid)
+ {
+ SM_ASSERT(CurChildren < INT_MAX);
+ CurChildren++;
+ }
+}
+/*
+** PROC_LIST_SET -- set pid task in process list
+**
+** Parameters:
+** pid -- pid to set
+** task -- task of pid
+**
+** Returns:
+** none.
+*/
+
+void
+proc_list_set(pid, task)
+ pid_t pid;
+ char *task;
+{
+ int i;
+
+ for (i = 0; i < ProcListSize; i++)
+ {
+ if (ProcListVec[i].proc_pid == pid)
+ {
+ PSTRSET(ProcListVec[i].proc_task, task);
+ break;
+ }
+ }
+}
+/*
+** PROC_LIST_DROP -- drop pid from process list
+**
+** Parameters:
+** pid -- pid to drop
+** st -- process status
+** other -- storage for proc_other (return).
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** May decrease CurChildren, CurRunners, or
+** set RestartRequest or ShutdownRequest.
+**
+** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+** DOING.
+*/
+
+void
+proc_list_drop(pid, st, other)
+ pid_t pid;
+ int st;
+ int *other;
+{
+ int i;
+ int type = PROC_NONE;
+
+ for (i = 0; i < ProcListSize; i++)
+ {
+ if (ProcListVec[i].proc_pid == pid)
+ {
+ ProcListVec[i].proc_pid = NO_PID;
+ type = ProcListVec[i].proc_type;
+ if (other != NULL)
+ *other = ProcListVec[i].proc_other;
+ break;
+ }
+ }
+ if (CurChildren > 0)
+ CurChildren--;
+
+
+ if (type == PROC_CONTROL && WIFEXITED(st))
+ {
+ /* if so, see if we need to restart or shutdown */
+ if (WEXITSTATUS(st) == EX_RESTART)
+ RestartRequest = "control socket";
+ else if (WEXITSTATUS(st) == EX_SHUTDOWN)
+ ShutdownRequest = "control socket";
+ }
+ else if (type == PROC_QUEUE_CHILD && !WIFSTOPPED(st) &&
+ ProcListVec[i].proc_other > -1)
+ {
+ /* restart this persistent runner */
+ mark_work_group_restart(ProcListVec[i].proc_other, st);
+ }
+ else if (type == PROC_QUEUE)
+ CurRunners -= ProcListVec[i].proc_count;
+}
+/*
+** PROC_LIST_CLEAR -- clear the process list
+**
+** Parameters:
+** none.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Sets CurChildren to zero.
+*/
+
+void
+proc_list_clear()
+{
+ int i;
+
+ /* start from 1 since 0 is the daemon itself */
+ for (i = 1; i < ProcListSize; i++)
+ ProcListVec[i].proc_pid = NO_PID;
+ CurChildren = 0;
+}
+/*
+** PROC_LIST_PROBE -- probe processes in the list to see if they still exist
+**
+** Parameters:
+** none
+**
+** Returns:
+** none
+**
+** Side Effects:
+** May decrease CurChildren.
+*/
+
+void
+proc_list_probe()
+{
+ int i;
+
+ /* start from 1 since 0 is the daemon itself */
+ for (i = 1; i < ProcListSize; i++)
+ {
+ if (ProcListVec[i].proc_pid == NO_PID)
+ continue;
+ if (kill(ProcListVec[i].proc_pid, 0) < 0)
+ {
+ if (LogLevel > 3)
+ sm_syslog(LOG_DEBUG, CurEnv->e_id,
+ "proc_list_probe: lost pid %d",
+ (int) ProcListVec[i].proc_pid);
+ ProcListVec[i].proc_pid = NO_PID;
+ SM_FREE_CLR(ProcListVec[i].proc_task);
+ CurChildren--;
+ }
+ }
+ if (CurChildren < 0)
+ CurChildren = 0;
+}
+
+/*
+** PROC_LIST_DISPLAY -- display the process list
+**
+** Parameters:
+** out -- output file pointer
+** prefix -- string to output in front of each line.
+**
+** Returns:
+** none.
+*/
+
+void
+proc_list_display(out, prefix)
+ SM_FILE_T *out;
+ char *prefix;
+{
+ int i;
+
+ for (i = 0; i < ProcListSize; i++)
+ {
+ if (ProcListVec[i].proc_pid == NO_PID)
+ continue;
+
+ (void) sm_io_fprintf(out, SM_TIME_DEFAULT, "%s%d %s%s\n",
+ prefix,
+ (int) ProcListVec[i].proc_pid,
+ ProcListVec[i].proc_task != NULL ?
+ ProcListVec[i].proc_task : "(unknown)",
+ (OpMode == MD_SMTP ||
+ OpMode == MD_DAEMON ||
+ OpMode == MD_ARPAFTP) ? "\r" : "");
+ }
+}
+
+/*
+** PROC_LIST_SIGNAL -- send a signal to a type of process in the list
+**
+** Parameters:
+** type -- type of process to signal
+** signal -- the type of signal to send
+**
+** Results:
+** none.
+**
+** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+** DOING.
+*/
+
+void
+proc_list_signal(type, signal)
+ int type;
+ int signal;
+{
+ int chldwasblocked;
+ int alrmwasblocked;
+ int i;
+ pid_t mypid = getpid();
+
+ /* block these signals so that we may signal cleanly */
+ chldwasblocked = sm_blocksignal(SIGCHLD);
+ alrmwasblocked = sm_blocksignal(SIGALRM);
+
+ /* Find all processes of type and send signal */
+ for (i = 0; i < ProcListSize; i++)
+ {
+ if (ProcListVec[i].proc_pid == NO_PID ||
+ ProcListVec[i].proc_pid == mypid)
+ continue;
+ if (ProcListVec[i].proc_type != type)
+ continue;
+ (void) kill(ProcListVec[i].proc_pid, signal);
+ }
+
+ /* restore the signals */
+ if (alrmwasblocked == 0)
+ (void) sm_releasesignal(SIGALRM);
+ if (chldwasblocked == 0)
+ (void) sm_releasesignal(SIGCHLD);
+}
+
+/*
+** COUNT_OPEN_CONNECTIONS
+**
+** Parameters:
+** hostaddr - ClientAddress
+**
+** Returns:
+** the number of open connections for this client
+**
+*/
+
+int
+count_open_connections(hostaddr)
+ SOCKADDR *hostaddr;
+{
+ int i, n;
+
+ if (hostaddr == NULL)
+ return 0;
+ n = 0;
+ for (i = 0; i < ProcListSize; i++)
+ {
+ if (ProcListVec[i].proc_pid == NO_PID)
+ continue;
+
+ if (hostaddr->sa.sa_family !=
+ ProcListVec[i].proc_hostaddr.sa.sa_family)
+ continue;
+#if NETINET
+ if (hostaddr->sa.sa_family == AF_INET &&
+ (hostaddr->sin.sin_addr.s_addr ==
+ ProcListVec[i].proc_hostaddr.sin.sin_addr.s_addr))
+ n++;
+#endif /* NETINET */
+#if NETINET6
+ if (hostaddr->sa.sa_family == AF_INET6 &&
+ IN6_ARE_ADDR_EQUAL(&(hostaddr->sin6.sin6_addr),
+ &(ProcListVec[i].proc_hostaddr.sin6.sin6_addr)))
+ n++;
+#endif /* NETINET6 */
+ }
+ return n;
+}
diff --git a/usr/src/cmd/sendmail/src/version.c b/usr/src/cmd/sendmail/src/version.c
new file mode 100644
index 0000000000..6c7631797b
--- /dev/null
+++ b/usr/src/cmd/sendmail/src/version.c
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 1998-2005 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sm/gen.h>
+
+SM_RCSID("@(#)$Id: version.c,v 8.145 2005/03/25 18:44:44 ca Exp $")
+
+char Version[] = "8.13.4+Sun";